diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..950d569 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,24 @@ +name: Node.js CI +on: + push: + branches: ['main'] + pull_request: +jobs: + build: + name: 'Node.js build: fmt, ci, lint, test' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 20.x + - latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm run fmt + - run: npm ci + - run: npm run lint + - run: npm run test diff --git a/.prettierrc.json b/.prettierrc.json index b413e8c..fb68343 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,10 +1,10 @@ { - "printWidth": 80, - "useTabs": true, - "tabWidth": 2, - "singleQuote": true, - "bracketSpacing": true, - "proseWrap": "always", - "semi": true, - "trailingComma": "all" + "printWidth": 80, + "useTabs": true, + "tabWidth": 2, + "singleQuote": true, + "bracketSpacing": true, + "proseWrap": "always", + "semi": true, + "trailingComma": "all" } diff --git a/COINJOIN-FLOW.md b/COINJOIN-FLOW.md deleted file mode 100644 index 342a52f..0000000 --- a/COINJOIN-FLOW.md +++ /dev/null @@ -1,73 +0,0 @@ -# Overview - -CoinJoin from a high level networking perspective - -# Flow - -For more info, see: -[Official DASH Core CoinJoin Specs](https://dashcore.readme.io/docs/core-guide-dash-features-privatesend) - -# Step 0: Setup a connection to a master node - -``` -let Network = require('./network.js'); -let COIN = require('./coin.js').COIN; -let MasterNodeConnection = require('./master-node-connection.js').MasterNodeConnection; -let mnConnection = new MasterNodeConnection({ - ip: '127.0.0.1', // assuming you have a master node there - port: '19998', - network: 'testnet', - ourIP: '10.2.1.10, - startBlockHeight: 87500, // TODO: will document how to get this - onStatusChange: stateChanged, - debugFunction: console.debug, -}); -``` - -## Step 1: client sends `dsa` - -```js -function stateChanged(obj) { - let self = obj.self; - switch (self.status) { - default: - console.info('unhandled status:', self.status); - break; - case 'NEEDS_AUTH': - case 'EXPECT_VERACK': - case 'EXPECT_HCDP': - case 'RESPOND_VERACK': - console.info('[ ... ] Handshake in progress'); - break; - case 'READY': - console.log('[+] Ready to start dealing with CoinJoin traffic...'); - /** - * This is where we send the `dsa` message - * \/ \/ \/ - * \/ \/ \/ - */ - self.client.write( - Network.packet.coinjoin.dsa({ - chosen_network: network, - denomination: COIN / 1000 + 1, - collateral: makeCollateralTx(), - }), - ); - break; - case 'EXPECT_DSQ': - console.info('[+] dsa sent'); - break; - } -} -``` - -# `dsa` TODO's: - -- Write a function that can - - [x] Create the correct transaction data bytes from an existing transaction - - [x] Can encode the bytes to be sent within a `dsa` message - -# `dssu` TODO's: - -- Write a function that can - - [ ] Parse all fields of a `dssu` message diff --git a/DENOMS.md b/DENOMS.md deleted file mode 100644 index 75ed690..0000000 --- a/DENOMS.md +++ /dev/null @@ -1,21 +0,0 @@ -# Overview - -How Dash Core (C++ codebase) creates denominated inputs. - -# Branch - -master - -# Entry point - -`bool CCoinJoinClientSession::CreateDenominated(CAmount nBalanceToDenominate)` - -- src/coinjoin/client.cpp:1559 - -# Coin selection - -`std::vector vecTally = mixingWallet.SelectCoinsGroupedByAddresses( true, // fSkipDenominated true, // fAnonymizable true, // fSkipUnconfirmed 400 // nMaxOupointsPerAddress );` - -# Look at - -- CTransactionBuilderOutput (coinjoin/util.cpp) diff --git a/DSS.md b/DSS.md deleted file mode 100644 index 075e425..0000000 --- a/DSS.md +++ /dev/null @@ -1,318 +0,0 @@ -# Overview - -Code review of Dash Core C++ code. Particularly, DSS flow. - -# Where we're concerned - -``` -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) CCoinJoinServer::CheckPool -- entries count 2 -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) CCoinJoinServer::CheckPool -- SIGNING -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) CCoinJoinServer::CommitFinalTransaction -- finalTransaction=CTransaction(hash=7efae2e243, ver=2, type=0, vin.size=6, vout.size=6, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(01c6baa2efc525ee41b1f6f57583293c1df8ae494565d223be55557d52c998fe, 0), scriptSig=483045022100eeb87227d8d3) - CTxIn(COutPoint(1914a266d3724a723294df7b36d8e530d4d3c28d9a92675ea48ceb7f5ed253fe, 0), scriptSig=483045022100942755e89e80) - CTxIn(COutPoint(1e19b1e944c4971213580bbcf96cda8916f38d692a40c782b1405182ec7f39ff, 0), scriptSig=483045022100a03c2f968a39) - CTxIn(COutPoint(2176c7b6b145d46db973bb736fa3635b39428931608ea76e92bf9b390ce7e8ff, 0), scriptSig=4730440220266a081aec8403) - CTxIn(COutPoint(2306d64528adf12109c34b0f740a494eefbdc8b026fc28f67e0da24915fd60ff, 0), scriptSig=47304402206aa6b8fc519157) - CTxIn(COutPoint(a5189b3aa2731ff5cfbcce4cd2228c68bc48908df94e6b5a2acd1e84b94954fe, 0), scriptSig=483045022100a2da22ef5494) - CTxOut(nValue=0.00100001, scriptPubKey=76a914164d9a977ebfe5dd7d805132) - CTxOut(nValue=0.00100001, scriptPubKey=76a91452f4609f8a34560d5f880393) - CTxOut(nValue=0.00100001, scriptPubKey=76a9147686f5fcf93bf3e15a13b9a7) - CTxOut(nValue=0.00100001, scriptPubKey=76a914781fe680d7d6ed65b7cd80a7) - CTxOut(nValue=0.00100001, scriptPubKey=76a91481026d6367d1ffbce44fe41d) - CTxOut(nValue=0.00100001, scriptPubKey=76a914d075a514565f8e022e5d7f11) -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) PrioritiseTransaction: 7efae2e2435138bf1ca30de8df49db47195812b52a1d0f8b031c338d612775fd feerate += 0.10 -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) AcceptToMemoryPoolWithTime: 7efae2e2435138bf1ca30de8df49db47195812b52a1d0f8b031c338d612775fd mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation) () -2023-07-07T04:02:11Z (mocktime: 2023-07-07T04:06:14Z) CCoinJoinServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid -``` - -# Entry `void CCoinJoinServer::CommitFinalTransaction()` in server.cpp - -server.cpp:327: `mempool.PrioritiseTransaction(hashTx, 0.1 * COIN);` - -Line 328 is the top of the call stack. It is where the DSS flow ultimately -fails. server.cpp:328: - -``` - if (!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, nullptr /* pfMissingInputs */, false /* bypass_limits */, DEFAULT_MAX_RAW_TX_FEE /* nAbsurdFee */)) { - LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n"); - WITH_LOCK(cs_coinjoin, SetNull()); - // not much we can do in this case, just notify clients - RelayCompletedTransaction(ERR_INVALID_TX); - return; - } - } -``` - -## PrioritiseTransaction - -- Updates ancestry data with fees - -## AcceptToMemoryPool - -Is just a wrapper function to `AcceptToMemoryPoolWithTime`. - -``` -bool AcceptToMemoryPool( - CTxMemPool& pool, // mempool - CValidationState &state, // validationState - const CTransactionRef &tx, // finalTransaction - bool* pfMissingInputs, - bool bypass_limits, - const CAmount nAbsurdFee, - bool test_accept) { - const CChainParams& chainparams = Params(); - return AcceptToMemoryPoolWithTime( - chainparams, // - pool, // mempool - state, // validationState - tx, // finalTransaction - pfMissingInputs, // nullptr when called - GetTime(), // - bypass_limits, // false when called - nAbsurdFee, // DEFAULT_MAX_RAW_TX_FEE - test_accept); -} -``` - -## validationState: - -``` -CValidationState validationState; -``` - -- is mostly used as a pass by reference parameter which will change by the - called functions it's passed to - -## finalTransaction - -``` -CTransactionRef finalTransaction = WITH_LOCK(cs_coinjoin, return MakeTransactionRef(finalMutableTransaction)); -uint256 hashTx = finalTransaction->GetHash(); -``` - -- locks the coinjoin mutex -- references the `finalMutableTransaction` -- fetches the hash... txid? - -## AcceptToMemoryPoolWithTime - -Description as per comment above function: - -``` -/** (try to) add transaction to memory pool with a specified acceptance time **/ -``` - -``` -static bool AcceptToMemoryPoolWithTime( -const CChainParams& chainparams, // when called: const CChainParams& chainparams = Params(); -CTxMemPool& pool, // mempool -CValidationState &state, // validationState -const CTransactionRef &tx, // finalTransaction -bool* pfMissingInputs, // nullptr when called -int64_t nAcceptTime, // GetTime() when called -bool bypass_limits, // false when called -const CAmount nAbsurdFee, // DEFAULT_MAX_RAW_TX_FEE -bool test_accept // not sure where this is set -) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -``` - -``` - boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); - const CTransaction& tx = *ptx; - const uint256 hash = tx.GetHash(); - AssertLockHeld(cs_main); - LOCK(pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool()) - if (pfMissingInputs) { - *pfMissingInputs = false; - } - -``` - -- pfMissingInputs is nullptr, so will not be set to false - -### Some things we can assume about AcceptToMemoryPoolWithTime - -- The transaction that gets prioritised is _not_ already in the mempool since - the following line does not cause the function to return early: - -``` - // is it already in the memory pool? - if (pool.exists(hash)) { -``` - -- It's not a coinbase tx -- It is a standard tx -- Serialized size is good: - -``` - // Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying - // 64-byte transactions. -``` - -- CheckTxInputs (for consensus) checks out - -``` -if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) { -``` - -- All inputs are standard: - -``` - // Check for non-standard pay-to-script-hash in inputs - if (fRequireStandard && !AreInputsStandard(tx, view)) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); -``` - -## CheckInputs - -``` - // Check against previous transactions - // This is done last to help prevent CPU exhaustion denial-of-service attacks. - PrecomputedTransactionData txdata; - if (!CheckInputs( - tx, // CTransactionRef (finalTransaction) - state, // validationState - view, // CCoinsViewCache view(&dummy); - true, // fScriptChecks - scriptVerifyFlags, // when called: STANDARD_SCRIPT_VERIFY_FLAGS - true, // cacheSigStore - false, // cacheFullScriptStore - txdata // PrecomputedTransactionData txdata; (pass by ref) - )) { - assert(IsTransactionReason(state.GetReason())); - return false; // state filled in by CheckInputs - } -``` - -### Function signature: - -``` -bool CheckInputs( -const CTransaction& tx, // finalTransaction -CValidationState &state, // validationState - const CCoinsViewCache &inputs, // CCoinsViewCache - bool fScriptChecks, // true when called - unsigned int flags, // STANDARD_SCRIPT_VERIFY_FLAGS - bool cacheSigStore, // true when called - bool cacheFullScriptStore, // false when called - PrecomputedTransactionData& txdata, // pass by ref. filled in by function - std::vector *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -``` - -## CheckInputs - -- Checks all inputs are valid - - no double spends - - scripts+sigs are good - - amounts are good -- if `pvChecks` not nullptr, - - script checks pushed onto it -- setting `cacheFullScriptStore` to false (we are) - - will remove elements from the corresponding cache - -### Point of failure - -``` - // Verify signature - CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); - if (pvChecks) { - pvChecks->push_back(CScriptCheck()); - check.swap(pvChecks->back()); - } else if (!check()) { <<<---- here -``` - -## CScriptCheck - -- The constructor is called at validation.cpp:1468 - -``` -CScriptCheck check( -coin.out, // inputs.AccessCoin(tx.vin[i].prevout) -tx, // -i, -flags, -cacheSigStore, -&txdata -); -``` - -- Constructor: - -``` - CScriptCheck( - const CTxOut& outIn, // m_tx_out - const CTransaction& txToIn, // ptxTo - unsigned int nInIn, // nIn - unsigned int nFlagsIn, // nFlags - bool cacheIn, // cacheStore - PrecomputedTransactionData* txdataIn // txdata - // error(SCRIPT_ERR_UNKNOWN_ERROR) - ) -``` - -- coin is the current vin's previous out: - -``` -for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const Coin& coin = inputs.AccessCoin(prevout); -``` - -``` -bool CScriptCheck::operator()() { - const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; - PrecomputedTransactionData txdata(*ptxTo); - return VerifyScript( - scriptSig, // CScript scriptSig vinput[outputIndex].scriptSig - m_tx_out.scriptPubKey, // - nFlags, // STANDARD_SCRIPT_VERIFY_FLAGS - CachingTransactionSignatureChecker(ptxTo, - nIn, - m_tx_out.nValue, - txdata, - cacheStore), - &error); -} -``` - -## VerifyScript - -``` -bool VerifyScript( -const CScript& scriptSig, // scriptSig - const CScript& scriptPubKey, //m_tx_out.scriptPubKey - unsigned int flags, // STANDARD_SCRIPT_VERIFY_FLAGS - const BaseSignatureChecker& checker, //CachingTransactionSignatureChecker - ScriptError* serror // pass by ref - ) -``` - -## EvalScript - -- `OP_CHECKSIGVERIFY` - - - `bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);` - - script/interpreter.cpp:998 - -- `OP_CHECKDATASIG/OP_CHECKDATASIGVERIFY` - -``` -bool fSuccess = false; -if (vchSig.size()) { - valtype vchHash(32); - CSHA256() - .Write(vchMessage.data(), vchMessage.size()) - .Finalize(vchHash.data()); - fSuccess = CPubKey(vchPubKey).Verify(uint256(vchHash), vchSig); -} -``` - -- script/interpreter.cpp:1039 - -- `OP_CHECKMULTISIG/OP_CHECKMULTISIGVERIFY` - -``` - -``` - -- script/interpreter.cpp:1136 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad18cf --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 AJ ONeal +Copyright (c) 2022 Dash Incubator + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NETWORK.md b/NETWORK.md deleted file mode 100644 index d4e614c..0000000 --- a/NETWORK.md +++ /dev/null @@ -1,148 +0,0 @@ -# What we know about the handshake so far - -The process seems to be: - -1. Client sends `version` to MN -2. MN responds with `version`, `verack`, `sendaddrv2` -3. Client sends `verack` to the MN's `version` message -4. MN responds with: - - `sendheaders` - - `sendcmpct` - - `senddsq` - - `ping` -5. The client should then send `dsa` coin join message - - Which includes valid collateral transaction -6. MN responds with `dssu` message Next steps are documented here: - [CoinJoin Messages](https://docs.dash.org/projects/core/en/stable/docs/reference/p2p-network-privatesend-messages.html#dssu) - -# How a `dsa` message looks (MN debug output): - -``` -2023-05-22T07:35:14Z DSACCEPT -- nDenom 16 (0.00100001) txCollateral CMutableTransaction(hash=c9c0ae6e9d, ver=3, type=0, vin.size=1, vout.size=0, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(3f56f3009ef3c54bd5123e70c5a2c9be1b90a3fddb3589ce2d1b9688b0926c9f, 0), scriptSig=000000006b483045022100f4) -``` - -These exact values may need to change, but this is proof that the MN accepted -our dsa even if the outpoint or signature script is not exactly correct. The -goal here was to properly encode a `dsa` message. The packet is correctly -encoded. - -# TODO: parse `dssu` message - -- [ ] Write a function that parses the `dssu` message - -# TODO: finalize `dsa` message logic - -The `dsa` packet creation functions are now encoding the denomination and -collateral transaction correctly. We now need to: - -- [ ] Verify the collateral transaction is properly encoded - (`SHA256(SHA256(txn))`) -- [ ] Verify the signature script is correct - -## Using dash core's `CCoinJoin::IsCollateralValid` - - - [ ] Add a `vout` to the `dsa` payload - - This seems to be *MANDATORY* - -- [ ] Create several collateral transactions for demo purposes and testing -- [ ] Unit test the integrity of all fields within a transaction - -# Notes on connection lifetimes - -From what I can tell, if you send your side of the handshake (the `version` -packet) and just let the program sit there without sending or receiving any data -on the socket, then the connection will stay open. There currently isn't any -logic to abort as it's very early in the development cycle. At some point the -library may implement an event listener interface to allow calling code to have -a bit more control over data flows. - -# Notes on Endian-ness - -In the documentation, you'll see that different messages and different fields -will contain phrases like "in _big endian order_". The library handles the -conversion to network byte order, so please _DO NOT_ convert your parameters to -any specific endian-ness. The library will take care of that for you. - -# How a successful `version` initiation looks like - -The first step in authenticating with a master node is by sending a `version` -packet. The master node should respond with `sendaddrv2` and a `verack`. On the -masternode side of things, the output should look like this: - -``` -2023-04-29T13:14:19Z Added connection to 127.0.0.1:50710 peer=9 -2023-04-29T13:14:19Z connection from 127.0.0.1:50710 accepted, sock=41, peer=9 -2023-04-29T13:14:19Z received: version (118 bytes) peer=9 -2023-04-29T13:14:19Z sending version (137 bytes) peer=9 -2023-04-29T13:14:19Z send version message: version 70227, blocks=326620, us=[::]:0, them=[::]:0, peer=9 -2023-04-29T13:14:19Z sending sendaddrv2 (0 bytes) peer=9 -2023-04-29T13:14:19Z sending verack (0 bytes) peer=9 -2023-04-29T13:14:19Z receive version message: : version 70227, blocks=89245, us=[0:1::]:19999, peer=9, peeraddr=127.0.0.1:50710 -2023-04-29T13:14:21Z socket closed for peer=9 -2023-04-29T13:14:21Z disconnecting peer=9 -2023-04-29T13:14:21Z ThreadSocketHandler -- removing node: peer=9 addr=127.0.0.1:50710 nRefCount=1 fInbound=1 m_masternode_connection=0 m_masternode_iqr_connection=0 -2023-04-29T13:14:21Z Cleared nodestate for peer=9 -``` - -If your masternode doesn't print this, then use the RPC to set `debug` to `all`. -`dash-cli debug all` - -Obviously, your exact `dash-cli` invocation will have more parameters. Fill them -in where needed. - -## How to tell if the `version` packet was parsed correctly - -The masternode should output something like this: - -``` -2023-04-29T13:14:19Z received: version (118 bytes) peer=9 -``` - -If you get something along the lines of `CDataStream::read(): end of data` Or -something about reading a packet that ended too early, then that usually means -you built the packet wrong. This could be a few things, but verify the integrity -of your packet. - -The following lines represent the second step in the masternode authentication -process. The masternode will parse your `version` packet, then respond with it's -own `version` packet. In addition to sending it's `version` packet, it will send -a `verack` and a `sendaddrv2` packet. - -If you specified a `mnauth_challenge` field when you built your `version` -packet, then you should see the response to that in the masternode's `verack` -packet. - -``` -2023-04-29T13:14:19Z sending version (137 bytes) peer=9 -2023-04-29T13:14:19Z send version message: version 70227, blocks=326620, us=[::]:0, them=[::]:0, peer=9 -2023-04-29T13:14:19Z sending sendaddrv2 (0 bytes) peer=9 -2023-04-29T13:14:19Z sending verack (0 bytes) peer=9 -2023-04-29T13:14:19Z receive version message: : version 70227, blocks=89245, us=[0:1::]:19999, peer=9, peeraddr=127.0.0.1:50710 -2023-04-29T13:14:21Z socket closed for peer=9 -``` - -# Where we're at: - -``` -2023-05-24T07:58:40Z DSACCEPT -- - nDenom 16 (0.00100001) ## CORRECT - txCollateral CMutableTransaction( - hash=30e5887f48, ## UNKNOWN(??) - ver=3, ## CORRECT - type=0, ## CORRECT - vin.size=1, ## CORRECT - vout.size=1, ## CORRECT - nLockTime=0, ## CORRECT - vExtraPayload.size=0 ## CORRECT - ) - CTxIn( - COutPoint( - 940effdb84072ff65c64c7d0446afcc3957569dbf058583ddd4f9586cf8a35b8, 0), ## [unknown,correct] - scriptSig=000000006b483045022100f4) - CTxOut( - nValue=0.00000000, ## CORRECT - scriptPubKey=6a) ## INVALID - -2023-05-24T07:58:40Z sending dssu (16 bytes) peer=19 -``` diff --git a/README.md b/README.md index dfe2356..898c1ed 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,137 @@ -# DashJoin.js +# [DashJoin.js](https://github.com/dashhive/DashJoin.js) -A non-custodial server-side SDK that allows users to mix using DASH's CoinJoin -feature. +A working reference implementation of Coin Join that can easily be ported to any +language. -# Current challenges: +# Table of Contents -- When sending `dsi` messages, we get: +- RegTest Setup +- Maturing Coins +- Install +- Running the Demo +- Troubleshooting +- Roadmap -``` -2023-06-23T06:14:01Z received: dsi (271 bytes) peer=598 -2023-06-23T06:14:01Z DSVIN -- txCollateral CTransaction(hash=15fba6d799, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(74c105f14d20e21f9561f169aa1ea33cb6aef77533a813507ab3ccd8d8afe42b, 0), scriptSig=) - CTxOut(nValue=0.00020000, scriptPubKey=76a9143e84bba18681f7fbb3f4bec4) - CTxOut(nValue=0.00030001, scriptPubKey=76a914088d9cc12f9f89ad0de85a44) -2023-06-23T06:14:01Z CCoinJoin::IsCollateralValid -- CTransaction(hash=15fba6d799, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(74c105f14d20e21f9561f169aa1ea33cb6aef77533a813507ab3ccd8d8afe42b, 0), scriptSig=) - CTxOut(nValue=0.00020000, scriptPubKey=76a9143e84bba18681f7fbb3f4bec4) - CTxOut(nValue=0.00030001, scriptPubKey=76a914088d9cc12f9f89ad0de85a44) -2023-06-23T06:14:01Z AcceptToMemoryPoolWithTime: 15fba6d799660930d593f712ebf134fd32146cb990de303598a509ec056b0d91 mandatory-script-verify-flag-failed (Operation not valid with the current stack size) () -2023-06-23T06:14:01Z CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool() -2023-06-23T06:14:01Z CCoinJoinServer::AddEntry -- ERROR: collateral not valid! -2023-06-23T06:14:01Z sending dssu (16 bytes) peer=598 -2023-06-23T06:14:01Z received: dsi (271 bytes) peer=599 -2023-06-23T06:14:01Z DSVIN -- txCollateral CTransaction(hash=78db06661c, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(781feadaaacd6d81b067a96230855d20499e228ea541d04eeec6bdff3f52a578, 0), scriptSig=) - CTxOut(nValue=0.00020000, scriptPubKey=76a9143e84bba18681f7fbb3f4bec4) - CTxOut(nValue=0.00030001, scriptPubKey=76a91413e72bb2fb4988193dad3233) -2023-06-23T06:14:01Z CCoinJoin::IsCollateralValid -- CTransaction(hash=78db06661c, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(781feadaaacd6d81b067a96230855d20499e228ea541d04eeec6bdff3f52a578, 0), scriptSig=) - CTxOut(nValue=0.00020000, scriptPubKey=76a9143e84bba18681f7fbb3f4bec4) - CTxOut(nValue=0.00030001, scriptPubKey=76a91413e72bb2fb4988193dad3233) -2023-06-23T06:14:01Z AcceptToMemoryPoolWithTime: 78db06661c77a82cbcbef81a5f98ba2e8058d725baf75c85771fc7c361252bce mandatory-script-verify-flag-failed (Operation not valid with the current stack size) () -2023-06-23T06:14:01Z CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool() -2023-06-23T06:14:01Z CCoinJoinServer::AddEntry -- ERROR: collateral not valid! -2023-06-23T06:14:01Z sending dssu (16 bytes) peer=599 -2023-06-23T06:14:01Z received: dsi (271 bytes) peer=596 -``` +# Prerequisites (RegTest) -# IN PROGRESS: +## RegTest -- [ ] Parse `dsi` messages - - TODO -- [ ] Send `dsi` messages with correctly format - - [ ] Send inputs - - [ ] Send collateral - - [ ] Send outputs +You will need: -# Architecture +- Build Tools +- Docker +- Dashmate +- RegTest -- `src/launcher.js` - - used for launching multiple instances of the `demo.js` script - - each instance of `demo.js` is given a dashboot instance name and username to - fetch +See . -## Seeding +## Maturing the Coinbase -- `bin/dboot` will have all that you'd need to create multiple wallets, - addresses, and transactions capable of being used for DashJoin.js development. - See the `--help` page. +Before running the demo, generate some blocks: -## Instances - -- An instance is just a folder that holds different wallets and other state that - is useful for testing DashJoin.js. See `bin/dboot --help` for more info. +```sh +./bin/dash-cli-generateblocks 20 +``` -# Using `bin/dboot` +You'll get some errors the first time you try to run the demo, generate even +more blocks: -## Create a bunch of wallets +```sh +./bin/dash-cli-generateblocks 105 +``` -This command will create an instance called `base` and create a bunch of -different wallets with randomly generated unique names. Each wallet with have -many addresses and UTXO's attached to it once dboot is done. +If you still get some errors on the 3rd run, just generate a few more for good +measure: ```sh -./bin/dboot --instance=base --create-wallets +./bin/dash-cli-generateblocks 10 ``` -## Generating DASH to a specific wallet - -The following command will generate DASH to the wallet named `ABCD`. +## Install ```sh -./bin/dboot --instance=base --generate-to=ABCD +git clone https://github.com/dashhive/DashJoin.js.git +pushd ./DashJoin.js/ + +npm ci --only=production ``` -## Unlocking all wallets +## How to Run the Demo + +1. Make sure the tests still pass: \ + (the tests replay known-good transactions from scratch) + + ```sh + npm run test + ``` + +2. Then run the demo in two different screens: + + ```sh + # in the FIRST screen + node ./demo.js 'foo' + ``` + + ```sh + # in the SECOND screen + node ./demo.js 'bar' + ``` + +3. You may need to watch the logs on each of the `dashd` instances to find + which one has been chosen for the coinjoin service (the ids are + non-deterministic / random): + + ```sh + docker ps | grep 'dashd:' | grep '_local_[1-3]' + + docker logs --since 1m -f e04133d8696b + ``` + +## Troubleshooting + +There are two categories you're likely to encounter: + +1. Coins / Coinbase / Block Height isn't mature. \ + For these, the solution is to generate more transactions, and try again: \ + (this functionality is documented only to work on Linux) + ```sh + ./bin/dash-cli-generateblocks 100 + ``` +2. Disk space (or other system resources) is/are low. \ + All sorts of strange things will happen. Doing a reset may help. + ```sh + ~/dashmate/bin/dashmate group stop + docker ps -aq | xargs docker stop + ``` + ```sh + ~/dashmate/bin/dashmate group reset + ``` + ```sh + ~/dashmate/bin/dashmate group start + ``` + You may need to `stop` and `start` a second time to get everything to come + back up - it's not entirely deterministic. That said, just one `reset` should + do. + +Example immature coinbase error: + +```json +{ + "code": -26, + "message": "bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 10" +} +``` -To unlock all wallets, run: +Low disk space (below 90% or below 4gb free) can cause seemingly random and +uncorrelatable errors, hangups, etc. -```sh -./bin/dboot --instance=base --unlock-all -``` +## This isn't a library (yet) + +`demo.js` still has a lot of business logic that will need to be carefully +teased out and abstracted to turn this into a library. + +The parsers and packers are a good chunk of it, but the networking is all in +`demo.js` for now. -# Release Status - -TBD - -# Install - -TBD - -# Features - -- Networking - - [x] Authenticate with a Masternode - - Status: _STABLE_ - - [x] Send `version` - - [x] Respond to `version` with `verack` - - [x] Respond to `ping` with `pong` - - [x] Handle all other requests by a Masternode - - [x] `sendheaders` - - [x] `sendcmpct` - - [x] `senddsq` - - [x] `getheaders` - - [x] `sendaddrv2` - - [x] Transaction encoding - - Status: _DONE_ - - Overview: A partial implementation of a Transaction is working on a - fundamental level. Encoding the transaction for the purpose of creating a - collateral transaction is the priority right now. - - [x] Craft a collateral transaction from user input - - [x] Place the encoded raw transaction into the `dsa` message - - See: (DASH `dsa` - docs)[https://docs.dash.org/projects/core/en/stable/docs/reference/p2p-network-privatesend-messages.html#dsa] - - [x] Transmitting CoinJoin to a Masternode - - Status: _IN PROGRESS_ - - Overview: `dsa` message is very close to being correct - - [x] Send `dsa` message - - [x] Craft a `dsa` message - - [x] Encode collateral transactions - - [x] Encode a single `vin` (transaction input hash - or: "outpoint") - - [x] Create raw transaction header with this encoded `vin` - - [x] Transmit collateral transactions - - notes: dsa packet structure is correct, collateral tx is _ALMOST THERE_ - - [x] Parse `dssu` messages - - Overview: Generic parsing of `dssu` works, but will need to identify - other types of dssu packets. In addition, the dssu packet will likely - affect logic moving forward based on things like the Message ID and Pool - Status/Pool Status Update fields. - - [x] Basic parsing of `dssu` - - [x] Recognizing and translating opcodes to string equivalents - - for things like pool status update, message ID, etc - - [x] Handle all message ID response codes - - [x] Handle all Pool State response codes - - [x] Handle all Pool Status Update response codes - - [ ] Parse `dsc` messages - - TODO - - [ ] Parse `dsq` messages - - TODO - - [ ] Parse `dss` messages - - TODO - - [ ] Parse `dsf` messages - - TODO - -# Demo - -A demo at this point in time would be a bit too premature. - -## Authentication to a Masternode - -It's possible to run `node ./launcher.js` to see how the code connects to the -Masternode of choice. Be sure to fill out your `.config.json` with the correct -info. Please note that `launcher.js` will spawn multiple instances of `demo.js`. -This is for the purpose of developing and testing `dsi` message transmission. - -# Version - -0.1.0 +The actual signing logic is in `dashtx` and the wallet logic is built from the +other `dash*` libraries, which are complete and fit for general use. diff --git a/WALLET-REQUIREMENTS.md b/WALLET-REQUIREMENTS.md deleted file mode 100644 index 6394875..0000000 --- a/WALLET-REQUIREMENTS.md +++ /dev/null @@ -1,8 +0,0 @@ -# Wallet requirements - -What the CJ SDK will require from a client - -- Must be able to provide collateral transactions - - A precise amount will be given to the wallet - - Must be able to sign said transactions - - diff --git a/bin/dash-cli-generateblocks b/bin/dash-cli-generateblocks new file mode 100755 index 0000000..7ce7379 --- /dev/null +++ b/bin/dash-cli-generateblocks @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +set -u + +g_num=${1:-10} +./bin/wallet-create 'foo' 2> /dev/null > /dev/null || true +g_address="$( + ./bin/dash-cli-wallet 'foo' getnewaddress +)" +./bin/dash-cli-wallet 'foo' generatetoaddress "${g_num}" "${g_address}" diff --git a/bin/dash-for-all b/bin/dash-for-all deleted file mode 100755 index 2ddb7ea..0000000 --- a/bin/dash-for-all +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# vi: filetype=sh - -INSTANCE_NAME=base2 -if test ! -z "$1"; then - INSTANCE_NAME="$1" -fi -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) -FILE="$PWD/.djo-users" - -"$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --unlock-all -#"$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --dash-for-all -"$SCRIPT_DIR"/list-users > "$FILE" -for user in $(cat "$FILE"); do - X=$("$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --denom-amt=100001 --username="$user" | wc -l) - echo "$user" has: "$X" denominations of 100001 -done diff --git a/bin/dboot b/bin/dboot deleted file mode 100755 index 92ece91..0000000 --- a/bin/dboot +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env node -'use strict' -// # vi: filetype=js - -const dboot = require('../src/bootstrap/index.js'); - -(async function(){ - await dboot.run_cli_program(); -})(); diff --git a/bin/demo b/bin/demo deleted file mode 100755 index 154946d..0000000 --- a/bin/demo +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -pushd $PWD -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) -cd "${SCRIPT_DIR}"/../src/ -./demo.js --instance=base2 --username=f49bf6f8c5c344e8ba00b0dc83a4fbdb --count=2 diff --git a/bin/list-denoms b/bin/list-denoms deleted file mode 100755 index 2ddb7ea..0000000 --- a/bin/list-denoms +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# vi: filetype=sh - -INSTANCE_NAME=base2 -if test ! -z "$1"; then - INSTANCE_NAME="$1" -fi -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) -FILE="$PWD/.djo-users" - -"$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --unlock-all -#"$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --dash-for-all -"$SCRIPT_DIR"/list-users > "$FILE" -for user in $(cat "$FILE"); do - X=$("$SCRIPT_DIR"/dboot --instance="$INSTANCE_NAME" --denom-amt=100001 --username="$user" | wc -l) - echo "$user" has: "$X" denominations of 100001 -done diff --git a/bin/list-users b/bin/list-users deleted file mode 100755 index dd46769..0000000 --- a/bin/list-users +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) -"$SCRIPT_DIR"/dboot --instance=$INSTANCE_NAME --list-users | cut -d "'" -f 2 | grep -vE '(\[|\])' | grep -v 'loading' | grep -v 'checking' diff --git a/bin/mobrun b/bin/mobrun deleted file mode 100755 index 6944f0d..0000000 --- a/bin/mobrun +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env node -'use strict'; -// # vi: filetype=js - -const launcher = require('../src/launcher.js'); -(async function(){ - await launcher.run_cli_program(); -})(); diff --git a/coinjoin.js b/coinjoin.js new file mode 100644 index 0000000..7c2de2d --- /dev/null +++ b/coinjoin.js @@ -0,0 +1,53 @@ +'use strict'; + +let CoinJoin = module.exports; + +CoinJoin.STANDARD_DENOMINATIONS = [ + // 0.00100001 + 100001, + // 0.01000010 + 1000010, + // 0.10000100 + 10000100, + // 1.00001000 + 100001000, + // 10.0000100 + 1000010000, +]; + +// TODO the spec seems to be more of an ID, though +// the implementation makes it look more like a mask... +CoinJoin.STANDARD_DENOMINATION_MASKS = { + // 0.00100001 + 100001: 0b00010000, + // 0.01000010 + 1000010: 0b00001000, + // 0.10000100 + 10000100: 0b00000100, + // 1.00001000 + 100001000: 0b00000010, + // 10.00010000 + 1000010000: 0b00000001, +}; + +CoinJoin.STANDARD_DENOMINATIONS_MAP = { + // 0.00100001 + 0b00010000: 100001, + // 0.01000010 + 0b00001000: 1000010, + // 0.10000100 + 0b00000100: 10000100, + // 1.00001000 + 0b00000010: 100001000, + // 10.00010000 + 0b00000001: 1000010000, +}; + +// (STANDARD_DENOMINATIONS[0] / 10).floor(); +CoinJoin.COLLATERAL = 10000; +// COLLATERAL * 4 +CoinJoin.MAX_COLLATERAL = 40000; + +CoinJoin.isDenominated = function (sats) { + return CoinJoin.STANDARD_DENOMINATIONS.includes(sats); +}; diff --git a/demo.js b/demo.js new file mode 100644 index 0000000..f3aebc8 --- /dev/null +++ b/demo.js @@ -0,0 +1,1316 @@ +'use strict'; + +let DotEnv = require('dotenv'); +void DotEnv.config({ path: '.env' }); +void DotEnv.config({ path: '.env.secret' }); + +//@ts-ignore - ts can't understand JSON, still... +let pkg = require('./package.json'); + +let Net = require('node:net'); + +let CoinJoin = require('./coinjoin.js'); +let Packer = require('./packer.js'); // TODO rename packer +let Parser = require('./parser.js'); + +let DashPhrase = require('dashphrase'); +let DashHd = require('dashhd'); +let DashKeys = require('dashkeys'); +let DashRpc = require('dashrpc'); +let DashTx = require('dashtx'); +let Secp256k1 = require('@dashincubator/secp256k1'); + +const DENOM_LOWEST = 100001; +const PREDENOM_MIN = DENOM_LOWEST + 193; +// const MIN_UNUSED = 2500; +const MIN_UNUSED = 1000; +const MIN_BALANCE = 100001 * 1000; +const MIN_DENOMINATED = 200; + +// https://github.com/dashpay/dash/blob/v19.x/src/coinjoin/coinjoin.h#L39 +// const COINJOIN_ENTRY_MAX_SIZE = 9; // real +const COINJOIN_ENTRY_MAX_SIZE = 2; // just for testing right now + +let rpcConfig = { + protocol: 'http', // https for remote, http for local / private networking + user: process.env.DASHD_RPC_USER, + pass: process.env.DASHD_RPC_PASS || process.env.DASHD_RPC_PASSWORD, + host: process.env.DASHD_RPC_HOST || '127.0.0.1', + port: process.env.DASHD_RPC_PORT || '19898', // mainnet=9998, testnet=19998, regtest=19898 + timeout: 10 * 1000, // bump default from 5s to 10s for up to 10k addresses +}; +if (process.env.DASHD_RPC_TIMEOUT) { + let rpcTimeoutSec = parseFloat(process.env.DASHD_RPC_TIMEOUT); + rpcConfig.timeout = rpcTimeoutSec * 1000; +} + +async function main() { + /* jshint maxstatements: 1000 */ + /* jshint maxcomplexity: 100 */ + + let walletSalt = process.argv[2] || ''; + let isHelp = walletSalt === 'help' || walletSalt === '--help'; + if (isHelp) { + throw new Error( + `USAGE\n ${process.argv[1]} [wallet-salt]\n\nEXAMPLE\n ${process.argv[1]} 'luke|han|chewie'`, + ); + } + + let walletPhrase = process.env.DASH_WALLET_PHRASE || ''; + if (!walletPhrase) { + throw new Error('missing DASH_WALLET_PHRASE'); + } + + let network = 'regtest'; + // let minimumParticipants = Packer.NETWORKS[network].minimumParticiparts; + rpcConfig.onconnected = async function () { + let rpc = this; + console.info(`[info] rpc client connected ${rpc.host}`); + }; + + let rpc = new DashRpc(rpcConfig); + rpc.onconnected = rpcConfig.onconnected; + let height = await rpc.init(rpc); + console.info(`[info] rpc server is ready. Height = ${height}`); + + let keyUtils = { + sign: async function (privKeyBytes, hashBytes) { + let sigOpts = { canonical: true, extraEntropy: true }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; + }, + getPrivateKey: async function (input) { + if (!input.address) { + //throw new Error('should put the address on the input there buddy...'); + console.warn('missing address:', input.txid, input.outputIndex); + return null; + } + let data = keysMap[input.address]; + let isUint = data.index > -1; + if (!isUint) { + throw new Error(`missing 'index'`); + } + // TODO map xkey by walletid + let addressKey = await xreceiveKey.deriveAddress(data.index); + + { + // sanity check + let privKeyHex = DashTx.utils.bytesToHex(addressKey.privateKey); + if (data._privKeyHex !== privKeyHex) { + if (data._privKeyHex) { + console.log(data._privKeyHex); + console.log(privKeyHex); + throw new Error('mismatch key bytes'); + } + data._privKeyHex = privKeyHex; + } + } + return addressKey.privateKey; + }, + toPublicKey: async function (privKeyBytes) { + // TODO use secp256k1 directly + return await DashKeys.utils.toPublicKey(privKeyBytes); + }, + }; + let dashTx = DashTx.create(keyUtils); + + let testCoin = '1'; + let seedBytes = await DashPhrase.toSeed(walletPhrase, walletSalt); + let walletKey = await DashHd.fromSeed(seedBytes, { + coinType: testCoin, + versions: DashHd.TESTNET, + }); + let walletId = await DashHd.toId(walletKey); + + let accountHdpath = `m/44'/1'/0'`; + let accountKey = await walletKey.deriveAccount(0); + let xreceiveKey = await accountKey.deriveXKey(walletKey, 0); //jshint ignore:line + // let xchangeKey = await accountKey.deriveXKey(walletKey, 1); + // let xprvHdpath = `m/44'/5'/0'/0`; + // let xprvKey = await DashHd.derivePath(walletKey, xprvHdpath); + + // generate bunches of keys + // remove the leading `m/` or `m'/` + let partialPath = accountHdpath.replace(/^m'?\//, ''); + let totalBalance = 0; + let keysMap = {}; //jshint ignore:line + let used = []; + let addresses = []; + let unusedMap = {}; + let index = 0; + let numAddresses = 100; + for (;;) { + let uncheckedAddresses = []; + for (let i = 0; i < numAddresses; i += 1) { + let addressKey = await xreceiveKey.deriveAddress(index); + + // Descriptors are in the form of + // - pkh(xpub123...abc/2) - for the 3rd address of a receiving or change xpub + // - pkh(xpub456...def/0/2) - for the 3rd receive address of an account xpub + // - pkh([walletid/44'/0'/0']xpub123...abc/0/2) - same, plus wallet & hd info + // - pkh([walletid/44'/0'/0'/0/2]Xaddr...#checksum) - same, but the address + // See also: https://github.com/dashpay/dash/blob/master/doc/descriptors.md + // TODO sort out sha vs double-sha vs fingerprint + let descriptor = `pkh([${walletId}/${partialPath}/0/${index}])`; + let address = await DashHd.toAddr(addressKey.publicKey, { + version: 'testnet', + }); + // let utxosRpc = await rpc.getAddressUtxos({ addresses: [address] }); + // let utxos = utxosRpc.result; + // console.log('utxosRpc.result.length', utxosRpc.result.length); + + let data = keysMap[address]; + if (!data) { + data = { + walletId: walletId, + prefix: "m/44'/1'", + account: 0, + usage: 0, + index: index, + descriptor: descriptor, + address: address, + // uxtos: utxos, + used: false, + reserved: 0, + satoshis: 0, + }; + // console.log('[debug] addr info', data); + addresses.push(address); + uncheckedAddresses.push(address); + } + keysMap[index] = data; + keysMap[address] = data; + // console.log('[DEBUG] address:', address); + if (!data.used) { + unusedMap[address] = data; + } + + index += 1; + } + // console.log('[debug] addresses.length', addresses.length); + // console.log('[debug] uncheckedAddresses.length', uncheckedAddresses.length); + + // TODO segment unused addresses + // let unusedAddresses = Object.keys(unusedMap); + // console.log('[debug] unusedAddresses.length', unusedAddresses.length); + + let mempooldeltas = await rpc.getAddressMempool({ + addresses: uncheckedAddresses, + // addresses: unusedAddresses, + }); + // console.log( + // '[debug] mempooldeltas.result.length', + // mempooldeltas.result.length, + // ); + // TODO check that we have a duplicate in both deltas by using txid, vin/vout + for (let delta of mempooldeltas.result) { + totalBalance += delta.satoshis; + + let data = keysMap[delta.address]; + data.satoshis += delta.satoshis; + data.used = true; + if (!used.includes(data)) { + used.push(data); + } + delete unusedMap[data.address]; + } + + let deltas = await rpc.getAddressDeltas({ + addresses: uncheckedAddresses, + }); + // console.log('[debug] deltas.result.length', deltas.result.length); + for (let delta of deltas.result) { + totalBalance += delta.satoshis; + + let data = keysMap[delta.address]; + data.satoshis += delta.satoshis; + data.used = true; + if (!used.includes(data)) { + used.push(data); + } + delete unusedMap[data.address]; + } + + let numUnused = addresses.length - used.length; + if (numUnused >= MIN_UNUSED) { + // console.log('[debug] addresses.length', addresses.length); + // console.log('[debug] used.length', used.length); + break; + } + } + console.log('[debug] wallet balance:', totalBalance); + + let denomination = 100001 * 1; + + void (await generateMinBalance()); + void (await generateDenominations()); + + // TODO sort denominated + // for (let addr of addresses) { ... } + + async function generateMinBalance() { + for (let addr of addresses) { + // console.log('[debug] totalBalance:', totalBalance); + if (totalBalance >= MIN_BALANCE) { + break; + } + + let data = keysMap[addr]; + let isAvailable = !data.used && !data.reserved; + if (!isAvailable) { + continue; + } + + void (await generateToAddressAndUpdateBalance(data)); + } + } + + async function generateDenominations() { + // jshint maxcomplexity: 25 + let denomCount = 0; + let denominable = []; + let denominated = {}; + for (let addr of addresses) { + let data = keysMap[addr]; + if (data.reserved) { + continue; + } + if (data.satoshis === 0) { + continue; + } + + // TODO denominations.includes(data.satoshis) + let isUndenominated = data.satoshis % DENOM_LOWEST; + if (isUndenominated) { + if (data.satoshis >= PREDENOM_MIN) { + denominable.push(data); + } + continue; + } + + if (!denominated[data.satoshis]) { + denominated[data.satoshis] = []; + } + denomCount += 1; + denominated[data.satoshis].push(data); + } + + // CAVEAT: this fee-approximation strategy that guarantees + // to denominate all coins _correctly_, but in some cases will + // create _smaller_ denominations than necessary - specifically + // 10 x 100001 instead of 1 x 1000010 when the lowest order of + // coin is near the single coin value (i.e. 551000010) + // (because 551000010 / 100194 yields 5499 x 100001 coins + full fees, + // but we actually only generate 5 + 4 + 9 + 9 = 27 coins, leaving + // well over 5472 * 193 extra value) + for (let data of denominable) { + // console.log('[debug] denominable', data); + if (denomCount >= MIN_DENOMINATED) { + break; + } + + let fee = data.satoshis; + + // 123 means + // - 3 x 100001 + // - 2 x 1000010 + // - 1 x 10000100 + let order = data.satoshis / PREDENOM_MIN; + order = Math.floor(order); + let orderStr = order.toString(); + // TODO mod and divide to loop and shift positions, rather than stringify + let orders = orderStr.split(''); + orders.reverse(); + + // TODO Math.min(orders.length, STANDARD_DENOMS.length); + // let numOutputs = 0; + let denomOutputs = []; + // let magnitudes = [0]; + for (let i = 0; i < orders.length; i += 1) { + let order = orders[i]; + let count = parseInt(order, 10); + let orderSingle = DENOM_LOWEST * Math.pow(10, i); + // let orderTotal = count * orderSingle; + // numOutputs += count; + for (let i = 0; i < count; i += 1) { + fee -= orderSingle; + denomOutputs.push({ + satoshis: orderSingle, + }); + } + // magnitudes.push(count); + } + // example: + // [ 0, 3, 2, 1 ] + // - 0 x 100001 * 0 + // - 3 x 100001 * 1 + // - 2 x 100001 * 10 + // - 1 x 100001 * 100 + + // console.log('[debug] denom outputs', denomOutputs); + // console.log('[debug] fee', fee); + // Note: this is where we reconcile the difference between + // the number of the smallest denom, and the number of actual denoms + // (and where we may end up with 10 x LOWEST, which we could carry + // over into the next tier, but won't right now for simplicity). + for (;;) { + let numInputs = 1; + let fees = DashTx._appraiseCounts(numInputs, denomOutputs.length + 1); + let nextCoinCost = DENOM_LOWEST + fees.max; + if (fee < nextCoinCost) { + // TODO split out 10200 (or 10193) collaterals as well + break; + } + fee -= DashTx.OUTPUT_SIZE; + fee -= DENOM_LOWEST; + denomOutputs.push({ + satoshis: DENOM_LOWEST, + }); + // numOutputs += 1; + // magnitudes[1] += 1; + } + // console.log('[debug] denom outputs', denomOutputs); + + let changes = []; + for (let addr of addresses) { + if (denomOutputs.length === 0) { + break; + } + + let unused = unusedMap[addr]; + if (!unused) { + continue; + } + + unused.reserved = Date.now(); + delete unusedMap[addr]; + + let denomValue = denomOutputs.pop(); + if (!denomValue) { + break; + } + + unused.satoshis = denomValue.satoshis; + changes.push(unused); + } + + let txInfo; + { + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + console.log('[debug] input utxo', utxo); + // utxo.sigHashType = 0x01; + utxo.address = data.address; + if (utxo.txid) { + // TODO fix in dashtx + utxo.txId = utxo.txid; + } + } + for (let change of changes) { + let pubKeyHashBytes = await DashKeys.addrToPkh(change.address, { + version: 'testnet', + }); + change.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + } + + txInfo = { + version: 3, + inputs: utxos, + outputs: changes, + locktime: 0, + }; + txInfo.inputs.sort(DashTx.sortInputs); + txInfo.outputs.sort(DashTx.sortOutputs); + } + + let keys = []; + for (let input of txInfo.inputs) { + let data = keysMap[input.address]; + let addressKey = await xreceiveKey.deriveAddress(data.index); + keys.push(addressKey.privateKey); + // DEBUG check pkh hex + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + data.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + console.log(data); + } + let txInfoSigned = await dashTx.hashAndSignAll(txInfo); + + console.log('[debug], txInfo, keys, txSigned'); + console.log(txInfo); + console.log(keys); + console.log(txInfoSigned); + await sleep(150); + let txRpc = await rpc.sendRawTransaction(txInfoSigned.transaction); + await sleep(150); + console.log('[debug] txRpc.result', txRpc.result); + + // TODO don't add collateral coins + for (let change of changes) { + denomCount += 1; + if (!denominated[change.satoshis]) { + denominated[change.satoshis] = []; + } + denominated[change.satoshis].push(change); + change.reserved = 0; + } + } + } + + async function generateToAddressAndUpdateBalance(data) { + let numBlocks = 1; + await sleep(150); + void (await rpc.generateToAddress(numBlocks, data.address)); + await sleep(150); + // let blocksRpc = await rpc.generateToAddress(numBlocks, addr); + // console.log('[debug] blocksRpc', blocksRpc); + + // let deltas = await rpc.getAddressMempool({ addresses: [addr] }); + // console.log('[debug] generatetoaddress mempool', deltas); + // let deltas2 = await rpc.getAddressDeltas({ addresses: [addr] }); + // console.log('[debug] generatetoaddress deltas', deltas); + // let results = deltas.result.concat(deltas2.result); + // for (let delta of results) { + // totalBalance += delta.satoshis; + // keysMap[delta.address].used = true; + // delete unusedMap[delta.address]; + // } + + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + // console.log(data.index, '[debug] utxo.satoshis', utxo.satoshis); + data.satoshis += utxo.satoshis; + totalBalance += utxo.satoshis; + keysMap[utxo.address].used = true; + delete unusedMap[utxo.address]; + } + } + + // TODO unreserve collateral after positive response + // (and check for use 30 seconds after failure message) + async function getCollateralTx() { + let barelyEnoughest = { satoshis: Infinity, reserved: 0 }; + for (let addr of addresses) { + let data = keysMap[addr]; + if (data.reserved > 0) { + continue; + } + + if (!data.satoshis) { + continue; + } + + if (barelyEnoughest.reserved > 0) { + let isDenom = data.satoshis % DENOM_LOWEST === 0; + if (isDenom) { + continue; + } + } + + if (data.satoshis < CoinJoin.COLLATERAL) { + continue; + } + + if (data.satoshis < barelyEnoughest.satoshis) { + barelyEnoughest = data; + barelyEnoughest.reserved = Date.now(); + } + } + console.log('[debug] barelyEnoughest coin:', barelyEnoughest); + + let collateralTxInfo; + { + let addr = barelyEnoughest.address; + let utxosRpc = await rpc.getAddressUtxos({ addresses: [addr] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + console.log('[debug] input utxo', utxo); + // utxo.sigHashType = 0x01; + utxo.address = addr; + if (utxo.txid) { + // TODO fix in dashtx + utxo.txId = utxo.txid; + } + } + + let output; + let leftover = barelyEnoughest.satoshis - CoinJoin.COLLATERAL; + if (leftover >= CoinJoin.COLLATERAL) { + let change = await reserveChangeAddress(); + output = Object.assign({}, change); + // TODO change.used = true; + // change.reserved = 0; + let pubKeyHashBytes = await DashKeys.addrToPkh(output.address, { + version: 'testnet', + }); + output.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + output.satoshis = leftover; + } else { + output = DashTx.createDonationOutput(); + // TODO 0-byte memo? no outputs (bypassing the normal restriction)? + } + + console.log('[debug] change or memo', output); + let txInfo = { + version: 3, + inputs: utxos, + outputs: [output], + locktime: 0, + }; + txInfo.inputs.sort(DashTx.sortInputs); + txInfo.outputs.sort(DashTx.sortOutputs); + + collateralTxInfo = txInfo; + } + + console.log('[debug] ds* collateral tx', collateralTxInfo); + return collateralTxInfo; + } + + async function reserveChangeAddress() { + for (let addr of addresses) { + let data = keysMap[addr]; + + let isAvailable = !data.used && !data.reserved; + if (!isAvailable) { + continue; + } + + data.reserved = Date.now(); + return data; + } + + let msg = + 'sanity fail: ran out of addresses despite having 500+ unused extra'; + throw new Error(msg); + } + + // async function getPrivateKeys(inputs) { + // let keys = []; + // for (let input of inputs) { + // let privKeyBytes = await keyUtils.getPrivateKey(input); + // keys.push(privKeyBytes); + // } + + // return keys; + // } + + let evonodes = []; + { + let resp = await rpc.masternodelist(); + let evonodesMap = resp.result; + let evonodeProTxIds = Object.keys(evonodesMap); + for (let id of evonodeProTxIds) { + let evonode = evonodesMap[id]; + if (evonode.status === 'ENABLED') { + let hostParts = evonode.address.split(':'); + let evodata = { + id: evonode.id, + hostname: hostParts[0], + port: hostParts[1], + type: evonode.type, + }; + evonodes.push(evodata); + } + } + if (!evonodes.length) { + throw new Error('Sanity Fail: no evonodes online'); + } + } + + // void shuffle(evonodes); + evonodes.sort(byId); + let evonode = evonodes.at(-1); + console.info('[info] chosen evonode:'); + console.log(JSON.stringify(evonode, null, 2)); + + let conn = Net.createConnection({ + host: evonode.hostname, + port: evonode.port, + keepAlive: true, + keepAliveInitialDelay: 3, + //localAddress: rpc.host, + }); + + /** @type {Array} */ + let chunks = []; + let chunksLength = 0; + let errReject; + + function onError(err) { + console.error('Error:'); + console.error(err); + conn.removeListener('error', onError); + errReject(err); + } + function onEnd() { + console.info('[info] disconnected from server'); + } + conn.on('error', onError); + conn.once('end', onEnd); + conn.setMaxListeners(2); + let dataCount = 0; + conn.on('data', function (data) { + console.log('[DEBUG] data'); + console.log(dataCount, data.length, data.toString('hex')); + dataCount += 1; + }); + + /** @type {Array} */ + let messages = []; + /** @type {Object} */ + let listenerMap = {}; + async function goRead() { + let pongSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + let pongMessageBytes = new Uint8Array(pongSize); + for (;;) { + console.log('[debug] readMessage()'); + let msg = await readMessage(); + + if (msg.command === 'ping') { + void Packer.packPong({ + network: network, + message: pongMessageBytes, + nonce: msg.payload, + }); + conn.write(pongMessageBytes); + console.log('[debug] sent pong'); + continue; + } + + if (msg.command === 'dssu') { + let dssu = await Parser.parseDssu(msg.payload); + console.log('[debug] dssu', dssu); + continue; + } + + let i = messages.length; + messages.push(msg); + let listeners = Object.values(listenerMap); + for (let ln of listeners) { + void ln(msg, i, messages); + } + } + } + void goRead(); + + /** + * Reads a for a full 24 bytes, parses those bytes as a header, + * and then reads the length of the payload. Any excess bytes will + * be saved for the next cycle - meaning it can handle multiple + * messages in a single packet. + */ + async function readMessage() { + const HEADER_SIZE = 24; + const PAYLOAD_SIZE_MAX = 4 * 1024 * 1024; + + // TODO setTimeout + let _resolve; + let _reject; + let p = new Promise(function (__resolve, __reject) { + _resolve = __resolve; + _reject = __reject; + }); + + let header; + + function cleanup() { + // console.log("[debug] readMessage handlers: remove 'onReadableHeader'"); + conn.removeListener('data', onReadableHeader); + conn.removeListener('readable', onReadableHeader); + + // console.log("[debug] readMessage handlers: remove 'onReadablePayload'"); + conn.removeListener('data', onReadablePayload); + conn.removeListener('readable', onReadablePayload); + } + + function resolve(data) { + cleanup(); + _resolve(data); + } + + function reject(err) { + cleanup(); + _reject(err); + } + + function onReadableHeader(data) { + let size = data?.length || 0; + console.log('State: reading header', size); + let chunk; + for (;;) { + chunk = data; + // chunk = conn.read(); // TODO reenable + if (!chunk) { + break; + } + chunks.push(chunk); + chunksLength += chunk.byteLength; + data = null; // TODO nix + } + if (chunksLength < HEADER_SIZE) { + return; + } + if (chunks.length > 1) { + chunk = Buffer.concat(chunks, chunksLength); + } else { + chunk = chunks[0]; + } + chunks = []; + chunksLength = 0; + if (chunk.byteLength > HEADER_SIZE) { + let extra = chunk.slice(HEADER_SIZE); + chunks.push(extra); + chunksLength += chunk.byteLength; + chunk = chunk.slice(0, HEADER_SIZE); + } + header = Parser.parseHeader(chunk); + if (header.payloadSize > PAYLOAD_SIZE_MAX) { + throw new Error('too big you are, handle you I cannot'); + } + // console.log('DEBUG header', header); + conn.removeListener('readable', onReadableHeader); + conn.removeListener('data', onReadableHeader); + + if (header.payloadSize === 0) { + resolve(header); + return; + } + + // console.log("[debug] readMessage handlers: add 'onReadablePayload'"); + //conn.on('readable', onReadablePayload); + conn.on('data', onReadablePayload); + onReadablePayload(null); + } + + function onReadablePayload(data) { + let size = data?.length || 0; + console.log('State: reading payload', size); + let chunk; + for (;;) { + chunk = data; + // chunk = conn.read(); // TODO revert + if (!chunk) { + break; + } + chunks.push(chunk); + chunksLength += chunk.byteLength; + data = null; // TODO nix + } + if (chunksLength < header.payloadSize) { + return; + } + if (chunks.length > 1) { + chunk = Buffer.concat(chunks, chunksLength); + } else if (chunks.length === 1) { + chunk = chunks[0]; + } else { + console.log("[warn] 'chunk' is 'null' (probably the debug null)"); + return; + } + chunks = []; + chunksLength = 0; + if (chunk.byteLength > header.payloadSize) { + let extra = chunk.slice(header.payloadSize); + chunks.push(extra); + chunksLength += chunk.byteLength; + chunk = chunk.slice(0, header.payloadSize); + } + header.payload = chunk; + conn.removeListener('readable', onReadablePayload); + conn.removeListener('data', onReadablePayload); + resolve(header); + } + + errReject = reject; + + // console.log("[debug] readMessage handlers: add 'onReadableHeader'"); + //conn.on('readable', onReadableHeader); + conn.on('data', onReadableHeader); + + if (chunks.length) { + onReadableHeader(null); + } + + let msg = await p; + return msg; + } + + async function waitForConnect() { + // connect / connected + // TODO setTimeout + await new Promise(function (_resolve, _reject) { + function cleanup() { + conn.removeListener('readable', onReadable); + conn.removeListener('data', onReadable); + } + + function resolve(data) { + cleanup(); + _resolve(data); + } + + function reject(err) { + cleanup(); + _reject(err); + } + + function onConnect() { + resolve(); + } + + function onReadable() { + // checking an impossible condition, just in case + throw new Error('unexpected response before request'); + } + + errReject = reject; + conn.once('connect', onConnect); + //conn.on('readable', onReadable); + conn.on('data', onReadable); + }); + } + + await waitForConnect(); + console.log('connected'); + + // + // version / verack + // + let versionMsg = Packer.version({ + network: network, // Packer.NETWORKS.regtest, + //protocol_version: Packer.PROTOCOL_VERSION, + //addr_recv_services: [Packer.IDENTIFIER_SERVICES.NETWORK], + addr_recv_ip: evonode.hostname, + addr_recv_port: evonode.port, + //addr_trans_services: [], + //addr_trans_ip: '127.0.01', + //addr_trans_port: null, + // addr_trans_ip: conn.localAddress, + // addr_trans_port: conn.localPort, + start_height: height, + //nonce: null, + user_agent: `DashJoin.js/${pkg.version}`, + // optional-ish + relay: false, + mnauth_challenge: null, + mn_connection: false, + }); + + // let versionBuffer = Buffer.from(versionMsg); + // console.log('version', versionBuffer.toString('hex')); + // console.log(Parser.parseHeader(versionBuffer.slice(0, 24))); + // console.log(Parser.parseVerack(versionBuffer.slice(24))); + + { + let versionP = new Promise(function (resolve, reject) { + listenerMap['version'] = async function (message) { + let versionResp = await Parser.parseVersion(message.payload); + console.log('DEBUG version', versionResp.version); + resolve(null); + listenerMap['version'] = null; + delete listenerMap['version']; + }; + }); + await sleep(150); + conn.write(versionMsg); + + await versionP; + } + + { + let verackP = await new Promise(function (resolve, reject) { + listenerMap['verack'] = async function (message) { + if (message.command !== 'verack') { + return; + } + + console.log('DEBUG verack', message); + resolve(); + listenerMap['verack'] = null; + delete listenerMap['verack']; + }; + }); + let verackBytes = Packer.packMessage({ + network, + command: 'verack', + payload: null, + }); + await sleep(150); + conn.write(verackBytes); + + await verackP; + } + + { + let mnauthP = new Promise(function (resolve, reject) { + listenerMap['mnauth'] = async function (message) { + if (message.command !== 'mnauth') { + return; + } + + resolve(); + listenerMap['mnauth'] = null; + delete listenerMap['mnauth']; + }; + }); + + let senddsqP = new Promise(function (resolve, reject) { + listenerMap['senddsq'] = async function (message) { + if (message.command !== 'senddsq') { + return; + } + + let sendDsqMessage = Packer.packSendDsq({ + network: network, + send: true, + }); + await sleep(150); + conn.write(sendDsqMessage); + console.log("[debug] sending 'senddsq':", sendDsqMessage); + + resolve(); + listenerMap['senddsq'] = null; + delete listenerMap['senddsq']; + }; + }); + + await mnauthP; + await senddsqP; + } + + { + let dsqPromise = new Promise(readDsq); + // + // dsa / dssu + dsq + // + //for (let i = 0; i < minimumParticipants; i += 1) + let collateralTx; + { + void (await generateMinBalance()); + void (await generateDenominations()); + + void (await generateMinBalance()); + let collateralTxInfo = await getCollateralTx(); + let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo); + collateralTx = DashTx.utils.hexToBytes(txInfoSigned.transaction); + } + let dsaMsg = await Packer.packAllow({ + network, + denomination, + collateralTx, + }); + await sleep(150); + conn.write(dsaMsg); + + let dsaBuf = Buffer.from(dsaMsg); + console.log('[debug] dsa', dsaBuf.toString('hex')); + + let dsq = await dsqPromise; + for (; !dsq.ready; ) { + dsq = await new Promise(readDsq); + if (dsq.ready) { + break; + } + } + } + + function readDsq(resolve, reject) { + listenerMap['dsq'] = async function (message) { + if (message.command !== 'dsq') { + return; + } + + let dsq = await Parser.parseDsq(message.payload); + console.log('DEBUG dsq', dsq); + + resolve(dsq); + listenerMap['dsq'] = null; + delete listenerMap['dsq']; + }; + } + + let dsfP = new Promise(function (resolve, reject) { + listenerMap['dsf'] = async function (message) { + if (message.command !== 'dsf') { + return; + } + + let dsf = Parser.parseDsf(message.payload); + resolve(dsf); + listenerMap['dsf'] = null; + delete listenerMap['dsf']; + }; + }); + + let dscP = new Promise(function (resolve, reject) { + listenerMap['dsc'] = async function (message) { + if (message.command !== 'dsc') { + return; + } + + console.log('[debug] DSC Status:', message.payload.slice(4)); + // let dsc = Parser.parseDsc(message.payload); + // resolve(dsc); + resolve(); + listenerMap['dsc'] = null; + delete listenerMap['dsc']; + }; + }); + + let inputs = []; + let outputs = []; + { + // build utxo inputs from addrs + for (let addr of addresses) { + if (inputs.length >= COINJOIN_ENTRY_MAX_SIZE) { + break; + } + + let data = keysMap[addr]; + // Note: we'd need to look at utxos (not total address balance) + // to be wholly accurate, but this is good enough for now + if (data.satoshis !== denomination) { + continue; + } + if (data.reserved) { + continue; + } + + data.reserved = Date.now(); + let utxosRpc = await rpc.getAddressUtxos({ addresses: [data.address] }); + let utxos = utxosRpc.result; + for (let utxo of utxos) { + // utxo.sigHashType = 0x01; + utxo.address = data.address; + utxo.index = data.index; + // TODO fix in dashtx + utxo.txId = utxo.txId || utxo.txid; + utxo.txid = utxo.txId || utxo.txid; + + // must have pubKeyHash for script to sign + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + utxo.pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + + console.log('[debug] input utxo', utxo); + inputs.push(utxo); + } + } + + // build output addrs + for (let addr of addresses) { + if (outputs.length >= inputs.length) { + break; + } + + let data = keysMap[addr]; + + let isFree = !data.used && !data.reserved; + if (!isFree) { + continue; + } + + data.reserved = Date.now(); + let pubKeyHashBytes = await DashKeys.addrToPkh(data.address, { + version: 'testnet', + }); + let pubKeyHash = DashKeys.utils.bytesToHex(pubKeyHashBytes); + + let output = { + pubKeyHash: pubKeyHash, + satoshis: denomination, + }; + + outputs.push(output); + } + // inputs.sort(DashTx.sortInputs); + // outputs.sort(DashTx.sortOutputs); + } + + console.log('sanity check 1: inputs', inputs); + let dsf; + { + void (await generateMinBalance()); + let collateralTxInfo = await getCollateralTx(); + let txInfoSigned = await dashTx.hashAndSignAll(collateralTxInfo); + let collateralTx = DashTx.utils.hexToBytes(txInfoSigned.transaction); + + let dsiMessageBytes = Packer.packDsi({ + network, + inputs, + collateralTx, + outputs, + }); + await sleep(150); + conn.write(dsiMessageBytes); + dsf = await dsfP; + } + + console.log('sanity check 2: inputs', inputs); + { + let txRequest = dsf.transaction_unsigned; + console.log('[debug] tx request (unsigned)', txRequest); + let sigHashType = DashTx.SIGHASH_ALL | DashTx.SIGHASH_ANYONECANPAY; //jshint ignore:line + // let sigHashType = DashTx.SIGHASH_ALL; + let txInfo = DashTx.parseUnknown(txRequest); + console.log('[debug] DashTx.parseRequest(dsfTxRequest)'); + console.log(txInfo); + for (let input of inputs) { + console.log('sanity check 3: input', input); + let privKeyBytes = await keyUtils.getPrivateKey(input); + let pubKeyBytes = await keyUtils.toPublicKey(privKeyBytes); + let publicKey = DashTx.utils.bytesToHex(pubKeyBytes); + + { + // sanity check + let addr = await DashKeys.pubkeyToAddr(pubKeyBytes, { + version: 'testnet', + }); + if (addr !== input.address) { + console.error(`privKeyBytes => 'addr': ${addr}`); + console.error(`'input.address': ${input.address}`); + throw new Error('sanity fail: address mismatch'); + } + } + + // let sighashInputs = []; + for (let sighashInput of txInfo.inputs) { + if (sighashInput.txid !== input.txid) { + continue; + } + if (sighashInput.outputIndex !== input.outputIndex) { + continue; + } + + sighashInput.index = input.index; + sighashInput.address = input.address; + sighashInput.satoshis = input.satoshis; + sighashInput.pubKeyHash = input.pubKeyHash; + // sighashInput.script = input.script; + sighashInput.publicKey = publicKey; + sighashInput.sigHashType = sigHashType; + console.log('[debug] YES, CAN HAZ INPUTS!!!', sighashInput); + // sighashInputs.push({ + // txId: input.txId || input.txid, + // txid: input.txid || input.txId, + // outputIndex: input.outputIndex, + // pubKeyHash: input.pubKeyHash, + // sigHashType: input.sigHashType, + // }); + break; + } + // if (sighashInputs.length !== 1) { + // let msg = + // 'expected exactly one selected input to match one tx request input'; + // throw new Error(msg); + // } + // let anyonecanpayIndex = 0; + // let txHashable = DashTx.createHashable( + // { + // version: txInfo.version, + // inputs: sighashInputs, // exactly 1 + // outputs: txInfo.outputs, + // locktime: txInfo.locktime, + // }, + // anyonecanpayIndex, + // ); + // console.log('[debug] txHashable (pre-sighashbyte)', txHashable); + + // let signableHashBytes = await DashTx.hashPartial(txHashable, sigHashType); + // let signableHashHex = DashTx.utils.bytesToHex(signableHashBytes); + // console.log('[debug] signableHashHex', signableHashHex); + // let sigBuf = await keyUtils.sign(privKeyBytes, signableHashBytes); + // let signature = DashTx.utils.bytesToHex(sigBuf); + // Object.assign(input, { publicKey, sigHashType, signature }); + } + + // for (let input of txInfo.inputs) { + // let inputs = Tx.selectSigHashInputs(txInfo, i, _sigHashType); + // let outputs = Tx.selectSigHashOutputs(txInfo, i, _sigHashType); + // let txForSig = Object.assign({}, txInfo, { inputs, outputs }); + // } + // let txSigned = await dashTx.hashAndSignAll(txForSig); + let txSigned = await dashTx.hashAndSignAll(txInfo); + console.log('[debug] txSigned', txSigned); + let signedInputs = []; + for (let input of txSigned.inputs) { + if (!input?.signature) { + continue; + } + signedInputs.push(input); + } + console.log('[debug] signed inputs', signedInputs); + + let dssMessageBytes = Packer.packDss({ + network: network, + inputs: signedInputs, + }); + console.log('[debug] dss =>', dssMessageBytes.length); + console.log(dssMessageBytes); + let dssHex = DashTx.utils.bytesToHex(dssMessageBytes); + console.log(dssHex); + await sleep(150); + conn.write(dssMessageBytes); + await dscP; + } + + console.log('Sweet, sweet victory!'); +} + +/** + * @param {Object} a + * @param {String} a.id + * @param {Object} b + * @param {String} b.id + */ +function byId(a, b) { + if (a.id > b.id) { + return 1; + } + if (a.id < b.id) { + return -1; + } + return 0; +} + +// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array +// function shuffle(arr) { +// let currentIndex = arr.length; + +// // While there remain elements to shuffle... +// for (; currentIndex !== 0; ) { +// // Pick a remaining element... +// let randomIndexFloat = Math.random() * currentIndex; +// let randomIndex = Math.floor(randomIndexFloat); +// currentIndex -= 1; + +// // And swap it with the current element. +// let temporaryValue = arr[currentIndex]; +// arr[currentIndex] = arr[randomIndex]; +// arr[randomIndex] = temporaryValue; +// } + +// return arr; +// } + +function sleep(ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms); + }); +} + +main() + .then(function () { + console.info('Done'); + process.exit(0); + }) + .catch(function (err) { + console.error('Fail:'); + console.error(err.stack || err); + process.exit(1); + }); diff --git a/devblog/2023-05-26.md b/devblog/2023-05-26.md deleted file mode 100644 index b192db6..0000000 --- a/devblog/2023-05-26.md +++ /dev/null @@ -1,54 +0,0 @@ -# Library overview - -A description of where we're at, what we've learned, and the road moving -forward. - -# Purpose - -A lot of the knowledge about this library and the masternode setup exists mostly -in my head. I'm using this file to serve as a dev blog of sorts. We're making -lots of progress and it would be terrible if none of it was documented. This -document also does not assume that organization of topics should be strictly -adhered to. - -# How it started - -Initially, I needed a masternode. Without the knowledge that I have now, I -hacked together pieces of the puzzle to make a hybrid full node/masternode. I -tested against this hybrid node until I ran into a snag: Deterministic -Masternode Lists. - -# Deterministic Masternode Lists - -When a masternode is spun up, it will parse the BLS private key and more or less -consult the blockchain for the "agreed upon" masternode list. This is a much -safer way to propagate masternode lists as it requires a rather large sum of -money (1k DASH) to get your masternode on that list. Mind you, this was done -using testnet. - -When any coinjoin network traffic makes it's way to a masternode, the first -thing it does is attempts to fetch details about itself. It'll try to validate -that the masternode exists on the Deterministic Masternode List. If it doesn't, -coinjoin is utterly impossible, unless you make significant edits to the source -code to bypass it. - -# Devnet to the rescue - -It turns out that you can spin up a development full node. It's also possible to -spin up a development master node. The option to allow this is -`-devnet=yourNameHere`. Combine this with `-allowprivatenet=1`, and you can -actually have a cluster of nodes and masternodes that all communicate with each -other on a LAN. - -# Current challenges - -I've created a couple of nodes that attempt to act as a masternode. The -immediate work moving forward will be to verify that a valid devnet masternode -is responding to all coinjoin traffic we send to it. The end result will be a -cluster of nodes running on my LAN which communicate to each other on a devnet. -This will be the ultimate test of the code's correctness and will be the optimal -way to develop and test this library. - -# Document date - -Fri 26 May 2023 22:51:27 MDT diff --git a/devblog/2023-06-01.md b/devblog/2023-06-01.md deleted file mode 100644 index 5045529..0000000 --- a/devblog/2023-06-01.md +++ /dev/null @@ -1,18 +0,0 @@ -# Dashmate - -Recently, I've abandoned the hacked-together masternode approach in favor of -creating a cluster of nodes (3 masternodes to be exact) using Dashmate. This is -the ideal way to develop this library moving forward. - -# Current challenges - -- Creating locking/unlocking scripts for collateral transactions. - -# Immediate goals - -- Generate a simple locking/unlocking script template, if possible -- Document steps in generating a script - -# Document date - -Thu Jun 1 07:00:25 UTC 2023 diff --git a/devblog/2023-06-06.md b/devblog/2023-06-06.md deleted file mode 100644 index b379bdb..0000000 --- a/devblog/2023-06-06.md +++ /dev/null @@ -1,4 +0,0 @@ -# Mempool - -If you create a secondary wallet, then send money to that wallet from another, -that transaction will live in the mem pool. `dc getrawmempool` diff --git a/devblog/2023-06-08.md b/devblog/2023-06-08.md deleted file mode 100644 index dd449cd..0000000 --- a/devblog/2023-06-08.md +++ /dev/null @@ -1,71 +0,0 @@ -# dsa message - -We now have a functioning `dsa` message. See below: - -``` -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) Added connection peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) connection accepted, sock=24, peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) received: version (163 bytes) peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) sending version (137 bytes) peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) send version message: version 70227, blocks=1803, us=[::]:0, peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) sending sendaddrv2 (0 bytes) peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) sending verack (0 bytes) peer=42 -2023-06-08T06:34:37Z (mocktime: 2023-06-08T06:39:11Z) receive version message: /Dash Core:19.1.0(devnet.devnet-privatesend)/: version 70227, blocks=90, us=[::]:20001, peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) received: verack (0 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) CMNAuth::PushMNAUTH -- Sending MNAUTH, peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending mnauth (128 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending sendheaders (0 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending sendcmpct (9 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending senddsq (1 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending ping (8 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) initial getheaders (1802) to peer=42 (startheight:90) -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) sending getheaders (741 bytes) peer=42 -2023-06-08T06:34:38Z (mocktime: 2023-06-08T06:39:11Z) received: pong (8 bytes) peer=42 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) received: dsa (229 bytes) peer=42 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) DSACCEPT -- nDenom 16 (0.00100001) txCollateral CMutableTransaction(hash=8b2670cd77, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(d8091601ebb95b50d5448f87e832f53682f4ad93b3819c529d2908f3e3cc2c29, 0), scriptSig=47304402203b035e3d90c37d) - CTxOut(nValue=0.00020000, scriptPubKey=76a914d398d0c3b9924b68242621b3) - CTxOut(nValue=124.36626936, scriptPubKey=76a914f9422f0f89c92d038f7689f8) -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) CCoinJoin::IsCollateralValid -- CTransaction(hash=8b2670cd77, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(d8091601ebb95b50d5448f87e832f53682f4ad93b3819c529d2908f3e3cc2c29, 0), scriptSig=47304402203b035e3d90c37d) - CTxOut(nValue=0.00020000, scriptPubKey=76a914d398d0c3b9924b68242621b3) - CTxOut(nValue=124.36626936, scriptPubKey=76a914f9422f0f89c92d038f7689f8) -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) CCoinJoinServer::SetState -- nState: 0, nStateNew: 1 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) CCoinJoinServer::CreateNewSession -- signing and relaying new queue: nDenom=16, nTime=1686206351, fReady=false, fTried=false, masternode=4276788af7ea33df13d3310bf131dcab22c0457baab1977deb94b88d65d1b180-0 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) sending dsq (142 bytes) peer=0 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) sending dsq (142 bytes) peer=1 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) sending dsq (142 bytes) peer=2 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) CCoinJoinServer::CreateNewSession -- new session created, nSessionID: 156391 nSessionDenom: 16 (0.00100001) vecSessionCollaterals.size(): 1 CCoinJoin::GetMaxPoolParticipants(): 20 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) DSACCEPT -- is compatible, please submit! -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) sending dssu (16 bytes) peer=42 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) received: dsq (142 bytes) peer=1 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) received: dsq (142 bytes) peer=0 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) received: dsq (142 bytes) peer=2 -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) Feeding 16795 bytes of dynamic environment data into RNG -2023-06-08T06:34:41Z (mocktime: 2023-06-08T06:39:11Z) CChainLocksHandler::EnforceBestChainLock -- enforcing block 1fe61b64172269b020d5ae627cbc38084cb20ea72805a06f069f22c15f6aab71 via CLSIG (CChainLockSig(nHeight=1803, blockHash=1fe61b64172269b020d5ae627cbc38084cb20ea72805a06f069f22c15f6aab71)) -``` - -# Notice: - -``` -2023-06-08T06:34:40Z (mocktime: 2023-06-08T06:39:11Z) DSACCEPT -- is compatible, please submit! -``` - -# Code - -See: [src/demo.js](src/demo.js) - -# Fixtures - -You'll notice that there are a few hard-coded file paths in the source code. -Those will be fixed in upcoming commits. For the time being, we need a front-end -library that is essentially that entire `makeCollateralTx` function but in the -browser. That can then be sent to us (the express server) and we can use it to -join (or create) a coinjoin queue on the masternode that we are connected to. - -# TODO: - -- Parse the `dssu` packet -- Parse `dsq` -- Parse `dsf` -- save the session details from `dssu` or `dsq` diff --git a/devblog/2023-06-13.md b/devblog/2023-06-13.md deleted file mode 100644 index 5093961..0000000 --- a/devblog/2023-06-13.md +++ /dev/null @@ -1,95 +0,0 @@ -# Library overview - -Major progress thanks to dashcore-lib! A solid library that has proven to be -extremely easy to use. - -# What we know so far - -- Any number of connections can come from a single host. We will need to test - this on testnet and mainnet, but as far as I can tell, a host can initiate - several sessions from the same IP address. This means that a single host could - potentially represent several coinjoin clients at once... each with their own - socket representing their connection. -- The `dsi` message is where we're currently at. The flow goes: - - connect to masternode - - complete `version`/`verack` handshake - - notify the masternode via `senddsq` - - this lets the MN know we want to send coinjoin traffic - - submit a `dsa` message - - wait for `dsq` message - - submit `dsi` message - -# Utility scripts - -I've created a script at `utils/denominate` which will handle a lot of the -hassles of sending denominations to many different wallets. In order to use this -script you will need to have setup your `~/bin/d*` scripts... i.e.: `~/bin/df`. -Those scripts actually should be in the `utils/` directory. I'll commit them -after this devblog is pushed, if they don't. - -# Current challenges - -- The `dsi` message requires the client to send equal numbers of inputs and - outputs, along with collateral transactions. _This is where we're at_. - -# Pitfalls - -- I've seen situations where the masternode will charge the client for - submitting incorrect or malformed data. I will create a document that details - exactly where and how those situations can arise and what we can do about it. - -# Possible concurrent work - -## Browser side code - -- At this point in time, it's possible to write some of the code that will be - present in the browser and on the server. The following things can be worked - on: - - Importing a WIF - - Creating and transmitting a collateral transaction - - just take the code from `src/demodata.js` - - it would essentially need to send `tx.uncheckedSerialize()` to a - predefined **RELATIVE** url. - - url can be hard-coded for now - - Contacting a RESTful API endpoint to begin matchmaking - - `POST /api/v1/matchmaking/session` - - payload should include output of `tx.uncheckedSerialize()` - - response will be a queue id - - `GET /api/v1/matchmaking/session/{QUEUE_ID}` - - `QUEUE_ID` is the response from `POST /api/v1/matchmaking/session` - - response will be something like: - ``` - { - queue_id: , - status: "pending_connection|connected|waiting_for_queue|joined|mixing|cleanup|error-X", - status_code: , - error: "A|B|C|D", - error_code: - } - ``` - - where `` is a randomly generated queue id - - where `` is an integral status code that corresponds to `status` - - where `status` is one of many strings. `error-X` is TBD - - where `error` is a string representing an error. it will likely be a - sentence or phrase - - where `error_code` is an integral error code corresponding to the - `error` - - `error` and `error_code` will only appear if `status` and `status_code` - indicate an error condition - - Rate-limiting middleware - - The calling code must be mindful of a rate limiting middleware that will - be active once the SDK is fully developed - - users should expect a json payload described below should the client - make too many requests - - if a client is rate limited, the API endpoint will respond with an HTTP - 200 status code but with a JSON payload of: - ```json - { - "error": "rate-limited", - "error_code": -1 - } - ``` - -# Document date - -Tue Jun 13 07:29:40 UTC 2023 diff --git a/devblog/2023-06-24.md b/devblog/2023-06-24.md deleted file mode 100644 index e8f2d97..0000000 --- a/devblog/2023-06-24.md +++ /dev/null @@ -1,136 +0,0 @@ -# Library overview - -`dsi` messages are no longer an unknown. - -- Successfully sent `dsi` packets to a masternode - -# Utility scripts - -See the `bin/` directory for scripts that will make your life a bit easier - -# Current challenges - -- None as of yet. - -# Next steps - -- When ERR_INVALID_COLLATERAL message is received, mark the transaction used - then resend the dsa message -- Study, document, and write the code for the flow after the `dsi` packet has - been sent and processed by the masternode. - - see: - [The CoinJoin docs](https://docs.dash.org/projects/core/en/stable/docs/reference/p2p-network-privatesend-messages.html#dsi) - -# Current masternode output - -``` -2023-06-24T11:56:56Z received: dsi (303 bytes) peer=836 -2023-06-24T11:56:56Z DSVIN -- txCollateral CTransaction(hash=6b3542a7f7, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(7c895ac26da93750d74f8099ab854285756642a7189e25182609434746375ea2, 0), scriptSig=48304502210098fdb15341f9) - CTxOut(nValue=0.00020000, scriptPubKey=76a914e1af1b71364a8e3d434aff68) - CTxOut(nValue=0.00030001, scriptPubKey=76a91463fc8ffa9e529089eb67ddaf) -2023-06-24T11:56:56Z CCoinJoin::IsCollateralValid -- CTransaction(hash=6b3542a7f7, ver=3, type=0, vin.size=1, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(7c895ac26da93750d74f8099ab854285756642a7189e25182609434746375ea2, 0), scriptSig=48304502210098fdb15341f9) - CTxOut(nValue=0.00020000, scriptPubKey=76a914e1af1b71364a8e3d434aff68) - CTxOut(nValue=0.00030001, scriptPubKey=76a91463fc8ffa9e529089eb67ddaf) -2023-06-24T11:56:56Z CCoinJoinServer::AddEntry -- txin=CTxIn(COutPoint(a7c5b47ade1ce020045ca885ec9b2e603b3621bc977740e0ab95af70fc8b6e8e, 0), scriptSig=) -2023-06-24T11:56:56Z CCoinJoinBaseSession::IsValidInOuts -- txin=CTxIn(COutPoint(a7c5b47ade1ce020045ca885ec9b2e603b3621bc977740e0ab95af70fc8b6e8e, 0), scriptSig=) -2023-06-24T11:56:56Z CCoinJoinServer::AddEntry -- adding entry 2 of 20 required -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=836 -2023-06-24T11:56:56Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=835 -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=836 -2023-06-24T11:56:56Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:56:57Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:56:58Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:56:59Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:56:59Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:00Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:01Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:02Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:03Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:04Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:04Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:05Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:06Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:07Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:08Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:09Z received: ping (8 bytes) peer=374 -2023-06-24T11:57:09Z sending pong (8 bytes) peer=374 -2023-06-24T11:57:09Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:09Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:10Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:11Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:12Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:13Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:14Z sending ping (8 bytes) peer=374 -2023-06-24T11:57:14Z received: pong (8 bytes) peer=374 -2023-06-24T11:57:15Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:16Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:17Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:18Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:19Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:19Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:20Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:21Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:22Z Flushed 0 addresses to peers.dat 12ms -2023-06-24T11:57:22Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:23Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:24Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:24Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:24Z CCoinJoinServer::CreateFinalTransaction -- FINALIZE TRANSACTIONS -2023-06-24T11:57:24Z CCoinJoinServer::CreateFinalTransaction -- finalMutableTransaction=CMutableTransaction(hash=138fe4094a, ver=2, type=0, vin.size=2, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(9c1b85e06285f848a5c6e10a46a2425f3d63dc93cb4fdd77cfbee66cb8e1f93f, 0), scriptSig=) - CTxIn(COutPoint(a7c5b47ade1ce020045ca885ec9b2e603b3621bc977740e0ab95af70fc8b6e8e, 0), scriptSig=) - CTxOut(nValue=0.00100001, scriptPubKey=76a914397994d7cfb78fd668887dcd) - CTxOut(nValue=0.00100001, scriptPubKey=76a9143b519e1156f4b4a66a0d1d82) -2023-06-24T11:57:24Z CCoinJoinServer::SetState -- nState: 2, nStateNew: 3 -2023-06-24T11:57:24Z CCoinJoinServer::RelayFinalTransaction -- nSessionID: 343829 nSessionDenom: 16 (0.00100001) -2023-06-24T11:57:24Z sending dsf (164 bytes) peer=835 -2023-06-24T11:57:24Z sending dsf (164 bytes) peer=836 -2023-06-24T11:57:25Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:26Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:27Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:28Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:29Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:29Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:30Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:31Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:32Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:33Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:34Z CChainLocksHandler::EnforceBestChainLock -- enforcing block 5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e via CLSIG (CChainLockSig(nHeight=12780, blockHash=5d858a3950a7865cca9c18010d2b423bfd1aad31c0d9eb690969c20b50e52b0e)) -2023-06-24T11:57:34Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:57:35Z CCoinJoinServer::CheckPool -- entries count 2 - -``` - -# Specific features needed - -## Once `AddEntry` succeeds, parse the `dssu` packet it sends immediately after that - -``` -2023-06-24T11:56:56Z CCoinJoinServer::AddEntry -- adding entry 2 of 20 required -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=836 -2023-06-24T11:56:56Z CCoinJoinServer::CheckPool -- entries count 2 -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=835 -2023-06-24T11:56:56Z sending dssu (16 bytes) peer=836 -``` - -## Once FINALIZE TRANSACTIONS is hit, parse the `dsf` packet it sends after that - -``` -2023-06-24T11:57:24Z CCoinJoinServer::CreateFinalTransaction -- FINALIZE TRANSACTIONS -2023-06-24T11:57:24Z CCoinJoinServer::CreateFinalTransaction -- finalMutableTransaction=CMutableTransaction(hash=138fe4094a, ver=2, type=0, vin.size=2, vout.size=2, nLockTime=0, vExtraPayload.size=0) - CTxIn(COutPoint(9c1b85e06285f848a5c6e10a46a2425f3d63dc93cb4fdd77cfbee66cb8e1f93f, 0), scriptSig=) - CTxIn(COutPoint(a7c5b47ade1ce020045ca885ec9b2e603b3621bc977740e0ab95af70fc8b6e8e, 0), scriptSig=) - CTxOut(nValue=0.00100001, scriptPubKey=76a914397994d7cfb78fd668887dcd) - CTxOut(nValue=0.00100001, scriptPubKey=76a9143b519e1156f4b4a66a0d1d82) -2023-06-24T11:57:24Z CCoinJoinServer::SetState -- nState: 2, nStateNew: 3 -2023-06-24T11:57:24Z CCoinJoinServer::RelayFinalTransaction -- nSessionID: 343829 nSessionDenom: 16 (0.00100001) -2023-06-24T11:57:24Z sending dsf (164 bytes) peer=835 -2023-06-24T11:57:24Z sending dsf (164 bytes) peer=836 -``` - -# Document date - -Sat Jun 24 12:26:13 UTC 2023 diff --git a/dsf-7250bb2a2e294f728081f50ee2bdd3a1.dat b/dsf-7250bb2a2e294f728081f50ee2bdd3a1.dat deleted file mode 100644 index 9551ca7..0000000 Binary files a/dsf-7250bb2a2e294f728081f50ee2bdd3a1.dat and /dev/null differ diff --git a/dsf-f49bf6f8c5c344e8ba00b0dc83a4fbdb.dat b/dsf-f49bf6f8c5c344e8ba00b0dc83a4fbdb.dat deleted file mode 100644 index 9551ca7..0000000 Binary files a/dsf-f49bf6f8c5c344e8ba00b0dc83a4fbdb.dat and /dev/null differ diff --git a/fixtures/anyonecanpay.0.dsa.hex b/fixtures/anyonecanpay.0.dsa.hex new file mode 100644 index 0000000..9f7cce8 --- /dev/null +++ b/fixtures/anyonecanpay.0.dsa.hex @@ -0,0 +1 @@ +fcc1b7dc647361000000000000000000c300000094172680100000000300000001520aa672368ee5b328eea6975f8ed85462f6ca6000a7eca571b00b5b9812cf83000000006a47304402204dfe8089d22da2130a8236d9c9cb4361bf29f664de47b7c3f7ea1c6523b0d47802204cb5c2938cda0c964f5d05f3ab1ee951b6bd827ce35ecd2890e9b9c74ddf43d40121035efd987d60495fc8180351558e4ee58b8c5d86eb36498e0182b9d1d26d22198bffffffff0179951d06000000001976a914da16dbe25379eb63ec0a1866cef414ee0f318a5b88ac00000000 diff --git a/fixtures/anyonecanpay.0.input-key.hex b/fixtures/anyonecanpay.0.input-key.hex new file mode 100644 index 0000000..be620b9 --- /dev/null +++ b/fixtures/anyonecanpay.0.input-key.hex @@ -0,0 +1 @@ +cVT5fWT5F4V4EkCK9d57LT1g3CoG18aqz1E2DGMy6KH1Cc5LnjkS diff --git a/fixtures/anyonecanpay.0.input.addr b/fixtures/anyonecanpay.0.input.addr new file mode 100644 index 0000000..6c0a6bc --- /dev/null +++ b/fixtures/anyonecanpay.0.input.addr @@ -0,0 +1,2 @@ +# index: 116 +yZV88MAMnV5Tw2NYQHLmz6NYCCVMX5vvpD diff --git a/fixtures/anyonecanpay.1.dsf.hex b/fixtures/anyonecanpay.1.dsf.hex new file mode 100644 index 0000000..3ddf06b --- /dev/null +++ b/fixtures/anyonecanpay.1.dsf.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000001976a914f3dc0395df80b4640d9940c3277bccc6f148421588acffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.1.tx-raw.hex b/fixtures/anyonecanpay.1.tx-raw.hex new file mode 100644 index 0000000..3ddf06b --- /dev/null +++ b/fixtures/anyonecanpay.1.tx-raw.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000001976a914f3dc0395df80b4640d9940c3277bccc6f148421588acffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.2.input-hash.hex b/fixtures/anyonecanpay.2.input-hash.hex new file mode 100644 index 0000000..8bd6aac --- /dev/null +++ b/fixtures/anyonecanpay.2.input-hash.hex @@ -0,0 +1 @@ +c9c9b979ecf34b1c6c4091cf8e83d8ae8b0823886b749b924dea75e5656bbe3f diff --git a/fixtures/anyonecanpay.2.input-sig.hex b/fixtures/anyonecanpay.2.input-sig.hex new file mode 100644 index 0000000..4c60fed --- /dev/null +++ b/fixtures/anyonecanpay.2.input-sig.hex @@ -0,0 +1 @@ +3045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf5 diff --git a/fixtures/anyonecanpay.2.input-tx.hex b/fixtures/anyonecanpay.2.input-tx.hex new file mode 100644 index 0000000..541e538 --- /dev/null +++ b/fixtures/anyonecanpay.2.input-tx.hex @@ -0,0 +1,2 @@ +02000000010db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000001976a914906e2ab1d4fdb9a5f4ff4f2e6538607a697dbb0e88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 +81000000 diff --git a/fixtures/anyonecanpay.3.dss.hex b/fixtures/anyonecanpay.3.dss.hex new file mode 100644 index 0000000..5a6fa67 --- /dev/null +++ b/fixtures/anyonecanpay.3.dss.hex @@ -0,0 +1,3 @@ +fcc1b7dc6473730000000000000000002901000093ebbbfb02f60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000006b483045022100ba34 +5735b58b47536c6534b8832ef727a525c1010e862781ad5263f91d185c9a022076744d504d3153b716fbb5c46f86628482a472eb23d061510aa8cb8970c6d8158121024de8fc8b47173306579f4b39b7f8c9a4491d0487f968a89074e44ddf397fc1b7ffffffff +0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000006b483045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf58121026bd10466e924ab4ab8be5f0a0daf65f6468e20c8a205f2df4fbdf510010a8ba0ffffffff diff --git a/fixtures/anyonecanpay.3.tx.hex b/fixtures/anyonecanpay.3.tx.hex new file mode 100644 index 0000000..98260ee --- /dev/null +++ b/fixtures/anyonecanpay.3.tx.hex @@ -0,0 +1 @@ +0200000004172754369ee634872efbc09f603c47c77770cd54dbd4fb533cefdcc0dbeb2601030000001976a914ac7b14bcd04d544914d90d01e0c1a1d4caba6abf88acfffffffff60f3e363041c9ba2130f07fd9fe32ad4a3f5034bdcf4a388b27f6c5f646d14f040000006b483045022100ba345735b58b47536c6534b8832ef727a525c1010e862781ad5263f91d185c9a022076744d504d3153b716fbb5c46f86628482a472eb23d061510aa8cb8970c6d8158121024de8fc8b47173306579f4b39b7f8c9a4491d0487f968a89074e44ddf397fc1b7ffffffff0db36506286ea00bc69c46bc9b90474d5b4a1c71553b44674a0ceec60e22ed68020000006b483045022100b46cdff770bb50ad4248af8c5c6169236b99d2a3f050951e6b84c9ab3d316f9202207f0370b7a7c31def382955a27c9234ad74538c1e07ad4e2dfa32902f8982acf58121026bd10466e924ab4ab8be5f0a0daf65f6468e20c8a205f2df4fbdf510010a8ba0ffffffff5d02ec3c1f151f4459bd3bea8ee7e05b94866b6702ecb31daf8363294c6c8f6b000000001976a9140b937639d4add223ac30d3ed70c62fe5f271bd0c88acffffffff04a1860100000000001976a914393357d3c0244c6934c5177ab4bcf84cd8d7a10a88aca1860100000000001976a914416298463aa890595e6b5a9c633ff237d6664eef88aca1860100000000001976a9144877d5d7132a9a2a9987445bb109213a5b7d461188aca1860100000000001976a9145488fda54d3e7469d19aca28a226ea521a9e920988ac00000000 diff --git a/fixtures/anyonecanpay.4.dsc.hex b/fixtures/anyonecanpay.4.dsc.hex new file mode 100644 index 0000000..a7bc570 --- /dev/null +++ b/fixtures/anyonecanpay.4.dsc.hex @@ -0,0 +1,4 @@ +fcc1b7dc +647363000000000000000000080000006265a450 +6ab00000 +14000000 diff --git a/fixtures/dsa.hex b/fixtures/dsa.hex new file mode 100644 index 0000000..526cf6d --- /dev/null +++ b/fixtures/dsa.hex @@ -0,0 +1,2 @@ +fcc1b7dc647361000000000000000000c40000004caa3c88 +04000000030000000127c03b36308e0925666682de270cdaa212b696776ac3529242851e338c627a1c000000006b4830450221008e1c7087a46e80e0bef0175b09a060f6b38f19dace07019819d4ab9addc0b8560220284e59145ec926e794d8f9150df9c5568fe7add6d54712208e607891be9719f30121026215ac5afb5a890d06e4eb07ed6383fb4cd46ca0de222cca4c4f8c9fcf9a6023ffffffff01bbae9606000000001976a914ef46d2e30c714916c43676364b27657ed753ef0788ac00000000 diff --git a/fixtures/dsf.hex b/fixtures/dsf.hex new file mode 100644 index 0000000..25af44d --- /dev/null +++ b/fixtures/dsf.hex @@ -0,0 +1,41 @@ +fcc1b7dc 647366000000000000000000 54050000 58eecedc574b0400 +02000000 +12 +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 00000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 01000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 02000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 03000000 00 ffffffff +7608252b5fa88eb470a21231637bbd6abf2abf2697dc306f9d182aecc9e77c25 04000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 01000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 02000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 03000000 00 ffffffff +191e8802ddc8345b2cfac9fa765dce581e178dc1d1c7a4c32a1c40f36607ec45 04000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 00000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 01000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 02000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 03000000 00 ffffffff +cb965a15bf7007d3037ffb7a2cefedb2e47c880ccd48ab5f07efa6be4d5eba76 04000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 01000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 02000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 03000000 00 ffffffff +0e94d350ef62f7e11b2e5e2ad7d5c1fd029a46bbce5900aa26329188839e61b2 04000000 00 ffffffff +12 +a186010000000000 19 7619 14 2180e2e082e504e2ff99300bb47b74d0bbe6fa5a 88ac +a186010000000000 19 7619 14 3a2142b82222413e45a469d975f185023a33ef2d 88ac +a186010000000000 19 7619 14 4a271dd96a7e096db0174fef0a95b1ab2131cabf 88ac +a186010000000000 19 7619 14 4ae6f64be49c8a9dad05091c33843d5c30ba7126 88ac +a186010000000000 19 7619 14 580c7812093a680367e2ea24f9503ee8c3731e6d 88ac +a186010000000000 19 7619 14 61ce8d35674e020bd91f63d175ad666cfd273b76 88ac +a186010000000000 19 7619 14 660f50b55e635ea347efd22314666024fd25990c 88ac +a186010000000000 19 7619 14 6dbe907ca08a5f893752449a6e5c67e0b8110938 88ac +a186010000000000 19 7619 14 74bc30a19f16aee3e8b48a8b5b7ccdde445bc8c6 88ac +a186010000000000 19 7619 14 7568a7c69ef2357459376f8c3d638bd205705fb6 88ac +a186010000000000 19 7619 14 8ec49f7e482b6ceb658156d9669d4b348843a179 88ac +a186010000000000 19 7619 14 94c1d34dfd35b5e5c108aab7dc8a82c6d046c0e4 88ac +a186010000000000 19 7619 14 9a2bb835bd557437fcdbdb09b3e1de86bf529cdf 88ac +a186010000000000 19 7619 14 9f7fc53ec2c2849da00d6b1ec6ded26d5a6409fa 88ac +a186010000000000 19 7619 14 af44b235ce58918edcb47c957f942f3fcb2ae1d8 88ac +a186010000000000 19 7619 14 b6055ef1bd4266a0b284fc7e34241cc4211f80d6 88ac +a186010000000000 19 7619 14 d547b11f8612213e7a22f2aefb5669a5e4242fc9 88ac +a186010000000000 19 7619 14 e3d4e7e53b80c1cc45268acc82921923fadb029e 88ac +00000000 diff --git a/fixtures/dsi.hex b/fixtures/dsi.hex new file mode 100644 index 0000000..b31c05d --- /dev/null +++ b/fixtures/dsi.hex @@ -0,0 +1,78 @@ +fcc1b7dc647369000000000000000000a3010000ed146dfd + +03 + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 02000000 + 00 + ffffffff + + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 0f000000 + 00 + ffffffff + + 36bdc3796c5630225f2c86c946e2221a + 9958378f5d08da380895c2656730b5c0 + 0d000000 + 00 + ffffffff + +01000000 + 01 + 83bd1980c71c38f035db9b14d7f934f7 + d595181b3436e36289902619f3f7d383 + 00000000 + 6b + 483045022100f4d8fa0ae4132235fecd540a + 62715ccfb1c9a97f8698d066656e30bb1e1e + 06b90220301b4cc93f38950a69396ed89dfc + c08e72ec8e6e7169463592a0bf504946d98b + 812102fa4b9c0f9e76e06d57c75cab9c8368 + a62a1ce8db6eb0c25c3e0719ddd9ab549c + ffffffff + + 01 + e093040000000000 + 19 + 76 + a9 + 14 + f8956a4eb0e53b05ee6b30edfd2770b5 + 26c1f1bb + 88 + ac + + 00000000 + +03 + e8e4f50500000000 + 19 + 76 + a9 + 14 + 14826d7ba05cf76588a5503c03951dc9 + 14c91b6c + 88 + ac + + e8e4f50500000000 + 19 + 76 + a9 + 14 + f01197177de2358928196a543b2bbd97 + 3c2ab002 + 88 + ac + + e8e4f50500000000 + 19 + 76 + a9 + 14 + 426614716e94812d483bca32374f6ac8 + cd121b0d + 88 + ac diff --git a/fixtures/dsq.hex b/fixtures/dsq.hex new file mode 100644 index 0000000..3159e8e --- /dev/null +++ b/fixtures/dsq.hex @@ -0,0 +1,2 @@ +fcc1b7dc6473710000000000000000008e000000968ff020 +04000000d03171667a17b053a987bb519cff4fb73fa8718eb7bc68c51cc0f3b44d5f9f58b9dc2d66000000000160ab6e501a57e993271ab86e230177b50b32a7cab720016c79b8acf2eb4dfa5a8b3a930fa5c1e2dd1d92111b377b0c40a80e06083f9f06bb7a78ec87a085c8c67ee4a9655d2e14ba4c8caac0116484fe2a19813f6cf7065fead64efd7f1c4139a7 diff --git a/fixtures/dsq.json b/fixtures/dsq.json new file mode 100644 index 0000000..a6301d8 --- /dev/null +++ b/fixtures/dsq.json @@ -0,0 +1,11 @@ +{ + "denomination_id": 4, + "denomination": 10000100, + "protxhash_bytes": null, + "protxhash": "d03171667a17b053a987bb519cff4fb73fa8718eb7bc68c51cc0f3b44d5f9f58", + "timestamp_unix": 1714281657, + "timestamp": "2024-04-28T05:20:57.000Z", + "ready": true, + "signature_bytes": null, + "signature": "60ab6e501a57e993271ab86e230177b50b32a7cab720016c79b8acf2eb4dfa5a8b3a930fa5c1e2dd1d92111b377b0c40a80e06083f9f06bb7a78ec87a085c8c67ee4a9655d2e14ba4c8caac0116484fe2a19813f6cf7065fead64efd7f1c4139a7" +} diff --git a/fixtures/dssu.hex b/fixtures/dssu.hex new file mode 100644 index 0000000..8760484 --- /dev/null +++ b/fixtures/dssu.hex @@ -0,0 +1,2 @@ +fcc1b7dc64737375000000000000000010000000889be7ee +5ecd0a00010000000100000013000000 diff --git a/fixtures/dssu.json b/fixtures/dssu.json new file mode 100644 index 0000000..355e4a9 --- /dev/null +++ b/fixtures/dssu.json @@ -0,0 +1,9 @@ +{ + "session_id": 707934, + "state_id": 1, + "state": "QUEUE", + "status_id": 1, + "status": "ACCEPTED", + "message_id": 19, + "message": "MSG_NOERR" +} diff --git a/fixtures/he-1-asn1-sig.hex b/fixtures/he-1-asn1-sig.hex new file mode 100644 index 0000000..afc7698 --- /dev/null +++ b/fixtures/he-1-asn1-sig.hex @@ -0,0 +1,2 @@ +30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d diff --git a/fixtures/he-1-hashtx.hex b/fixtures/he-1-hashtx.hex new file mode 100644 index 0000000..2073560 --- /dev/null +++ b/fixtures/he-1-hashtx.hex @@ -0,0 +1,41 @@ +02000000 + +0b +82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c00 00000000 00 +ffffffff +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +19 76 a9 14 37b00a500178dfb1bb95d66fe7ba10d9baf9d14e 88 ac ffffffff +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 00 +ffffffff +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 00 +ffffffff +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 00 +ffffffff +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 00 +ffffffff +a2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f38195 07000000 00 +ffffffff +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 00 +ffffffff +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 00 +ffffffff +177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb7 02000000 00 +ffffffff +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 00 +ffffffff + +0b +a186010000000000 19 76 a9 14 1e624bcdc64f72e4e68eacc59d40ab7a3400aeae 88 ac +a186010000000000 19 76 a9 14 335194d41dbc6c7e71b263711f0319a6bef5b452 88 ac +a186010000000000 19 76 a9 14 75922991d66988fd0defee70a60753728882d59b 88 ac +a186010000000000 19 76 a9 14 7c47c66f6c7a09e20be326692b42f43576a6249e 88 ac +a186010000000000 19 76 a9 14 93a314141f390ddd111f9be2b550e8e74b703043 88 ac +a186010000000000 19 76 a9 14 b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c 88 ac +a186010000000000 19 76 a9 14 cfd97c2953dbda72b66d1e37d8e8f8acef4e3d49 88 ac +a186010000000000 19 76 a9 14 d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e 88 ac +a186010000000000 19 76 a9 14 df35875deec179fa1d39d7182fe2d746ff1a2e21 88 ac +a186010000000000 19 76 a9 14 e58de7fe595532146be33e08493ed2b26f18251c 88 ac +a186010000000000 19 76 a9 14 fdebd30c80543b3c72b5cfff7c788e63d63d29a6 88 ac + +00000000 +01000000 diff --git a/fixtures/he-1-p2pkh.hex b/fixtures/he-1-p2pkh.hex new file mode 100644 index 0000000..6d9981a --- /dev/null +++ b/fixtures/he-1-p2pkh.hex @@ -0,0 +1 @@ +76a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88ac diff --git a/fixtures/he-1-privkey.hex b/fixtures/he-1-privkey.hex new file mode 100644 index 0000000..2e49c48 --- /dev/null +++ b/fixtures/he-1-privkey.hex @@ -0,0 +1 @@ +107da4635cb63a387ec4c17258d600c8813599b7ec72893d5bab0bf3aa514788 diff --git a/fixtures/he-1-sighash.hex b/fixtures/he-1-sighash.hex new file mode 100644 index 0000000..67623b4 --- /dev/null +++ b/fixtures/he-1-sighash.hex @@ -0,0 +1 @@ +35a639ce51073d0be3531fa724745fa3c1bf1c609132cff4c98e8045ccce235f diff --git a/fixtures/he-1-utxo.json b/fixtures/he-1-utxo.json new file mode 100644 index 0000000..0674e7f --- /dev/null +++ b/fixtures/he-1-utxo.json @@ -0,0 +1,5 @@ +{ + "_txid": "c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f", + "txid": "3f807423b29a738f31be9af3fff0fb1a66f87d6952336bd3d62c68a904b647c5", + "outputIndex": 3 +} diff --git a/fixtures/he-cj-full.log b/fixtures/he-cj-full.log new file mode 100644 index 0000000..99376d3 --- /dev/null +++ b/fixtures/he-cj-full.log @@ -0,0 +1,58 @@ +Known-working, coinjoin transaction, broadcast at: +https://insight.testnet.networks.dash.org:3002/insight/address/yRPtuFHK7Czn7GhfpGLQAYvsGjGkojhzS4 + +08:47:22 27 CoinJoinClientSession.processFinalTransaction: DSFINALTX: +dsf: +80eb0100020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac00000000 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 1 with key 107da4635cb63a387ec4c17258d600c8813599b7ec72893d5bab0bf3aa514788 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end: +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f030000001976a91437b00a500178dfb1bb95d66fe7ba10d9baf9d14e88acffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 35a639ce51073d0be3531fa724745fa3c1bf1c609132cff4c98e8045ccce235f + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914786381547e752eaa9a06f70bb1265145e685753988ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 2 with key 325c50fff4c6cb307ec5eda1310e2766d632091d984ff19ad3848e1e65db742a +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46040000001976a914786381547e752eaa9a06f70bb1265145e685753988acffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 5c564906aa2496f00b789665a042d03c1135ee3e7c9a129822697c85ac68667a + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91419c171c953f1766d325a9181909380b0a31595fd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 3 with key b1ae60833f531c642dbda4d509462e6f5dde064d217c88547ceef18360b8bb54 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58010000001976a91419c171c953f1766d325a9181909380b0a31595fd88acffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash c9ca6800a866e63d31eebe62ab9d31e49bccb0b08b19af8b0b53031396f0280b + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914ccf7c90b25ecf67011e160bd7ca99fadaa8d272888ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 4 with key 5dc84baacdc67aea283c64270500b072d65b7aa2bced91998f9c2fe599378d17 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a0000001976a914ccf7c90b25ecf67011e160bd7ca99fadaa8d272888acffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 9ab23bb0d34ba3b1520ffdd7f838d41dd183762ee5330b7c983c7a8ea9be5d57 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914ffe035d9e39100e9d179601ae3ee2f2223c864fd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 5 with key 33479550caf0a09153fd3d22e884581e2a0e28f397b5a56c02a846409e4e7bd7 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95090000001976a914ffe035d9e39100e9d179601ae3ee2f2223c864fd88acffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash e8798f8b97df67ea033bdc2ea896e15919f99f4ac8a3984afd0cdf2ff8bee69f + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914b1c59bf3ce5bd944c5213259c9398a0edbaa724188ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 7 with key 9e38eadc76529b88f1cb983f894fea4dd93042e18f47717c3c90f0229abc0909 +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a090000001976a914b1c59bf3ce5bd944c5213259c9398a0edbaa724188acffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash 5427e074bc5c932998cee33f660dc5cbf5a158f734e7a87b13236ad2caf06a29 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a91483b7f4eb6e8f004ae4c37dfca20c15b4daac746488ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 8 with key b8df0a2e512cad7a13dc83816eea7d35a2e972c9b11ed04e56fc619ca974cefe +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa080000001976a91483b7f4eb6e8f004ae4c37dfca20c15b4daac746488acffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d60700000000ffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash eaa607b7b47db553f0ab6a8214c621b6a055b58b76c87b23c51f22193a888d78 + +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: script pub key 76a914903dda3fb6996c5788ae855b696f0c276a80a6dd88ac +08:47:22 27 CoinJoinTransactionSigner.signInputs: DSFINAL: signing input: 10 with key 0ff7f48db747dcdf5607186460d143dfb0233c083c1cbc6722bb32acaf9b54dd +08:47:22 27 Transaction.hashForSignature: DSFINAL: ready-to-sign transaction with the sighash byte at the end +020000000b82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c000000000000ffffffffc547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f0300000000ffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e460400000000ffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed580100000000ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a00000000ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f950900000000ffffffffa2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f381950700000000ffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a0900000000ffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa0800000000ffffffff177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb70200000000ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6070000001976a914903dda3fb6996c5788ae855b696f0c276a80a6dd88acffffffff0ba1860100000000001976a9141e624bcdc64f72e4e68eacc59d40ab7a3400aeae88aca1860100000000001976a914335194d41dbc6c7e71b263711f0319a6bef5b45288aca1860100000000001976a91475922991d66988fd0defee70a60753728882d59b88aca1860100000000001976a9147c47c66f6c7a09e20be326692b42f43576a6249e88aca1860100000000001976a91493a314141f390ddd111f9be2b550e8e74b70304388aca1860100000000001976a914b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c88aca1860100000000001976a914cfd97c2953dbda72b66d1e37d8e8f8acef4e3d4988aca1860100000000001976a914d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e88aca1860100000000001976a914df35875deec179fa1d39d7182fe2d746ff1a2e2188aca1860100000000001976a914e58de7fe595532146be33e08493ed2b26f18251c88aca1860100000000001976a914fdebd30c80543b3c72b5cfff7c788e63d63d29a688ac0000000001000000 +08:47:22 27 Transaction.calculateSignature: DSFINAL: sig hash c443f57d11d30ff9b9e7c3f4f02b545d6cfb1edf91d51a488141a43af58f5835 + +08:47:22 27 CoinJoinClientSession.signFinalTransaction: DSFINAL: +dss: +08c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f030000006b48304502210085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b602207730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d0121030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ecffffffffc0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46040000006a47304402204316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a022058bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba0121023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7dffffffff204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58010000006a47304402200b049725050b28a4a42f80db28bd8e48f266e2a3460cf6a798457420fa54bd0d02204edd66bf280cfda43019f48394439009cb234ee5871c2ec490d4d7ba84e6a461012102dde437be25a79dedc6dbf4cf73ac8e3eefd90112ddae827c9042b43d0e8d9bf6ffffffff86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d770a0000006b4830450221009048e55404378c68962bdb4c2acb4ea6592a040ac17be5f5cc2a957b087930620220694e165128a6c40c3bc4f77b369949d80606b9bdcf34e2dd8622107b9a87e3ff0121025064ef597ec1520e2e3b7836261457eb79522299314703a7beeea2bbf42b9175ffffffffe6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95090000006a473044022059c43a7d1327d36adce6cfef92e5e2159c83dc9ebc6b28c5ac8c1ec46e6cdcb2022050dea4f95e4a8a6c4a06d71e6c7e5fc9b7249a967f09bd4283c2ed08a1225ed601210257f68289f77638b808706bced459895b349001a5d86641da619886474e9b417cffffffffc05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a090000006a47304402205b3398b3ea59a6ebe5cad2be234b64ddc852057197aaf7071c5621739e54ed5f02201330850ddd04f318cc5cffb8ce8464dc0d17dcc8e8d37c84db8bdef0b980363f01210297f200b4f0dc3967d77d822189276d60e6baad8075d954be933724dfa7ded74fffffffff697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa080000006a47304402203c3023ee03dee54982ef2176c6b26c81be06d8c09fb19e98ef98c257210f3c0e0220193b4f24aa9cc5720186dd02c05c9257a46383eed23586a3348d4b9af2d3f304012103e943423c41299842d5532a7800d0e7f6d4a4e89a68b992f071988e3325ca3e82ffffffffe2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6070000006b483045022100de784c07605de564af098672900651b901bbbfd05a9daa51b51d50f414073e16022020881559ada92c99777d1ec103dded571cbb8a9cfc0052262fbd3bcf14ac9dfe01210336d148cfc9a3a3603fa27aca926a73e2d47861194871f1473d9cc173c9ee5df0ffffffff diff --git a/fixtures/he-dsf.hex b/fixtures/he-dsf.hex new file mode 100644 index 0000000..3efe7b0 --- /dev/null +++ b/fixtures/he-dsf.hex @@ -0,0 +1,30 @@ +80eb0100 +02000000 + +0b +82c0158cb7847c69e7b8234692595d39ce8ea94dd9d77d8ae261bed1fa741c00 00000000 00 ffffffff +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 00 ffffffff +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 00 ffffffff +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 00 ffffffff +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 00 ffffffff +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 00 ffffffff +a2b2b771242df3d263317b508549940c47e61ba5420d30820a403be5f0f38195 07000000 00 ffffffff +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 00 ffffffff +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 00 ffffffff +177af5a77194cd370d1dca2a24a40a116be1fe22cdfa33aa3ac0ae73d6be6cb7 02000000 00 ffffffff +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 00 ffffffff + +0b +a186010000000000 19 76 a9 14 1e624bcdc64f72e4e68eacc59d40ab7a3400aeae 88 ac +a186010000000000 19 76 a9 14 335194d41dbc6c7e71b263711f0319a6bef5b452 88 ac +a186010000000000 19 76 a9 14 75922991d66988fd0defee70a60753728882d59b 88 ac +a186010000000000 19 76 a9 14 7c47c66f6c7a09e20be326692b42f43576a6249e 88 ac +a186010000000000 19 76 a9 14 93a314141f390ddd111f9be2b550e8e74b703043 88 ac +a186010000000000 19 76 a9 14 b705bfd8367fcacd8a4b0d6c08bd6862eaa96b2c 88 ac +a186010000000000 19 76 a9 14 cfd97c2953dbda72b66d1e37d8e8f8acef4e3d49 88 ac +a186010000000000 19 76 a9 14 d0ee85ec9e29373f29fd922b4b1aa049c8b8e11e 88 ac +a186010000000000 19 76 a9 14 df35875deec179fa1d39d7182fe2d746ff1a2e21 88 ac +a186010000000000 19 76 a9 14 e58de7fe595532146be33e08493ed2b26f18251c 88 ac +a186010000000000 19 76 a9 14 fdebd30c80543b3c72b5cfff7c788e63d63d29a6 88 ac + +00000000 diff --git a/fixtures/he-dss-partial.hex b/fixtures/he-dss-partial.hex new file mode 100644 index 0000000..b772d2c --- /dev/null +++ b/fixtures/he-dss-partial.hex @@ -0,0 +1,13 @@ +02 + +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +6b 48 30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d + 01 21 030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ec +ffffffff + +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 +6a 47 30 44 02 20 4316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a + 02 20 58bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba + 01 21 023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7d +ffffffff diff --git a/fixtures/he-dss.hex b/fixtures/he-dss.hex new file mode 100644 index 0000000..5dfb63e --- /dev/null +++ b/fixtures/he-dss.hex @@ -0,0 +1,49 @@ +08 + +c547b604a9682cd6d36b3352697df8661afbf0fff39abe318f739ab22374803f 03000000 +6b 48 30 45 02 21 0085e8bd03a92422fc8f656ded2c19ceb70c68707a1699dd54782c4395532022b6 + 02 20 7730c221bcf818c0b0445f576b36d2ee0698edc4d3fe872aeaff772122e4251d + 01 21 030c467477c7d4aefb377cbe11c48765669676c556d0149c06da25fd0c2ade39ec +ffffffff + +c0c771b0ce74bf4fa4845c990f4ec4faff7374fd6344c0b25f41dd30ebbe5e46 04000000 +6a 47 30 44 02 20 4316f56e90d27505ecb938e52391cc9b0b77474b67283983a63463d0823a813a + 02 20 58bcb6eab28c2f4f936ba85183e750d010808e1bb11201fab5bae6d5a25391ba + 01 21 023c2ff18948d68270f5e150456caa8c1fcb0e3ed33e68340b5e087fd6651cbe7d +ffffffff + +204578c8a6564fb12d78e304420ed8738514712ce219c4611cef6b0bdb36ed58 01000000 +6a 47 30 44 02 20 0b049725050b28a4a42f80db28bd8e48f266e2a3460cf6a798457420fa54bd0d + 02 20 4edd66bf280cfda43019f48394439009cb234ee5871c2ec490d4d7ba84e6a461 + 01 21 02dde437be25a79dedc6dbf4cf73ac8e3eefd90112ddae827c9042b43d0e8d9bf6 +ffffffff + +86473250bdabf066fd57bad86de9f141849112e395ea777aceeb0e0fb8388d77 0a000000 +6b 48 30 45 02 21 009048e55404378c68962bdb4c2acb4ea6592a040ac17be5f5cc2a957b08793062 + 02 20 694e165128a6c40c3bc4f77b369949d80606b9bdcf34e2dd8622107b9a87e3ff + 01 21 025064ef597ec1520e2e3b7836261457eb79522299314703a7beeea2bbf42b9175 +ffffffff + +e6cb88bd8c3418b3665cc2987e778762e58f461fa84737315622923453c61f95 09000000 +6a 47 30 44 02 20 59c43a7d1327d36adce6cfef92e5e2159c83dc9ebc6b28c5ac8c1ec46e6cdcb2 + 02 20 50dea4f95e4a8a6c4a06d71e6c7e5fc9b7249a967f09bd4283c2ed08a1225ed6 + 01 21 0257f68289f77638b808706bced459895b349001a5d86641da619886474e9b417c +ffffffff + +c05155aa36cd041a8fa5e8a676877adccecae72f02561379275fb29d429fa79a 09000000 +6a 47 30 44 02 20 5b3398b3ea59a6ebe5cad2be234b64ddc852057197aaf7071c5621739e54ed5f + 02 20 1330850ddd04f318cc5cffb8ce8464dc0d17dcc8e8d37c84db8bdef0b980363f + 01 21 0297f200b4f0dc3967d77d822189276d60e6baad8075d954be933724dfa7ded74f +ffffffff + +697bccd54ca8c47eb9bf18d948c12226e44234ae1a4ea16c732533518e1074aa 08000000 +6a 47 30 44 02 20 3c3023ee03dee54982ef2176c6b26c81be06d8c09fb19e98ef98c257210f3c0e + 02 20 193b4f24aa9cc5720186dd02c05c9257a46383eed23586a3348d4b9af2d3f304 + 01 21 03e943423c41299842d5532a7800d0e7f6d4a4e89a68b992f071988e3325ca3e82 +ffffffff + +e2c1cea1e1532922532a2ec7bfa1cd33d202b5067e59decaa7025377ca2de4d6 07000000 +6b 48 30 45 02 21 00de784c07605de564af098672900651b901bbbfd05a9daa51b51d50f414073e16 + 02 20 20881559ada92c99777d1ec103dded571cbb8a9cfc0052262fbd3bcf14ac9dfe + 01 21 0336d148cfc9a3a3603fa27aca926a73e2d47861194871f1473d9cc173c9ee5df0 +ffffffff diff --git a/fixtures/mnauth.hex b/fixtures/mnauth.hex new file mode 100644 index 0000000..04bf001 --- /dev/null +++ b/fixtures/mnauth.hex @@ -0,0 +1,2 @@ +fcc1b7dc6d6e6175746800000000000080000000feef82b8 +7451ba14649d8bcc2268aa661e065202cda23fe78e190d26f0f16341c930f7c78bed254584fcdeb02ca90f05cf4851be9debfb5557c7bada57040b6cc85062009342c636decd5af9fadf3e5bff55eb150b4df12e122547b3cfc697e69afa7d2b86a743f64146ac4c253e998de946b0a4e99c9c521a862fbed91bc3bc5e14b749fcc1b7dc73656e646865616465727300000000005df6e0e2fcc1b7dc73656e64636d70637400000009000000ccfe104a000100000000000000fcc1b7dc73656e646473710000000000010000009c12cfdc01 diff --git a/fixtures/ping.hex b/fixtures/ping.hex new file mode 100644 index 0000000..4a00387 --- /dev/null +++ b/fixtures/ping.hex @@ -0,0 +1,2 @@ +fcc1b7dc70696e670000000000000000080000001c97a722 +adb85b0a56e96472 diff --git a/fixtures/sendaddrv2.hex b/fixtures/sendaddrv2.hex new file mode 100644 index 0000000..3de971e --- /dev/null +++ b/fixtures/sendaddrv2.hex @@ -0,0 +1 @@ +fcc1b7dc73656e646164647276320000000000005df6e0e2 diff --git a/fixtures/senddsq.hex b/fixtures/senddsq.hex new file mode 100644 index 0000000..50da297 --- /dev/null +++ b/fixtures/senddsq.hex @@ -0,0 +1,2 @@ +fcc1b7dc73656e646473710000000000010000009c12cfdc +01 diff --git a/fixtures/verack.hex b/fixtures/verack.hex new file mode 100644 index 0000000..cd473b1 --- /dev/null +++ b/fixtures/verack.hex @@ -0,0 +1 @@ +fcc1b7dc76657261636b000000000000000000005df6e0e2 diff --git a/fixtures/version.hex b/fixtures/version.hex new file mode 100644 index 0000000..fa99198 --- /dev/null +++ b/fixtures/version.hex @@ -0,0 +1,2 @@ +fcc1b7dc76657273696f6e0000000000890000002f9b4c82 +57120100050c00000000000023dc2d66000000000000000000000000000000000000000000000000000000000000050c000000000000000000000000000000000000000000000000d354bb37d36fa13c122f4461736820436f72653a32302e312e302f892600000179b1a12ee108929deac59da9996060a153519bca1ef85af184e58a3f75198f6900 diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..4283551 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,116 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": ["./typings","./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "*.js", + "bin/**/*.js", + "lib/**/*.js", + "src/**/*.js" +], + "exclude": ["node_modules"] +} diff --git a/package-lock.json b/package-lock.json index df1e535..4420166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,75 +1,31 @@ { - "name": "dashjoin.js", + "name": "dashjoin", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "dashjoin.js", + "name": "dashjoin", "version": "1.0.0", "license": "MIT", "dependencies": { - "@dashevo/dashcore-lib": "^0.20.6", "@dashincubator/secp256k1": "^1.7.1-5", - "@mentoc/xtract": "^1.0.3", - "dashtx": "^0.9.0", - "node-lmdb": "^0.9.7" + "dashhd": "^3.3.3", + "dashkeys": "^1.1.4", + "dashphrase": "^1.4.0", + "dashrpc": "^19.0.1", + "dashtx": "^0.18.1", + "dotenv": "^16.4.5" }, "devDependencies": { "mocha": "^10.2.0" } }, - "node_modules/@dashevo/bls": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@dashevo/bls/-/bls-1.2.9.tgz", - "integrity": "sha512-7+j0tQMi8fPmgcLgxnfvML2GW/A6xYA1cTLDLN2prfvV6V1OpH6EiwVGKTXBquJT0OQX++upClOKYUlIEQ8+0A==", - "dependencies": { - "binascii": "0.0.2" - } - }, - "node_modules/@dashevo/dashcore-lib": { - "version": "0.20.6", - "resolved": "https://registry.npmjs.org/@dashevo/dashcore-lib/-/dashcore-lib-0.20.6.tgz", - "integrity": "sha512-GDiQJZ8ZV3kyYSMdRgJ0kaNyzBj5xJa0+IKyQdSqLv5v+25E9MCfTrza8hxci29/Hm3yN/nnkGv0a/0BmV24wA==", - "dependencies": { - "@dashevo/bls": "~1.2.9", - "@dashevo/x11-hash-js": "^1.0.2", - "@types/node": "^12.12.47", - "bloom-filter": "^0.2.0", - "bn.js": "^4.12.0", - "bs58": "=4.0.1", - "elliptic": "^6.5.4", - "inherits": "=2.0.1", - "lodash": "^4.17.20", - "ripemd160": "^2.0.2", - "unorm": "^1.6.0" - } - }, - "node_modules/@dashevo/dashcore-lib/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" - }, - "node_modules/@dashevo/x11-hash-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@dashevo/x11-hash-js/-/x11-hash-js-1.0.2.tgz", - "integrity": "sha512-3vvnZweBca4URBXHF+FTrM4sdTpp3IMt73G1zUKQEdYm/kJkIKN94qpFai7YZDl87k64RCH+ckRZk6ruQPz5KQ==" - }, "node_modules/@dashincubator/secp256k1": { "version": "1.7.1-5", "resolved": "https://registry.npmjs.org/@dashincubator/secp256k1/-/secp256k1-1.7.1-5.tgz", "integrity": "sha512-3iA+RDZrJsRFPpWhlYkp3EdoFAlKjdqkNFiRwajMrzcpA/G/IBX0AnC1pwRLkTrM+tUowcyGrkJfT03U4ETZeg==" }, - "node_modules/@mentoc/xtract": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@mentoc/xtract/-/xtract-1.0.3.tgz", - "integrity": "sha512-0Fcz116CLucafGiXFhovFVo3QH+e7Cerxg5g6GBhmgARUfskF+q7dt6Izab0LdbgAe35ju0HJk9tA7bq0bf9bQ==" - }, - "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -128,14 +84,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -145,21 +93,6 @@ "node": ">=8" } }, - "node_modules/binascii": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/binascii/-/binascii-0.0.2.tgz", - "integrity": "sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==" - }, - "node_modules/bloom-filter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bloom-filter/-/bloom-filter-0.2.0.tgz", - "integrity": "sha512-RMG2RpnKczVzRsEYSPaT5rKsyj0w5/wpQRjaW4vOMe1WyUDQpoqxjNc10uROEjdhu63ytRt6aFRPXFePi/Rd7A==" - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -182,25 +115,12 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -303,10 +223,30 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/dashhd": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dashhd/-/dashhd-3.3.3.tgz", + "integrity": "sha512-sbhLV8EtmebnlIdx/d1hcbnxdfka/0rcLx+UO5y44kZdu5tyJ5ftBFbhhIb38vd+T+Xfcwpeo0z+0ZDznRkfaw==" + }, + "node_modules/dashkeys": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dashkeys/-/dashkeys-1.1.4.tgz", + "integrity": "sha512-y62hg+r8V56gqUfvnyNqCmQ0MvA/wGmRPWfuKFKDCZY02dvWkhCD0zBRVpBGPjfk+T2gWKbkcrHOCrb2RXZ9cw==" + }, + "node_modules/dashphrase": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dashphrase/-/dashphrase-1.4.0.tgz", + "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA==" + }, + "node_modules/dashrpc": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/dashrpc/-/dashrpc-19.0.1.tgz", + "integrity": "sha512-1BLXnYZPHHRwvehIF6HqLLSfv2bTZlU97dq/8XJ2F0cBEk3ofi9/fbxYVmDwWtVjqtIJPfdXhSFx7fJu2hJNPA==" + }, "node_modules/dashtx": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.9.0.tgz", - "integrity": "sha512-DDbH5vPChUpOrYMOoM+6g/Iy99KqG4nkJ6f8TphnGibzAY7mitjMgtFSc62YzbZdoPGYeSPm8N4jmz+Mbwm7Eg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.18.1.tgz", + "integrity": "sha512-5PA8E9tg5ykv6fm6E7fy345gz7pZSrZ+Ao1F8Sq+9CzSIKFPEVBoJm/1i+58An24VBvrrRJUSVq5v7KSGmwm9g==", "bin": { "dashtx-inspect": "bin/inspect.js" } @@ -355,18 +295,15 @@ "node": ">=0.3.1" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/emoji-regex": { @@ -515,28 +452,6 @@ "node": ">=8" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -546,16 +461,6 @@ "he": "bin/he" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -569,7 +474,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -670,11 +576,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -691,16 +592,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, "node_modules/minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -768,11 +659,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, "node_modules/nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -785,26 +671,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-lmdb": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/node-lmdb/-/node-lmdb-0.9.7.tgz", - "integrity": "sha512-RyYSB3kTByjp1yCKE474yuoBxi/+8kKvjYFOVuDBVoS31D6JvzjwMgNnQrMwoHSbZ6RwucW7jmF93Cm9ltziJQ==", - "hasInstallScript": true, - "dependencies": { - "nan": "^2.14.1", - "node-gyp-build": "^4.2.3" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -892,19 +758,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -926,19 +779,11 @@ "node": ">=0.10.0" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -963,14 +808,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1036,19 +873,6 @@ "node": ">=8.0" } }, - "node_modules/unorm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", - "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -1143,59 +967,11 @@ } }, "dependencies": { - "@dashevo/bls": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@dashevo/bls/-/bls-1.2.9.tgz", - "integrity": "sha512-7+j0tQMi8fPmgcLgxnfvML2GW/A6xYA1cTLDLN2prfvV6V1OpH6EiwVGKTXBquJT0OQX++upClOKYUlIEQ8+0A==", - "requires": { - "binascii": "0.0.2" - } - }, - "@dashevo/dashcore-lib": { - "version": "0.20.6", - "resolved": "https://registry.npmjs.org/@dashevo/dashcore-lib/-/dashcore-lib-0.20.6.tgz", - "integrity": "sha512-GDiQJZ8ZV3kyYSMdRgJ0kaNyzBj5xJa0+IKyQdSqLv5v+25E9MCfTrza8hxci29/Hm3yN/nnkGv0a/0BmV24wA==", - "requires": { - "@dashevo/bls": "~1.2.9", - "@dashevo/x11-hash-js": "^1.0.2", - "@types/node": "^12.12.47", - "bloom-filter": "^0.2.0", - "bn.js": "^4.12.0", - "bs58": "=4.0.1", - "elliptic": "^6.5.4", - "inherits": "=2.0.1", - "lodash": "^4.17.20", - "ripemd160": "^2.0.2", - "unorm": "^1.6.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" - } - } - }, - "@dashevo/x11-hash-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@dashevo/x11-hash-js/-/x11-hash-js-1.0.2.tgz", - "integrity": "sha512-3vvnZweBca4URBXHF+FTrM4sdTpp3IMt73G1zUKQEdYm/kJkIKN94qpFai7YZDl87k64RCH+ckRZk6ruQPz5KQ==" - }, "@dashincubator/secp256k1": { "version": "1.7.1-5", "resolved": "https://registry.npmjs.org/@dashincubator/secp256k1/-/secp256k1-1.7.1-5.tgz", "integrity": "sha512-3iA+RDZrJsRFPpWhlYkp3EdoFAlKjdqkNFiRwajMrzcpA/G/IBX0AnC1pwRLkTrM+tUowcyGrkJfT03U4ETZeg==" }, - "@mentoc/xtract": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@mentoc/xtract/-/xtract-1.0.3.tgz", - "integrity": "sha512-0Fcz116CLucafGiXFhovFVo3QH+e7Cerxg5g6GBhmgARUfskF+q7dt6Izab0LdbgAe35ju0HJk9tA7bq0bf9bQ==" - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1239,35 +1015,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "binascii": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/binascii/-/binascii-0.0.2.tgz", - "integrity": "sha512-rA2CrUl1+6yKrn+XgLs8Hdy18OER1UW146nM+ixzhQXDY+Bd3ySkyIJGwF2a4I45JwbvF1mDL/nWkqBwpOcdBA==" - }, - "bloom-filter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bloom-filter/-/bloom-filter-0.2.0.tgz", - "integrity": "sha512-RMG2RpnKczVzRsEYSPaT5rKsyj0w5/wpQRjaW4vOMe1WyUDQpoqxjNc10uROEjdhu63ytRt6aFRPXFePi/Rd7A==" - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1287,25 +1040,12 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "requires": { - "base-x": "^3.0.2" - } - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1381,10 +1121,30 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "dashhd": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dashhd/-/dashhd-3.3.3.tgz", + "integrity": "sha512-sbhLV8EtmebnlIdx/d1hcbnxdfka/0rcLx+UO5y44kZdu5tyJ5ftBFbhhIb38vd+T+Xfcwpeo0z+0ZDznRkfaw==" + }, + "dashkeys": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dashkeys/-/dashkeys-1.1.4.tgz", + "integrity": "sha512-y62hg+r8V56gqUfvnyNqCmQ0MvA/wGmRPWfuKFKDCZY02dvWkhCD0zBRVpBGPjfk+T2gWKbkcrHOCrb2RXZ9cw==" + }, + "dashphrase": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dashphrase/-/dashphrase-1.4.0.tgz", + "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA==" + }, + "dashrpc": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/dashrpc/-/dashrpc-19.0.1.tgz", + "integrity": "sha512-1BLXnYZPHHRwvehIF6HqLLSfv2bTZlU97dq/8XJ2F0cBEk3ofi9/fbxYVmDwWtVjqtIJPfdXhSFx7fJu2hJNPA==" + }, "dashtx": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.9.0.tgz", - "integrity": "sha512-DDbH5vPChUpOrYMOoM+6g/Iy99KqG4nkJ6f8TphnGibzAY7mitjMgtFSc62YzbZdoPGYeSPm8N4jmz+Mbwm7Eg==" + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.18.1.tgz", + "integrity": "sha512-5PA8E9tg5ykv6fm6E7fy345gz7pZSrZ+Ao1F8Sq+9CzSIKFPEVBoJm/1i+58An24VBvrrRJUSVq5v7KSGmwm9g==" }, "debug": { "version": "4.3.4", @@ -1415,19 +1175,10 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" }, "emoji-regex": { "version": "8.0.0", @@ -1531,41 +1282,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1579,7 +1301,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -1647,11 +1370,6 @@ "p-locate": "^5.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1662,16 +1380,6 @@ "is-unicode-supported": "^0.1.0" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, "minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -1727,31 +1435,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" - }, "nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, - "node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" - }, - "node-lmdb": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/node-lmdb/-/node-lmdb-0.9.7.tgz", - "integrity": "sha512-RyYSB3kTByjp1yCKE474yuoBxi/+8kKvjYFOVuDBVoS31D6JvzjwMgNnQrMwoHSbZ6RwucW7jmF93Cm9ltziJQ==", - "requires": { - "nan": "^2.14.1", - "node-gyp-build": "^4.2.3" - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1812,16 +1501,6 @@ "safe-buffer": "^5.1.0" } }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1837,19 +1516,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true }, "serialize-javascript": { "version": "6.0.0", @@ -1860,14 +1531,6 @@ "randombytes": "^2.1.0" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1912,16 +1575,6 @@ "is-number": "^7.0.0" } }, - "unorm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", - "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", diff --git a/package.json b/package.json index e425471..2ef8115 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,45 @@ { - "name": "dashjoin.js", + "name": "dashjoin", "version": "1.0.0", - "description": "A pure node.js implementation of DASH's CoinJoin/PrivateSend implementation.", + "description": "JavaScript reference implementation of CoinJoin", "main": "index.js", - "directories": { - "doc": "docs", - "test": "tests" - }, + "files": [ + "*.js" + ], "scripts": { - "demo": "./src/demo.js", + "bump": "npm version -m \"chore(release): bump to v%s\"", "fmt": "npm run prettier", - "test": "./node_modules/mocha/bin/mocha.js", - "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md}'" + "lint": "npm run jshint", + "--------": "-------------------------------------------------", + "jshint": "npx -p jshint@2.x -- jshint -c ./.jshintrc ./*.js", + "prettier": "npx -p prettier@3.x -- prettier -w '**/*.{js,md}'", + "tsc": "npx -p typescript@4.x -- tsc -p ./jsconfig.json", + "---------": "------------------------------------------------", + "test": "node ./tests/" }, "repository": { "type": "git", "url": "git+ssh://git@github.com/dashhive/DashJoin.js.git" }, "keywords": [ - "dash", - "coin", - "join", - "private", - "send", - "cryto", - "currency" + "Dash", + "CoinJoin", + "PrivateSend", + "Crpytocurrency" ], - "author": "William Merfalen (https://github.com/wmerfalen)", + "author": "AJ ONeal (https://therootcompany.com)", "license": "MIT", "bugs": { "url": "https://github.com/dashhive/DashJoin.js/issues" }, "homepage": "https://github.com/dashhive/DashJoin.js#readme", - "devDependencies": { - "mocha": "^10.2.0" - }, "dependencies": { - "@dashevo/dashcore-lib": "^0.20.6", "@dashincubator/secp256k1": "^1.7.1-5", - "@mentoc/xtract": "^1.0.3", - "dashtx": "^0.9.0", - "node-lmdb": "^0.9.7" + "dashhd": "^3.3.3", + "dashkeys": "^1.1.4", + "dashphrase": "^1.4.0", + "dashrpc": "^19.0.1", + "dashtx": "^0.18.1", + "dotenv": "^16.4.5" } } diff --git a/packer.js b/packer.js new file mode 100644 index 0000000..7a9693c --- /dev/null +++ b/packer.js @@ -0,0 +1,748 @@ +'use strict'; + +let Packer = module.exports; + +let Crypto = require('node:crypto'); + +let CoinJoin = require('./coinjoin.js'); + +Packer.PROTOCOL_VERSION = 70227; + +Packer.FIELD_SIZES = { + VERSION: 4, + SERVICES: 8, + TIMESTAMP: 8, + ADDR_RECV_SERVICES: 8, + ADDR_RECV_IP: 16, + ADDR_RECV_PORT: 2, + ADDR_TRANS_SERVICES: 8, + ADDR_TRANS_IP: 16, + ADDR_TRANS_PORT: 2, + NONCE: 8, + USER_AGENT_BYTES: 1, // can be skipped + USER_AGENT_STRING: 0, + START_HEIGHT: 4, + // The following 2 fields are OPTIONAL + RELAY: 0, + RELAY_NONEMPTY: 1, + MNAUTH_CHALLENGE: 0, + MNAUTH_CHALLENGE_NONEMPTY: 32, + MN_CONNECTION: 0, + MN_CONNECTION_NONEMPTY: 1, +}; + +Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION = 70001; +Packer.MNAUTH_PROTOCOL_VERSION_INTRODUCTION = 70214; + +let textEncoder = new TextEncoder(); + +let SIZES = { + MAGIC_BYTES: 4, + COMMAND_NAME: 12, + PAYLOAD_SIZE: 4, + CHECKSUM: 4, +}; +const TOTAL_HEADER_SIZE = + SIZES.MAGIC_BYTES + SIZES.COMMAND_NAME + SIZES.PAYLOAD_SIZE + SIZES.CHECKSUM; +Packer.HEADER_SIZE = TOTAL_HEADER_SIZE; + +Packer.PING_SIZE = Packer.FIELD_SIZES.NONCE; +Packer.DSQ_SIZE = 1; // bool + +const EMPTY_CHECKSUM = [0x5d, 0xf6, 0xe0, 0xe2]; + +/** + * @typedef {"mainnet"|"testnet"|"regtest"|"devnet"} NetworkName + */ + +Packer.NETWORKS = {}; +Packer.NETWORKS.mainnet = { + port: 9999, + magic: new Uint8Array([ + //0xBD6B0CBF, + 0xbf, 0x0c, 0x6b, 0xbd, + ]), + start: 0xbf0c6bbd, + nBits: 0x1e0ffff0, + minimumParticiparts: 3, +}; +Packer.NETWORKS.testnet = { + port: 19999, + magic: new Uint8Array([ + //0xFFCAE2CE, + 0xce, 0xe2, 0xca, 0xff, + ]), + start: 0xcee2caff, + nBits: 0x1e0ffff0, + minimumParticiparts: 2, +}; +Packer.NETWORKS.regtest = { + port: 19899, + magic: new Uint8Array([ + //0xDCB7C1FC, + 0xfc, 0xc1, 0xb7, 0xdc, + ]), + start: 0xfcc1b7dc, + nBits: 0x207fffff, + minimumParticiparts: 2, +}; +Packer.NETWORKS.devnet = { + port: 19799, + magic: new Uint8Array([ + //0xCEFFCAE2, + 0xe2, 0xca, 0xff, 0xce, + ]), + start: 0xe2caffce, + nBits: 0x207fffff, + minimumParticiparts: 2, +}; + +/** + * @typedef {0x01|0x02|0x04|0x400} ServiceBitmask + * @typedef {"NETWORK"|"GETUTXO "|"BLOOM"|"NETWORK_LIMITED"} ServiceName + */ + +/** @type {Object.} */ +let SERVICE_IDENTIFIERS = {}; + +/** + * 0x00 is the default - not a full node, no guarantees + */ + +/** + * NODE_NETWORK: + * This is a full node and can be asked for full + * blocks. It should implement all protocol features + * available in its self-reported protocol version. + */ +SERVICE_IDENTIFIERS.NETWORK = 0x01; + +/** + * NODE_GETUTXO: + * This node is capable of responding to the getutxo + * protocol request. Dash Core does not support + * this service. + */ +SERVICE_IDENTIFIERS.GETUTXO = 0x02; + +/** + * NODE_BLOOM: + * This node is capable and willing to handle bloom- + * filtered connections. Dash Core nodes used to support + * this by default, without advertising this bit, but + * no longer do as of protocol version 70201 + * (= NO_BLOOM_VERSION) + */ +SERVICE_IDENTIFIERS.BLOOM = 0x04; + +/** + * 0x08 is not supported by Dash + */ + +/** + * NODE_NETWORK_LIMITED: + * This is the same as NODE_NETWORK with the + * limitation of only serving the last 288 blocks. + * Not supported prior to Dash Core 0.16.0 + */ +SERVICE_IDENTIFIERS.NETWORK_LIMITED = 0x400; + +/** + * @typedef VersionOpts + * @prop {NetworkName} network - "mainnet", "testnet", etc + * @prop {Uint32?} [protocol_version] - features (default: Packer.PROTOCOL_VERSION) + * @prop {Array?} [addr_recv_services] - default: NETWORK + * @prop {String} addr_recv_ip - ipv6 address (can be 'ipv4-mapped') of the server + * @prop {Uint16} addr_recv_port - 9999, 19999, etc (can be arbitrary on testnet) + * @prop {Array?} [addr_trans_services] - default: NONE + * @prop {String?} [addr_trans_ip]- null, or the external ipv6 or ipv4-mapped address + * @prop {Uint16} [addr_trans_port] - null, or the external port (ignored for tcp?) + * @prop {Uint32} start_height - start height of your best block + * @prop {Uint8Array?} [nonce] - 8 random bytes to identify this transmission + * @prop {String?} [user_agent] - ex: "DashJoin/1.0 request/1.0 node/20.0.0 macos/14.0" + * @prop {Boolean?} [relay] - request all network tx & inv messages to be relayed to you + * @prop {Uint8Array?} [mnauth_challenge] - 32 bytes for the masternode to sign as proof + */ + +/** + * Constructs a version message, with fields in the correct byte order. + * @param {VersionOpts} opts + * + * See also: + * - https://dashcore.readme.io/docs/core-ref-p2p-network-control-messages#version + */ +/* jshint maxcomplexity: 9001 */ +/* jshint maxstatements:150 */ +/* (it's simply very complex, okay?) */ +Packer.version = function ({ + network, + protocol_version = Packer.PROTOCOL_VERSION, + // alias of addr_trans_services + //services, + addr_recv_services = [SERVICE_IDENTIFIERS.NETWORK], + addr_recv_ip, + addr_recv_port, + addr_trans_services = [], + addr_trans_ip = '127.0.0.1', + addr_trans_port = 65535, + start_height, + nonce = null, + user_agent = null, + relay = null, + mnauth_challenge = null, +}) { + const command = 'version'; + + let args = { + network, + protocol_version, + addr_recv_services, + addr_recv_ip, + addr_recv_port, + addr_trans_services, + addr_trans_ip, + addr_trans_port, + start_height, + nonce, + user_agent, + relay, + mnauth_challenge, + }; + let SIZES = Object.assign({}, Packer.FIELD_SIZES); + + if (!Packer.NETWORKS[args.network]) { + throw new Error(`"network" '${args.network}' is invalid.`); + } + if (!Array.isArray(args.addr_recv_services)) { + throw new Error('"addr_recv_services" must be an array'); + } + if ( + //@ts-ignore - protocol_version has a default value + args.protocol_version < Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION && + args.relay !== null + ) { + throw new Error( + `"relay" field is not supported in protocol versions prior to ${Packer.RELAY_PROTOCOL_VERSION_INTRODUCTION}`, + ); + } + if ( + //@ts-ignore - protocol_version has a default value + args.protocol_version < Packer.MNAUTH_PROTOCOL_VERSION_INTRODUCTION && + args.mnauth_challenge !== null + ) { + throw new Error( + '"mnauth_challenge" field is not supported in protocol versions prior to MNAUTH_CHALLENGE_OFFSET', + ); + } + if (args.mnauth_challenge !== null) { + if (!(args.mnauth_challenge instanceof Uint8Array)) { + throw new Error('"mnauth_challenge" field must be a Uint8Array'); + } + if ( + args.mnauth_challenge.length !== Packer.SIZES.MNAUTH_CHALLENGE_NONEMPTY + ) { + throw new Error( + `"mnauth_challenge" field must be ${Packer.SIZES.MNAUTH_CHALLENGE_NONEMPTY} bytes long`, + ); + } + } + SIZES.USER_AGENT_STRING = args.user_agent?.length || 0; + if (args.relay !== null) { + SIZES.RELAY = Packer.FIELD_SIZES.RELAY_NONEMPTY; + } + // if (args.mnauth_challenge !== null) { + SIZES.MNAUTH_CHALLENGE = Packer.FIELD_SIZES.MNAUTH_CHALLENGE_NONEMPTY; + // } + SIZES.MN_CONNECTION = Packer.FIELD_SIZES.MN_CONNECTION_NONEMPTY; + + let TOTAL_SIZE = + SIZES.VERSION + + SIZES.SERVICES + + SIZES.TIMESTAMP + + SIZES.ADDR_RECV_SERVICES + + SIZES.ADDR_RECV_IP + + SIZES.ADDR_RECV_PORT + + SIZES.ADDR_TRANS_SERVICES + + SIZES.ADDR_TRANS_IP + + SIZES.ADDR_TRANS_PORT + + SIZES.NONCE + + SIZES.USER_AGENT_BYTES + + SIZES.USER_AGENT_STRING + + SIZES.START_HEIGHT + + SIZES.RELAY + + SIZES.MNAUTH_CHALLENGE + + SIZES.MN_CONNECTION; + let payload = new Uint8Array(TOTAL_SIZE); + // Protocol version + + //@ts-ignore - protocol_version has a default value + let versionBytes = uint32ToBytesLE(args.protocol_version); + payload.set(versionBytes, 0); + + /** + * Set services to NODE_NETWORK (1) + NODE_BLOOM (4) + */ + const SERVICES_OFFSET = SIZES.VERSION; + let senderServicesBytes; + { + let senderServicesMask = 0n; + //@ts-ignore - addr_trans_services has a default value of [] + for (const serviceBit of addr_trans_services) { + senderServicesMask += BigInt(serviceBit); + } + let senderServices64 = new BigInt64Array([senderServicesMask]); // jshint ignore:line + senderServicesBytes = new Uint8Array(senderServices64.buffer); + payload.set(senderServicesBytes, SERVICES_OFFSET); + } + + const TIMESTAMP_OFFSET = SERVICES_OFFSET + SIZES.SERVICES; + { + let tsBytes = uint32ToBytesLE(Date.now()); + payload.set(tsBytes, TIMESTAMP_OFFSET); + } + + let ADDR_RECV_SERVICES_OFFSET = TIMESTAMP_OFFSET + SIZES.TIMESTAMP; + { + let serverServicesMask = 0n; + //@ts-ignore - addr_recv_services has a default value + for (const serviceBit of addr_recv_services) { + serverServicesMask += BigInt(serviceBit); + } + let serverServices64 = new BigInt64Array([serverServicesMask]); // jshint ignore:line + let serverServicesBytes = new Uint8Array(serverServices64.buffer); + payload.set(serverServicesBytes, ADDR_RECV_SERVICES_OFFSET); + } + + /** + * "ADDR_RECV" means the host that we're sending this traffic to. + * So, in other words, it's the master node + */ + let ADDR_RECV_IP_OFFSET = + ADDR_RECV_SERVICES_OFFSET + SIZES.ADDR_RECV_SERVICES; + { + let ipBytesBE = ipv4ToBytesBE(args.addr_recv_ip); + payload.set([0xff, 0xff], ADDR_RECV_IP_OFFSET + 10); + payload.set(ipBytesBE, ADDR_RECV_IP_OFFSET + 12); + } + + /** + * Copy address recv port + */ + let ADDR_RECV_PORT_OFFSET = ADDR_RECV_IP_OFFSET + SIZES.ADDR_RECV_IP; + { + let portBytes16 = Uint16Array.from([args.addr_recv_port]); + let portBytes = new Uint8Array(portBytes16.buffer); + portBytes.reverse(); + payload.set(portBytes, ADDR_RECV_PORT_OFFSET); + } + + /** + * Copy address transmitted services + */ + let ADDR_TRANS_SERVICES_OFFSET = ADDR_RECV_PORT_OFFSET + SIZES.ADDR_RECV_PORT; + payload.set(senderServicesBytes, ADDR_TRANS_SERVICES_OFFSET); + + /** + * We add the extra 10, so that we can encode an ipv4-mapped ipv6 address + */ + let ADDR_TRANS_IP_OFFSET = + ADDR_TRANS_SERVICES_OFFSET + SIZES.ADDR_TRANS_SERVICES; + { + //@ts-ignore - addr_trans_ip has a default value + if (is_ipv6_mapped_ipv4(args.addr_trans_ip)) { + //@ts-ignore - addr_trans_ip has a default value + let ipv6Parts = args.addr_trans_ip.split(':'); + let ipv4Str = ipv6Parts.at(-1); + //@ts-ignore - guaranteed to be defined, actually + let ipBytesBE = ipv4ToBytesBE(ipv4Str); + payload.set(ipBytesBE, ADDR_TRANS_IP_OFFSET + 12); + payload.set([0xff, 0xff], ADDR_TRANS_IP_OFFSET + 10); // we add the 10 so that we can fill the latter 6 bytes + } else { + /** TODO: ipv4-only & ipv6-only */ + //@ts-ignore - addr_trans_ip has a default value + let ipBytesBE = ipv4ToBytesBE(args.addr_trans_ip); + payload.set(ipBytesBE, ADDR_TRANS_IP_OFFSET + 12); + payload.set([0xff, 0xff], ADDR_TRANS_IP_OFFSET + 10); // we add the 10 so that we can fill the latter 6 bytes + } + } + + let ADDR_TRANS_PORT_OFFSET = ADDR_TRANS_IP_OFFSET + SIZES.ADDR_TRANS_IP; + { + let portBytes16 = Uint16Array.from([args.addr_trans_port]); + let portBytes = new Uint8Array(portBytes16.buffer); + portBytes.reverse(); + payload.set(portBytes, ADDR_TRANS_PORT_OFFSET); + } + + // TODO we should set this to prevent duplicate broadcast + // this can be left zero + let NONCE_OFFSET = ADDR_TRANS_PORT_OFFSET + SIZES.ADDR_TRANS_PORT; + if (!args.nonce) { + args.nonce = new Uint8Array(SIZES.NONCE); + Crypto.getRandomValues(args.nonce); + } + payload.set(args.nonce, NONCE_OFFSET); + + let USER_AGENT_BYTES_OFFSET = NONCE_OFFSET + SIZES.NONCE; + if (null !== args.user_agent && typeof args.user_agent === 'string') { + let userAgentSize = args.user_agent.length; + payload.set([userAgentSize], USER_AGENT_BYTES_OFFSET); + let uaBytes = textEncoder.encode(args.user_agent); + payload.set(uaBytes, USER_AGENT_BYTES_OFFSET + 1); + } else { + payload.set([0x0], USER_AGENT_BYTES_OFFSET); + } + + let START_HEIGHT_OFFSET = + USER_AGENT_BYTES_OFFSET + SIZES.USER_AGENT_BYTES + SIZES.USER_AGENT_STRING; + { + let heightBytes = uint32ToBytesLE(args.start_height); + payload.set(heightBytes, START_HEIGHT_OFFSET); + } + + let RELAY_OFFSET = START_HEIGHT_OFFSET + SIZES.START_HEIGHT; + if (args.relay !== null) { + let bytes = [0x00]; + if (args.relay) { + bytes[0] = 0x01; + } + payload.set(bytes, RELAY_OFFSET); + } + + let MNAUTH_CHALLENGE_OFFSET = RELAY_OFFSET + SIZES.RELAY; + if (!args.mnauth_challenge) { + let rnd = new Uint8Array(32); + Crypto.getRandomValues(rnd); + args.mnauth_challenge = rnd; + } + payload.set(args.mnauth_challenge, MNAUTH_CHALLENGE_OFFSET); + + // let MNAUTH_CONNECTION_OFFSET = MNAUTH_CHALLENGE_OFFSET + SIZES.MN_CONNECTION; + // if (args.mn_connection) { + // payload.set([0x01], MNAUTH_CONNECTION_OFFSET); + // } + + payload = Packer.packMessage({ network, command, payload }); + return payload; +}; + +/** + * In this case the only bytes are the nonce + * Use a .subarray(offset) to define an offset. + * (a manual offset will not work consistently, and .byteOffset is context-sensitive) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Uint8Array?} [opts.nonce] + */ +Packer.packPing = function ({ network, message = null, nonce = null }) { + const command = 'ping'; + + if (!message) { + let pingSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + message = new Uint8Array(pingSize); + } + let payload = message.subarray(Packer.HEADER_SIZE); + + if (!nonce) { + nonce = payload; + Crypto.getRandomValues(nonce); + } else { + payload.set(nonce, 0); + } + + void Packer.packMessage({ network, command, bytes: message }); + return message; +}; + +/** + * In this case the only bytes are the nonce + * Use a .subarray(offset) to define an offset. + * (a manual offset will not work consistently, and .byteOffset is context-sensitive) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Uint8Array} opts.nonce + */ +Packer.packPong = function ({ network, message = null, nonce }) { + const command = 'pong'; + + if (!message) { + let pongSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + message = new Uint8Array(pongSize); + } + let payload = message.subarray(Packer.HEADER_SIZE); + payload.set(nonce, 0); + + void Packer.packMessage({ network, command, bytes: message }); + return message; +}; + +/** + * Turns on or off DSQ messages (necessary for CoinJoin, but off by default) + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint8Array?} [opts.message] + * @param {Boolean?} [opts.send] + */ +Packer.packSendDsq = function ({ network, message = null, send = true }) { + const command = 'senddsq'; + + if (!message) { + let dsqSize = Packer.HEADER_SIZE + Packer.DSQ_SIZE; + message = new Uint8Array(dsqSize); + } + + let sendByte = [0x01]; + if (!send) { + sendByte = [0x00]; + } + let payload = message.subarray(Packer.HEADER_SIZE); + payload.set(sendByte, 0); + + void Packer.packMessage({ network, command, bytes: message }); + + return message; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Uint32} opts.denomination + * @param {Uint8Array} opts.collateralTx + */ +Packer.packAllow = function ({ network, denomination, collateralTx }) { + const command = 'dsa'; + const DENOMINATION_SIZE = 4; + + //@ts-ignore - numbers can be used as map keys + let denomMask = CoinJoin.STANDARD_DENOMINATION_MASKS[denomination]; + if (!denomMask) { + throw new Error( + `contact your local Dash representative to vote for denominations of '${denomination}'`, + ); + } + + let totalLength = DENOMINATION_SIZE + collateralTx.length; + let payload = new Uint8Array(totalLength); + let dv = new DataView(payload.buffer); + let offset = 0; + + let DV_LITTLE_ENDIAN = true; + dv.setUint32(offset, denomMask, DV_LITTLE_ENDIAN); + offset += DENOMINATION_SIZE; + + payload.set(collateralTx, offset); + + let message = Packer.packMessage({ network, command, payload }); + return message; +}; + +let DashTx = require('dashtx'); + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Array} opts.inputs + * @param {Array} opts.outputs + * @param {Uint8Array} opts.collateralTx + */ +Packer.packDsi = function ({ network, inputs, collateralTx, outputs }) { + const command = 'dsi'; + + let neutered = []; + for (let input of inputs) { + let _input = { + txId: input.txId || input.txid, + txid: input.txid || input.txId, + outputIndex: input.outputIndex, + }; + neutered.push(_input); + } + + let inputsHex = DashTx.serializeInputs(inputs); + let inputHex = inputsHex.join(''); + let outputsHex = DashTx.serializeOutputs(outputs); + let outputHex = outputsHex.join(''); + + let len = collateralTx.length; + len += inputHex.length / 2; + len += outputHex.length / 2; + let bytes = new Uint8Array(Packer.HEADER_SIZE + len); + + let offset = Packer.HEADER_SIZE; + + { + let inputsPayload = bytes.subarray(offset); + let j = 0; + for (let i = 0; i < inputHex.length; i += 2) { + let end = i + 2; + let hex = inputHex.slice(i, end); + inputsPayload[j] = parseInt(hex, 16); + j += 1; + } + offset += inputHex.length / 2; + } + + bytes.set(collateralTx, offset); + offset += collateralTx.length; + + { + let outputsPayload = bytes.subarray(offset); + let j = 0; + for (let i = 0; i < outputHex.length; i += 2) { + let end = i + 2; + let hex = outputHex.slice(i, end); + outputsPayload[j] = parseInt(hex, 16); + j += 1; + } + offset += outputHex.length / 2; + } + + void Packer.packMessage({ network, command, bytes }); + return bytes; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {Array} [opts.inputs] + */ +Packer.packDss = function ({ network, inputs }) { + const command = 'dss'; + + if (!inputs?.length) { + // TODO make better + throw new Error('you must provide some inputs'); + } + + let txInputsHex = DashTx.serializeInputs(inputs); + let txInputHex = txInputsHex.join(''); + let payload = DashTx.utils.hexToBytes(txInputHex); + + // TODO prealloc bytes + let bytes = Packer.packMessage({ network, command, payload }); + return bytes; +}; + +/** + * @param {Object} opts + * @param {NetworkName} opts.network - "mainnet", "testnet", etc + * @param {String} opts.command + * @param {Uint8Array?} [opts.bytes] + * @param {Uint8Array?} [opts.payload] + */ +Packer.packMessage = function ({ + network, + command, + bytes = null, + payload = null, +}) { + let payloadLength = payload?.byteLength || 0; + let messageSize = Packer.HEADER_SIZE + payloadLength; + let offset = 0; + + let embeddedPayload = false; + let message = bytes; + if (message) { + if (!payload) { + payload = message.subarray(Packer.HEADER_SIZE); + payloadLength = payload.byteLength; + messageSize = Packer.HEADER_SIZE + payloadLength; + embeddedPayload = true; + } + } else { + message = new Uint8Array(messageSize); + } + if (message.length !== messageSize) { + throw new Error( + `expected bytes of length ${messageSize}, but got ${message.length}`, + ); + } + message.set(Packer.NETWORKS[network].magic, offset); + offset += SIZES.MAGIC_BYTES; + + // Set command_name (char[12]) + let nameBytes = textEncoder.encode(command); + message.set(nameBytes, offset); + offset += SIZES.COMMAND_NAME; + + // Finally, append the payload to the header + if (!payload) { + // skip because it's already initialized to 0 + //message.set(payloadLength, offset); + offset += SIZES.PAYLOAD_SIZE; + + message.set(EMPTY_CHECKSUM, offset); + return message; + } + + let payloadSizeBytes = uint32ToBytesLE(payloadLength); + message.set(payloadSizeBytes, offset); + offset += SIZES.PAYLOAD_SIZE; + + let checksum = compute_checksum(payload); + message.set(checksum, offset); + offset += SIZES.CHECKSUM; + + if (!embeddedPayload) { + message.set(payload, offset); + } + return message; +}; + +/** + * First 4 bytes of SHA256(SHA256(payload)) in internal byte order. + * @param {Uint8Array} payload + */ +function compute_checksum(payload) { + // TODO this should be node-specific in node for performance reasons + let hash = Crypto.createHash('sha256').update(payload).digest(); + let hashOfHash = Crypto.createHash('sha256').update(hash).digest(); + return hashOfHash.slice(0, 4); +} + +/** + * @param {String} ipv4 + */ +function ipv4ToBytesBE(ipv4) { + let u8s = []; + // let u8s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff /*,0,0,0,0*/]; + + let octets = ipv4.split('.'); + for (let octet of octets) { + let int8 = parseInt(octet); + u8s.push(int8); + } + + let bytes = Uint8Array.from(u8s); + return bytes; +} + +/** + * @param {Uint32} n + */ +function uint32ToBytesLE(n) { + let u32 = new Uint32Array([n]); + let u8 = new Uint8Array(u32.buffer); + return u8; +} + +/** + * @param {String} ip + */ +function is_ipv6_mapped_ipv4(ip) { + return !!ip.match(/^[:]{2}[f]{4}[:]{1}.*$/); +} + +/** + * @typedef {Number} Uint64 + * @typedef {Number} Uint32 + * @typedef {Number} Uint16 + */ + +/** + * + * addr_recv_ip is the ipv6 address of the master node (can be 'ipv4-mapped') + * @typedef {String} Ipv6Addr + */ diff --git a/parser.js b/parser.js new file mode 100644 index 0000000..f2d7179 --- /dev/null +++ b/parser.js @@ -0,0 +1,397 @@ +'use strict'; + +let Parser = module.exports; + +const DV_LITTLE_ENDIAN = true; +// const DV_BIG_ENDIAN = false; +//let EMPTY_HASH = Buffer.from('5df6e0e2', 'hex'); + +Parser.HEADER_SIZE = 24; +Parser.DSSU_SIZE = 16; +Parser.DSQ_SIZE = 142; +Parser.SESSION_ID_SIZE = 4; + +let CoinJoin = require('./coinjoin.js'); +let DashTx = require('dashtx'); + +/** + * Parse the 24-byte P2P Message Header + * - 4 byte magic bytes (delimiter) (possibly intended for non-tcp messages?) + * - 12 byte string (stop at first null) + * - 4 byte payload size + * - 4 byte checksum + * + * See also: + * - https://docs.dash.org/projects/core/en/stable/docs/reference/p2p-network-message-headers.html#message-headers + * @param {Uint8Array} bytes + */ +Parser.parseHeader = function (bytes) { + let buffer = Buffer.from(bytes); + // console.log( + // new Date(), + // '[debug] parseHeader(bytes)', + // buffer.length, + // buffer.toString('hex'), + // ); + // console.log(buffer.toString('utf8')); + + bytes = new Uint8Array(buffer); + if (bytes.length < Parser.HEADER_SIZE) { + let msg = `developer error: header should be ${Parser.HEADER_SIZE}+ bytes (optional payload), not ${bytes.length}`; + throw new Error(msg); + } + let dv = new DataView(bytes.buffer); + + let commandStart = 4; + let payloadSizeStart = 16; + let checksumStart = 20; + + let magicBytes = buffer.slice(0, commandStart); + + let commandEnd = buffer.indexOf(0x00, commandStart); + if (commandEnd >= payloadSizeStart) { + throw new Error('command name longer than 12 bytes'); + } + let commandBuf = buffer.slice(commandStart, commandEnd); + let command = commandBuf.toString('utf8'); + + let payloadSize = dv.getUint32(payloadSizeStart, DV_LITTLE_ENDIAN); + let checksum = buffer.slice(checksumStart, checksumStart + 4); + + let headerMessage = { + magicBytes, + command, + payloadSize, + checksum, + }; + + // if (command !== 'inv') { + // console.log(new Date(), headerMessage); + // } + // console.log(); + return headerMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseVersion = function (bytes) { + let buffer = Buffer.from(bytes); + // console.log( + // '[debug] parseVersion(bytes)', + // buffer.length, + // buffer.toString('hex'), + // ); + // console.log(buffer.toString('utf8')); + + bytes = new Uint8Array(buffer); + let dv = new DataView(bytes.buffer); + + let versionStart = 0; + let version = dv.getUint32(versionStart, DV_LITTLE_ENDIAN); + + let servicesStart = versionStart + 4; // + SIZES.VERSION (4) + let servicesMask = dv.getBigUint64(servicesStart, DV_LITTLE_ENDIAN); + + let timestampStart = servicesStart + 8; // + SIZES.SERVICES (8) + let timestamp64n = dv.getBigInt64(timestampStart, DV_LITTLE_ENDIAN); + let timestamp64 = Number(timestamp64n); + let timestampMs = timestamp64 * 1000; + let timestamp = new Date(timestampMs); + + let addrRecvServicesStart = timestampStart + 8; // + SIZES.TIMESTAMP (8) + let addrRecvServicesMask = dv.getBigUint64( + addrRecvServicesStart, + DV_LITTLE_ENDIAN, + ); + + let addrRecvAddressStart = addrRecvServicesStart + 8; // + SIZES.SERVICES (8) + let addrRecvAddress = buffer.slice( + addrRecvAddressStart, + addrRecvAddressStart + 16, + ); + + let addrRecvPortStart = addrRecvAddressStart + 16; // + SIZES.IPV6 (16) + let addrRecvPort = dv.getUint16(addrRecvPortStart, DV_LITTLE_ENDIAN); + + let addrTransServicesStart = addrRecvPortStart + 2; // + SIZES.PORT (2) + let addrTransServicesMask = dv.getBigUint64( + addrTransServicesStart, + DV_LITTLE_ENDIAN, + ); + + let addrTransAddressStart = addrTransServicesStart + 8; // + SIZES.SERVICES (8) + let addrTransAddress = buffer.slice( + addrTransAddressStart, + addrTransAddressStart + 16, + ); + + let addrTransPortStart = addrTransAddressStart + 16; // + SIZES.IPV6 (16) + let addrTransPort = dv.getUint16(addrTransPortStart, DV_LITTLE_ENDIAN); + + let nonceStart = addrTransPortStart + 2; // + SIZES.PORT (2) + let nonce = buffer.slice(nonceStart, nonceStart + 8); + + let uaSizeStart = 80; // + SIZES.PORT (2) + let uaSize = buffer[uaSizeStart]; + + let uaStart = uaSizeStart + 1; + let uaBytes = buffer.slice(uaStart, uaStart + uaSize); + let ua = uaBytes.toString('utf8'); + + let startHeightStart = uaStart + uaSize; + let startHeight = dv.getUint32(startHeightStart, DV_LITTLE_ENDIAN); + + let relayStart = startHeightStart + 4; + /** @type {Boolean?} */ + let relay = null; + if (buffer.length > relayStart) { + relay = buffer[relayStart] > 0; + } + + let mnAuthChStart = relayStart + 1; + /** @type {Uint8Array?} */ + let mnAuthChallenge = null; + if (buffer.length > mnAuthChStart) { + mnAuthChallenge = buffer.slice(mnAuthChStart, mnAuthChStart + 32); + } + + let mnConnStart = mnAuthChStart + 32; + /** @type {Boolean?} */ + let mnConn = null; + if (buffer.length > mnConnStart) { + mnConn = buffer[mnConnStart] > 0; + } + + let versionMessage = { + version, + servicesMask, + timestamp, + addrRecvServicesMask, + addrRecvAddress, + addrRecvPort, + addrTransServicesMask, + addrTransAddress, + addrTransPort, + nonce, + ua, + startHeight, + relay, + mnAuthChallenge, + mnConn, + }; + + // console.log(versionMessage); + // console.log(); + return versionMessage; +}; + +Parser._DSSU_MESSAGE_IDS = { + 0x00: 'ERR_ALREADY_HAVE', + 0x01: 'ERR_DENOM', + 0x02: 'ERR_ENTRIES_FULL', + 0x03: 'ERR_EXISTING_TX', + 0x04: 'ERR_FEES', + 0x05: 'ERR_INVALID_COLLATERAL', + 0x06: 'ERR_INVALID_INPUT', + 0x07: 'ERR_INVALID_SCRIPT', + 0x08: 'ERR_INVALID_TX', + 0x09: 'ERR_MAXIMUM', + 0x0a: 'ERR_MN_LIST', // <-- + 0x0b: 'ERR_MODE', + 0x0c: 'ERR_NON_STANDARD_PUBKEY', // (Not used) + 0x0d: 'ERR_NOT_A_MN', //(Not used) + 0x0e: 'ERR_QUEUE_FULL', + 0x0f: 'ERR_RECENT', + 0x10: 'ERR_SESSION', + 0x11: 'ERR_MISSING_TX', + 0x12: 'ERR_VERSION', + 0x13: 'MSG_NOERR', + 0x14: 'MSG_SUCCESS', + 0x15: 'MSG_ENTRIES_ADDED', + 0x16: 'ERR_SIZE_MISMATCH', +}; + +Parser._DSSU_STATES = { + 0x00: 'IDLE', + 0x01: 'QUEUE', + 0x02: 'ACCEPTING_ENTRIES', + 0x03: 'SIGNING', + 0x04: 'ERROR', + 0x05: 'SUCCESS', +}; + +Parser._DSSU_STATUSES = { + 0x00: 'REJECTED', + 0x01: 'ACCEPTED', +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDssu = function (bytes) { + let buffer = Buffer.from(bytes); + + bytes = new Uint8Array(buffer); + let dv = new DataView(bytes.buffer); + // console.log('[debug] parseDssu(bytes)', bytes.length, buffer.toString('hex')); + // console.log(buffer.toString('utf8')); + if (bytes.length !== Parser.DSSU_SIZE) { + let msg = `developer error: a 'dssu' message is 16 bytes, but got ${bytes.length}`; + throw new Error(msg); + } + + /** + * 4 nMsgSessionID - Required - Session ID + * 4 nMsgState - Required - Current state of processing + * 4 nMsgEntriesCount - Required - Number of entries in the pool (deprecated) + * 4 nMsgStatusUpdate - Required - Update state and/or signal if entry was accepted or not + * 4 nMsgMessageID - Required - ID of the typical masternode reply message + */ + const SIZES = { + SESSION_ID: Parser.SESSION_ID_SIZE, + STATE: 4, + ENTRIES_COUNT: 4, + STATUS_UPDATE: 4, + MESSAGE_ID: 4, + }; + + let offset = 0; + + let session_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.SESSION_ID; + + let state_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.STATE; + + ///** + // * Grab the entries count + // * Not parsed because apparently master nodes no longer send + // * the entries count. + // */ + //parsed.entries_count = dv.getUint32(offset, DV_LITTLE_ENDIAN); + //offset += SIZES.ENTRIES_COUNT; + + let status_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.STATUS_UPDATE; + + let message_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + + let dssuMessage = { + session_id: session_id, + state_id: state_id, + state: Parser._DSSU_STATES[state_id], + // entries_count: 0, + status_id: status_id, + status: Parser._DSSU_STATUSES[status_id], + message_id: message_id, + message: Parser._DSSU_MESSAGE_IDS[message_id], + }; + + // console.log(dssuMessage); + // console.log(); + return dssuMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDsq = function (bytes) { + let buffer = Buffer.from(bytes); + + bytes = new Uint8Array(buffer); + if (bytes.length !== Parser.DSQ_SIZE) { + let msg = `developer error: 'dsq' messages are ${Parser.DSQ_SIZE} bytes, not ${bytes.length}`; + throw new Error(msg); + } + let dv = new DataView(bytes.buffer); + // console.log('[debug] parseDsq(bytes)', bytes.length, buffer.toString('hex')); + // console.log(buffer.toString('utf8')); + + const SIZES = { + DENOM: 4, + PROTX: 32, + TIME: 8, + READY: 1, + SIG: 97, + }; + + let offset = 0; + + let denomination_id = dv.getUint32(offset, DV_LITTLE_ENDIAN); + offset += SIZES.DENOM; + + //@ts-ignore - correctness of denomination must be checked higher up + let denomination = CoinJoin.STANDARD_DENOMINATIONS_MAP[denomination_id]; + + /** + * Grab the protxhash + */ + let protxhash_bytes = bytes.slice(offset, offset + SIZES.PROTX); + offset += SIZES.PROTX; + + /** + * Grab the time + */ + let timestamp64n = dv.getBigInt64(offset, DV_LITTLE_ENDIAN); + offset += SIZES.TIME; + let timestamp_unix = Number(timestamp64n); + let timestampMs = timestamp_unix * 1000; + let timestampDate = new Date(timestampMs); + let timestamp = timestampDate.toISOString(); + + /** + * Grab the fReady + */ + let ready = bytes[offset] > 0x00; + offset += SIZES.READY; + + let signature_bytes = bytes.slice(offset, offset + SIZES.SIG); + + let dsqMessage = { + denomination_id, + denomination, + protxhash_bytes, + // protxhash: '', + timestamp_unix, + timestamp, + ready, + signature_bytes, + // signature: '', + }; + + // console.log(dsqMessage); + // console.log(); + return dsqMessage; +}; + +/** + * @param {Uint8Array} bytes + */ +Parser.parseDsf = function (bytes) { + // console.log( + // new Date(), + // '[debug] parseDsf (msg len)', + // bytes.length, + // bytes.toString('hex'), + // ); + + let offset = 0; + let sessionId = bytes.subarray(offset, Parser.SESSION_ID_SIZE); + let session_id = DashTx.utils.bytesToHex(sessionId); + offset += Parser.SESSION_ID_SIZE; + + // TODO parse transaction completely with DashTx + let transactionUnsigned = bytes.subarray(offset); + let transaction_unsigned = DashTx.utils.bytesToHex(transactionUnsigned); + + // let txLen = transaction_unsigned.length / 2; + // console.log( + // new Date(), + // '[debug] parseDsf (tx len)', + // txLen, + // transaction_unsigned, + // ); + + return { session_id, transaction_unsigned }; +}; diff --git a/src/.config.json.example b/src/.config.json.example deleted file mode 100644 index cfe2ae7..0000000 --- a/src/.config.json.example +++ /dev/null @@ -1 +0,0 @@ -{"masterNodeIP":"10.0.2.15","masterNodePort":19996,"network":"devnet","startBlockHeight":90,"userAgent":"/Dash Core:19.1.0(devnet.devnet-privatesend)/"} diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 00453e6..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -dash-core-src -.config.json -data/ -dcore/ -data -dcore -electrum-dash/ diff --git a/src/.mn0-config.json b/src/.mn0-config.json deleted file mode 100644 index 69a9f0c..0000000 --- a/src/.mn0-config.json +++ /dev/null @@ -1 +0,0 @@ -{"masterNodeIP":"0.0.0.0","masterNodePort":20001,"network":"regtest","startBlockHeight": 184,"userAgent":"/Dash Core:19.1.0(devnet.devnet-privatesend)/"} diff --git a/src/.mn1-config.json b/src/.mn1-config.json deleted file mode 100644 index 5abc8db..0000000 --- a/src/.mn1-config.json +++ /dev/null @@ -1 +0,0 @@ -{"masterNodeIP":"0.0.0.0","masterNodePort":20101,"network":"regtest","startBlockHeight": 184,"userAgent":"/Dash Core:19.1.0(devnet.devnet-privatesend)/"} diff --git a/src/.mn2-config.json b/src/.mn2-config.json deleted file mode 100644 index aaf394d..0000000 --- a/src/.mn2-config.json +++ /dev/null @@ -1 +0,0 @@ -{"masterNodeIP":"0.0.0.0","masterNodePort":20201,"network":"regtest","startBlockHeight": 184,"userAgent":"/Dash Core:19.1.0(devnet.devnet-privatesend)/"} diff --git a/src/argv.js b/src/argv.js deleted file mode 100755 index 6c570b9..0000000 --- a/src/argv.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node -'use strict'; -let Lib = {}; -module.exports = Lib; -Lib.extractOption = function (opt, capture = false) { - /** - * opt should be something like: 'instance-name'. - * Then extractOption would look in process.argv for - * --instance-name. - * - * if capture is truth-y, extractOption will look for - * --instance-name=N and will return a the value of N - * - */ - for (const arg of process.argv) { - let regex = '^--' + opt; - if (capture) { - let regex = '^--' + opt + '=(.*)$'; - let match = arg.match(regex); - if (match) { - return match[1]; - } - } - let match = arg.match(regex); - if (match) { - return match; - } - } - return null; -}; diff --git a/src/array-utils.js b/src/array-utils.js deleted file mode 100755 index 8d2f7f8..0000000 --- a/src/array-utils.js +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const xt = require('@mentoc/xtract').xt; -const crypto = require('crypto'); - -function random(array) { - for (let i = 0; i < array.length; i++) { - if (crypto.rng(1) > crypto.rng(1)) { - continue; - } else { - return array[i]; - } - } - return random(array); -} -function extract(array, key) { - let selected = []; - for (const ele of array) { - selected.push(ele[key]); - } - return selected; -} -function bigint_safe_json_stringify(buffer, stringify_space = 2) { - return JSON.stringify( - buffer, - function (key, value) { - this.k = key; - return typeof value === 'bigint' ? value.toString() + 'n' : value; - }, - stringify_space, - ); -} -function uniqueByKey(array, key) { - let map = {}; - let saved = []; - for (const ele of array) { - if (typeof map[ele[key]] !== 'undefined') { - continue; - } - map[ele[key]] = 1; - saved.push(ele); - } - return saved; -} -function flatten(arr) { - if (arr.length === 1 && Array.isArray(arr[0])) { - return flatten(arr[0]); - } - if (!Array.isArray(arr)) { - return arr; - } - if (Array.isArray(arr) && !Array.isArray(arr[0])) { - return arr[0]; - } - return arr; -} -function unique(arr) { - let map = {}; - let uni = []; - for (const a of arr) { - if (typeof map[a] !== 'undefined') { - continue; - } - map[a] = 1; - uni.push(a); - } - return uni; -} - -function ps_extract(ps, newlines = true) { - let out = ps.stdout.toString(); - let err = ps.stderr.toString(); - out = out.replace(/^[\s]+/, '').replace(/[\s]+$/, ''); - err = err.replace(/^[\s]+/, '').replace(/[\s]+$/, ''); - if (!newlines) { - out = out.replace(/[\n]+$/, ''); - err = err.replace(/[\n]+$/, ''); - } - return { err, out }; -} -module.exports = { - extract, - bigint_safe_json_stringify, - uniqueByKey, - flatten, - unique, - ps_extract, - xt, - random, -}; diff --git a/src/bootstrap/index.js b/src/bootstrap/index.js deleted file mode 100644 index 838ee24..0000000 --- a/src/bootstrap/index.js +++ /dev/null @@ -1,2076 +0,0 @@ -'use strict'; - -/** - * - lmdb file should be ~/.dashjoinjs//db/data.mdb - * - */ - -const xt = require('@mentoc/xtract').xt; -const MetaDB = require('./metadb.js'); -const ArrayUtils = require('../array-utils.js'); -const DebugLib = require('../debug.js'); -const Sanitizers = require('../sanitizers.js'); -const { unique, bigint_safe_json_stringify, ps_extract } = ArrayUtils; -const { dd, d } = DebugLib; -const { sanitize_txid, sanitize_address, sanitize_addresses } = Sanitizers; -const { sanitize_tx_format } = Sanitizers; -const { sanitize_psbt } = Sanitizers; -const { extractOption } = require('../argv.js'); -const COIN = require('../coin-join-constants.js').COIN; -const NetworkUtil = require('../network-util.js'); -const hashByteOrder = NetworkUtil.hashByteOrder; -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -const Signature = DashCore.crypto.Signature; -const PrivateKey = DashCore.PrivateKey; -let db_cj, db_cj_ns, db_put, db_get, db_append, db_del; -let db_set_ns; -const UserDB = require('./userdb.js'); - -function RingBuffer(items) { - let self = this; - self.items = items; - self.i = 0; - self.next = function () { - if (self.i >= self.items.length) { - self.i = 0; - } - let item = self.items[self.i]; - self.i += 1; - return item; - }; -} - -let Bootstrap = {}; -let db = Bootstrap; -module.exports = Bootstrap; - -let cproc = require('child_process'); - -//const crypto = require('crypto'); -const fs = require('fs'); - -async function LogUtxos(user, utxos) { - console.log(`LogUtxos: ${user}`); - let fn = `${process.env.HOME}/data/${user}-utxos.json`; - await require('node:fs').promises.writeFile(fn, JSON.stringify(utxos)); -} -function cli_args(list) { - return [ - '-conf=' + process.env.HOME + '/.dashmate/local_seed/core/dash.conf', - ...list, - ]; -} - -function mkudb(username) { - return new UserDB( - { - db_cj, - db_cj_ns, - db_put, - db_get, - db_append, - db_del, - db_set_ns, - db_make_key: Bootstrap.MetaDB.DB.make_key, - }, - username, - 'cj', - ); -} - -/** - * options is exactly like the man page says: - { - minimumAmount, //(numeric or string, optional, default=0) Minimum value of each UTXO in DASH - maximumAmount, //(numeric or string, optional, default=unlimited) Maximum value of each UTXO in DASH - maximumCount, // (numeric, optional, default=unlimited) Maximum number of UTXOs - minimumSumAmount, // (numeric or string, optional, default=unlimited) Minimum sum value of all UTXOs in DASH - coinType, //(numeric, optional, default=0) Filter coinTypes as follows: - //0=ALL_COINS, 1=ONLY_FULLY_MIXED, 2=ONLY_READY_TO_MIX, 3=ONLY_NONDENOMINATED, - //4=ONLY_MASTERNODE_COLLATERAL, 5=ONLY_COINJOIN_COLLATERAL - }; - ---- - If addresses is a string or an array, it'll be used. - If not set, then the `main-address` will be used - -*/ - -Bootstrap.list_unspent_advanced = async function ( - username, - options, - addresses = null, -) { - username = Bootstrap.alias_check(username); - let udb = mkudb(username); - let main_address = await udb.main_address(); - - let addr_json = JSON.stringify([main_address]); - if (Array.isArray(addresses)) { - let addr = unique(sanitize_addresses(addresses)); - addr_json = JSON.stringify(addr); - } else if (typeof addresses === 'string') { - addr_json = JSON.stringify(sanitize_addresses([addresses])); - } - let minimumAmount = null; - let maximumAmount = null; - let maximumCount = null; - let minimumSumAmount = null; - let coinType = 0; - - if (xt(options, 'minimumAmount') !== null) { - minimumAmount = options.minimumAmount; - } - if (xt(options, 'maximumAmount') !== null) { - maximumAmount = options.maximumAmount; - } - if (xt(options, 'maximumCount') !== null) { - maximumCount = options.maximumCount; - } - if (xt(options, 'minimumSumAmount') !== null) { - minimumSumAmount = options.minimumSumAmount; - } - if (xt(options, 'coinType') !== null) { - coinType = options.coinType; - } - - let query_options = {}; - if (minimumAmount !== null) { - query_options.minimumAmount = minimumAmount; - } - if (maximumAmount !== null) { - query_options.maximumAmount = maximumAmount; - } - if (maximumCount !== null) { - query_options.maximumCount = maximumCount; - } - if (minimumSumAmount !== null) { - query_options.minimumSumAmount = minimumSumAmount; - } - if (coinType !== null) { - query_options.coinType = coinType; - } - query_options = JSON.stringify(query_options); - let unspent_options = [ - 'listunspent', - '1', // 1. minconf (numeric, optional, default=1) The minimum confirmations to filter - '9999999', // 2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter - addr_json, // 3. addresses (json array, optional, default=empty array) The dash addresses to filter - 'true', // (boolean, optional, default=true) Include outputs that are not safe to spend - query_options, // 5. query_options (json object, optional) JSON with query options - ]; - let { out, err } = await Bootstrap.auto.wallet_exec( - username, - unspent_options, - ); - if (err.length) { - throw new Error(err); - } - try { - out = JSON.parse(out); - } catch (e) { - throw new Error(e); - } - return out; -}; -/** - * Take a single UTXO and split it up into as many denominated inputs - * as possible. - */ -Bootstrap.split_utxo = async function (username) { - const AMOUNT = '0.00100001'; - const SATOSHIS = parseInt(parseFloat(AMOUNT, 10) * COIN, 10); - username = Bootstrap.alias_check(username); - const udb = mkudb(username); - let main_address = await udb.main_address(); - if (main_address === null || main_address.length === 0) { - let ps = await Bootstrap.wallet_exec(username, ['listaddressbalances']); - let { err, out } = ps_extract(ps); - if (err) { - throw new Error(err); - } - out = JSON.parse(out); - main_address = Object.keys(out)[0]; - await udb.set_main_address(main_address); - main_address = await udb.main_address(); - } - - let query_options = { - minimumAmount: 10, //(numeric or string, optional, default=0) Minimum value of each UTXO in DASH - //maximumAmount: AMOUNT, //(numeric or string, optional, default=unlimited) Maximum value of each UTXO in DASH - maximumCount: 10, // (numeric, optional, default=unlimited) Maximum number of UTXOs - //"minimumSumAmount": amount, (numeric or string, optional, default=unlimited) Minimum sum value of all UTXOs in DASH - coinType: 0, //(numeric, optional, default=0) Filter coinTypes as follows: - //0=ALL_COINS, 1=ONLY_FULLY_MIXED, 2=ONLY_READY_TO_MIX, 3=ONLY_NONDENOMINATED, - //4=ONLY_MASTERNODE_COLLATERAL, 5=ONLY_COINJOIN_COLLATERAL - }; - let utxos = await Bootstrap.list_unspent_advanced(username, query_options); - if (utxos.length === 0) { - throw new Error('no utxos matching the criteria could be found'); - } - let created = await Bootstrap.generate_new_addresses(username, 2); - await Bootstrap.store_denominated_addresses(username, created); - let change_address = await Bootstrap.get_multi_change_address_from_cli( - username, - 1, - ); - let rb_address = new RingBuffer(created); - if (Array.isArray(change_address)) { - change_address = change_address[0]; - } - for (const choice of utxos) { - let tx = new Transaction(); - tx.from({ - txId: choice.txid, - outputIndex: choice.vout, - scriptPubKey: Script.buildPublicKeyHashOut( - choice.address, - Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY, - ), - amount: choice.amount, - }); - let pk = await Bootstrap.get_private_key(username, choice.address); - pk = PrivateKey.fromWIF(pk); - for (let i = 0; i < 200; i++) { - tx.to(rb_address.next(), SATOSHIS); - } - tx.change(change_address); - tx.sign(pk, Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY); - let ser = tx.serialize(); - let output = await Bootstrap.wallet_exec(username, [ - 'sendrawtransaction', - ser, - ]); - let { out, err } = ps_extract(output); - if (err.length) { - throw new Error(err); - } - d(out); - await Bootstrap.save_denominated_tx(username, out); - } -}; -Bootstrap.get_junk_username = function () { - for (const key in Bootstrap.user_aliases) { - if (Bootstrap.user_aliases[key].match(/^junk/)) { - return Bootstrap.user_aliases[key]; - } - } - return null; -}; -Bootstrap.grind_junk_user = async function () { - await Bootstrap.load_alias_ram_slots(); - await Bootstrap.unlock_all_wallets(); - for (let i = 0; i < 100; i++) { - await Bootstrap.generate_dash_to('junk'); - } -}; -db.junk_name = function () { - return 'junk'; -}; -db.make_junk_wallet = async function () { - let wallet_name = await db.junk_name(); - await db.user_create(wallet_name); - console.info(`[ok]: user "${wallet_name}" created`); - await db.run([ - 'createwallet', - wallet_name, - 'false', - 'false', - 'foobar', - 'false', - 'true', - ]); - - let w_addresses = []; - let buffer = await db.wallet_exec(wallet_name, ['getnewaddress']); - let { out } = ps_extract(buffer, false); - if (out.length) { - w_addresses.push(out); - } - await Bootstrap.set_addresses(wallet_name, sanitize_addresses(w_addresses)); - await Bootstrap.unlock_wallet(wallet_name); - await Bootstrap.alias_users(); - await Bootstrap.unlock_all_wallets(); -}; -function sanitize_txid_list(out) { - for (let i = 0; i < out.length; i++) { - out[i] = sanitize_txid(out[i]); - } - return out; -} - -Bootstrap.save_denominated_tx = async function (username, out) { - username = Bootstrap.alias_check(username); - if (Array.isArray(out)) { - out = sanitize_txid_list(out); - return await mkudb(username).append_array('denom-txids', out); - } else { - out = sanitize_txid(out); - return await mkudb(username).append_array('denom-txids', [out]); - } -}; -Bootstrap.send_satoshis_to = async function (send_to, amt, times = 10) { - send_to = Bootstrap.alias_check(send_to); - let address = await Bootstrap.first_address(send_to); - d(`sending to ${address}`); - //await Bootstrap.wallet_exec(send_to, ['generatetoaddress', '200', address]); - //address = await Bootstrap.nth_address(send_to, 2); - - for (let i = 0; i < times; i++) { - let output = await Bootstrap.wallet_exec(send_to, [ - 'sendtoaddress', - address, - amt, - ]); - let { out, err } = ps_extract(output); - d({ out, err }); - } -}; - -Bootstrap.nth_address = async function (username, n) { - let address = await Bootstrap.get_addresses(username, n); - if (Array.isArray(address)) { - return address[n - 1]; - } - return address; -}; -Bootstrap.first_address = async function (username) { - let address = await Bootstrap.get_addresses(username, 1); - if (Array.isArray(address)) { - return address[0]; - } - return address; -}; -Bootstrap.send_raw_transaction = async function (username, str) { - let output = await Bootstrap.auto.wallet_exec(username, [ - 'sendrawtransaction', - sanitize_tx_format(str), - ]); - if (output.err.length) { - throw new Error(output.err); - } - return output.out; -}; -Bootstrap.decode_raw_transaction = async function (username, decode) { - let output = await Bootstrap.auto.wallet_exec(username, [ - 'decoderawtransaction', - sanitize_tx_format(decode), - ]); - if (output.err.length) { - throw new Error(output.err); - } - try { - return JSON.parse(output.out); - } catch (e) { - throw new Error(e); - } -}; -Bootstrap.get_transaction = async function ( - username, - txid_list, - keep_errors = false, -) { - if (keep_errors) { - Bootstrap.get_transaction_errors = []; - } - username = Bootstrap.alias_check(username); - if (Array.isArray(txid_list) === false) { - txid_list = [txid_list]; - } - let mappings = {}; - for (const txid of txid_list) { - let output = await Bootstrap.wallet_exec(username, [ - 'gettransaction', - sanitize_txid(txid), - ]); - let { out, err } = ps_extract(output); - if (err.length) { - if (keep_errors) { - Bootstrap.get_transaction_errors.push(err); - } - } - try { - mappings[txid] = JSON.parse(out); - } catch (e) { - if (keep_errors) { - Bootstrap.get_transaction_errors.push(e); - } - mappings[txid] = null; - } - } - return mappings; -}; -Bootstrap.get_address_from_txid = async function (username, txid_list) { - username = Bootstrap.alias_check(username); - if (Array.isArray(txid_list) === false) { - txid_list = [txid_list]; - } - let mappings = {}; - for (const txid of txid_list) { - let output = await Bootstrap.wallet_exec(username, [ - 'gettransaction', - sanitize_txid(txid), - ]); - let { out, err } = ps_extract(output); - if (err.length) { - throw new Error(err); - } - try { - out = JSON.parse(out); - mappings[txid] = xt(out, 'details.0.address'); - } catch (e) { - throw new Error(e); - } - } - return mappings; -}; -Bootstrap.save_exec = false; -Bootstrap.verbose = false; -Bootstrap.list_unspent = async function (username, opts = []) { - await Bootstrap.unlock_all_wallets(); - username = Bootstrap.alias_check(username); - let params = ['listunspent']; - if (Array.isArray(opts)) { - for (const p of opts) { - params.push(p); - } - } - let output = await Bootstrap.wallet_exec(username, params); - let { out, err } = ps_extract(output); - if (err) { - throw new Error(err); - } - //dd(out.split('\n').length); - return JSON.parse(out); -}; -Bootstrap.list_unspent_by_address = async function ( - username, - address, - query_opts = null, -) { - await Bootstrap.unlock_all_wallets(); - username = Bootstrap.alias_check(username); - let params = [ - 'listunspent', - '1', - '99999', - `["${sanitize_address(address)}"]`, - ]; - let query = {}; - if (query_opts !== null) { - if (xt(query_opts, 'minimumAmount')) { - query.minimumAmount = xt(query_opts, 'minimumAmount'); - } - if (xt(query_opts, 'maximumAmount')) { - query.maximumAmount = xt(query_opts, 'maximumAmount'); - } - if (xt(query_opts, 'maximumCount')) { - query.maximumCount = xt(query_opts, 'maximumCount'); - } - if ([true, false].includes(xt(query_opts, 'include_unsafe'))) { - params.push(xt(query_opts, 'include_safe')); - } else { - params.push('false'); - } - params.push(JSON.stringify(query)); - } - - let output = await Bootstrap.wallet_exec(username, params); - let { out, err } = ps_extract(output); - if (err) { - throw new Error(err); - } - return JSON.parse(out); -}; -Bootstrap.get_denominated_utxos = async function ( - username, - denominatedAmount = null, - count = 5250, - filter_used = true, -) { - username = Bootstrap.alias_check(username); - let list = []; - let utxos = await Bootstrap.user_utxos_from_cli(username); - for (const u of utxos) { - if (filter_used && Bootstrap.is_txid_used(u.txid)) { - continue; - } - if (denominatedAmount === null) { - list.push(u); - } else if (u.satoshis === denominatedAmount) { - list.push(u); - } - if (list.length === count) { - return list; - } - } - return list; -}; -Bootstrap.alias_users = async function () { - let users = await Bootstrap.user_list(); - for (const user of users) { - let alias = await Bootstrap.alias_user(user); - d(`Assigned "${alias}" to "${user}"`); - } -}; -Bootstrap.alias_check = function (user) { - if (user === 'junk') { - user = Bootstrap.get_junk_username(); - if (!user) { - return 'junk'; - } - } - if (xt(user, 'clazz') === 'ClientSession') { - user = user.username; - } - if (user.match(/^user[\d]+$/)) { - DebugLib.setNickname(user); - return Bootstrap.user_aliases[user]; - } - DebugLib.setNickname(user); - return user; -}; -Bootstrap.alias_ctr = 0; -Bootstrap.alias_user = async function (username) { - db_cj_ns(username); - Bootstrap.alias_ctr += 1; - const alias = 'user' + String(Bootstrap.alias_ctr); - await db_put('alias', alias); - return alias; -}; -Bootstrap.user_by_alias = async function (alias) { - for (const user of await Bootstrap.user_list()) { - db_cj_ns(user); - let db_alias = await db_get('alias'); - if (db_alias === alias) { - return user; - } - } - return null; -}; -Bootstrap.ring_buffer_init = async function (key_name, values) { - db_cj_ns('ring_buffer'); - await db_put( - key_name, - JSON.stringify({ - value: values[0], - values, - }), - ); -}; -Bootstrap.ring_buffer_next = async function (key_name) { - db_cj_ns('ring_buffer'); - let val = await db_get(key_name); - try { - val = JSON.parse(val); - } catch (e) { - throw new Error(e); - } - if (val === null) { - throw new Error('needs-init'); - } - let values = val.values; - let current = val.value; - let index = values.indexOf(current); - if (index + 1 >= values.length) { - val.value = values[0]; - } else { - val.value = values[index + 1]; - } - await db_put( - key_name, - JSON.stringify({ - value: val.value, - values: val.values, - }), - ); - return val.value; -}; -Bootstrap.increment_key = async function (username, key_name) { - username = Bootstrap.alias_check(username); - db_cj_ns([username, 'counters']); - let ctr = await db_get(key_name); - if (typeof ctr === 'undefined' || ctr === null) { - ctr = 0; - } - ctr = parseInt(ctr, 10); - if (isNaN(ctr)) { - ctr = 0; - } - ++ctr; - await db_put(key_name, String(ctr)); - return ctr; -}; -Bootstrap.raw_transaction = async function (username, txid) { - username = Bootstrap.alias_check(username); - let ps = await Bootstrap.wallet_exec(username, [ - 'getrawtransaction', - sanitize_txid(txid), - ]); - let { out, err } = ps_extract(ps); - if (err.length) { - throw new Error(err); - } - if (out.length) { - return out; - } -}; -Bootstrap.store_dsf = async function (data) { - if (data instanceof Uint8Array) { - data = data.toString(); - } - throw new Error('stub'); -}; -function arbuf_to_hexstr(buffer) { - // buffer is an ArrayBuffer - return [...new Uint8Array(buffer)] - .map((x) => x.toString(16).padStart(2, '0')) - .join(''); -} -Bootstrap.ps_extract = ps_extract; -Bootstrap.set_dash_cli = function (p) { - Bootstrap.DASH_CLI = p; -}; -Bootstrap.get_dash_cli = function () { - return Bootstrap.DASH_CLI; -}; -Bootstrap.get_config = function () { - Bootstrap.__config = { - db: { - handle: Bootstrap.DB, - }, - helpers: Bootstrap.helpers(), - instance: Bootstrap.__data.instance, - }; - return Bootstrap.__config; -}; -Bootstrap.__data = { - instance: { - name: 'base', - db_path: null, - db_name: 'dash', - max_dbs: 10, - }, -}; -Bootstrap.shuffle = function (a) { - /** - * Shuffles array in place. - * @param {Array} a items An array containing the items. - */ - let j, x, i; - for (i = a.length - 1; i > 0; i--) { - j = Math.floor(Math.random() * (i + 1)); - x = a[i]; - a[i] = a[j]; - a[j] = x; - } - return a; -}; -Bootstrap.filter_shuffle_address_count = async function ( - username, - except, - count, -) { - let addr = await Bootstrap.user_addresses(username); - addr = Bootstrap.shuffle(addr); - let selected = []; - let selectedMap = {}; - for (const a of addr) { - if (except.indexOf(a) === -1) { - if (typeof selectedMap[a] !== 'undefined') { - continue; - } - selectedMap[a] = 1; - selected.push(a); - if (selected.length === count) { - return selected; - } - } - } -}; -Bootstrap.filter_shuffle_address = async function (username, except) { - username = Bootstrap.alias_check(username); - let addr = await Bootstrap.user_addresses(username); - addr = Bootstrap.shuffle(addr); - if (except.length === 0) { - return addr[0]; - } - for (const a of addr) { - if (except.indexOf(a) === -1) { - return a; - } - } -}; -Bootstrap.filter_denominated_transaction = async function ( - username, - denominatedAmount, - count, - except, -) { - count = parseInt(count, 10); - if (isNaN(count) || count <= 0) { - throw new Error('count must be a positive non-zero integer'); - } - - let utxos = await Bootstrap.get_denominated_utxos( - username, - denominatedAmount, - count, - ); - let selected = []; - let selMap = {}; - for (const utxo of utxos) { - if (except.indexOf(utxo.txid) !== -1) { - continue; - } - if (typeof selMap[utxo.txid] === 'undefined') { - selMap[utxo.txid] = 1; - selected.push(utxo); - if (selected.length === count) { - return selected; - } - } - } - throw new Error("Couldn't find enough transactions"); -}; -Bootstrap.random_change_address = async function (username, except) { - username = Bootstrap.alias_check(username); - let addr = await Bootstrap.get_addresses(username, 500); - addr = Bootstrap.shuffle(addr); - for (const a of addr) { - if (except.indexOf(a) === -1) { - return a; - } - } -}; -Bootstrap.mkpath = async function (path) { - await cproc.spawnSync('mkdir', ['-p', path]); -}; -Bootstrap._data = { - user_names: [ - 'luke', - 'han', - 'chewie', - 'vader', - 'padme', - 'wedge', - 'tobias', - 'obi', - 'jarjar', - 'lando', - 'boba', - 'jango', - 'finn', - 'kit', - ], - un_index: -1, -}; - -Bootstrap.random_name = async function () { - Bootstrap._data.un_index += 1; - return Bootstrap._data.user_names[Bootstrap._data.un_index]; -}; - -Bootstrap.run = async function (cli_arguments) { - return await cproc.spawnSync(Bootstrap.DASH_CLI, cli_args(cli_arguments)); -}; - -Bootstrap.__error = null; -Bootstrap.saved_exec_list = []; - -Bootstrap.load_instance = async function (instance_name, options = {}) { - if (instance_name === null) { - let file = `${process.env.HOME}/.dashjoinjs/current`; - instance_name = await fs.readFileSync(file); - instance_name = instance_name.toString().replace(/[^a-z0-9]+/gi, ''); - } - if ([true, false].includes(xt(options, 'save_exec'))) { - Bootstrap.save_exec = options.save_exec; - } - Bootstrap.saved_exec_list = []; - Bootstrap.DASH_CLI = 'dash-cli'; - Bootstrap.DB = require('../lmdb/lmdb.js'); - Bootstrap.__data.instance.name = instance_name; - if (!Bootstrap.sane_instance()) { - throw new Error(`Couldn't load instance: "${Bootstrap.__error}"`); - } - let n = Bootstrap.__data.instance.name; - let db_path = [process.env.HOME, '.dashjoinjs', n, 'db'].join('/'); - Bootstrap.__data.instance.db_path = db_path; - await Bootstrap.mkpath(db_path); - - let exists = await fs.existsSync(db_path.replace(/\/$/, '') + '/data.mdb'); - Bootstrap.DB.open({ - path: db_path, - db_name: Bootstrap.__data.instance.db_name, - create: !exists, - maxDbs: Bootstrap.__data.instance.max_dbs, - mapSize: 32 * 1024 * 1024, - }); - Bootstrap.MetaDB = new MetaDB(Bootstrap.DB); - db_set_ns = Bootstrap.MetaDB.set_namespaces; - db_cj = Bootstrap.MetaDB.db_cj; - db_cj_ns = Bootstrap.MetaDB.db_cj_ns; - db_get = Bootstrap.MetaDB.db_get; - db_put = Bootstrap.MetaDB.db_put; - db_append = Bootstrap.MetaDB.db_append; - db_del = Bootstrap.MetaDB.db_del; - db_cj(); - await Bootstrap.load_used_txid_ram_slots(); - await Bootstrap.load_alias_ram_slots(); - return Bootstrap; -}; -Bootstrap.initialize = Bootstrap.load_instance; - -Bootstrap.sane_instance = function () { - if (typeof Bootstrap.__data.instance.name === 'undefined') { - Bootstrap.__error = 'instance structure corrupt'; - return false; - } - let n = Bootstrap.__data.instance.name; - if (n === null || String(n).length === 0) { - Bootstrap.__error = 'empty instance name'; - return false; - } - Bootstrap.__data.instance.name = n.replace(/[^a-z0-9_]+/gi, ''); - if (Bootstrap.__data.instance.name.length === 0) { - Bootstrap.__error = 'after sanitization: empty instance name'; - return false; - } - return true; -}; -Bootstrap.user_list = async function (options = {}) { - db_cj(); - let list = await db_get('users'); - try { - list = JSON.parse(list); - if (!Array.isArray(list)) { - return []; - } - if (xt(options, 'with') === 'alias') { - for (let i = 0; i < list.length; i++) { - db_cj_ns(list[i]); - let alias = await db_get('alias'); - list[i] = { user: list[i], alias }; - } - } - return list; - } catch (e) { - return []; - } -}; - -/** - * opts can be: - * { - change: true/false, // to get change addresses as well - only_change: true, // will return only change addresses and not combined with addresses - main_only: true, // will return main address - include_all: true, // will give main, change, and addresses - } - */ -Bootstrap.get_addresses = async function (username, opts = {}) { - username = Bootstrap.alias_check(username); - let udb = mkudb(username); - let change = xt(opts, 'change'); - let only_change = xt(opts, 'only_change'); - let main_only = xt(opts, 'main_only'); - let include_all = xt(opts, 'include_all'); - let main = await udb.main_address(); - if (main_only === true) { - return main; - } - if (only_change === true) { - return unique(await udb.get_array('change')); - } - let list = await udb.get_array('addresses'); - if (change === true) { - list = unique([...list, ...(await udb.get_array('change'))]); - } - if (include_all === true) { - return unique([...list, ...(await udb.get_array('change')), main]); - } - - return list; -}; -Bootstrap.user_addresses = async function (username) { - username = Bootstrap.alias_check(username); - return await mkudb(username).get_array('addresses'); -}; -Bootstrap.sanitize_address = sanitize_address; - -Bootstrap.import_user_addresses_from_cli = async function (username) { - username = Bootstrap.alias_check(username); - let ps = await Bootstrap.wallet_exec(username, ['listaddressbalances']); - let { err, out } = ps_extract(ps); - if (err.length) { - console.error(err); - } else { - try { - let output = JSON.parse(out); - let keep = []; - for (const address in output) { - keep.push(address); - } - if (keep.length) { - await mkudb(username).set_array('addresses', keep); - console.info(`${keep.length} addresses imported for user: ${username}`); - return true; - } else { - console.warn(`No addresses for user: ${username}`); - } - } catch (e) { - console.error(e); - } - } - return false; -}; - -Bootstrap.is_sane_address = function (address) { - try { - if (address === null || address === 'null') { - return false; - } - let a = sanitize_address(address); - if (a === null || String(a).length === 0) { - return false; - } - return a.length > 0; - } catch (e) { - return false; - } -}; -Bootstrap.user_utxos_from_cli = async function (username, addresses = []) { - await Bootstrap.unlock_all_wallets(); - username = Bootstrap.alias_check(username); - let utxos = []; - if (addresses === null || addresses.length === 0) { - addresses = await Bootstrap.get_addresses(username); - } - for (const address of addresses) { - if (Bootstrap.is_sane_address(address) === false) { - d(`weird address: "${address}"`); - continue; - } - let encoded = JSON.stringify({ - addresses: [Bootstrap.sanitize_address(address)], - }); - let ps = await Bootstrap.wallet_exec(username, [ - 'getaddressutxos', - encoded, - ]); - let { err, out } = ps_extract(ps); - if (err.length) { - console.error({ user_utxos_from_cli_ERROR: err, encoded }); - } else { - if (out === '[\n]') { - continue; - } - try { - let txns = JSON.parse(out); - if (Array.isArray(txns)) { - utxos.push(...txns); - } else { - utxos.push(txns); - } - } catch (e) { - d(e); - } - } - } - return utxos; -}; -Bootstrap.user_exists = async function (username) { - username = Bootstrap.alias_check(username); - let users = await Bootstrap.user_list(); - for (const user of users) { - if (user === username) { - return true; - } - } - return false; -}; - -Bootstrap.user_create = async function (username) { - username = Bootstrap.alias_check(username); - db_cj(); - let list = await db_get('users'); - try { - list = JSON.parse(list); - if (!Array.isArray(list)) { - list = []; - } - } catch (e) { - list = []; - } - for (let user of list) { - if (user === username) { - throw new Error('user already exists'); - } - } - list.push(username); - await db_put('users', JSON.stringify(list)); -}; - -Bootstrap.auto = {}; -Bootstrap.auto.build_executor = async function (wallet_name) { - wallet_name = Bootstrap.alias_check(wallet_name); - let _wallet = wallet_name; - return async function (...args) { - return await Bootstrap.auto.wallet_exec(_wallet, [...args]); - }; -}; -Bootstrap.auto.wallet_exec = async function (wallet_name, cli_arguments) { - wallet_name = Bootstrap.alias_check(wallet_name); - if (['1', 'true'].indexOf(String(extractOption('verbose', true))) !== -1) { - let args = cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]); - if (Bootstrap.verbose || Bootstrap.save_exec) { - console.info(`wallet_exec: "${Bootstrap.DASH_CLI} ${args}`); - } - } - if (Bootstrap.save_exec) { - Bootstrap.saved_exec_list.push([ - Bootstrap.DASH_CLI, - cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]), - ]); - } - let output = await cproc.spawnSync( - Bootstrap.DASH_CLI, - cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]), - ); - return ps_extract(output); -}; -Bootstrap.wallet_exec = async function (wallet_name, cli_arguments) { - wallet_name = Bootstrap.alias_check(wallet_name); - if (['1', 'true'].indexOf(String(extractOption('verbose', true))) !== -1) { - let args = cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]); - if (Bootstrap.verbose || Bootstrap.save_exec) { - console.info(`wallet_exec: "${Bootstrap.DASH_CLI} ${args}`); - } - } - if (Bootstrap.save_exec) { - Bootstrap.saved_exec_list.push([ - Bootstrap.DASH_CLI, - cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]), - ]); - } - return await cproc.spawnSync( - Bootstrap.DASH_CLI, - cli_args([`-rpcwallet=${wallet_name}`, ...cli_arguments]), - ); -}; -Bootstrap.get_multi_change_address_from_cli = async function ( - username, - count, - save = true, -) { - let addresses = []; - for (let i = 0; i < count; i++) { - let buffer = await Bootstrap.wallet_exec(username, ['getrawchangeaddress']); - let { out } = ps_extract(buffer, false); - if (out.length) { - addresses.push(out); - } - } - if (save) { - await Bootstrap.store_addresses(username, addresses, 'change'); - } - return addresses; -}; -Bootstrap.get_change_address_from_cli = async function (username, save = true) { - username = Bootstrap.alias_check(username); - let buffer = await Bootstrap.wallet_exec(username, ['getrawchangeaddress']); - let { out, err } = ps_extract(buffer, false); - if (err.length) { - throw new Error(err); - } - if (!save) { - return out; - } - - await Bootstrap.store_addresses(username, [out], 'change'); - return out; -}; -Bootstrap.get_change_addresses = async function (username) { - username = Bootstrap.alias_check(username); - return await mkudb(username).get_array('change'); -}; -Bootstrap.decode = function (in_ps_extracted) { - if (in_ps_extracted.err.length) { - throw new Error(in_ps_extracted.err); - } - try { - return JSON.parse(in_ps_extracted.out); - } catch (e) { - throw new Error(e); - } -}; -Bootstrap.finalize_psbt = async function (username, psbt) { - username = Bootstrap.alias_check(username); - return Bootstrap.decode( - await Bootstrap.auto.wallet_exec(username, [ - 'finalizepsbt', - sanitize_psbt(psbt), - ]), - ); -}; -Bootstrap.generate_new_addresses = async function (username, count) { - username = Bootstrap.alias_check(username); - let addresses = []; - for (let i = 0; i < count; i++) { - let buffer = await Bootstrap.wallet_exec(username, ['getnewaddress']); - let { out } = ps_extract(buffer, false); - if (out.length) { - addresses.push(out); - } - } - await Bootstrap.store_addresses(username, addresses); - return addresses; -}; -Bootstrap.generate_address = async function (username, count = 10) { - if (isNaN(parseInt(count, 10))) { - count = 10; - } - username = Bootstrap.alias_check(username); - let addresses = []; - for (let i = 0; i < count; i++) { - let buffer = await Bootstrap.wallet_exec(username, ['getnewaddress']); - let { out } = ps_extract(buffer, false); - if (out.length) { - addresses.push(out); - } - } - await Bootstrap.store_addresses(username, addresses); - return addresses; -}; -Bootstrap.store_change_addresses = async function (username, w_addresses) { - username = Bootstrap.alias_check(username); - return await Bootstrap.store_addresses( - username, - sanitize_addresses(w_addresses), - 'change', - ); -}; -Bootstrap.normalize_pk = async function (privateKey) { - if (Array.isArray(privateKey)) { - if (Array.isArray(privateKey[0])) { - return privateKey[0][0]; - } - } - return privateKey; -}; -Bootstrap.get_private_key = async function (username, address) { - username = Bootstrap.alias_check(username); - let buffer = await Bootstrap.wallet_exec(username, ['dumpprivkey', address]); - let { out, err } = ps_extract(buffer, false); - if (err.length) { - console.error(err); - } - if (out.length) { - return out; - } -}; -Bootstrap.set_addresses = async function (username, w_addresses) { - username = Bootstrap.alias_check(username); - return await mkudb(username).set_array( - 'addresses', - sanitize_addresses(w_addresses), - ); -}; - -Bootstrap.store_denominated_addresses = async function (username, addresses) { - username = Bootstrap.alias_check(username); - return await Bootstrap.store_addresses( - username, - addresses, - 'denominated-addresses', - ); -}; -Bootstrap.get_denominated_addresses = async function (username) { - username = Bootstrap.alias_check(username); - return await Bootstrap.get_addresses(username, 'denominated-addresses'); -}; - -Bootstrap.get_addresses = async function (username, key = 'addresses') { - username = Bootstrap.alias_check(username); - let udb = mkudb(username); - let addresses = await udb.get_array(key); - if (Array.isArray(addresses)) { - return unique(sanitize_addresses(addresses)); - } - return []; -}; - -Bootstrap.store_addresses = async function ( - username, - w_addresses, - key = 'addresses', -) { - username = Bootstrap.alias_check(username); - let udb = mkudb(username); - let addresses = await udb.get_array(key); - if (Array.isArray(addresses)) { - addresses = [...addresses, ...sanitize_addresses(w_addresses)]; - } else { - addresses = sanitize_addresses(w_addresses); - } - addresses = unique(addresses); - return await udb.set_array(key, addresses); -}; - -/** - * Returns a user that is not `forUser` - */ -Bootstrap.get_random_payee = async function (forUser) { - forUser = Bootstrap.alias_check(forUser); - let users = await Bootstrap.user_list(); - users = Bootstrap.shuffle(users); - for (const user of users) { - if (user !== forUser) { - return user; - } - } -}; - -Bootstrap.random_payee_address = async function (forUser) { - forUser = Bootstrap.alias_check(forUser); - let users = await Bootstrap.user_list(); - users = Bootstrap.shuffle(users); - let addy = null; - let addresses = []; - for (const user of users) { - if (user !== forUser) { - addresses = await Bootstrap.user_addresses(user); - addresses = Bootstrap.shuffle(addresses); - addy = { user: user, address: addresses[0] }; - return addy; - } - } - return addy; -}; -Bootstrap.filter_address = async function (username, except) { - username = Bootstrap.alias_check(username); - let keep = []; - await mkudb(username).page_array('addresses', function (address) { - if (except.indexOf(address) === -1) { - keep.push(address); - } - }); - return keep; -}; - -Bootstrap.get_users_with_denominated_utxos = async function (userDenoms) { - await Bootstrap.unlock_all_wallets(); - let users = await Bootstrap.user_list(); - let usersWithDenoms = []; - for (const userName of users) { - let utxos = await Bootstrap.get_denominated_utxos(userName, userDenoms); - if (utxos.length === 0) { - continue; - } - usersWithDenoms.push(userName); - } - return unique(usersWithDenoms); -}; -Bootstrap.create_wallets = async function (count = 3) { - for (let ctr = 0; ctr < count; ctr++) { - let wallet_name = await Bootstrap.random_name(); - await Bootstrap.user_create(wallet_name).catch(function (error) { - console.error('ERROR: ', error); - }); - //console.info(`[ok]: user "${wallet_name}" created`); - await Bootstrap.run([ - 'createwallet', - wallet_name, - 'false', - 'false', - 'foobar', - 'false', - 'true', - ]); - //console.info(`[ok]: wallet "${wallet_name}" created`); - - let w_addresses = []; - for (let actr = 0; actr < 1; actr++) { - let buffer = await Bootstrap.wallet_exec(wallet_name, ['getnewaddress']); - let { out } = ps_extract(buffer, false); - if (out.length) { - await mkudb(wallet_name).set_main_address(out); - w_addresses.push(out); - } - } - await Bootstrap.store_addresses( - wallet_name, - sanitize_addresses(w_addresses), - ); - await Bootstrap.unlock_wallet(wallet_name); - } - await Bootstrap.alias_users(); - await Bootstrap.unlock_all_wallets(); -}; -Bootstrap.grind = async function () { - await Bootstrap.unlock_all_wallets(); - for (let i = 0; i < 100; i++) { - if (i % 10 === 0) { - await Bootstrap.generate_dash_to_all(); - } - await Bootstrap.create_denominations_to_all(); - } -}; -Bootstrap.create_denominations_to_all = async function (iterations = 10) { - for (let i = 0; i < iterations; i++) { - await Bootstrap.unlock_all_wallets(); - const AMOUNT = '0.00100001'; - /** - * Loop through all wallets and send the lowest denomination to - * all other users - */ - let users = await Bootstrap.user_list(); - for (const user of users) { - for (const otherUser of users) { - if (otherUser === user) { - continue; - } - let address = await mkudb(otherUser).main_address(); - let ps = await Bootstrap.wallet_exec(user, [ - 'sendtoaddress', - address, - AMOUNT, - ]); - let { out, err } = ps_extract(ps); - if (err) { - console.error(`ERROR: ${err}`); - } else { - console.log(out); - } - } - } - } -}; -const LOW_COLLATERAL_AMOUNT = 0.0007; -Bootstrap.collateral_amount = { - amount: LOW_COLLATERAL_AMOUNT, - satoshis: parseInt(LOW_COLLATERAL_AMOUNT * COIN, 10), -}; -Bootstrap.create_collaterals_to_all = async function (iterations = 1) { - for (let i = 0; i < iterations; i++) { - await Bootstrap.unlock_all_wallets(); - const AMOUNT = String(Bootstrap.collateral_amount.amount); - /** - * Loop through all wallets and send the lowest denomination to - * all other users - */ - let users = await Bootstrap.user_list(); - for (const user of users) { - for (const otherUser of users) { - if (otherUser === user) { - continue; - } - let address = await mkudb(otherUser).main_address(); - let ps = await Bootstrap.wallet_exec(user, [ - 'sendtoaddress', - address, - AMOUNT, - ]); - let { out, err } = ps_extract(ps); - if (out.length) { - let txid = sanitize_txid(out); - console.log({ - sent_to: address, - from: user, - to: otherUser, - txid, - }); - } - if (err.length) { - console.error({ address, from: user, to: otherUser, err }); - } - } - } - } -}; -Bootstrap.unlock_wallet = async function (username) { - username = Bootstrap.alias_check(username); - return await Bootstrap.run( - cli_args([ - `-rpcwallet=${username}`, - 'walletpassphrase', - 'foobar', - '100000000', - ]), - ); -}; - -Bootstrap.unlock_all_wallets = async function (options = {}) { - let errors = []; - let keep = []; - let silent = true; - if (typeof options.verbose !== 'undefined' && options.verbose === true) { - silent = false; - } - const users = await Bootstrap.user_list(); - for (const user of users) { - if (!silent) { - process.stdout.write(`[ ] unlocking "${user}"...`); - } - let ps = await Bootstrap.unlock_wallet(user); - let err = ps.stderr.toString(); - err = err.replace(/[\s]+$/i, ''); - if (err.length) { - if (!silent) { - console.log('[x] ERROR'); - } - errors.push(user); - } else { - if (!silent) { - console.log('[+] unlocked'); - } - keep.push(user); - } - } - if (!silent) { - console.info('The following wallets should probably be cleaned up:'); - console.info(JSON.stringify(errors, null, 2)); - } - return { - bad_wallets: errors, - good_wallets: keep, - }; -}; - -function trim(s) { - let f = s.replace(/[\s]+$/, ''); - f.replace(/^[\s]+/, ''); - return f; -} -function line() { - console.log(''); - for (let i = 0; i < 80; i++) { - process.stdout.write('-'); - } - console.log(''); -} - -Bootstrap.dump_private_key = async function (username, address) { - username = Bootstrap.alias_check(username); - let ps = await Bootstrap.wallet_exec(username, ['dumpprivkey', address]); - let err = trim(ps.stderr.toString()); - let out = trim(ps.stdout.toString()); - if (out.length) { - return out; - } - throw new Error(`dumpprivkey failed: "${err}"`); -}; - -Bootstrap.generate_dash_to_all = async function (iterations = 1) { - for (let i = 0; i < iterations; i++) { - const users = await Bootstrap.user_list(); - for (const user of users) { - let address = await mkudb(user).main_address(); - let ps = await Bootstrap.wallet_exec(user, [ - 'generatetoaddress', - '2', - address, - ]); - let { err, out } = ps_extract(ps, false); - if (err.length) { - console.error(`ERROR: ${user}: "${err}"`); - } else { - console.log(out); - } - } - } -}; - -Bootstrap.generate_dash_to = async function (username) { - username = Bootstrap.alias_check(username); - let address = await mkudb(username).main_address(); - if (address === null) { - address = await Bootstrap.generate_new_addresses(username, 1); - await mkudb(username).set_main_address(address[0]); - return await Bootstrap.generate_dash_to(username); - } - let ps = await Bootstrap.wallet_exec(username, [ - 'generatetoaddress', - '10', - address, - ]); - let { err, out } = ps_extract(ps, false); - if (err.length) { - console.error(`ERROR: ${username}: "${err}"`); - } else { - console.log(out); - } -}; - -function usage() { - console.log('Usage: dashboot [options] --instance=N'); - console.log(''); - console.log('# Options'); - console.log('-------------------------------------------------------'); - console.log( - '--instance=N Uses N as the instance. If not passed, defaults to "base"', - ); - console.log('--unlock-all Unlocks all user wallets.'); - console.log('--addrfromtxid=TX [EXPERIMENTAL] '); - console.log(' i---> Requires --username=U '); - console.log('--generate-to=N Generates DASH to the user named N'); - console.log('--dash-for-all Generates DASH to EVERY user'); - console.log("--create-wallets Creates wallets, addresses, and UTXO's"); - console.log('--create-n-wallet=N Creates N wallets'); - console.log( - "--denom-amt=N Search through user's UTXO's for denominated amounts matching N", - ); - console.log( - ' denom-amt also requires that you pass in --username=U', - ); - console.log( - '--create-denoms Loops through all wallets and sends each wallet 0.00100001 DASH', - ); - console.log( - '--create-cols Loops through all wallets and creates collaterals', - ); - console.log('--list-users Lists all users'); - console.log( - '--filter-unused=User Filter unused txids and discard used txids from lmdb entries for User', - ); - console.log( - '--user-denoms=AMT Lists all users with the desired denominated amount', - ); - console.log( - '--send-to=USER/--amount=SAT When combined, sends SAT satoshis to USER', - ); - console.log('--list-addr=user Lists all addresses for a user'); - console.log("--list-utxos=user Lists all UTXO's for a user"); - console.log("--all-utxos Lists all UTXO's for ALL users"); - console.log('--new-addr=user Creates 10 new addresses for the given user'); - console.log( - '--wallet-cmd=user Gives you the ability to call dash-cli for the specified user', - ); - console.log( - '--dump-dsf Dumps the db contents for DSF example payloads that have been logged', - ); - console.log( - '--dsf-to=FILE Dumps the db contents for DSF example payloads to the specified file', - ); - console.log( - '--import-addresses=USER Import addresses from dash-cli into lmdb', - ); - console.log( - '--sync-all Will import all addresses from dash-cli into lmdb for ALL users', - ); - console.log('--alias-users Will give easy to use names for each user'); - console.log( - '--grind Calls generate dash and create denoms in a loop', - ); - console.log('--hash-byte-order=N Convert the string N into hash byte order'); - console.log('--split-utxos=USER Split one big transaction into 0.00100001'); - console.log('--make-junk-user Create a junk user'); - console.log( - '--grind-junk-user Send a ton of dash to this junk user to give the more important users confirmations', - ); - if (extractOption('helpinstances')) { - console.log(''); - console.log('# What are instances?'); - console.log('-------------------------------------------------------'); - console.log(' An instance is just a folder, but it helps in that it '); - console.log(' it will help you separate wallets on a name basis. '); - console.log(' Passing in an instance of "foobar" will create the '); - console.log(' following folder: '); - console.log(' ~/.dashjoinjs/foobar/db/ '); - console.log(' '); - console.log(' Stored in that directory will be the lmdb database '); - console.log(' which has all wallet data for that instance. This '); - console.log(' makes it trivial to use different datasets by using '); - console.log(' different instances. '); - console.log(' '); - console.log(' Keep in mind that if you end up deleting an instance '); - console.log(' directory, the wallet and all its transaction data '); - console.log(' still exists in your dashmate cluster. '); - console.log(' This is usually not a problem as the point of dashboot'); - console.log(' is to allow you to easily create lots of wallets and '); - console.log(' addresses/utxos really easily. '); - console.log(' '); - console.log('# Ideal use case '); - console.log('-------------------------------------------------------'); - console.log(' The ideal usecase is to create a completely brand new '); - console.log(' dashmate regtest cluster, then run dashboot for a few '); - console.log(' minutes. Then, point your development code at the LMDB'); - console.log(' database which has all the randomly named wallets, '); - console.log(' utxos, and addresses. '); - console.log(' '); - } -} -Bootstrap.run_cli_program = async function () { - let help = false; - let config = { - instance: 'base', - unlock: null, - generateTo: null, - create_wallets: false, - create_denoms: false, - create_collaterals: false, - dash_for_all: false, - list_users: false, - list_addr: null, - list_utxos: null, - wallet_cmd: null, - dump_dsf: false, - dsf_to_file: null, - //console.log(`--dump-dsf Dumps the db contents for DSF example payloads that have been logged`); - //console.log(`--dsf-to=FILE Dumps the db contents for DSF example payloads to the specified file`); - }; - config.create_collaterals = extractOption('create-cols'); - let dump_dsf = extractOption('dump-dsf'); - if (dump_dsf) { - config.dump_dsf = true; - help = false; - } - let iname = extractOption('instance', true); - if (iname) { - config.instance = iname; - help = false; - } - let hbo = extractOption('hash-byte-order', true); - if (hbo) { - process.stdout.write(hashByteOrder(hbo)); - process.exit(0); - } - await Bootstrap.load_instance(config.instance); - await Bootstrap.load_alias_ram_slots(); - { - let user = extractOption('split-utxos', true); - let count = extractOption('count', true); - if (user) { - count = parseInt(count, 10); - if (isNaN(count)) { - count = 1; - } - for (let i = 0; i < count; i++) { - d(await Bootstrap.split_utxo(user)); - } - process.exit(0); - } - } - { - if (extractOption('make-junk-user')) { - d(await db.make_junk_wallet()); - process.exit(0); - } - } - { - if (extractOption('grind-junk-user')) { - d(await Bootstrap.grind_junk_user()); - process.exit(0); - } - } - { - if (extractOption('grind')) { - d(await Bootstrap.grind()); - process.exit(0); - } - } - { - let send_to = extractOption('send-to', true); - let amt = extractOption('amount', true); - if (send_to && amt) { - let times = extractOption('times', true); - if (!times) { - console.log('defaulting to 20 sends. to override, use --times=N'); - times = 20; - } - d(await Bootstrap.send_satoshis_to(send_to, amt, times)); - process.exit(0); - } - } - { - let user = extractOption('new-addr', true); - if (user) { - let count = extractOption('count', true); - d(await Bootstrap.generate_address(user, count)); - process.exit(0); - } - } - { - let user = extractOption('filter-unused', true); - if (user) { - d(await Bootstrap.filter_unused_txids(user)); - process.exit(0); - } - } - let txid = null; - if ((txid = extractOption('addrfromtxid', true))) { - d( - await Bootstrap.get_address_from_txid( - extractOption('username', true), - txid, - ), - ); - process.exit(0); - } - { - if (extractOption('all-utxos')) { - let users = await Bootstrap.user_list(); - for (const user of users) { - line(); - console.log({ [user]: 'start' }); - line(); - let addresses = await Bootstrap.user_addresses(user); - let utxos = await Bootstrap.user_utxos_from_cli(user, addresses); - for (const u of utxos) { - console.log(u); - } - line(); - console.log({ [user]: utxos.length }); - line(); - } - process.exit(0); - } - } - { - if (extractOption('alias-users')) { - d(await Bootstrap.alias_users()); - process.exit(0); - } - } - { - let user = extractOption('import-addresses', true); - if (user) { - if (user === 'all') { - const users = await Bootstrap.user_list(); - for (const user of users) { - await Bootstrap.import_user_addresses_from_cli(user); - } - d('done importing all user addresses'); - process.exit(0); - } - d(await Bootstrap.import_user_addresses_from_cli(user)); - process.exit(0); - } - } - { - if (extractOption('sync-all')) { - let users = await Bootstrap.user_list(); - for (const user of users) { - await Bootstrap.import_user_addresses_from_cli(user); - } - process.exit(0); - } - } - if (extractOption('increment')) { - d(await Bootstrap.increment_key('randomuser', 'ctr')); - process.exit(0); - } - if (extractOption('dsftest1')) { - let buffer = await fs.readFileSync( - './dsf-7250bb2a2e294f728081f50ee2bdd3a1.dat', - ); - d(await Bootstrap._dsftest1(buffer, '7250bb2a2e294f728081f50ee2bdd3a1')); - process.exit(0); - } - let denom = extractOption('denom-amt', true); - if (denom) { - let username = extractOption('username', true); - if (!username) { - console.error('Error: --username must be passed with --denom-amt'); - process.exit(1); - } - let utxos = await Bootstrap.get_denominated_utxos( - username, - parseInt(denom, 10), - ); - await LogUtxos(username, utxos); - d(utxos); - process.exit(0); - } - let userDenoms = extractOption('user-denom', true); - if (userDenoms) { - d(await Bootstrap.get_users_with_denominated_utxos(userDenoms)); - process.exit(0); - } - let cmd = extractOption('wallet-cmd', true); - if (cmd) { - let capture = false; - let args = []; - for (const arg of process.argv) { - if (arg.match(/^--wallet-cmd.*$/)) { - capture = true; - continue; - } - if (capture) { - args.push(arg); - } - } - cmd = Bootstrap.alias_check(cmd); - let ps = await Bootstrap.wallet_exec(cmd, args); - let { out, err } = ps_extract(ps); - if (out.length) { - console.log(out); - } - if (err.length) { - console.error(err); - } - process.exit(0); - } - if (extractOption('list-users')) { - config.list_users = true; - help = false; - } - let uAddr = extractOption('list-addr', true); - if (uAddr) { - config.list_addr = uAddr; - help = false; - } - //console.log(`--list-utxos=user Lists all UTXO's for a user`); - let utxos = extractOption('list-utxos', true); - if (utxos) { - config.list_utxos = utxos; - help = false; - } - - if (extractOption('help') || extractOption('h')) { - help = true; - } - if (help) { - usage(); - process.exit(1); - } - if (extractOption('create-denoms')) { - help = false; - config.create_denoms = true; - } - if (extractOption('dash-for-all')) { - config.dash_for_all = true; - help = false; - } - if (extractOption('unlock-all')) { - config.unlock = 'all'; - console.debug('all'); - help = false; - } - let genTo = extractOption('generate-to', true); - if (genTo) { - config.generateTo = genTo; - help = false; - } - let cwall = extractOption('create-wallets'); - if (cwall) { - config.create_wallets = true; - } - if (extractOption('create-wallets')) { - config.create_wallets = true; - help = false; - } - - if (config.unlock === 'all') { - console.info('[status]: Unlocking...'); - d(await Bootstrap.unlock_all_wallets()); - console.log('[DONE]'); - process.exit(0); - return; - } - if (config.generateTo !== null) { - console.info( - '[status]: Generating dash to user:', - config.generateTo, - '...', - ); - await Bootstrap.unlock_all_wallets(); - d(await Bootstrap.generate_dash_to(config.generateTo)); - console.log('[DONE]'); - process.exit(0); - return; - } - let countArg = extractOption('create-n-wallets', true); - if (countArg !== null) { - let count = parseInt(String(countArg), 10); - if (isNaN(count)) { - d('usage: --create-n-wallets=N where N is a number'); - process.exit(1); - } - await Bootstrap.create_wallets(count); - d('created wallets'); - process.exit(0); - } - if (config.create_wallets ?? false) { - d(await Bootstrap.create_wallets()); - process.exit(0); - } - if (config.dash_for_all) { - await Bootstrap.unlock_all_wallets(); - d(await Bootstrap.generate_dash_to_all(100)); - process.exit(0); - } - if (config.create_collaterals) { - d(await Bootstrap.create_collaterals_to_all()); - process.exit(0); - } - if (config.create_denoms) { - d(await Bootstrap.create_denominations_to_all()); - process.exit(0); - } - if (config.list_users) { - d(await Bootstrap.user_list({ with: 'alias' })); - process.exit(0); - } - if (config.list_addr) { - config.list_addr = Bootstrap.alias_check(config.list_addr); - d({ conf_user: config.list_addr }); - d({ 'main-address': await mkudb(config.list_addr).main_address() }); - let address = await Bootstrap.user_addresses(config.list_addr); - d(address); - process.exit(0); - } - if (config.list_utxos) { - config.list_utxos = Bootstrap.alias_check(config.list_utxos); - d(await Bootstrap.get_denominated_utxos(config.list_utxos, null, 5000)); - process.exit(0); - } - usage(); - process.exit(1); -}; -Bootstrap.is_txid_used = function (username, txid) { - if (txid === null || typeof txid === 'undefined') { - txid = username; - } - return Bootstrap.ram_txid_used(txid); -}; -Bootstrap.used_txids = []; -Bootstrap.load_used_txid_ram_slots = async function () { - //console.info('[ ] Loading used txids into ram....'); - Bootstrap.used_txids = []; - let map = {}; - for (const user of await Bootstrap.user_list()) { - let used = await Bootstrap.get_used_txids(user); - for (const u of used) { - if (u === null) { - continue; - } - if (Array.isArray(u)) { - for (const u2 of u) { - if (u2 === null) { - continue; - } - if (map[u2] !== 1) { - Bootstrap.used_txids.push(u2); - map[u2] = 1; - } - } - } else { - if (map[u] !== 1) { - Bootstrap.used_txids.push(u); - map[u] = 1; - } - } - } - } - //console.info(`[+] ${Bootstrap.used_txids.length} used txids loaded into ram`); -}; -Bootstrap.user_aliases = {}; -Bootstrap.load_alias_ram_slots = async function () { - //console.info('[ ] Loading user aliases into ram....'); - Bootstrap.user_aliases = {}; - for (const user of await Bootstrap.user_list({ with: 'alias' })) { - Bootstrap.user_aliases[user.alias] = user.user; - } - //console.info(Bootstrap.user_aliases); - //console.info( - // `[+] ${ - // Object.keys(Bootstrap.user_aliases).length - // } user aliases loaded into ram` - //); -}; -Bootstrap.mark_txid_used = async function (username, txid) { - username = Bootstrap.alias_check(username); - let existing = await Bootstrap.get_used_txids(username); - existing.push(txid); - existing = unique(existing); - Bootstrap.used_txids.push(txid); - return await mkudb(username).append_array('usedtxids', existing); -}; -Bootstrap.get_used_txids = async function (username) { - username = Bootstrap.alias_check(username); - return await mkudb(username).get_array('usedtxids'); -}; -Bootstrap.ram_txid_used = function (txid) { - return Bootstrap.used_txids.indexOf(txid) !== -1; -}; - -Bootstrap.extract_unique_users = async function ( - count, - denominatedAmount, - besides, -) { - await Bootstrap.unlock_all_wallets(); - await Bootstrap.load_used_txid_ram_slots(); - let users = await Bootstrap.user_list(); - let choices = []; - denominatedAmount = parseInt(denominatedAmount, 10); - - users = users.filter(function (user) { - return besides.indexOf(user) === -1; - }); - - for (const user of users) { - d({ user }); - if (count === choices.length - 1) { - return choices; - } - let rando = await Bootstrap.getRandomPayee(user); - choices.push({ - user, - utxos: await Bootstrap.get_denominated_utxos(user, denominatedAmount), - changeAddress: await Bootstrap.get_change_address_from_cli(user), - randomPayee: rando, - }); - } - d({ choices }); - return choices; -}; -Bootstrap.getRandomPayee = async function (username) { - username = Bootstrap.alias_check(username); - let users = await Bootstrap.user_list(); - for (const user of users) { - if (user !== username && Math.random() * 100 > 50) { - return user; - } - } - return await Bootstrap.getRandomPayee(username); -}; -Bootstrap.helpers = function () { - return { - db_cj, - db_cj_ns, - db_put, - db_get, - db_append, - rng: { - random_name: Bootstrap.random_name, - }, - shell: { - ps_extract: ps_extract, - mkpath: Bootstrap.mkpath, - run: Bootstrap.run, - cli_args, - wallet_exec: Bootstrap.wallet_exec, - }, - validation: { - sanitize_address, - sanitize_txid, - }, - users: { - get_list: Bootstrap.user_list, - user_create: Bootstrap.user_create, - }, - conversion: { - arbuf_to_hexstr, - bigint_safe_json_stringify, - }, - debug: { - dd, - d, - }, - }; -}; -Bootstrap.utils = Bootstrap.helpers(); diff --git a/src/bootstrap/metadb.js b/src/bootstrap/metadb.js deleted file mode 100644 index 80e00bd..0000000 --- a/src/bootstrap/metadb.js +++ /dev/null @@ -1,179 +0,0 @@ -'use strict'; - -//const cproc = require('child_process'); -//const crypto = require('crypto'); -//const fs = require('fs'); -//const { xt } = require('@mentoc/xtract'); -//const ArrayUtils = require('../array-utils.js'); - -function d(f) { - console.debug(f); -} -//function dd(f) { -// console.debug(f); -// process.exit(); -//} - -module.exports = function (_DB) { - let Lib = { DB: _DB, debug: false }; - Lib.before_tx = function () {}; - Lib.set_debug = function (on_or_off) { - Lib.debug = on_or_off; - }; - Lib.get_debug = function () { - return Lib.debug; - }; - Lib.set_namespaces = function (n) { - Lib.DB.set_namespaces(n); - }; - Lib.db_cj = function db_cj() { - Lib.DB.set_namespaces(['coinjoin']); - }; - Lib.db_cj_ns = function db_cj_ns(list) { - Lib.DB.set_namespaces(['coinjoin', ...list]); - }; - Lib.db_put = async function db_put(key, val) { - if (Lib.debug) { - d({ db_put: { key, val } }); - } - await Lib.DB.ns.put(key, val); - }; - Lib.db_del = async function db_del(key) { - if (Lib.debug) { - d({ db_del: key }); - } - return await Lib.DB.ns.del(key); - }; - Lib.db_get = async function db_get(key) { - if (Lib.debug) { - d({ db_get: key }); - } - return await Lib.DB.ns.get(key); - }; - Lib.db_append = function db_append(key, val) { - let ex = Lib.DB.ns.get(key); - Lib.DB.ns.put(key, ex + val); - }; - - Lib.meta_get = async function (username, key) { - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - try { - let t = await Lib.db_get(key); - t = JSON.parse(t); - if (!Array.isArray(t)) { - return []; - } - return t; - } catch (e) { - return []; - } - }; - Lib.meta_set = async function (username, key, values) { - if (Lib.debug) { - if (Array.isArray(values)) { - d('meta_set entry', { - username, - key, - item_count: values.length, - }); - } else { - d('meta_set entry', { username, key, item_count: 'not-array' }); - } - } - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - if (!Array.isArray(values)) { - values = [values]; - } - await Lib.db_put(key, JSON.stringify(values)); - }; - Lib.meta_store = async function (username, key, values) { - if (Lib.debug) { - if (Array.isArray(values)) { - d('meta_store entry', { - username, - key, - item_count: values.length, - }); - } else { - d('meta_store entry', { - username, - key, - item_count: 'not-array', - }); - } - } - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - let existing = await Lib.meta_get(username, key); - if (!Array.isArray(existing)) { - existing = []; - } - if (Array.isArray(values)) { - for (const r of values) { - existing.push(r); - } - } else { - existing.push(values); - } - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - await Lib.db_put(key, JSON.stringify(existing)); - }; - Lib.meta_remove = async function (username, key, values) { - if (Lib.debug) { - if (Array.isArray(values)) { - d('meta_remove entry', { - username, - key, - item_count: values.length, - }); - } else { - d('meta_remove entry', { - username, - key, - item_count: 'not-array', - }); - } - } - - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - let existing = await Lib.meta_get(username, key); - if (!Array.isArray(existing)) { - existing = []; - } - if (Array.isArray(values)) { - existing = existing.filter(function (val) { - return values.indexOf(val) === -1; - }); - } else { - existing = existing.filter(function (val) { - return val !== values; - }); - } - if (Array.isArray(username)) { - Lib.db_cj_ns(username); - } else { - Lib.db_cj_ns([username]); - } - await Lib.db_put(key, JSON.stringify(existing)); - }; - return Lib; -}; diff --git a/src/bootstrap/user-details.js b/src/bootstrap/user-details.js deleted file mode 100755 index 895feac..0000000 --- a/src/bootstrap/user-details.js +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node -'use strict'; -var Lib = {}; -module.exports = Lib; -const dboot = require('./index.js'); -const { d, dd } = require('../debug.js'); - -Lib.extractUserDetails = async function (username) { - let addresses = await dboot.get_addresses(username, null); - let utxos = await dboot - .user_utxos_from_cli(username, addresses) - .catch(function (error) { - console.error({ error }); - return null; - }); - if (!utxos || utxos.length === 0) { - throw new Error("User doesn't have any UTXOS!"); - } - //dd({ utxos }); - let addrMap = {}; - for (const u of utxos) { - addrMap[u.address] = 1; - } - for (const addr in addrMap) { - let buffer = await dboot.wallet_exec(username, ['dumpprivkey', addr]); - let { out, err } = dboot.ps_extract(buffer, false); - if (err.length) { - console.error(err); - } - if (out.length) { - addrMap[addr] = out; - } - } - let flatUtxos = []; - for (let u of utxos) { - let used = dboot.is_txid_used(username, u.txid); - if (used) { - continue; - } - u.privateKey = addrMap[u.address]; - flatUtxos.push(u); - } - d({ flatUtxos: flatUtxos.length }); - //let rando = await dboot.getRandomPayee(username); - let data = { - user: username, - utxos: flatUtxos, - changeAddress: await dboot.get_change_address_from_cli(username), - }; - //d({ data }); - return data; -}; diff --git a/src/bootstrap/userdb.js b/src/bootstrap/userdb.js deleted file mode 100644 index d6c0029..0000000 --- a/src/bootstrap/userdb.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - -function UserDB(globs, u = null, ns = 'cj') { - let db_put = globs.db_put; - let db_get = globs.db_get; - let db_del = globs.db_del; - let db_set_ns = globs.db_set_ns; - let db_make_key = globs.db_make_key; - let self = this; - self.user = u; - self.ns = ns; - self.set_ns = function (s) { - self.ns = s; - }; - self.build_ns = function () { - let params = []; - if (self.ns) { - params.push(self.ns); - } - params.push(self.user); - params.push(''); - db_set_ns(params); - }; - self.set_username = function (u) { - self.user = u; - }; - self.get_username = function () { - return self.user; - }; - self.u = self.set_username; - - self.make_key = function (key) { - return db_make_key(key); - }; - self.get = async function (key) { - self.build_ns(); - return await db_get(key); - }; - self.put = async function (key, val) { - self.build_ns(); - return await db_put(key, String(val)); - }; - self.del = async function (key) { - self.build_ns(); - return await db_del(key); - }; - self.main_address = async function () { - return await self.get('main-address'); - }; - self.set_main_address = async function (mad) { - return await self.put('main-address', mad); - }; - self.change_address = async function () { - return await self.get('change-address'); - }; - self.set_change_address = async function (ch) { - return await self.put('change-address', ch); - }; - self.set_array = async function (key, values) { - await self.put(`${key}|n`, values.length); - let ctr = 0; - for (const val of values) { - await self.put(`${key}|${ctr}`, val); - ++ctr; - } - return ctr; - }; - self.remove_array = async function (key) { - let items = await self.get(`${key}|n`); - await self.del(`${key}|n`); - let ctr = 0; - for (let i = 0; i < items; i++) { - await self.del(`${key}|${ctr}`); - } - return ctr; - }; - self.get_array = async function (key) { - let items = await self.get(`${key}|n`); - let ctr = 0; - let res = []; - for (let i = 0; i < items; i++) { - res.push(await self.get(`${key}|${ctr}`)); - ++ctr; - } - return res; - }; - self.page_array = async function (key, cb) { - let items = await self.get(`${key}|n`); - for (let i = 0; i < items; i++) { - let val = await self.get(`${key}|${i}`); - let result = await cb(val); - if (result === false) { - return; - } - } - }; - self.append_array = async function (key, values) { - let items = await self.get(`${key}|n`); - if (items === null || items === 0) { - return await self.set_array(key, values); - } - let existing = []; - for (let i = 0; i < items; i++) { - existing.push(await self.get(`${key}|${i}`)); - } - for (const v of values) { - existing.push(v); - await self.put(`${key}|${items}`, v); - ++items; - } - await self.put(`${key}|n`, existing.length); - return existing.length; - }; - self.list_array = async function (key) { - let item_count = await self.get(`${key}|n`); - let good = []; - for (let i = 0; i < item_count; i++) { - if (await self.get(`${key}|${i}`)) { - good.push(`${key}|${i}`); - } - } - return good; - }; -} -module.exports = UserDB; diff --git a/src/browser.js b/src/browser.js deleted file mode 100755 index 35db8c7..0000000 --- a/src/browser.js +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const COIN = require('./coin-join-constants.js').COIN; -const Network = require('./network.js'); - -let Lib = {}; -module.exports = Lib; - -let config = require('./.config.json'); -let masterNodeIP = config.masterNodeIP; -let masterNodePort = config.masterNodePort; -let network = config.network; -let ourIP = config.ourIP; -let startBlockHeight = config.startBlockHeight; -const TxnConstants = require('./transaction-constants.js'); -const NetUtil = require('./network-util.js'); -const hexToBytes = NetUtil.hexToBytes; - -let DashCore = require('@dashevo/dashcore-lib'); -let Transaction = DashCore.Transaction; -let Script = DashCore.Script; -let PrivateKey = DashCore.PrivateKey; -let Address = DashCore.Address; -const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const HI_COLLATERAL = LOW_COLLATERAL * 4; -const fs = require('fs'); - -async function read_file(fname) { - return await fs - .readFileSync(fname) - .toString() - .replace(/^\s+/, '') - .replace(/\s+$/, ''); -} -async function logUsedTransaction(fileName, txnId) { - let buffer = await fs.readFileSync(fileName); - buffer = buffer.toString(); - let data = JSON.parse(buffer); - data.list.push(txnId); - await fs.writeFileSync(fileName, JSON.stringify(data, null, 2)); -} -async function isUsed(fileName, txnId) { - let buffer = await fs.readFileSync(fileName); - buffer = buffer.toString(); - let data = JSON.parse(buffer); - return data.list.indexOf(txnId) !== -1; -} -const NETWORK = 'regtest'; -let PsendUsedTxnFile = './data/w-psend-used-txn.json'; -let PsendTxnList = require('./data/w-psend-txn.json'); -let PsendChangeAddress = await read_file('./data/w-psend-change-address-0'); -let sourceAddress = await read_file('./data/w-psend-address-0'); -let payeeAddress = await read_file('./data/w-foobar-address-0'); -let privkeySet = PrivateKey( - PrivateKey.fromWIF(await read_file('./data/w-psend-privkey-0'), NETWORK), -); - -/** - * Parameters: - * 1) An unused txn input - - txid - - vout - - satoshis - * 2) WIF associated with txn input address - * 3) The network (regtest,testnet,livenet) - * 4) A random destination address - * - not incredibly important - * - but needs to be there from what i can tell - * 5) denominations - * 6) - */ - -module.exports = { - Session, -}; - -let BrowserLib = {}; - -BrowserLib.Session = function ( - args = { - txid, - vout, - satoshis, - wif, - network, - payeeAddress, - sourceAddress, - denomination, - }, -) { - let self = this; - self.sourceAddress = args.sourceAddress; - self.txid = args.txid; - self.vout = args.vout; - self.satoshis = parseInt(args.satoshis, 10); - self.wif = args.wif; - self.network = args.network; - self.payeeAddress = args.payeeAddress; - self.denomination = args.denomination; - - self.makeCollateralTx = function () { - let fee = 50000; - let amount = parseInt(LOW_COLLATERAL * 2, 10); - let unspent = self.satoshis - amount; - let sourceAddress = Address(self.sourceAddress, self.network); - let utxos = { - txId: self.txid, - outputIndex: self.vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis: self.satoshis, - }; - var tx = new Transaction() - .from(utxos) - .to(payeeAddress, amount) - .to(PsendChangeAddress, unspent - fee) - .sign(privkeySet); - - return hexToBytes(tx.uncheckedSerialize()); - }; - - /** - * Connect to the backend server and send in the collateral transaction - * bytes created by makeCollateralTx(). This should ask the server - * to put you into a queue pending coinjoin session arrival. - * - * Once the server has a spot for you in a master node, it will call - * the cb parameter. - */ - self.matchmake = async function (cb) {}; -}; - -async function getUnusedTxn() { - for (let txn of PsendTxnList) { - /** - * Pull from PsendTxnList where: - * 1) category is 'generate'. - * 2) has more than zero confirmations - * 3) where address matches dp-address-0 - * 4) txid does NOT exist in ./data/w-psend-used-txn.json - */ - if (txn.category !== 'generate') { - continue; - } - if (txn.confirmations === 0) { - continue; - } - if (txn.address !== sourceAddress) { - continue; - } - if (await isUsed(PsendUsedTxnFile, txn.txid)) { - continue; - } - return txn; - break; - } - return null; -} -(async function () { - let PsendTx = await getUnusedTxn(); - - if (PsendTx === null) { - throw new Error('Couldnt find unused transaction'); - } - - let amount = LOW_COLLATERAL * 2; - async function makeCollateralTx(prefs) { - let origAmount = PsendTx.amount * COIN; - let times = 1; - - let fee = 50000; - amount = parseInt(amount, 10); - let unspent = origAmount - amount; - let sourceAddress = Address(PsendTx.address, NETWORK); - let utxos = { - txId: PsendTx.txid, - outputIndex: PsendTx.vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis: origAmount, - }; - var tx = new Transaction() - .from(utxos) - .to(payeeAddress, amount) - .to(PsendChangeAddress, unspent - fee) - .sign(privkeySet); - - if (typeof prefs.logUsed !== 'undefined' && prefs.logUsed) { - await logUsedTransaction(PsendUsedTxnFile, utxos.txId); - } - return hexToBytes(tx.uncheckedSerialize()); - } - - function exit() { - process.exit(0); - } - async function sendCoins(prefs) { - let origAmount = PsendTx.amount * COIN; - let times = 1; - - let fee = 1000; - amount = parseInt(amount, 10); - let unspent = origAmount - amount; - unspent -= fee; - console.debug({ fee, PsendTx, unspent, amount, origAmount }); - let sourceAddress = Address(PsendTx.address, NETWORK); - /** - * Take the input of the PrevTx, send it to another wallet - * which will hold the new funds and the change will go to - * a change address. - */ - let utxos = { - txId: PsendTx.txid, - outputIndex: PsendTx.vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis: origAmount, - }; - var tx = new Transaction() - .from(utxos) // Feed information about what unspent outputs one can use - .to(payeeAddress, amount) // Add an output with the given amount of satoshis - .to(PsendChangeAddress, unspent) - .change(PsendChangeAddress) // Sets up a change address where the rest of the funds will go - .fee(fee) - .sign(privkeySet); // Signs all the inputs it can - - console.debug({ tx, cereal: tx.serialize() }); - console.debug(tx.inputs); - console.debug(tx.outputs); - console.debug(tx._changeScript); - - console.debug(tx.serialize()); - if (typeof prefs.logUsed !== 'undefined' && prefs.logUsed) { - await logUsedTransaction(PsendUsedTxnFile, utxos.txId); - } - } - let logUsed = false; - if (process.argv.includes('--log-used')) { - logUsed = true; - } - if (process.argv.includes('--send-coin')) { - sendCoins({ - logUsed, - }).then(function () { - process.exit(0); - }); - } - - let dsaSent = false; - - function stateChanged(obj) { - let masterNode = obj.self; - switch (masterNode.status) { - default: - console.info('unhandled status:', masterNode.status); - break; - case 'CLOSED': - console.warn('[-] Connection closed'); - break; - case 'NEEDS_AUTH': - case 'EXPECT_VERACK': - case 'EXPECT_HCDP': - case 'RESPOND_VERACK': - console.info('[ ... ] Handshake in progress'); - break; - case 'READY': - console.log('[+] Ready to start dealing with CoinJoin traffic...'); - masterNode.switchHandlerTo('coinjoin'); - if (dsaSent === false) { - setTimeout(async () => { - masterNode.client.write( - Network.packet.coinjoin.dsa({ - chosen_network: network, - denomination: COIN / 1000 + 1, - collateral: await makeCollateralTx({ logUsed }), - }), - ); - dsaSent = true; - }, 2000); - } - break; - case 'EXPECT_DSQ': - console.info('[+] dsa sent'); - break; - } - } - - let MasterNodeConnection = - require('./masternode-connection.js').MasterNodeConnection; - let masterNodeConnection = new MasterNodeConnection({ - ip: masterNodeIP, - port: masterNodePort, - network, - ourIP, - startBlockHeight, - onStatusChange: stateChanged, - debugFunction: console.debug, - userAgent: config.userAgent ?? null, - }); - - masterNodeConnection.connect(); -})(); diff --git a/src/cdata-stream.js b/src/cdata-stream.js deleted file mode 100644 index 01d403d..0000000 --- a/src/cdata-stream.js +++ /dev/null @@ -1,78 +0,0 @@ -/** Double ended buffer combining vector and stream-like interfaces. - * - * >> and << read and write unformatted data using the above serialization templates. - * Fills with data in linear time; some stringstream implementations take N^2 time. - */ -let Lib = {}; -const ONLY_READY_TO_MIX = ''; -const { MAX_MONEY } = require('./coin.js'); -const CoinType = require('./cointype-constants.js'); - -const Vector = require('./vector.js'); -module.exports = Lib; - -function CDataStream(args = {}) { - let self = this; - this.vch = new Uint8Array(); - this.nReadPos = 0; - this.nType = 0; - this.nVersion = 0; - - this.Init = function (nTypeIn, nVersionIn) { - self.nReadPos = 0; - self.nType = nTypeIn; - self.nVersion = nVersionIn; - }; - - this.read = function (pch, nSize) { - if (nSize == 0) { - return; - } - - // Read from the beginning of the buffer - let nReadPosNext = self.nReadPos + nSize; - if (nReadPosNext > self.vch.size()) { - throw new Error('CDataStream::read(): end of data'); - } - //orig: memcpy(pch, &vch[nReadPos], nSize); - for (let i = 0; i < nSize; i++) { - self.vch[nReadPos + i] = pch[i]; - } - if (nReadPosNext == self.vch.size()) { - self.nReadPos = 0; - self.vch = new Uint8Array(); - return; - } - self.nReadPos = nReadPosNext; - }; - - this.write = function (pch, nSize) { - // Write to the end of the buffer - for (let i = 0; i < nSize; i++) { - self.vch[self.vch.length + i] = pch[i]; - } - }; - - /** - * XOR the contents of this stream with a certain key. - * - * @param[in] key The key used to XOR the data in this stream. - */ - this.Xor = function (key) { - if (key.size() == 0) { - return; - } - - for (let i = 0, j = 0; i != self.size(); i++) { - self.vch[i] ^= key[j++]; - - // This potentially acts on very many bytes of data, so it's - // important that we calculate `j`, i.e. the `key` index in this - // way instead of doing a %, which would effectively be a division - // for each byte Xor'd -- much slower than need be. - if (j == key.size()) { - j = 0; - } - } - }; -} diff --git a/src/choose-inputs.js b/src/choose-inputs.js deleted file mode 100755 index 851c5c0..0000000 --- a/src/choose-inputs.js +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const DebugLib = require('./debug.js'); -let dboot = null; -let localState = { - dboot: null, - denom: null, -}; -function setDboot(db) { - localState.dboot = db; - dboot = localState.dboot; -} -function setDenom(d) { - localState.denom = parseInt(d, 10); - if (isNaN(localState.denom)) { - throw new Error('denomination invalid'); - } -} -function getDemoDenomination() { - return parseInt(localState.denom, 10); -} -async function getPrivateKey(username, address) { - let privateKey = await dboot - .get_private_key(username, address) - .catch(function (error) { - console.error(error); - return null; - }); - if (privateKey === null) { - throw new Error('private key could not be loaded'); - } - return privateKey; -} -async function getUserInputs(client_session, denominatedAmount, count) { - //username, denominatedAmount, count, txids) { - count = parseInt(count, 10); - if (count <= 0 || isNaN(count)) { - throw new Error('count must be a valid positive integer'); - } - denominatedAmount = parseInt(denominatedAmount, 10); - if (denominatedAmount <= 0 || isNaN(denominatedAmount)) { - throw new Error('denominatedAmount must be a valid positive integer'); - } - let utxos = await dboot.get_denominated_utxos( - client_session.username, - denominatedAmount, - ); - let selected = []; - let txids = {}; - let i = 0; - //let utxos = client_session.mainUser.utxos; - while (selected.length < count) { - if (typeof txids[utxos[i].txid] !== 'undefined') { - ++i; - continue; - } - txids[utxos[i].txid] = 1; - selected.push(utxos[i]); - await dboot.mark_txid_used(client_session.username, utxos[i].txid); - ++i; - } - client_session.selected_user_inputs = selected; - return selected; -} - -let LibInput = {}; - -module.exports = LibInput; - -LibInput.getUserInputs = getUserInputs; -LibInput.setDboot = setDboot; -LibInput.getPrivateKey = getPrivateKey; -LibInput.getDemoDenomination = getDemoDenomination; -LibInput.setDenom = setDenom; -LibInput.initialize = async function (obj) { - DebugLib.setNickname(obj.nickName); - setDboot(obj.dboot); - setDenom(obj.denominatedAmount); -}; diff --git a/src/cli-sign.js b/src/cli-sign.js deleted file mode 100755 index df9a6ec..0000000 --- a/src/cli-sign.js +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const NetUtil = require('./network-util.js'); -const { COIN } = require('./coin-join-constants.js'); -const { hashByteOrder } = NetUtil; -const { hexToBytes } = NetUtil; -const bytesToString = NetUtil.bytesToString; -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -const ArrayUtils = require('./array-utils.js'); -const Signature = DashCore.crypto.Signature; -const Bootstrap = require('./bootstrap/index.js'); -const fs = require('fs'); -const { ClientSession } = require('./client-session.js'); -const { d } = require('./debug.js'); -const { dd } = require('./debug.js'); -const { ps_extract } = ArrayUtils; -const { xt } = require('@mentoc/xtract'); -const { - sanitize_vout, - sanitize_txid, - sanitize_private_key, - sanitize_address, - sanitize_username, - sanitize_tx_format, -} = require('./sanitizers.js'); - -function build_pk_sig(pk) { - let s = '['; - let ctr = 1; - for (const p of pk) { - s += `"${sanitize_private_key(p)}"`; - if (++ctr >= pk.length) { - break; - } - s += ','; - } - s += ']'; - return s; -} -async function signTransaction(dboot, client_session, txid) { - await dboot.unlock_all_wallets(); - let username = client_session.username; - username = sanitize_username(username); - let txns = await dboot.list_unspent(username); - let choice = null; - for (const input of txns) { - if (input.txid === txid) { - choice = input; - break; - } - } - if (choice === null) { - throw new Error('couldnt find denominated input'); - } - let utxos = { - txId: sanitize_txid(choice.txid), - outputIndex: sanitize_vout(choice.vout), - address: sanitize_address(choice.address), - sequenceNumber: 0xffffffff, - satoshis: parseInt(choice.amount * COIN, 10), - scriptPubKey: Script.buildPublicKeyHashOut( - sanitize_address(choice.address), - ), - }; - let pk = await dboot.get_private_key(username, choice.address); - let payeeAddress = await dboot.generate_new_addresses( - client_session.username, - 1, - ); - payeeAddress = payeeAddress[0]; - //for (let input of client_session.mixing_inputs) { - // let transaction = await dboot.get_transaction(username, utxos.txId); - // input.gettransaction = transaction; - // pk.push( - // await dboot.get_private_key( - // username, - // xt(transaction, 'details.0.address') - // ) - // ); - // d(input.txid, 'gettransaction:', input.gettransaction, pk); - //} - - let fee = 200; - let tx = new Transaction() - .from(utxos) - .fee(fee) - .to(payeeAddress, utxos.satoshis - fee) - .sign(pk, Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY); - let signature = tx.inputs[0]._script.toHex(); - let encodedScript = hexToBytes(signature); - let len = encodedScript.length; - client_session.cli_sign = { - _script: tx.inputs[0]._script, - _hex: tx.inputs[0]._script.toHex(), - utxos, - choice, - pk, - tx, - }; - await client_session.write('clisign'); - return [len, ...encodedScript]; -} - -let Lib = {}; -Lib.signTransaction = signTransaction; -module.exports = Lib; diff --git a/src/client-manager.js b/src/client-manager.js deleted file mode 100644 index 9ee4911..0000000 --- a/src/client-manager.js +++ /dev/null @@ -1,475 +0,0 @@ -/** - * A port of DASH core's CCoinJoinClientManager - */ - -const HardDrive = require('hd.js'); // TODO: .GetDataDir())) { -const Set = require('./set.js'); -const NetMsgType = require('net-msg.js').NetMsgType; -const MasterNodeSync = require('master-node-sync.js'); -const Vector = require('./vector.js'); -const WalletImpl = require('wallet.js'); -const PoolState = require('./pool-state.js'); -const POOL_STATE_QUEUE = PoolState.POOL_STATE_QUEUE; -const CoinJoinConstants = require('./coin-join-contants.js'); -const COINJOIN_AUTO_TIMEOUT_MIN = CoinJoinConstants.COINJOIN_AUTO_TIMEOUT_MIN; -const COINJOIN_AUTO_TIMEOUT_MAX = CoinJoinConstants.COINJOIN_AUTO_TIMEOUT_MAX; -const GetRandInt = require('./random.js').GetRandInt; -const COutPoint = require('./outpoint.js'); -const CCoinJoinClientSession = require('./client-session.js'); - -let Lib = { core_name: 'CCoinJoinClientManager' }; -module.exports = Lib; -// std::map> coinJoinClientManagers; -Lib.NetMsgType = NetMsgType; -Lib._shutdownRequested = false; -Lib.ShutdownRequested = function () { - return Lib._shutdownRequested; -}; - -//std::vector vecMasternodesUsed; -Lib.vecMasternodesUsed = new Vector(new COutPoint()); -// Keep track of the used Masternodes -//orig: const std::unique_ptr& m_mn_sync; -Lib.m_mn_sync = new MasterNodeSync(); - -Lib.CCoinJoinClientOptions = require('./options.js'); - -//orig: std::deque deqSessions GUARDED_BY(cs_deqsessions); -Lib.deqSessions = new Vector(new CCoinJoinClientSession()); -// std::atomic fMixing{false}; -Lib.fMixing = false; -Lib.IsMixing = function () { - return Lib.fMixing; -}; -// orig: int nCachedLastSuccessBlock{0}; -Lib.nCachedLastSuccessBlock = 0; -// how many blocks to wait for after one successful mixing tx in non-multisession mode -//orig: int nMinBlocksToWait{1}; // how many blocks to wait for after one successful mixing tx in non-multisession mode -Lib.nMinBlocksToWait = 1; -// orig: bilingual_str strAutoDenomResult; -Lib.strAutoDenomResult = ''; -//orig: CWallet& mixingWallet; -Lib.mixingWallet = new WalletImpl(); - -// Keep track of current block height -//orig: int nCachedBlockHeight{0}; -Lib.nCachedBlockHeight = 0; -// orig: bool WaitForAnotherBlock() const; -Lib.WaitForAnotherBlock = function () { - /** Returns true/false */ - if (!Lib.m_mn_sync.IsBlockchainSynced()) { - return true; - } - - if (Lib.CCoinJoinClientOptions.IsMultiSessionEnabled()) { - return false; - } - - return ( - Lib.nCachedBlockHeight - Lib.nCachedLastSuccessBlock < Lib.nMinBlocksToWait - ); -}; - -Lib.StartMixing = function () { - return (Lib.fMixing = true); -}; - -Lib.StopMixing = function () { - Lib.fMixing = false; -}; - -Lib.IsMixing = function () { - return Lib.fMixing; -}; -Lib.ResetPool = function () { - Lib.nCachedLastSuccessBlock = 0; - Lib.vecMasternodesUsed.clear(); - for (const session of Lib.deqSessions.contents) { - session.ResetPool(); - } - Lib.deqSessions.clear(); -}; - -// Make sure we have enough keys since last backup -//orig: bool CheckAutomaticBackup(); -/** - * @return bool - */ -Lib.CheckAutomaticBackup = function () { - /** - * Returns bool - */ - if (!Lib.CCoinJoinClientOptions.IsEnabled() || !Lib.IsMixing()) { - return false; - } - - //TODO - // switch(Lib.nWalletBackups) { - // case 0: - // strAutoDenomResult = _("Automatic backups disabled") + Untranslated(", ") + _("no mixing available."); - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // StopMixing(); - // mixingWallet.nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" - // return false; - // case -1: - // // Automatic backup failed, nothing else we can do until user fixes the issue manually. - // // There is no way to bring user attention in daemon mode, so we just update status and - // // keep spamming if debug is on. - // strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(", ") + _("see debug.log for details."); - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // return false; - // case -2: - // // We were able to create automatic backup but keypool was not replenished because wallet is locked. - // // There is no way to bring user attention in daemon mode, so we just update status and - // // keep spamming if debug is on. - // strAutoDenomResult = _("WARNING! Failed to replenish keypool, please unlock your wallet to do so.") + Untranslated(", ") + _("see debug.log for details."); - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // return false; - // } - // - // if(mixingWallet.nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_STOP) { - // // We should never get here via mixing itself but probably something else is still actively using keypool - // strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + Untranslated(", ") + _("no mixing available."), mixingWallet.nKeysLeftSinceAutoBackup); - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // // It's getting really dangerous, stop mixing - // StopMixing(); - // return false; - // } else if(mixingWallet.nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_WARNING) { - // // Low number of keys left, but it's still more or less safe to continue - // strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), mixingWallet.nKeysLeftSinceAutoBackup); - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // - // if(fCreateAutoBackups) { - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- Trying to create new backup.\n"); - // bilingual_str errorString; - // std::vector warnings; - // - // if(!mixingWallet.AutoBackupWallet("", errorString, warnings)) { - // if(!warnings.empty()) { - // // There were some issues saving backup but yet more or less safe to continue - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- WARNING! Something went wrong on automatic backup: %s\n", Join(warnings, Untranslated("\n")).translated); - // } - // if(!errorString.original.empty()) { - // // Things are really broken - // strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(": ") + errorString; - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - // return false; - // } - // } - // } else { - // // Wait for something else (e.g. GUI action) to create automatic backup for us - // return false; - // } - // } - // - // LogPrint(BCLog::COINJOIN, "CCoinJoinClientManager::CheckAutomaticBackup -- Keys left since latest backup: %d\n", mixingWallet.nKeysLeftSinceAutoBackup); - // - return true; -}; - -//int nCachedNumBlocks{std::numeric_limits::max()}; // used for the overview screen -Lib.nCachedNumBlocks = 9999; /** FIXME: use numeric_limits::max */ -//bool fCreateAutoBackups{true}; // builtin support for automatic backups -Lib.fCreateAutoBackups = true; // builtin support for automatic backups -//void ProcessMessage(CNode& peer, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) LOCKS_EXCLUDED(cs_deqsessions); -Lib.ProcessMessage = function ( - /*CNode& */ peer, - /*CConnman& */ connman, - /*const CTxMemPool&*/ mempool, - /*std::string_view*/ msg_type, - /*CDataStream&*/ vRecv, -) { - if (!Lib.CCoinJoinClientOptions.IsEnabled()) { - return; - } - if (!Lib.m_mn_sync.IsBlockchainSynced()) { - return; - } - - if (!HardDrive.CheckDiskSpace(HardDrive.GetDataDir())) { - Lib.ResetPool(); - Lib.StopMixing(); - Lib.LogPrint( - 'CCoinJoinClientManager::ProcessMessage -- Not enough disk space, disabling CoinJoin.', - ); - return; - } - - if ( - msg_type == Lib.NetMsgType.DSSTATUSUPDATE || - msg_type == Lib.NetMsgType.DSFINALTX || - msg_type == Lib.NetMsgType.DSCOMPLETE - ) { - for (const session of Lib.deqSessions.contents) { - session.ProcessMessage(peer, connman, mempool, msg_type, vRecv); - } - } -}; - -//orig: bilingual_str CCoinJoinClientManager::GetStatuses() -Lib.GetStatuses = function () { - let strStatus; - let fWaitForBlock = Lib.WaitForAnotherBlock(); - - for (const session of Lib.deqSessions.contents) { - strStatus = strStatus + session.GetStatus(fWaitForBlock) + '; '; - } - return strStatus; -}; - -//std::string CCoinJoinClientManager::GetSessionDenoms() -Lib.GetSessionDenoms = function () { - let strSessionDenoms; - - for (const session of Lib.deqSessions.contents) { - strSessionDenoms += Lib.CCoinJoin.DenominationToString( - session.nSessionDenom, - ); - strSessionDenoms += '; '; - } - return strSessionDenoms.length === 0 ? 'N/A' : strSessionDenoms; -}; - -//orig: bool CCoinJoinClientManager::GetMixingMasternodesInfo(std::vector& vecDmnsRet) const -Lib.GetMixingMasternodesInfo = function () { - let vecDmnsRet = []; - for (const session of Lib.deqSessions.contents) { - //CDeterministicMNCPtr dmn; - let dmn = session.GetMixingMasternodeInfo(); - if (dmn) { - vecDmnsRet.push(dmn); - } - } - return vecDmnsRet.length > 0; -}; - -//orig: void CCoinJoinClientManager::CheckTimeout() -Lib.CheckTimeout = function () { - if (!Lib.CCoinJoinClientOptions.IsEnabled() || !Lib.IsMixing()) { - return; - } - for (const session of Lib.deqSessions.contents) { - if (session.CheckTimeout()) { - strAutoDenomResult = 'Session timed out.'; - } - } -}; - -//orig: void CCoinJoinClientManager::UpdatedSuccessBlock() -Lib.UpdatedSuccessBlock = function () { - Lib.nCachedLastSuccessBlock = Lib.nCachedBlockHeight; -}; - -//orig: bool CCoinJoinClientManager::WaitForAnotherBlock() const -Lib.WaitForAnotherBlock = function () { - if (!Lib.m_mn_sync.IsBlockchainSynced()) { - return true; - } - - if (Lib.CCoinJoinClientOptions.IsMultiSessionEnabled()) { - return false; - } - - return ( - Lib.nCachedBlockHeight - Lib.nCachedLastSuccessBlock < Lib.nMinBlocksToWait - ); -}; - -//orig: bool CCoinJoinClientManager::DoAutomaticDenominating(CTxMemPool& mempool, CConnman& connman, bool fDryRun) -Lib.DoAutomaticDenominating = function (mempool, connman, fDryRun) { - if (!Lib.CCoinJoinClientOptions.IsEnabled() || !Lib.IsMixing()) { - return false; - } - - if (!Lib.m_mn_sync.IsBlockchainSynced()) { - Lib.strAutoDenomResult = "Can't mix while sync in progress."; - return false; - } - - if (!fDryRun && Lib.mixingWallet.IsLocked(true)) { - Lib.strAutoDenomResult = 'Wallet is locked.'; - return false; - } - - //FIXME: we need to port the deterministicMNManager - let nMnCountEnabled = Lib.deterministicMNManager - .GetListAtChainTip() - .GetValidMNsCount(); - - // If we've used 90% of the Masternode list then drop the oldest first ~30% - let nThreshold_high = Lib.nMnCountEnabled * 0.9; - let nThreshold_low = Lib.nThreshold_high * 0.7; - Lib.LogPrint( - `Checking vecMasternodesUsed: size: ${Lib.vecMasternodesUsed.size()}, threshold: ${nThreshold_high}`, - ); - - if (Lib.vecMasternodesUsed.size() > nThreshold_high) { - Lib.vecMasternodesUsed.erase(0, vecMasternodesUsed.size() - nThreshold_low); - Lib.LogPrint( - `vecMasternodesUsed: new size: ${Lib.vecMasternodesUsed.size()}, threshold: ${nThreshold_high}`, - ); - } - - let fResult = true; - if (Lib.deqSessions.size() < Lib.CCoinJoinClientOptions.GetSessions()) { - Lib.deqSessions.emplace_back(Lib.mixingWallet, Lib.m_mn_sync); - } - for (const session of Lib.deqSessions.contents) { - if (!Lib.CheckAutomaticBackup()) { - return false; - } - - if (Lib.WaitForAnotherBlock()) { - Lib.strAutoDenomResult = 'Last successful action was too recent.'; - Lib.LogPrint( - `CCoinJoinClientManager::DoAutomaticDenominating -- ${Lib.strAutoDenomResult.original}`, - ); - return false; - } - - fResult &= session.DoAutomaticDenominating(mempool, connman, fDryRun); - } - - return fResult; -}; - -//orig: void CCoinJoinClientManager::AddUsedMasternode(const COutPoint& outpointMn) -Lib.AddUsedMasternode = function (outpointMn) { - Lib.vecMasternodesUsed.push_back(outpointMn); -}; -Lib.Shuffle = function (elements) {}; -Lib.excludeSet = {}; -//orig: CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() -Lib.GetRandomNotUsedMasternode = function () { - const __FUNCTION__ = 'GetRandomNotUsedMasternode'; - //TODO FIXME: get deterministicMNManager impl - let mnList = Lib.deterministicMNManager.GetListAtChainTip(); - - let nCountEnabled = mnList.GetValidMNsCount(); - let nCountNotExcluded = nCountEnabled - Lib.vecMasternodesUsed.size(); - - Lib.LogPrint( - `CCoinJoinClientManager::${__FUNCTION__} -- ${nCountEnabled} ` + - `enabled masternodes, ${nCountNotExcluded} masternodes to choose from`, - ); - if (nCountNotExcluded < 1) { - return null; - } - - // fill a vector - //std::vector vpMasternodesShuffled; - // TODO FIXME: need to do a deep dive on this - let vpMasternodesShuffled = []; - mnList.ForEachMNShared(true, function (dmn) { - vpMasternodesShuffled.push(dmn); - }); - - // shuffle pointers - vpMasternodesShuffled = Lib.Shuffle(vpMasternodesShuffled); //TODO: need a Shuffle function - - //std::set excludeSet(vecMasternodesUsed.begin(), vecMasternodesUsed.end()); - let excludeSet = new Set.create(vecMasternodesUsed); - // loop through - for (const dmn of vpMasternodesShuffled) { - // count() returns the number of items that have that key in the set - if (excludeSet.count(dmn.collateralOutpoint)) { - continue; - } - Lib.LogPrint( - `CCoinJoinClientManager::${__FUNCTION__} -- found, masternode=${dmn.collateralOutpoint.ToStringShort()}`, - ); // TODO: FIXME: do dmn.collateralOutpoint - return dmn; - } - - Lib.LogPrint(`CCoinJoinClientManager::${__FUNCTION__} -- failed`); - return nullptr; -}; -//orig: void CCoinJoinClientManager::ProcessPendingDsaRequest(CConnman& connman) -Lib.ProcessPendingDsaRequest = function (connman) { - for (const session of Lib.deqSessions.contents) { - if (session.ProcessPendingDsaRequest(connman)) { - // TODO: FIXME: must implement ProcessPendingDsaRequest - Lib.strAutoDenomResult = 'Mixing in progress...'; - } - } -}; -//orig: bool CCoinJoinClientManager::TrySubmitDenominate(const CService& mnAddr, CConnman& connman) -Lib.TrySubmitDenominate = function (mnAddr, connman) { - for (const session of Lib.deqSessions.contents) { - //CDeterministicMNCPtr mnMixing; - let mnMixing = null; - mnMixing = session.GetMixingMasternodeInfo(mnMixing); - if ( - mnMixing && - mnMixing.pdmnState.addr == mnAddr && - session.GetState() == POOL_STATE_QUEUE - ) { - session.SubmitDenominate(connman); // TODO FIXME: session needs SubmitDenominate - return true; - } - } - return false; -}; - -//orig: bool CCoinJoinClientManager::MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) const -Lib.MarkAlreadyJoinedQueueAsTried = function (dsq) { - for (const session of Lib.deqSessions.contents) { - //CDeterministicMNCPtr mnMixing; - let mnMixing = null; - mnMixing = session.GetMixingMasternodeInfo(mnMixing); - if (mnMixing && mnMixing.collateralOutpoint == dsq.masternodeOutpoint) { - // TODO: FIXME: need dsq.masternodeOutpoint to be coded - dsq.fTried = true; // TODO: FIXME: need fTried on dsq - return true; - } - } - return false; -}; -//orig: void CCoinJoinClientManager::UpdatedBlockTip(const CBlockIndex* pindex) -Lib.UpdatedBlockTip = function (pindex) { - Lib.nCachedBlockHeight = pindex.nHeight; // TODO FIXME: get nHeight - Lib.LogPrint( - `CCoinJoinClientManager::UpdatedBlockTip -- nCachedBlockHeight: ${Lib.nCachedBlockHeight}`, - ); -}; -//orig: void CCoinJoinClientManager::DoMaintenance(CTxMemPool& mempool, CConnman& connman) -Lib.DoMaintenance = function (mempool, connman) { - if (!Lib.CCoinJoinClientOptions.IsEnabled()) { - return; - } - if (Lib.m_mn_sync == null) { - return; - } - // TODO: FIXME: do IsBlockchainSynced() - if (!Lib.m_mn_sync.IsBlockchainSynced() || Lib.ShutdownRequested()) { - return; - } - - let nTick = 0; - let nDoAutoNextRun = nTick + COINJOIN_AUTO_TIMEOUT_MIN; - - nTick++; - Lib.CheckTimeout(); - Lib.ProcessPendingDsaRequest(connman); - if (nDoAutoNextRun == nTick) { - Lib.DoAutomaticDenominating(mempool, connman); - nDoAutoNextRun = - nTick + - COINJOIN_AUTO_TIMEOUT_MIN + - GetRandInt(COINJOIN_AUTO_TIMEOUT_MAX - COINJOIN_AUTO_TIMEOUT_MIN); - } -}; -//orig: void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const -Lib.GetJsonInfo = function (obj) { - let session_list = []; - - for (const session of Lib.deqSessions.contents) { - if (session.GetState() != POOL_STATE_IDLE) { - session_list.push(session.GetJsonInfo()); - } - } - return { - running: Lib.IsMixing(), - sessions: session_list, - }; -}; diff --git a/src/client-session.js b/src/client-session.js deleted file mode 100644 index b6fb72a..0000000 --- a/src/client-session.js +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const ArrayUtils = require('./array-utils.js'); -const FileLib = require('./file.js'); - -function ClientSession() { - let self = this; - self.clazz = 'ClientSession'; - self.address_info = {}; - self.username = null; - self.used_txids = []; - self.col_txids = []; - self.used_addresses = []; - self.mixing_inputs = []; - self.generated = []; - self.get_used_txids = function () { - return [...self.used_txids, ...self.col_txids]; - }; - self.add_inputs = function (chosenInputTxns) { - self.add_txids(ArrayUtils.extract(chosenInputTxns, 'txid')); - self.add_addresses(ArrayUtils.extract(chosenInputTxns, 'address')); - self.mixing_inputs = [...self.mixing_inputs, ...chosenInputTxns]; - }; - self.add_generated_addresses = function (list) { - self.generated = [...self.generated, ...list]; - return self.generated; - }; - self.get_generated_addresses = function () { - return self.generated; - }; - self.get_inputs = function () { - return self.mixing_inputs; - }; - self.get_used_addresses = function () { - return self.used_addresses; - }; - self.add_txids = function (a) { - self.used_txids = [...self.used_txids, ...a]; - }; - self.add_addresses = function (a) { - self.used_addresses = [...self.used_addresses, ...a]; - }; - self.report_inputs = function () { - let rep = []; - for (const i of self.get_inputs()) { - rep.push(`txid: ${i.txid}`); - rep.push(`address: ${i.address}`); - rep.push(`vout: ${i.vout ?? '?'}`); - rep.push(`outputIndex: ${i.outputIndex ?? '?'}`); - rep.push('============================================================='); - } - return rep.join('\n'); - }; - self.write = async function (prefix = 'dss') { - let rel_path = `${prefix}-${self.username}-#DATE#`; - await FileLib.write_json(rel_path, self); - }; -} - -module.exports = { - ClientSession, -}; diff --git a/src/coin-join-constants.js b/src/coin-join-constants.js deleted file mode 100644 index 079bd52..0000000 --- a/src/coin-join-constants.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * A port of DASH core's CCoinJoinClientManager - */ - -let Lib = {}; -module.exports = Lib; -const { COIN, MAX_MONEY } = require('./coin.js'); -Lib.COINJOIN_AUTO_TIMEOUT_MIN = 5; -Lib.COINJOIN_AUTO_TIMEOUT_MAX = 15; -Lib.COINJOIN_QUEUE_TIMEOUT = 30; -Lib.COINJOIN_SIGNING_TIMEOUT = 15; -Lib.COINJOIN_ENTRY_MAX_SIZE = 9; -Lib.COINJOIN_DENOM_OUTPUTS_THRESHOLD = 500; -// Warn user if mixing in gui or try to create backup if mixing in daemon mode -// when we have only this many keys left -Lib.COINJOIN_KEYS_THRESHOLD_WARNING = 100; -// Stop mixing completely, it's too dangerous to continue when we have only this many keys left -Lib.COINJOIN_KEYS_THRESHOLD_STOP = 50; -// Pseudorandomly mix up to this many times in addition to base round count -Lib.COINJOIN_RANDOM_ROUNDS = 3; - -Lib.MIN_COINJOIN_SESSIONS = 1; -Lib.MIN_COINJOIN_ROUNDS = 2; -Lib.MIN_COINJOIN_AMOUNT = 2; -Lib.MIN_COINJOIN_DENOMS_GOAL = 10; -Lib.MIN_COINJOIN_DENOMS_HARDCAP = 10; -Lib.MAX_COINJOIN_SESSIONS = 10; -Lib.MAX_COINJOIN_ROUNDS = 16; -Lib.MAX_COINJOIN_DENOMS_GOAL = 100000; -Lib.MAX_COINJOIN_DENOMS_HARDCAP = 100000; -Lib.MAX_COINJOIN_AMOUNT = MAX_MONEY / COIN; -Lib.DEFAULT_COINJOIN_SESSIONS = 4; -Lib.DEFAULT_COINJOIN_ROUNDS = 4; -Lib.DEFAULT_COINJOIN_AMOUNT = 1000; -Lib.DEFAULT_COINJOIN_DENOMS_GOAL = 50; -Lib.DEFAULT_COINJOIN_DENOMS_HARDCAP = 300; - -Lib.DEFAULT_COINJOIN_AUTOSTART = false; -Lib.DEFAULT_COINJOIN_MULTISESSION = false; - -// How many new denom outputs to create before we consider the "goal" loop in CreateDenominated -// a final one and start creating an actual tx. Same limit applies for the "hard cap" part of the algo. -// NOTE: We do not allow txes larger than 100kB, so we have to limit the number of outputs here. -// We still want to create a lot of outputs though. -// Knowing that each CTxOut is ~35b big, 400 outputs should take 400 x ~35b = ~17.5kb. -// More than 500 outputs starts to make qt quite laggy. -// Additionally to need all 500 outputs (assuming a max per denom of 50) you'd need to be trying to -// create denominations for over 3000 dash! -Lib.COINJOIN_DENOM_OUTPUTS_THRESHOLD = 500; - -// Warn user if mixing in gui or try to create backup if mixing in daemon mode -// when we have only this many keys left -Lib.COINJOIN_KEYS_THRESHOLD_WARNING = 100; -// Stop mixing completely, it's too dangerous to continue when we have only this many keys left -Lib.COINJOIN_KEYS_THRESHOLD_STOP = 50; -// Pseudorandomly mix up to this many times in addition to base round count -Lib.COINJOIN_RANDOM_ROUNDS = 3; -Lib.ERR_ALREADY_HAVE = 0; -Lib.ERR_DENOM = 1; -Lib.ERR_ENTRIES_FULL = 2; -Lib.ERR_EXISTING_TX = 3; -Lib.ERR_FEES = 4; -Lib.ERR_INVALID_COLLATERAL = 5; -Lib.ERR_INVALID_INPUT = 6; -Lib.ERR_INVALID_SCRIPT = 7; -Lib.ERR_INVALID_TX = 8; -Lib.ERR_MAXIMUM = 9; -Lib.ERR_MN_LIST = 10; -Lib.ERR_MODE = 11; -Lib.ERR_NON_STANDARD_PUBKEY = 12; // not used -(Lib.ERR_NOT_A_MN = 13), // not used - (Lib.ERR_QUEUE_FULL = 14); -Lib.ERR_RECENT = 15; -Lib.ERR_SESSION = 16; -Lib.ERR_MISSING_TX = 17; -Lib.ERR_VERSION = 18; -Lib.MSG_NOERR = 19; -Lib.MSG_SUCCESS = 20; -Lib.MSG_ENTRIES_ADDED = 21; -Lib.ERR_SIZE_MISMATCH = 22; -Lib.MSG_POOL_MIN = Lib.ERR_ALREADY_HAVE; -Lib.MSG_POOL_MAX = Lib.ERR_SIZE_MISMATCH; - -Lib.STANDARD_DENOMINATIONS = [ - 10 * COIN + 10000, - 1 * COIN + 1000, - COIN / 10 + 100, - COIN / 100 + 10, - COIN / 1000 + 1, -]; -Lib.COIN = COIN; - -module.exports = Lib; diff --git a/src/coin-join-denominations.js b/src/coin-join-denominations.js deleted file mode 100644 index c02948c..0000000 --- a/src/coin-join-denominations.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * A port of DASH core's CCoinJoin - */ - -let Lib = {}; -const { COIN } = require('./coin.js'); -const { COINJOIN_ENTRY_MAX_SIZE } = require('./coin-join-constants.js'); - -module.exports = Lib; -// static members -Lib.vecStandardDenominations = [ - 10 * COIN + 10000, - 1 * COIN + 1000, - COIN / 10 + 100, - COIN / 100 + 10, - COIN / 1000 + 1, -]; - -//orig: static constexpr std::array GetStandardDenominations() { return vecStandardDenominations; } -Lib.GetStandardDenominations = function () { - return Lib.vecStandardDenominations; -}; -//orig: static constexpr CAmount GetSmallestDenomination() { return vecStandardDenominations.back(); } -Lib.GetSmallestDenomination = function () { - return Lib.vecStandardDenominations[Lib.vecStandardDenominations.length - 1]; -}; - -//orig: static constexpr bool IsDenominatedAmount(CAmount nInputAmount) { return AmountToDenomination(nInputAmount) > 0; } -Lib.IsDenominatedAmount = function (nInputAmount) { - return Lib.AmountToDenomination(nInputAmount) > 0; -}; -//orig: static constexpr bool IsValidDenomination(int nDenom) { return DenominationToAmount(nDenom) > 0; } -Lib.IsValidDenomination = function (nDenom) { - return Lib.DenominationToAmount(nDenom) > 0; -}; -/* - Return a bitshifted integer representing a denomination in vecStandardDenominations - or 0 if none was found -*/ -//orig: static constexpr int AmountToDenomination(CAmount nInputAmount) -Lib.AmountToDenomination = function (nInputAmount) { - for (let i = 0; i < Lib.vecStandardDenominations.length; ++i) { - if (nInputAmount == Lib.vecStandardDenominations[i]) { - return 1 << i; - } - } - return 0; -}; - -/* - Returns: - - one of standard denominations from vecStandardDenominations based on the provided bitshifted integer - - 0 for non-initialized sessions (nDenom = 0) - - a value below 0 if an error occurred while converting from one to another -*/ -//orig: static constexpr CAmount DenominationToAmount(int nDenom) -Lib.DenominationToAmount = function (nDenom) { - if (nDenom == 0) { - // not initialized - return 0; - } - - let nMaxDenoms = Lib.vecStandardDenominations.length; - - if (nDenom >= 1 << nMaxDenoms || nDenom < 0) { - // out of bounds - return -1; - } - - if ((nDenom & (nDenom - 1)) != 0) { - // non-denom - return -2; - } - - let nDenomAmount = -3; - for (let i = 0; i < nMaxDenoms; ++i) { - if (nDenom & (1 << i)) { - nDenomAmount = Lib.vecStandardDenominations[i]; - break; - } - } - - return nDenomAmount; -}; - -/* -Same as DenominationToAmount but returns a string representation -*/ -//orig: static std::string DenominationToString(int nDenom); -Lib.DenominationToString = function (nDenom) {}; -Lib.GetCollateralAmount = function () { - return Lib.GetSmallestDenomination() / 10; -}; -Lib.GetMaxCollateralAmount = function () { - return Lib.GetCollateralAmount() * 4; -}; diff --git a/src/coin-join.js b/src/coin-join.js deleted file mode 100644 index f4a79cb..0000000 --- a/src/coin-join.js +++ /dev/null @@ -1,300 +0,0 @@ -/** - * A port of DASH core's CCoinJoin - */ - -let Lib = { core_name: 'CCoinJoin' }; -const { COIN } = require('./coin.js'); -const { COINJOIN_ENTRY_MAX_SIZE } = require('./coin-join-constants.js'); -const llmq = require('./llmq.js'); -let CoinJoinDenominations = require('./coin-join-denominations.js'); - -module.exports = Lib; -let Validation = {}; -//orig: CChainState& ChainstateActive() -Validation.ChainstateActiveCoinsTipGetCoin = function (outpoint, coin) { - //bool CChainState::LoadChainTip(const CChainParams& chainparams) - //{ - // AssertLockHeld(cs_main); - // const CCoinsViewCache& coins_cache = CoinsTip(); - // assert(!coins_cache.GetBestBlock().IsNull()); // Never called when the coins view is empty - // const CBlockIndex* tip = m_chain.Tip(); - // - // if (tip && tip->GetBlockHash() == coins_cache.GetBestBlock()) { - // return true; - // } - // - // // Load pointer to end of best chain - // CBlockIndex* pindex = LookupBlockIndex(coins_cache.GetBestBlock()); - // if (!pindex) { - // return false; - // } - // m_chain.SetTip(pindex); - // PruneBlockIndexCandidates(); - // - // tip = m_chain.Tip(); - // LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n", - // tip->GetBlockHash().ToString(), - // m_chain.Height(), - // FormatISO8601DateTime(tip->GetBlockTime()), - // GuessVerificationProgress(chainparams.TxData(), tip)); - // return true; - //} - // - //return g_chainman.m_active_chainstate; // TODO: g_chainman.m_active_chainstate - //bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const { - // CCoinsMap::const_iterator it = FetchCoin(outpoint); - // if (it != cacheCoins.end()) { - // coin = it->second.coin; - // return !coin.IsSpent(); - // } - // return false; - //} - //CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const { - // CCoinsMap::iterator it = cacheCoins.find(outpoint); - // if (it != cacheCoins.end()) - // return it; - // Coin tmp; - // if (!base->GetCoin(outpoint, tmp)) - // return cacheCoins.end(); - // CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first; - // if (ret->second.coin.IsSpent()) { - // // The parent only has an empty entry for this outpoint; we can consider our - // // version as fresh. - // ret->second.flags = CCoinsCacheEntry::FRESH; - // } - // cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage(); - // return ret; - //} - - let ret = { - valid: false, - }; - return ret; -}; - -//orig: bool GetUTXOCoin(const COutPoint& outpoint, Coin& coin) -Validation.GetUTXOCoin = function (outpoint, coin) { - let utxoCoin = {}; - - //orig: if (!Lib.ChainstateActive().CoinsTip().GetCoin(outpoint, coin)){ - utxoCoin.coin = Validation.ChainstateActiveCoinsTipGetCoin(outpoint, coin); - if (!utxoCoin.coin.valid) { - utxoCoin.valid = false; - return utxoCoin; - } - if (coin.IsSpent()) { - utxoCoin.valid = false; - return utxoCoin; - } - utxoCoin.out = { - nValue: coin.out.nValue, - }; - return utxoCoin; -}; - -//orig: static std::map mapDSTX GUARDED_BY(cs_mapdstx); -Lib.mapDSTX = {}; // FIXME: this can most likely just be an object -//orig: static void CheckDSTXes(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) LOCKS_EXCLUDED(cs_mapdstx); -Lib.CheckDSTXes = function (pindex, clhandler) {}; - -/** - * Denominations related functions - */ -Lib.vecStandardDenominations = CoinJoinDenominations.vecStandardDenominations; -//orig: static constexpr std::array GetStandardDenominations() { return vecStandardDenominations; } -Lib.GetStandardDenominations = CoinJoinDenominations.GetStandardDenominations; -//orig: static constexpr CAmount GetSmallestDenomination() { return vecStandardDenominations.back(); } -Lib.GetSmallestDenomination = CoinJoinDenominations.GetSmallestDenomination; -//orig: static constexpr bool IsDenominatedAmount(CAmount nInputAmount) { return AmountToDenomination(nInputAmount) > 0; } -Lib.IsDenominatedAmount = CoinJoinDenominations.IsDenominatedAmount; -//orig: static constexpr bool IsValidDenomination(int nDenom) { return DenominationToAmount(nDenom) > 0; } -Lib.IsValidDenomination = CoinJoinDenominations.IsValidDenomination; -//orig: static constexpr int AmountToDenomination(CAmount nInputAmount) -Lib.AmountToDenomination = CoinJoinDenominations.AmountToDenomination; -//orig: static constexpr CAmount DenominationToAmount(int nDenom) -Lib.DenominationToAmount = CoinJoinDenominations.DenominationToAmount; -//orig: static std::string DenominationToString(int nDenom); -Lib.DenominationToString = CoinJoinDenominations.DenominationToString; - -//orig: static bilingual_str GetMessageByID(PoolMessage nMessageID); -Lib.GetMessageByID = function (nMessageID) {}; - -/// Get the minimum/maximum number of participants for the pool -//orig: static int GetMinPoolParticipants(); -Lib.GetMinPoolParticipants = function () {}; -//orig: static int GetMaxPoolParticipants(); -Lib.GetMaxPoolParticipants = function () {}; - -//orig: static constexpr CAmount GetMaxPoolAmount() { return COINJOIN_ENTRY_MAX_SIZE * vecStandardDenominations.front(); } -Lib.GetMaxPoolAmount = function () { - return COINJOIN_ENTRY_MAX_SIZE * Lib.vecStandardDenominations[0]; -}; - -/// If the collateral is valid given by a client -//orig: static bool IsCollateralValid(CTxMemPool& mempool, const CTransaction& txCollateral); -Lib.IsCollateralValid = function (mempool, txCollateral) { - /** TODO: FIXME: this function references a lot of currently unimplemented functionality. - */ - // TODO: FIXME: create CTransaction with .vout.empty() - if (txCollateral.vout.empty()) { - return false; - } - // TODO: implement CTransaction.nLockTime - if (txCollateral.nLockTime !== 0) { - return false; - } - - //CAmount nValueIn = 0; - //CAmount nValueOut = 0; - - // TODO: implement CAmount - - let nValueIn = 0; // TODO: convert to CAmount - let nValueOut = 0; // TODO: convert to CAmount - for (const txout of txCollateral.vout) { - // TODO: implement .vout - nValueOut += txout.nValue; // TODO: implement .nValue - - // TODO: implement scriptPubKey.IsPayToPublicKeyHash() - // TODO: implement scriptPubKey.IsUnspendable() - if ( - !txout.scriptPubKey.IsPayToPublicKeyHash() && - !txout.scriptPubKey.IsUnspendable() - ) { - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- Invalid Script, txCollateral=${txCollateral.ToString()}`, - ); // TODO: implement CTransaction::ToString() - return false; - } - } - - for (const txin of txCollateral.vin) { - // TODO: implement CTransaction::vin - //Coin coin; - let coin = 0; - let mempoolTx = mempool.get(txin.prevout.hash); // TODO: prevout.hash - let utxoCoin = Validation.GetUTXOCoin(txin.prevout, coin); - if (mempoolTx !== null) { - if ( - mempool.isSpent(txin.prevout) || - !llmq.quorumInstantSendManager.IsLocked(txin.prevout.hash) - ) { - // TODO: llmq, llmq.quorumInstantSendManager, llmq.IsLocked() - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- spent or non-locked mempool input! txin=${txin.ToString()}`, - ); // TODO: txin.ToString() - return false; - } - nValueIn += mempoolTx.vout[txin.prevout.n].nValue; // TODO: txin.prevout, txin.prevout.n - /*orig: } else if (Validation.GetUTXOCoin(txin.prevout, coin)) { - nValueIn += coin.out.nValue; - */ - } else if (utxoCoin.valid) { - nValueIn += utxoCoin.out.nValue; - } else { - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- Unknown inputs in collateral transaction, txCollateral=${txCollateral.ToString()}`, - ); /* Continued */ - return false; - } - } - - //collateral transactions are required to pay out a small fee to the miners - if (nValueIn - nValueOut < Lib.GetCollateralAmount()) { - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- did not include enough fees in transaction: fees: ${ - nValueOut - nValueIn - }, txCollateral=${txCollateral.ToString()}`, - ); /* Continued */ - return false; - } - - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- ${txCollateral.ToString()}`, - ); /* Continued */ - - { - //CValidationState validationState; - let validationState = 0; - if ( - !Lib.AcceptToMemoryPool( - mempool, - validationState, - Lib.MakeTransactionRef(txCollateral), - /*pfMissingInputs=*/ null, - /*bypass_limits=*/ false, - /*nAbsurdFee=*/ DEFAULT_MAX_RAW_TX_FEE /** TODO FIXME: need this defined */, - /*test_accept=*/ true, - ) - ) { - Lib.LogPrint( - `CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool()`, - ); - return false; - } - } - - return true; -}; -//orig: static constexpr CAmount GetCollateralAmount() { return GetSmallestDenomination() / 10; } -Lib.GetCollateralAmount = function () { - return Lib.GetSmallestDenomination() / 10; -}; -//orig: static constexpr CAmount GetMaxCollateralAmount() { return GetCollateralAmount() * 4; } -Lib.GetMaxCollateralAmount = function () { - return Lib.GetCollateralAmount() * 4; -}; - -//orig: static constexpr bool IsCollateralAmount(CAmount nInputAmount) -Lib.IsCollateralAmount = function (nInputAmount) { - // collateral input can be anything between 1x and "max" (including both) - return ( - nInputAmount >= Lib.GetCollateralAmount() && - nInputAmount <= Lib.GetMaxCollateralAmount() - ); -}; - -//orig: static constexpr int CalculateAmountPriority(CAmount nInputAmount) -Lib.CalculateAmountPriority = function (nInputAmount) { - /* - if (auto optDenom = ranges::find_if_opt(GetStandardDenominations(), [&nInputAmount](const auto& denom) { - return nInputAmount == denom; - })) { - return (float)COIN / *optDenom * 10000; - } - if (nInputAmount < COIN) { - return 20000; - } - */ - let optDenom = null; - for (const denom of Lib.GetStandardDenominations()) { - if (denom === nInputAmount) { - optDenom = denom; - return (COIN / optDenom) * 10000; - } - } - if (nInputAmount < COIN) { - return 20000; - } - //nondenom return largest first - return -1 * (nInputAmount / COIN); -}; - -//orig: static void AddDSTX(const CCoinJoinBroadcastTx& dstx) LOCKS_EXCLUDED(cs_mapdstx); -Lib.AddDSTX = function (dstx) {}; -//orig: static CCoinJoinBroadcastTx GetDSTX(const uint256& hash) LOCKS_EXCLUDED(cs_mapdstx); -Lib.GetDSTX = function (hash) {}; - -//orig: static void UpdatedBlockTip(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const std::unique_ptr& mn_sync); -Lib.UpdatedBlockTip = function (pindex, clhandler, mn_sync) {}; -//orig: static void NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const std::unique_ptr& mn_sync); -Lib.NotifyChainLock = function (pindex, clhandler, mn_sync) {}; - -//orig: static void UpdateDSTXConfirmedHeight(const CTransactionRef& tx, int nHeight); -Lib.UpdateDSTXConfirmedHeight = function (tx, nHeight) {}; -//orig: static void TransactionAddedToMempool(const CTransactionRef& tx) LOCKS_EXCLUDED(cs_mapdstx); -Lib.TransactionAddedToMempool = function (tx) {}; -//orig: static void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) LOCKS_EXCLUDED(cs_mapdstx); -Lib.BlockConnected = function (pblock, pindex) {}; -//orig: static void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex*) LOCKS_EXCLUDED(cs_mapdstx); -Lib.BlockDisconnected = function (pblock, CBlockIndex) {}; diff --git a/src/coin.js b/src/coin.js deleted file mode 100644 index d81c1e9..0000000 --- a/src/coin.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * A port of DASH core's CoinJoin CCoinJoinClientOptions class - */ - -let COIN = 100000000; -module.exports = { - COIN, - MAX_MONEY: 21000000 * COIN, -}; diff --git a/src/coincontrol.js b/src/coincontrol.js deleted file mode 100644 index 7ed7179..0000000 --- a/src/coincontrol.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * This is a port of Dash Core's CCoinControl class. - * - * How to use this: - * 1) You will need a generic "wallet" variable. This can be any - * javascript object that has the following object defined on it: - { - "destChange", - "fRequireAllInputs", - "fAllowWatchOnly", - "m_feerate", - "m_discard_feerate", - "m_confirm_target", - "m_avoid_partial_spends", - "m_avoid_address_reuse", - "m_fee_mode", - "m_min_depth", - "nCoinType", - } - - Assuming your wallet object is named 'my_wallet', you could do - the following: - - let my_wallet = {}; - my_wallet.coin_control = { - destChange: nul, - fRequireAllInputs: true, - fAllowWatchOnly: false, - nCoinType: CoinType.ALL_COINS, - }; - Note that all key/value pairs are optional, but if you decide - to leave one out, the CCoinControl class will fill those in - with a default value. You can always go back and set these - values on the resulting CCoinControl object after you've - constructed it. See step 3 for how - - - 2) Once you've set the coin_control object on your wallet, - you can now create a CCoinControl object: - - let coin_control = new CCoinControl(my_wallet); - - 3) Optionally, you can always set the variables on coin_control - after you've constructed the object: - - For example, maybe you want to change the nCoinType: - - coin_control.nCoinType = CoinType.ONLY_FULLY_MIXED; - - */ - -let Lib = {}; -const Vector = require('./vector.js'); -const COutPoint = require('./coutpoint.js'); -const COutput = require('./coutput.js'); -const CoinType = require('./cointype-constants.js'); -const { ONLY_FULLY_MIXED, ALL_COINS } = CoinType; -module.exports = { - CCoinControl, - constants: { - CoinType, - }, -}; -//class CCoinControl -function CCoinControl( - args = { - wallet: null, - }, -) { - let self = this; - //CTxDestination destChange; - this.destChange = 0; - //! If false, allows unselected inputs, but requires all selected inputs be used if fAllowOtherInputs is true (default) - //bool fAllowOtherInputs; - this.fAllowOtherInputs = false; - //! If false, only include as many inputs as necessary to fulfill a coin selection request. Only usable together with fAllowOtherInputs - this.fRequireAllInputs = false; - //! Includes watch only addresses which are solvable - this.fAllowWatchOnly = false; - //! Override automatic min/max checks on fee, m_feerate must be set if true - this.fOverrideFeeRate = false; - //! Override the wallet's m_pay_tx_fee if set - this.m_feerate = 0; - //! Override the discard feerate estimation with m_discard_feerate in CreateTransaction if set - this.m_discard_feerate = 0; - //! Override the default confirmation target if set - this.m_confirm_target = 0; - //! Avoid partial use of funds sent to a given address - this.m_avoid_partial_spends = false; - //! Forbids inclusion of dirty (previously used) addresses - this.m_avoid_address_reuse = false; - //! Fee estimation mode to control arguments to estimateSmartFee - this.m_fee_mode = ''; - //! Minimum chain depth value for coin availability - this.m_min_depth = 0; - //! Controls which types of coins are allowed to be used (default: ALL_COINS) - this.nCoinType = CoinType.ALL_COINS; - - //std::set setSelected; - this.setSelected = {}; - this.assignable = [ - 'destChange', - 'fRequireAllInputs', - 'fAllowWatchOnly', - 'm_feerate', - 'm_discard_feerate', - 'm_confirm_target', - 'm_avoid_partial_spends', - 'm_avoid_address_reuse', - 'm_fee_mode', - 'm_min_depth', - 'nCoinType', - ]; - this.defaults = { - destChange: null, - fRequireAllInputs: true, - fAllowWatchOnly: false, - m_feerate: 0, - m_discard_feerate: 0, - m_confirm_target: true, - m_avoid_partial_spends: true, - m_avoid_address_reuse: true, - m_fee_mode: '', - m_min_depth: 0, - nCoinType: CoinType.ALL_COINS, - }; - this.setFromWallet = function (w) { - for (const key of self.assignable) { - if ('undefined' !== typeof w.coinControl[key]) { - self[key] = w.coinControl[key]; - } else { - self[key] = self.defaults[key]; - } - } - }; - this.SetNull = function (fResetCoinType = true) {}; - - this.HasSelected = function () { - return Object.keys(self.setSelected).length > 0; - }; - - this.IsSelected = function (output) { - return 'undefined' !== self.setSelected[output]; - }; - - this.Select = function (output) { - self.setSelected[output] = 1; - }; - - this.UnSelect = function (output) { - delete self.setSelected[output]; - }; - - this.UnSelectAll = function () { - self.setSelected = {}; - }; - - this.ListSelected = function (vOutpoints) { - //FIXME - vOutpoints.assign(self.setSelected); - }; - - // Dash-specific helpers - - this.UseCoinJoin = function (fUseCoinJoin) { - self.nCoinType = fUseCoinJoin ? ONLY_FULLY_MIXED : ALL_COINS; - }; - - this.IsUsingCoinJoin = function () { - return self.nCoinType === ONLY_FULLY_MIXED; - }; - - if (null !== args.wallet) { - this.setFromWallet(args.wallet); - } -} diff --git a/src/cointype-constants.js b/src/cointype-constants.js deleted file mode 100644 index 4bb4bcb..0000000 --- a/src/cointype-constants.js +++ /dev/null @@ -1,13 +0,0 @@ -const CoinType = { - ALL_COINS: 0, - ONLY_FULLY_MIXED: 1, - ONLY_READY_TO_MIX: 2, - ONLY_NONDENOMINATED: 3, - ONLY_MASTERNODE_COLLATERAL: 4, // find masternode outputs including locked ones (use with caution) - ONLY_COINJOIN_COLLATERAL: 5, - // Attributes - MIN_COIN_TYPE: 0, - MAX_COIN_TYPE: 5, -}; - -module.exports = CoinType; diff --git a/src/coltx.json b/src/coltx.json deleted file mode 100644 index 11c5919..0000000 --- a/src/coltx.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "hash": "6e6a867379edd4caf6f7db30a211bc149efac260c3844c5ad00d38924c240db8", - "version": 3, - "inputs": [ - { - "prevTxId": "010940e6dbbd57ee226dd6eea855eb31fa6ced683dda193ae1347c1debf59c63", - "outputIndex": 0, - "sequenceNumber": 4294967295, - "script": "483045022100dff4f4edb6a9120a006faf501c7f9fc738e4b100deb0b239bac4fcc0e9c198c9022011e003b34f2d64ddbe294983872dc0be034d050dafdfa02e327799b11661c05b0121023f46dab5842361d1606f1939b9a793bd505c630c9f7fd96930a11fbbf40593d8", - "scriptString": "72 0x3045022100dff4f4edb6a9120a006faf501c7f9fc738e4b100deb0b239bac4fcc0e9c198c9022011e003b34f2d64ddbe294983872dc0be034d050dafdfa02e327799b11661c05b01 33 0x023f46dab5842361d1606f1939b9a793bd505c630c9f7fd96930a11fbbf40593d8", - "output": { - "satoshis": 12436696936, - "script": "76a91464b28383cc35bbd03c1ed9617707098152fdbe0688ac" - } - } - ], - "outputs": [ - { - "satoshis": 20100, - "script": "76a914009c0f2f82419b3eae635a74c13bdf9bf68d73a588ac" - }, - { - "satoshis": 12436676610, - "script": "76a91464b28383cc35bbd03c1ed9617707098152fdbe0688ac" - } - ], - "nLockTime": 0, - "changeScript": "OP_DUP OP_HASH160 20 0x64b28383cc35bbd03c1ed9617707098152fdbe06 OP_EQUALVERIFY OP_CHECKSIG", - "changeIndex": 1, - "fee": 226, - "serialized": "0300000001639cf5eb1d7c34e13a19da3d68ed6cfa31eb55a8eed66d22ee57bddbe6400901000000006b483045022100dff4f4edb6a9120a006faf501c7f9fc738e4b100deb0b239bac4fcc0e9c198c9022011e003b34f2d64ddbe294983872dc0be034d050dafdfa02e327799b11661c05b0121023f46dab5842361d1606f1939b9a793bd505c630c9f7fd96930a11fbbf40593d8ffffffff02844e0000000000001976a914009c0f2f82419b3eae635a74c13bdf9bf68d73a588ac02a048e5020000001976a91464b28383cc35bbd03c1ed9617707098152fdbe0688ac00000000" -} diff --git a/src/config.demodata.json.example b/src/config.demodata.json.example deleted file mode 100644 index 120197a..0000000 --- a/src/config.demodata.json.example +++ /dev/null @@ -1,8 +0,0 @@ -{ - "usedTxn": "./data/w-psend-used-txns.json", - "txnList": "./data/w-psend-txns.json", - "changeAddress": "./data/w-psend-change-address-0", - "sourceAddress": "./data/w-psend-address-0", - "wif": "./data/w-psend-privkey-0", - "payeeAddress": "./data/w-foobar-address-0" -} diff --git a/src/cscript.js b/src/cscript.js deleted file mode 100644 index bdd7f9c..0000000 --- a/src/cscript.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * ATTN: this script is woefully incomplete! - */ -/** Serialized script, used inside transaction inputs and outputs */ -//class CScript : public CScriptBase -function CScript( - args = { - int64In: null, - opcodeIn: null, - cScriptNum: null, - cScript: null, // in the case of instantiating this with another CScript() object - }, -) { - let self = this; - this.constructorId = 0; - this.vector_contents = []; - this.push_back = function (input, type) { - self.vector_contents.push({ value: input, type }); - }; - this.push_int64 = function (n) { - if (n == -1 || (n >= 1 && n <= 16)) { - self.push_back(n + (OP_1 - 1), 'opcode'); - } else if (n == 0) { - self.push_back(OP_0, 'opcode'); - } else { - self.push_back(n, 'cscriptnum'); - } - }; - this.clear = function () { - self.vector_contents = []; - }; - this.equals = function (other) { - // FIXME: this probably isn't right - return self.vector_contents === other.vector_contents; - }; - this.ToHexStr = function () { - function toHexString(byteArray) { - return Array.from(byteArray, function (byte) { - return ('0' + (byte & 0xff).toString(16)).slice(-2); - }).join(''); - } - return toHexString(self.vector_contents); - }; - /** - * Support for constructor: - explicit CScript(int64_t b) { operator<<(b); } - */ - if (null !== args.int64In) { - self.push_back(args.int64In, 'int64'); - this.constructorId = 1; - } - /** - * Support for constructor: - explicit CScript(opcodetype b) { operator<<(b); } - */ - if (null !== args.opcodeIn) { - this.constructorId = 2; - self.push_back(args.opcodeIn, 'opcode'); - } - /** - * Support for constructor: - explicit CScript(const CScriptNum& b) { operator<<(b); } - */ - if (null !== args.cScriptNum) { - this.constructorId = 3; - self.push_back(args.cScriptNum, 'cscriptnum'); - } -} diff --git a/src/ctransaction.js b/src/ctransaction.js deleted file mode 100644 index d08a328..0000000 --- a/src/ctransaction.js +++ /dev/null @@ -1,81 +0,0 @@ -const NetUtil = require('./network-util.js'); -const assert = require('assert'); -let DashCore = require('@dashevo/dashcore-lib'); -let Transaction = DashCore.Transaction; -let Script = DashCore.Script; -let PrivateKey = DashCore.PrivateKey; -let Address = DashCore.Address; -const fs = require('fs'); -const { - calculateCompactSize, - encodeCompactSizeBytes, - setUint32, - setSignedInt64, - hexToBytes, -} = NetUtil; - -module.exports = { - read_file, - logUsedTransaction, - isUsed, - sendCoins, -}; - -async function read_file(fname) { - return await fs - .readFileSync(fname) - .toString() - .replace(/^\s+/, '') - .replace(/\s+$/, ''); -} -async function logUsedTransaction(fileName, txnId) { - let buffer = await fs.readFileSync(fileName); - buffer = buffer.toString(); - let data = JSON.parse(buffer); - data.list.push(txnId); - await fs.writeFileSync(fileName, JSON.stringify(data, null, 2)); -} -async function isUsed(fileName, txnId) { - let buffer = await fs.readFileSync(fileName); - buffer = buffer.toString(); - let data = JSON.parse(buffer); - return data.list.indexOf(txnId) !== -1; -} -async function sendCoins( - origAmount, - sendAmount, - fromAddress, - payeeAddress, - changeAddress, - network, - txid, - vout, - privkeySet, -) { - let times = 1; - - let fee = 1000; - sendAmount = parseInt(sendAmount, 10); - let unspent = origAmount - sendAmount; - unspent -= fee; - let sourceAddress = Address(fromAddress, network); - /** - * Take the input of the PrevTx, send it to another wallet - * which will hold the new funds and the change will go to - * a change address. - */ - let utxos = { - txId: txid, - outputIndex: vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis: origAmount, - }; - return new Transaction() - .from(utxos) // Feed information about what unspent outputs one can use - .to(payeeAddress, sendAmount) // Add an output with the given amount of satoshis - .to(changeAddress, unspent) - .change(changeAddress) // Sets up a change address where the rest of the funds will go - .fee(fee) - .sign(privkeySet); // Signs all the inputs it can -} diff --git a/src/ctxin-constants.js b/src/ctxin-constants.js deleted file mode 100644 index 4d97826..0000000 --- a/src/ctxin-constants.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Ported from Dash core - * Dash Core file: https://github.com/dashpay/dash/blob/master/src/primitives/transaction.h - * - * An input of a transaction. It contains the location of the previous - * transaction's output that it claims and a signature that matches the - * output's public key. - */ - -/* Setting nSequence to this value for every input in a transaction - * disables nLockTime. */ -const /*uint32_t*/ SEQUENCE_FINAL = 0xffffffff; -/* Below flags apply in the context of BIP 68*/ -/* If this flag set, CTxIn::nSequence is NOT interpreted as a - * relative lock-time. */ -const /*uint32_t*/ SEQUENCE_LOCKTIME_DISABLE_FLAG = 1 << 31; -/* If CTxIn::nSequence encodes a relative lock-time and this flag - * is set, the relative lock-time has units of 512 seconds, - * otherwise it specifies blocks with a granularity of 1. */ -const /*uint32_t*/ SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22; -/* If CTxIn::nSequence encodes a relative lock-time, this mask is - * applied to extract that lock-time from the sequence field. */ -const /*uint32_t*/ SEQUENCE_LOCKTIME_MASK = 0x0000ffff; -/* In order to use the same number of bits to encode roughly the - * same wall-clock duration, and because blocks are naturally - * limited to occur every 600s on average, the minimum granularity - * for time-based relative lock-time is fixed at 512 seconds. - * Converting from CTxIn::nSequence to seconds is performed by - * multiplying by 512 = 2^9, or equivalently shifting up by - * 9 bits. */ -const /*int*/ SEQUENCE_LOCKTIME_GRANULARITY = 9; - -module.exports = { - SEQUENCE_FINAL, - SEQUENCE_LOCKTIME_DISABLE_FLAG, - SEQUENCE_LOCKTIME_TYPE_FLAG, - SEQUENCE_LOCKTIME_MASK, - SEQUENCE_LOCKTIME_GRANULARITY, -}; diff --git a/src/ctxin.js b/src/ctxin.js deleted file mode 100644 index 65ae0f9..0000000 --- a/src/ctxin.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Ported from Dash core - * Dash Core file: https://github.com/dashpay/dash/blob/master/src/primitives/transaction.h - * - * An input of a transaction. It contains the location of the previous - * transaction's output that it claims and a signature that matches the - * output's public key. - */ - -let COutPoint = require('./coutpoint.js'); -let CScript = require('./cscript.js'); -const { - SEQUENCE_FINAL, - SEQUENCE_LOCKTIME_DISABLE_FLAG, - SEQUENCE_LOCKTIME_TYPE_FLAG, - SEQUENCE_LOCKTIME_MASK, - SEQUENCE_LOCKTIME_GRANULARITY, -} = require('./ctxin-constants.js'); - -module.exports = { - constants: { - SEQUENCE_FINAL, - SEQUENCE_LOCKTIME_DISABLE_FLAG, - SEQUENCE_LOCKTIME_TYPE_FLAG, - SEQUENCE_LOCKTIME_MASK, - SEQUENCE_LOCKTIME_GRANULARITY, - }, - CTxIn, -}; - -//explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL); -//CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL); -function CTxIn( - args = { - hashPrevTx: null, - nOut: null, - prevoutIn: null, - scriptSigIn: null, - nSequenceIn: SEQUENCE_FINAL, - }, -) { - let self = this; - //console.debug({args}); - this.prevout = new COutPoint(); - this.scriptSig = new CScript(); - this.nSequence = SEQUENCE_FINAL; - this.constructorId = 0; - - /** - * Support for this constructor: - * CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn) - */ - if ( - null !== args.prevoutIn && - null !== args.scriptSigIn && - null !== args.nSequenceIn - ) { - this.constructorId = 2; - this.prevout = new COutPoint({ hashIn: args.prevoutIn }); - this.scriptSig = new CScript({ cscript: args.scriptSigIn }); - this.nSequence = args.nSequenceIn; - } - /** - * Support for this constructor: - CTxIn::CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn, uint32_t nSequenceIn) - */ - if ( - null !== args.hashPrevTx && - null !== args.nOut && - null !== args.scriptSigIn && - null !== args.nSequenceIn - ) { - this.constructorId = 3; - this.prevout = new COutPoint({ - hashIn: args.hashPrevTx, - nIn: args.nOut, - }); - this.scriptSig = new CScript({ cscript: args.scriptSigIn }); - this.nSequence = args.nSequenceIn; - } - this.compare = function (other) { - return ( - self.prevout.equals(other.prevout) && - self.scriptSig.equals(other.scriptSig) && - self.nSequence.equals(other.nSequence) - ); - }; - this.equals = this.compare; - - this.lessThan = function (other) { - return self.prevout.lessThan(other.prevout); - }; - this.ToString = function () { - let str; - str += 'CTxIn('; - str += self.prevout.ToString(); - if (self.prevout.IsNull()) { - str += ', coinbase ' + self.scriptSig.ToHexStr(); - } else { - str += ', scriptSig=' + self.scriptSig.ToHexStr().substr(0, 24); - } - if (self.nSequence != SEQUENCE_FINAL) { - str += ', nSequence=' + self.nSequence; - } - str += ')'; - return str; - }; -} diff --git a/src/ctxout.js b/src/ctxout.js deleted file mode 100644 index 18e3cf3..0000000 --- a/src/ctxout.js +++ /dev/null @@ -1,53 +0,0 @@ -/** An output of a transaction. It contains the public key that the next input - * must be able to sign with to claim it. - */ -//class CTxOut -let CScript = require('./cscript.js'); -function CTxOut( - args = { - nValueIn: null, - scriptPubKeyIn: null, - }, -) { - let self = this; - - this.nValue = 0; - this.scriptPubKey = new CScript(); - this.constructorId = 0; - - this.SetNull = function () { - self.nValue = -1; - self.scriptPubKey.clear(); - }; - - /** - * Support for default constructor: - * CTxOut() - */ - this.SetNull(); - this.constructorId = 1; - - /** - * Support for this constructor: - CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn); - */ - if (null !== args.nValueIn && null !== args.scriptPubKeyIn) { - this.constructorId = 2; - this.nValue = args.nValueIn; - this.scriptPubKey = new CScript(args.scriptPubKeyIn); - } - - this.IsNull = function () { - return self.nValue.equals(-1); - }; - - this.equals = function (b) { - let a = self; - return a.nValue.equals(b.nValue) && a.scriptPubKey.equals(b.scriptPubKey); - }; - - this.ToString = function () { - // TODO: this is just temporary - return JSON.stringify(self); - }; -} diff --git a/src/debug.js b/src/debug.js deleted file mode 100755 index e8835bf..0000000 --- a/src/debug.js +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -let nickName; - -function setNickname(n) { - nickName = n; -} - -function debug(...args) { - console.debug(`${nickName}:`, ...args); -} -function info(...args) { - console.info(`${nickName}[INFO]:`, ...args); -} -function error(...args) { - console.error(`[${nickName}[ERROR]:`, ...args); -} -function d(...args) { - debug(...args); -} -function dd(...args) { - debug(...args); - process.exit(); -} - -module.exports = { - setNickname, - debug, - info, - dd, - d, - error, -}; diff --git a/src/demo.js b/src/demo.js deleted file mode 100755 index 6f0475d..0000000 --- a/src/demo.js +++ /dev/null @@ -1,464 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const COIN = require('./coin-join-constants.js').COIN; -const Network = require('./network.js'); -//const NetworkUtil = require('./network-util.js'); -//const { hashByteOrder } = NetworkUtil; -const { ClientSession } = require('./client-session.js'); -const { xt } = require('@mentoc/xtract'); -const Util = require('./util.js'); -const SigScript = require('./sigscript.js'); -const DsiFactory = require('./dsi-factory.js'); -const DebugLib = require('./debug.js'); -const { debug, info, d } = DebugLib; -const { dd } = DebugLib; -const LibInput = require('./choose-inputs.js'); -const extractOption = require('./argv.js').extractOption; -const UserDetails = require('./bootstrap/user-details.js'); -const dashboot = require('./bootstrap/index.js'); -const FileLib = require('./file.js'); -const MasterNodeConnection = - require('./masternode-connection.js').MasterNodeConnection; -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -//const PrivateKey = DashCore.PrivateKey; -const Address = DashCore.Address; -const Signature = DashCore.crypto.Signature; -const Sanitizers = require('./sanitizers.js'); -const { sanitize_txid, sanitize_vout } = Sanitizers; - -//const ArrayUtils = require('./array-utils.js'); -//const random = ArrayUtils.random; -let done = false; -let dboot; -let network; -let sendDsi; -let username; -let INPUTS = 1; -let client_session; -let mainUser; -let masterNodeConnection; -function getDemoDenomination() { - return parseInt(COIN / 1000 + 1, 10); -} -//function getDenominatedOutput(txn, amount) { -// let vout = 0; -// for (let output of txn.outputs) { -// if (output._satoshis === parseInt(amount * COIN, 10)) { -// output.vout = vout; -// return output; -// } -// ++vout; -// } -// return null; -//} -async function onDSFMessage(parsed, masterNode) { - d('DSF message received'); - - client_session.dsf_parsed = parsed; - d(`submitted transactions: ${client_session.get_inputs().length}`); - d('Submitting DSS packet'); - await FileLib.write_json( - `dss-outputs-${client_session.username}-#DATE#`, - client_session, - ); - masterNode.client.write( - await Network.packet.coinjoin.dss({ - chosen_network: masterNode.network, - dsfPacket: parsed, - client_session, - dboot, - }), - ); - d('DSS sent'); -} -async function onDSSUChanged(parsed, masterNode) { - let msgId = parsed.message_id[1]; - let state = parsed.state[1]; - let update = parsed.status_update[1]; - if (msgId === 'MSG_NOERR') { - msgId = 'OKAY'; - } - d({ - username: masterNode.client_session.username, - nick: masterNode.nickName, - msgId, - state, - update, - }); - if (update === 'REJECTED') { - if (msgId === 'ERR_QUEUE_FULL') { - await masterNodeConnection.disconnect(function () { - done = true; - console.log('Closed connection'); - process.exit(0); - }); - } - } - if (msgId === 'ERR_INVALID_COLLATERAL') { - client_session.used_txids.push(masterNode.collateralTx.txid); - await dboot.mark_txid_used( - client_session.username, - masterNode.collateralTx.txid, - ); - debug(`marked collateral input: ${masterNode.collateralTx.txid} as used`); - debug('input: ', masterNode.collateralTx); - await masterNodeConnection.disconnect(function () { - done = true; - console.log('Closed connection'); - process.exit(0); - }); - } -} -async function onCollateralTxCreated(tx, masterNode) { - debug(`onCollateralTxCreated via masterNode: ${masterNode.id()}`); - await dboot.mark_txid_used(tx.user, tx.tx.txid); -} -async function stateChanged(obj) { - let dsaSent = false; - let self = obj.self; - let masterNode = self; - switch (masterNode.status) { - default: - break; - case 'CLOSED': - console.warn('[-] Connection closed'); - break; - case 'NEEDS_AUTH': - case 'EXPECT_VERACK': - case 'EXPECT_HCDP': - case 'RESPOND_VERACK': - break; - case 'READY': - if (dsaSent === false) { - self.denominationsAmount = getDemoDenomination(); - masterNode.client.write( - Network.packet.coinjoin.dsa({ - chosen_network: network, - denomination: getDemoDenomination(), - collateral: await masterNode.makeCollateralTx(), - }), - ); - dsaSent = true; - } - break; - case 'DSQ_RECEIVED': - { - if (self.dsq.fReady) { - debug('sending dsi'); - } else { - info('[-][COINJOIN] masternode not ready for dsi...'); - return; - } - if (String(sendDsi) === 'false') { - info('not sending dsi as per cli switch'); - return; - } - let packet = await DsiFactory.createDSIPacket( - masterNode, - username, - getDemoDenomination(), - INPUTS, - ); - masterNode.client.write(packet); - debug('sent dsi packet'); - } - break; - case 'EXPECT_DSQ': - break; - } -} -const AMOUNT = 0.00100001; -const SATOSHIS = parseInt(COIN * parseFloat(AMOUNT, 10), 10); -async function preInit( - _in_instanceName, - _in_username, - _in_nickname, - _in_count, - _in_send_dsi, - _in_verbose, - _in_mn_choice, -) { - let nickName = _in_username; - let id = {}; - let config = require('./.mn0-config.json'); - switch (_in_mn_choice) { - default: - case 'local_1': - config = require('./.mn0-config.json'); - id.mn = 0; - break; - case 'local_2': - config = require('./.mn1-config.json'); - id.mn = 1; - break; - case 'local_3': - config = require('./.mn2-config.json'); - id.mn = 2; - } - nickName += `(${_in_mn_choice})`; - DebugLib.setNickname(nickName); - if (String(_in_verbose).toLowerCase() === 'true') { - SigScript.setVerbosity(true); - } else { - SigScript.setVerbosity(false); - } - client_session = new ClientSession(); - sendDsi = _in_send_dsi; - - for (const i of Array.from(Array(10).keys())) { - console.log(`masternode chosen: ${id.mn} [${i}]`); - } - - let masterNodeIP = config.masterNodeIP; - let masterNodePort = config.masterNodePort; - network = config.network; - let ourIP = config.ourIP; - let startBlockHeight = config.startBlockHeight; - if (_in_count) { - INPUTS = parseInt(_in_count, 10); - } - if (isNaN(INPUTS)) { - throw new Error('--count must be a positive integer'); - } - if (INPUTS >= 253) { - throw new Error('--count currently only supports a max of 252'); - } - let instanceName = _in_instanceName; - username = _in_username; - dboot = await dashboot.load_instance(instanceName); - - /** - * Grab all unspent utxos - */ - let keep = []; - let utxos = await dboot.get_denominated_utxos(username, SATOSHIS); - let payeeAddress = await dboot.get_change_addresses(username); - let good = 0; - let bad = 0; - for (const u of utxos) { - /** - * 'u' looks like this: - address: 'yRFXBhHDkzZ7NM44tC8ZgbgP2Hm1dR4Li9', - txid: '010cef7befeb498eeac48080cf6263f76a9a8837ee4603c79aa9b01f4cc95638', - outputIndex: 156, - script: '76a914361a95ab2dd6be825e9aca6a54b6ccb2c6a09ee088ac', - satoshis: 100001, - height: 1846 - - */ - if (u.satoshis !== SATOSHIS) { - throw new Error('got weird satoshi value'); - } - let tx = await dboot.get_transaction(username, u.txid); - for (const detail of tx[u.txid].details) { - if (detail.category === 'receive') { - ++good; - d({ good }); - d({ address: detail.address }); - let address = Address.fromString(detail.address); - let utxo = { - txId: u.txid, - outputIndex: detail.vout, - satoshis: parseInt(parseFloat(detail.amount, 10) * COIN, 10), - scriptPubKey: Script.buildPublicKeyHashOut( - address, - Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY, - ), - sequence: 0xffffffff, - }; - let txn = new Transaction().from(utxo); - let pk = await dboot.get_private_key(username, detail.address); - txn.sign(pk, Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY); - d(txn.inputs[0]._script.toHex()); - keep.push({ - signed: txn, - private_key: pk, - from_get_transaction: tx, - payee: payeeAddress[0], - utxo, - }); - } else { - ++bad; - } - if (keep.length === INPUTS) { - break; - } - } - } - d({ good, bad, done: true }); - client_session.mixing_inputs = keep; - - mainUser = await UserDetails.extractUserDetails(username); - mainUser.user = username; - //dd({ mainUser }); - d('user details fetched'); - let randomPayeeName = await dboot.get_random_payee(username); - d('random payee fetched'); - let payee = await UserDetails.extractUserDetails(randomPayeeName); - d('payee fetched'); - - client_session.nickName = nickName; - client_session.instanceName = instanceName; - client_session.username = _in_username; - client_session.dboot = dboot; - client_session.mainUser = mainUser; - client_session.randomPayeeName = randomPayeeName; - client_session.payee = payee; - client_session.denominatedAmount = getDemoDenomination(); - - LibInput.initialize({ - dboot, - denominatedAmount: getDemoDenomination(), - client_session, - nickName, - }); - DsiFactory.initialize( - dboot, - _in_username, - nickName, - _in_count, - _in_send_dsi, - getDemoDenomination(), - client_session, - mainUser, - randomPayeeName, - payee, - ); - - //{ - // /** FIXME - // */ - // await psbt_main(dboot, client_session); - // process.exit(0); - //} - masterNodeConnection = new MasterNodeConnection({ - ip: masterNodeIP, - port: masterNodePort, - network, - ourIP, - startBlockHeight, - onCollateralTxCreated: onCollateralTxCreated, - onStatusChange: stateChanged, - onDSSU: onDSSUChanged, - onDSF: onDSFMessage, - debugFunction: null, - userAgent: config.userAgent ?? null, - coinJoinData: mainUser, - user: mainUser.user, - payee, - changeAddresses: mainUser.changeAddresses, - nickName, - client_session, - }); - debug('Connecting...'); - await masterNodeConnection.connect(); - debug('connected.'); - while (!done) { - await Util.sleep_ms(1500); - } - debug('exiting main function'); -} -preInit( - extractOption('instance', true), - extractOption('username', true), - extractOption('nickname', true), - extractOption('count', true), - extractOption('senddsi', true), - extractOption('verbose', true), - extractOption('mn', true), -); -async function psbt_main(dboot, client_session) { - const quota = 3; - const SAT = 0.00109991; - let cs = client_session; - let wallet_exec = await dboot.auto.build_executor(cs); - /** - * 1) Get unspent - */ - let addresses = {}; - let address = await dboot.nth_address(cs, 1); - let unspent = await dboot.list_unspent_by_address(cs, address, { - minimumAmount: SAT, - maximumAmount: 0.002, - }); - for (const tx of unspent) { - if (tx.amount === SAT) { - if (typeof addresses[tx.address] === 'undefined') { - addresses[tx.address] = []; - } - addresses[tx.address].push(tx); - } - } - let chosen = []; - for (const address in addresses) { - if (addresses[address].length >= quota) { - for (let i = 0; i < quota; i++) { - chosen.push(addresses[address][i]); - } - } - if (chosen.length === quota) { - break; - } - } - if (chosen.length !== quota) { - throw new Error('unable to fill quota'); - } - /** - * 2) Create quota inputs - */ - let payee = await dboot.generate_address(cs, quota); - let json = []; - for (const tx of chosen) { - json.push({ - txid: sanitize_txid(tx.txid), - vout: sanitize_vout(tx.vout), - }); - } - cs.privateKey = await dboot.get_private_key(cs, chosen[0].address); - let inputs = JSON.stringify(json); - let out_json = []; - cs.payee = payee; - cs.payouts = []; - for (const address of payee) { - out_json.push({ [address]: 0.00100001 }); - cs.payouts.push([address, 0.00100001]); - } - let outputs = JSON.stringify(out_json); - let { out, err } = await wallet_exec('createpsbt', inputs, outputs); - if (err.length) { - throw new Error(err); - } - - let output = await wallet_exec( - 'walletprocesspsbt', - out, - 'true', - 'ALL|ANYONECANPAY', - ); - let hex = null; - try { - let decoded = JSON.parse(output.out); - if (xt(decoded, 'psbt')) { - let psbt = xt(decoded, 'psbt'); - output = await dboot.finalize_psbt(cs, psbt); - if (xt(output, 'hex')) { - hex = output.hex; - } - } - } catch (e) { - throw new Error(e); - } - if (hex) { - output = await dboot.decode_raw_transaction(cs, hex); - d(output); - cs.mixing_inputs = output; - output = await dboot.send_raw_transaction(cs, hex); - dd(output); - } - - /** - * 3) pass to cli - */ -} diff --git a/src/demodata.js b/src/demodata.js deleted file mode 100644 index f744def..0000000 --- a/src/demodata.js +++ /dev/null @@ -1,252 +0,0 @@ -'use strict'; -const COIN = require('./coin-join-constants.js').COIN; - -let Lib = {}; -module.exports = Lib; - -let config = require('./.config.json'); -let NETWORK = config.network; - -let DashCore = require('@dashevo/dashcore-lib'); -let Transaction = DashCore.Transaction; -let Script = DashCore.Script; -let PrivateKey = DashCore.PrivateKey; -let Address = DashCore.Address; -let { hexToBytes } = require('./network-util.js'); -const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const fs = require('fs'); -const { read_file, logUsedTransaction, isUsed } = require('./ctransaction.js'); -let user = 'psend'; -Lib.initialize = function (uname) { - user = uname; -}; -Lib.util = {}; -Lib.util.fetchData = async function () { - let data = await fetchData(); - return { - usedTxnFileName: data.PsendUsedTxnFile, - txnList: data.PsendTxnList, - changeAddress: data.PsendChangeAddress, - sourceAddress: data.sourceAddress, - payeeAddress: data.payeeAddress, - privateKeySet: data.privkeySet, - }; -}; -/** - * @param denomination - integer - one of `CoinJoinDenominations.GetStandardDenominations()` - * @return Array of transactions - */ -Lib.getMultipleUnusedTransactions = async function (count) { - let txns = []; - for (let i = 0; i < count; i++) { - txns.push(await getUnusedTxn()); - } - return txns; -}; -Lib.getMultipleUnusedTransactionsFilter = async function (count, properties) { - let txns = await Lib.getMultipleUnusedTransactions(count); - let finalTxns = []; - for (let txn of txns) { - let obj = {}; - for (let prop of properties) { - obj[prop] = txn[prop]; - } - finalTxns.push(obj); - obj = {}; - } - return finalTxns; -}; - -async function getUnusedTxn() { - let { PsendTxnList, sourceAddress, PsendUsedTxnFile } = await fetchData(); - for (let txn of PsendTxnList) { - /** - * Pull from PsendTxnList where: - * 1) category is 'generate'. - * 2) has more than zero confirmations - * 3) where address matches w-psend-address-0 - * 4) txid does NOT exist in ./data/w-psend-used-txn.json - */ - if (txn.category === 'receive' || txn.category === 'send') { - continue; - } - if (txn.confirmations === 0) { - continue; - } - if (txn.address !== sourceAddress) { - continue; - } - if (await isUsed(PsendUsedTxnFile, txn.txid)) { - continue; - } - return txn; - } - return null; -} -Lib.logUsedTransaction = async function (txnId) { - let fileName = await fetchData(); - fileName = fileName.PsendUsedTxnFile; - let buffer = await fs.readFileSync(fileName); - buffer = buffer.toString(); - let data = JSON.parse(buffer); - data.list.push(txnId); - await fs.writeFileSync(fileName, JSON.stringify(data, null, 2)); -}; - -function dd(f) { - console.debug(f); - process.exit(); -} - -function sanitizePrivateKey(p) { - if (p === null) { - throw new Error('Private key is null'); - } - return String(p).replace(/[^a-zA-Z0-9]+/gi, ''); -} -/** - * Returns { - * txid, - * vout, - * sourceAddress, - * satoshis, - * privateKey, - * changeAddress, - * payeeAddress, - * } - */ -Lib.getUnusedTransaction = async function () { - let data = await fetchData(); - let found = false; - let sourceAddress = null; - let privateKey = null; - - for (let address in data.denominations) { - for (let row of data.denominations[address].transactions) { - if (await isUsed(data.PsendUsedTxnFile, row.txid)) { - continue; - } - found = row; - sourceAddress = address; - privateKey = PrivateKey( - sanitizePrivateKey(data.denominations[address].privateKey), - ); - break; - } - if (found) { - break; - } - } - if (!found) { - throw new Error("Couldn't find an unused transaction"); - } - if (!sourceAddress) { - throw new Error("Couldn't find source address associated with transaction"); - } - if (!privateKey) { - throw new Error('Private Key not found'); - } - console.debug({ found }); - await logUsedTransaction(data.PsendUsedTxnFile, found.txid); - return { - txid: found.txid, - sourceAddress: Address(sourceAddress, NETWORK), - vout: parseInt(found.vout, 10), - satoshis: parseInt(found.amount * COIN, 10), - privateKey, - changeAddress: data.PsendChangeAddress, - payeeAddress: data.payeeAddress, - }; -}; -Lib.makeDSICollateralTx = async function () { - let PsendTx = await Lib.getUnusedTransaction(); - - if (PsendTx === null) { - throw new Error('Couldnt find unused transaction'); - } - - let amount = parseInt(LOW_COLLATERAL * 2, 10); - let fee = 50000; // FIXME - let { - payeeAddress, - sourceAddress, - txid, - vout, - satoshis, - changeAddress, - privateKey, - } = PsendTx; - let unspent = satoshis - amount; - let utxos = { - txId: txid, - outputIndex: vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis, - }; - var tx = new Transaction() - .from(utxos) - .to(payeeAddress, amount) - .to(changeAddress, unspent - fee) - .sign(privateKey); - return tx; - //return hexToBytes(tx.uncheckedSerialize()); -}; - -Lib.getPrivateKeyForAddress = async function (address) {}; - -Lib.makeCollateralTx = async function () { - let PsendTx = await Lib.getUnusedTransaction(); - - if (PsendTx === null) { - throw new Error('Couldnt find unused transaction'); - } - - let amount = parseInt(LOW_COLLATERAL * 2, 10); - let fee = 50000; // FIXME - let { - payeeAddress, - sourceAddress, - txid, - vout, - satoshis, - changeAddress, - privateKey, - } = PsendTx; - let unspent = satoshis - amount; - let utxos = { - txId: txid, - outputIndex: vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis, - }; - var tx = new Transaction() - .from(utxos) - .to(payeeAddress, amount) - .to(changeAddress, unspent - fee) - .sign(privateKey); - return hexToBytes(tx.uncheckedSerialize()); -}; -Lib.LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -async function fetchData() { - let files = require(`../${user}-config.demodata.json`); - let denominations = require(`../data/w-${user}-denominations.json`); - let PsendUsedTxnFile = files.usedTxn; - let PsendTxnList = require(files.txnList); - let PsendChangeAddress = await read_file(files.changeAddress); - let sourceAddress = await read_file(files.sourceAddress); - let payeeAddress = await read_file(files.payeeAddress); - let privkeySet = PrivateKey( - PrivateKey.fromWIF(await read_file(files.wif), NETWORK), - ); - return { - denominations, - PsendUsedTxnFile, - PsendTxnList, - PsendChangeAddress, - sourceAddress, - payeeAddress, - privkeySet, - }; -} diff --git a/src/denominations.js b/src/denominations.js deleted file mode 100644 index 0788ac1..0000000 --- a/src/denominations.js +++ /dev/null @@ -1,390 +0,0 @@ -let Lib = {}; -const ONLY_READY_TO_MIX = ''; -const { MAX_MONEY } = require('./coin.js'); -const CoinType = require('./cointype-constants.js'); - -const CCoinJoin = require('./coin-join.js'); -const { SEQUENCE_FINAL } = require('./ctxin-constants.js'); -const Vector = require('./vector.js'); -const COutPoint = require('./coutpoint.js'); -const COutput = require('./coutput.js'); -const CCoinControl = require('./coincontrol.js'); -const CCoinJoinDenominations = require('./coin-join-denominations.js'); -const LOCKTIME_THRESHOLD = 500000000; -const LOCKTIME_MEDIAN_TIME_PAST = 1 << 1; -const { COIN } = require('./coin.js'); -const { SER_GETHASH, PROTOCOL_VERSION } = require('./protocol-constants.js'); -Lib.constants = CoinType; -module.exports = Lib; - -//orig: bool CWallet::SelectDenominatedAmounts(CAmount nValueMax, std::set& setAmountsRet) const { -/** - * This function will have to return an object that - * will contain a boolean result and the setAmountsRet - * reference variable. - * - * In short, because C++ allows you to pass by reference, - * we cannot accept the second parameter like how core does. - * - * Should return: - * { - * valid: true|false, - * setAmountsRet: {} // a set emulating std::set - * } - * - * Requirements: - * wallet - needs to have a function defined on it called GetSpendableTXs(options). - * Where 'options' is an object: - * { - onlySafe: boolean, - minDepth: int, - maxDepth: int, - isMatureCoinBase: boolean, - coinType: any value from 'cointype-constants.js' i.e.: ALL_COINS, - minAmount: int, - maxAmount: int, - allowUsed: boolean, - } - * - */ -Lib.SelectDenominatedAmounts = function (nValueMax, wallet) { - let setAmountsRet = {}; - let nValueTotal = 0; - let coinControl = new CCoinControl({ - wallet, - }); - let { vCoins } = Lib.AvailableCoins({ - wallet, - fOnlySafe: true, - coinControl, - }); - // larger denoms first - vCoins = Lib.sortByLargestDenoms(vCoins); - - for (const out of vCoins.contents) { - let nValue = out.tx.tx.vout[out.i].nValue; - if (nValueTotal + nValue <= nValueMax) { - nValueTotal += nValue; - setAmountsRet[nValue] = 1; - } - } - - return { - valid: nValueTotal >= CCoinJoinDenominations.GetSmallestDenomination(), - setAmountsRet, - }; -}; - -Lib.CalculateAmountPriority = function (nInputAmount) { - for (const denom of CCoinJoinDenominations.GetStandardDenominations()) { - if (nInputAmount === denom) { - return (COIN / denom) * 10000; - } - } - if (nInputAmount < COIN) { - return 20000; - } - - //nondenom return largest first - return -1 * (nInputAmount / COIN); -}; -Lib.sortByLargestDenoms = function (vCoins) { - //(const COutput& t1, const COutput& t2) const { - vCoins.contents.sort(function (a, b) { - let aval = Lib.CalculateAmountPriority(a.GetInputCoin().effective_value); - let bval = Lib.CalculateAmountPriority(b.GetInputCoin().effective_value); - if (aval < bval) { - return -1; - } - if (aval > bval) { - return 1; - } - return 0; - }); -}; - -//orig: bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) -Lib.IsFinalTx = function (tx, nBlockHeight, nBlockTime) { - if (tx.nLockTime == 0) { - return true; - } - if ( - tx.nLockTime < - (tx.nLockTime < LOCKTIME_THRESHOLD ? nBlockHeight : nBlockTime) - ) { - return true; - } - - // Even if tx.nLockTime isn't satisfied by nBlockHeight/nBlockTime, a - // transaction is still considered final if all inputs' nSequence == - // SEQUENCE_FINAL (0xffffffff), in which case nLockTime is ignored. - // - // Because of this behavior OP_CHECKLOCKTIMEVERIFY/CheckLockTime() will - // also check that the spending input's nSequence != SEQUENCE_FINAL, - // ensuring that an unsatisfied nLockTime value will actually cause - // IsFinalTx() to return false here: - for (const txin of tx.vin) { - if (!(txin.nSequence === SEQUENCE_FINAL)) { - return false; - } - } - return true; -}; -/** - * This should essentially do what chain().checkFinalTx() does in - * src/validation.cpp - */ -//orig: bool CheckFinalTx(const CTransaction &tx, int flags) -Lib.checkFinalTx = function (wallet, tx) { - // By convention a negative value for flags indicates that the - // current network-enforced consensus rules should be used. In - // a future soft-fork scenario that would mean checking which - // rules would be enforced for the next block and setting the - // appropriate flags. At the present time no soft-forks are - // scheduled, so no flags are set. - wallet.flags = Lib.max(wallet.flags, 0); - - // CheckFinalTx() uses ::ChainActive().Height()+1 to evaluate - // nLockTime because when IsFinalTx() is called within - // CBlock::AcceptBlock(), the height of the block *being* - // evaluated is what is used. Thus if we want to know if a - // transaction can be part of the *next* block, we need to call - // IsFinalTx() with one more than ::ChainActive().Height(). - const /*int*/ nBlockHeight = Lib.ChainActive().Height() + 1; // TODO: ChainActive().Height() - - // BIP113 requires that time-locked transactions have nLockTime set to - // less than the median time of the previous block they're contained in. - // When the next block is created its previous block will be the current - // chain tip, so we use that to calculate the median time passed to - // IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set. - const /*int64_t*/ nBlockTime = - flags & LOCKTIME_MEDIAN_TIME_PAST - ? Lib.ChainActive().Tip().GetMedianTimePast() // TODO: ChainActive().Tip().GetMedianTimePast() - : Lib.GetAdjustedTime(); // TODO: GetAdjustedTime() - - return Lib.IsFinalTx(tx, nBlockHeight, nBlockTime); // TODO: IsFinalTx -}; - -Lib.GetBlocksToMaturity = function (coin) { - // TODO: - //if(!Lib.IsCoinBase(coin)) { - // return false; - //} - //let chain_depth = Lib.GetDepthInMainChain(); - //return Lib.max(0, (COINBASE_MATURITY+1) - chain_depth); -}; -Lib.IsImmatureCoinBase = function (coin) { - // note GetBlocksToMaturity is 0 for non-coinbase tx - //TODO: return Lib.GetBlocksToMaturity(coin) > 0; - return false; // FIXME -}; -Lib.GetDepthInMainChain = function (coin) {}; -/** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds. - * This salt is needed to prevent an attacker from learning how many extra times - * the input was mixed based only on information in the blockchain. - */ -//uint256 nCoinJoinSalt; -Lib.getCoinJoinSalt = function (wallet) { - /** TODO: */ -}; -//orig: bool CWallet::IsFullyMixed(const COutPoint& outpoint) const { -Lib.IsFullyMixed = function (wallet, outpoint) { - let nRounds = GetRealOutpointCoinJoinRounds(outpoint); - // Mix again if we don't have N rounds yet - if (nRounds < wallet.CCoinJoinClientOptions().GetRounds()) { - // TODO: make sure this wallet function is documented correctly - return false; - } - - // Try to mix a "random" number of rounds more than minimum. - // If we have already mixed N + MaxOffset rounds, don't mix again. - // Otherwise, we should mix again 50% of the time, this results in an exponential decay - // N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop. - if ( - nRounds < - wallet.CCoinJoinClientOptions().GetRounds() + - wallet.CCoinJoinClientOptions().GetRandomRounds() - ) { - // TODO: make sure these wallet functions are documented correctly - //CDataStream ss(SER_GETHASH, PROTOCOL_VERSION); - //ss << outpoint << nCoinJoinSalt; - //uint256 nHash; - let ss = new CDataStream(SER_GETHASH, PROTOCOL_VERSION); - // This is how the '<<' operator is defined in streams.h - // for the CDataStream type. It's a write operation. It - // will take the parameters and write them to the stream's - // internal vch vector. - //template - //CDataStream& operator<<(const T& obj) - //{ - // // Serialize to this stream - // ::Serialize(*this, obj); - // return (*this); - //} - ss.write(outpoint); - ss.write(Lib.getCoinJoinSalt(wallet)); - let nHash; - // TODO: need CSHA256() - // TODO: need CSHA256().Write(stream) - // TODO: need CSHA256().Write(stream).Finalize(hash) - CSHA256().Write(ss).Finalize(nHash); - if (ReadLE64(nHash) % 2 == 0) { - // TODO: need ReadLE64 - return false; - } - } - - return true; -}; -//orig: void CWallet::AvailableCoins( -//std::vector& vCoins, -//bool fOnlySafe, -//const CCoinControl *coinControl, -//const CAmount& nMinimumAmount, -//const CAmount& nMaximumAmount, -//const CAmount& nMinimumSumAmount, -//const uint64_t nMaximumCount, -//const int nMinDepth, -//const int nMaxDepth) const { -Lib.AvailableCoins = function ({ - wallet, - coinControl, - fOnlySafe = true, - nMinimumAmount = 1, - nMaximumAmount = MAX_MONEY, - nMinimumSumAmount = MAX_MONEY, - nMaximumCount = 0, - nMinDepth = 0, - nMaxDepth = 9999999, - allow_used_addresses = false, -}) { - let ret = { - vCoins: new Vector(COutput), - }; - - let nCoinType = coinControl.nCoinType; - - let nTotal = 0; - // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where - // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses - //let allow_used_addresses = !Lib.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl.m_avoid_address_reuse); - - /** - * At some point, we may have to write GetSpendableTXs() and bring - * that logic into this library. As of right now, it's fine to - * leave this up to the wallet impelementation. - */ - for (let pcoin of wallet.GetSpendableTXs({ - onlySafe: fOnlySafe, - minDepth: nMinDepth, - maxDepth: nMaxDepth, - isMatureCoinBase: true, - coinType: nCoinType, - minAmount: nMinimumAmount, - maxAmount: nMaximumAmount, - allowUsed: allow_used_addresses, - })) { - let wtxid = Lib.GetCoinHash(pcoin); // TODO: implement GetCoinHash(pcoin) - - let vOuts = Lib.GetVoutsFromTx(pcoin.tx); // TODO: implement GetVoutsFromTx - for (let i = 0; i < vOuts.length; i++) { - let found = false; - let nValue = vOuts[i].nValue; - if (nCoinType == CoinType.ONLY_FULLY_MIXED) { - if (!CCoinJoinDenominations.IsDenominatedAmount(nValue)) { - continue; - } - found = Lib.IsFullyMixed(new COutPoint({ hashIn: wtxid, nIn: i })); - } else if (nCoinType == CoinType.ONLY_READY_TO_MIX) { - if (!CCoinJoinDenominations.IsDenominatedAmount(nValue)) { - continue; - } - found = !Lib.IsFullyMixed(new COutPoint({ hashIn: wtxid, nIn: i })); - } else if (nCoinType == CoinType.ONLY_NONDENOMINATED) { - if (CCoinJoinDenominations.IsCollateralAmount(nValue)) { - continue; // do not use collateral amounts - } - found = !CCoinJoinDenominations.IsDenominatedAmount(nValue); - } else if (nCoinType == CoinType.ONLY_MASTERNODE_COLLATERAL) { - found = dmn_types.IsCollateralAmount(nValue); - } else if (nCoinType == CoinType.ONLY_COINJOIN_COLLATERAL) { - found = CCoinJoin.IsCollateralAmount(nValue); - } else { - found = true; - } - if (!found) { - continue; - } - - if (nValue < nMinimumAmount || nValue > nMaximumAmount) { - continue; - } - - if ( - coinControl.HasSelected() && - !coinControl.fAllowOtherInputs && - !coinControl.IsSelected(new COutPoint({ hashIn: wtxid, nIn: i })) - ) { - continue; - } - - if ( - Lib.IsLockedCoin(wtxid, i) && // TODO: Implement IsLockedCoin - nCoinType != CoinType.ONLY_MASTERNODE_COLLATERAL - ) { - continue; - } - - if (Lib.IsSpent(wtxid, i)) { - // TODO: implement IsSpent - continue; - } - - let mine = Lib.IsMine(vOuts[i]); // TODO: implement IsMine - - if (mine == ISMINE_NO) { - // TODO: deinf ISMINE_NO - continue; - } - - // TODO: complete this - //let provider = Lib.GetSigningProvider(pcoin.tx.vout[i].scriptPubKey); - //let solvable = provider - // ? Lib.IsSolvable(provider, pcoin.tx.vout[i].scriptPubKey) - // : false; - //let spendable = - // (mine & ISMINE_SPENDABLE) != ISMINE_NO || - // ((mine & ISMINE_WATCH_ONLY) != ISMINE_NO && - // coinControl && - // coinControl.fAllowWatchOnly && - // solvable); - - //TODO: let nDepth = Lib.GetDepthInMainChain(pcoin); - let nDepth = 0; - ret.vCoins.push_back({ - tx: pcoin, - i, - nDepth, - fSpendable: spendable, - fSolvable: solvable, - fSafe: safeTx, - use_max_sig: coinControl.fAllowWatchOnly, - }); - - // Checks the sum amount of all UTXO's. - if (nMinimumSumAmount != MAX_MONEY) { - nTotal += vOuts[i].nValue; - - if (nTotal >= nMinimumSumAmount) { - return ret; - } - } - - // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && ret.vCoins.size() >= nMaximumCount) { - return ret; - } - } - } - return ret; -}; diff --git a/src/dsf-inspect.js b/src/dsf-inspect.js deleted file mode 100755 index 158dfbc..0000000 --- a/src/dsf-inspect.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; -const Network = require('./network.js'); -const xt = require('@mentoc/xtract').xt; - -let LibDsfInput = {}; - -module.exports = LibDsfInput; - -LibDsfInput.dump_parsed = function dump_parsed(parsed) { - console.log('sessionID', xt(parsed, 'sessionID')); - console.log('transaction: { '); - console.log('version: ', xt(parsed, 'transaction.version'), ','); - console.log('inputCount: ', xt(parsed, 'transaction.inputCount'), ','); - process.stdout.write('inputs: ['); - const inputs = xt(parsed, 'transaction.inputs'); - for (let i = 0; i < inputs.length; i++) { - process.stdout.write(bigint_safe_json_stringify(inputs[i], 2)); - if (i + 1 !== inputs.length) { - console.log(','); - } - } - console.log('],'); - console.log('outputCount: ', xt(parsed, 'transaction.outputCount'), ','); - process.stdout.write('outputs: ['); - const outputs = xt(parsed, 'transaction.outputs'); - for (let i = 0; i < outputs.length; i++) { - process.stdout.write('{'); - console.log('duffs: ', xt(outputs, `${i}.duffs`), ','); - console.log( - 'pubkey_script_bytes: ', - xt(outputs, `${i}.pubkey_script_bytes`), - ',', - ); - console.log('pubkey_script: ['); - Network.util.dumpAsHex(xt(outputs, `${i}.pubkey_script`)); - process.stdout.write(']}'); - if (i + 1 !== outputs.length) { - console.log(','); - } - } - console.log('],'); -}; diff --git a/src/dsi-factory.js b/src/dsi-factory.js deleted file mode 100755 index e82703e..0000000 --- a/src/dsi-factory.js +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const COIN = require('./coin-join-constants.js').COIN; -//const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const Network = require('./network.js'); -const Util = require('./util.js'); -const DebugLib = require('./debug.js'); -const { debug, d, dd } = DebugLib; -const Sanitizers = require('./sanitizers.js'); -const { sanitize_address } = Sanitizers; - -const assert = require('assert'); -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -const Signature = DashCore.crypto.Signature; -const fs = require('fs'); -const { bigint_safe_json_stringify } = require('./array-utils.js'); - -let client_session; -function setClientSession(c) { - client_session = c; -} -let dboot; - -async function makeDSICollateralTx(masterNode, client_session) { - return masterNode.makeCollateralTx({ no_serialize: true }); -} -async function createDSIPacket(masterNode, username, denominatedAmount, count) { - let collateralTxn = await makeDSICollateralTx(masterNode, client_session); - assert.equal( - client_session.mixing_inputs.length, - count, - 'mixing inputs should equal count', - ); - debug({ inputs: client_session.mixing_inputs }); - - client_session.generated_addresses = await dboot.generate_new_addresses( - client_session.username, - count, - ); - { - let other = []; - for (let i = 0; i < client_session.generated_addresses.length; i++) { - let obj = { - privateKey: await dboot.get_private_key( - client_session.username, - client_session.generated_addresses[i], - ), - address: client_session.generated_addresses[i], - }; - other.push(obj); - } - client_session.generated_addresses = other; - } - if (await Util.dataDirExists()) { - let lmdb_counter = await dboot.increment_key(username, 'dsfcounter'); - await fs.writeFileSync( - `${Util.getDataDir()}/dsf-mixing-inputs-${username}-${lmdb_counter}.json`, - bigint_safe_json_stringify(client_session, null, 2), - ); - } - return Network.packet.coinjoin.dsi({ - chosen_network: masterNode.network, - collateralTxn, - client_session, - denominatedAmount, - }); -} -async function initialize( - _in_dboot, - _in_username, - _in_nickname, - _in_count, - _in_send_dsi, - _in_denominated_amount, - _in_client_session, -) { - debug(`${_in_username},${_in_nickname},${_in_count},${_in_send_dsi}`); - DebugLib.setNickname(_in_nickname); - dboot = _in_dboot; - client_session = _in_client_session; - setClientSession(client_session); -} - -module.exports = { - setClientSession, - makeDSICollateralTx, - createDSIPacket, - initialize, -}; diff --git a/src/example-dsf.js b/src/example-dsf.js deleted file mode 100644 index ea5ed31..0000000 --- a/src/example-dsf.js +++ /dev/null @@ -1,15 +0,0 @@ -let Lib = {}; -module.exports = Lib; -Lib.example = new Uint8Array([ - 252, 193, 183, 220, 100, 115, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 0, 0, 0, - 104, 36, 112, 2, 58, 90, 3, 0, 2, 0, 0, 0, 2, 162, 94, 55, 70, 71, 67, 9, 38, - 24, 37, 158, 24, 167, 66, 102, 117, 133, 66, 133, 171, 153, 128, 79, 215, 80, - 55, 169, 109, 194, 90, 137, 124, 0, 0, 0, 0, 0, 255, 255, 255, 255, 63, 31, - 53, 214, 219, 204, 138, 156, 45, 19, 157, 204, 198, 73, 206, 170, 61, 74, 91, - 231, 56, 94, 3, 156, 161, 174, 203, 154, 44, 47, 146, 143, 0, 0, 0, 0, 0, 255, - 255, 255, 255, 2, 161, 134, 1, 0, 0, 0, 0, 0, 25, 118, 169, 20, 59, 81, 158, - 17, 86, 244, 180, 166, 106, 13, 29, 130, 35, 104, 29, 124, 40, 111, 167, 108, - 136, 172, 161, 134, 1, 0, 0, 0, 0, 0, 25, 118, 169, 20, 99, 252, 143, 250, - 158, 82, 144, 137, 235, 103, 221, 175, 28, 32, 71, 161, 153, 64, 57, 102, 136, - 172, 0, 0, 0, 0, -]); diff --git a/src/examples/paged-lmdb-storage.js b/src/examples/paged-lmdb-storage.js deleted file mode 100755 index f01201c..0000000 --- a/src/examples/paged-lmdb-storage.js +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env node -'use strict'; -//const { xt } = require('@mentoc/xtract'); -const MetaDB = require('../bootstrap/metadb.js'); -const DB = require('../lmdb/lmdb.js'); -const mdb = new MetaDB(DB); -const fs = require('fs'); -const { extractOption } = require('../argv.js'); - -(async function () { - const db_path = `${process.env.HOME}/db2`; - let exists = await fs.existsSync(db_path.replace(/\/$/, '') + '/data.mdb'); - DB.open({ - path: db_path, - db_name: 'foobar', - create: !exists, - maxDbs: 10, - mapSize: 2 * 1024 * 1024 * 1024, - }); - async function create_data() { - let values = []; - /** - * I have a thousand values that I want to store in LMDB. - * As page 1 fills up with 250 items (the default), page 2 - * gets created and then the values go from 250 to 499 since - * it's inclusive. This continues up until 1000. - */ - for (let i = 0; i < 1000; i++) { - values.push(i); - } - /** - * IF we wanted to, we could override the default 250 per - * page limit and call mdb.paged_store like so: - const PER_PAGE = 512; - await mdb.paged_store('user1','utxos',values,PER_PAGE); - */ - await mdb.paged_store('user1', 'utxos', values); - /** - * We can grab the page information which will give us - * metadata like how many pages are in this particular - * paged lmdb key. - */ - } - if (extractOption('store')) { - await create_data(); - } - let page = await mdb.pages('user1', 'utxos'); - console.log({ page }); - /** - * Above will output something like: - * - { - page: { - pages: 27, - template: 'user1|utxos|page|27', - items_per_page: 250 - } - } - */ - //for (let i = 1; i <= page.pages; i++) { - // let stored = await mdb.paged_get('user1', 'utxos', i); - // console.debug({ - // page: i, - // type: typeof stored, - // }); - //} - /** - * An alternate way to do something for every page - */ - await mdb.paged_for_each( - 'user1', - 'utxos', - { include_meta: true }, - async function (rows, meta) { - console.log(`Current page: ${meta.page}`); - if (!Array.isArray(rows)) { - return true; // returning true means keep looping - } - for (const value of rows) { - process.stdout.write(`${value}\t`); - if (value === 42) { - console.log('Found!'); - return false; // returning false means stop looping - } - } - }, - ); -})(); diff --git a/src/file.js b/src/file.js deleted file mode 100644 index e1707b5..0000000 --- a/src/file.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; -const { xt } = require('@mentoc/xtract'); -const assert = require('assert'); -const fs = require('node:fs'); -const { bigint_safe_json_stringify } = require('./array-utils.js'); - -let Lib = {}; -module.exports = Lib; -function date() { - const d = new Date(); - let h = d.getHours(); - if (String(h).length === 1) { - h = `0${h}`; - } - let m = d.getMinutes(); - if (String(m).length === 1) { - m = `0${m}`; - } - let s = d.getSeconds(); - if (String(s).length === 1) { - s = `0${s}`; - } - return ( - [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') + - [h, m, s].join(':') - ); -} -async function write_json(rel_path, data) { - let fn = `${process.env.HOME}/data/`; - rel_path = rel_path.replace('#DATE#', date()); - rel_path = rel_path.replace(/[^a-z0-9_-]+/gi, ''); - fn += rel_path + '.json'; - return await fs.writeFileSync(fn, bigint_safe_json_stringify(data, 2) + '\n'); -} - -Lib.write_json = write_json; diff --git a/src/foo.js b/src/foo.js deleted file mode 100755 index a7e8aa1..0000000 --- a/src/foo.js +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const { xt } = require('@mentoc/xtract'); -const Util = require('./util.js'); -const dashboot = require('./bootstrap/index.js'); -const SigScript = require('./sigscript.js'); -const { dd, d } = require('./debug.js'); -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -const MetaDB = require('./bootstrap/metadb.js'); -const DB = require('./lmdb/lmdb.js'); -const mdb = new MetaDB(DB); -const fs = require('node:fs'); -const { extractOption } = require('./argv.js'); -const ArrayUtils = require('./array-utils.js'); -const { ps_extract } = ArrayUtils; -let Address = DashCore.Address; -let PrivateKey = DashCore.PrivateKey; -let Signature = DashCore.crypto.Signature; -let assert = require('assert'); -const NetworkUtil = require('./network-util.js'); -const { hexToBytes, hashByteOrder } = NetworkUtil; -let cproc = require('child_process'); - -(async function () { - let username = 'user1'; - let dboot = await dashboot.load_instance('base1'); - dboot.DASH_CLI = [process.env.HOME, 'bin', 'dash-cli'].join('/'); - let inputs = require('../data/dss-outputs-fde664227d6541a4abf0d6da6f8832db-2023-7-108:56:45.json'); - //inputs = await dboot.get_denominated_utxos(username, 100001); - inputs = [ - { - address: 'yPK8VVu6V4NGctZcraSgnWS36GnyvXfWHR', - txid: 'd446dd9db51cb8fec9d34aa9a9ab7492f2e38eba431a0ad523cfcb34bad3b01f', - outputIndex: 0, - script: '76a91420d90dc122f556f153dcfb67b6d3d5b3ece6ede488ac', - satoshis: 100001, - height: 3471, - privateKey: 'cW9Yk5V62HKDtP2NxuZoe2NJzMpZcEPU3ouNSxMEvGYTjL3Q1tjE', - }, - { - address: 'yPK8VVu6V4NGctZcraSgnWS36GnyvXfWHR', - txid: '958314a80d578738872eee19cca143393f8994091e943f79e05940b6b02d0a1b', - outputIndex: 0, - script: '76a91420d90dc122f556f153dcfb67b6d3d5b3ece6ede488ac', - satoshis: 100001, - height: 3472, - privateKey: 'cW9Yk5V62HKDtP2NxuZoe2NJzMpZcEPU3ouNSxMEvGYTjL3Q1tjE', - }, - { - address: 'yPK8VVu6V4NGctZcraSgnWS36GnyvXfWHR', - txid: 'a69934a9bf5b625f293ca6bee4b33aa47a3eeb29ddb5c6bfb5b21bf51ad6df37', - outputIndex: 0, - script: '76a91420d90dc122f556f153dcfb67b6d3d5b3ece6ede488ac', - satoshis: 100001, - height: 3472, - privateKey: 'cW9Yk5V62HKDtP2NxuZoe2NJzMpZcEPU3ouNSxMEvGYTjL3Q1tjE', - }, - ]; - for (let input of inputs) { - input.privateKey = await dboot.get_private_key(username, input.address); - input.publicKey = new PrivateKey(input.privateKey).publicKey; - } - inputs = inputs.splice(0, 3); - const USER_INPUT_SIZE = inputs.length; - let client_session = { - mixing_inputs: inputs, - generated_addresses: await dboot.generate_new_addresses( - username, - inputs.length, - ), - }; - - let signatures = []; - //d({ inputs, client_session }); - /** - * The input count byte - */ - let TOTAL_SIZE = 1; // TODO: support compactSize - const TXID_LENGTH = 32; - const OUTPUT_INDEX_LENGTH = 4; - const SEQUENCE_NUMBER_LENGTH = 4; - - let pubkeys = []; - for (const input of inputs) { - pubkeys.push(input.publicKey); - } - let threshold = 1; - for (let i = 0; i < client_session.mixing_inputs.length; i++) { - let txid = client_session.mixing_inputs[i].txid; - //let _tx = hashByteOrder(txid); - TOTAL_SIZE += TXID_LENGTH + OUTPUT_INDEX_LENGTH; - /** - * Assumes that the length byte of signatures[txid].signature - * is present as the first byte - */ - console.debug({ txid }); - let utxo = { - txId: client_session.mixing_inputs[i].txid, - outputIndex: client_session.mixing_inputs[i].outputIndex, - scriptPubKey: Script.buildMultisigOut(pubkeys, threshold), - satoshis: client_session.mixing_inputs[i].satoshis, - }; - let tx = new Transaction() - .from(utxo) - .to( - client_session.generated_addresses[i], - client_session.mixing_inputs[i].satoshis, - ); - dd({ tx }); - - let output = await dboot.wallet_exec(username, [ - 'decoderawtransaction', - tx.toString(), - ]); - let { out } = ps_extract(output); - let json = JSON.parse(out); - //dd(out); - //d(json.vout[0].scriptPubKey); - //d(json.vin[0].scriptSig); - - // we then extract the signature from the first input - let inputIndex = client_session.mixing_inputs[i].outputIndex; - let sig = tx.getSignatures(client_session.mixing_inputs[i].privateKey)[ - inputIndex - ].signature; - signatures.push(sig.toBuffer()); - client_session.mixing_inputs[i].signature = sig; - TOTAL_SIZE += 1; - TOTAL_SIZE += hexToBytes(sig).length; - TOTAL_SIZE += SEQUENCE_NUMBER_LENGTH; - } - let s = Script.buildP2SHMultisigIn(pubkeys, threshold, signatures); - for (let i = 0; i < client_session.mixing_inputs.length; i++) { - let txid = client_session.mixing_inputs[i].txid; - let utxo = { - txId: client_session.mixing_inputs[i].txid, - outputIndex: client_session.mixing_inputs[i].outputIndex, - scriptPubKey: signatures[i], - satoshis: client_session.mixing_inputs[i].satoshis, - }; - let tx = new Transaction() - .from(utxo) - .to( - client_session.generated_addresses[i], - client_session.mixing_inputs[i].satoshis, - ) - .sign( - [client_session.mixing_inputs[0].privateKey], - Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY, - ); - - let output = await dboot.wallet_exec(username, [ - 'decoderawtransaction', - tx.toString(), - ]); - let { out } = ps_extract(output); - let json = JSON.parse(out); - dd(out); - //d(json.vout[0].scriptPubKey); - //d(json.vin[0].scriptSig); - - // we then extract the signature from the first input - //var inputIndex = client_session.mixing_inputs[i].outputIndex; - //var sig = tx.getSignatures(client_session.mixing_inputs[i].privateKey)[ - // inputIndex - //].signature; - //signatures.push(sig.toBuffer()); - //client_session.mixing_inputs[i].signature = sig; - //TOTAL_SIZE += 1; - //TOTAL_SIZE += hexToBytes(sig).length; - //TOTAL_SIZE += SEQUENCE_NUMBER_LENGTH; - } - - dd(s); - - /** - * Packet payload - */ - let offset = 0; - let packet = new Uint8Array(TOTAL_SIZE); - packet.set([USER_INPUT_SIZE], 0); - offset += 1; // TODO: compactSize - - /** - * Add each input - */ - for (const input of client_session.mixing_inputs) { - packet.set(hexToBytes(input.txid), offset); - offset += 32; - packet.set([input.outputIndex], offset); - offset += 4; - packet.set([hexToBytes(input.signature).length], offset); - offset += 1; - packet.set(hexToBytes(input.signature), offset); - offset += hexToBytes(input.signature).length; - packet.set(hexToBytes('ffffffff'), offset); - offset += 4; - } -})(); diff --git a/src/fs-util.js b/src/fs-util.js deleted file mode 100644 index cb85938..0000000 --- a/src/fs-util.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -let fs = require('fs'); -async function file_exists(fn) { - return await fs.existsSync(fn); -} -let FS = {}; -module.exports = FS; - -FS.file_exists = file_exists; diff --git a/src/hd.js b/src/hd.js deleted file mode 100644 index 8a10748..0000000 --- a/src/hd.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * A port of DASH core's CCoinJoinClientManager - */ - -let HardDrive = { - GetDataDir: function () { - return './'; //FIXME - }, - CheckDiskSpace: function (dir) { - /** - * TODO: FIXME: if not enough space, return false - */ - return true; //FIXME - }, -}; - -module.exports = HardDrive; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/init.sh b/src/init.sh deleted file mode 100755 index c839127..0000000 --- a/src/init.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -echo 'base' > ~/.dashjoinjs/current - -DB=~/bin/db - -if [[ $1 == "--force" ]]; then - dashmate group stop - dashmate group reset - LOOP=yes - while [[ $LOOP == "yes" ]]; do - dashmate group start - if [[ $? -eq 0 ]]; then - LOOP=no - fi - done - - rm -rf ~/.dashjoinjs/base - - echo 'base' > ~/.dashjoinjs/current - - $DB --create-wallets - $DB --make-junk-user - -fi - -$DB --dash-for-all - -$DB --grind-junk-user - -LOOP=yes -GEN=no -CTR=0 -while [[ $LOOP == "yes" ]]; do - CTR=$((CTR + 1)) - if [[ $CTR -eq 10 ]]; then - LOOP="no" - break - fi - for ID in $(seq 1 3); do - $DB --split-utxos=user$ID - if [[ $? -ne 0 ]]; then - GEN=yes - fi - done - if [[ $GEN == "yes" ]]; then - $DB --dash-for-all - GEN=no - fi -done diff --git a/src/launcher.js b/src/launcher.js deleted file mode 100755 index 92d64d1..0000000 --- a/src/launcher.js +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env node -'use strict'; -/** - * launcher.js: - * Spawns CONCURRENT_USERS demo.js instances. Passes to each instance - * a unique user which already has denomination transactions in the - * amount of 0.00100001 as per `bin/dboot --instance=INST --create-denoms` - * - * Each demo.js instance reads from the instance providede and the user's - * information as stored in combination of dash-cli and the lmdb database - * that was created. - * - * Each instance then goes on to complete the Dash coinjoin handshake and - * ultimately sends dsa, dsi, and the rest of the interaction is handled - * by demo.js. - */ - -const COIN = require('./coin-join-constants.js').COIN; -const cproc = require('child_process'); -const extractOption = require('./argv.js').extractOption; -const path = require('path'); -const dashboot = require('./bootstrap/index.js'); -const DebugLib = require('./debug.js'); -const { dd } = DebugLib; - -const INPUTS = 1; -const CURDIR = path.resolve(__dirname); -let dboot = null; - -let id = {}; - -/** - * Periodically print id information - */ -if (process.argv.includes('--id')) { - setInterval(function () { - console.info(id); - }, 10000); -} -const CONCURRENT_USERS = 3; -module.exports = { - run_cli_program: function () { - (async function (instanceName) { - /** - * Start CONCURRENT_USERS clients simultaneously - */ - console.info(`[status]: loading "${instanceName}" instance...`); - dboot = await dashboot.load_instance(instanceName); - let mnRingBuffer = null; - try { - mnRingBuffer = await dboot.ring_buffer_next('masternodes'); - } catch (e) { - if (e.message === 'needs-init') { - await dboot.ring_buffer_init('masternodes', [ - 'local_1', - 'local_2', - 'local_3', - ]); - } - } - mnRingBuffer = await dboot.ring_buffer_next('masternodes'); - let except = []; - let uniqueUsers = await dboot.extract_unique_users( - CONCURRENT_USERS, - getDemoDenomination(), - except, - ); - - /** - * Pass choices[N] to a different process. - */ - let f = []; - for (const choice of uniqueUsers) { - /** - * Spawn CONCURRENT_USERS different processes. - * Hand them each their own user - * Have them each submit to the same masternode - * - */ - //dd({ user: choice.user }); - let m = cproc.spawn('node', [ - `${CURDIR}/demo.js`, - `--instance=${instanceName}`, - `--username=${choice.user}`, - `--nickname=${choice.user}`, - '--verbose=false', - `--mn=${mnRingBuffer}`, - `--count=${INPUTS}`, - '--senddsi=true', - ]); - m.stdout.on('data', (data) => { - console.log(data.toString()); - }); - m.stderr.on('data', (data) => { - console.error('error', data.toString()); - }); - f.push(m); - } - let i = 0; - do { - await sleep(500); - } while (i < 100); - })(extractOption('instance', true)); - }, -}; -async function sleep(ms) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, ms); - }); -} -/** FIXME: put in library */ -function getDemoDenomination() { - return parseInt(COIN / 1000 + 1, 10); -} diff --git a/src/lmdb/config.json b/src/lmdb/config.json deleted file mode 100644 index d4c715b..0000000 --- a/src/lmdb/config.json +++ /dev/null @@ -1 +0,0 @@ -{"db_path":"~/db/","db_name":"dashjoin","max_dbs":10} diff --git a/src/lmdb/demodata.js b/src/lmdb/demodata.js deleted file mode 100644 index d76340b..0000000 --- a/src/lmdb/demodata.js +++ /dev/null @@ -1,221 +0,0 @@ -'use strict'; -const COIN = require('../coin-join-constants.js').COIN; -const Network = require('../network.js'); - -let Lib = {}; -module.exports = Lib; - -let config = require('../.config.json'); -let NETWORK = config.network; - -let DashCore = require('@dashevo/dashcore-lib'); -let Transaction = DashCore.Transaction; -let Script = DashCore.Script; -let PrivateKey = DashCore.PrivateKey; -let Address = DashCore.Address; -let { hexToBytes } = require('../network-util.js'); -const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const fs = require('fs'); -const { read_file, logUsedTransaction, isUsed } = require('../ctransaction.js'); - -let DB = require('./lmdb.js'); -let user = 'dp'; -async function file_exists(f) { - return await fs.existsSync(f); -} -Lib.initialize = async function (uname, config) { - user = uname; - let exists = await file_exists( - config.db_path.replace(/\/$/, '') + '/data.mdb', - ); - DB.open({ - path: config.db_path, - db_name: config.db_name, - create: !exists, - maxDbs: config.max_dbs, - }); - db_cj(); -}; - -function db_cj() { - DB.set_namespaces(['coinjoin']); -} -function db_cj_ns(list) { - DB.set_namespaces(['coinjoin', ...list]); -} -function db_put(key, val) { - DB.ns.put(key, val); -} -function db_get(key) { - return DB.ns.get(key); -} -function db_append(key, val) { - let ex = DB.ns.get(key); - DB.ns.put(key, ex + val); -} -Lib.store = { user: {} }; -Lib.store.create_user = async function (username) { - let list = db_get('users'); - try { - list = JSON.parse(list); - } catch (e) { - list = []; - } - for (let user of list) { - if (user === username) { - throw new Error('user already exists'); - } - } - list.push(username); - db_put('users', JSON.stringify(list)); -}; - -Lib.user = {}; -Lib.user.create_user = Lib.store.create_user; -Lib.store.user.add_address = function (username, address) { - db_cj_ns([username]); - let existing = Lib.address.get_all('addresses'); - if (existing.indexOf(address) === -1) { - existing.push(address); - db_cj_ns([username]); - db_put('addresses', JSON.stringify(existing)); - return true; - } - return false; -}; -Lib.address = {}; -Lib.address.add = Lib.store.user.add_address; -Lib.address.get_all = function (username) { - db_cj_ns([username]); - try { - let t = db_get('addresses'); - t = JSON.parse(t); - if (!Array.isArray(t)) { - return []; - } - return t; - } catch (e) { - return []; - } -}; - -Lib.transaction = {}; -Lib.transaction.get_all = function (username) { - db_cj_ns([username]); - try { - let t = db_get('transactions'); - t = JSON.parse(t); - if (!Array.isArray(t)) { - return []; - } - return t; - } catch (e) { - return []; - } -}; -Lib.transaction.remove = function (username, txn) { - db_cj_ns([username]); - let existing = Lib.transaction.get_all(username); - if (existing.length === 0) { - return; - } - let keep = []; - for (let tx of existing) { - if (tx.txid === txn.txid) { - continue; - } - keep.push(tx); - } - Lib.transaction.set(username, keep); -}; - -Lib.transaction.set = function (username, items) { - if (!Array.isArray(items)) { - throw new Error(`items must be an array`); - } - db_cj_ns([username]); - db_put('transactions', JSON.stringify(items)); -}; -Lib.transaction.add = Lib.store.user.transaction; - -Lib.store.user.transaction = function (username, txn) { - /** - * This is assuming you pass in an array or a single - * json object that is the result of the `listtransactions` - * dash-cli command - */ - let existing = Lib.transaction.get_all(username); - db_cj_ns([username]); - /** - * FIXME: guarantee that the same transaction doesn't get added - */ - if (Array.isArray(txn)) { - for (let t of txn) { - existing.push(t); - } - } else { - existing.push(txn); - } - db_put('transactions', JSON.stringify(existing)); -}; - -function d(f) { - console.debug(f); -} -function dd(f) { - console.debug(f); - process.exit(); -} - -const txns = [ - { - address: 'yjNhKBVgajCpKorbbcc4u8WXojcd6wkzPt', - category: 'receive', - amount: 3.0, - vout: 0, - confirmations: 0, - instantlock: true, - instantlock_internal: true, - chainlock: false, - trusted: true, - txid: 'a45cc2d45f09e5408ad367fdab8e53d1fb9f21517dde6c2e4b70c753cef3dbdc', - walletconflicts: [], - time: 1686896516, - timereceived: 1686896516, - }, - { - address: 'yjPpZi9mPott4zeHzP1LtgoB9jPRBmB8hs', - category: 'receive', - amount: 3.0, - vout: 0, - confirmations: 0, - instantlock: true, - instantlock_internal: true, - chainlock: false, - trusted: true, - txid: 'e07a0d29a74f8d865f4160b772857c7a4a4b5328bfaa8289f8654cda1ad0d40d', - walletconflicts: [], - time: 1686896516, - timereceived: 1686896516, - }, -]; - -(async () => { - let os = require('os'); - //@ts-ignore - tsc can't understand JSON - let lmdbConfig = require('./config.json'); - let homedir = os.homedir(); - lmdbConfig.db_path = lmdbConfig.db_path.replace(/^~[/]/, `${homedir}/`); - await Lib.initialize('psend', lmdbConfig); - //Lib.store.create_user('psend'); - //d(db_get('users')); - let address = 'yjPpZi9mPott4zeHzP1LtgoB9jPRBmB8hs'; - Lib.store.user.add_address('psend', address); - d(Lib.address.get_all('psend')); - - //Lib.store.user.transaction('psend',txns); - //d(Lib.transaction.get_all('psend')); - Lib.transaction.remove('psend', txns[0]); - Lib.transaction.remove('psend', txns[1]); - d(Lib.transaction.get_all('psend')); -})(); diff --git a/src/lmdb/lmdb.js b/src/lmdb/lmdb.js deleted file mode 100644 index 54885ef..0000000 --- a/src/lmdb/lmdb.js +++ /dev/null @@ -1,163 +0,0 @@ -var Lib = {}; -module.exports = Lib; - -Lib._data = { - lmdb: require('node-lmdb'), - dbi: null, - env: null, -}; -Lib.print_version = function () { - console.log('Current lmdb version is', Lib.__data.lmdb.version); -}; -Lib.open = function ( - args = { - path, - db_name, - create: false, - maxDbs: 10, - mapSize: null, - }, -) { - let DB = Lib._data.lmdb; - let DBI = Lib._data.dbi; - let mapSize = args.mapSize ?? 2 * 1024 * 1024 * 1024; - - // Print the version - // Create new LMDB environment - Lib._data.env = new DB.Env(); - let env = Lib._data.env; - // Open the environment - env.open({ - // Path to the environment - // IMPORTANT: you will get an error if the directory doesn't exist! - path: args.path, - // Maximum number of databases - maxDbs: args.maxDbs, - mapSize: mapSize, - }); - // Open database - Lib._data.dbi = env.openDbi({ - name: args.db_name, - create: args.create, - }); - return Lib._data; -}; - -Lib.txn = () => { - let { env } = Lib._data; - // Begin transaction - Lib._data.txn = env.beginTxn(); -}; - -Lib.get = (str) => { - let { txn, dbi } = Lib._data; - - let val = txn.getString(dbi, str); - // Goodbye Christopher Null... - if ('null' === val || 'undefined' === val) { - val = null; - } - return val; -}; - -Lib.put = (k, val) => { - let { txn, dbi } = Lib._data; - try { - return txn.putString(dbi, k, String(val)); - } catch (e) { - return null; - } -}; - -Lib.del = (k) => { - let { txn, dbi } = Lib._data; - try { - txn.del(dbi, k); - } catch (e) {} -}; - -Lib.commit = () => { - let { txn } = Lib._data; - // Commit transaction - txn.commit(); -}; - -Lib.close = () => { - let { env, dbi } = Lib._data; - // Close the database - dbi.close(); - // Close the environment - env.close(); -}; - -Lib.mput = function (items) { - Lib.txn(); - for (const row of items) { - for (const key in row) { - Lib.put(key, row[key]); - } - } - Lib.commit(); -}; - -Lib._ns = []; -Lib.ns = {}; -Lib.set_namespaces = function (list) { - Lib._ns = [...list]; -}; -Lib.get_namespaces = function () { - return Lib._ns; -}; -Lib.make_key = function (key) { - if (Array.isArray(key)) { - return Lib._ns.join('|') + key.join('|') + '|'; - } - return Lib._ns.join('|') + key + '|'; -}; - -Lib.ns.get = function (key) { - Lib.txn(); - let val = Lib.get(Lib.make_key(key)); - Lib.commit(); - return val; -}; -Lib.ns.mget = function (items) { - Lib.txn(); - let ret = []; - for (const key of items) { - let val = Lib.get(Lib.make_key(key)); - ret.push({ [key]: val }); - } - Lib.commit(); - return ret; -}; -Lib.ns.mgetarray = function (items) { - Lib.txn(); - let ret = []; - for (const key of items) { - ret.push(Lib.get(Lib.make_key(key))); - } - Lib.commit(); - return ret; -}; - -Lib.ns.mput = function (items) { - Lib.txn(); - for (const row of items) { - for (const key in row) { - Lib.put(Lib.make_key(key), row[key]); - } - } - Lib.commit(); -}; -Lib.ns.del = function (key) { - Lib.txn(); - Lib.del(Lib.make_key(key)); - Lib.commit(); -}; - -Lib.ns.put = function (key, val) { - Lib.txn(); - Lib.put(Lib.make_key(key), val); - Lib.commit(); -}; diff --git a/src/lmdb/utils.js b/src/lmdb/utils.js deleted file mode 100644 index eb77187..0000000 --- a/src/lmdb/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -let Lib = {}; -module.exports = Lib; - -const fs = require('fs'); -const { read_file, logUsedTransaction, isUsed } = require('../ctransaction.js'); -const { extractOption } = require('../argv.js'); - -let DB = require('../bootstrap/index.js'); - -(async function () { - let inst = await DB.load_instance(extractOption('instance', true)); -})(); -function d(f) { - console.debug(f); -} -function dd(f) { - console.debug(f); - process.exit(); -} diff --git a/src/logger.js b/src/logger.js deleted file mode 100755 index a32db30..0000000 --- a/src/logger.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict'; -const xt = require('@mentoc/xtract').xt; -const fs = require('fs'); -const fsPromises = require('fs/promises'); - -let nickName; -let initialized = false; - -function setNickname(n) { - nickName = n; - nickName = nickName.replace(/[^a-z0-9]+/gi, ''); -} - -function getLogDir() { - return `${process.env.HOME}/logs`; -} - -async function dirExists() { - return await fs.existsSync(getLogDir()); -} -async function mkdir(dir) { - return await fs.mkdirSync(dir, { recursive: true }); -} - -async function initialize(_in_nickName) { - setNickname(_in_nickName); - if (!(await dirExists())) { - await mkdir(getLogDir()); - } - initialized = true; -} - -function bigint_safe_json_stringify(buffer, stringify_space = 2) { - return JSON.stringify( - buffer, - function (key, value) { - this.k = key; - return typeof value === 'bigint' ? value.toString() + 'n' : value; - }, - stringify_space, - ); -} -function getLogFile(_in_nickName = null) { - if (_in_nickName) { - setNickname(_in_nickName); - } - let nick = nickName.replace(/[^a-z0-9]+/gi, ''); - return `${getLogDir()}/${nick}.log`; -} -let verbosity = false; -function verbose(setting) { - verbosity = setting; -} -function date() { - const d = new Date(); - let h = d.getHours(); - if (String(h).length === 1) { - h = `0${h}`; - } - let m = d.getMinutes(); - if (String(m).length === 1) { - m = `0${m}`; - } - let s = d.getSeconds(); - if (String(s).length === 1) { - s = `0${s}`; - } - return ( - [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') + - ' ' + - [h, m, s].join(':') - ); -} -async function log(...args) { - if (!initialized) { - await initialize('node'); - } - return fsPromises - .open(getLogFile(), 'a', 0o600) - .then(function (fp) { - fp.appendFile(`${date()}: ${bigint_safe_json_stringify(args, 2)}\n`); - return true; - }) - .catch(function (error) { - if (verbosity) { - console.error(error); - } - return null; - }); -} -module.exports = { - setNickname, - log, - initialize, -}; diff --git a/src/master-node-sync.js b/src/master-node-sync.js deleted file mode 100644 index a07a0bd..0000000 --- a/src/master-node-sync.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * A port of DASH core's CMasternodeSync - */ - -function MasterNodeSync() { - this.contents = []; - - return this; -} -module.exports = MasterNodeSync; diff --git a/src/masternode-connection.js b/src/masternode-connection.js deleted file mode 100644 index 3a49b07..0000000 --- a/src/masternode-connection.js +++ /dev/null @@ -1,673 +0,0 @@ -'use strict'; -const Network = require('./network.js'); -const crypto = require('crypto'); -const net = require('net'); -const COIN = require('./coin-join-constants.js').COIN; -let DashCore = require('@dashevo/dashcore-lib'); -let Transaction = DashCore.Transaction; -let Script = DashCore.Script; -let { hexToBytes } = require('./network-util.js'); -const DebugLib = require('./debug.js'); -const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const { d, dd } = DebugLib; -const STATIC_PAYEE = 'yPhJTWFdAktwBokE3SzNxafNb1Qvb7hsPo'; -const { xt } = require('@mentoc/xtract'); - -let Lib = {}; -module.exports = Lib; - -function getopt(opt) { - /** - * Accepts something like: - * 'ip' - * and will search argv for anything like: - * --ip=127.0.0.1 - * - */ - for (let arg of process.argv) { - let matches = arg.match(/^[-]{2}([^=]+)=(.*)$/); - if (matches) { - if (matches[1] === opt) { - return matches[2]; - } - } - } -} -const { - PROTOCOL_VERSION, - MNAUTH_CHALLENGE_SIZE, - //TESTNET, - SERVICE_IDENTIFIERS, - MESSAGE_HEADER_SIZE, - VERSION_PACKET_MINIMUM_SIZE, - //SENDHEADERS_PAYLOAD_SIZE /* (H) sendheaders payload */, - //SENDCMPCT_PAYLOAD_SIZE /* (C) sendcmpct payload */, - //SENDDSQ_PAYLOAD_SIZE /* (D) senddsq payload */, - //PING_PAYLOAD_SIZE /* (P) Ping message payload */, - //PING_NONCE_SIZE, -} = Network.constants; - -const { mapIPv4ToIpv6 } = Network.util; -const PacketParser = Network.packet.parse; - -/** - * "constructor" - */ -function MasterNode({ - ip, - port, - network, - ourIP, - startBlockHeight, - onCollateralTxCreated = null, - onStatusChange = null, - onDSSU = null, - onDSF = null, - debugFunction = null, - userAgent = null, - coinJoinData, - payee, - onConnectionClose = null, - onConnectionError = null, - nickName, - client_session, -}) { - DebugLib.setNickname(nickName); - let self = this; - /** - * Our member variables - */ - self.client_session = client_session; - self.nickName = nickName; - self.buffer = new Uint8Array(); - self.client = null; - self.collateralTx = null; - self.debugFunction = null; - if (!debugFunction) { - self.debugFunction = () => {}; - } else { - self.debugFunction = debugFunction; - } - self.coinJoinData = coinJoinData; - self.dsf = null; - self.dsfOrig = null; - self.dsq = null; - self.frames = []; - self.userAgent = userAgent; - self.handshakeState = { - version: false, - verack: false, - sendaddr: false, - }; - self.handshakeStatePhase2 = { - sendaddrv2: false, - sendheaders: false, - sendcmpct: false, - senddsq: false, - ping: false, - mnauth: false, - }; - self.id = function () { - return `masternode(${self.ip}@${self.network})`; - }; - self.ip = ip; - self.mnauth_challenge = null; - self.network = network; - self.onCollateralTxCreated = null; - self.onConnectionClose = null; - self.onConnectionError = null; - self.onStatusChange = onStatusChange; - self.onDSF = onDSF; - self.onDSSU = onDSSU; - self.ourIP = ourIP; - self.payee = payee; - self.port = port; - self.processDebounce = null; - self.processRecvBuffer = null; - self.recv = []; - self.senddsq = null; - self.startBlockHeight = startBlockHeight; - self.status = null; - self.statusChangedAt = 0; - - if (typeof onCollateralTxCreated === 'function') { - self.onCollateralTxCreated = onCollateralTxCreated; - } - if (typeof onConnectionClose === 'function') { - self.onConnectionClose = onConnectionClose; - } - if (typeof onConnectionError === 'function') { - self.onConnectionError = onConnectionError; - } - /** - * Member functions - */ - self.dispatchCollateralTxCreated = function (tx) { - if (typeof self.onCollateralTxCreated === 'function') { - self.onCollateralTxCreated(tx, self); - } - }; - self.dispatchConnectionClosed = function () { - if (typeof self.onConnectionClose === 'function') { - self.onConnectionClose(self); - } - }; - self.dispatchConnectionError = function (err) { - if (typeof self.onConnectionError === 'function') { - self.onConnectionError(err, self); - } - }; - self.dispatchDSF = function (packet) { - if (typeof self.onDSF === 'function') { - self.onDSF(packet, self); - } - }; - self.disconnect = function (cb) { - self.client.destroy(); - cb(); - }; - self.dispatchDSSU = function (packet) { - if (typeof self.onDSSU === 'function') { - self.onDSSU(packet, self); - } - }; - self.existingCollateralTxns = []; - self.makeCollateralTx = async function (options = {}) { - let amount = parseInt(LOW_COLLATERAL * 2, 10); - let fee = 10000; // FIXME - let changeAddress = self.coinJoinData.changeAddress; - if (changeAddress === null) { - throw new Error('changeAddress cannot be null'); - } - d({ cjd_count: self.coinJoinData.utxos.length }); - for (let i = 0; i < self.coinJoinData.utxos.length; i++) { - let txid = self.coinJoinData.utxos[i].txid; - let payeeAddress = STATIC_PAYEE; - let sourceAddress = self.coinJoinData.utxos[i].address; - let vout = self.coinJoinData.utxos[i].outputIndex; - let satoshis = self.coinJoinData.utxos[i].satoshis; - let privateKey = self.coinJoinData.utxos[i].privateKey; - let unspent = satoshis - amount; - if (unspent - fee < 0) { - continue; - } - if ( - self.existingCollateralTxns.length && - self.existingCollateralTxns.indexOf(txid) !== -1 - ) { - continue; - } - self.existingCollateralTxns.push(txid); - - let utxos = { - txId: txid, - outputIndex: vout, - sequenceNumber: 0xffffffff, - scriptPubKey: Script.buildPublicKeyHashOut(sourceAddress), - satoshis, - }; - //d({ txid, utxos, amount, ch: unspent - fee, unspent, fee }); - let tx = new Transaction(); - tx.from(utxos); - tx.to(payeeAddress, amount - fee); - tx.fee(fee); - tx.change(changeAddress); - tx.sign(privateKey); - self.collateralTx = { - tx, - utxos, - payeeAddress, - amount, - changeAddress, - privateKey, - txid, - vout, - satoshis, - sourceAddress, - user: self.coinJoinData.user, - }; - self.dispatchCollateralTxCreated(self.collateralTx); - if (xt(options, 'no_serialize')) { - return tx; - } - return hexToBytes(tx.uncheckedSerialize()); - } - }; - self.debug = function (...args) { - if (self.debugFunction) { - self.debugFunction(...args); - } - }; - self.setStatus = function (s) { - self.status = s; - self.statusChangedAt = Date.now(); - if ('function' === typeof self.onStatusChange) { - self.onStatusChange({ - self, - }); - } - }; - self.createMNAuthChallenge = function () { - return new Uint8Array(crypto.randomBytes(MNAUTH_CHALLENGE_SIZE)); - }; - self.extract = function (buffer, start, end) { - if (start > end) { - return new Uint8Array(); - } - let extracted = new Uint8Array(end - start); - let extractedIndex = 0; - for (let i = start; i < end; i++) { - extracted[extractedIndex++] = buffer[i]; - } - return extracted; - }; - self.clearBuffer = function () { - self.buffer = new Uint8Array(); - }; - self.appendBuffer = function (dest, src) { - let finalBuffer = new Uint8Array(dest.length + src.length); - finalBuffer.set(dest, 0); - finalBuffer.set(src, dest.length); - return finalBuffer; - }; - self.cutBuffer = function (buffer, start, end) { - let finalBuffer = new Uint8Array(end - start); - let k = 0; - for (let i = start; i < end; i++) { - finalBuffer.set([buffer[i]], k++); - } - return finalBuffer; - }; - self.handshakePhase2Completed = function () { - return ( - self.handshakeStatePhase2.sendheaders && - self.handshakeStatePhase2.sendcmpct && - self.handshakeStatePhase2.senddsq && - self.handshakeStatePhase2.ping && - self.handshakeStatePhase2.mnauth - ); - }; - self.handshakePhase1Completed = function () { - return ( - self.handshakeState.version && - self.handshakeState.verack && - self.handshakeState.sendaddrv2 - ); - }; - - self.processCoinJoinRecvBuffer = function () { - self.debugFunction('[+] processCoinJoinRecvBuffer'); - let i = PacketParser.extractItems(self.buffer, ['command', 'payloadSize']); - let command = i[0]; - let payloadSize = i[1]; - self.debugFunction({ command, payloadSize }); - if (command === 'getheaders') { - let parsed = PacketParser.getheaders(self.buffer); - payloadSize += parsed.hashes.length * 32; - self.debugFunction({ parsed, payloadSize }); - } - if (command === 'dsf') { - let parsed = PacketParser.dsf(self.buffer); - self.debugFunction('dsf:', parsed); - self.dispatchDSF(parsed); - } - if (command === 'dssu') { - let parsed = PacketParser.dssu(self.buffer); - self.debugFunction('dssu:', parsed); - self.dispatchDSSU(parsed); - } - if (command === 'ping') { - let nonce = PacketParser.extractPingNonce(self.buffer); - self.client.write( - Network.packet.pong({ chosen_network: network, nonce }), - ); - } - self.buffer = self.extract( - self.buffer, - MESSAGE_HEADER_SIZE + payloadSize, - self.buffer.length, - ); - }; - self.switchHandlerTo = function (which) { - if ('function' === typeof which) { - self.processRecvBuffer = which; - return; - } - switch (which) { - case 'coinjoin': - self.processRecvBuffer = self.processCoinJoinRecvBuffer; - break; - case 'handshake': - default: - self.processRecvBuffer = self.processHandshakeBuffer; - break; - } - }; - self.getDefaultRecvFunctions = function () { - return { - coinjoin: self.processCoinJoinRecvBuffer, - handshake: self.processHandshakeBuffer, - }; - }; - self.extractRemaining = function (payloadSize) { - self.buffer = self.extract( - self.buffer, - MESSAGE_HEADER_SIZE + payloadSize, - self.buffer.length, - ); - }; - self.processHandshakeBuffer = function () { - self.debug('[+] processHandshakeBuffer'); - let i = PacketParser.extractItems(self.buffer, ['command', 'payloadSize']); - let command = i[0]; - let payloadSize = i[1]; - - self.debug({ justParsed: { command, payloadSize } }); - if (command === 'dsq') { - self.debugFunction({ command, payloadSize }); - self.dsq = PacketParser.dsq(self.buffer); - self.dsqOrig = self.buffer; - self.extractRemaining(payloadSize); - self.setStatus('DSQ_RECEIVED'); - return; - } - if ( - self.status === 'DSQ_RECEIVED' || - self.status === 'EXPECT_HCDP' || - self.status === 'READY' - ) { - self.debug('EXPECT_HCDP status'); - let packet; - let parsed = null; - while (self.buffer.length) { - command = PacketParser.commandName(self.buffer); - payloadSize = PacketParser.payloadSize(self.buffer); - packet = {}; - switch (command) { - case 'getheaders': - self.handshakeStatePhase2.getheaders = true; - parsed = PacketParser.getheaders(self.buffer); - payloadSize += parsed.hashes.length * 32; - break; - case 'mnauth': - /** - * No specific response needed - */ - break; - case 'sendheaders': - /** - * No specific response needed - */ - break; - case 'sendcmpct': - break; - case 'senddsq': - setTimeout( - (function (_self, _net) { - return function () { - _self.client.write( - Network.packet.senddsq({ - chosen_network: _net, - fSendDSQueue: true, - }), - ); - _self.setStatus('READY'); - }; - })(self, network), - 800, - ); - break; - case 'dsq': - self.debugFunction({ command, payloadSize }); - packet = PacketParser.dsq(self.buffer); - self.debugFunction(command, packet); - self.dsq = packet; - self.setStatus('DSQ_RECEIVED'); - break; - case 'dsf': - self.debugFunction({ command, payloadSize }); - packet = PacketParser.dsf(self.buffer); - self.dsf = packet; - self.dsfOrig = self.buffer; - self.dispatchDSF(packet); - break; - case 'dssu': - self.debugFunction({ command, payloadSize }); - packet = PacketParser.dssu(self.buffer); - self.dispatchDSSU(packet); - /** - * If STATUS_REJECTED, then the collateral would - * have been charged by the masternode - * 1) mark the collateral transaction as used - */ - self.debugFunction(command, packet); - break; - case 'ping': - self.handshakeStatePhase2.ping = true; - self.client.write( - Network.packet.pong({ - chosen_network: network, - nonce: PacketParser.extractPingNonce(self.buffer), - }), - ); - break; - default: - self.debug('defaulted:', { command, payloadSize }); - break; - } - self.extractRemaining(payloadSize); - } - } - if (self.status === 'EXPECT_VERACK') { - if ( - self.buffer.length < - MESSAGE_HEADER_SIZE * 3 + VERSION_PACKET_MINIMUM_SIZE - ) { - self.debug( - 'EXPECT_VERACK but VERSION_PACKET_MINIMUM_SIZE not met:', - self.buffer.length, - 'expected:', - VERSION_PACKET_MINIMUM_SIZE, - ); - return; - } - /** - * Step 3: parse `version`, `verack`, and `sendaddrv2` from MN - */ - /** - * This means we have the following: - * 1) version packet (24 byte header, plus variable payload size) - * 2) verack packet (24 bytes) - * 3) sendaddrv2 (24 bytes) - */ - let command = PacketParser.commandName(self.buffer); - let payloadSize = PacketParser.payloadSize(self.buffer); - self.debug('processing handshakePhase1...', { - command, - payloadSize, - }); - while ( - self.buffer.length && - command.length && - self.handshakePhase1Completed() === false - ) { - payloadSize = PacketParser.payloadSize(self.buffer); - if (command === 'version') { - self.debug('got version'); - self.masterNodeVersion = self.extract( - self.buffer, - 0, - MESSAGE_HEADER_SIZE + payloadSize, - ); - self.buffer = self.extract( - self.buffer, - MESSAGE_HEADER_SIZE + payloadSize, - self.buffer.length, - ); - self.handshakeState.version = true; - } else if (command === 'verack') { - self.debug('got verack', self.buffer); - self.buffer = self.extract( - self.buffer, - MESSAGE_HEADER_SIZE, - self.buffer.length, - ); - self.handshakeState.verack = true; - } else if (command === 'sendaddrv2') { - self.debug('got sendaddrv2', self.buffer); - self.buffer = self.extract( - self.buffer, - MESSAGE_HEADER_SIZE, - self.buffer.length, - ); - self.handshakeState.sendaddrv2 = true; - } - command = PacketParser.commandName(self.buffer); - } - } - if (self.handshakePhase1Completed() && self.status === 'EXPECT_VERACK') { - self.debug('handshakePhase1Completed. EXPECT_VERACK is next'); - self.debugFunction('buffer before clear: ', self.buffer); - self.clearBuffer(); - self.setStatus('RESPOND_VERACK'); - self.client.write( - Network.packet.verack({ chosen_network: network }), - function () { - self.debug('[+] Done sending verack'); - self.setStatus('EXPECT_HCDP'); - }, - ); - return; - } - self.debug('reached end of function'); - }; - - /** - * Creates a socket descriptor and saves it to self.client. - * The user may use self.client to write packets to the wire. - * No message header is prefixed, so treat self.client.write as - * a direct socket call. - */ - self.connect = function () { - /** - * There's different phases in the MasterNode handshake. - * This is one of them. - * switchHandlerTo("handshake") will parse and verify - * that all traffic needed to authenticate to a master node - * is handled correctly. - * - * The underlying code will automatically change the handler to - * "coinjoin" once all steps in the handshake process have been - * parsed and completed. - */ - self.switchHandlerTo('handshake'); - - /** - * Here we create the socket - */ - self.client = new net.Socket(); - /** - * We have to handle several events on this socket. - */ - self.client.on('close', function () { - self.setStatus('CLOSED'); - self.client.destroy(); - self.dispatchConnectionClosed(self); - }); - self.client.on('error', function (err) { - self.client.destroy(); - self.dispatchConnectionError(err); - }); - - /** - * The "data" event is triggered when the socket receives - * bytes off the wire. This is equivalent to a recv() system call - * (see man 2 recv), except there is no user intervention needed - * to receive data. Instead, it just comes off the wire whenever - * the kernel gets data. There doesn't seem to be a way to manually - * fetch data, unless you use pause/resume mechanics. See the node - * docs for more info on that. - */ - self.client.on('data', function (payload) { - /** - * This is an atomic operation - */ - self.buffer = self.appendBuffer(self.buffer, payload); - - /** - * Debouncing the call so that we can wait until - * we have a larger amount of data before processing it. - */ - if (self.processDebounce) { - clearInterval(self.processDebounce); - } - self.processDebounce = setInterval(() => { - self.processRecvBuffer(); - clearInterval(self.processDebounce); - self.processDebounce = null; - }, 800); - }); - - /** - * As soon as we're connected, the "ready" event will - * be emitted. This is will be our chance to send the - * first packet. Masternodes expect a `version` message - */ - self.client.on('ready', function () { - /** - * Step 2: once connected, send a `version` message - */ - - /** - * see: https://dashcore.readme.io/docs/core-ref-p2p-network-control-messages#version - */ - let versionPayload = { - chosen_network: self.network, - protocol_version: PROTOCOL_VERSION, - services: [ - SERVICE_IDENTIFIERS.NODE_UNNAMED, - //SERVICE_IDENTIFIERS.NODE_NETWORK, - //SERVICE_IDENTIFIERS.NODE_BLOOM, - ], - addr_recv_services: [ - SERVICE_IDENTIFIERS.NODE_NETWORK, - //SERVICE_IDENTIFIERS.NODE_BLOOM, - ], - start_height: self.startBlockHeight, - addr_recv_ip: mapIPv4ToIpv6(self.ip), - addr_recv_port: self.port, - addr_trans_ip: mapIPv4ToIpv6(self.ourIP), - addr_trans_port: self.client.localPort, - relay: true, - mnauth_challenge: self.createMNAuthChallenge(), - }; - if (null !== self.userAgent) { - versionPayload.user_agent = self.userAgent; - } - self.setStatus('EXPECT_VERACK'); - self.client.write(Network.packet.version(versionPayload)); - }); - /** - * Step 1: connect to the remote host - */ - self.setStatus('NEEDS_AUTH'); - let data = { - port: self.port, - host: self.ip, - keepAlive: true, - keepAliveInitialDelay: 3, - }; - let ip = getopt('ip'); - if (ip) { - data.localAddress = '127.0.0.' + ip; - } - ip = getopt('absip'); - if (ip) { - data.localAddress = ip; - } - //console.debug({ data }); - self.client.connect(data); - }; -} - -Lib.MasterNodeConnection = MasterNode; diff --git a/src/net-msg.js b/src/net-msg.js deleted file mode 100644 index fa48158..0000000 --- a/src/net-msg.js +++ /dev/null @@ -1,78 +0,0 @@ -let Lib = {}; -module.exports = Lib; -Lib.NetMsgType = { - VERSION: 'version', - VERACK: 'verack', - ADDR: 'addr', - ADDRV2: 'addrv2', - SENDADDRV2: 'sendaddrv2', - INV: 'inv', - GETDATA: 'getdata', - MERKLEBLOCK: 'merkleblock', - GETBLOCKS: 'getblocks', - GETHEADERS: 'getheaders', - TX: 'tx', - HEADERS: 'headers', - BLOCK: 'block', - GETADDR: 'getaddr', - MEMPOOL: 'mempool', - PING: 'ping', - PONG: 'pong', - NOTFOUND: 'notfound', - FILTERLOAD: 'filterload', - FILTERADD: 'filteradd', - FILTERCLEAR: 'filterclear', - SENDHEADERS: 'sendheaders', - SENDCMPCT: 'sendcmpct', - CMPCTBLOCK: 'cmpctblock', - GETBLOCKTXN: 'getblocktxn', - BLOCKTXN: 'blocktxn', - GETCFILTERS: 'getcfilters', - CFILTER: 'cfilter', - GETCFHEADERS: 'getcfheaders', - CFHEADERS: 'cfheaders', - GETCFCHECKPT: 'getcfcheckpt', - CFCHECKPT: 'cfcheckpt', - LEGACYTXLOCKREQUEST: 'ix', - SPORK: 'spork', - GETSPORKS: 'getsporks', - DSACCEPT: 'dsa', - DSVIN: 'dsi', - DSFINALTX: 'dsf', - DSSIGNFINALTX: 'dss', - DSCOMPLETE: 'dsc', - DSSTATUSUPDATE: 'dssu', - DSTX: 'dstx', - DSQUEUE: 'dsq', - SENDDSQUEUE: 'senddsq', - SYNCSTATUSCOUNT: 'ssc', - MNGOVERNANCESYNC: 'govsync', - MNGOVERNANCEOBJECT: 'govobj', - MNGOVERNANCEOBJECTVOTE: 'govobjvote', - GETMNLISTDIFF: 'getmnlistd', - MNLISTDIFF: 'mnlistdiff', - QSENDRECSIGS: 'qsendrecsigs', - QFCOMMITMENT: 'qfcommit', - QCONTRIB: 'qcontrib', - QCOMPLAINT: 'qcomplaint', - QJUSTIFICATION: 'qjustify', - QPCOMMITMENT: 'qpcommit', - QWATCH: 'qwatch', - QSIGSESANN: 'qsigsesann', - QSIGSHARESINV: 'qsigsinv', - QGETSIGSHARES: 'qgetsigs', - QBSIGSHARES: 'qbsigs', - QSIGREC: 'qsigrec', - QSIGSHARE: 'qsigshare', - QGETDATA: 'qgetdata', - QDATA: 'qdata', - CLSIG: 'clsig', - ISLOCK: 'islock', - ISDLOCK: 'isdlock', - MNAUTH: 'mnauth', - GETHEADERS2: 'getheaders2', - SENDHEADERS2: 'sendheaders2', - HEADERS2: 'headers2', - GETQUORUMROTATIONINFO: 'getqrinfo', - QUORUMROTATIONINFO: 'qrinfo', -}; diff --git a/src/network-util.js b/src/network-util.js deleted file mode 100644 index f065b8e..0000000 --- a/src/network-util.js +++ /dev/null @@ -1,211 +0,0 @@ -'use strict'; - -let crypto = require('crypto'); -function hashOfHash(data) { - return crypto - .createHash('sha256') - .update(crypto.createHash('sha256').update(data).digest()) - .digest(); -} -function hashByteOrder(str) { - let bytes = []; - for (let i = str.length - 1; i >= 0; i -= 2) { - bytes.push(str.substr(i - 1, 2)); - } - return bytes.join(''); -} - -/** - * Compact Size UINT documentation: - * https://docs.dash.org/projects/core/en/stable/docs/reference/transactions-compactsize-unsigned-integers.html - */ -function calculateCompactSize(obj) { - if (obj.length > 0 && obj.length <= 252) { - return 1; - } - if (obj.length > 253 && obj.length <= 0xffff) { - return 3; - } - if (obj.length > 0x10000 && obj.length <= 0xffffffff) { - return 5; - } - if (obj.length > 0x100000000 && obj.length <= 0xffffffffffffffff) { - return 9; - } - return 0; -} -function encodeCompactSizeBytes(obj) { - let size = calculateCompactSize(obj); - if (size === 0) { - return [0]; - } - let len = 0; - if (typeof obj === 'number') { - len = obj; - } else { - len = obj.length; - } - switch (size) { - case 1: - return [len]; - case 3: - return [0xfd, len & 0xff, len >> 8]; - case 5: - /** - * 32 24 16 8 1 - * |-------|-------|-------|-------| - * - */ - return [ - 0xfe, - len & 0xff, - len >> 8, - (len >> 16) & 0xff, - (len >> 24) & 0xff, - ]; - case 9: - return [ - 0xff, - len & 0xff, // byte 1 - (len >> 8) & 0xff, // byte 2 - (len >> 16) & 0xff, // byte 3 - (len >> 24) & 0xff, // byte 4 - (len >> 32) & 0xff, // byte 5 - (len >> 40) & 0xff, // byte 6 - (len >> 48) & 0xff, // byte 7 - (len >> 56) & 0xff, // byte 8 - ]; - } -} - -function allZeroes(buffer) { - for (let ch of buffer) { - if (ch !== 0) { - return false; - } - } - return true; -} - -function hexToBytes(hex) { - let bytes = new Uint8Array(hex.length / 2); - let i = 0; - for (let c = 0; c < hex.length; c += 2) { - bytes[i] = parseInt(hex.substr(c, 2), 16); - ++i; - } - return bytes; -} -function bytesToString(b) { - let bytes = new Uint8Array(b); - let str = []; - for (let i = 0; i < bytes.length; i++) { - if (bytes[i].toString(16).length === 1) { - str.push(0); - } - str.push(bytes[i].toString(16)); - } - return str.join(''); -} -function str2uint8(text) { - return Uint8Array.from( - Array.from(text).map((letter) => letter.charCodeAt(0)), - ); -} -function extractUint32(data, at) { - let uiArray = new Uint32Array([0]); - for (let i = at; i < at + 4; i++) { - uiArray[0] += data[at]; - } - return uiArray[0]; -} -function extractChunk(buffer, start, end) { - let uiArray = new Uint8Array(end - start); - let k = 0; - for (let i = start; i < end; i++, k++) { - uiArray[k] = buffer[i]; - } - return uiArray; -} -function setSignedInt32(pkt, data, at) { - pkt.set(new Uint8Array(new Int32Array([data]).buffer), at); - return pkt; -} -function setUint32(pkt, data, at) { - pkt.set(new Uint8Array(new Uint32Array([data]).buffer), at); - return pkt; -} -function setSignedInt64(pkt, data, at) { - if (data === 0) { - pkt.set(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), at); - return pkt; - } - - pkt.set(new Uint8Array(new BigInt64Array([data]).buffer), at); - return pkt; -} -function setUint64(pkt, data, at) { - if (data === 0) { - pkt.set(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]), at); - return pkt; - } - pkt.set(new Uint8Array(new BigUint64Array([data]).buffer), at); - return pkt; -} -function dot2num(dot) { - // the same as ip2long in php - var d = dot.split('.'); - return (+d[0] << 24) + (+d[1] << 16) + (+d[2] << 8) + +d[3]; -} - -function num2array(num) { - return [ - (num & 0xff000000) >>> 24, - (num & 0x00ff0000) >>> 16, - (num & 0x0000ff00) >>> 8, - num & 0x000000ff, - ]; -} - -function htonl(x) { - return dot2num(num2array(x).reverse().join('.')); -} -function is_ipv6_mapped_ipv4(ip) { - return !!ip.match(/^[:]{2}[f]{4}[:]{1}.*$/); -} - -/** - * Convert a 16-bit quantity (short integer) from host byte order to network byte order (Little-Endian to Big-Endian). - * - * @param {Array|Buffer} b Array of octets or a nodejs Buffer - * @param {number} i Zero-based index at which to write into b - * @param {number} v Value to convert - */ -function htons(b, i, v) { - b[i] = 0xff & (v >> 8); - b[i + 1] = 0xff & v; -} -function mapIPv4ToIpv6(ip) { - return '::ffff:' + ip; -} -let Lib = { - dot2num, - hashByteOrder, - hashOfHash, - htonl, - htons, - is_ipv6_mapped_ipv4, - mapIPv4ToIpv6, - hexToBytes, - num2array, - setUint32, - setUint64, - setSignedInt32, - setSignedInt64, - str2uint8, - allZeroes, - calculateCompactSize, - encodeCompactSizeBytes, - bytesToString, -}; -module.exports = Lib; diff --git a/src/network.js b/src/network.js deleted file mode 100644 index 2ab44db..0000000 --- a/src/network.js +++ /dev/null @@ -1,1366 +0,0 @@ -'use strict'; -/** - * There are places in the code where we speak of ipv6 addresses. - * Where posisble, we note with the string 'ipv4-mapped'. If you - * see this, it means that the variable or object expects a IPv6 - * IP address, with the exception that it can be a "IPv4-mapped IPv6 address". - * See: http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses - */ -const LibCliSign = require('./cli-sign.js'); -const crypto = require('crypto'); -const { createHash } = crypto; -const NetUtil = require('./network-util.js'); -const COIN = require('./coin-join-constants.js').COIN; -const hashByteOrder = NetUtil.hashByteOrder; -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Address = DashCore.Address; -const assert = require('assert'); -const FileLib = require('./file.js'); -const DebugLib = require('./debug.js'); -const { d } = DebugLib; - -const Lib = { packet: { parse: {} } }; -module.exports = Lib; - -const PROTOCOL_VERSION = 70227; -const RELAY_PROTOCOL_VERSION_INTRODUCTION = 70001; -const MNAUTH_PROTOCOL_VERSION_INTRODUCTION = 70214; -const MNAUTH_CHALLENGE_SIZE = 32; -const PING_NONCE_SIZE = 8; -const MAINNET = 'mainnet'; -const TESTNET = 'testnet'; -const DEVNET = 'devnet'; -const REGTEST = 'regtest'; -//const DEVNET_PS = 'devnet-privatesend'; -const VALID_NETS = [MAINNET, TESTNET, DEVNET, REGTEST]; -const MAINNET_PORT = 9999; -const TESTNET_PORT = 19999; -const REGTEST_PORT = 19899; -const DEVTEST_PORT = 19799; -//const DEVNET_PS_PORT = 19999; -//const MAX_PAYLOAD_SIZE = 0x02000000; -const MSG_HEADER = { - MAGIC: 4, - COMMAND: 12, - PAYLOAD: 4, - CHECKSUM: 4, -}; - -let POOL_STATE_VALUES = { - IDLE: 0, - QUEUE: 1, - ACCEPTING_ENTRIES: 2, - SIGNING: 3, - ERROR: 4, - SUCCESS: 5, -}; -let POOL_STATE_HUSK = { - toString: function (i) { - for (const psKey in POOL_STATE_VALUES) { - if (POOL_STATE_VALUES[psKey] === i) { - return String(psKey); - } - } - }, -}; -const POOL_STATE = Object.assign(POOL_STATE_HUSK, POOL_STATE_VALUES); - -const POOL_STATUS_UPDATE = { - REJECTED: 0, - ACCEPTED: 1, - toString: function (i) { - switch (i) { - case POOL_STATUS_UPDATE.REJECTED: - return 'REJECTED'; - case POOL_STATUS_UPDATE.ACCEPTED: - return 'ACCEPTED'; - default: - return null; - } - }, -}; - -const MESSAGE_ID = { - ERR_ALREADY_HAVE: 0x00, - ERR_DENOM: 0x01, - ERR_ENTRIES_FULL: 0x02, - ERR_EXISTING_TX: 0x03, - ERR_FEES: 0x04, - ERR_INVALID_COLLATERAL: 0x05, - ERR_INVALID_INPUT: 0x06, - ERR_INVALID_SCRIPT: 0x07, - ERR_INVALID_TX: 0x08, - ERR_MAXIMUM: 0x09, - ERR_MN_LIST: 0x0a, // <-- - ERR_MODE: 0x0b, - ERR_NON_STANDARD_PUBKEY: 0x0c, // (Not used) - ERR_NOT_A_MN: 0x0d, //(Not used) - ERR_QUEUE_FULL: 0x0e, - ERR_RECENT: 0x0f, - ERR_SESSION: 0x10, - ERR_MISSING_TX: 0x11, - ERR_VERSION: 0x12, - MSG_NOERR: 0x13, - MSG_SUCCESS: 0x14, - MSG_ENTRIES_ADDED: 0x15, - ERR_SIZE_MISMATCH: 0x16, - toString: function (i) { - for (const key in MESSAGE_ID) { - if (key === 'toString') { - continue; - } - if (MESSAGE_ID[key] === i) { - return String(key); - } - } - }, -}; - -let PRE_CALC_MESSAGE_HEADER_SIZE = 0; -for (const key in MSG_HEADER) { - PRE_CALC_MESSAGE_HEADER_SIZE += MSG_HEADER[key]; -} -const MESSAGE_HEADER_SIZE = PRE_CALC_MESSAGE_HEADER_SIZE; -const PAYLOAD_OFFSET = MSG_HEADER.MAGIC + MSG_HEADER.COMMAND; -const SENDHEADERS_PAYLOAD_SIZE = 0; -const SENDCMPCT_PAYLOAD_SIZE = 9; -const SENDDSQ_PAYLOAD_SIZE = 1; -const PING_PAYLOAD_SIZE = 8; - -const getVersionSizes = function () { - let SIZES = { - VERSION: 4, - SERVICES: 8, - TIMESTAMP: 8, - ADDR_RECV_SERVICES: 8, - ADDR_RECV_IP: 16, - ADDR_RECV_PORT: 2, - ADDR_TRANS_SERVICES: 8, - ADDR_TRANS_IP: 16, - ADDR_TRANS_PORT: 2, - NONCE: 8, - USER_AGENT_BYTES: 1, // can be skipped - USER_AGENT_STRING: 0, - START_HEIGHT: 4, - // The following 2 fields are OPTIONAL - RELAY: 0, - MNAUTH_CHALLENGE: 0, - }; - return SIZES; -}; -let VERSION_PACKET_MINIMUM_SIZE = 0; -(function () { - VERSION_PACKET_MINIMUM_SIZE = 0; - let sizes = getVersionSizes(); - for (const key in sizes) { - VERSION_PACKET_MINIMUM_SIZE += sizes[key]; - } -})(); - -const NETWORKS = { - [MAINNET]: { - port: MAINNET_PORT, - magic: new Uint8Array([ - //0xBD6B0CBF, - 0xbf, 0x0c, 0x6b, 0xbd, - ]), - start: 0xbf0c6bbd, - nBits: 0x1e0ffff0, - }, - [TESTNET]: { - port: TESTNET_PORT, - magic: new Uint8Array([ - //0xFFCAE2CE, - 0xce, 0xe2, 0xca, 0xff, - ]), - start: 0xcee2caff, - nBits: 0x1e0ffff0, - }, - [REGTEST]: { - port: REGTEST_PORT, - magic: new Uint8Array([ - //0xDCB7C1FC, - 0xfc, 0xc1, 0xb7, 0xdc, - ]), - start: 0xfcc1b7dc, - nBits: 0x207fffff, - }, - [DEVNET]: { - port: DEVTEST_PORT, - magic: new Uint8Array([ - //0xCEFFCAE2, - 0xe2, 0xca, 0xff, 0xce, - ]), - start: 0xe2caffce, - nBits: 0x207fffff, - }, -}; -const RELAY_SIZE = 1; - -let SERVICE_IDENTIFIERS = { - /** - * NODE_UNNAMED: - * This node is not a full node. It may not be - * able to provide any data except for the - * transactions it originates. - */ - NODE_UNNAMED: 0x00, - /** - * NODE_NETWORK: - * This is a full node and can be asked for full - * blocks. It should implement all protocol features - * available in its self-reported protocol version. - */ - NODE_NETWORK: 0x01, - /** - * NODE_GETUTXO: - * This node is capable of responding to the getutxo - * protocol request. Dash Core does not support - * this service. - */ - NODE_GETUTXO: 0x02, - /** - * NODE_BLOOM: - * This node is capable and willing to handle bloom- - * filtered connections. Dash Core nodes used to support - * this by default, without advertising this bit, but - * no longer do as of protocol version 70201 - * (= NO_BLOOM_VERSION) - */ - NODE_BLOOM: 0x04, - /** - * NODE_XTHIN: - * This node supports Xtreme Thinblocks. Dash Core - * does not support this service. - */ - NODE_XTHIN: 0x08, - /** - * NODE_NETWORK_LIMITED: - * This is the same as NODE_NETWORK with the - * limitation of only serving the last 288 blocks. - * Not supported prior to Dash Core 0.16.0 - */ - NODE_NETWORK_LIMITED: 0x400, -}; -Lib.constants = { - MAINNET, - MESSAGE_ID, - MNAUTH_PROTOCOL_VERSION_INTRODUCTION, - MNAUTH_CHALLENGE_SIZE, - NETWORKS, - PING_NONCE_SIZE, - POOL_STATE, - PROTOCOL_VERSION, - RELAY_PROTOCOL_VERSION_INTRODUCTION, - TESTNET, - DEVNET, - REGTEST, - VALID_NETS, - RELAY_SIZE, - SERVICE_IDENTIFIERS, - MESSAGE_HEADER_SIZE, - MSG_HEADER, - VERSION_PACKET_MINIMUM_SIZE, - SENDHEADERS_PAYLOAD_SIZE, - SENDCMPCT_PAYLOAD_SIZE, - SENDDSQ_PAYLOAD_SIZE, - PING_PAYLOAD_SIZE, -}; -let allZeroes = NetUtil.allZeroes; -let hexToBytes = NetUtil.hexToBytes; -let str2uint8 = NetUtil.str2uint8; - -function arbuf_to_hexstr(buffer) { - // buffer is an ArrayBuffer - return [...new Uint8Array(buffer)] - .map((x) => x.toString(16).padStart(2, '0')) - .join(''); -} -function toSerializedFormat(uint8Dsf) { - if (!(uint8Dsf instanceof Uint8Array)) { - throw new Error('parameter must be an instance of Uint8Array'); - } - return arbuf_to_hexstr(uint8Dsf); -} - -function extractInt64(data, at) { - let a = new Uint8Array([ - data[at], - data[at + 1], - data[at + 2], - data[at + 3], - data[at + 4], - data[at + 5], - data[at + 6], - data[at + 7], - ]); - let b = new BigInt64Array(a.buffer); - return b[0]; -} -function extractUint64(data, at) { - let a = new Uint8Array([ - data[at], - data[at + 1], - data[at + 2], - data[at + 3], - data[at + 4], - data[at + 5], - data[at + 6], - data[at + 7], - ]); - let b = new BigUint64Array(a.buffer); - return b[0]; -} -function extractUint32(data, at) { - let a = new Uint8Array([data[at], data[at + 1], data[at + 2], data[at + 3]]); - let b = new Uint32Array(a.buffer); - return b[0]; -} -function accumulateUint32(data, at) { - let uiArray = new Uint32Array([0]); - for (let i = at; i < at + 4; i++) { - uiArray[0] += data[at]; - } - return uiArray[0]; -} -function extractChunk(buffer, start, end) { - let uiArray = new Uint8Array(end - start); - let k = 0; - for (let i = start; i < end; i++, k++) { - uiArray[k] = buffer[i]; - } - return uiArray; -} -let setUint32 = NetUtil.setUint32; -let dot2num = NetUtil.dot2num; -//let num2array = NetUtil.num2array; -let htonl = NetUtil.htonl; -let is_ipv6_mapped_ipv4 = NetUtil.is_ipv6_mapped_ipv4; -let htons = NetUtil.htons; -//let mapIPv4ToIpv6 = NetUtil.mapIPv4ToIpv6; - -Lib.util = NetUtil; - -/** - * First 4 bytes of SHA256(SHA256(payload)) in internal byte order. - */ -const compute_checksum = (payload) => { - let hash = createHash('sha256').update(payload).digest(); - let hashOfHash = createHash('sha256').update(hash).digest(); - return hashOfHash.slice(0, 4); -}; - -const wrap_packet = (net, command_name, payload, payload_size) => { - const SIZES = { - MAGIC_BYTES: 4, - COMMAND_NAME: 12, - PAYLOAD_SIZE: 4, - CHECKSUM: 4, - }; - let TOTAL_SIZE = 0; - for (const key in SIZES) { - TOTAL_SIZE += SIZES[key]; - } - TOTAL_SIZE += payload_size; - - let packet = new Uint8Array(TOTAL_SIZE); - packet.set(NETWORKS[net].magic, 0); - - /** - * Set command_name (char[12]) - */ - let COMMAND_NAME_OFFSET = SIZES.MAGIC_BYTES; - packet.set(str2uint8(command_name), COMMAND_NAME_OFFSET); - - let PAYLOAD_SIZE_OFFSET = COMMAND_NAME_OFFSET + SIZES.COMMAND_NAME; - let CHECKSUM_OFFSET = PAYLOAD_SIZE_OFFSET + SIZES.PAYLOAD_SIZE; - if (payload_size === 0 || payload === null) { - packet.set([0x5d, 0xf6, 0xe0, 0xe2], CHECKSUM_OFFSET); - return packet; - } - packet = setUint32(packet, payload_size, PAYLOAD_SIZE_OFFSET); - packet.set(compute_checksum(payload), CHECKSUM_OFFSET); - /** - * Finally, append the payload to the header - */ - let ACTUAL_PAYLOAD_OFFSET = CHECKSUM_OFFSET + SIZES.CHECKSUM; - packet.set(payload, ACTUAL_PAYLOAD_OFFSET); - return packet; -}; - -Lib.net = { - compute_checksum, - wrap_packet, -}; - -/** - * The arguments to this function closely follow the variable names - * used on this page: https://dashcore.readme.io/docs/core-ref-p2p-network-control-messages#version - * DO NOT convert any values from host to network byte order! The - * function will handle that for you! - */ -function version( - args = { - /** - * Required. - * - * Must be one of the values in NETWORKS constant above. - */ - chosen_network: null, - /** - * Required. - */ - protocol_version: null, - /** - * Required. - */ - services: null, - /** - * Required. - */ - addr_recv_services: null, - /** - * Required. - * - * addr_recv_ip is the ipv6 address of the master node (can be 'ipv4-mapped') - * - * DO NOT convert to big endian! - */ - addr_recv_ip: null, - /** - * Required. - * - * This has to be the port on the master node that you're connecting to. - * This is sometimes a port like 9999, 19999, but it can sometimes be - * a port chosen by the masternode owners themselves. - * - * DO NOT convert to big endian! - */ - addr_recv_port: null, - - /** - * Required. - * - * This has to be the IPv6 IP of our machine (can be 'ipv4-mapped'). - * If you're not sure, leave it null and the library will fill it for you. - * - * DO NOT convert to big endian! - */ - addr_trans_ip: null, - /** - * Required. - * - * This is the port that corresponds to your current socket connection - * to the master node. Usually, the operating system gives you a random - * port. - * - * DO NOT convert to big endian! - */ - addr_trans_port: null, - - /** - * Required. - * - * Start height of your best block chain. - */ - start_height: null, - - /** - * Optional. - * - * If you specify a nonce, be prepared to see that value in verack messages. - */ - nonce: null, - - /** - * Optional. - * - * If you'd like to, you can specify a user agent as a string of bytes. - */ - user_agent: null, - - /** - * Optional. - * - * If you specify a protocol_version that - * is before 70001, this will be ignored. - * This is a bit of a complex field, so I would suggest you - * checkout the docs below. - * - * If you're not sure, just leave it as null or 0x00. - * @see https://dashcore.readme.io/docs/core-ref-p2p-network-control-messages#version - */ - relay: null, - - /** - * Optional. - * - * If you pass in a protocol_version that is less than 70214, - * this field will be ignored. - * - * Use this field if you want a signed response by the masternode - * in it's verack message. See the docs for more info. - * - */ - mnauth_challenge: null, - }, -) { - let SIZES = getVersionSizes(); - - if (!VALID_NETS.includes(args.chosen_network)) { - throw new Error('"chosen_network" is invalid.'); - } - if (!Array.isArray(args.services)) { - throw new Error('"services" needs to be an array'); - } - if ( - args.protocol_version < RELAY_PROTOCOL_VERSION_INTRODUCTION && - 'undefined' !== typeof args.relay - ) { - throw new Error( - `"relay" field is not supported in protocol versions prior to ${RELAY_PROTOCOL_VERSION_INTRODUCTION}`, - ); - } - if ( - args.protocol_version < MNAUTH_PROTOCOL_VERSION_INTRODUCTION && - 'undefined' !== typeof args.mnauth_challenge - ) { - throw new Error( - '"mnauth_challenge" field is not supported in protocol versions prior to MNAUTH_CHALLENGE_OFFSET', - ); - } - if ('undefined' !== typeof args.mnauth_challenge) { - if (!(args.mnauth_challenge instanceof Uint8Array)) { - throw new Error('"mnauth_challenge" field must be a Uint8Array'); - } - if (args.mnauth_challenge.length !== MNAUTH_CHALLENGE_SIZE) { - throw new Error( - `"mnauth_challenge" field must be ${MNAUTH_CHALLENGE_SIZE} bytes long`, - ); - } - } - if ('undefined' !== typeof args.relay) { - SIZES.RELAY = RELAY_SIZE; - } - if ('undefined' !== typeof args.mnauth_challenge) { - SIZES.MNAUTH_CHALLENGE = MNAUTH_CHALLENGE_SIZE; - } - if ( - 'undefined' !== typeof args.user_agent && - 'string' === typeof args.user_agent - ) { - SIZES.USER_AGENT_STRING = args.user_agent.length; - } - - let TOTAL_SIZE = 0; - - for (const key in SIZES) { - TOTAL_SIZE += SIZES[key]; - } - let packet = new Uint8Array(TOTAL_SIZE); - // Protocol version - - packet = setUint32(packet, args.protocol_version, 0); - /** - * Set services to NODE_NETWORK (1) + NODE_BLOOM (4) - */ - const SERVICES_OFFSET = SIZES.VERSION; - let services = 0; - for (const service of args.services) { - services += service; - } - packet.set([services], SERVICES_OFFSET); - - const TIMESTAMP_OFFSET = SERVICES_OFFSET + SIZES.SERVICES; - packet = setUint32(packet, Date.now(), TIMESTAMP_OFFSET); - - let ADDR_RECV_SERVICES_OFFSET = TIMESTAMP_OFFSET + SIZES.TIMESTAMP; - packet.set([services], ADDR_RECV_SERVICES_OFFSET); - - /** - * "ADDR_RECV" means the host that we're sending this traffic to. - * So, in other words, it's the master node - */ - let ADDR_RECV_IP_OFFSET = - ADDR_RECV_SERVICES_OFFSET + SIZES.ADDR_RECV_SERVICES; - let ipBytes = dot2num(args.addr_recv_ip); - let inv = htonl(ipBytes); - packet = setUint32(packet, inv, ADDR_RECV_IP_OFFSET); - - /** - * Copy address recv port - */ - let ADDR_RECV_PORT_OFFSET = ADDR_RECV_IP_OFFSET + SIZES.ADDR_RECV_IP; - let portBuffer = new Uint8Array(2); - htons(portBuffer, 0, args.addr_recv_port); - packet.set(portBuffer, ADDR_RECV_PORT_OFFSET); - - /** - * Copy address transmitted services - */ - let ADDR_TRANS_SERVICES_OFFSET = ADDR_RECV_PORT_OFFSET + SIZES.ADDR_RECV_PORT; - packet.set([services], ADDR_TRANS_SERVICES_OFFSET); - - /** - * We add the extra 10, so that we can encode an ipv4-mapped ipv6 address - */ - let ADDR_TRANS_IP_OFFSET = - ADDR_TRANS_SERVICES_OFFSET + SIZES.ADDR_TRANS_SERVICES; - let transmittingIP = args.addr_trans_ip; - if (is_ipv6_mapped_ipv4(transmittingIP)) { - let ipBytes = dot2num(transmittingIP.split(':').reverse()[0]); - let inv = htonl(ipBytes); - packet = setUint32(packet, inv, ADDR_TRANS_IP_OFFSET + 12); - packet.set([0xff, 0xff], ADDR_TRANS_IP_OFFSET + 10); // we add the 10 so that we can fill the latter 6 bytes - } else { - /** TODO: */ - } - - let ADDR_TRANS_PORT_OFFSET = ADDR_TRANS_IP_OFFSET + SIZES.ADDR_TRANS_IP; - portBuffer = new Uint8Array(2); - htons(portBuffer, 0, args.addr_trans_port); - packet.set(portBuffer, ADDR_TRANS_PORT_OFFSET); - - // this can be left zero - let NONCE_OFFSET = ADDR_TRANS_PORT_OFFSET + SIZES.ADDR_TRANS_PORT; - if ('undefined' !== typeof args.nonce) { - if (args.nonce instanceof Uint8Array) { - packet.set(args.nonce, NONCE_OFFSET); - } else { - throw new Error('"nonce" field must be an array of 8 bytes'); - } - } else { - packet.set(new Uint8Array(SIZES.NONCE), NONCE_OFFSET); - } - - let USER_AGENT_BYTES_OFFSET = NONCE_OFFSET + SIZES.NONCE; - if (null !== args.user_agent && typeof args.user_agent === 'string') { - let userAgentSize = args.user_agent.length; - packet.set([userAgentSize], USER_AGENT_BYTES_OFFSET); - packet.set(str2uint8(args.user_agent), USER_AGENT_BYTES_OFFSET + 1); - } else { - packet.set([0x0], USER_AGENT_BYTES_OFFSET); - } - - // Skipping user agent. it can be zero - let START_HEIGHT_OFFSET = - USER_AGENT_BYTES_OFFSET + SIZES.USER_AGENT_BYTES + SIZES.USER_AGENT_STRING; - packet = setUint32(packet, args.start_height, START_HEIGHT_OFFSET); - - let RELAY_OFFSET = START_HEIGHT_OFFSET + SIZES.START_HEIGHT; - if ('undefined' !== typeof args.relay) { - packet.set([args.relay ? 0x01 : 0x00], RELAY_OFFSET); - } - - let MNAUTH_CHALLENGE_OFFSET = RELAY_OFFSET + SIZES.RELAY; - if ('undefined' !== typeof args.mnauth_challenge) { - packet.set(args.mnauth_challenge, MNAUTH_CHALLENGE_OFFSET); - } - packet = wrap_packet(args.chosen_network, 'version', packet, TOTAL_SIZE); - return packet; -} -function getaddr() { - const cmd = 'getaddr'; - const MAGIC_BYTES_SIZE = 4; - const COMMAND_SIZE = 12; - const PAYLOAD_SIZE = 4; - const CHECKSUM_SIZE = 4; - const TOTAL_SIZE = - MAGIC_BYTES_SIZE + COMMAND_SIZE + PAYLOAD_SIZE + CHECKSUM_SIZE; - let packet = new Uint8Array(TOTAL_SIZE); - // TESTNET magic bytes - packet[0] = 0xce; - packet[1] = 0xe2; - packet[2] = 0xca; - packet[3] = 0xff; - // point us to the beginning of the command name char[12] - let cmdArray = str2uint8(cmd); - packet.set(cmdArray, MAGIC_BYTES_SIZE); - - packet.set( - [0x5d, 0xf6, 0xe0, 0xe2], - MAGIC_BYTES_SIZE + COMMAND_SIZE + PAYLOAD_SIZE, - ); - return packet; -} - -function pong( - args = { - chosen_network: null, - nonce: null, - }, -) { - let nonceBuffer = new Uint8Array(PING_NONCE_SIZE); - nonceBuffer.set(args.nonce, 0); - return wrap_packet(args.chosen_network, 'pong', nonceBuffer, PING_NONCE_SIZE); -} -function verack( - args = { - chosen_network: null, - }, -) { - return wrap_packet(args.chosen_network, 'verack', null, 0); -} -function senddsq( - args = { - chosen_network: null, - fSendDSQueue: null, - }, -) { - let buffer = new Uint8Array([args.fSendDSQueue ? 1 : 0]); - return wrap_packet(args.chosen_network, 'senddsq', buffer, buffer.length); -} -function sendaddrv2( - args = { - chosen_network: null, - }, -) { - return wrap_packet(args.chosen_network, 'sendaddrv2', null, 0); -} -function sendaddr( - args = { - chosen_network: null, - }, -) { - return wrap_packet(args.chosen_network, 'sendaddr', null, 0); -} - -const CJDenoms = require('./coin-join-constants.js').STANDARD_DENOMINATIONS; -let CJLib = require('./coin-join-denominations.js'); - -function isStandardDenomination(d) { - return CJDenoms.includes(d); -} - -function dsa( - args = { - chosen_network: null, // 'testnet' - denomination: null, // COIN / 1000 + 1 - collateral: null, // see: ctransaction.js - }, -) { - if (!isStandardDenomination(args.denomination)) { - throw new Error('Invalid denomination value'); - } - let encodedDenom = CJLib.AmountToDenomination(args.denomination); - if (encodedDenom === 0) { - throw new Error("Couldn't serialize denomination"); - } - - const SIZES = { - DENOMINATION: 4, - COLLATERAL: args.collateral.length, - }; - let TOTAL_SIZE = 0; - for (const key in SIZES) { - TOTAL_SIZE += SIZES[key]; - } - - let offset = 0; - /** - * Packet payload - */ - let packet = new Uint8Array(TOTAL_SIZE); - - packet.set([encodedDenom, 0, 0, 0], offset); - - offset += SIZES.DENOMINATION; - //console.debug("collateral size:", args.collateral.length); - packet.set(args.collateral, offset); - - //console.debug({ packet }); - - return wrap_packet(args.chosen_network, 'dsa', packet, packet.length); -} -function dsc() {} -function dsf() {} -function dsi( - args = { - chosen_network: null, // 'testnet' - collateralTxn: null, - denominatedAmount: null, - client_session: null, - }, -) { - let client_session = args.client_session; - let denominatedAmount = args.denominatedAmount; - if (!(args.collateralTxn instanceof Transaction)) { - throw new Error('collateralTxn must be Transaction'); - } - let utxos = []; - for (const input of client_session.mixing_inputs) { - utxos.push(input.utxo); - } - let userInputTxn = new Transaction().from(utxos); - let userOutputTxn = new Transaction(); - for (const address of client_session.generated_addresses) { - userOutputTxn.to(Address.fromString(address.address), denominatedAmount); - } - - // FIXME: very hacky - let trimmedUserInput = userInputTxn - .uncheckedSerialize() - .substr(8) - .replace(/[0]{10}$/, ''); - - //d({ - // collateral: args.collateralTxn, - // serialized: args.collateralTxn.uncheckedSerialize(), - //}); - let collateralTxn = hexToBytes(args.collateralTxn.uncheckedSerialize()); - - // FIXME: very hacky - let trimmedUserOutput = userOutputTxn - .uncheckedSerialize() - .substr(10) - .replace(/[0]{8}$/, ''); - //dd(trimmedUserOutput); - - let userInputPayload = hexToBytes(trimmedUserInput); - let userOutputPayload = hexToBytes(trimmedUserOutput); - - let TOTAL_SIZE = - userInputPayload.length + collateralTxn.length + userOutputPayload.length; - - /** - * Packet payload - */ - let offset = 0; - let packet = new Uint8Array(TOTAL_SIZE); - /** - * Set the user inputs - */ - packet.set(userInputPayload); - offset += userInputPayload.length; - - /** - * Set the collateral txn(s) - */ - packet.set(collateralTxn, offset); - offset += collateralTxn.length; - - /** - * Set the outputs - */ - packet.set(userOutputPayload, offset); - - assert.equal( - packet.length, - TOTAL_SIZE, - 'packet length doesnt match TOTAL_SIZE', - ); - - return wrap_packet(args.chosen_network, 'dsi', packet, TOTAL_SIZE); -} - -async function dss( - args = { - chosen_network: null, - dsfPacket: null, - client_session: null, - dboot: null, - }, -) { - /** - * User inputs - * ----------- - * (for now) support only up to 252 inputs (FIXME: use compactSize integer encoding here) - */ - let client_session = args.client_session; - let TOTAL_SIZE = 0; - TOTAL_SIZE += 1; // input size length - client_session.signatures = {}; - for (const input of client_session.mixing_inputs) { - TOTAL_SIZE += 32; - TOTAL_SIZE += 4; - TOTAL_SIZE += 1; // script length - let rawSig = input.signed.inputs[0]._script.toHex(); - let encodedSignature = hexToBytes(rawSig); - let len = encodedSignature.length; - d({ len, rawSig, encodedSignature }); - TOTAL_SIZE += len; - TOTAL_SIZE += 4; - } - let rel_path = `dss-${client_session.username}-#DATE#`; - await FileLib.write_json(rel_path, client_session); - - let packet = new Uint8Array(TOTAL_SIZE); - let offset = 0; - packet.set([client_session.mixing_inputs.length], offset); - offset += 1; - - for (const input of client_session.mixing_inputs) { - packet.set(hexToBytes(hashByteOrder(input.utxo.txId)), offset); - assert.equal( - hexToBytes(input.utxo.txId).length, - 32, - 'txid should equal 32 bytes', - ); - offset += 32; - packet = setUint32(packet, input.utxo.outputIndex, offset); - offset += 4; - let rawSig = input.signed.inputs[0]._script.toHex(); - let encodedSignature = hexToBytes(rawSig); - let len = encodedSignature.length; - packet.set([len], offset); - offset += 1; - packet.set(encodedSignature, offset); - offset += len; - packet = setUint32(packet, 0xffffffff, offset); - offset += 4; - } - return wrap_packet(args.chosen_network, 'dss', packet, TOTAL_SIZE); -} - -function dsq() {} -function dssu() {} -function dstx() {} - -Lib.packet.messagesWithNoPayload = [ - 'filterclear', - 'getaddr', - 'getsporks', - 'mempool', - 'sendaddr', - 'sendaddrv2', - 'sendheaders', - 'sendheaders2', - 'verack', -]; -Lib.packet.parse.extractItems = function (buffer, items) { - let extracted = []; - for (let item of items) { - switch (item) { - case 'command': - extracted.push(Lib.packet.parse.commandName(buffer)); - break; - case 'payloadSize': - extracted.push(Lib.packet.parse.payloadSize(buffer)); - break; - case 'magic': - extracted.push(Lib.packet.parse.magicBytes(buffer)); - break; - default: - break; - } - } - return extracted; -}; - -Lib.packet.parse.extractPingNonce = function (buffer) { - let offset = MESSAGE_HEADER_SIZE; - let k = 0; - let buf = new Uint8Array(PING_NONCE_SIZE); - for (let i = offset; i < MESSAGE_HEADER_SIZE + PING_NONCE_SIZE; i++, k++) { - buf[k] = buffer[i]; - } - return buf; -}; -Lib.packet.parse.hasPayload = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - return !Lib.packet.messagesWithNoPayload.includes( - Lib.packet.parse.commandName(buffer), - ); -}; -Lib.packet.parse.payloadSize = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - if (buffer.length < MESSAGE_HEADER_SIZE) { - return null; - } - let uiBuffer = new Uint32Array([0]); - uiBuffer[0] = buffer[PAYLOAD_OFFSET]; - uiBuffer[0] += buffer[PAYLOAD_OFFSET + 1]; - uiBuffer[0] += buffer[PAYLOAD_OFFSET + 2]; - uiBuffer[0] += buffer[PAYLOAD_OFFSET + 3]; - return uiBuffer[0]; -}; -Lib.packet.parse.magicBytes = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let copy = new Uint8Array(4); - for (let i = 0; i < 4; i++) { - copy[i] = buffer[i]; - } - return copy; -}; - -Lib.packet.parse.identifyMagicBytes = function (buffer) { - for (let key in NETWORKS) { - let bytesMatched = 0; - for (let i = 0; i < 4; i++) { - if (NETWORKS[key].magic[i] !== buffer[i]) { - bytesMatched = 0; - break; - } - ++bytesMatched; - } - if (bytesMatched === 4) { - return key; - } - } - return null; -}; - -Lib.packet.parse.commandName = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let cmd = ''; - for (let i = 4; i < 16 && buffer[i] !== 0x0; ++i) { - cmd += String.fromCharCode(buffer[i]); - } - return cmd; -}; - -Lib.packet.parse.getheaders = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let commandName = Lib.packet.parse.commandName(buffer); - if (commandName !== 'getheaders') { - throw new Error('Not a getheaders packet'); - } - let parsed = { - version: new Uint8Array(4), - hashCount: 0, - hashes: [], - }; - /** - * getheaders message structure: - * version - 4 bytes - * hash count - varies - * block header hashes - varies - */ - for (let i = 0; i < 4; i++) { - parsed.version[i] = buffer[i]; - } - parsed.hashCount = accumulateUint32(buffer, 4); - const OFFSET = 8; - const HASH_SIZE = 32; - let hash = new Uint8Array(32); - for (let i = 0; i < parsed.hashCount; i++) { - hash = extractChunk( - buffer, - OFFSET + i * HASH_SIZE, - OFFSET + i * HASH_SIZE + HASH_SIZE, - ); - if (allZeroes(hash)) { - continue; - } - parsed.hashes.push(hash); - } - parsed.hashCount = parsed.hashes.length; - return parsed; -}; - -Lib.packet.parse.dsq = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let commandName = Lib.packet.parse.commandName(buffer); - if (commandName !== 'dsq') { - throw new Error('Not a dsq packet'); - } - let parsed = { - nDenom: 0, - proTxHash: 0, - nTime: 0, - fReady: false, - vchSig: null, - }; - const SIZES = { - DENOM: 4, - PROTX: 32, - TIME: 8, - READY: 1, - SIG: 97, - }; - - //console.debug("Size of dsq packet:", buffer.length); - /** - * We'll need to point past the message header in - * order to get to the dsq packet details. - */ - let offset = MESSAGE_HEADER_SIZE; - - /** - * Grab the denomination - */ - parsed.nDenom = extractUint32(buffer, offset); - offset += SIZES.DENOM; - - /** - * Grab the protxhash - */ - parsed.proTxHash = extractChunk(buffer, offset, offset + SIZES.PROTX); - offset += SIZES.PROTX; - - /** - * Grab the time - */ - parsed.nTime = extractInt64(buffer, offset); - offset += SIZES.TIME; - - /** - * Grab the fReady - */ - parsed.fReady = buffer[offset]; - offset += SIZES.READY; - - parsed.vchSig = extractChunk(buffer, offset, offset + SIZES.SIG); - return parsed; -}; -Lib.packet.parse.dssu = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let commandName = Lib.packet.parse.commandName(buffer); - if (commandName !== 'dssu') { - throw new Error('Not a dssu packet'); - } - let parsed = { - session_id: 0, - state: 0, - entries_count: 0, - status_update: 0, - message_id: 0, - }; - /** - * 4 nMsgSessionID - Required - Session ID - * 4 nMsgState - Required - Current state of processing - * 4 nMsgEntriesCount - Required - Number of entries in the pool (deprecated) - * 4 nMsgStatusUpdate - Required - Update state and/or signal if entry was accepted or not - * 4 nMsgMessageID - Required - ID of the typical masternode reply message - */ - const SIZES = { - SESSION_ID: 4, - STATE: 4, - ENTRIES_COUNT: 4, - STATUS_UPDATE: 4, - MESSAGE_ID: 4, - }; - - //console.debug("Size of dssu packet:", buffer.length); - /** - * We'll need to point past the message header in - * order to get to the dssu packet details. - */ - let offset = MESSAGE_HEADER_SIZE; - - /** - * Grab the session id - */ - parsed.session_id = extractUint32(buffer, offset); - offset += SIZES.SESSION_ID; - - /** - * Grab the state - */ - let state = extractUint32(buffer, offset); - offset += SIZES.STATE; - - ///** - // * Grab the entries count - // Not parsed because apparently master nodes no longer send - // the entries count. - // */ - //parsed.entries_count = extractUint32(buffer, offset); - //offset += SIZES.ENTRIES_COUNT; - - /** - * Grab the status update - */ - let status_update = extractUint32(buffer, offset); - offset += SIZES.STATUS_UPDATE; - - /** - * Grab the message id - */ - let message_id = extractUint32(buffer, offset); - parsed.message_id = [message_id, MESSAGE_ID.toString(message_id)]; - parsed.state = [state, POOL_STATE.toString(state)]; - parsed.status_update = [ - status_update, - POOL_STATUS_UPDATE.toString(status_update), - ]; - return parsed; -}; -Lib.packet.parse.dsf = function (buffer) { - if (!(buffer instanceof Uint8Array)) { - throw new Error('Must be an instance of Uint8Array'); - } - let commandName = Lib.packet.parse.commandName(buffer); - if (commandName !== 'dsf') { - throw new Error('Not a dsf packet'); - } - let parsed = { - sessionID: 0, - transaction: { - version: 0, - inputCount: 0, - inputs: [], - outputCount: 0, - outputs: [], - }, - }; - const TXID_HASH_SIZE = 32; - const VOUT_SIZE = 4; - const SIGSCRIPT_SIZE = 1; - const SEQUENCE_NUMBER_SIZE = 4; - /** - * TRANSACTION_SIZE: - * 1) A TxID Hash (32 bytes) - * 2) vout (4 bytes) - * 3) sigscript bytes (1 byte) - * 4) sequence number (4 bytes) - */ - //const TRANSACTION_SIZE = - // TXID_HASH_SIZE + VOUT_SIZE + SIGSCRIPT_SIZE + SEQUENCE_NUMBER_SIZE; - let SIZES = { - SESSIONID: 4, - VERSION: 4, - INPUT_COUNT: 1, - INPUTS: 0, - OUTPUT_COUNT: 1, - OUTPUTS: 0, - LOCKTIME: 4, - }; - - //console.debug('Size of dsf packet:', buffer.length); - /** - * We'll need to point past the message header in - * order to get to the dsq packet details. - */ - let offset = MESSAGE_HEADER_SIZE; - - /** - * Grab the SESSION ID - */ - parsed.sessionID = extractUint32(buffer, offset); - offset += SIZES.SESSIONID; - - /** - * Grab the VERSION - */ - parsed.transaction.version = parseInt( - extractChunk(buffer, offset, offset + SIZES.VERSION), - 10, - ); - offset += SIZES.VERSION; - - /** - * Grab the INPUT COUNT - */ - // FIXME: parse compactSize int - parsed.transaction.inputCount = parseInt( - extractChunk(buffer, offset, offset + SIZES.INPUT_COUNT), - 10, - ); - - let inputs = parseInt(parsed.transaction.inputCount, 10); - if (isNaN(inputs)) { - throw new Error('tx input count is not a valid integer'); - } - offset += SIZES.INPUT_COUNT; - for (let i = 0; i < inputs; i++) { - let transaction = {}; - transaction.txid = toSerializedFormat( - extractChunk(buffer, offset, offset + TXID_HASH_SIZE), - ); - offset += TXID_HASH_SIZE; - transaction.vout = parseInt(extractUint32(buffer, offset), 10); - offset += VOUT_SIZE; - transaction.sigscript_bytes = parseInt( - extractChunk(buffer, offset, offset + SIGSCRIPT_SIZE), - 10, - ); - offset += SIGSCRIPT_SIZE; - transaction.sequence = parseInt(extractUint32(buffer, offset), 10); - offset += SEQUENCE_NUMBER_SIZE; - parsed.transaction.inputs.push(transaction); - } - - parsed.transaction.outputCount = parseInt( - extractChunk(buffer, offset, offset + SIZES.OUTPUT_COUNT), - 10, - ); - if (isNaN(parsed.transaction.outputCount)) { - throw new Error("couldn't parse output count"); - } - offset += SIZES.OUTPUT_COUNT; - for (let i = 0; i < parsed.transaction.outputCount; i++) { - let output = {}; - output.duffs = extractUint64(buffer, offset); - offset += 8; - output.pubkey_script_bytes = parseInt( - extractChunk(buffer, offset, offset + 1), - 10, - ); - offset += 1; - output.pubkey_script = extractChunk( - buffer, - offset, - offset + output.pubkey_script_bytes, - ); - offset += output.pubkey_script_bytes; - parsed.transaction.outputs.push(output); - } - - parsed.raw_buffer = buffer; - return parsed; -}; -function dd(...args) { - console.debug(...args); - process.exit(); -} -Lib.packet.coinjoin = { - dsa, - dsc, - dsf, - dsi, - dsq, - dssu, - dss, - dstx, -}; -Lib.packet = Object.assign(Lib.packet, { - getaddr, - senddsq, - sendaddr, - sendaddrv2, - pong, - verack, - version, -}); -function dumpAsHex(arr, prefix = null) { - let ctr = 0; - for (const ch of arr) { - process.stdout.write([prefix ?? '', ch.toString(16), ','].join('')); - if (++ctr % 8 === 0) { - process.stdout.write('\n'); - } - } -} -if (process.argv.includes('--run-dsf-test')) { - (async function () { - const buffer = require('./example-dsf.js').example; - let dsf = Lib.packet.parse.dsf(buffer); - dd( - JSON.stringify( - dsf, - (key, value) => - typeof value === 'bigint' ? value.toString() + 'n' : value, - 2, - ), - ); - process.exit(0); - })(); -} -Lib.util.toSerializedFormat = toSerializedFormat; -Lib.util.dumpAsHex = dumpAsHex; diff --git a/src/opcodes.js b/src/opcodes.js deleted file mode 100644 index 178c9b5..0000000 --- a/src/opcodes.js +++ /dev/null @@ -1,149 +0,0 @@ -const OP_0 = 0x00; -const OP_1 = 0x51; -const OP_CHECKLOCKTIMEVERIFY = 0xb1; -const OP_CHECKSEQUENCEVERIFY = 0xb2; -const OPCODES = { - // push value - OP_0, - OP_FALSE: OP_0, - OP_PUSHDATA1: 0x4c, - OP_PUSHDATA2: 0x4d, - OP_PUSHDATA4: 0x4e, - OP_1NEGATE: 0x4f, - OP_RESERVED: 0x50, - OP_1, - OP_TRUE: OP_1, - OP_2: 0x52, - OP_3: 0x53, - OP_4: 0x54, - OP_5: 0x55, - OP_6: 0x56, - OP_7: 0x57, - OP_8: 0x58, - OP_9: 0x59, - OP_10: 0x5a, - OP_11: 0x5b, - OP_12: 0x5c, - OP_13: 0x5d, - OP_14: 0x5e, - OP_15: 0x5f, - OP_16: 0x60, - - // control - OP_NOP: 0x61, - OP_VER: 0x62, - OP_IF: 0x63, - OP_NOTIF: 0x64, - OP_VERIF: 0x65, - OP_VERNOTIF: 0x66, - OP_ELSE: 0x67, - OP_ENDIF: 0x68, - OP_VERIFY: 0x69, - OP_RETURN: 0x6a, - - // stack ops - OP_TOALTSTACK: 0x6b, - OP_FROMALTSTACK: 0x6c, - OP_2DROP: 0x6d, - OP_2DUP: 0x6e, - OP_3DUP: 0x6f, - OP_2OVER: 0x70, - OP_2ROT: 0x71, - OP_2SWAP: 0x72, - OP_IFDUP: 0x73, - OP_DEPTH: 0x74, - OP_DROP: 0x75, - OP_DUP: 0x76, - OP_NIP: 0x77, - OP_OVER: 0x78, - OP_PICK: 0x79, - OP_ROLL: 0x7a, - OP_ROT: 0x7b, - OP_SWAP: 0x7c, - OP_TUCK: 0x7d, - - // splice ops - OP_CAT: 0x7e, - OP_SPLIT: 0x7f, - OP_SIZE: 0x82, - - // conversion ops - OP_NUM2BIN: 0x80, - OP_BIN2NUM: 0x81, - - // bit logic - OP_INVERT: 0x83, - OP_AND: 0x84, - OP_OR: 0x85, - OP_XOR: 0x86, - OP_EQUAL: 0x87, - OP_EQUALVERIFY: 0x88, - OP_RESERVED1: 0x89, - OP_RESERVED2: 0x8a, - - // numeric - OP_1ADD: 0x8b, - OP_1SUB: 0x8c, - OP_2MUL: 0x8d, - OP_2DIV: 0x8e, - OP_NEGATE: 0x8f, - OP_ABS: 0x90, - OP_NOT: 0x91, - OP_0NOTEQUAL: 0x92, - - OP_ADD: 0x93, - OP_SUB: 0x94, - OP_MUL: 0x95, - OP_DIV: 0x96, - OP_MOD: 0x97, - OP_LSHIFT: 0x98, - OP_RSHIFT: 0x99, - - OP_BOOLAND: 0x9a, - OP_BOOLOR: 0x9b, - OP_NUMEQUAL: 0x9c, - OP_NUMEQUALVERIFY: 0x9d, - OP_NUMNOTEQUAL: 0x9e, - OP_LESSTHAN: 0x9f, - OP_GREATERTHAN: 0xa0, - OP_LESSTHANOREQUAL: 0xa1, - OP_GREATERTHANOREQUAL: 0xa2, - OP_MIN: 0xa3, - OP_MAX: 0xa4, - - OP_WITHIN: 0xa5, - - // crypto - OP_RIPEMD160: 0xa6, - OP_SHA1: 0xa7, - OP_SHA256: 0xa8, - OP_HASH160: 0xa9, - OP_HASH256: 0xaa, - OP_CODESEPARATOR: 0xab, - OP_CHECKSIG: 0xac, - OP_CHECKSIGVERIFY: 0xad, - OP_CHECKMULTISIG: 0xae, - OP_CHECKMULTISIGVERIFY: 0xaf, - - // expansion - OP_NOP1: 0xb0, - OP_CHECKLOCKTIMEVERIFY, - OP_NOP2: OP_CHECKLOCKTIMEVERIFY, - OP_CHECKSEQUENCEVERIFY, - OP_NOP3: OP_CHECKSEQUENCEVERIFY, - OP_NOP4: 0xb3, - OP_NOP5: 0xb4, - OP_NOP6: 0xb5, - OP_NOP7: 0xb6, - OP_NOP8: 0xb7, - OP_NOP9: 0xb8, - OP_NOP10: 0xb9, - - // More crypto - OP_CHECKDATASIG: 0xba, - OP_CHECKDATASIGVERIFY: 0xbb, - - OP_INVALIDOPCODE: 0xff, -}; - -module.exports = OPCODES; diff --git a/src/options.js b/src/options.js deleted file mode 100644 index 833f013..0000000 --- a/src/options.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * A port of DASH core's CoinJoin CCoinJoinClientOptions class - */ - -const DEFAULT_COINJOIN_SESSIONS = 4; -const DEFAULT_COINJOIN_ROUNDS = 4; -const DEFAULT_COINJOIN_AMOUNT = 1000; -const DEFAULT_COINJOIN_DENOMS_GOAL = 50; -const DEFAULT_COINJOIN_DENOMS_HARDCAP = 300; -const DEFAULT_COINJOIN_AUTOSTART = false; -const DEFAULT_COINJOIN_MULTISESSION = false; - -let Lib = {}; -module.exports = Lib; -/* Application wide mixing options */ -Lib.CCoinJoinClientOptions = {}; -Lib.CCoinJoinClientOptions._instance = null; -Lib.CCoinJoinClientOptions.GetSessions = function () { - return Lib._instance.nCoinJoinSessions; -}; -Lib.CCoinJoinClientOptions.GetRounds = function () { - return Lib._instance.nCoinJoinRounds; -}; -Lib.CCoinJoinClientOptions.GetRandomRounds = function () { - return Lib._instance.nCoinJoinRandomRounds; -}; -Lib.CCoinJoinClientOptions.GetAmount = function () { - return Lib._instance.nCoinJoinAmount; -}; -Lib.CCoinJoinClientOptions.GetDenomsGoal = function () { - return Lib._instance.nCoinJoinDenomsGoal; -}; -Lib.CCoinJoinClientOptions.GetDenomsHardCap = function () { - return Lib._instance.nCoinJoinDenomsHardCap; -}; -Lib.CCoinJoinClientOptions.IsEnabled = function () { - return Lib._instance.fEnableCoinJoin; -}; -Lib.IsMultiSessionEnabled = function () { - return Lib._instance.fCoinJoinMultiSession; -}; - -Lib.CCoinJoinClientOptions.SetEnabled = function (fEnabled) { - Lib._instance.fEnableCoinJoin = fEnabled; -}; -Lib.CCoinJoinClientOptions.SetMultiSessionEnabled = function (fEnabled) { - Lib._instance.fCoinJoinMultiSession = fEnabled; -}; -Lib.CCoinJoinClientOptions.SetRounds = function (nRounds) { - Lib._instance.nCoinJoinRounds = nRounds; -}; -Lib.CCoinJoinClientOptions.SetAmount = function (amount) { - Lib._instance.nCoinJoinAmount = amount; -}; -Lib.CCoinJoinClientOptions.Init = function () { - Lib._instance = {}; - Lib._instance.fCoinJoinMultiSession = DEFAULT_COINJOIN_MULTISESSION; - Lib._instance.nCoinJoinSessions = DEFAULT_COINJOIN_SESSIONS; - Lib._instance.nCoinJoinRounds = DEFAULT_COINJOIN_ROUNDS; - Lib._instance.nCoinJoinAmount = DEFAULT_COINJOIN_AMOUNT; - Lib._instance.nCoinJoinDenomsGoal = DEFAULT_COINJOIN_DENOMS_GOAL; - Lib._instance.nCoinJoinDenomsHardCap = DEFAULT_COINJOIN_DENOMS_HARDCAP; -}; - -Lib.CCoinJoinClientOptions.GetJsonInfo = function () { - return JSON.stringify({ - enabled: Lib._instance.fEnableCoinJoin, - multisession: Lib._instance.fCoinJoinMultiSession, - max_sessions: Lib._instance.nCoinJoinSessions, - max_rounds: Lib._instance.nCoinJoinRounds, - max_amount: Lib._instance.nCoinJoinAmount, - denoms_goal: Lib._instance.nCoinJoinDenomsGoal, - denoms_hardcap: Lib._instance.nCoinJoinDenomsHardCap, - }); -}; diff --git a/src/pool-state.js b/src/pool-state.js deleted file mode 100644 index 3288698..0000000 --- a/src/pool-state.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * A port of DASH core's CCoinJoinClientManager - */ - -let Lib = {}; -module.exports = Lib; -Lib.POOL_STATE_IDLE = 0; -Lib.POOL_STATE_QUEUE = 1; -Lib.POOL_STATE_ACCEPTING_ENTRIES = 2; -Lib.POOL_STATE_SIGNING = 3; -Lib.POOL_STATE_ERROR = 4; -Lib.POOL_STATE_MIN = Lib.POOL_STATE_IDLE; -Lib.POOL_STATE_MAX = Lib.POOL_STATE_ERROR; diff --git a/src/protocol-constants.js b/src/protocol-constants.js deleted file mode 100644 index bf5d110..0000000 --- a/src/protocol-constants.js +++ /dev/null @@ -1,63 +0,0 @@ -// primary actions -const SER_NETWORK = 1 << 0; -const SER_DISK = 1 << 1; -const SER_GETHASH = 1 << 2; -const PROTOCOL_VERSION = 70227; // from version.h -//! initial proto version, to be increased after version/verack negotiation -const /* int */ INIT_PROTO_VERSION = 209; - -//! disconnect from peers older than this proto version -const /* int */ MIN_PEER_PROTO_VERSION = 70215; - -//! minimum proto version of masternode to accept in DKGs -const /* int */ MIN_MASTERNODE_PROTO_VERSION = 70227; - -//! nTime field added to CAddress, starting with this version; -//! if possible, avoid requesting addresses nodes older than this -const /* int */ CADDR_TIME_VERSION = 31402; - -//! protocol version is included in MNAUTH starting with this version -const /* int */ MNAUTH_NODE_VER_VERSION = 70218; - -//! /* int */roduction of QGETDATA/QDATA messages -const /* int */ LLMQ_DATA_MESSAGES_VERSION = 70219; - -//! /* int */roduction of instant send deterministic lock (ISDLOCK) -const /* int */ ISDLOCK_PROTO_VERSION = 70220; - -//! GOVSCRIPT was activated in this version -const /* int */ GOVSCRIPT_PROTO_VERSION = 70221; - -//! ADDRV2 was /* int */roduced in this version -const /* int */ ADDRV2_PROTO_VERSION = 70223; - -//! CCoinJoinStatusUpdate bug fix was /* int */roduced in this version -const /* int */ COINJOIN_SU_PROTO_VERSION = 70224; - -//! BLS scheme was /* int */roduced in this version -const /* int */ BLS_SCHEME_PROTO_VERSION = 70225; - -//! DSQ and DSTX started using protx hash in this version -const /* int */ COINJOIN_PROTX_HASH_PROTO_VERSION = 70226; - -//! Masternode type was /* int */roduced in this version -const /* int */ DMN_TYPE_PROTO_VERSION = 70227; - -module.exports = { - SER_NETWORK, - SER_DISK, - SER_GETHASH, - INIT_PROTO_VERSION, - MIN_PEER_PROTO_VERSION, - MIN_MASTERNODE_PROTO_VERSION, - CADDR_TIME_VERSION, - MNAUTH_NODE_VER_VERSION, - LLMQ_DATA_MESSAGES_VERSION, - ISDLOCK_PROTO_VERSION, - GOVSCRIPT_PROTO_VERSION, - ADDRV2_PROTO_VERSION, - COINJOIN_SU_PROTO_VERSION, - BLS_SCHEME_PROTO_VERSION, - COINJOIN_PROTX_HASH_PROTO_VERSION, - DMN_TYPE_PROTO_VERSION, -}; diff --git a/src/random.js b/src/random.js deleted file mode 100644 index 5544a65..0000000 --- a/src/random.js +++ /dev/null @@ -1,7 +0,0 @@ -let Lib = {}; -module.exports = Lib; - -const crypto = require('crypto'); -Lib.GetRandInt = function (max) { - return crypto.randomInt(max); -}; diff --git a/src/sanitizers.js b/src/sanitizers.js deleted file mode 100755 index a085178..0000000 --- a/src/sanitizers.js +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env node -'use strict'; -function sanitize_username(u) { - return u.replace(/[^a-f0-9]+/gi, '').replace(/[\n]+$/, ''); -} - -function sanitize_txid(txid) { - return txid.replace(/[^a-f0-9]+/gi, '').replace(/[\n]+$/, ''); -} -function sanitize_address(address) { - if (address === null || typeof address === 'undefined') { - throw new Error('empty address'); - } - return address.replace(/[^a-zA-Z0-9]+/gi, '').replace(/[\n]+$/, ''); -} - -function sanitize_addresses(list) { - let flist = []; - for (const row of list) { - if (row === null || typeof row === 'undefined') { - continue; - } - flist.push(row.replace(/[^a-zA-Z0-9]+/gi, '')); - } - return flist; -} -function sanitize_psbt(str) { - if (typeof str === 'undefined' || str === null) { - return ''; - } - return String(str).replace(/[^a-zA-Z0-9\+=\/]+/gi, ''); -} -function sanitize_private_key(str) { - // example: privateKey": "cRhoitVgpq4svK5RraxYpBC7RwBBkUpDYAN3Yic6BGWwwb4BU1sp", - if (str === null) { - throw new Error('private key cannot be null'); - } - if (typeof str === 'undefined') { - throw new Error('private key is undefined'); - } - if (typeof str !== 'string') { - throw new Error('private key must be a string'); - } - return String(str).replace(/[^a-z0-9]+/gi, ''); -} -function sanitize_tx_format(str) { - if (str === null) { - throw new Error('tx cannot be null'); - } - if (typeof str === 'undefined') { - throw new Error('tx is undefined'); - } - if (typeof str !== 'string') { - throw new Error('tx must be a string'); - } - return String(str).replace(/[^0-9a-f]+/gi, ''); -} -function sanitize_vout(str) { - if (str === null) { - throw new Error('vout cannot be null'); - } - if (typeof str === 'undefined') { - throw new Error('vout is undefined'); - } - let tmp = parseInt(String(str).replace(/[^0-9]+/gi, ''), 10); - if (isNaN(tmp)) { - throw new Error('after sanitization, vout is not an integer'); - } - return tmp; -} -function sanitize_hex(str) { - if (str === null) { - throw new Error('hex string is null'); - } - if (typeof str === 'undefined') { - throw new Error('hex string is undefined'); - } - return String(str).replace(/[^a-f0-9]+/gi, ''); -} - -function sanitize_pubkey(str) { - if (str === null) { - throw new Error('pubkey cannot be null'); - } - if (typeof str === 'undefined') { - throw new Error('pubkey is undefined'); - } - return sanitize_hex(str); -} -function sanitize_satoshis(amount) { - if (amount === null) { - throw new Error('satoshis cannot be null'); - } - if (typeof amount === 'undefined') { - throw new Error('satoshis is undefined'); - } - return parseInt(String(amount).replace(/[^0-9]+/, ''), 10); -} -module.exports = { - sanitize_txid, - sanitize_address, - sanitize_addresses, - sanitize_private_key, - sanitize_tx_format, - sanitize_vout, - sanitize_pubkey, - sanitize_satoshis, - sanitize_username, - sanitize_psbt, -}; diff --git a/src/set.js b/src/set.js deleted file mode 100644 index 4e66044..0000000 --- a/src/set.js +++ /dev/null @@ -1,17 +0,0 @@ -let Lib = {}; -module.exports = Lib; -Lib.create = function (items) { - this.contents = {}; - let self = this; - this.count = function (value) { - return 'undefined' !== typeof self.contents[value]; - }; - this.make = function (values) { - self.contents = {}; - for (const value of values) { - self.contents[value] = 1; - } - }; - this.make(items); - return this; -}; diff --git a/src/sigscript.js b/src/sigscript.js deleted file mode 100755 index c08bca5..0000000 --- a/src/sigscript.js +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node -'use strict'; -//const { xt } = require('@mentoc/xtract'); -const NetworkUtil = require('./network-util.js'); -const hexToBytes = NetworkUtil.hexToBytes; -const hashByteOrder = NetworkUtil.hashByteOrder; -const DashCore = require('@dashevo/dashcore-lib'); -const Transaction = DashCore.Transaction; -const Script = DashCore.Script; -const PrivateKey = DashCore.PrivateKey; -const Signature = DashCore.crypto.Signature; -const d = require('./debug.js'); - -let state = { - verbosity: false, -}; -function setVerbosity(setting) { - state.verbosity = setting; -} -function verbose() { - return state.verbosity; -} -async function extractSigScript( - dboot, - username, - utxoInfo, - denominatedAmount, - onlyTx = false, -) { - let txid = utxoInfo.txid; - if (utxoInfo.needs_hash_byte_order) { - txid = hashByteOrder(utxoInfo.txid); - } - let outputIndex = parseInt(utxoInfo.outputIndex, 10); - let seq = 0xffffffff; - let pk = PrivateKey.fromWIF(utxoInfo.privateKey); - let pub = pk.publicKey; - let utxos = { - txId: txid, - outputIndex, - sequenceNumber: seq, - scriptPubKey: Script.buildPublicKeyHashOut(pub), - satoshis: denominatedAmount, - }; - if (verbose()) { - d.d({ utxos }); - d.d({ utxoInfo, pk: utxoInfo.privateKey }); - } - - let tx = new Transaction() - .from(utxos) - //.to(address, denominatedAmount) - .sign([pk], Signature.SIGHASH_ALL | Signature.SIGHASH_ANYONECANPAY); - d.d(tx.verify()); - if (onlyTx) { - return tx; - } - let sigScript = tx.inputs[0]._scriptBuffer; - let encodedScript = hexToBytes(sigScript.toString('hex')); - let len = encodedScript.length; - return new Uint8Array([len, ...encodedScript]); -} -module.exports = { - extractSigScript, - setVerbosity, -}; diff --git a/src/stream.js b/src/stream.js deleted file mode 100644 index 79e9662..0000000 --- a/src/stream.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * A port of DASH core's CoinJoin client - */ - -let Lib = {}; -module.exports = Lib; -// std::map> coinJoinClientManagers; -Lib.NetMsgType = require('net-msg.js').NetMsgType; - -Lib.CDataStream = {}; -//typedef CSerializeData vector_type; -// Orig: vector_type vch; -Lib.vch = require('vector.js'); - -//Orig: unsigned int nReadPos; -Lib.nReadPos = 0; -// Orig: int nType; -Lib.nType = 0; -// Orig: int nVersion; -Lib.nVersion; - -Lib.Init = function (nTypeIn, nVersionIn) { - Lib.nReadPos = 0; - Lib.nType = nTypeIn; - Lib.nVersion = nVersionIn; -}; - -Lib.read = function (obj) { - /** - * Orig: - template - CDataStream& operator>>(T&& obj) - { - // Unserialize from this stream - ::Unserialize(*this, obj); - return (*this); - } - */ -}; diff --git a/src/transaction-constants.js b/src/transaction-constants.js deleted file mode 100644 index 3193783..0000000 --- a/src/transaction-constants.js +++ /dev/null @@ -1,54 +0,0 @@ -const CURRENT_VERSION = 3; -const MAX_STANDARD_VERSION = 3; -const TRANSACTION_NORMAL = 0; -const OUTPOINT_SIZE = 36; -const SEQUENCE_SIZE = 4; -const HASH_TXID_SIZE = 32; -const INDEX_SIZE = 4; -const LOCK_TIME_SIZE = 4; -const TXIN_HASH_SIZE = 32; -const TXIN_INDEX_SIZE = 4; -const DEFAULT_TXIN_SEQUENCE = 0xffffffff; - -// About : CTxIn -//================= -// /* Setting nSequence to this value for every input in a transaction -// * disables nLockTime. */ -// static const uint32_t SEQUENCE_FINAL = 0xffffffff; -// -// /* Below flags apply in the context of BIP 68*/ -// /* If this flag set, CTxIn::nSequence is NOT interpreted as a -// * relative lock-time. */ -// static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1U << 31); -// -// /* If CTxIn::nSequence encodes a relative lock-time and this flag -// * is set, the relative lock-time has units of 512 seconds, -// * otherwise it specifies blocks with a granularity of 1. */ -// static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22); -// -// /* If CTxIn::nSequence encodes a relative lock-time, this mask is -// * applied to extract that lock-time from the sequence field. */ -// static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff; -// -// /* In order to use the same number of bits to encode roughly the -// * same wall-clock duration, and because blocks are naturally -// * limited to occur every 600s on average, the minimum granularity -// * for time-based relative lock-time is fixed at 512 seconds. -// * Converting from CTxIn::nSequence to seconds is performed by -// * multiplying by 512 = 2^9, or equivalently shifting up by -// * 9 bits. */ -// static const int SEQUENCE_LOCKTIME_GRANULARITY = 9; - -module.exports = { - CURRENT_VERSION, - MAX_STANDARD_VERSION, - TRANSACTION_NORMAL, - OUTPOINT_SIZE, - SEQUENCE_SIZE, - HASH_TXID_SIZE, - INDEX_SIZE, - LOCK_TIME_SIZE, - TXIN_HASH_SIZE, - TXIN_INDEX_SIZE, - DEFAULT_TXIN_SEQUENCE, -}; diff --git a/src/tx-dashcore-lib.js b/src/tx-dashcore-lib.js deleted file mode 100644 index e006994..0000000 --- a/src/tx-dashcore-lib.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict'; -/** - * 1) Choose an input - * 2) Create a change output - * 3) sign it - */ - -let Dashcore = require('@dashevo/dashcore-lib'); -let Transaction = Dashcore.Transaction; -let Script = Dashcore.Script; -let Utils = require('./network-util.js'); -let hashByteOrder = Utils.hashByteOrder; -let hexToBytes = Utils.hexToBytes; -let wallets = require('./wallets.json'); -let addresses = Object.keys(wallets); -const COIN = 100000000; -const LOW_COLLATERAL = (COIN / 1000 + 1) / 10; -const HI_COLLATERAL = LOW_COLLATERAL * 4; - -//console.debug({LOW_COLLATERAL,HI_COLLATERAL}); -//console.debug({in_dash: LOW_COLLATERAL / COIN, hi_in_dash: HI_COLLATERAL / COIN }); -(function () { - /** - * Owned by "psend" wallet - */ - let PrevTx = { - address: 'yfLCwJqTN3uKkNM2vvaaMofbNQscCDYZ9a', - amount: parseInt(99.57515735 * COIN, 10), - vout: 0, - txid: '1593a250efe1b4e3cf8edd2301e7ed614e105758b4136c0daea89eacb2dfc626', - }; - let amount = parseInt(LOW_COLLATERAL + 100 * COIN, 10); - let privkeySet = 'cV3gU6nXfiAQ5bCj8ffAMDf8RjBAmPrLHPAAFVERygctsrTJVo3R'; - let changeAddress = 'yUXAvpMJ73eAaRTNi95Q8NR7oLYHxzUB5Q'; - /** - * Owned by "funbar" wallet - */ - let toAddress = 'yhvXaFqbcXKJaVi5s15yympWy2xZifvyoy'; - let utxos = { - txid: PrevTx.txid, - vout: PrevTx.vout, - sequenceNumber: 0xffffffff, - script: new Script(Script.buildPublicKeyHashOut(toAddress)), - satoshis: amount, - }; - - let tx = new Transaction() - .from(utxos) - .to(toAddress, amount) - .change(changeAddress) - .fee(67) - .sign(privkeySet); - console.debug(tx.serialize()); -})(); -process.exit(0); -//return; - -function demo(index = 2) { - return splitTransaction(index); -} - -function splitTransaction(index) { - //let PrevTx = require('./tx.json')[index]; - - let PrevTx = { - address: 'yN2DYvCAxL3zBUQnNjMnFteGCXRF7egyQC', - category: 'receive', - amount: 0.00025, - label: '', - vout: 0, - confirmations: 0, - instantlock: false, - instantlock_internal: false, - chainlock: false, - trusted: false, - txid: 'c73c690e24dba9fddc9ef23c4a8a50b11a553f414c6e7331ec4c06c6fd3672ee', - walletconflicts: [], - time: 1685520437, - timereceived: 1685520437, - }; - console.debug({ PrevTx }); - /** - * FIXME: remove before production - */ - let sourceAddr = PrevTx.address; - let sourceWif = wallets[sourceAddr]; - if (sourceWif === null || typeof sourceWif === 'undefined') { - throw new Error('sourceWif for address could not be found!'); - } - let payAddr = addresses[0]; - if (payAddr === null || typeof payAddr === 'undefined') { - throw new Error('payAddr couldnt be found'); - } - - let unspentAmount = LOW_COLLATERAL * COIN; //PrevTx.amount * COIN; - let payAmount = 0; //LOW_COLLATERAL; - let feeAmount = 225; - - /** - * You can get a pubkey hash by doing: - * -> dp gettxout 15538e287a4adbd45fa35290eaaf14bdb63cce3003f3710b1e953a898e268ab5 0 - * It should show something like: - * - { - "bestblock": "51c8381e5dad2b46ede81b59129c5b291e73af78723887b74d8a040b02191c82", - "confirmations": 8, - "value": 115.48361444, - "scriptPubKey": { - "asm": "OP_DUP OP_HASH160 82acf25253e190ee72f73cb7bdbfd607f18c0285 OP_EQUALVERIFY OP_CHECKSIG", - "hex": "76a91482acf25253e190ee72f73cb7bdbfd607f18c028588ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "yYEPsgMKLK6V7Gee4mYzxRDvAQhevfSob2" - ] - }, - "coinbase": true - } - */ - let pubkeyHash = '12ae145ffb38a91938d0876b3540c4452a04b310'; - - let tx = sendTo({ - sourceWif, - sourceAddr, - payAddr, - unspentAmount, - feeAmount, - payAmount, - txId: PrevTx.txid, - pubkeyHash, - }); - console.debug(JSON.stringify(tx, null, 2)); - - console.debug(tx.serialize()); - return tx; -} - -module.exports = { - sendTo, - demo, // FIXME: remove me on production -}; -function sendTo({ - sourceWif, - sourceAddr, - payAddr, - unspentAmount, - feeAmount, - payAmount, - txId, - pubkeyHash, -}) { - //@ts-ignore - no input required, actually - let lockScript = [ - '76', // OP_DUP - 'a9', // OP_HASH160 - '14', // Byte Length: 20 - pubkeyHash, - //"5bcd0d776a7252310b9f1a7eee1a749d42126944", // PubKeyHash - '88', // OP_EQUALVERIFY - 'ac', // OP_CHECKSIG - ].join(''); - let tx = new Transaction() - //@ts-ignore - allows single value or array - .from([ - // CoreUtxo - { - txId, - outputIndex: 0, - address: sourceAddr, - script: lockScript, - satoshis: unspentAmount, - }, - ]); - - //@ts-ignore - see above - tx.fee(feeAmount); - tx.change(sourceAddr); - tx.sign([sourceWif]); - - return tx; -} diff --git a/src/tx.json b/src/tx.json deleted file mode 100644 index 0090046..0000000 --- a/src/tx.json +++ /dev/null @@ -1,9439 +0,0 @@ -[ - { - "txid": "c92b6a425150fcb95f7dd16675d06d350cb2e5172e1825c8e64d746470ac12fe", - "vout": 1, - "address": "ydxLvxiMft7ynQQeksp8tnV3SVAKHZ1SqL", - "scriptPubKey": "76a914c17496bea23110aff4ed29d3af884f355c2d6b3188ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([c17496be]03e02dc0914c80fd3d4b3eabd00494a3e087e12a96cd3c843f68743bc4c016ee4b)#m9uvu4uf", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b8fca362ddad850f3ce526ee840e0376960cca49b890b5e7621a1754d1b072fa", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "54d37d99cae39fccc4f394935c7627f1c6e92c5434e76d58defb164b7a4db9f9", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5989c9b5b6cd118d2921ceb1e25114cfab93d5e27c972bb5b4d5741083e507f7", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "965ddbcf74ed201650f84b92a47dee790c95f27e972946576b86211ba3ee9ef6", - "vout": 1, - "address": "yb4P43sAcqBL7rinfahBiYAhRgZAQXzuv9", - "scriptPubKey": "76a914a1b07a9313dfc1e9e897144aa6752633374aa60488ac", - "amount": 35.24907564, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([a1b07a93]02da35e87a68b8b1f7b45281706d418865c2faf6ab7622635d080a2d28e515231f)#llylnwd5", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "7980e1876fc50bc417e71929dbf995f636269fff191496ed42c204d4041ae9f5", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9e1f0865685115eb18808bb14ac76a833ae1ee50fd9e1c7b4c6c6c26889ce8f2", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3de50ad92342326b0c20fc9abc571092bd73f7a598febe5ad7e10b0ceb52a1f2", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8efe12d012aa470458fbb4b550bb79f015da3ccb78884392e712ab56fedf24f1", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3f461c1c1e50f4107a484c3c7d5f3c1e516f70c3a26fbe63085bb64460e2dff0", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b27debc41b0994b0f675dab1b869fc2834fe9121fa41b4acff291631bce167f0", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e7d47f00f29217bc7c83adaec2271c327d639b7ff896e5f01f61c238eadd45f0", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "90aad2550f39373e16723cd93f6f3a341504b94654a5cb33dec20a90fa5483ee", - "vout": 1, - "address": "yN6LHE3u8YkaVARWEyniwzkBAzgCKwb5Ue", - "scriptPubKey": "76a91413755c63a762253dfec945d91d4a248b6993e5cb88ac", - "amount": 0.01093705, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([13755c63]02953440558094bed97dba4643a6bc153f02e6c2fcc33f9c7183c9af66ec79a0cc)#jg0l8ydz", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "2848f4fbdf1c0ab8f1a5a9cbc9495af1eb6b67958fc27f39aa974746e71d7cee", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4c0d5f28c7c32c17b9b210d0e794044e68203482220af4266fb761c31923b1eb", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8a987dc8ae608afceb58813e24e8de71504a386576e7b0d8c98588a2a26fe4ea", - "vout": 1, - "address": "yj2dd1XhQbGM6m2YjeXUdh65uUUs6HdkgN", - "scriptPubKey": "76a914f91cd06b11b602495e20935dd56b6ff5c9457f4188ac", - "amount": 115.47258958, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([f91cd06b]03e86d77ec9d434096f326ac06dd9039cede995a20af5e9a22046b8a4cabe654f2)#w3g0v3yx", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "aa905ee46d593845bb71a24b73fed51bfd3aeb9aeef72bc9017e43f72e1e1dea", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f83d28f09c37bc5a00282529550e3f5316297e74a6628741a62aeebe67b10aea", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "dba78d8c0c83da0a2322671a4ff49f66dafcb47419e28ebe10713ab8fa2e50e9", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d7c26072ff1dd1102c428c8aa785116ff994a071cfb81c60d90ee9b1af788fe7", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5076042c85464805fa0ed2109c78b0ce331f9ddca802d8a2de215e0a7b1cdae5", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e7da689a9e4af48f67c30140d93190d37b033d3b56bc5358cdfc9cd218a761e5", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1ba9fd6452c69586bafff313a9b319e8c55cc456d5fd584f8ad8acc10453f1e4", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "aacb4b1806b3996a1af74176a2fc71be455a76a47b9ce37085cb5c88286b36e4", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3afda7c8f1f7ac2b36df2d292d7e929595bdb2e29b9c5cb9cac08f79c9a00be3", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "478953359faf0dcb31a91701c29acb65e8abf7e1a20ddbe7d772b9669887ece1", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3bb98dc2806cb2ec309c7bb91ed2f67044f43195633ff7797c673db1ac60c8df", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e6fbc19ed25cdfe8eaa11511f77f7e51dd093df76c715b2d12f7f264be20e1e4", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "600573c20ee5f0ff2bbbfb1f5dcc22b4bb316123225b31ed13d85315292c85df", - "vout": 1, - "address": "yTYH2pu4YxYtgavSb5kDyTGLFxK9MZGKCj", - "scriptPubKey": "76a9144f3617e313fca39561a147736528a9b1f1ad8fe888ac", - "amount": 50.72781830, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([4f3617e3]033e7e01c420cfb2f0dab4ce1e8fccbcb2ef07eafeeede29b92cc9c34c94410a61)#7qfwuypu", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "0c7155b623936c82560a5fc28be9d83be713ae782892756030cdac6ecccb7edc", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1cbbb53c1ba0805b6ed8f1c74289816e7776bdd0a2f79bba9d527a3394dd2bdc", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7f4736ee9b9cd92846ec0eb99f73d89f6856ff806c7a2485260c78a9ea7544db", - "vout": 1, - "address": "yds3pbEUudTHkb96bjKYq7opxnAGjM6CY9", - "scriptPubKey": "76a914c07439f9c5b77ea7b0858fed24c05bdb161f9b5988ac", - "amount": 35.23805078, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([c07439f9]02853ef2dd93a77f168d32f7ab53b7c736cfb3b1a4fff16985fb2c6a413b791dde)#6wffpjz9", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "c7eedb05d6f676ab50f98db6a3768714826c8bf632231c6bb88a5eaff82e76d9", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8d2ae156c47dcaa2cba54e9efee6f069d4deeaae3b45585881ece95b9e5883d8", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9287c53254564f27e30a10a52a9054e5ef5d6a1fe467e403273d59922ff123d4", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8634471479449195d9a76cecdaf78d131b4a946babc141916a71053e831cd8d0", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2a0da3d3710bc07a20f79d4db97f232a81931401a2608acb91559ac4f45450cf", - "vout": 1, - "address": "ygVWP2hgvL55vJQwmkNq1quFUcUXwhr9Ps", - "scriptPubKey": "76a914dd499f8cd367c6dec284805bda8db6b5d3bbe6b188ac", - "amount": 115.47389556, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([dd499f8c]03d9bce205adcede2ccbda4eef93886689df9715c4441e97ad3f5dcc46cbee1199)#xxs77rkg", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "735e9a21f9710bfbac1267b3535c7121c85d3af0afd38610ddace712956dd8ce", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e0af1e2bfeff3cf72f9f18bb878f070c269e71f5cc15c4cf629f9ec825aac9ce", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "627a5c05f774b2d830fca37f9a6da1952963a9ff8bccd1191b9cf7fff0f2c4ca", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5670961fdf10addcc2c15dfb61a48bd741dccd5dd979869e096119ea73266bc8", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7c9d7e1bae38c009fc4679d46611681c954c545c8a1608a32b1e3cceb072a4c7", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7002bfb39137d2b9e90995b3888f37b0b6d66dc4e5a68d77e9c12b72fdf445c6", - "vout": 1, - "address": "ycGN8fARknRUyuAQFrd265W2peX7PPa4xc", - "scriptPubKey": "76a914aeecd3e44e7fcaa2ddc67d95ab1a533e09f7d11388ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([aeecd3e4]03e589220acc2b02bbea902589cb32e46ffa2546c48dc74616f37eb063dcef0fb2)#r6hu0z2r", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "dc030d2880df83d64547110b7ce723120b879e3a24baf2a6ea3fee6e5ebb32c6", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "99fe083d4d6a4bf6c80f7e430090b7fcce1e4e1f6fc247f705515a274771b0c4", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "77cc21b6950b0b9316b3a1833743fe83290a0b980496c4cd337e731b6e66cbc2", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8dda80e2644ea64c4bfa7338b09f92b39680c595462ca386cb28304c79a2c2c0", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f6ba084fc761be584edb8e954b5fec45df1d651fc81552974aa9cd3035b197bd", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d8ae59c09bba1dc903e8a9e8e75e92f5e4349ebf7021bd9ac5651dd1570d82bb", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "37002ddf2f08b47aca32448c2fb453ba6f108bace80c1b300c15caf2e4bc5abb", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c1ce16717739c277b793b0668783e402613cf83031645f6a0ab5f882909c4bbb", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "70b186494fd1d95ac071215ae0fe2c99f7e2978e359863d498fb940c184388b7", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7ebf0904effb7e74525c85c859b94a4427df67ad722e85462c17bed2a0614cb7", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b7489cd385b5f3979d1e2e8b1304a62849ba09e6127ae6e3576f6bc207e3bb7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b061b0b3ee011d107ec5d0e52c538e330b2c0d4f9000d73c34c9a4304785b5b6", - "vout": 1, - "address": "yTCUsKyFuM51yFMsKDfJqxX3kkisEEWfhd", - "scriptPubKey": "76a9144b77a78a9c9a8a00889951c094ef17baf976eac188ac", - "amount": 0.01093705, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([4b77a78a]0346da4d6b2e7491c24875900cbc19ca549fcd15a89c7d5b2a5a07c518a09b2e59)#8jkykphm", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "a608674890599d27082e79ed2c9e4fef08aa50cf1275cce0414b6d67c58814b6", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "09b164a7c122529192ce28d6b8c8ee46d07e748b9704eff5d12eee1958739fb5", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fdadc062c9e75f12be75e1dea2b5454479177ba42f2eca17796d9f267276cab4", - "vout": 1, - "address": "ydbFU6Xew2Xco3hbUGWo6ktJatsAQnfQj3", - "scriptPubKey": "76a914bd774b0f6c7624d7b683f1a62d1ca03d98c9219f88ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([bd774b0f]0225158e55b19adbb1da0627a74cf165cecf0437fa09dab1424bf5400ab0ce9708)#wer537l7", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "5d71fa8918a9828725f021464ae63e55d36be027141f0629ffcf8fb1848bb5b3", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ea3f4148ec9e9b4e20f026cf5c25a0f97d5e648e79a6b6b7166eaa8bb0f120b2", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "92bb2b9784fe5e27a880486351a33b1c1055833cebef1fe457faec612aa946ae", - "vout": 1, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6f990bb5876c263bf7704dc33a207d8d129d2b68c52bb2d7326879c1a094bdac", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2d54270dfac58318b6c48edda98e5aa92d797c3e964eef98c63349b852184fd7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0f982c8d4e657d871631db98b53451865c98e07525c38dcc6a420c586ba64da9", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b053f741883eba33cb604a4adf0c6adce668025855a7095f95c33b3c6881dca8", - "vout": 1, - "address": "yU31C7acRpCBqtSsgUjDH6WYtXbLcWe4Du", - "scriptPubKey": "76a91454a4ef09daa59515a3a91d6b19bc2473cef7f71c88ac", - "amount": 35.23905304, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([54a4ef09]02ed0337fc02200813a29e9f0b8db600379a43c9362f6933031669358511e60bc8)#gv2zrrf7", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "fac7a2bf53e796893d1275bc0d8bb38c9623dba933755b1d4ee7369daf00f9de", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3e631c25b59e925b52ade4b9284798d85977aebc3ff4dfa7aeddb2d7ca4a9da8", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "356c97e0d3babf0b00d3f4e0ddd54333f308c1937ad3556fada7524a59dbf2a7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "76261bef42ce83abd599be84df43e8b69e7d57c69fb66776f06e914c61d2b3dd", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "75f71b5120c04f06d516615eb1314d92d182904b00816ddc765d7305d00c48a7", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2fde0d0b94f5d9ee64589c03f331789bac2d0f88331bbc15d900802a60e001e8", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3c2b3f9acbd319a27211de8996770dcd31f054d6687d110bc1886ee098bf0dd4", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "58c7544bc5aacb22d24968ec96741832945edb8bf54f9191c57b04982ae599ab", - "vout": 1, - "address": "ydkQ5gpEv2TzhDt3kwzCrAxWGgXTVgh8v1", - "scriptPubKey": "76a914bf323a277c9badf5d995f28ed233381e8ae0fa8588ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([bf323a27]02dc80c1a5e6c5b0032616404bbb352e9903fe5697c3a4ad25797c032de387f1fd)#ssswa36w", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "5bc4d8e8d9a75f78a568bc5f2173ec4cde753028f003b1a2f1cb9d9c801c2fa7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1bbe69e555f1208c1392acbeb1c0275be335d8d0ad4de4efb24285616b4d4ef7", - "vout": 1, - "address": "yVFAby5YmCGWpfF9p4QyqxXLpNf3coyWNV", - "scriptPubKey": "76a91461e9e83e2afa767c1626ce4e74472121a1a7ba7588ac", - "amount": 50.72681604, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([61e9e83e]020f40c07987146bee1bd5992863f45d7cf6342aff63360e260af71b45b57dca8d)#jwqr0phz", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "d8be68a02b9df858c686e11589a948602ee6c80dcd96118af554db6da80fb9a6", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "11d09d94abff18c5ac07b09af6f42ca0876b65c1d5f6c8c8b1b29037276633a6", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "de28b63e4dc1018eac62d0736954f4b37b8e042f36b11b7b710785f911580aff", - "vout": 1, - "address": "ydowpSWpDXZwdiDg9HVgwna699RF5tWSpC", - "scriptPubKey": "76a914bfddf7c66359ceba59c27b1e43873d35ef27890288ac", - "amount": 50.13408425, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([bfddf7c6]02fddb10838f047b883d9176638cb5daa3adc11f8e4a26665bdbd1a5ec53e70756)#lqaehak2", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "c65f79e6156d4fb99cfa8849e37b3865172f80bb9740d6a7f43f4b1f205807bd", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "141880a94b6031326c548f6d545a65767582627e39192408939bd91b5f807ea4", - "vout": 1, - "address": "yfViyqgC4h9oA9NYLYcxqoHg62P3APiL2w", - "scriptPubKey": "76a914d25c03fb309ac5bd6dcbf49917bb393db35171b588ac", - "amount": 124.35173546, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([d25c03fb]03b646f51cd3c854d84b7537c9ede96f776580c15105fc0a36cad93c6e6b210e2a)#gekxvuxs", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "8affe737fa0a71ab957c7f67a7f3baaa3ce9a9f1cf2d8b0fc6f823ef03b56dd9", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cd52300a21269952c070b47627ce07bcfad9dabb47ca3468db31cc295fa61fa4", - "vout": 1, - "address": "yT8b5kyirBRwVSidjpiVSNmjXZsv3Lr6U1", - "scriptPubKey": "76a9144abb2d963a7c4d14a033008bd6f525f9275c07e688ac", - "amount": 37.95992622, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([4abb2d96]0346c1ee02517220b222e2ffa98cf2e567ee44a78831aa833dcabdbc635d66d3d5)#wrtahf45", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "454d5c99e7129d6d3c8a8ad73fca9b9e811b0c02ad35df66bfd74c2fc7cf16a4", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "16489d907ac0a2a4eae3f2753d7e94b721d60e8bb8e4d81464349537ddc965a3", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1bd035984d8f4af2a28a6eed7697095dd1b9cb09f3326a0319746b25dafe47a1", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "00e961d27d0ce64fa61f129ca040c6731d3fcc7a86a776876c7ab3251639f9a0", - "vout": 1, - "address": "yhdWEmm4b3h8DBrdW8baVUsas8WKd5h5vJ", - "scriptPubKey": "76a914e9c4f537397e1aea42dfc0502b56bd898773287e88ac", - "amount": 0.01890305, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([e9c4f537]0330bc5ae13383f958101a8a82e90b8d2b73787cf29fdaff4a6494d10718aa8f9b)#jjvzmuqm", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b1e6fe22ff3e96ccbbd98684bccfed1f01ad4a6c89067acd250492c98ae847a0", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "be0c21b6bb65bcf025aa90b3c25b9211c76435fa6f9f6387b449c36fb9572ca0", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e86aa4b9a3600f3fa396b66e97d1367e2e4d75c5851817f5ab0eb87dd03b469f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8fe006d7d210ae6923285b59f4687e3e3e6f46a5cc106c5ee3474de2697771e6", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "740457d7f4ea4a8531734bb70e417c97dad36d96315e1326e73a3cd10cd4a79d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0e618e3d4e02c943b48d384c62c437373b0dbade83740353691c36971fd8479d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3871ed2900644f39ed0e2ff4ea3d950c284db5d9d330476defe3e33b0de9d9da", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "18bb987a852d42364a468b14d892617a80d4a0a511a4f5d8dbe1763e9723739a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b97729aa4c2dda6b8b1e90806d94eddedec533227f13fd026100aefa4fac6da2", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7441eb19eecbd7ed34e7021dd41d79928096f0f852aa715b1f30be8863da5a9a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "387f3ab00b40488306e8ac06db118bb2a009eb55f4a9e5ac872e97640a0a7099", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6d09e777d4d6948744c35d85589ba00e4852a376b2d2b3b2d7e7a62100e14299", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "de1b33583651d397b0764ee59c3cbed4ffcff2f18c0e8d29c1f01b8bfa8450d6", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a78aea7c85cb234bfbdc4a302a9d1a4de59692b384b592213bc3dfc3e2685e98", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4b3099e845ea79097472a2b3f728ecf79e74f0582f2d1d20245d75e1c9c37a9f", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "806dfb588dae48e40de0bfb8b4990e6e4b9e0c733c17af2678f1d7caae4cbe96", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "45a4d11a10083713030da21aebcf46a3bc69dc432e386369a780d803dd7e6d96", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d34095c4506ad30140fd5a7bb114c3d0c8c451c7835e4cb397ea6219879fdb95", - "vout": 1, - "address": "yQFqLAHkYpxjHPA32GH4GX3tJfQNTakbDw", - "scriptPubKey": "76a9142b319b395bcb240108dd751a7acbed56c57836d488ac", - "amount": 124.34091060, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([2b319b39]032d02f9b6601b91f87ea51e83ada849f383c172768b5945d559c82ca0944cf742)#jpa4wudq", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "ee6b199c1a61775502e2e545086cc24956f7ca4e12e09ba28a366d5c03489895", - "vout": 1, - "address": "yP4caGMWvYrxmyU9wxTFDRqQFc8G9WkkMf", - "scriptPubKey": "76a9141e1a41916d386c817a50862ad19abeddb6c02cb488ac", - "amount": 0.02078853, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([1e1a4191]0271f7f7378c6fec5babc0f1f7da3b53fdb15d17ae3cce82f89e4fcb7487d69b37)#9clnhh92", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "9512ccafd790bafae7c051d79510c2da29e60eccd7d1edc4f698d2c9997078a1", - "vout": 1, - "address": "ydFX8gbw8thbEhuUNZ7KZP7y1iTYkkiJiH", - "scriptPubKey": "76a914b9bc0cfae5087665f018928b2626229728c57be988ac", - "amount": 50.13508651, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b9bc0cfa]038c5527c4992e5984495f873b8fecbee01550afd86fe2f21f45dce84d287f8a9b)#u795s2xa", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "45423c450a9449b31ca8b3881d9063d798ab37e63633302465f08373ea483a95", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "637acb0dd5c27d1ca4e78f7bcf90ef7148641ecd098ab1c5dce2cac6e00484f7", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "29cdf27ac99d3561a37e5d66584f87de08e8fea91f9e31899785c6a06cdcc094", - "vout": 1, - "address": "yVdeMYhquSccGatowyMh9RanwAYEB6zuTT", - "scriptPubKey": "76a914662a395aa352d97fa8015cc603031cbf0b9a98bd88ac", - "amount": 35.24506660, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([662a395a]0394c7cb393fd62c804a800f6e53abf8bb56148a217def8d32dcf54fa5b086bcc1)#xng96cct", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "57cb3145004368dde848e9e43c5a342cadba9b2fcd704a940d5f2ec140943d94", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1d5fcd0cce2dfec0ae875169f72d25270bf262de9cb64b705e722d04ba91b593", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "255cbba88a6f5bd7378492fe0e0db4723a9d86ce05909b8db60ea1ad1205ec92", - "vout": 1, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ae2356d0296a1dce3168b3f7fd383d2a07216736e0162fa6fe9030a7b2202e92", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "37efd37bb6b2c61247cce5d580e4c6c0d65b7b357ba1c4ba86cc4b67f2bd9391", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3c051edeab8749fc3e0e1569d61b1a852b483cba740441d0274dbe825e2c7291", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bafb9f1869fe177792c17398c8f84dd34c83b0c336f5b81a379337b51cd6ea90", - "vout": 1, - "address": "yRNmfNKQ48GoGq1dZmJx5ST3RuDgp3Ynrk", - "scriptPubKey": "76a914377994ce01809eb40b40962b54c5a453dc1ad6ed88ac", - "amount": 50.13508651, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([377994ce]0296d32ed2618729a1d17422f121961bcb1833a46e3f2ab9c9593de83683c8a6bc)#7lceazry", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "6e9adcfb41014101607d1df82c0e893c96c898d1ec7e41bff231b7c1b51c87e4", - "vout": 1, - "address": "yUct9SSY6A3j5VsvSg8ThYWTJEY4rxucco", - "scriptPubKey": "76a9145b0d31d4137ef0ca1f84e33a4ad0daa69eb62d1488ac", - "amount": 124.34892868, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([5b0d31d4]0285b61447d8fd0a6f988da2590010f2aa1605221c28a1f83e9b1caa97d0b51e4b)#l96jv97r", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "dce1c7d0b246bd6ed823e232d60d084fc3ecf2d006a0bd984393139d06d13590", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "197316aec05a1479c8a460fc8c9f90298d6534fcf3acfac28292083e648fca8f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a85b0381ae90fb3d4b4f8d649edf4973a55411b977f945be097157e19989128f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "27779c8843cd6d0c6306674786e996401b92c176202bbd1034e85033e948048f", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b3cc6be26d02e5a7f10b7c5f2b6ab0285e9d0705179440e8f8f536baa87cbf8e", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a72c3ad168d4e346764d1f6f414cab5ce5bcbb019432c66945cb389a0eaca18e", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4cb290ab45a7cf64d6e12767e21aa3554258d24bcadee05c0fc876463c656b8e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4bf782999aae04323171c1d5ff7faf7109e1ba01f5e2b05ae48da78fb81e768d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "584685ed0c3aa17dc50720ea210308ea740356ef6ddd78db55ceaa77de8fde8c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b9e2c0719d3f416f6bea07ab7bf7b14822839e5c1ac5dd19a803743a2f3fdb8c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "59a607f149cae2710d3224b2df39a1e7aa99cb52311b62ad32b472d4c2f0da8c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1f05a73ae5dc12f9698be4befad0a8df40a10a9d281105aaacc3bb1029e646c0", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d016914848a94664d34b5a4ddeeda40bb7c8a624243b967e273c9d485349158c", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4c19cf25778e4ede085d2c6c1cdf8c6c55c9ac89312f6b67fad0f900dacdf98b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "879b90162cd5bf342784e8697c4dbfd00d0d0361e37743d7f0a27d992be06689", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0beb06364b3d7b092ba82fad07472e220caa09f8b19b4a8532c8fdb551893f89", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "46b9572eed1896271d6a2921a513adfaf0b15d21ab9bb1e36526f78432f4c388", - "vout": 1, - "address": "yVbenfDxeAQXESeuptrGs2dfCebsx9J1jD", - "scriptPubKey": "76a91465c9c0e82fa0c5b6e963aa59ccb4b3883219e5e688ac", - "amount": 0.01293413, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([65c9c0e8]022bb74e2e1a3c011bfcfac1831cd77711544fdf93d20326f4251823df6f13d08b)#aakq3j02", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "bde02f1156d052c52f9b2f36f6ce11fbff30733ec815f1e92959bf3e08938488", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b0af8825f1d630bdb6213ed0e3c0f54e5a529951482150c794862a56d1d06588", - "vout": 1, - "address": "yhJV1NqGwA32XgfyLe5fVBL75RuwSyHTpK", - "scriptPubKey": "76a914e62c06ecdcbd58428d8ccf4f225046f818cde48888ac", - "amount": 115.47359184, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([e62c06ec]0280c8850daea7edfc92aa9fd39716f72229bfec4d7ead15fb6509060ef9782b85)#euunyg5v", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "d9bde13ae6487662855cd1b4b315f47ee20119e3e936c4030ce6f813c568b6e7", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "62670958c0e95b3c2faa1838330fff15f8bb29497e0b6ddd2ba498bb3a002188", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "aa2e06aa7efbe9d5080587d745e091b557b9f5cfdf25bdc1363267a4d2f08e86", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a55214c82de10e6e2429ce92b1783a0fc2c215aa83c09a1154fb4b2e7f52d585", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ba2eff120da23797602af624d86d49af2df1e0792e8b6556251f097335477184", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9cc4832328b482e25e0fdac87d06b19d998c39ea6137a2a50ce45efae1afdcdf", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "94bff25073d0a100e99e9db3eabd4b0fe77d192d063d65cfa97f45e28f638c88", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f6d8f9a8304f7a13d23125acfd56270d5eaf71b32fb1c02552f565c0345d0484", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5db58d4adabcf03fa5651cd6c31fbe5e18bc4e8e70d2655d6ad00b97f43cfa82", - "vout": 1, - "address": "yVjwaqwZ7zNeSBfPqUha1GLu9DunkTF92z", - "scriptPubKey": "76a914675b1a217b7dd275204a21825d766a85d2a7105d88ac", - "amount": 50.13408425, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([675b1a21]03fa417860fd29ef5c597c459cba9e5abb6face4559820ad44f13ed75d6571b590)#z55vk7gw", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "da14d24309f458318faf51e0ed123ac7bfee6f7919ee6e1450ddb40e0da5d7d5", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bc10b9b34ceaa31c0ca1047b6fc2290677fba8717afb3cf20d0537c784b30096", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "75c1dd76120726df4fa20fca5d7c2512655dca63ffba4729ba97e771f46bdc81", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "54f0c1017392bc30771ecb22a49e4ce3e4eecfb3f6e9198a6ddfc6269ec17d80", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1be38a625aff20bd4eda4636297332761a01c6ce3866292695e65c0990fe2080", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "127c27b416632da1f4e07a427adcf30160d7d855310a1b26d5116014f1443abb", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "09fd1b89355c956c75c8e896886b4c31d1b3178ce7b15e08e9214b2f6346d87e", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3883145e4db7d62e0aa99be30884d04ce09eb4cfc294d00276311d8e802d1c7e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bb1314be2c046ec79898d749f894c775b4799ea27100ed798a0218d056f0cc98", - "vout": 1, - "address": "yYzGnio5TA7hg9DNPQJXbGzqaxeBZ6Eq7R", - "scriptPubKey": "76a9148af9558fdb3a955ead36c167c2df1d70b13f294288ac", - "amount": 35.23905304, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([8af9558f]0274b504a3cff7d6e9ca208346672da427409733e78a218a5b394fb95de3e79ae9)#8nefgwmw", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "f034ed1ac617b6ba2e70107bce4fa1dfe4a0699671a36c1b83ce0e866d43f37c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9804bc57344f67a9737a804d8a247fba706d88509a4b5791a54ec7bc7a85807c", - "vout": 1, - "address": "yPge7hkYgdsx15hTv1b9Cz7QrfBqCrbEJg", - "scriptPubKey": "76a91424ea8499e9fde1a664bb2b7cc7ba6ffd861e0cd688ac", - "amount": 0.02087037, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([24ea8499]0381f99dd7788291d97c031eac6b26f87eefb592aeee23e56b39bd0b092394fa4c)#39q5ylde", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "7ec33903cce8a44c57fb684c8ea9eb33d4e6ffd6c1407f638a32ff4969497a7c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a976d1333de4bb8a436fcce156926039d41a0f75b32d7f86128119eecadadb7b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b44bcddffbc98506be9212493d855107f202382de5c2484b87176dd88934d67f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9dcccd4a6a6d059a63cebebc8245b4843a711cbacd912ce8606ac33a711bd87b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cc9ee31de7c7898bd9a75f61639059c1fb956137b56f2f163718a144587f377b", - "vout": 1, - "address": "yfahT1mQv1C9qJmffwf5PgMGNPfVsP1Ryj", - "scriptPubKey": "76a914d34cd1232705ec117ac09cbde4e613a6165b770188ac", - "amount": 0.01493121, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([d34cd123]02dfc03de9bfbd161965c9f3695f6601d5b752e9753976122e260fc44db648492c)#e2t30kqn", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "1345e3d8ab99098323261960fc9c508aec3a4fe657ae9b01ff521186c416f17a", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b42e7978e5ede0c5895ac9026e2d6ecef81708849ce452092e31320d0205179", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "08d05f5f6ccbde32643a4f41edd7e9cb9640ff328946dd55dd099c903bc3be78", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6bab2fb982d27607dfba51235192cb226ee3da9cac19e6d61c307694736d33a7", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2979a09bb8445937fb80ea049cfcbace189298d3787ab01ae84ad8570caa9f78", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "968806413de0c3c9c0b1e9637384fca7428ad97ce469a2dc492e3db440913d78", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "93d37b5f00f4d9c7dab2394dd1afe083d741813e9c08bfcca1640a36bbd80ed9", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5cafb5b77751e7056d312247898c8519e48edbec9412a4db1abcab6409a9f280", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "eaebc247872a14e20b5ae8ed627a16a75def129dea668a877bda55b484622178", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8e08f2fe3c20aca9264e541b0a1b4a45175e779fef0a145318908a48323bd177", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "813578fbb2aa00a3ddb7faff1e5e97e239e22bdfe417eb32129ffdaa633c2077", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6b4780ad42f85c421c05af98704471e3d2be85f0e645a3d44cfad6d0306c9b76", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ae959b0d87ab3ea221eeba5597dd235eced0ad54d9acf9c8b34989ea86395d76", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "42029ec3e1424ba509b4f464e03306c55da818637638b8ddbc91bb2cc25ff075", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "506ba8673b3c58380ffaad4c33fdcf285392063238491db43d52774a535d8c75", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bc63d37ffaf3d6c318e41b90bb75dadc211b70456822e760d9aebf528b917d75", - "vout": 1, - "address": "yePKNydPduXoThje5ujiyKp2AzY5wjpak9", - "scriptPubKey": "76a914c62db15b987951c6b281461b02e64aeb44a454bc88ac", - "amount": 35.24807338, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([c62db15b]02ac1e84ff0e12ff24126f691a4236758ff971ad3f5eb4b4ff27aaaab2c46fb6f4)#hwc5gs0s", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "8b4b9195a31b48110a220705c18d81e7a2fc5b73b2b142086bd215745812a674", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e36387431aa4f1fc4943a8743f9b62b19736c5592516c8a19b455430d3cbeb73", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2b2a3478a896c6925f24eae691a39fd0de1829736a8b87c55957f0ddfb04be73", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "890e9ee51520f27f7a4b63e0b6bd671ac9fef484825e428c6c0aacce74e5d0af", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a373ad30f0542643501c63cec3ea36b5900c21ff0ccf1bab93476732fe607f73", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c0197ffb51971004adc3fa7bc666149688258ac5a02dda813df4e551edc31d8b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9442e042238b622870bdb43d6e1ba8da0c571cdeb7865382faee088a11683273", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "363e288273cf8debc0df89565b7022c4b6784b164e2229b91cc3f15ba6c9b872", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2ad0e7f71629c45222c5f863898cc34be6cea8a1211901d3264d48a8e0adc3bf", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1a123ab558902fe27369310349ccbc62bef136b257011c3d90047dd9f5bd6372", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "df176658f7488577405f71baa18468ecbff823eb69a61fd1e15459a9123bbeef", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1fcfdaa25f295c32424a0feb9b98fc7d1ce51bd3f37c0b7166fe43d1abe8e871", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0d2aa60554bd54704b9f5405d4e4e095e21b753a7b94ee44a1a3b21472c8be70", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7a49c4e491c279554b5e2a82e3af78a3a187de0372ea95908e2122e2369002b5", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "81d45dfc17dfb3276fa00d3035a3ce381ceb3fc2afe1f0dbc807b2d3046ac26f", - "vout": 1, - "address": "yN9E4yjWrcdaULzumxzjBcTa9Zufi6SVTa", - "scriptPubKey": "76a91414016c896d61a2300ddcf95232e23371d2625b4188ac", - "amount": 124.35193546, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([14016c89]02c8ae42a4bcfc2ffeba1f752052aacc0227b4ca176e5a8f7a7913ade0f102533e)#g242xtqw", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "e2eaa5ed80208f3cb01144ef6a7a69718625bbc8fee80aeb4a673cc14faee5ed", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "744f6d8e3544678a370762b026c938843167bd8efd964b25df51f9e0fe11d26e", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f855e7c9c2e188e50cb17d54424dd3b1a07613fe5dab14938520e1141e71c56e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "da1b912d5ea8e5cf71696506eadc6053a039bf766c983822a76b4edde2b9736e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "389a9d6160517b32aa7993a4979f08d80ea893c93aabb4febee0f85ff7b79b6d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c0915b7591a00fd057915d378bafd4ff8e6d8f302f4526e04c77e43765873a6d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a64cef5223fb960aa38dc0f3f7229c982aa2e1bdaa7e2bbc4cb926e4755e216d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9579beeb22d9a1866f8ac8b6a777c7fdc9ca106abb2b05441214e21df120f26c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "df0e62404056d7c5a5462d13e03ff31b2ae0a360727941376250d00cf8b6c3b4", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "07b7e51dfe9ae0bcfe9e671269392fe86cdc20cbdc5f1eecf45b2e31ff02536b", - "vout": 1, - "address": "ygRYsNaeUUGmMLodLdSrWU1nW8Sec89sB1", - "scriptPubKey": "76a914dc8a096dbcc91156c8fc6f13a06389555ec9e0a288ac", - "amount": 124.35193546, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([dc8a096d]03aadcd289b56cd519106120a725a061b5b90124ecda0dcaf5426ae0cbd828ca3c)#auyzrcl8", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b979aa3abcddae01497a9c013443e07716241ebb4f7f363be600780e6428fa6a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1d705a90506903e273be2a57e6161bcfa862b436ae76e51529d937c242caed6a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "17934bf8fc59e63808601f80972f8b586df5df3488c6200ccddd6d8aeb48d36a", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1dc11258ac6c17d15b8626f92ead57ff7c171f7eaa8b0a1e463de1de66bfa869", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "18ab1d514db3c8ade54a2ca05e4a479b169f71a7da37e2577950753ad26273ba", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f0536e0b01145e5b9fb5e541341cc8ef989a4be33ac3145f70c56449a4c57da8", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bd042f054ce6c06340d40b3853d38a328b479286a3e209dacf1d8c277b2e8d68", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fd7b390ce40d282fd1491b4e7333a6944b3fd6cd6e517fd82ec01590159b4668", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "924b909e024ef8d95faca21f141c3f9204beb0d25e65f160e25af4956571f267", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8236a135abb0e6b4f1568cfb54dcff77c3ec17393600a7308accc6686dbbe867", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9bc2ae5d0f3048df1de01f6cf30fa2a2de50b00cc151f4d03cd2effd80b058fd", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ba289a2eb7a1795eaa76f80ea9aa5d950dcc10d5f6eb7a618b5dafa4d53d4bb3", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b262ee0ade785ca01e9e9909beb8956f388e16cf332672226db59c47aa408667", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cb0e7ba23da343599dd8505a1d2a24b6473e16f7a71783d49258f1987f071767", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e78b841034cd33c8334fd12c7e94cb91ef7f74d2d4a22b1f470c00363376f466", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4d3ee4a4bf8676dace433d1d8a3ca454f8ba0933cfe5a61a8801d722c669e665", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cce1798e2f47cbddaa0ca9f246acd94e316a6817d146dc6e82b003bd35948267", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "117db735f759cdd5f4fd29ceb487c596d9abe6900a2c9de483b23cd3a88a4565", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "dd792f3b5b5b12feb708609fe4761b0e38b11fce76f36ef73eaf96a2cc06b02f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e9a59c128ebe3cc47d88c9f8863f6dcd893b5839afacea3f120192cae14bd447", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b350cc1d7ad4ed10d04e5b23e96b2bb919b3f91849d9a01c3bc11f51b113422f", - "vout": 1, - "address": "yizK4F8douvuwRDJbDQqojGLNYF38rQySL", - "scriptPubKey": "76a914f8ac7c288c481de010817f9fcb7cfe0f298f4d0688ac", - "amount": 115.47339184, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([f8ac7c28]029e4fe0dd6220e81af56c5b35008fa97bb41ac8115462fe4d1f29a9fe5b838065)#ykntll3s", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "66ae3f5906a96efaca4b3c5c161a5f46a011142881a351e1e10b5c6792021c2f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cccf44f2032c0c986c40016733a01fd711d8b792e0cbf93c8833aa5c03e6d318", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c3dfd87f3719a5f841b374fc564001d5002a796f94f27f9fb6b8e6e7f54d892e", - "vout": 1, - "address": "ygaz6Z7qUyoAbKwZA8USyWaUDeQkkcW8iH", - "scriptPubKey": "76a914de52d6d2a4cf2a3b2cd6d8d435b390c9cd90bc0688ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([de52d6d2]0337d1fa9abc5acbaab42d666fd106996ed29f1f8af17956109fdd7b4b47b8adf2)#ezkweala", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "7f3cc4cd46bf697abda278b41657261134167b9a5cebe29dd2f4694b429f6a2c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "374f32e84d93a6625afeb3757c2abbc30a5c7d29dd998fe62295911519b8232b", - "vout": 1, - "address": "yYgiFjc8743haBVAvG73UHPvdMA2LeSzfy", - "scriptPubKey": "76a91487a71acae90c13872ec70a79536b378b9e86b22d88ac", - "amount": 124.34892868, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([87a71aca]026f095468e48e7657b635e5f5ee36bd2392864c2afab0ec02a77ce188053e69d1)#4sc7nzw5", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "279be1fe618c98ce8db651f2b3ea8485ff27ea1c9ee7980dc7dff19150fb978b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "68e0b793da02a4641fe370671f0278ede731f6c3ab1f3a1b13bcebe740d7da28", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8fbff20822b40a9b36ef9b677f2b13ef482eaf66c1ecd6901dcf2ba161764117", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "59071e84172a2c5e62ed909c765af88280a8b97e45f54064539c5d028f868974", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "60497a502433767fc20f2c7da6dd6d4e43710298082ef08be88ed6ad03e28227", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ecba5cdeb947dcf2303287d3e0157c85a9ce3c04d3997906407f9edab429ad16", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "51fe97c7cd6de6e50f9e1ea87d18f7b0608330a84e6278073c090c9ef2e6ea16", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5509039641c3708595483b6142ea3953b45a98752a44207539c8acd972b34927", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "be6111d05d779b67e901042440c33d2507abb7eec1549da8ca8c55621176aa8c", - "vout": 1, - "address": "ycYxKdy8t89Spwp6cDD84xLJQNEgyDXJMn", - "scriptPubKey": "76a914b21005db8f858f4b7e39a3c1e6c6d4f48f74db7988ac", - "amount": 35.24807338, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b21005db]031010a41b7961bc47254aa4d09e5454a9aeb03c6db5562b83d6f5c8930e3565bc)#zpa9a3pz", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "aaa72ab84bb0d44bc1bfadffcdfc2342421acdc32f3bb9a9eef63db7d3ed3147", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "211739e4daddb34b5408c67a82e14f0ecad08f01a72bfc654158d9a7d2fc4ddc", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e38746598a36781441c003b277a81d992e24ac1efc3f5fdefbe2e519ed4ace25", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "002c89d62efbc110652baad419cf56423a90b66bb5e17ce4534cb4d62c1272eb", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b2e1bc5af2c3ffb73a85ff5499555aa352f4fa4cbfafdbfb1af1a8ec7a716f22", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a4eec03478a474cdeb12f0db14f7c70c3b65005395e0020517459a9c00f3b1dd", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c92b6e4e6470afe1c0dfba05bc348d5cc83527d0089e38fd41c352aec265e430", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "47430dc773ea447c10d89525c763a1f8a64c9cd92e85ddd5235edaef7f523021", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "611e90494b75f24388443fe74c7e8a338e83dc129b58cf0093c425802f5c068c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2c83e5f0994299a11e5dd44c48c1fa2922ba65fd139d7fc60dcac50810bae505", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "700846c6df9f349e9af013f5821541e91ab16df18dac31af52e686c47274c21f", - "vout": 1, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b7e5001abb48fec91e5d6473522245e91ee8177a4d492930fc5652b7b5e6df25", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "de05c7ee9096de78e471f2ae724dff52498cc44677d63d9867afc8488e9d8d1f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a6ea773c36f10a8138f98ed202bb494a3aebd35fca55b97553392a75f632611f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ed5b67d36c3ef511efe487435d617c4eee171c1c92118377c3e5e9dfd2b24e1f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b36505144526a756ec5493330de4db35df23dabfc56bc5068fb70297922d03bc", - "vout": 1, - "address": "yiCmXULs76L1PatgXNRS8jmugksidCQ2yV", - "scriptPubKey": "76a914f00f7242bce1669b0300dbdff919889c7123556288ac", - "amount": 50.72781830, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([f00f7242]02cd64fe7bea174b486a9cd45ee61e81235f4bbceef8149e16ad72d3f0c4f0a960)#ptmkmenp", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "5b6affdd7081d96c19c18e7789ad7b2145cc70f17f292ce12fabc13882d8331f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1cc67777bb5861a1cdbf17a6cc68b9742300d96b765dba01e3b71eb9eb13e422", - "vout": 1, - "address": "yhE9fdD2khvp4Ee9LE4EjNkUV85wXDUaX3", - "scriptPubKey": "76a914e55a37f11ca55449a1bc9b145b17aa23d382b90188ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([e55a37f1]0244d208f549f6914d384d4e58b69e17def13948e891c18644b4770be7b2af1b83)#4x7efvam", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "92568b6bd031cd0cdc7d4121bca4e9a56cf1ea1b3155d25c00d77ad2008c7e69", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "658ca056621a1ace63cdfb92296d5444e59651bce72ca6d3f20a004b4674053e", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e83b5bb7641ad0beb8b7ec1a50ceee67925086bc486bf0eab8e1056c5c1cd399", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8d10327c37bb8ad2fe62135bafcbd8657aedc90670bf33fa51c82c9d7a43258a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f3a023ec9a437d2d0a11f50fb815efe751a9387c822fcdb7d1c7ac4c169fa85c", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7071412aa67fe69be16dede651257f5021177314abcae7bf7166c5a00f1a1b1f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2aa92f7e54927f57d769810fd0d1b976b2fc687847bca5de826a49ce981f2b1e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "13a910b2cb2837f1833382f150c60ff30dc5805d2718096cf9fe85f46fd75a53", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "766b126d19633de80d9abd8d6cc710b396a4ec84a657a260924afa0e10f241b5", - "vout": 1, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9f97af02e9d0df848007257666ba1e06c22d84c13ded3fd660718105e2a47e28", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "196e5990ad8c9da2e5a144cd3573cb8f834735b019407e20711ae5a3ad6da553", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "372f15b782587a445d0d919c1f62c07123c43703079638bf549d774653891259", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a5791d3d7802822354b49f5e127590d6503d1b516e601504a73aa908b7b27961", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "940567155c621ebd50f4ac7c966fc66051ce6bdacf5b57d519f67bcfeddec5c4", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5b95c374b04e75feb0b7e0afbb710881c019586c47292d9ff818b3136874a620", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b79556fef6a886860b088b02bca18f3ede526d7e079114a427c6f92898844734", - "vout": 1, - "address": "ycFYHCFXV7bgG6DnY4pwVWkoFJTtwMB9Bj", - "scriptPubKey": "76a914aec4e1d620c5e8934fe0aea5a83465a4e879fecc88ac", - "amount": 115.47359184, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([aec4e1d6]038917eed0b878b5d57f759ee27d59962aae3669e7e265857fd422803aecac6bdc)#v9mhmekn", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "1738144da87d1155e1f9498937dc246d29890c91ac3924a03c7db46da27e801d", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8befc8ab9fcd76f335a752bdddfeaf5ff7702179b02658dff2788337ff009a1c", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d9fa1500c91985cca51599b9bbacdf84dd50d5c9eaad5c3c6778803e3bf46f60", - "vout": 1, - "address": "yiMvUBpunNXxGH1u73CgsoXkgE3Z82g4QN", - "scriptPubKey": "76a914f1caa7d102b4b931486c6a2fd29f67f12a93f53488ac", - "amount": 50.13408425, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([f1caa7d1]02989a7dc08be4f82a5317698465a5c5c89a77a8fe351d4887bd5ef418679b2949)#nqnklkms", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "33f2cf11ab345ed61e98bbc7c017b468b1e6b24532bf3df7a59e272b66e94f11", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0df31ebee8a1cf18982f3370fc5451c362875a9b0b50f6cfe14ad9bbd12bbb15", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e0546bf83b2ee382b5bedc5e14825123dabbd2a85f453b6fe6224b8343df6acd", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ee4aae0af0c02129c0108613c7417632cd814ffdbba1b2d1a96f59e0c1366424", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e953a571207a2224de39856d39bf8bc1af89a7a8bf93bed99595c07d4444763d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7e0848e7691611b93c00381dfa523710eb89a8c7cc7e4934a6e7f9c399d10a1c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "810fbef174209f5646e4df0cc917bed9993bcf35813e6807dab5f261ed0c20ba", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ba09e1cf8a98a23c5712654d5f11f12647e7fe51bb8977b611ea1f508f1cce38", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "681b7ec57661284c4b69e1a12fab1c45f1644767ae140f862c39858378c742ac", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "33b85f925d4c1258c649ed9492d6ee20a8e6bd562259b4138609abc9239edd27", - "vout": 1, - "address": "yjGBaCdVXwSLitrGRwrkwTqwryWUiXVrxv", - "scriptPubKey": "76a914fbace4ba62fa1bad474ef4d4a2436e875e42e40e88ac", - "amount": 0.02087037, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fbace4ba]024c1c0e0039293515a3e6fbd425d00cf803e67ed169ce00ee656d908404490c0c)#hjwjve45", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "f60edfda5c67ddfa4d3d24584f530c2ac622842eb1eacc82b50b182b1bbac353", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e6687fa0b70c35c5ef686ad2061a5f401e9deaf8f94c7e8eb2186180265492ca", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cd4dad64c75c20ad78edc5edc0ba4941f64147a83f9a39dd37ffcb31f26ef191", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1d5d816747f455dd6fef3d72d86ef763e91f4d90c3c840d5a6abe71ef33c1b56", - "vout": 1, - "address": "ycLgbuLbJjfLiQv59XDwxYgcYqA8oC22AQ", - "scriptPubKey": "76a914afbde8c83051ff3ce87e61628b556c4e0fab106788ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([afbde8c8]02be6700e285f32eb032ed91baec413982a96532d7193fb251b9e30a937abd2ecb)#lqv5cll0", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "25ba35258ebc878aa8a6a0de2392a81f0d27edd65d0c7621e0f429e6d1e7f850", - "vout": 1, - "address": "yXbRvh5W2GiW3eA83qzSxT1ZXiGVg6evAB", - "scriptPubKey": "76a9147baf44d3c021ccb11bbbf66221fbc8286061c00488ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([7baf44d3]028616add66dfb2c3acf7904f37911b0a704929b52dbb1235aaf561e2f082dadfb)#6qrey86g", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "eaf92c61f8eb23fa22c3ca53b42de0f7d191e819d68f36cbbd29f1c4189b9419", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "895b67c83f96f0308e94118b4c875ecf39da0f1e6614fd8bce3d36916bdfcb54", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3bfa2af07a1d0afea3617cb477ef9aba695c1edc196026cbd8bfc39493788902", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "51a7ab0b2890974832adb0e085ddd8de3f898d91a24590b408059b33234da917", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4fc4524e78c245653c4dcb5905a55afd7f126666c4ee4b0c921e81a0e9682744", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f9ed970d14f575881a1651290d07eb426cc5134abd1be8d9e2b5ded3d546c9ca", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "04bf134dbab209fb3af97d080795bdd8f1060b7645fc2d3184934500e477058c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f00e9c0aae75c6cfc85341c9d1fee12e1ba332edc0f21d74e912fa3e08ee7675", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "11ff1778207a15f2102d568f851720327773818aadbe8db3b64dc10f9916a917", - "vout": 1, - "address": "yNzt8aHKWyowe1rhoNPSo8xkxLBhr9hcvo", - "scriptPubKey": "76a9141d6594228eed9fbf6b5122b40c2bca9bdbbc557a88ac", - "amount": 50.72681604, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([1d659422]03227cefe077b6098f56f5e9cdf271cd988f71ebb8f4526ffc75b579aa62f659cb)#50x95cyn", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "c1ddc45b9c63af06cac778791d74a3a83f47680a9389c44109415a6ee6ed4613", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f91fb1662ba99fffb11586272cd2c9771319f9cbd9473821893cbc70c75edb4c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0982e4635913058506b438ec40c989bead81b145d0d0735ee2ac3005582c4b90", - "vout": 1, - "address": "yjbAyXLLY5eNLgMK9y6QLXV6dpgiv9FjnZ", - "scriptPubKey": "76a914ff444c32dd3e1cc47150f4e23efe372ff3ec040088ac", - "amount": 124.33890608, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([ff444c32]036ccdf62749e27e3a695f70434e7fe020d593e40b8e403088a6b32149b861808c)#d9sex0sm", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "2fa5503f445da596e208edd784fb1ea35bf3147c235b2fc1fa37a45b5a645585", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a5234d6289fe2bf7fa128a45eefb7137be9bf6844b910c0a42ec911f8956a417", - "vout": 1, - "address": "yfShqQgTx6tDp6Nyh8LbH8nKW73aDQwHHa", - "scriptPubKey": "76a914d1c9cfc2d7941b8c5680bd5d18527fc6e7a6701e88ac", - "amount": 50.13508651, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([d1c9cfc2]03bd49ba8e8a43bcca4ee5339c541677c1af118e98351b95d59555265dd4a97022)#9h0282ec", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "d30a2baf94cec8c075775311dd614962c6c2ffd864efa0526a57e718bbb3ffdb", - "vout": 1, - "address": "ydTpH5yLfsi83HUPm6sK92Qm9VXREoxafY", - "scriptPubKey": "76a914bc0f5aa435143091a4e2a113742fee0a43483b4788ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([bc0f5aa4]02763c32c8677345a25bda605e6492be611206218f564b1e924f043f4a9ed58ee3)#r2r4ra2v", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "01921670c51a31b6e27ddbe98a758213084a90432c9ab58d037a284af1b90413", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b447abf902ba2da7b7aaed312ff520db5e90b187214bc53cabb602e77416317", - "vout": 1, - "address": "ySoCK6wabkg4uWPAo2d9NQEakQXvZV7DhY", - "scriptPubKey": "76a91447104475b53f042a2eba809531596a81aa7ee55488ac", - "amount": 50.13408425, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([47104475]0256aa84c7d10513f219a9a13efd673673e292a6668d3aa2cecc5d2d14e9829636)#2l2g8f2s", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "391ef80d18789c72003444103a7e9599dbb9a540198b444675e8bda8e34ed065", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "64a2f3ecabc9e6d8349aac05d20789ee7b02402a37a12706bd0818b185541717", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "17ee4db8d9464234f52317308475bb03e9d3082b662f02eb1547f0b2525d9aad", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6d98e04bb34b4ff6e656f8d30cc7639e88629ce21d379bfd155484bbd651916a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "02c8e6816c562ce982df19b94202489d2ed51d417fe9804e4045316a896abf2d", - "vout": 1, - "address": "yPs3ABqTszFHyePqZPto2FDb6UauBWTtPs", - "scriptPubKey": "76a91426e1e9abd854dfa45a320c09fc407a5f3a0d7a0088ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([26e1e9ab]03afc6cbcd65236de667801cd6d2e0f783b28a493b4004a776b3c73f46ad4d94b0)#c9f7xm3k", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "5bc70ae12677b5152bbc5f27436cf512671dcbd7fa2d02f53bb498cf7f79b40c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5d4e7adede012494b898feec76fa09f261e6ddaf8402a73de2c7c2e7879dc509", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ce18ad889abe105c9ce1b70a9bf4e2a20d9d6463ec74add1e052dad00932582c", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5457b5a125790016b45800a8d6f0532bedfec73e61160d9be3de87c34afdae1a", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "86b0d262745bb1ae8026eafa2fa23ff94d376547676e2eccba6ff56d321c3052", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "07d85c7c4bf091a775f9de754e21cfcd862dd8df70b0655f2bd0c7b5520952e7", - "vout": 1, - "address": "ycmfPysEzWGEeHjLDiUaEw4Vo1H6WF5YKs", - "scriptPubKey": "76a914b4774d504d7ee7ead11e3b55f244dc81cd9685e888ac", - "amount": 50.13408425, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b4774d50]0273585a746d22986f3bd6ae842bd7e0b90f43e1a63f6275652e04b962db7c84a7)#fjxhaun8", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "f025e687b84eb1c4f7bfee3827453453e2bf9ef7c8c41e71e57cc2448f1e6e17", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4ceac2cb7c7d553ac6934546bd9b9c59b6183805aa0ae681718965581ddc0f91", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "237f2f126d65dd57e2e57605aca2eed49fb7a2187666276a4e281e9d9b2b9e08", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c9d55835d5f18412fba55cecad8262d1ca7910baf39192603cd287b7ae5a494f", - "vout": 1, - "address": "yVm9Vnxx1HFKpgTbBs9VFA4zxaxr6dCsnb", - "scriptPubKey": "76a91467957661d63de760154ae943f22f8c77dfa3c97288ac", - "amount": 124.34391738, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([67957661]03e8e7da2ddd5efd18f547ffd1e4b65b6a6b9bfa3f544d3085e477cccd93c923d8)#3qr2j7e7", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "9ffe0345eb39baa597fb5b73fe9b00afdf7e814ae25cc110e937ca477184c883", - "vout": 1, - "address": "yYFqBf3t5TaBUjBu2ErWv4xNju493pN4X7", - "scriptPubKey": "76a91482f27d7f08abe1d11c6b2bc4afed2e098123738a88ac", - "amount": 0.01293413, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([82f27d7f]03472ad05fc7414e8ed92823bef082e1e25bd2d89a8f5a57baeb3d6fb953ad404a)#5x752578", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b31a7c8d2084cb9e36046b49eb1d691afb55435c36268f5b5ab1545711604721", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "28c3ae36de43622322f404f38b28d749bb885a6404c752332151f30c480e0bcd", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6ee977c2ec6b05a4e705858d9f14b598bd6e27f74da843a5c5097342a3c19a99", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "73f338b61bbe97f1a31156398cdcb2e29530d0ce7f0c5c60d42d22c111364d7f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b9aad98c97b02fdca8da2d6e582885a53f011ee07e17f26684ef5f86cf28bb03", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9579c0c65a91a562f85726d34e3e20e793ac6c49a158f97256a751588a696d20", - "vout": 1, - "address": "yYogUZcCVNytsnkWHyjRu9eQ7nwSZBnhFP", - "scriptPubKey": "76a91488f888100f9169bf9bfc055f062af76407f7336088ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([88f88810]02836b2908402579af0f6003bacfb36fbdf8f5e0ee7505a25660001ab661fe5e4a)#7x53dtpv", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "bdce6b99b1749a96cb19be7d204ebf05db40e4858edf6ee2e8ff54ae556ca549", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f50d61e180434346916679da9e31fe081b9a4fc0248215af0777bce24f543a06", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1436c9ae887aa46c26b2214708f9d3582430619154c9b3f7560e5b8b67d078ff", - "vout": 1, - "address": "ycBax7i6R3pVq353xnPkQc5Uy3GpW18mcr", - "scriptPubKey": "76a914ae0572b14fcdfc105c66e96bc0edf146b066439a88ac", - "amount": 0.02086293, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([ae0572b1]0282386fa239f1c0b0188409fbc9f2bbfe2d0712afede24be770d8fbdfe22082d5)#t68ats82", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "db9ffd149678f80a5df31208898a9b1d377df84eb072553c34902ba7dd8094a7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b8f6ffa6c1617880769c53318b9436fb67efb0f7f49353f38517364ae6256150", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4c0e2b4317ea2b83f3fc0593ef764804b98f795378a277afc0e3e19c9ed96c58", - "vout": 1, - "address": "yS5AmkGfbH473fHoomAsWvjvzmRfxvEYH4", - "scriptPubKey": "76a9143f1d82cf3f846c03b1ccd57969fc84bf67eb11d188ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([3f1d82cf]022c0ad4e1a62e1d20463f30f647ef03f37209af3ba5e76927cad633afee9b69eb)#frgj3av0", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "a54f52c40f726713102e215e548c7cf95032ac3630d18e6cf70dcde3caaf8285", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8c5e8ea54d2440d39215447e1c7ff76c2114b34125efe9586957da5db4ab6b3d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "36e9a947a03e8ab616bb5a002c7e84adf841acdc929548d56d86e415a2864506", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5a284e23b9e41652773cd8471faa884b1935286b384d62ed91bd2aabada5b72c", - "vout": 1, - "address": "yaMDWmAitJvHcaqEAiPokwHwfe9sbEn68R", - "scriptPubKey": "76a91499e7761420bf505fc2858f31dd6e3fe99725817d88ac", - "amount": 0.01391407, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([99e77614]038f1d99479865027ed6344fb727fe2e4aadf43072f2eed7c895bcd0982e4cd31a)#9uzl8egm", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "267337e72cf5fda48a2c732d14774744bc52a30cfdf50a2c062b3cdb1740c103", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "26841b51085594500575b2222b9960b338fb00561efef560cdbaa2142067b6e2", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "11cb85fe907d89e56f1bde9325f25c3615ad9694f06e9dcf137faf714a801677", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "31620148f654860da91347b2dfac3e4063dd984d2ad457eb8ddc6a17f614de1d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b047de9c01c111c537e5999e0fd9c1279f1b77959cc105e86817e5e00afc271c", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e704420fcc8ae58dfad7c829b4a6d292aeb5389573bff0d74926e4472d231803", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "447e1d8fda5cde3f2185bfb3e81a51e5d68b419c72e2d96076f2a990736d7c18", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "59b694f4bb3229ea484d891428113ef9fa33051fbe2dde80fb2778ece60e9e9b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cfa8d6f9be94f03f0ea25655a409dbc293d1176f056b4160741ff70f15c71477", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d9a2d03b2444c563b949b024ac92657ba7553e90fa5e095d3990eab5dcb4db2b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "386eadb00c276c6e51dc10f800093839b2dd9578632b251a0942f2389ee60713", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "807b3dd3a4397791dfe5349267f0f1a45a3f7b1920ecc001e5a66a45f8b5a34e", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7c2bbfd67872a33b1721de817eec4982d9350331fd654f92031177936d67d605", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "af41a041b879541b114ac3e4cbefd27c6012defb40e2b26954fc376210715b50", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f4eca124ebed834793f45bf7b7f9aeee9b1f7362b6d0dfb84d474c6e84522b34", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "64b215d85c50118c02dd1a4fa63f1d35308504ca22a9656098c7e8d7c3931769", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b73b8a06914bb1c14010090da95c32a02d2413c7c362b856561a47e054ead406", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c1dbc8ff8a17012435511a618ca51845691026c2e0c014383eb82909e4f58b18", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2ee253fe512e54090e96c4120f971b87af7a30bba86fdd98da421b93c3a4f642", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7a78b233096328eb40c8d4527b2d41d2ba5343f38d2deba0362f34c1658cdb4e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "16d8d0c4798f0fde72e49cda16d0f3bc6bcda20e2590ac054263a30ee3b45812", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d57627f8932b89ea546f2ea614a3f46ae4396e8330c82fc53dff9b188e4f0f08", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "818f3535ed676b014597010ad37acc1fb4461a35932b1ec2929ea03ecab1b209", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b16f2b504e78a1a9caa47cb259c4812e5bdb57ea9a52c3e7518428b304012efb", - "vout": 1, - "address": "yeDgEQHhfBbfWNTPXTNsV542rEZiXFLUCh", - "scriptPubKey": "76a914c45af08f9993da61911594170ba5acaf810a8ff188ac", - "amount": 37.95992622, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([c45af08f]0291bb226c6158d7b74d8944ad4accd422301d08fc3e912e1572223ec55bd39b9d)#xdn0d37v", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "1ea5626680110ca5f1c46e3fdf42e51edf0e8c9558bce80b15d1aa7223a31338", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7d86ca8a2eb9bba3573c8b7fb0e9e095b43179a71b944121a410efdbcd5e50f0", - "vout": 1, - "address": "ycRGT5sSAuGvSGyorLdw6WBtBnoQkagQXL", - "scriptPubKey": "76a914b09bd3f81a31345bccfcfbf79e488851e22bbad688ac", - "amount": 50.72781830, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b09bd3f8]03774b0a8ecbbbdc55065fa2391a590f451f2ccb24a7e72a83a6b63253f7cedc07)#9cgva2xe", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "610be53dc6af0fc1fdd9362e9c807b386c3346da2d51c0c4ccbd808b82dfa81f", - "vout": 1, - "address": "yWXvAQ6iZgH92Wo1MLWogt2Jp82jQfMeXJ", - "scriptPubKey": "76a914700d0cbafb216b0bf7ec05eb24e605f7d54a9e9a88ac", - "amount": 50.72882056, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([700d0cba]02683a16a37a2fdecc37155397a67ddba7b3c6b548cad008725dfcfb8bbdf5ee7f)#p28xc4ex", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "bfff58c1276072b9aac2448fb72a197c77b95bb689043a6354fe6025153d4704", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c705bd929a5b7df94c1e3a0746768c235dd6afc30c50bc7d3d0ac9697a9ede9d", - "vout": 1, - "address": "ygeQe9W3e4nh5LkLvDnq66EqFxny5U5Np2", - "scriptPubKey": "76a914def8936fbdddb88cfd081f0405b449a8a93c843388ac", - "amount": 0.02087037, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([def8936f]032cc48d6148cb035f0935a5446b90d4da34db9267a0cfc0644e1c308cd812ca47)#y5lgl5s8", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "d08826f2ff737cedb2d9bf3fba061760023b26105b29d022d4df2b5f554d7e34", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "63703f9f966d3e7dffaf322df7719df1c9c7ef0a96f4c8ecb319ddb8feae2706", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2439475cafa1a3a87e480ad771b81cc8bfd1dce0ad2584b22421ac8948eac320", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ef52202cc4d9e388b8a2af90eeec168dc65081cd094672272d57360ab4449802", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "83e8636f18672b2b9d0b3e389944e98c21a5861a6998a648bf8103be8cd70505", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5cfec53fa17f27359f9018343961719c7868224f41292b6a184f36136804c602", - "vout": 1, - "address": "yPphetcQXgeZv6wwWB5RADxs11zJ42pw5A", - "scriptPubKey": "76a9142670cc7e2c2d00e9d1c3dac4acae7151f6586f3e88ac", - "amount": 50.72581378, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([2670cc7e]02c64381e8ea9afd91bb943918d972964215b973a8c9d3f88729ad55abba708d74)#e2fws0a9", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "91b3ea6442e184e2b3abaa6d45b6ef2c1a1e4279138df49afd84c68641c735ef", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a7900c54838fcf2c48ea0e2f5c68d1960f47d9a225235de45aace676f490d907", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "63aeed6ecd68afbca2996653fa231fecf511c2aab902c1145e87533c25f99314", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9298b5e4e1553771b011f2e3486040b526b17fa1ac521680d544c5200583f3e3", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fbf756e71b0675a87f4ab1fe20ff21af5a170a606a35cca9a640cb71ad2da109", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d901e8bfcc61b1a4011dbcc186b316f5b8dda062e645d75df2d27161f4d086e2", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fb76715995a2e2f712318f9f62d5d5d798b6b555889066cb7d4bc23d1109e620", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ca3d94c5b6b60b8a92027b9085d77650f5bcd6a6830409d27f201c947c234a36", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6f0687ab1eacde7b8f82e56464d1cd576e91fe0b7a7ee56812d327ac14249b32", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "05140cea6869faa7790c2afd2f87b18083a0dbc40dcb18726a1a9759d76456ce", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b6d9602e11237ea923f6d39634527d744308881adba53f06ce4cbc7e6173aa3e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6730bab6d4541464cc30229cbf9b76d7ab2404ec27b1f77ff978839271d321d0", - "vout": 1, - "address": "ybw2n1jZGXKuScLQG8sEFpSoTahv4m283i", - "scriptPubKey": "76a914ab44c4530604a535b1a2131554f2e9eff1b683f188ac", - "amount": 35.24807338, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([ab44c453]021a6285856641f3bc75b644497b65e12b9c80e9a256f379b383125d390add31fb)#zjxh42zj", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b746941b27739e10f6766a6edfe26574b85e226230bf360255a0588757d9634f", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "00563e479d1902751a3d44c4b078d2027594811227528d01ab1e6291163b1d63", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ba068f216f2192119e2c9f6c6507b1b80412d6d7037596387dac7d542def230f", - "vout": 1, - "address": "ydw8YqGtck2unpnhHPoeW2jkVox84RxECo", - "scriptPubKey": "76a914c139d659fea15cc6a15dcd062334bf8f341159a888ac", - "amount": 50.13508651, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([c139d659]026da90dffb720a3c21db5ae0f10abd5228d2ee3fd7b5013ad6eb82becba25ffbd)#fmhd2nmn", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b186a3a80e0d852233651bc351ac79175ede91c7c0336caecaec9863f401245a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "72d22e743ef990b359a22b64bf9a1aa0381d5ecf308218ea36264ee09268e60c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7f893f2ba1e37921b93145e7878e06867989b53a2dc7c20d21d04cc602b88e99", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3f10ff87ed719cc7beccb8a38197d1084da0e2f99dc337303ae1263744e50a2a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a365f94fa595e7dc2e3036df806b062804127c2f7ab1a4a26332e2d7bf1ece02", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "df9841d35cba2ef3b94ffbb17f0b1c689b0cc56b3a91a7d52398746e92410f07", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "822328e0355f5ec0354e34a74829991467aae25b5aae555349cdae229c9cdd35", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f0783f385d48ae2e53c7fd3b05ae27e13077b98abd13b5d62d9d3eac72005f2c", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ed6162c49d68a20be0e04ab7a0b6b34248b50dbb96c7f83ed10ffdefb6a9d84b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4c5b62a43b9d207391a54bd8996bf10e7438d223483bbd31f6d7219e497c7006", - "vout": 1, - "address": "yhPmWm4Po6zuMtvPNtcYBzwPQJkgNhkYpe", - "scriptPubKey": "76a914e72be2c92f90134b0fa425541c54bb809f349bec88ac", - "amount": 35.24907564, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([e72be2c9]024d7b2a121ac78c83c49711a1f0535c278e38872556271e90840279a528239215)#sfnkrefl", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "8252575700912871b252b032a0bd9ded9102834e931d8bf9c2ec20fa2f399c5b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0bf7ea994b42f726a4580a4d37a9f8b68f07fda167e4c48a128ada95ad266f28", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4035ed06e0865609cbf4d3cea6faf0ad5eb7630db13c7f2b2d017552e95cb2ee", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "eb1e7f3346025b83956452fe3dad94e63b6ab5c4b788c5202d4bba1e5ac5e84f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "acbeb9753c0f2f95b291271d69906ae0a84c4820511b510a9c3871e0dd328028", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d9bce0688a45ce111388d9a661d1993af2970e86207ccf3f574122914533d35e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6f7d04fe37036f583b1b2f6a214fea5e4c68dddda8e4a52d6d8ccffc9c695960", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d0e16b932ad64b995f2e9b8f5dfd6d50036cdac19c6c6e1807e7e4924a2d8b20", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b59e0894557fd4eb4462a4e5ca4f7b21ffe6cee102570a47a0dd3c2ae729241b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0cdb20443cb7e80e13b9a9478f67131c7c5fc039ebb1c644f1f0710b75ea6f10", - "vout": 1, - "address": "yXrcfZxJrUBRyaubeKo4mhPAzjPLyqbDMF", - "scriptPubKey": "76a9147e8e79145dbc4bf0d82258bae30cb8e082e0266c88ac", - "amount": 0.02085177, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([7e8e7914]028dbed34e26c8af8ccee3d2dba15853865de7dc23cf923871eee4322736d7a9df)#zwpe0783", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b9a4f3279686c6446509db05838f5e73fd3aeeb535eab267a1da7d6ee733f37f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0264d9db431c834f905ab3b5351290baf39db0dcb25dbb9bb75784292f5e1e75", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "909753c722b27041344d0b82c8d923edf819d16098abb72f786c9e768a0c7843", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e2c810dd615a5ba63896ff8d5da2c3aa84d32628e9bb872ce8016c3ade3b17ed", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "da88d3e0ce159ac5bbcb886ac9ea2730e47422a17a0da15536f57b005db3844e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5ed3b938a3941844428a505c6bf78c225588d6370994263658c2f90a8188bd9c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "14887507a538c680fb65c9b65d6b01a7a0c1ac14f1b15534e30d8c493cd4b427", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "50d97a373f5ea26de3d4f70dd7790f6b2fa13f0a94e8297b683db3a479723221", - "vout": 1, - "address": "yhJGytyAV2kNBuxYvkZChayBPAPatgsfBQ", - "scriptPubKey": "76a914e621fd18227b4ece81a2e05d57e7486b96a814cc88ac", - "amount": 35.23905304, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([e621fd18]02530538698931a3095937d6980997034cd18cd14347ecd8fc782fa79b47e686d0)#d2jpkmus", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "84c085ed2cd79a4e4581e5132e1ecd4e7a580440b2a7a503830c51220e1413a2", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f72003ea81f0b6acbc3b0697ada5b06708ad87e5d93c71321be21a9ad429245b", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f87122ebac222924cf7a795b2019db60905e3470676394624d4c4036a4a6cbfa", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fd8ac2c00eb81284a264dda18dfb8652654a31f1e26f7ac95bf6d2aa49908d7d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3242262853cf7e6a252e2d2e524ec0f6e4494df35e624e694d01e2c7fb68851e", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "04359abd4997fc80b976189a7cf30ec8bb767359e179aef80ab4c5ba76ba8700", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fc17e93d83230d48a5509f91638fcf06fa1072358a6cad0d4481c84934667bdf", - "vout": 1, - "address": "yXtHdps12nn5PZisEEA1fBFfqWF2ghN6Wp", - "scriptPubKey": "76a9147edf6b5e0fd07ae1bc0cbb511d25654ee4b01aba88ac", - "amount": 124.34692416, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([7edf6b5e]0342fce99e597bcffe46008db30ef6ac05e17f636e40b226eac9abfec618290948)#c09q4s03", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "f33374e4f71a2163ff517c651e60e4396098280a3630411403f2a2154ee361cd", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1a97ed86bbe1b3842f2f52a08344d9c2cb6b1cef6af1535a728839eaf8e13a03", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b77b1c7d144c7308d19974bf775f5d9ceb64f2e471c0edd67bbd5b5f1a8d4503", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cc83cfd5e9fca6be3bb1b0f5c004a0ae0be282b628d55b3c8deee1a8394e3bb9", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5d04c546f4346b27dd6b14da8c251000cb7bda5feffb50db194e22f54804e32f", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2505934ba003a5299a3001118da49e2e7b57ba6f948ee73a66f5474dfc9f5e4d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4f7209e803897ad55baa4703a173ff9ba24cb3b82725f8a150a52b3e6c1d392b", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "500a709c9858511aa7d85538bc824e80f47cfb59fe6fee926d8d317390a95f2c", - "vout": 1, - "address": "yYwWfLCzHMraVrzsUUz7pXpdQ6dPsYcAhV", - "scriptPubKey": "76a9148a73aaa658be4cf923e73ebfdf29e5a5c8ec802a88ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([8a73aaa6]03284a8ec5c1f52043cdbc0f47ff109f1bd5aeae0502ada2e274fe5d7ec58cc521)#uatc49se", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "a0342c1d431e47d3ea4e10466b2ee05d3219f6e439e2d76fdcf5d995d20d984c", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1035b6366d2ff2b82f872a922e1ccd3e95efcc18035180e9025d88b1bdf7b9e8", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fe540eef6263cbb7a9ea966831f44985db2274dbf17a5b20ddcf3f564ccfab00", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fe327e955dbed04ae2fa26d5c3b8ef81612c29a8838e148156ed5d1c3a2f5e02", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "455b186ee7fc475967aa19a3d298bfd625d4f2a5aca522e17a8d87d6877f5768", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0fb6dc5704a8f523210cec9dda0ef569d15900a6e6c677d6133a0210a830834b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "89178a192128501bc624ed358ce8647ef78348b042e368cdf683d352b2ec9f24", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1815c3d6ef1601db0b55f52550a649d989a97870aa6eabd39bb4d216dc508179", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d574c5e679a92fa225539c67d6838e2b5032f6d21a70ac8ba47cf4c1e316770a", - "vout": 1, - "address": "yhXdU7AWZ3P13nvJ9ZAJ79dCqWDT7qW68Z", - "scriptPubKey": "76a914e8a87f50af249e5b4600edb7c930c649d374295c88ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([e8a87f50]02524c64803eba245078dbb11a245a7b588a13a168a73a29affe73ea6db73f49ca)#qze28wyh", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "d02c2405b62f0e254317fbadb5a243d4f28e87a251aaf1cee893c411c1e8a4f2", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "673b5e5951d842ea6419659b7606fe025f1d1530e0e2d4e964537bbb871c9420", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "82cd949591c14f66f5ee0ba22286623fc6b0d12d5eda3f2008cfe84265c2b119", - "vout": 1, - "address": "yPoxbWW3CA75d7gwUbvQx8nGW1L8Be5sax", - "scriptPubKey": "76a914264cdb04f3e0c26f342f95db72f50f5b1e5528da88ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([264cdb04]031065aa5512eacd4b1da2001245885e6a746bcaf2aaaddd605e0ebfa64126218d)#yu64wufp", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "897357bd53da3feb14e5a6c4f59fbcab1ea2a930d9c9b67b968ff4cf9cc57b16", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d1fa7e4b12db37724acc1315ab1880f37a41b5aea45e7ed9f67a35d05d9f9e14", - "vout": 1, - "address": "yfesT5y5Qc533rFT9UUYyoAzM5GYKUn4Nv", - "scriptPubKey": "76a914d416d4545668a64854fe2289753b1f4e7650c69d88ac", - "amount": 35.24807338, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([d416d454]026f47a8dfbc4509d8c0e5e0a32c8cb53904a50e05ad02d68a01703d5c7e802000)#pnfxs664", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "c61391a8edcea18970bcdb562c1334dd7b5f214c936952301ad6d05f5b7f8c0b", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3233caa67261ca438cefac99aa7f06673e591556d2c83d0e7240a18688f2985c", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ba92f4f286ce308c193645918667b9fadcbd8a5894011716752fa1f04b49c769", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "686874dc3606c16097f11dca0e710f0b952a6894d7a7aec4ccbb1bda69181329", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "42941e8b2f18441cdd56e5825e735ad98447e9dec3fdd16a8e414415e493ce7a", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "47561492ebb9a84b8307af5a9eae33437ffc04b75e5b93974dd19ebe9c11a30b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e81390bd58c701d37c5237bf4164c14a31d0efa98f6ea933c226400838d6a773", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4591f7cff8f5584c7814aa48b2c7c6bae7aa4a57fde5c580158aa3c2137eb744", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ab9cac4783c9a1bc932cc85abdb3ed8e290d6586d909fa3b39ff3b1dabe4b993", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9b51a6de053e597c09480adf492630a93ca74c5c73954d12b1b9ebe60131170d", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e27608cca8509dcab2e71596825b6c0e08046eb9b7403de72c7a121785a2fe50", - "vout": 1, - "address": "yPmwiQRtYn5GTkzCjrTNFj9aacLQwYDXuk", - "scriptPubKey": "76a91425eb49c11b22e5d971aba0fb5a39a245089b4f9f88ac", - "amount": 0.02046293, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([25eb49c1]038e4b42b7d03be25a2eba81ad3ebff9b44e6a4496defa9e97679186beb697b7e0)#2kgjuhka", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "fb37d5d8e5247bc6d20d978db3206095a38ccb70997ae7e950c52381c8de31ad", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "932338e438772bc386ba0e8d7a2e97e43846b8b5f3952b4d8578ce4f1d116d5e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1ec506b611923ac6fbbac590080dc45a659f5d37c9af6db55f3a181321b4dd2e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e62e25a75a5beceef4925f8708800feff07c6af23e79befa4dc51a984bc74029", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3d0569580a70b6a985546378b92f17cf3a97b6d989467d0cf2bcf86754f0af61", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "336a22f1e41d9a5edfdc35e23fc5ad52f7d6372c25e781b3d7e3350d166c190e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cf4cc9b3b37467bb1f4f6cf7492d4eba278c7224a4df00c6e1ee99a19032c189", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a65b11ab31d529c7d691c05710fb601cd9b23500d3d95a2afa0a85a415c4ae5b", - "vout": 1, - "address": "yVGbvjvZWLP3iNdpdoL2mSBMxjybhLeahM", - "scriptPubKey": "76a914622f76595024a22a454166ef485b1c7c1b1c955788ac", - "amount": 50.72882056, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([622f7659]02e8f51264116b9a7a06093b2d55873c63e3572001941f17080c99d147cd3a4ed4)#lkh802rh", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "53926ecf7b180da429af31ecd41b06ddf1521bc589fb6fe26cb5d1dbcebb81c5", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "797ddb7396bb503a60649870aa5f251452913066ef3bc3be9a7fd307efc1dd08", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "903acf6d200636161991a363e4fe0a44b7e39ca090c997ebe80806abaa830810", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2fe7f7b815c86a6a20f048eb170480d7c6c87d22e8b53fa12d38be507be60ede", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b4c813479675c22c3a072a8d0a655faec150ba0f536f5f6fcea9cdcffb8523ba", - "vout": 1, - "address": "ycqnJjHD8tDrJkbVeC6wsN6YGpWQauGtsf", - "scriptPubKey": "76a914b53ebbcbc3a6d4a887ccea6d3203de352c84e76a88ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b53ebbcb]0287642f17159b2d9832318d3497bab06c76be7844dfb8131039509e8495190db3)#6gnjvy0m", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "a2b7084ec893e0a82159565b9931cb00ca4c450c945b4835f881e8358be5740f", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a0ceb07dcb68254e99fec0a7819220b193a38b24a66a86068b9e7c381575bf0c", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b36aa5a7fb89f34422308012a8f9dcb82178a7e84f62885b8b6515c10e37e9e9", - "vout": 1, - "address": "yXtMhpUQL58jB2FgMtfrNU9hmSzLjdDxW2", - "scriptPubKey": "76a9147ee2d0df6473ca2c53c2e83f18415b3ca9f3dbed88ac", - "amount": 124.35293772, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([7ee2d0df]02aedcfaf2642359a66f4ba1ed372f9c000d3dbaaf5af35c2be3f2f21af3dc1326)#h7rm67vg", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "7e0f9d8c14145a788c065be7fe0d07fa5b684ec5882474b280685ebd6bfb385c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e22b1e3df2b53a84e63c3c11d8f2f2f50defe76d7f41dbfcd26afdb59b58ed04", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5305addc8d10e198df501b6600c2f1e8dcbf3ae8bc7570790776b06988efa514", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fec0cfe9dc71ccd6cd8a85a5f6844b6224d1226103fa5de322d1ad3339b54509", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "85aa360f91b8aa357c29063c364ac80fc965b3c83d65dbe893de8369f8a67912", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8e826da711024489c4fefd3b8d3d2ee8116f7d4b896d7461e92b2bc143b39b06", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a7210010b556a699dcccaf53f5a17114398dd50f22659ddbbdfd8236582a41a3", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b1034c1c6bec54a94b31b0ca61d90cda5eead2b81c52b23bbb556ff0b5ace71", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "dbf5419cfbd202961b6e144c510d918ce73b9e5c6239845ab207d23b649fe709", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c888741199b93854f2e5108e0ca907ef1dbc138aec13ebb0c0a972fca9ecbf0a", - "vout": 1, - "address": "yV7ae8ycXJ4Ky5DXDzyVwUf7xUXJYdp5v7", - "scriptPubKey": "76a914607aa51a89b690944f12110ab382be75fa81def588ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([607aa51a]03f7372da647481230609288d401b6c98c1772c0da7cbc5cfaac00c3224838228e)#j0v8yjv4", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "4f1d696f968c838b7113f00d968e5fbd743fdc3456be2618d83c440d4a8f3a95", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "00d919872804238c53337de74115c526d05cd5d08ceae779f650d514c369b21a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6c35a1b34c6d6cab313dc15125587093b92aafdcbfac0c8e5caf8bee93655b10", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d25e50f9aa325b3ea9b7f0e4ef93ddc95a23aabcbf7aac70bf773d730de68e07", - "vout": 1, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "33202e8f3191f91a1270f286e379fb01ec40dc64f208bf41d9b305facf16b3bb", - "vout": 1, - "address": "yMm6WA5VBo4Y7swwGrkoVHbaBDjxFD2VXW", - "scriptPubKey": "76a9140fd1f50629630d517fcc9fa48342b118eeb70e5a88ac", - "amount": 124.33990834, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([0fd1f506]03cdb33fc62e34e4cac7ef1baf566adbd47c344e31d7f68f8b30327fcc883861cb)#3lpcq0a6", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "18c51974de3af676b73fc941f139ca0c16559227a1ed1f19ce200451241f7f3b", - "vout": 1, - "address": "yNXrosduzsNE3k83gT6oFKX56JA8mfifv2", - "scriptPubKey": "76a91418493e683264e3d99bd9b4d89e835850f9eab45288ac", - "amount": 0.02080341, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([18493e68]035c6b19b623ea448bc4dd5701a337f55a34010f1961c2ddd781e27e1d87f920d0)#prndw5xc", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "16696298a521b6a1419c6821489e885d676b08332db5ca965479e4940739a5b0", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "31a89ad461c0864ebc02a0deb43365343d1479ba0dc80da2ab60e27b709ef645", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6a26929fd607b24f41d6bf316534fe473e9eac0510e167cc2d0c00eb7537a440", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "45ebfe1a04345bd0e374bf79883da4a2035382bc7a121a112fb6b9238bc1f109", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6147dc8c0d6796920f6730950986ac159cbb38fa2d56effd63e4f2b23a62dd58", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e889676dcadf7742ff0572f2a0e01c6452dcd6c544f0e47c5ea28f37fd641714", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9ff99b06397511324b688f90997dfed2afaadd431cf72a39341442c626c4a775", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "74d9d88f6b6f31c0d1a2cdbd7480f00f001ef237ca7cc55bdbfafe45c6c87013", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9bf3161aa6c5ef45f41bcbd7e520d8442bf1f5219aa73f863b26bb276436d20c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "63b1d0ed99716fe47ccfb8b378e4fac896bf0d7cae1d0f900d04ee88537d6ac1", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b63653b2a5398f8581370b995029da970e4861244d374198be11ec30a652d338", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "36ac382315bfa1ca46a9a03f27bbb73860aa98f69458fea6b3343ca8d0e1f242", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2bd7f669ed8f63d4d3ef5392c141711f748fb271d9b76cbf790e119b974a2bc6", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e1cc0068a845fd62e69e665b35737092f9708567b7dc17799fcd19bb58aabe5c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a6d8e806b755250ec683bfdb1f0fa499bc1ac8a217199b00dc64f33fe4650215", - "vout": 1, - "address": "yhca8kcZbJhXFW9XJnTd22Lj2ro8gkYzYx", - "scriptPubKey": "76a914e997cb5795aed0d3ef692eb2db77eec3e6a0d2ab88ac", - "amount": 50.72882056, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([e997cb57]0387aa782ef806cecd87267eaea233904e1e1c2ea5da2ca6c6e3bfd3c149fb2df6)#jy4ex3eh", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "3182f52a6e3735d0a18643ee36bf23fc19616ba71489d26cb22d6dce8d2b4f70", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ab2cb3be7f28d7cd8bfca766e4c4155fe78549c67e63fdc6d3a5253b9735d66c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5dce5fd8990e6f015da8f34a9562addfa62efcf87d6a578f9bd226c4056d4b27", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2813afca49742f27ef79c0067c5469d4eb7ed0453985b696ef0d182d7671a719", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "03591807c248ba88ab319d4e66b5a84e1cc4ea0e718e707c71e23efc92ca1342", - "vout": 1, - "address": "yLnkqJ7PDdQmFnqM2JiFjxKoDaqreNehJY", - "scriptPubKey": "76a914052a3e59931691132f41dddadf6ebf29929efad988ac", - "amount": 37.95792170, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([052a3e59]0223d96b23217eb9e6958ecb2d78443ce6cdd677fe5c95bff52f94ec839e6cca0f)#a7mhth9j", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "01a7adaa9caf835c2e0e47b02c8275d6c4f19f1cbedb7a1b5bee1a9945e85b00", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "57bcdceb2877eecd4a6bcd2e7c6cc032897fce57b3ae73d8194b7b7796801730", - "vout": 1, - "address": "yVY2GngSkarcMAyMYjQat8ozWj3DFMcCMY", - "scriptPubKey": "76a914651a064258532f97d128fbb7fbecfe5e589d785088ac", - "amount": 50.72781830, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([651a0642]035e45105e7cbc3b4472c673b1b9e6c771e60ebaba18e460d70822311441e9ee36)#644vj7t9", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "16f1ac37c57cd2bec91598b35b016e2173dba977df1f74944b73901865f8aa90", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "13feb91f000d9c9a505904c567a17917cc9dd8f002462224900d7d3a4f4dbf53", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e4d7df2a94bd510fa13bcb767c79eba08f09eae2bc2220e5b9de96a995566e77", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "455b31c8279f8ea68772457fdbac02d7d017d9db4287b0622e6c1de4bc0b0477", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ceaaccf99f77a4a6175dde80edde97ba1b6a855afa97384c9a666c43af9fbf30", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d4e2e160a1229472529e535674bad8debc436ed9c61e8b36ca0a345dd654d935", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "dc6691f680840a0fe0818b77d7aebfbc4cb29c564740a3ab0b5d8d2002e5154a", - "vout": 1, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bf4d4be2a2a0ddbbb086a5fb76de3e47ed48b1517f21481be34659590b6d0172", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "78b730e01d041e10efa8b4477b593d4066fe1377f2833eb5700a6609ab5ef629", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1bf3e6d78a60b977ba4763ae7b2bc541a451a92a315c5506eaa36045a019154b", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7beac26dc554ddd422d0764be299d1cf5f5307ff349523963c634e0317d0bdef", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0c219c6f987cd43a08f5b6f6782ed6e2a24a92e878adeed91e11b24201f004a9", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cad2a456710bbfbcd04744a7ec5e4a779ffce09c5e3550fe503af895ec853132", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b060cc7d9d8edb1bf401648b4eb7d645f087c54e82389bd723a310085194a132", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cdac2aad13864b02f24c15935c69e9415af5c003495903a360c5cfa462cc7df8", - "vout": 1, - "address": "yeU4wjL76T6q7cS8tGe1BKn2TVRsoV6uRG", - "scriptPubKey": "76a914c713b8ff3140cf6e98b54b689344902d80fca2c488ac", - "amount": 35.24807338, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([c713b8ff]02fef7814d44852125fc2a491a5f9a1ba0281e50f5cecba900362f5677edec2824)#t6a3jm38", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "b44a642e43f2103e0dfbdc5256f34b1400f5397982854f80bdb499b4e94da432", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8f3b3ea2a314fb3ff9546019230682044df90dc4e2963e4177ab05f6e78db56b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8dd64d21e1fec7936667b3684a8df9849d5e81b9dcd23df35dbf91e1fb97c134", - "vout": 1, - "address": "yM7TtazV7EB2rywhhhja8gK3RZvkQ64krk", - "scriptPubKey": "76a91408b400d8dc29d96e580d1fa32eb4c255527e3bac88ac", - "amount": 35.24907564, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([08b400d8]02e3517fa237f5394afc656db5c303e28757baa2cd5b0c37779039aef5e6aa3874)#cwwqmmw6", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "28c5ac63c580b960323c3c68651f228d0412d4eb307840cf4476e12a91b6f927", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fa6554c142771817ef383116b29be203be800288129265915b0ee2d75d23ff32", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7568afd6d2c920c01c37b33537ff85a160d63a745664620224cc3fef1a4e949a", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b614a8ea62731b075316f168a0ddeab9e1845b933daa31ec9d38796eb4cadd57", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "be10b8009d52b5b0962e060f22e92b15fc9e3c36edf909c1baaac9d88ea40b33", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "617d08078389640c7eab1d926ccbebf2100b9944cadabdb16311a8873f555c47", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bf07adb770d56ffdecf89dc7bf4cad047a06e13bb747be603a5290244ba10434", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4a7f76829920e9d4eefb33b08145f2bcae939fe97ffca1d058b925eab9090c0f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f7559135bf8000dfe9214862c1761125e185f97277f90cde58ca78c944b11334", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "70b1c9974d02b553737f7289912d23c716db365e7074a6b48194c66af6147735", - "vout": 1, - "address": "yNh49VnAq1gy3tnRNMkeZ5HGd7j8QiK4gb", - "scriptPubKey": "76a9141a0673c30a213241657ac268c1929fe088ebdda988ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([1a0673c3]02a35c78c48ad92c25af2074abdc66a3f31f3c2f2ead3722c008519da219d8d2c8)#prtehj4q", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "e112aa9ff0a74fe1048c85c17359a192886b9b5a6285bc1fac81739af6d1d589", - "vout": 1, - "address": "yNgFSLCGJ7oiVhuzR9QGjBt8qAjaDhvJy8", - "scriptPubKey": "76a91419df75fb18d19a0dcd609855cbd50d7f89e80afd88ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([19df75fb]03966a6a0e0ede0079ff7ceef359d75dee082eeb304049c7175ab0a9a29934e2c9)#5epndg9c", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "4429058178d342129d52f9355b13d960d73245a978cb3cfe2ce5ce4a9d20ad0b", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "698d57e7384f64ad96b7a250d756f78500dd42b5f2b9266d6fc195010c3a8134", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "510ed84bbe905567b818b2be695351d7da300af6af4574784f6d861eb996349f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "133daf7c3d7ee04b47f7a68fa8cadf15cdc807f2661e227a0f41a776cf02753c", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8e32de5226cd8e45ac9d971278b5addff2099fc457a14b5459f62c52e190a91a", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8fb8998d162ee91d5f20a06058fa2b37cce56cd978e09f19937c7323f4649404", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a565f6d7cae7d7441bcc9a40f000a8c06d08c05367ca48867a20b65df1b9815e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "749b2d89e0976a7b3da469be307f7cac705436da457542f05eed30027f39a135", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2cfe7f1bb75662780a8250d81dcc0423e70d43a8fc348167e4d70d2a5e90fbfc", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9cd853cf94d27c63d9daa795e5db08c9b830d779c8d6af34003f1c8d12e1f28d", - "vout": 1, - "address": "yfK9bS6ohpbWoxL4FV1kodfseW7URqbJtM", - "scriptPubKey": "76a914d05bfc873b7469b1a02d1564b8e657ef3440221f88ac", - "amount": 115.47427942, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([d05bfc87]03f080d5ff6344de33618a936f1cdd8ce40e65990c81b5be0aaaa8b3eed9f8ef3a)#mr72x4ct", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "1fa6bb1394db80cc3696c061c50c29efa54e86d7d1a8748ee9c9383961aa8029", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b5952eb697128453b4740432d63dc6ff6c13d4e9db8653d560a8dfbb11729e5f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "df796ec9b13c4d911bd69e52163120dc0279d0a9202d6797632f450bf868f435", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1e462f16b7c19af5a317f22cc81d3d39357f7e3a0aca3dfefb2bc11daf112236", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5b54c3758d80d8e3f39039f12c04c51580f0f5f0593b9fa2f604afb627e3f736", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "de27030b866450e8ea7b236a0fdcdea006f415c5ace1895a83e4d1264692d04d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d5e6c11bbccd18f629d2219654ec81767ef3ab62e9ec8b06b88d913571b19433", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "06a498be3c51f6dc01df65a35746ba67919d2dd672be1b0ee4af0f1ae17d0c37", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "26f6af0dc65f0fc67d6afca8f9715ec76728708f87c87cefe218a2aeed257159", - "vout": 1, - "address": "yLSyd6tH96aMfwzPz7tCH5jhePyyegMbAN", - "scriptPubKey": "76a914016c99c605827c8128d52fab672cd9a8b06f479d88ac", - "amount": 37.95792170, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([016c99c6]030a96334e9bdcd75d5b661bd420524e1d2b5f17aba47050e43e9c19d19a9b61e0)#fednfuu8", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "0cd66280a48aaa893e032984426fae4e337f8c887e1160581bc120e7f7b11b37", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d260d76aeb1873ead918be6cd1148fb55304abfdc1d17aada02ce229cbfdac19", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "36a7c9f43df75e38cc78f9ff4f6c79b25d53b91bd5e94df890d330886611d238", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3fcd62f8a38d4bcabf4d8184e39063a7d7a96d9b90cae316d0b8eccd29d58e43", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "305eb5a6e534eb3a175d3cd45b130cd9481633a9d86f63ea3c1087a41f330407", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ef89b6536422812977580262024adddf4a601d2016399d4213ab3b2b88e68339", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0aa5ca26b30ee1e8fad9867d998fdd6579eecea68d0b9bb8f53d0c37b8c88a25", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "73ec0992d9637ee133a3374bcb1201992b7b43c3fc7ab2fd3861969e3eaa431f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e653bad14bc0a331954734946cc06f6cc4563fc5bdce34f593b98c3d99e1c939", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d6f3c7898ead0a46d662c28d6b4b5bc6f4b0124a627fbfdaaad1938dd9ba79fa", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "51a2852ae7eddce1b694e9037accc8fd2530a6fbc3bed2fa3a4c0e6a36daf63a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "efae7538494dc99779b75774a1e979bec4832f7e110c1b5546fe76ddbeb184bd", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "737aaea8ac4691514dcff30f51216b248a3e9dcbfac5b536ffe35b5e5be1deb0", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 25, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bac8c20fa6f7f5eb8617a9e4aa45bbfeeb430ad6314053a67c05a086cffcd709", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "87bbb52c9c62779fcdb4541f6719ae254d7a74326bfb4bed5a18d7e2cef6f452", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a29f19e5fd6f344203f821d8bee7205de3c7fa1d12fad6e6b304740df6940f3b", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9f710db819b6c0d5c909e7db775395975ebf66f5990588699cb8f0e8b2920edd", - "vout": 1, - "address": "ygBRKQ2ARvBhAgXE5X6tZLq34kZ64eo96T", - "scriptPubKey": "76a914d9dde8eca899cd12d2ebc44a12cbfc190ba7ab0788ac", - "amount": 35.24907564, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([d9dde8ec]021f22a4eddc14e717d22e15a3284dac0bd0a66df88abd76afc435c3ceb332d0ff)#hz4zgl7h", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "205029b5a735fd93221177bcb33a5b64a2e0a73c923ebc59ad6db7a24e212a3b", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ddd49cfa371eb8fc346ccbd565649cfb3f7f595ad7189da976a0ab754feca087", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6280964a61103b804dae3aca03685af41d45ce8c95b37774dcd149e405588a3f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6cae8e3d820dee252a1e1ad0d67dd2ec2be0728339a9ce2a7a6212fa7161f63b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "fce28d39867fd2b2c1dd91892a7bdfafc5f814975bdfdac7e9dc7934f07fed32", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4b00518c373e68f708135f1631748ca45a3e6e8965d1a3a25ae07114bcd43c3c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "5c692d0551da338f80154d02b43301b26b9eab384c15aef91b938fef20a30fdf", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0bcab7aa36aca2c9c4ee0eccc3f2197f5d14455551e53842c48ae5cd553a8e8d", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "854cb8ad13c7ebb1ef3d3c9d12f35deff59de4ad674e50b5bce8cb19ab78413c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b2365a66f9d972736f2919a391fe1c35601b7bcf1905a2abf8e4c7709f1c658", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "822711a8446973ab697bb0deeaeb9d0e26f3e7c897735eb96dc67308439c0c32", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4ea576f19cf07cd88921d1c1a64c43edc13d6a11a8e39672b23687a89f26083d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "59f2b440ae4cd28a74e71fff1af132b37fe6ada76f05ac2335adf67fedb7183d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1b7bedb9da0460fb0aff086a7390776dc990c0467c5c428881cb72bb6027503d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c4c4cf356a544972a1b70da72df7dfa0486710a0d37a0cef5daa373cf12e5176", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "221e732f49f81c1b6cc335240b110e3d747c365e7c008222453840e2ace2743d", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "062274de00d8613d955449b6b846f9503595c6428db54dbed343a1802d4f3304", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "bdae1e679b50d95095f3e76faf92c9fd46f991708f3d1356f2ab70db5604d717", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "94966154319fbc0b559dfb32ba0f665f16a7bd38a15d4429345a179ee2bf8739", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4dab80135a9e7a45f7b8d7a32bdc32aa63c129a5acb867cc93d5019461e1c63d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0b7e5639a793a53af23711054305cfae9aadebeb8fbdc5cbfe8cfef51232cefe", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4fe455d4d5d1c3394b2035d269b89aa08a4b33b39bf79e440bd6d03d9238d546", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "66cdf7a6b426373461772b3238751b1d40cd911db449f4e2e57519af2bb88a3f", - "vout": 1, - "address": "yUFWYuGANrkRdLuRXBEc2P9uAGsrHbweCr", - "scriptPubKey": "76a91457026eb507179586e6dcd6dc8c660c4eb946e68488ac", - "amount": 0.02087037, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([57026eb5]02970b46ea20212790d497deea9645cd93198b29687b95482b88de4bcbe6e458ab)#n0qlhcgp", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "11796bb91006443d8514a44c59b36fc2bdf682c5bbf0869837668b17d1d13342", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c8303f8023545abe7473580acba926eae1319cd88cfff5dad7e8525b93e44472", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "23d14e0721d105881e580b266ead7471510cc4eeb2c6a826240de2a099e54942", - "vout": 1, - "address": "yUjhMB9R47RHxQ4fAakHxVvfSp7Ziw7SER", - "scriptPubKey": "76a9145c5717cb9805fdee417f61299117a98e6c38722888ac", - "amount": 115.47559636, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([5c5717cb]03a39b70bd72408b813da414fa75c1895f1965e472795f33231309b4b3c811e076)#uq2mrnmg", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "f17b449e66656a84c906b2e7fdf5ac608a6b14e6a04be52b50ef058e66ffcc68", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "362bc304f31135b5bfb1448c8c206d06640e1b86b50e399e6c4710ebeba21943", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "89a2d0bce7877504e9bbd9d2dca7051aae795b56dc0acae13c3ca325ead501e8", - "vout": 1, - "address": "yTSo1pe3hzGWfCPK826jXNBp4FGoM1Nu4K", - "scriptPubKey": "76a9144e2ca035c085f91faf2be5c249b4c917bb5557aa88ac", - "amount": 35.24907564, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([4e2ca035]03dcf3b8e85547f4f599c807c433da9777da82a93f45610419acd3e9b0b7dd2100)#lnmt9xw4", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "bc857eb5fbbc7fc4c20a4567ac69ae33080ed75b876374bcf295ca5dfbde5848", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "79ca9b81a18046777df1c33974fef1005cb0a2491a614b785590a91e40354549", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "751fef8ef746f37df2be3efe74499eaa79d67258998724427caa2983ec2e5bf8", - "vout": 1, - "address": "yfTSAvNKk763vfoHD1KtDykJUgcxzGiNJn", - "scriptPubKey": "76a914d1ed26f8181998f18fb401ca5a0c9eecbe13f45788ac", - "amount": 35.23905304, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([d1ed26f8]03ab0926dd9b21126acfcff3b63310208e6314610a34896f477df27e24671b1d79)#ng24wrvg", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "bf27b3cf103ec4981806c0950d1b951426f413af6c37e01f2c1c47fd746d3698", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "34d6fb5597a71780499883139ede069f185e896115c879d8c51479908c23f806", - "vout": 1, - "address": "yLmnJwJN51Lc5XopGtAh8yw2N4nuovVZty", - "scriptPubKey": "76a91404fb0f677fb76910aba98dcea564419fd8e9628f88ac", - "amount": 115.47359184, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([04fb0f67]02c179f292ca8301813e4db05c2ce4ac5083ec5851588c6d3e62b932da156faaed)#k8pws854", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "a1f31bc1731cec94f00162bcbe722c3dcb77ef8fd451c47a771e7ab968b8e949", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d1f27ec2d98127c3f36895e5df5840192e8207f7c7c4a655844ff376d2f437ec", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "445cfa4b202050835273ae0f860a292c09ce526797af2f5c5c4a7daba8e7c7df", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6542fa94e3721faae89a6b2b34cf674a5203d819b82c2ac990b2e68d8a0c5710", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f6c0b9bdffa6f7965bb6c491c25cbe019c07d2c372dadcadbcbf5f79c08bf849", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2fcca9c0da77894cc9371f49043396b63b995ea1a9326ff6f9cc5aea6c9e95c8", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7b735d3a758375e511b8e6a6f4c0d443479a67ba3257437b3b70e57fee40104c", - "vout": 1, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c1b81e1361304d2609185646122f1cadc37b2ee9e1ba7669781be76bd899acfe", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8eb9c43633fca0a73703700133f3f56183e696285be5a4c48a87a923e2ec424c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4b62206ea0844b43e334d51adb162b493dd33279394d187f460da11ea21f8c4c", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "be3ddb4ed1a1ea87994bcc2caa6d2344d74e595a406a25775f2399786af5741d", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a920746fa9829aefd4f0ed82d53c62aac7762302ebc1a6cacd134817cc41675b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "642ef4babaf2951eb45495fffae358f2ea30efb6b3017015a8528d18ec1ca34d", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7f7be46677f1cb57a865332693ed6cba4307663f3d06073a6018bebcb8b18fb1", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "be506ed28290e6357048495110f8ce5fe93e18860b3fcaf0054cfc528885364e", - "vout": 1, - "address": "yhuaBPACcFqFXPVkCYHjFUzi5x4aH3DerF", - "scriptPubKey": "76a914eccee74c5c54aa6a3c476b03c8b0f89c79ac35a488ac", - "amount": 0.02087037, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([eccee74c]026f95b710da226f45e5f69f968b4b2d0bb7dc620814c44348053493648b7b2bed)#5lta0vc4", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "6b9b2ad7aec4ff4f84a45198da85ca6796da9f8a3e4c8ed812a72c8e79c7374e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3d5d7f0fa464b764f7f1ae8bfce5d14d7b5b589acefb8b6a96e2685f6375a54e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "70e194610453e6427823f991da075349987249a9965ef8e20cc8a552ce243605", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a7cacceddb631d958813870ab6d3736748c52495e205fc806edef8894852b012", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7156841abfc9a80fec15121130c7db0dd01587eb3273b7670c36214d2671bb4e", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d05d9446a654bc3ffcbc26f6da22998aca0dbc42701c7f03e64d433e29966d98", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ed7d6871023e29b6595cc17c9a30bc51ad58fd6a55371e67dda5eddd6064da4e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "35fa947c48975fc3b063143dc3091b267e626eb23c1cb8739c50cebb500b63a7", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a80b730d4a25ed3bec65acc80ce3429a4393a929a64f8be8a12788d5d94fcf0b", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e3060c289353e15900b86b80eccc8d8838178fe05329da9dd8239716a505254f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2651919033fff07916da3b20439bfeb38d2dacdaaf6af70714dc4b72d26b4679", - "vout": 1, - "address": "yQDMQEkiBU7Yx25bk7xZLcvbHXfscR9Qdb", - "scriptPubKey": "76a9142ab975ba5c6f37e18d408905a5712e6a8ac4df0b88ac", - "amount": 124.34892868, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([2ab975ba]0267e0fc46b938d8940766e92018237e7f874a140bc7be252e8a2c53cefc78f40e)#na5f472j", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "36b70be5d3d2f0fe13c6c7e25146a60c46e1403dfb4f8b5a47ddbd2391be1f44", - "vout": 1, - "address": "yd9gvHDuEfSyJAWDyMkWb87R7qUJm3mnsB", - "scriptPubKey": "76a914b8a1b95833a47665ef64a3620360aa77276daf8c88ac", - "amount": 0.01890305, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([b8a1b958]0202fa2d233a8364a3673981395111ddb94958dfabf10350b477c0100413de52a9)#grnsg8cq", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "2c547fe0db9c51dd54d7b95cd903131820bc38e01eb09bacd26f2d60622a0750", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4e0dd5d7c9800449cf8a6380759bb5c1baa6555d75c26ebd9121c5f33742c2a3", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "973deef6b508361ce21d21b8d2cc60acfae461c569b00625f84de95851627969", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c988be993c65f0abcc2e22c367da0ce973092512686fc9f779cdb27953f96724", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b1f7a4c4992d326fa9a721dcaaca01e162e357b4934335a159a1608e8251e150", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "60ad91016c8088304edfa15f84dc5654a384a9077c77fd3ed36f90dc5c6a9aca", - "vout": 1, - "address": "yS1eJgvznzo8rrMBTuUKEZrPSy6dxejWvX", - "scriptPubKey": "76a9143e72d4b7af92681766fc87c5fe10f821d212986c88ac", - "amount": 50.72882056, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([3e72d4b7]0378869a590271aa89b125f3966e7ea4f8304d1af31501f25e9995f1a7f3c1dbee)#4w02yree", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "11c233a0639a779f76a8204c58f354c349df4b28a4aca6843c66eee926cd3957", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cd39e2eef8978f1939efe1a0af031b98074b9000e14636f68afc4be38313bb93", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cb96db9cf65ac6752056bc127e722782f5606be8d97cfeb06470402815d34d53", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6f4b7200e521bd7d6a85a2e61566ef2b729216d27280f40b5ee3a15529009a7a", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "48829c578fc885e59f4af3473839317230aa436f064c66630b40e0c54c69d741", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f6d04bdae2d0549896609bde64cdf9c34b086c2f01f8d16316ea970ea116d554", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "6f04ddd093625f6746f52bd4d8e7f98b44e8c1aff2ecf1a81d35e16e72b7fe54", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "79c7ac9aa7e83537e0cc03f9e3971af74eb26e1ead7cb88a5091c2e764e8ad56", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "c179a3360651b667b77d77bf51c46d4ee2a72a0fb375d15b758412cc55ab2380", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b09a28bccdc00c171b6fdcb5c6cf55ee6a42b3237bbe8ebac2f45bc844e47b5e", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8460d178dda681d538bc0251335d950f739e9ef66e2b9da533899e0f67f67a1a", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "dff9c17d8d6482d4fb1060046fa33b24c9b5de99c964e90abaf93fdd55993453", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d0b6d72440cae53000e68ce4785ab7fc679bc3af6aae7601bebedb1116f90b57", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b574cc795bdcd560b945ff3da688133d647a646c775359b0588946bec2233f41", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "4222405572f2e074e320a86a7252c44bb1599d6c9d7d75deaf770ee44c702d16", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "d3ddfca516eabf0b5cad6a2e61c569e6aa2d821fdb1bf43207ad47beebddab58", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "52a5ba7f00ec2d3104afe017e12c25db22eeb2de516966ac9fbf67f3f4104e37", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "91e13fa77f18298e990b85829fcccaed017340c8fdd11eac6fdc3c3d894d9459", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "962f8ecc7615ca4cd418b0c09c5092b34c1efb62ec7957f169800ce8673e8d97", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7bcd2e382d2545ac8936ba3d8aa73e025f9c73b6f37d6cf0a885502aef7f7f0c", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "520f3cc91ce5d5862a9ed1140006dddc57067e9fe9185f9dd1c1d67403c79459", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9ca13dc23e376f0c87f9c076ac5ff22676fd6822ee56cf4b6d76e61ea6d63d5a", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ac7622037a2a84855f3dc3164335e8f27927437d83b2f0c751833406cdde065b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "f76a8722b913ecc2550cce653d4f854055594830fa87fcae088cd957e5e6075b", - "vout": 1, - "address": "yTL4Kf3Z4b9W1C3KJMPSHXbBS76dcdBX1e", - "scriptPubKey": "76a9144ce67dfcd2dc62bc5e5628947ded51a978d21a7788ac", - "amount": 0.01292669, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([4ce67dfc]03956c5291003de30c9fc159f6fa813c47ee112f1d3274906d3d52457a2cae9967)#zdremyyl", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "1a486463555a2da6a529dd334c115493ac5dfc235a0ed703766a16c451b02ade", - "vout": 0, - "address": "yN51hwyDVt6hyrA6KryMPEk3j1kkzMVkan", - "scriptPubKey": "76a914133570bae2bf71c3922a32957c036d3744346a2c88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([133570ba]02d6367c00f3c2dd4c49891fd4f4495895ae9276b7ce3cb8aa5156c3b84c76327e)#ldstj2xw", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "7981348b5e3d5f8bb42ae8a286fd9546557b09459f3fc5a230ab558f0e02245b", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "089368d4e22f62e3398800d59d4a084fd4056afa0bff1f6cf3267398abd807df", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "699fd76b1c8958f1015a59f66ae293277d68d10920dd948f9511354c876cd819", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "10c5e953abc274999681f9a29e2784ade843e47c4a87381663b1961aff2f365b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "3de62e2d7af4a323805ca15eebd9c68c2eb540804700d864d2a20d625ab27985", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "e2e4c8fe7214a563df617cd2800f0895336098120c9045b555841448ff811f45", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8394406894c49431ab9f52c09bf372dd8b5155ffb70a37846af84d32d2cdf65c", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "0951e58d1fe84c7f6ba6c95f11f03ff8914368148f875f338a377aa398c3b65d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "cc3b70aa391eb98c3831799b53620fb2a45a540854589e4b13112679e41df919", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "1a778806c9f545d373ce355bd2e213fe67710ddc02f067a11700f77988c0c45d", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "ef9fc0bcf8dfd6f76059ee86ddaa19aa85d71983296747f9d155c974f8a785cc", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "9bf7deef739abf5629523eae9da69277364425c3b496d1628c9d2a2d807cc55e", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "17d22ab98eb2ece2969175f5bb25d3f737600e0b29bba0f32a3dbe8769eaddd0", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "940c44d15f964ce010d9ca387a21dfab316d1a72ea5c2cf6f95d7d00b5ac395f", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a377bf2c866381dcb887d567376955e00282546d7d31a8f858c54a72300db53d", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a8ebc1259003611da787f4c306acd60d9b42c38f87a0aac947ece37c5feb7c5f", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a4eee0880feed155ad42da8a8fec9e4812e2a9a7c8deb8faa48da7dcc266b1fd", - "vout": 1, - "address": "ycfhkBPXXEMPxr3tpKmhSMwcmyYPDgPwZ5", - "scriptPubKey": "76a914b356c4369fae949659e4e0f98703b5998caafcb888ac", - "amount": 115.47459410, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([b356c436]039614c773a1a09c3c3ef9c9cd9f2f8ffc69ada69186a0d6e2e80a597d710c3202)#epg203c8", - "safe": true, - "coinjoin_rounds": -2 - }, - { - "txid": "99116faa409ee5556bb30b1c9fc2f13fc1f8b15a5296b8a5c3ca75af2af81b96", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "a7e26c6594de810c82e032fa6d80fd14ea24e8c05e872548fb961fc071530595", - "vout": 0, - "address": "yQxddGsXYYxyxCAZBiDF9edBjbvdNKDPRc", - "label": "", - "scriptPubKey": "76a91432e8e3d6189e943ee767435e7da8af45fdd50b0e88ac", - "amount": 0.00100001, - "confirmations": 24, - "spendable": true, - "solvable": true, - "desc": "pkh([32e8e3d6]025075b8bbca767c3c8a519a65c41f1606a8af5005d80e3913e8a919e80d45d839)#w6zkmn9q", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "b6b70601f6f331534f13f0d98d49388652de93c2526c7374a05b530085ef741f", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "2bc707d5fe4272f43b7e4e59ffac85b4e70b3c6c89d569a5c0af414d62f61f60", - "vout": 0, - "address": "yQsMoF85HagMfoa6TW36woUu9CAjMTmcKZ", - "label": "", - "scriptPubKey": "76a91431e998f850d59296fcf63ea70960f456282d582c88ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([31e998f8]03c5ebf4cc95aac7d24be75aa717f17a6253e1f249d7adb929d8ea252f310a07af)#jj9lpryx", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "44d9cb8e8ae60f728efa2dff0f8ed2577d65c892d4637b2bd0b8ae9a98b0ac0b", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 23, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "8cfc12694a63a5f8b02cc721b48f373482778705d38471f75adda611f1d68a60", - "vout": 0, - "address": "yZr7125wJobKYuPk8G9CEoqMWsuvRJiX9q", - "label": "", - "scriptPubKey": "76a9149465f63448da6e5378da8d4a3a57fa521d6dbb5888ac", - "amount": 0.00100001, - "confirmations": 21, - "spendable": true, - "solvable": true, - "desc": "pkh([9465f634]02814b7fef9acdc2ce38b28786d80d9bf6b4e35fc914f16eb3982ddb26c3ec3df8)#qpapr6p5", - "safe": true, - "coinjoin_rounds": 0 - }, - { - "txid": "73d3b38ec1840a7e26de5216b31132d5715673c7549a14087dae0370f0e05163", - "vout": 0, - "address": "yjDxeAPX25AsmcsXuaf3zhuqr8JhN4c9ve", - "scriptPubKey": "76a914fb414446cdedabb84229cbac6ca604b2d7cf237088ac", - "amount": 0.00100001, - "confirmations": 22, - "spendable": true, - "solvable": true, - "desc": "pkh([fb414446]023dde34fa026f8bfcc95b47b7699f2873b4c7bf876b1312bf205b26a36ff52dbb)#tnm4u3ka", - "safe": true, - "coinjoin_rounds": 0 - } -] diff --git a/src/util.js b/src/util.js deleted file mode 100755 index 82cccc1..0000000 --- a/src/util.js +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node -'use strict'; -const xt = require('@mentoc/xtract').xt; -const fs = require('fs'); - -function date() { - const d = new Date(); - let h = d.getHours(); - if (String(h).length === 1) { - h = `0${h}`; - } - let m = d.getMinutes(); - if (String(m).length === 1) { - m = `0${m}`; - } - let s = d.getSeconds(); - if (String(s).length === 1) { - s = `0${s}`; - } - return ( - [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') + - ' ' + - [h, m, s].join(':') - ); -} -function getDataDir() { - return `${process.env.HOME}/data`; -} - -async function dataDirExists() { - return await fs.existsSync(getDataDir()); -} - -function ps_extract(ps, newlines = true) { - let out = ps.stdout.toString(); - let err = ps.stderr.toString(); - out = out.replace(/^[\s]+/, '').replace(/[\s]+$/, ''); - err = err.replace(/^[\s]+/, '').replace(/[\s]+$/, ''); - if (!newlines) { - out = out.replace(/[\n]+$/, ''); - err = err.replace(/[\n]+$/, ''); - } - return { err, out }; -} -async function sleep_ms(ms) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, ms); - }); -} -module.exports = { - getDataDir, - dataDirExists, - ps_extract, - xt, - sleep_ms, - date, -}; diff --git a/src/vector.js b/src/vector.js deleted file mode 100644 index 28354ed..0000000 --- a/src/vector.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * A port of DASH core's CoinJoin client - */ - -function Vector(_optional_prototype = null) { - let self = this; - this.contents = []; - this.proto = _optional_prototype; - this.get = function () { - return self.contents; - }; - this.clear = function (_in_proto) { - self.proto = _in_proto; - self.contents = []; - }; - this.erase = function (start, end) { - let saved = []; - for (let i = 0; i < self.contents.length; ++i) { - if (i >= start && i < end) { - continue; - } - saved.push(self.contents[i]); - } - self.contents = saved; - }; - this.size = function () { - return self.contents.length; - }; - this.emplace_back = function (...args) { - if (self.proto) { - self.contents.push(new self.proto(args)); - } else { - self.contents.push(args); - } - }; - this.push_back = function (...args) { - self.emplace_back(...args); - }; -} -module.exports = Vector; diff --git a/src/wallet.js b/src/wallet.js deleted file mode 100644 index 96cbfeb..0000000 --- a/src/wallet.js +++ /dev/null @@ -1,9 +0,0 @@ -function Wallet(...args) { - /** - * TODO fill this in - */ - this.contents = args; - return this; -} - -module.exports = Wallet; diff --git a/src/wallets.json b/src/wallets.json deleted file mode 100644 index 4c868a9..0000000 --- a/src/wallets.json +++ /dev/null @@ -1,6 +0,0 @@ -{"yLNfoPqjCMgKYXq2Ky5Lrx39VENtXUBcyR": "cRNTVNxhj8BtXdcwxVGpD2ToHUS6dCSgiRpt3VKH6EDrjnFbsoyj", -"yVVtGjjBAkZtNVn8kz9EnoG99uDRSuJtym": "cPbp5o1Xvgq6oPYfJAtdR9PeNp37d2U7QhuxE9mRcBUJzSmgbXg7", -"yXoKLN74kZNg7NuWcjdUHLdSgVccfARyNG": "cRcCGytCJChwsq5c76YAXAchrPMvCRqnubaqEk4J9zNjPAopCG27", -"yYEPsgMKLK6V7Gee4mYzxRDvAQhevfSob2": "cULZAqnQpzbyT2KziByA372YHGErLaHKQwGMhkrfxLC8MY9333QM", -"yN2DYvCAxL3zBUQnNjMnFteGCXRF7egyQC": "cRu1REpK2yFyJ4qNtbXXeWGqnEKpYY4uu9PoD6DkW4CWPyro2gFh"} - diff --git a/tasks/browser-sdk.md b/tasks/browser-sdk.md deleted file mode 100644 index 21f0256..0000000 --- a/tasks/browser-sdk.md +++ /dev/null @@ -1,206 +0,0 @@ -# Overview - -The following tasks can be completed concurrently (and almost 100%) -independantly from the backend tasks that are currently in progress. These are -browser tasks that are made with modularity in mind. The SDK described below -will be a data-driven one where we feed a json configuration to drive the -integration. - -# The _Big picture_ - -We need a small Browser-based library that can take advantage of `dashcore-lib`. -If you feel `DashTx.js` is a better option, or you're more comfortable with it, -that's fine too. - -## Browser side functionality - -The goal of the `DashJoin.js` is to allow the user the ability to have their -wallet info in the browser and not on a server anywhere. This means that a -private key or WIF **CANNOT** be transmitted to the server portion. Instead, the -browser portion will create and sign data then submit that payload to the -server. - -This gives us the benefit of allowing the server to handle sensitive PII in a -way that is cryptographically secure since the server has no idea how to sign -your transactions without your private key. It is this proxy methodology that -opens up CoinJoin to potentially many different use-cases and platforms. Imagine -running CoinJoin purely through a couple of `curl` commands. That's not part of -the scope, but since we're using https as a bridge, the possibility is there. - -# Terminology - -I say `object` but really it can be a class, or an object with methods and -properties. Anyway you choose, just do what feels right and allows for -flexibility. Make sure to modularize your code. If you've seen @coolAJ86's code -where he does something like this: - -```js -let Lib = {}; -module.exports = Lib; - -Lib.load_wif = async function (file) { - // ... code here -}; -``` - -... that would be a perfect pattern to use for all components described in this -document. - -## Create a `wallet object` - -- [ ] Ability to load a private key as a WIF or any format that `dashcore-lib` - uses. - - See `node_modules/@dashevo/dashcore-lib/lib/privatekey.js` -- [ ] Can create collateral transactions - - this is partially done. ask @wmerfalen for more details - - Example code is in `src/demodata.js` - - search for `makeCollateralTxn()` -- [ ] Can sign/serialize transactions - - [ ] Must be able to take the output and feed it to the RPC - `sendrawtransaction` or `decoderawtransaction` - - Look at `makeCollateralTxn()` and look for anywhere where it says - `tx.uncheckedSerialize()` or `tx.serialize()`. The former will serialize - it even if there are errors, while the latter will throw exceptions. - -## Create a `configuration object` - -We will need the library to communicate with an origin server. The origin server -and other configuration options need to be configured. Create a configuration -object which can be fed to the `wallet object` and the `Server object` - -- [ ] Create a config loader that will accept a json file - - [ ] The JSON schema doesn't have to be exact, but it should accept the - following things - - `origin_server`: this will be a fully-qualified domain name. this is the - backend that you will be communicating with - - `api.version`: depending on where we're at with the code base, this should - be one of: - - `alpha` - - `beta` - - `v1` - - `vN` where N is a future version - - `api.user_agent`: Have this pull from the package.json. I believe - @coolaj86 has some cool techniques to make a nifty looking and informative - user agent string. Just for logging and auditing purposes mostly. - - `network`: this should be one of the following: - - `testnet` - - `regtest` - - `devnet` - - `mainnet` or `livenet` - - make sure it can accept one or the other - - `coin_join.rounds`: integer. how many rounds the user would like - - `coin_join.denomination`: see the dash core docs, but this has to be one - of the standard denominations in `satoshis`. - - `coin_join.txn_pool`: this should be an array of signed transaction inputs - and outputs. this can be blank for now until we flesh out the rest of the - API - -## Create a `server object` - -The wallet and configuration will need to have access to a server library that -can understand how to communicate with a backend server. The server object needs -to communicate over https (bonus: if you can get websockets working too, that -would be awesome!) - -- [ ] Create a server object that creates urls based on configuration properties - like: - - `origin_server`, `network`, and `api.version` -- [ ] Urls will follow a pattern: - - `/api/${VERSION}/
//[IDENTIFIER]?query=param....` - - example: `POST /api/alpha/matchmaking/session` - - All `POST/PUT` must have a json content type -- [ ] All URL's will respond with JSON. so make a JSON decoding method - - Some libraries will allow you to set the base url and all you have to pass - in is the URI. - - [ ] Make that happen ^. Set the base url to `origin_server` and replace - `${VERSION}` with `api.version` -- [ ] Pass in a `X-CoinJoin-UserAgent` header - - value is `api.user_agent` (see above) -- [ ] Each client will need a custom header to send, but only after you've hit - the `/auth/create` route described below - - The header key should be: `X-CoinJoin-SessionID` - - This value will be a unique UUID string that is created once you - authenticate (don't worry, there's no user/pw login lol) -- [ ] The first route to hit before any other URL is the `auth` route: - - `POST /api/${VERSION}/auth/create` - ```json - { - "network": "testnet|devnet|regtest|mainnet|livenet", - "coin_join": "pass in entire coin_join config object here", - "dsi": "tbd", - "dsa": "pass in return from makeCollateralTxn() here " - } - ``` - - The server will respond with a 200 and a response object - ```json - { - "status": "", - "status_code": "", - "session_id": "", - "error": "[string] .. only present if error", - "error_code": "[integer] .. only present if error" - } - ``` -- [ ] Take the `session_id` and use it to populate `X-CoinJoin-SessionID` header -- [ ] You are now ready to matchmake with other participants - - `POST /api/${VERSION}/matchmaking/session` - ```json - { - "inputs": ["tbd"], - "collateral": ["tbd"], - "outputs": ["tbd"] - } - ``` - - Must have `X-CoinJoin-SessionID` header in request - - This portion of the server SDK is a work in progress. - - each key/value pair is TBD -- [ ] It's possible to get an updated status: - - `GET /api/${VERSION}/matchmaking/session/${SESSION_ID}` - - `${SESSION_ID}` is the same value as what you place in - `X-CoinJoin-SessionID` - - this is the only route where you have to pass `X-CoinJoin-SessionID` in - the URL. - - if `X-CoinJoin-SessionID` is present in the headers, it will be ignored - and ${SESSION_ID} will be honored instead - - This route should respond with something like: - ```json - { - "stage": "", - "status": "", - "status_code": "", - "error": "// if errors", - "error_code": "// if errors" - } - ``` - - It is safe and most likely preferable to display the `status` to the user, - but always sanitize. Never trust even integral inputs (parse them using - parseInt()) - - `stage` is an integer that corresponds to the numbers `0` through `11` in - the link provided here: - [dash-features-coinjoin.html#coinjoin-processing](https://docs.dash.org/projects/core/en/stable/docs/guide/dash-features-coinjoin.html#coinjoin-processing) -- [ ] Canceling a session that's in progress is theoretically possible, but it - will cause the masternode to charge you a fee. That's what the collateral - inputs are for. That part of the SDK is TBD - -## Upcoming features TBD: - -- [ ] continuously check the `/matchmaking/session/${SESSION_ID}` route -- [ ] start a websocket that connects to the origin server - - [ ] make sure it upgrades fully to the most secure proto (WSS, I believe) -- [ ] A polyfill for the websocket API would be to continuously poll - `/matchmaking/session/${SESSION_ID}` -- [ ] A rate-limiting middleware will be built into the express server. Be - prepared to handle: - - [ ] Rate limit hit. HTTP status `429 (Too Many Requests)` - - [ ] Might change, but: anything over 120 requests per minute will be rate - limited - - rate limit punishment will be no requests can go through until 10 - seconds after the rate limit was hit - -# Author(s) - -William Merfalen [github/wmerfalen](https://github.com/wmerfalen) - -# Date published - -`Tue Jun 13 15:25:52 UTC 2023` diff --git a/test/ctransaction.js b/test/ctransaction.js deleted file mode 100644 index cf1df1a..0000000 --- a/test/ctransaction.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; -let assert = require('assert'); -let path = require('path'); -let Transaction = require(__dirname + '/../src/ctransaction.js'); -let NetUtil = require(__dirname + '/../src/network-util.js'); -let calculateCompactSize = NetUtil.calculateCompactSize; -let hexToBytes = NetUtil.hexToBytes; -let crypto = require('crypto'); -let TxnConstants = require(__dirname + '/../src/transaction-constants.js'); -const { LOCK_TIME_SIZE, OUTPOINT_SIZE, SEQUENCE_SIZE, DEFAULT_TXIN_SEQUENCE } = - TxnConstants; -const OPCODES = require(__dirname + '/../src/opcodes.js'); - -function randomHash(len) { - return crypto.randomBytes(len); -} - -function getBaseTxnSize() { - return 4 /* VERSION + TYPE */ + LOCK_TIME_SIZE; -} -const script_zero = hexToBytes( - [ - '76', // OP_DUP - 'a9', // OP_HASH160 - '14', // Byte Length: 20 - '5bcd0d776a7252310b9f1a7eee1a749d42126944', // PubKeyHash - '88', // OP_EQUALVERIFY - 'ac', // OP_CHECKSIG - ].join(''), -); - -function getTestScript() { - return script_zero; -} -/** - * Create datasets for calculateSize() comparisons - */ -let dataSet = [ - { - vin: { - hash: randomHash(32), - index: 0, - signatureScript: script_zero, - sequence: DEFAULT_TXIN_SEQUENCE, - }, - calculateSize: 75, - }, -]; - -describe('Transactions', function () { - describe('addVin', function () { - it('should throw when hash isnt 32 bytes', function () { - let txn = new Transaction(); - assert.throws(function () { - txn.addVin({ - hash: randomHash(3), - index: 0, - }); - }, /bytes/); - }); - }); - describe('calculateSize', function () { - it('should calculate the correct size based on lengths of fields', function () { - let payload = { - hash: randomHash(32), - index: 0, - signatureScript: getTestScript(), - sequence: DEFAULT_TXIN_SEQUENCE, - }; - - let collateralTx = new Transaction(); - collateralTx.addVin(payload); - //collateralTx.addVout({ value: 0, script: [OPCODES.OP_RETURN] }); - let size = collateralTx.calculateSize(); - assert.equal(size.total, 75); - }); - it('should calculate the correct base packet size', function () { - let txn = new Transaction(); - let { total, txinCount, txoutCount, extraPayloadCount } = - txn.calculateSize(); - assert.equal(total, getBaseTxnSize()); - assert.equal(txinCount, 0); - assert.equal(txoutCount, 0); - assert.equal(extraPayloadCount, 0); - }); - it('should calculate the correct packet size per txin', function () { - let txn = new Transaction(); - txn.clearVin(); - txn.addVin({ - hash: randomHash(32), - index: 0, - signatureScript: getTestScript(), - sequence: DEFAULT_TXIN_SEQUENCE, - }); - let { total, txinCount, txoutCount, extraPayloadCount } = - txn.calculateSize(); - assert.notEqual(total, getBaseTxnSize()); - assert.equal(txinCount, 1); - assert.equal(txoutCount, 0); - assert.equal(extraPayloadCount, 0); - - let vinEncoded = txn.encodeVin(txn.vin[0]); - let size = - 4 /* VERSION + TYPE */ + - calculateCompactSize(vinEncoded) /* TXIN COUNT */ + - vinEncoded.length /* tx_in contents */ + - LOCK_TIME_SIZE; - assert.equal(total, size); - assert.equal(total, 75); - }); - }); -}); diff --git a/test/denominations.js b/test/denominations.js deleted file mode 100644 index 6449154..0000000 --- a/test/denominations.js +++ /dev/null @@ -1 +0,0 @@ -const assert = require('assert'); diff --git a/test/network-util.js b/test/network-util.js deleted file mode 100644 index a68e6ee..0000000 --- a/test/network-util.js +++ /dev/null @@ -1,31 +0,0 @@ -let assert = require('assert'); -let Transaction = require(__dirname + '/../src/ctransaction.js'); -let NetUtil = require(__dirname + '/../src/network-util.js'); -let { calculateCompactSize, encodeCompactSizeBytes } = NetUtil; - -describe('calculateCompactSize', function () { - it('gives predictable byte lengths', function () { - for (let pair of [ - [32, 1], - [256, 3], - [0x10001, 5], - ]) { - let bytes = new Uint8Array(pair[0]); - let bytesSize = calculateCompactSize(bytes); - assert.equal(bytesSize, pair[1]); - } - }); -}); -describe('encodeCompactSizeBytes', function () { - it('prefixes sizes with byte code markers', function () { - for (let pair of [ - [32, 32], - [256, 0xfd], - [0x10001, 0xfe], - ]) { - let bytes = new Uint8Array(pair[0]); - let bytesSize = encodeCompactSizeBytes(bytes); - assert.equal(bytesSize[0], pair[1]); - } - }); -}); diff --git a/tests/dsa.js b/tests/dsa.js new file mode 100644 index 0000000..745c412 --- /dev/null +++ b/tests/dsa.js @@ -0,0 +1,52 @@ +'use strict'; + +let Packer = require('../packer.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +// regtest magic network bytes +let network = 'regtest'; +let regtest = 'fcc1b7dc'; + +// dsa\0\0\0\0\0\0\0\0\0 +let command = '647361000000000000000000'; + +// collateralTx.length (195 or 0xc3) as LITTLE ENDIAN +let payloadSize = 'c3000000'; + +// sha256(sha256(collateralTx)) +let checksum = 'edf0d8b1'; + +// 0.10000100 +// 10000100: 0b00000100 as LITTLE_ENDIAN +let denomination = 10000100; +let denomMask = '04000000'; + +// 10000 fee, +// (decode at https://live.blockcypher.com/dash/decodetx/) +let collateralTxHex = + '030000000127c03b36308e0925666682de270cdaa212b696776ac3529242851e338c627a1c000000006a47304402203d8a4f9d25dbff1b7c4711411d6102c6bd8c868a393081d262480f9df89c428a022079a61ce32d26fc522b05eebbceae17a056bf58518de7b6e17e89ff7ee980b64f0121026215ac5afb5a890d06e4eb07ed6383fb4cd46ca0de222cca4c4f8c9fcf9a6023ffffffff01bbae9606000000001976a914ef46d2e30c714916c43676364b27657ed753ef0788ac00000000'; + +function test() { + let expectedHex = `${regtest}${command}${payloadSize}${checksum}${denomMask}${collateralTxHex}`; + let collateralTx = DashKeys.utils.hexToBytes(collateralTxHex); + let message = Packer.packAllow({ network, denomination, collateralTx }); + let messageHex = DashKeys.utils.bytesToHex(message); + if (expectedHex.length !== messageHex.length) { + let length = expectedHex.length / 2; + throw new Error( + `expected message.length to be ${length}, but actual message is ${message.length}`, + ); + } + if (expectedHex !== messageHex) { + throw new Error( + 'bytes of dsa (allow / join request) messages do not match', + ); + } + + console.info( + `PASS: Packer.packAllow({ network, denomination, collateralTx }) matches`, + ); +} + +test(); diff --git a/tests/dsf.js b/tests/dsf.js new file mode 100644 index 0000000..2779413 --- /dev/null +++ b/tests/dsf.js @@ -0,0 +1,60 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let fixtureDsfBytes = await readFixtureHex('dsf'); + // let fixtureDsqJson = require('../fixtures/dsf.json'); + + let header = Parser.parseHeader(fixtureDsfBytes); + if (header.command !== 'dsf') { + throw new Error( + `sanity fail: should have loaded 'dsf' fixture, but got '${header.command}'`, + ); + } + + let payload = fixtureDsfBytes.subarray(Parser.HEADER_SIZE); + // TODO verify + // - a chosen subset of our offered inputs (e.g. we offer 9, but 3 are selected) + // - that equally many of our offered outputs are selected (e.g. 3 = 3) + // - that the satoshi values of our outputs match coin for coin + let dsf = Parser.parseDsf(payload); + console.log(new Date(), '[debug] dsf obj:', dsf); + if ('string' !== typeof dsf.transaction_unsigned) { + throw new Error("'.transactionUnsigned' should exist as a hex string"); + } + let txLen = dsf.transaction_unsigned.length / 2; + let expectedLen = header.payloadSize - Parser.SESSION_ID_SIZE; + console.log(new Date(), '[debug] dsf len:', txLen); + if (txLen !== expectedLen) { + throw new Error( + `expected '.transactionUnsigned' to represent ${header.payloadSize} bytes, but got ${txLen}`, + ); + } + + // Assert.deepEqual(dsq, fixtureDsqJson); + + // console.info('PASS: dsf correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dsi.js b/tests/dsi.js new file mode 100644 index 0000000..11fbb65 --- /dev/null +++ b/tests/dsi.js @@ -0,0 +1,100 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Packer = require('../packer.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let expectedHex = await readFixtureHex('dsi'); + + let network = 'regtest'; + + // Canonical documentation example + // See + + let denomination = 100001 * 1000; + + let inputs = [ + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 2, + }, + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 15, + }, + { + txid: 'c0b5306765c2950838da085d8f3758991a22e246c9862c5f2230566c79c3bd36', + outputIndex: 13, + }, + ]; + + let collateralTxHexes = [ + '01000000', + '01', + '83bd1980c71c38f035db9b14d7f934f7', + 'd595181b3436e36289902619f3f7d383', + '00000000', + '6b', + '483045022100f4d8fa0ae4132235fecd540a', + '62715ccfb1c9a97f8698d066656e30bb1e1e', + '06b90220301b4cc93f38950a69396ed89dfc', + 'c08e72ec8e6e7169463592a0bf504946d98b', + '812102fa4b9c0f9e76e06d57c75cab9c8368', + 'a62a1ce8db6eb0c25c3e0719ddd9ab549c', + 'ffffffff', + '01', + 'e093040000000000', + '19', + '76', + 'a9', + '14', + 'f8956a4eb0e53b05ee6b30edfd2770b5', + '26c1f1bb', + '88', + 'ac', + '00000000', + ]; + let collateralTxHex = collateralTxHexes.join(''); + let collateralTx = DashKeys.utils.hexToBytes(collateralTxHex); + + let outputs = [ + { + pubKeyHash: '14826d7ba05cf76588a5503c03951dc914c91b6c', + satoshis: denomination, + }, + { + pubKeyHash: 'f01197177de2358928196a543b2bbd973c2ab002', + satoshis: denomination, + }, + { + pubKeyHash: '426614716e94812d483bca32374f6ac8cd121b0d', + satoshis: denomination, + }, + ]; + + let message = Packer.packDsi({ network, inputs, collateralTx, outputs }); + let messageHex = DashKeys.utils.bytesToHex(message); + + Assert.equal(messageHex, expectedHex); + + console.info( + `PASS: Packer.packDsi({ network, inputs, collateralTx, outputs }) matches`, + ); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for readability / debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + return fixtureStr; +} + +test(); diff --git a/tests/dsq.js b/tests/dsq.js new file mode 100644 index 0000000..9cc9746 --- /dev/null +++ b/tests/dsq.js @@ -0,0 +1,62 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let totalSize = Parser.HEADER_SIZE + Parser.DSQ_SIZE; + let fixtureDsqBytes = await readFixtureHex('dsq'); + let fixtureDsqJson = require('../fixtures/dsq.json'); + + if (fixtureDsqBytes.length !== totalSize) { + let msg = `sanity fail: fixture should be ${totalSize} bytes, not ${fixtureDsqBytes.length}`; + throw new Error(msg); + } + + let header = Parser.parseHeader(fixtureDsqBytes); + if (header.command !== 'dsq') { + throw new Error('sanity fail: loaded incorrect fixture'); + } + + if (header.payloadSize !== Parser.DSQ_SIZE) { + throw new Error('sanity fail: wrong payload size in header'); + } + + let payload = fixtureDsqBytes.subarray(Parser.HEADER_SIZE); + if (payload.length !== Parser.DSQ_SIZE) { + throw new Error('sanity fail: payload has trailing bytes'); + } + + let dsq = Parser.parseDsq(payload); + + // JSON-ify + dsq.protxhash = DashKeys.utils.bytesToHex(dsq.protxhash_bytes); + dsq.protxhash_bytes = null; + dsq.signature = DashKeys.utils.bytesToHex(dsq.signature_bytes); + dsq.signature_bytes = null; + + Assert.deepEqual(dsq, fixtureDsqJson); + + console.info('PASS: dsq correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dss.sighashall.js b/tests/dss.sighashall.js new file mode 100644 index 0000000..1cc79cd --- /dev/null +++ b/tests/dss.sighashall.js @@ -0,0 +1,154 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +// let Parser = require('../parser.js'); +let DashKeys = require('dashkeys'); +let DashTx = require('dashtx'); +require('dashtx'); + +let Secp256k1 = require('@dashincubator/secp256k1'); + +async function signNonRandom(privKeyBytes, hashBytes) { + let sigOpts = { canonical: true /*extraEntropy: true*/ }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; +} + +async function sign(privKeyBytes, hashBytes) { + let sigOpts = { canonical: true, extraEntropy: true }; + let sigBytes = await Secp256k1.sign(hashBytes, privKeyBytes, sigOpts); + return sigBytes; +} + +async function test() { + let privHex = await readFixtureHex('he-1-privkey'); + let privBytes = DashKeys.utils.hexToBytes(privHex); + + let pubBytes = await DashKeys.utils.toPublicKey(privBytes); + // let pubHex = DashKeys.utils.bytesToHex(pubBytes); + + let p2pkhHex = await readFixtureHex('he-1-p2pkh'); + let originalPkhHex = p2pkhHex.slice(6, -4); + + let pkhBytes = await DashKeys.pubkeyToPkh(pubBytes); + let pkhHex = DashKeys.utils.bytesToHex(pkhBytes); + + Assert.equal(originalPkhHex, pkhHex); + + let hashTxHex = await readFixtureHex('he-1-hashtx'); + let hashTxBytes = DashKeys.utils.hexToBytes(hashTxHex); + { + // let hashTxHead = hashTxHex.slice(0, 24); + // let hashTxFoot = hashTxHex.slice(-24); + //console.log(`[debug] hashTx hex ${hashTxHex.length} ${hashTxHead}...${hashTxFoot}`); + } + + let sigHashHexExp = await readFixtureHex('he-1-sighash'); + + let sigHashBytes = await DashTx.doubleSha256(hashTxBytes); + let sigHashHex = DashKeys.utils.bytesToHex(sigHashBytes); + Assert.equal(sigHashHex, sigHashHexExp); + + let sigHexExp = await readFixtureHex('he-1-asn1-sig'); + + // non-random for the purpose of testing + { + let sigBytes = await signNonRandom(privBytes, sigHashBytes); + let sigHex = DashKeys.utils.bytesToHex(sigBytes); + + Assert.equal(sigHex, sigHexExp); + + let verified = await Secp256k1.verify(sigBytes, sigHashBytes, pubBytes); + Assert.equal(verified, true); + } + + { + let sigRndBytes = await sign(privBytes, sigHashBytes); + let sigRndHex = DashKeys.utils.bytesToHex(sigRndBytes); + Assert.notEqual(sigRndHex, sigHexExp); + + let verified = await Secp256k1.verify(sigRndBytes, sigHashBytes, pubBytes); + Assert.equal(verified, true); + } + + { + let dsfHex = await readFixtureHex('he-dsf'); + + // let dsfSerial = dsfHex.slice(0, 8); + // console.log('[debug] dsf serial', dsfSerial); + + let txRequestHex = dsfHex.slice(8); + let dsfTxInfo = DashTx.parseUnknown(txRequestHex); + let txRequestHex2 = DashTx.serialize(dsfTxInfo); + + Assert.equal(txRequestHex, txRequestHex2); + + let coin = require('../fixtures/he-1-utxo.json'); + let inputIndex = -1; + let version = dsfTxInfo.version; + let inputs = []; + for (let i = 0; i < dsfTxInfo.inputs.length; i += 1) { + let _input = dsfTxInfo.inputs[i]; + let input = { + txid: _input.txid || _input.txId, + txId: _input.txid || _input.txId, + outputIndex: _input.outputIndex, + pubKeyHash: '', + }; + inputs.push(input); + if (_input.txid !== coin.txid) { + continue; + } + if (_input.outputIndex !== coin.outputIndex) { + continue; + } + input.pubKeyHash = pkhHex; + inputIndex = i; + } + let outputs = []; + for (let _output of dsfTxInfo.outputs) { + let output = { + satoshis: _output.satoshis, + pubKeyHash: _output.pubKeyHash, + }; + outputs.push(output); + } + let locktime = dsfTxInfo.locktime; + + let txInfo = { + version, + inputs, + outputs, + locktime, + }; + let hasIndex = inputIndex > -1; + if (!hasIndex) { + throw new Error('selected coin not found in selected inputs'); + } + let sigHashType = 0x01; + let txForSig = DashTx.createForSig(txInfo, inputIndex, sigHashType); + let hashTxHex2 = DashTx.serializeForSig(txForSig, sigHashType); + + Assert.equal(hashTxHex2, hashTxHex); + } + + console.info("PASS: verify signature of 'he' fixture"); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + return fixtureStr; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/dssu.js b/tests/dssu.js new file mode 100644 index 0000000..5e253c7 --- /dev/null +++ b/tests/dssu.js @@ -0,0 +1,55 @@ +'use strict'; + +let Assert = require('node:assert/strict'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +let Parser = require('../parser.js'); +// TODO copy .utils.bytesToHex rather than depend on it +let DashKeys = require('dashkeys'); + +async function test() { + let totalSize = Parser.HEADER_SIZE + Parser.DSSU_SIZE; + let fixtureDssuBytes = await readFixtureHex('dssu'); + let fixtureDssuJson = require('../fixtures/dssu.json'); + + if (fixtureDssuBytes.length !== totalSize) { + let msg = `sanity fail: fixture should be ${totalSize} bytes, not ${fixtureDssuBytes.length}`; + throw new Error(msg); + } + + let header = Parser.parseHeader(fixtureDssuBytes); + if (header.command !== 'dssu') { + throw new Error('sanity fail: loaded incorrect fixture'); + } + + if (header.payloadSize !== Parser.DSSU_SIZE) { + throw new Error('sanity fail: wrong payload size in header'); + } + + let payload = fixtureDssuBytes.subarray(Parser.HEADER_SIZE); + if (payload.length !== Parser.DSSU_SIZE) { + throw new Error('sanity fail: payload has trailing bytes'); + } + + let dssu = Parser.parseDssu(payload); + Assert.deepEqual(dssu, fixtureDssuJson); + + console.info('PASS: dssu correctly parsed and values match'); +} + +async function readFixtureHex(name) { + let fixturePath = Path.resolve(__dirname, `../fixtures/${name}.hex`); + let fixtureStr = await Fs.readFile(fixturePath, 'ascii'); + + // nix whitespace used for debugging + fixtureStr = fixtureStr.replace(/[\s\n]+/g, ''); + + let bytes = DashKeys.utils.hexToBytes(fixtureStr); + return bytes; +} + +test().catch(function (err) { + console.error(err); + process.exit(1); +}); diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000..23eea49 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,104 @@ +'use strict'; + +let ChildProcess = require('child_process'); +let Fs = require('node:fs/promises'); +let Path = require('node:path'); + +async function main() { + console.info('TAP version 13'); + + let dirents = await Fs.readdir(__dirname, { withFileTypes: true }); + + let failures = 0; + let count = 0; + for (let dirent of dirents) { + if (dirent.name === 'index.js') { + continue; + } + + count += 1; + let direntPath = Path.join(__dirname, dirent.name); + let relPath = Path.relative('.', direntPath); + + let success = await handleEach(count, relPath); + if (!success) { + failures += 1; + } + } + + let passes = count - failures; + console.info(``); + console.info(`1..${count}`); + console.info(`# tests ${count}`); + console.info(`# pass ${passes}`); + console.info(`# fail ${failures}`); + console.info(`# skip 0`); + + if (failures !== 0) { + process.exit(1); + } +} + +async function handleEach(count, relPath) { + let success = await exec('node', [relPath]) + .then(function (result) { + console.info(`ok ${count} - ${relPath}`); + return true; + }) + .catch(function (err) { + console.info(`not ok ${count} - ${relPath}`); + if (err.code) { + console.info(` # Error: ${err.code}`); + } + if (err.stderr) { + console.info(` # Stderr: ${err.stderr}`); + } + return false; + }); + + return success; +} + +async function exec(exe, args) { + return new Promise(function (resolve, reject) { + let cmd = ChildProcess.spawn(exe, args); + + let stdout = []; + let stderr = []; + + cmd.stdout.on('data', function (data) { + stdout.push(data.toString('utf8')); + }); + + cmd.stderr.on('data', function (data) { + stderr.push(data.toString('utf8')); + }); + + cmd.on('close', function (code) { + let result = { + code: code, + stdout: stdout.join(''), + stderr: stderr.join(''), + }; + + if (!code) { + resolve(result); + return; + } + + let err = new Error(result.stderr); + Object.assign(err, result); + reject(err); + }); + }); +} + +main() + .then(function () { + process.exit(0); + }) + .catch(function (err) { + console.error('Fail:'); + console.error(err.stack || err); + process.exit(1); + }); diff --git a/tests/ping-pong.js b/tests/ping-pong.js new file mode 100644 index 0000000..8befc48 --- /dev/null +++ b/tests/ping-pong.js @@ -0,0 +1,155 @@ +'use strict'; + +let Packer = require('../packer.js'); + +function test() { + let network = 'regtest'; + let messageSize = Packer.HEADER_SIZE + Packer.PING_SIZE; + let message = new Uint8Array(messageSize); + + // These two are the same, but the latter is more intuitive to use: + // + // - this is ONLY a reference IF the underlying ArrayBuffer is used + // - the OFFSET is ONLY a property, it DOES NOT affect .set() + //let pingBytes = new Uint8Array(bytes.buffer, offset); + // - this is a reference, as you'd expect + // - .set() works from the offset + let pingBytes = message.subarray(Packer.HEADER_SIZE); + let zeroNonceStr = [0, 0, 0, 0, 0, 0, 0, 0].toString(); + if (zeroNonceStr !== pingBytes.toString()) { + throw new Error('sanity fail: nonce bytes should be initialized to 0'); + } + void Packer.packPing({ network, message }); + let randNonceStr = pingBytes.toString(); + if (zeroNonceStr === randNonceStr) { + throw new Error('nonce bytes should be randomized, not 0'); + } + let randNonceBytes2 = message.slice(Packer.HEADER_SIZE); + let randNonceStr2 = randNonceBytes2.toString(); + if (randNonceStr !== randNonceStr2) { + throw new Error( + 'nonce bytes subarray and nonce bytes slice should be equal', + ); + } + + let nonce = new Uint8Array(Packer.FIELD_SIZES.NONCE); + nonce[0] = 0xab; + nonce[1] = 0xcd; + nonce[2] = 0xef; + nonce[3] = 0x12; + nonce[4] = 0x34; + nonce[5] = 0x56; + nonce[6] = 0x78; + nonce[7] = 0x90; + let staticNonceStr = nonce.toString(); + void Packer.packPing({ network, message, nonce }); + let staticNonceStr2 = nonce.toString(); + if (staticNonceStr !== staticNonceStr2) { + throw new Error('static nonce bytes should NOT have been overwritten'); + } + + let nonceBytes3 = pingBytes.slice(0); + let staticNonceStr3 = nonceBytes3.toString(); + if (staticNonceStr !== staticNonceStr3) { + throw new Error( + 'ping nonce bytes and static nonce bytes should have been identical', + ); + } + + let nonceBytes4 = message.slice(Packer.HEADER_SIZE); + let staticNonceStr4 = nonceBytes4.toString(); + if (staticNonceStr !== staticNonceStr4) { + throw new Error( + 'message bytes at ping nonce offset should have matched static nonce', + ); + } + + { + void Packer.packMessage({ + network: 'regtest', + command: 'ping', + bytes: message, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "ping" + 8 trailing nulls + 112, 105, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = message.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete messages did not match'); + } + } + + { + let messageBytes = Packer.packMessage({ + network: 'regtest', + command: 'ping', + payload: pingBytes, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "ping" + 8 trailing nulls + 112, 105, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = messageBytes.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete messages did not match'); + } + } + + console.log(`PASS: headers and ping pack as expected`); + + { + let messageBytes = Packer.packPong({ + network, + nonce, + }); + let headerInts = [ + // regtest + 252, 193, 183, 220, + // 4-char "pong" + 8 trailing nulls + 112, 111, 110, 103, + // + 0, 0, 0, 0, + // + 0, 0, 0, 0, + // little-endian Uint32 payload size + 8, 0, 0, 0, + // first 4 bytes of the sha256(sha256(staticNonce)) + 97, 172, 101, 125, + ]; + let headerStr = headerInts.toString(); + let expectedStr = `${headerStr},${staticNonceStr}`; + let messageStr = messageBytes.toString(); + if (expectedStr !== messageStr) { + throw new Error('complete pong messages did not match'); + } + } + + console.log(`PASS: headers and pong pack as expected`); +} + +test(); diff --git a/todo/TODO.md b/todo/TODO.md deleted file mode 100644 index 67467dd..0000000 --- a/todo/TODO.md +++ /dev/null @@ -1,39 +0,0 @@ -# Roadmap - -_A LOT_ of logic is behind creating denominated coins from a user's wallet and -their transactions. If we could hard-code this portion for the time being, we -can focus on the bigger issues of CoinJoin which will allow us to get closer to -a functioning alpha stage than if we perfected and ported the logic from the -denominations. This isn't to say that the denominations logic will not be part -of the final product: it _definitely_ will be part of the end product. The goal -right now is to prove that this SDK can communicate with testnet master nodes -and can participate with a coin join session. - -# Alpha goals - -1. Connect to the DASH p2p network and to a master node -2. Request to join a coin join queue -3. Participate in mixing - - using coins that are already broken up into denominations -4. Sign a transaction - -- [ ] Lib.LogPrint -- [ ] Lib.sort: - - [ ] `vecTally = Lib.sort(vecTally,function(a, b) {` - -# Wallet functions (add these to `src/wallet.js`) - -- [ ] Lib.mixingWallet.SelectCoinsGroupedByAddresses(true, true, true, 400); -- [ ] Lib.mixingWallet.HasCollateralInputs(); - -# CompactTallyItem - -- [ ] Port this to `src/compact-tally-item.js` - -# CTransactionBuilder - -- defined in `src/coinjoin/util.h` -- code in `src/coinjoin/util.cpp` -- [ ] Port this to `src/transaction-builder.js` -- [ ] `let txBuilder = new CTransactionBuilder(pwallet,tallyItem);` - - this might actually be delegated to the frontend diff --git a/todo/client-session-prioritized.md b/todo/client-session-prioritized.md deleted file mode 100644 index 4187843..0000000 --- a/todo/client-session-prioritized.md +++ /dev/null @@ -1,158 +0,0 @@ -# Overview - -The following contains what I think will get us to a pre-alpha state the -fastest. - -# High priority - -- [ ] `bool CreateDenominated(CAmount nBalanceToDenominate);` - - place this in `client-session.js` - - [ ] Write unit test for this -- [ ] `bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals);` - - [ ] unit tests -- [ ] `bool CreateCollateralTransaction(CMutableTransaction& txCollateral, std::string& strReason);` - - [ ] unit tests -- [ ] `bool MakeCollateralAmounts();` - - [ ] unit tests - -## Split up large inputs or make fee sized inputs - -- [ ] `bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated);` - - [ ] unit tests -- [ ] `bool SelectDenominate(std::string& strErrorRet, std::vector& vecTxDSInRet);` - - [ ] unit tests - -## step 0: select denominated inputs and txouts - -- [ ] `bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, const std::vector& vecTxDSIn, std::vector >& vecPSInOutPairsRet, bool fDryRun = false);` - - [ ] unit tests - -## step 1: prepare denominated inputs and outputs - -- [ ] `bool SendDenominate(const std::vector >& vecPSInOutPairsIn, CConnman& connman) LOCKS_EXCLUDED(cs_coinjoin);` - - [ ] unit tests - -## step 2: send denominated inputs and outputs prepared in step 1 - -- [ ] `void CompletedTransaction(PoolMessage nMessageID);` - - [ ] unit tests -- [ ] `bool SignFinalTransaction(const CTxMemPool& mempool, const CTransaction& finalTransactionNew, CNode& peer, CConnman& connman) LOCKS_EXCLUDED(cs_coinjoin);` - - [ ] unit tests - -## As a client, check and sign the final transaction - -# First steps - -- [ ] `std::vector vecEntries GUARDED_BY(cs_coinjoin); // Masternode/clients entries` - - [ ] This should be one of the first things to implement (CCoinJoinEntry) - -# `CTxIn` and `CTxOut` - -- [x] Implement these two classes -- [ ] Come up with an array that contains the pool state - - [ ] `std::atomic nState{POOL_STATE_IDLE}; // should be one of the POOL_STATE_XXX values` -- [ ] `bool IsValidInOuts(const CTxMemPool& mempool, const std::vector& vin, const std::vector& vout, PoolMessage& nMessageIDRet, bool* fConsumeCollateralRet) const;` - -# Implement `CTransaction` - -- [ ] unit tests - -# Implement `CAmount` - -- [x] `CAmount` is just an `int64_t`. See this `typedef` from `src/amount.h`: - -``` -/** Amount in satoshis (Can be negative) */ -typedef int64_t CAmount; -``` - -# Masternode data - -These variables would be absolutely crucial. For a pre-alpha, we may be able to -get away with simplifying/hard-coding this for the time being. - -- [ ] `const std::unique_ptr& m_mn_sync;` -- [ ] `CDeterministicMNCPtr mixingMasternode;` -- [ ] `void ProcessPoolStateUpdate(CCoinJoinStatusUpdate psssup);` - - Process Masternode updates about the progress of mixing - -# Networking - -- [ ] `CPendingDsaRequest pendingDsaRequest;` - - This should be a priority. - - Anything relating to DS-prefixed constants will be something we want to - implement quickly/first -- [ ] `bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);` -- [ ] `bool StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);` -- [ ] `void ProcessMessage(CNode& peer, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv);` -- [ ] `bool ProcessPendingDsaRequest(CConnman& connman);` - -# Collateral - -- [ ] `CMutableTransaction txMyCollateral; // client side collateral` - -# Anything below this line - -1. has been deemed less important for the time being -2. may be moved up higher in priority at a later date -3. Has the potential of being moved up due to cohesion with the implementation - of the code listed above this line - -- [ ] `std::atomic nTimeLastSuccessfulStep{0}; // the time when last successful mixing step was performed` -- [ ] `std::atomic nSessionID{0}; // 0 if no mixing session is active` -- [ ] `CMutableTransaction finalMutableTransaction GUARDED_BY(cs_coinjoin); // the finalized transaction ready for signing` -- [ ] `int nSessionDenom{0}; // Users must submit a denom matching this` - -## Member functions - -- [ ] `void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);` -- [ ] `int GetState() const { return nState; }` -- [ ] `std::string GetStateString() const;` -- [ ] `int GetEntriesCount() const LOCKS_EXCLUDED(cs_coinjoin) { LOCK(cs_coinjoin); return vecEntries.size(); }` -- [ ] `int GetEntriesCountLocked() const EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin) { return vecEntries.size(); }` - -# Second, the derived class - -## `CCoinJoinClientSession` - -``` -class CCoinJoinClientSession : public CCoinJoinBaseSession -``` - -## Member variables - -- [ ] `bilingual_str strLastMessage;` -- [ ] `bilingual_str strAutoDenomResult;` -- [ ] `std::vector vecOutPointLocked;` - - - This could possibly be simplified greatly - - we could theoretically just have a connection to the master node and leave - it at that - -- [ ] `CKeyHolderStorage keyHolderStorage; // storage for keys used in PrepareDenominate` - - This could possibly be simplified/left out -- [ ] `CWallet& mixingWallet;` - - We can rely on a static wallet for development/testing - -## Member functions - -- `/// Create denominations` -- [ ] `void SetState(PoolState nStateNew);` - - Set the 'state' value, with some logging and capturing when the state - changed -- [ ] `void RelayIn(const CCoinJoinEntry& entry, CConnman& connman) const;` -- [ ] `void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);` -- [ ] `explicit CCoinJoinClientSession(CWallet& pwallet, const std::unique_ptr& mn_sync) : m_mn_sync(mn_sync), mixingWallet(pwallet)` -- [ ] `void UnlockCoins();` -- [ ] `void ResetPool() LOCKS_EXCLUDED(cs_coinjoin);` -- [ ] `bilingual_str GetStatus(bool fWaitForBlock) const;` -- [ ] `bool GetMixingMasternodeInfo(CDeterministicMNCPtr& ret) const;` -- [ ] `bool DoAutomaticDenominating(CTxMemPool& mempool, CConnman& connman, bool fDryRun = false) LOCKS_EXCLUDED(cs_coinjoin);` - - Passively run mixing in the background according to the configuration in - settings -- [ ] `bool SubmitDenominate(CConnman& connman);` - - As a client, submit part of a future mixing transaction to a Masternode to - start the process -- [ ] `bool CheckTimeout();` -- [ ] `void GetJsonInfo(UniValue& obj) const;` - we won't really be needing - this. Serializing to json will be trivial in any use case }; diff --git a/todo/client-session.md b/todo/client-session.md deleted file mode 100644 index 3be7006..0000000 --- a/todo/client-session.md +++ /dev/null @@ -1,111 +0,0 @@ -# Overview - -The following is a TODO list made from the header file located in Dash core at -`src/coinjoin/client.h`. It contains all member functions and types that the -coinjoin client session relies on. The order that items are listed here are in -non-prioritized order. For a prioritized list of features that will get us to a -pre-alpha the fastest, take a look at `client-session-prioritized.md` - -# First, the base class - -## `CCoinJoinBaseSession` - -The Client Session class relies on this base class - -- `// base class` -- `class CCoinJoinBaseSession` - -## Member variables - -- [ ] `mutable Mutex cs_coinjoin;` - - this is a mutex, which javascript does _not_ have. We'll have to figure out - locking on our own -- [ ] `std::vector vecEntries GUARDED_BY(cs_coinjoin); // Masternode/clients entries` -- [ ] `std::atomic nState{POOL_STATE_IDLE}; // should be one of the POOL_STATE_XXX values` -- [ ] `std::atomic nTimeLastSuccessfulStep{0}; // the time when last successful mixing step was performed` -- [ ] `std::atomic nSessionID{0}; // 0 if no mixing session is active` -- [ ] `CMutableTransaction finalMutableTransaction GUARDED_BY(cs_coinjoin); // the finalized transaction ready for signing` -- [ ] `int nSessionDenom{0}; // Users must submit a denom matching this` - -## Member functions - -- [ ] `void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);` -- [ ] `bool IsValidInOuts(const CTxMemPool& mempool, const std::vector& vin, const std::vector& vout, PoolMessage& nMessageIDRet, bool* fConsumeCollateralRet) const;` -- [ ] `int GetState() const { return nState; }` -- [ ] `std::string GetStateString() const;` -- [ ] `int GetEntriesCount() const LOCKS_EXCLUDED(cs_coinjoin) { LOCK(cs_coinjoin); return vecEntries.size(); }` -- [ ] `int GetEntriesCountLocked() const EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin) { return vecEntries.size(); }` - -# Second, the derived class - -## `CCoinJoinClientSession` - -``` -class CCoinJoinClientSession : public CCoinJoinBaseSession -``` - -## Member variables - -- [ ] `bilingual_str strLastMessage;` -- [ ] `bilingual_str strAutoDenomResult;` -- [ ] `const std::unique_ptr& m_mn_sync;` -- [ ] `std::vector vecOutPointLocked;` -- [ ] `CDeterministicMNCPtr mixingMasternode;` - - This could possibly be simplified greatly - - we could theoretically just have a connection to the master node and leave - it at that -- [ ] `CMutableTransaction txMyCollateral; // client side collateral` -- [ ] `CPendingDsaRequest pendingDsaRequest;` - - - This should be a priority. - - Anything relating to DS-prefixed constants will be something we want to - implement quickly/first - -- [ ] `CKeyHolderStorage keyHolderStorage; // storage for keys used in PrepareDenominate` - - This could possibly be simplified/left out -- [ ] `CWallet& mixingWallet;` - - We can rely on a static wallet for development/testing - -## Member functions - -- `/// Create denominations` -- [ ] `bool CreateDenominated(CAmount nBalanceToDenominate);` -- [ ] `bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals);` -- [ ] `bool MakeCollateralAmounts();` - - Split up large inputs or make fee sized inputs -- [ ] `bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated);` -- [ ] `bool CreateCollateralTransaction(CMutableTransaction& txCollateral, std::string& strReason);` -- [ ] `bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);` -- [ ] `bool StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);` -- [ ] `bool SelectDenominate(std::string& strErrorRet, std::vector& vecTxDSInRet);` - - step 0: select denominated inputs and txouts -- [ ] `bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, const std::vector& vecTxDSIn, std::vector >& vecPSInOutPairsRet, bool fDryRun = false);` - - step 1: prepare denominated inputs and outputs -- [ ] `bool SendDenominate(const std::vector >& vecPSInOutPairsIn, CConnman& connman) LOCKS_EXCLUDED(cs_coinjoin);` - - step 2: send denominated inputs and outputs prepared in step 1 -- [ ] `void ProcessPoolStateUpdate(CCoinJoinStatusUpdate psssup);` - - Process Masternode updates about the progress of mixing -- [ ] `void SetState(PoolState nStateNew);` - - Set the 'state' value, with some logging and capturing when the state - changed -- [ ] `void CompletedTransaction(PoolMessage nMessageID);` -- [ ] `bool SignFinalTransaction(const CTxMemPool& mempool, const CTransaction& finalTransactionNew, CNode& peer, CConnman& connman) LOCKS_EXCLUDED(cs_coinjoin);` - - As a client, check and sign the final transaction -- [ ] `void RelayIn(const CCoinJoinEntry& entry, CConnman& connman) const;` -- [ ] `void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);` -- [ ] `explicit CCoinJoinClientSession(CWallet& pwallet, const std::unique_ptr& mn_sync) : m_mn_sync(mn_sync), mixingWallet(pwallet)` -- [ ] `void ProcessMessage(CNode& peer, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv);` -- [ ] `void UnlockCoins();` -- [ ] `void ResetPool() LOCKS_EXCLUDED(cs_coinjoin);` -- [ ] `bilingual_str GetStatus(bool fWaitForBlock) const;` -- [ ] `bool GetMixingMasternodeInfo(CDeterministicMNCPtr& ret) const;` -- [ ] `bool DoAutomaticDenominating(CTxMemPool& mempool, CConnman& connman, bool fDryRun = false) LOCKS_EXCLUDED(cs_coinjoin);` - - Passively run mixing in the background according to the configuration in - settings -- [ ] `bool SubmitDenominate(CConnman& connman);` - - As a client, submit part of a future mixing transaction to a Masternode to - start the process -- [ ] `bool ProcessPendingDsaRequest(CConnman& connman);` -- [ ] `bool CheckTimeout();` -- [ ] `void GetJsonInfo(UniValue& obj) const;` - we won't really be needing - this. Serializing to json will be trivial in any use case }; diff --git a/utils/denominate b/utils/denominate deleted file mode 100755 index 56ccfb9..0000000 --- a/utils/denominate +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env node -//# vi: ft=js - -let DENOM_AMOUNT = 1.00001; -const usage = [ - 'Usage: denominate [--custom-source=N] [--dry-run]', - '', - 'flags: ', - ' --users=foo,bar specify your own comma-delimited users.', - ` --amount=N specify an amount (in DASH). Defaults to ${DENOM_AMOUNT}.`, - ' --dump-users if specified, will dump the users variable then exit.', - ' If used with --users=foo,bar, will dump the users after ', - ' FILE has been imported by --users', - ' --only-source when combined with --custom-source=FOO, will only transfer ', - ' funds from the wallet named FOO and will use all other wallets ', - ' as destination wallets. Handy if you have one wallet with tons of ', - ' coins and a bunch of other wallets with hardly anything.', - ' --custom-source=N use a specific source wallet to pay all other wallets', - ' --inspect if set, will show debug info', - ' --dry-run if set, wont actually run commands that spend DASH', - ' --help|-h this help screen', - '', - 'example: denominate --custom-source=han', - 'example: denominate', - '', - 'denominate will loop through test fixture users', - 'and attempt to send denominations to each address it finds in the ', - 'listaddressgroupings sub-cmd of dash-cli. ', - '', - 'The default mode is to do the above, or you can have one wallet sending to all other wallets ', - 'by using --custom-source=N where N is the user. The user must exist ', - 'in the users array.', - '', - '#-----------------------', - '# Sending custom amounts', - '#-----------------------', - " It's possible to send a custom amount instead of the default of " + - DENOM_AMOUNT + - '.', - ' Simply, use --amount=N, where N is your amount. For example: ', - ' denominate --amount=0.0500', - '', - '#-------------------------------', - "# Dumping the 'users' array ", - '#-------------------------------', - ' The users array can be dumped by passing in --dump-users', - ' For example: ', - ' denominate --dump-users --dry-run', - ' [', - ' "foobar",', - ' "psend",', - ' "luke",', - ' "han",', - ' "chewie",', - ' ]', - '', -].join('\n'); -if ( - process.argv.length < 3 || - process.argv.includes('--help') || - process.argv.includes('h') -) { - console.info(usage); - process.exit(1); -} -const proc = require('child_process'); -for (let arg of process.argv) { - let matches = arg.match(/^\-\-amount=(.*)$/); - if (matches) { - DENOM_AMOUNT = matches[1]; - console.info(`[+] Using "${DENOM_AMOUNT}" as amount.`); - break; - } -} -function denomination_count() { - return DENOM_AMOUNT; -} -let source = 'luke'; -for (let arg of process.argv) { - let matches = arg.match(/^\-\-custom\-source=(.*)$/); - if (matches) { - source = matches[1]; - console.info(`[+] Using "${source}" as source wallet.`); - break; - } -} -let users = [ - { name: 'foobar' }, - { name: 'psend' }, - { name: 'luke' }, - { name: 'han' }, - { name: 'chewie' }, -]; -for (const arg of process.argv) { - let matches = arg.match(/^\-\-users=(.*)$/); - if (matches) { - users = []; - let usernames = matches[1].split(/[,\s]+/); - for (let username of usernames) { - let user = { name: username }; - users.push(user); - } - console.info(`[+] Using users: '${matches[1]}'`); - } -} -if (process.argv.includes('--dump-users')) { - console.log(users); - process.exit(); -} -function inspectUsers() { - for (const user of users) { - console.info('[name]:', user.name); - for (const g of s.groupings) { - console.info(g); - } - } -} -async function processUser(user, address) { - for (const s of users) { - if (s.name === user.name) { - continue; - } - if (process.argv.includes('--dry-run')) { - console.log( - 'mocking:', - s.name, - 'sendtoaddress', - address[0], - denomination_count(), - ); - continue; - } - let ps = await proc.spawnSync('./bin/dash-cli-wallet', [ - s.name, - 'sendtoaddress', - address[0], - denomination_count(), - ]); - if (typeof ps.stdout.toString !== 'undefined') { - console.log(ps.stdout.toString().replace(/[\n]+$/, '')); - } - if (typeof ps.stderr.toString !== 'undefined') { - if (ps.stderr.toString().length) { - console.error(ps.stderr.toString()); - } - } - } -} -function selectSourceUser() { - for (let user of users) { - if (user.name === source) { - return user; - } - } -} -async function processUsers() { - let src = selectSourceUser(); - if (process.argv.includes('--only-source')) { - for (const s of users) { - if (s.name === source) { - continue; - } - for (const g of s.groupings[0]) { - if (process.argv.includes('--dry-run')) { - console.log( - 'mocking:', - src.name, - 'sendtoaddress', - g[0], - denomination_count(), - ); - continue; - } - let ps = await proc.spawnSync('./bin/dash-cli-wallet', [ - src.name, - 'sendtoaddress', - g[0], - denomination_count(), - ]); - if (typeof ps.stdout.toString !== 'undefined') { - console.log(ps.stdout.toString().replace(/[\n]+$/, '')); - } - if (typeof ps.stderr.toString !== 'undefined') { - if (ps.stderr.toString().length) { - console.error(ps.stderr.toString()); - } - } - } - } - process.exit(); - return; - } - for (const s of users) { - for (const g of s.groupings[0]) { - await processUser(s, g); - } - } -} -(async function () { - for (let s of users) { - let ps = await proc.spawnSync('./bin/dash-cli-wallet', [ - s.name, - 'listaddressgroupings', - ]); - s.groupings = JSON.parse(ps.stdout.toString()); - } - if (process.argv.includes('--inspect')) { - inspectUsers(); - } - await processUsers(); - if (process.argv.includes('--inspect')) { - inspectUsers(); - } -})(); diff --git a/utils/dump-fixture-data.js b/utils/dump-fixture-data.js deleted file mode 100755 index 6f1045a..0000000 --- a/utils/dump-fixture-data.js +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// #vi: filetype=js -const fs = require('node:fs/promises'); -const path = require('node:path'); -const cproc = require('node:child_process'); - -let DENOMS = [1.00001]; - -(async () => { - { - /** - * A script to generate listtransactions output - */ - let SCRIPT = [ - '#!/bin/sh', - 'set -e', - 'set -u', - '', - 'for username in foobar psend luke han chewie; do ', - ' ./bin/dash-cli-listtransactions "${username}" 20000 \\', - ' > ./data/w-"${username}"-txns-staged.json', - 'done', - '', - 'git add ./data/w-*-txns-staged.json', - '#git commit -m "chore: update staged txns"', - ].join('\n'); - await fs.writeFile('/tmp/foo', SCRIPT); - await cproc.spawnSync('chmod', ['+x', '/tmp/foo']); - let ps = await cproc.spawnSync('/tmp/foo'); - console.debug(ps.stdout.toString()); - console.debug(ps.stderr.toString()); - } - - async function get_priv_key(username, address) { - let ps = await cproc.spawnSync('./bin/dash-cli-wallet', [ - username, - 'dumpprivkey', - address, - ]); - let privateKey = ps.stdout.toString(); - if (privateKey.length) { - return privateKey; - } - //console.info(`[+] ${script} private key for "${address}": '${privateKey}'`); - if ( - ps.stderr?.toString && - ps.stderr.toString().replace(/^\s+\s+$/, '').length - ) { - console.error(`Exception: '${ps.stderr.toString()}'`); - } - } - - { - /** - * Process all files in ./data/w-*-denominations.json - */ - - let dir = './data'; - let files = await fs.readdir(dir); - let keep = []; - let sorted = {}; - for (const file of files) { - if (!file.match(/^w-[^-]+-txns\-staged\.json$/)) { - continue; - } - - sorted = {}; - let fileParts = file.split('-'); - let username = fileParts[1]; - let finalName = `${dir}/w-${username}-denominations.json`; - let fullName = path.resolve(`${dir}/${file}`); - let contents = require(fullName); - keep = []; - /* - * [{ category: 'receive', amount; 1.00001, address: '...' }] - */ - for (let entry of contents) { - if (entry.category !== 'receive' || !DENOMS.includes(entry.amount)) { - continue; - } - - if (typeof sorted[entry.address] === 'undefined') { - sorted[entry.address] = { - transactions: [], - privateKey: await get_priv_key(username, entry.address), - }; - } - sorted[entry.address].transactions.push({ - txid: entry.txid, - vout: entry.vout, - amount: entry.amount, - confirmations: entry.confirmations, - }); - } - await fs.writeFile(finalName, JSON.stringify(sorted, null, 2)); - console.info( - `[+] Wrote ${ - JSON.stringify(sorted, null, 2).length - } bytes to ${finalName}`, - ); - } - } - - { - /** - * A script to generate listtransactions output - */ - let SCRIPT = [ - '#!/bin/sh', - 'set -e', - 'set -u', - '', - 'for u in foobar psend luke han chewie; do ', - ' git add ./data/w-"$u"-denominations.json', - 'done', - '', - '#git commit -m "chore: update denominations json"', - ].join('\n'); - await fs.writeFile('/tmp/foo', SCRIPT); - await cproc.spawnSync('chmod', ['+x', '/tmp/foo']); - let ps = await cproc.spawnSync('/tmp/foo'); - console.debug(ps.stdout.toString()); - console.debug(ps.stderr.toString()); - } -})(); diff --git a/utils/init-test-fixtures b/utils/init-test-fixtures deleted file mode 100755 index d5a79b4..0000000 --- a/utils/init-test-fixtures +++ /dev/null @@ -1,241 +0,0 @@ -#!/bin/bash -set -e -set -u - -function dump_denoms() { - ./utils/update-manifest -} - -function generate_for_user() { - a_username="${1}" - for _ in $(seq 1 10); do - ./bin/dash-cli-wallet "${a_username}" generatetoaddress 10 "$(cat ./data/w-"${a_username}"-address-0)" - done - ./bin/dash-cli-listtransactions "${a_username}" 500 > ./data/w-"${a_username}"-txns.json -} - -function init_user() { - a_username="${1}" - ./bin/wallet-create "${a_username}" - ./bin/dash-cli-wallet "${a_username}" getnewaddress > ./data/w-"${a_username}"-address-0 - ./bin/dash-cli-wallet "${a_username}" getrawchangeaddress > ./data/w-"${a_username}"-change-address-0 - ./bin/dash-cli-wallet "${a_username}" walletpassphrase foobar 100000000 - ./bin/dash-cli-wallet "${a_username}" dumpprivkey "$(cat ./data/w-"${a_username}"-address-0)" > ./data/w-"${a_username}"-privkey-0 - for _ in $(seq 1 4); do - ./bin/dash-cli-wallet "${a_username}" generatetoaddress 10 "$(cat ./data/w-"${a_username}"-address-0)" - done - ./bin/dash-cli-listtransactions "${a_username}" 500 > ./data/w-"${a_username}"-txns.json -} - -HELP=0 -CREATE=0 -UNLOCK=0 -TXN=0 -UNUSED=0 -DUMP_DENOMS=0 -HAN=0 -CHEWIE=0 -LUKE=0 -FOOBAR=0 -PSEND=0 -LOAD_HAN=0 -LOAD_CHEWIE=0 -LOAD_LUKE=0 -LOAD_FOOBAR=0 -LOAD_PSEND=0 - -if test -z "${*}"; then - set -- "--help" -fi -for arg in "${@}"; do - if [[ $arg == "--all" ]]; then - CREATE=1 - UNLOCK=1 - TXN=1 - UNUSED=1 - DUMP_DENOMS=1 - HAN=1 - CHEWIE=1 - LUKE=1 - FOOBAR=1 - PSEND=1 - LOAD_HAN=1 - LOAD_CHEWIE=1 - LOAD_LUKE=1 - LOAD_FOOBAR=1 - LOAD_PSEND=1 - break - fi - - if [[ $arg == "--help" || $arg == "-h" ]]; then - HELP=1 - fi - if [[ $arg == "--create" ]]; then - CREATE=1 - fi - if [[ $arg == "--dump-denoms" ]]; then - DUMP_DENOMS=1 - fi - if [[ $arg == "--txn" ]]; then - TXN=1 - fi - if [[ $arg == "--create" ]]; then - CREATE=1 - fi - if [[ $arg == "--unlock" ]]; then - UNLOCK=1 - fi - if [[ $arg == "--han" ]]; then - HAN=1 - fi - if [[ $arg == "--luke" ]]; then - LUKE=1 - fi - if [[ $arg == "--chewie" ]]; then - CHEWIE=1 - fi - if [[ $arg == "--psend" ]]; then - PSEND=1 - fi - - if [[ $arg == "--load-han" ]]; then - LOAD_HAN=1 - fi - - if [[ $arg == "--load-chewie" ]]; then - LOAD_CHEWIE=1 - fi - if [[ $arg == "--load-luke" ]]; then - LOAD_LUKE=1 - fi - if [[ $arg == "--load-psend" ]]; then - LOAD_PSEND=1 - fi - if [[ $arg == "--load-foobar" ]]; then - LOAD_FOOBAR=1 - fi - -done - -if [[ $HELP == 1 ]]; then - echo "Usage: $0 [--all|--dump-denoms|--create|--unlock|--txn|--unused]" - echo " --all Same as passing the following flags: " - echo " --dump-denoms --create --unlock --txn --unused " - echo " --han --chewie --luke --psend --foobar" - echo " --dump-denoms runs the script at ./utils/update-manifest" - echo " --txn generates transactions to all wallets listed" - echo " --create creates all wallets, private,keys, and coins. Overwriting previous configs." - echo " --unlock Unlocks all the wallets using walletpassphrase" - - echo " --foobar Create Foobar's wallet" - echo " --psend Create Psend's wallet" - echo " --luke Create Luke's wallet" - echo " --han Create Han's wallet" - echo " --chewie Create Chewie's wallet" - echo - echo " --load-foobar Generate 100 blocks to Foobar's wallet" - echo " --load-psend Generate 100 blocks to Psend's wallet" - echo " --load-luke Generate 100 blocks to Luke's wallet" - echo " --load-han Generate 100 blocks to Han's wallet" - echo " --load-chewie Generate 100 blocks to Chewie's wallet" - echo - echo " --help|-h This help screen" - echo - exit 1 -fi - -if [[ $CREATE == 1 ]]; then - ./bin/wallet-create 'foobar' - ./bin/wallet-create 'psend' - ./bin/wallet-create 'han' - ./bin/wallet-create 'luke' - ./bin/wallet-create 'chewie' - - ./bin/dash-cli-wallet 'foobar' getnewaddress > ./data/w-foobar-address-0 - ./bin/dash-cli-wallet 'psend' getnewaddress > ./data/w-psend-address-0 - ./bin/dash-cli-wallet 'luke' getnewaddress > ./data/w-luke-address-0 - ./bin/dash-cli-wallet 'han' getnewaddress > ./data/w-han-address-0 - ./bin/dash-cli-wallet 'chewie' getnewaddress > ./data/w-chewie-address-0 - - ./bin/dash-cli-wallet 'foobar' getnewaddress > ./data/w-foobar-change-address-0 - ./bin/dash-cli-wallet 'psend' getnewaddress > ./data/w-psend-change-address-0 - ./bin/dash-cli-wallet 'luke' getnewaddress > ./data/w-luke-change-address-0 - ./bin/dash-cli-wallet 'han' getnewaddress > ./data/w-han-change-address-0 - ./bin/dash-cli-wallet 'chewie' getnewaddress > ./data/w-chewie-change-address-0 -fi - -if [[ $UNLOCK == 1 ]]; then - b_wallet_salt='foobar' - b_unlock_time=100000000 - - ./bin/dash-cli-wallet 'foobar' walletpassphrase "${b_wallet_salt}" "${b_unlock_time}" - ./bin/dash-cli-wallet 'psend' walletpassphrase "${b_wallet_salt}" "${b_unlock_time}" - ./bin/dash-cli-wallet 'luke' walletpassphrase "${b_wallet_salt}" "${b_unlock_time}" - ./bin/dash-cli-wallet 'han' walletpassphrase "${b_wallet_salt}" "${b_unlock_time}" - ./bin/dash-cli-wallet 'chewie' walletpassphrase "${b_wallet_salt}" "${b_unlock_time}" - - ./bin/dash-cli-wallet 'foobar' dumpprivkey "$(cat ./data/w-foobar-address-0)" \ - > ./data/w-foobar-privkey-0 - ./bin/dash-cli-wallet 'psend' dumpprivkey "$(cat ./data/w-psend-address-0)" \ - > ./data/w-psend-privkey-0 - ./bin/dash-cli-wallet 'luke' dumpprivkey "$(cat ./data/w-luke-address-0)" \ - > ./data/w-luke-privkey-0 - ./bin/dash-cli-wallet 'han' dumpprivkey "$(cat ./data/w-han-address-0)" \ - > ./data/w-han-privkey-0 - ./bin/dash-cli-wallet 'chewie' dumpprivkey "$(cat ./data/w-chewie-address-0)" \ - > ./data/w-chewie-privkey-0 -fi - -if [[ $TXN == 1 ]]; then - for _ in $(seq 1 10); do - ./bin/dash-cli-wallet 'foobar' generatetoaddress 10 "$(cat ./data/w-foobar-address-0)" - ./bin/dash-cli-wallet 'psend' generatetoaddress 10 "$(cat ./data/w-psend-address-0)" - ./bin/dash-cli-wallet 'luke' generatetoaddress 10 "$(cat ./data/w-luke-address-0)" - ./bin/dash-cli-wallet 'han' generatetoaddress 10 "$(cat ./data/w-han-address-0)" - ./bin/dash-cli-wallet 'chewie' generatetoaddress 10 "$(cat ./data/w-chewie-address-0)" - done -fi - -if [[ $UNUSED == 1 ]]; then - ./bin/dash-cli-listtransactions 'foobar' 500 > ./data/w-foobar-txns.json - ./bin/dash-cli-listtransactions 'psend' 500 > ./data/w-psend-txns.json - ./bin/dash-cli-listtransactions 'luke' 500 > ./data/w-luke-txns.json - ./bin/dash-cli-listtransactions 'han' 500 > ./data/w-han-txns.json - ./bin/dash-cli-listtransactions 'chewie' 500 > ./data/w-chewie-txns.json -fi - -if [[ $DUMP_DENOMS == 1 ]]; then - dump_denoms -fi - -if [[ $FOOBAR == 1 ]]; then - init_user 'foobar' -fi -if [[ $PSEND == 1 ]]; then - init_user 'psend' -fi -if [[ $HAN == 1 ]]; then - init_user 'han' -fi -if [[ $LUKE == 1 ]]; then - init_user 'luke' -fi -if [[ $CHEWIE == 1 ]]; then - init_user 'chewie' -fi - -if [[ $LOAD_FOOBAR == 1 ]]; then - generate_for_user 'foobar' -fi -if [[ $LOAD_PSEND == 1 ]]; then - generate_for_user 'psend' -fi -if [[ $LOAD_LUKE == 1 ]]; then - generate_for_user 'luke' -fi -if [[ $LOAD_HAN == 1 ]]; then - generate_for_user 'han' -fi -if [[ $LOAD_CHEWIE == 1 ]]; then - generate_for_user 'chewie' -fi diff --git a/utils/make-ctags.sh b/utils/make-ctags.sh deleted file mode 100755 index af52e1a..0000000 --- a/utils/make-ctags.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -#DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -#cd $DIR/../ -export WEBROOT=$HOME/code/dash/src -TAGS=$WEBROOT/tags -rm $TAGS 2> /dev/null -cd $WEBROOT -ls *.cpp *.h *.hpp */*p */*.h | grep -v ':' 2> /dev/null > $WEBROOT/cscope.files -ctags -R -f $TAGS --links=no --totals=yes \ - --exclude='*.py' --exclude=Makefile* \ - --exclude='*.js' \ - --totals=no \ - --tag-relative=yes \ - --if0=no \ - --fields=+a+f+i+K+n+s+S+z+t \ - --C++-kinds=+f+c+e-g-l+m-u+v & -cscope -R -b -i $WEBROOT/cscope.files & diff --git a/utils/wallet-coins b/utils/wallet-coins deleted file mode 100755 index 250e908..0000000 --- a/utils/wallet-coins +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -e -set -u - -g_username='psend' -dash-cli-wallet "${g_username}" listaddressbalances | cut -d'"' -f 2 | grep -vE '({|})' diff --git a/utils/wallet-generate-coins b/utils/wallet-generate-coins deleted file mode 100755 index 831ffdc..0000000 --- a/utils/wallet-generate-coins +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -set -e -set -u - -main() { ( - a_username="${1}" - a_num_blocks="${2:-100}" - if test -z "${a_username}"; then - { - #shellcheck disable=SC2016 # variables should not expand here in the docs - echo 'Runs dash-cli -conf=$HOME/.dashmate/local_seed/core/dash.conf -rpcwallet="$username" generatetoaddress [generatetoaddress-opts...]' - echo "" - echo "USAGE" - echo " wallet-generate-coins [count] [generatetoaddress-opts...]" - echo "" - echo "EXAMPLES" - echo " wallet-generate-coins luke 100" - echo "" - } >&2 - exit 1 - fi - if test -n "${2:-}"; then - shift - fi - shift - - b_new_address="$( - dash-cli -conf="${HOME}/.dashmate/local_seed/core/dash.conf" \ - -rpcwallet="${a_username}" \ - getnewaddress - )" - echo "New address: ${b_new_address}" - #shellcheck disable=SC2048,2086 # we want 0 or more, whitespace-delimited args - dash-cli -conf="${HOME}/.dashmate/local_seed/core/dash.conf" \ - -rpcwallet="${a_username}" \ - generatetoaddress ${a_num_blocks} "${b_new_address}" $* -); } - -main "${@}"