diff options
| author | bitflash-coin <bookofshamers@outlook.com> | 2026-08-04 06:34:32 -0700 |
|---|---|---|
| committer | bitflash-coin <bookofshamers@outlook.com> | 2026-08-04 06:34:32 -0700 |
| commit | 5ce650382352c1503f40abf08129ada69402af55 (patch) | |
| tree | 6a18198901f1e2c4933f1ac53c9365eec512bf63 /src/walletcmd.cpp | |
| parent | f34113291da7e5e3dc7d2468d4f9c212c05c584b (diff) | |
public commit
Diffstat (limited to 'src/walletcmd.cpp')
| -rw-r--r-- | src/walletcmd.cpp | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp new file mode 100644 index 0000000..06f35a5 --- /dev/null +++ b/src/walletcmd.cpp | |||
| @@ -0,0 +1,517 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | // | ||
| 4 | // Recovery-phrase commands, from the command line. | ||
| 5 | // | ||
| 6 | // The phrase reaches the person who typed the command and nowhere else. In this | ||
| 7 | // tree printf is OutputDebugStringF, which writes into the data directory's | ||
| 8 | // debug.log -- the file we routinely ask users to attach to issues. A wallet's | ||
| 9 | // twelve words must never go there, so this file takes the real printf and | ||
| 10 | // AttachTerminal() gives it somewhere to land on Windows. | ||
| 11 | |||
| 12 | #include "headers_core.h" | ||
| 13 | #include "bip32.h" | ||
| 14 | #include "walletcmd.h" | ||
| 15 | |||
| 16 | #undef printf | ||
| 17 | |||
| 18 | // How far ahead to look for used addresses when restoring. | ||
| 19 | // | ||
| 20 | // A wallet is restored from a phrase alone, so the node has no idea how many | ||
| 21 | // addresses the old one handed out. It derives forward in batches and stops | ||
| 22 | // once a whole batch turns up unused. A fixed number would be simpler and wrong | ||
| 23 | // for exactly this network: a miner burns an address per block, so a node that | ||
| 24 | // mined for a week is thousands of keys deep, and stopping at a few hundred | ||
| 25 | // would restore a wallet that looks empty. | ||
| 26 | static const int RESTORE_BATCH = 100; | ||
| 27 | static const int RESTORE_MAX = 10000; | ||
| 28 | // Quiet batches required before giving up. Three, because the wallet's own | ||
| 29 | // bookkeeping can leave a gap of two hundred used-nothing indices between one | ||
| 30 | // used address and the next -- see the note at the stop condition. One was not | ||
| 31 | // enough and cost a real balance in testing. | ||
| 32 | static const int RESTORE_QUIET_BATCHES = 3; | ||
| 33 | // The wallet may have restored once, scanned a few quiet batches, and then | ||
| 34 | // spent after that. Change starts from wherever that restore stopped, so the | ||
| 35 | // next real change output can be just beyond the old quiet window. The default | ||
| 36 | // restore has to look far enough for that ordinary "restore, then spend, then | ||
| 37 | // restore again" path; explicit -restoredepth can still go deeper. | ||
| 38 | static const int RESTORE_MIN_SCAN = | ||
| 39 | KEYPOOL_SIZE + (RESTORE_QUIET_BATCHES + 1) * RESTORE_BATCH; | ||
| 40 | |||
| 41 | bool RestoreScanReachedDepth(int nSchema, | ||
| 42 | unsigned int nReceiveNext, | ||
| 43 | unsigned int nChangeNext, | ||
| 44 | unsigned int nLegacyNext, | ||
| 45 | int nStopDepth) | ||
| 46 | { | ||
| 47 | if (nStopDepth <= 0) | ||
| 48 | return true; | ||
| 49 | unsigned int nDepth = (unsigned int)nStopDepth; | ||
| 50 | if (nSchema == HD_SCHEMA_BIP44) | ||
| 51 | return nReceiveNext >= nDepth && | ||
| 52 | nChangeNext >= nDepth && | ||
| 53 | nLegacyNext >= nDepth; | ||
| 54 | return nLegacyNext >= nDepth; | ||
| 55 | } | ||
| 56 | |||
| 57 | int CmdNewPhrase() | ||
| 58 | { | ||
| 59 | AttachTerminal(); | ||
| 60 | |||
| 61 | if (HaveHDSeed()) | ||
| 62 | { | ||
| 63 | printf("This wallet already has a recovery phrase.\n"); | ||
| 64 | printf("Creating another would replace it, and coins on addresses derived from\n"); | ||
| 65 | printf("the current one would no longer come back from the phrase you write down.\n"); | ||
| 66 | printf("Nothing was changed.\n"); | ||
| 67 | return 1; | ||
| 68 | } | ||
| 69 | |||
| 70 | std::string strMnemonic, strError; | ||
| 71 | if (!bitflash::BIP39GenerateMnemonic(strMnemonic, strError)) | ||
| 72 | { | ||
| 73 | fprintf(stderr, "Could not generate a phrase: %s\n", strError.c_str()); | ||
| 74 | return 1; | ||
| 75 | } | ||
| 76 | |||
| 77 | if (!SetHDSeedFromMnemonic(strMnemonic, strError)) | ||
| 78 | { | ||
| 79 | fprintf(stderr, "Could not install the seed: %s\n", strError.c_str()); | ||
| 80 | return 1; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Fill the pool from the new seed straight away, so the addresses this | ||
| 84 | // wallet hands out next are ones the phrase can reproduce. | ||
| 85 | TopUpKeyPool(); | ||
| 86 | |||
| 87 | printf("\n"); | ||
| 88 | printf("Write these twelve words down, in order, on paper.\n"); | ||
| 89 | printf("They are the only thing that can rebuild this wallet if the file is lost.\n"); | ||
| 90 | printf("Anyone who reads them can spend your coins. They are not stored anywhere,\n"); | ||
| 91 | printf("and this is the only time they will be shown.\n"); | ||
| 92 | printf("\n"); | ||
| 93 | printf(" %s\n", strMnemonic.c_str()); | ||
| 94 | printf("\n"); | ||
| 95 | printf("Keys already in this wallet are NOT covered by the phrase -- they existed\n"); | ||
| 96 | printf("before it did. Keep your file backups as well.\n"); | ||
| 97 | fflush(stdout); | ||
| 98 | return 0; | ||
| 99 | } | ||
| 100 | |||
| 101 | bool RestoreFromPhrase(const std::string& strMnemonic, | ||
| 102 | int nMinDepth, | ||
| 103 | RestoreProgressFn fnProgress, | ||
| 104 | void* pArg, | ||
| 105 | std::string& strErrorRet, | ||
| 106 | int& nRecoveredRet, | ||
| 107 | int& nDerivedRet) | ||
| 108 | { | ||
| 109 | strErrorRet.clear(); | ||
| 110 | nRecoveredRet = 0; | ||
| 111 | nDerivedRet = 0; | ||
| 112 | |||
| 113 | // Refuse before touching the wallet, not after. | ||
| 114 | // | ||
| 115 | // Without this, a restore on a node that has not synced derives the right | ||
| 116 | // keys, scans a chain that is not there, and reports "no transactions were | ||
| 117 | // found" -- which reads as "your coins are gone" to the one person least | ||
| 118 | // able to argue with it. Measured while testing: a copied data directory | ||
| 119 | // whose Berkeley DB environment failed to load left the node at height 0, | ||
| 120 | // the scan walked one block, and the message said nothing was found across | ||
| 121 | // 600 addresses. | ||
| 122 | if (!CanScanWalletTransactions(strErrorRet)) | ||
| 123 | return false; | ||
| 124 | |||
| 125 | size_t nWalletStart = 0; | ||
| 126 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 127 | nWalletStart = mapWallet.size(); | ||
| 128 | |||
| 129 | if (!SetHDSeedFromMnemonic(strMnemonic, strErrorRet)) | ||
| 130 | return false; | ||
| 131 | |||
| 132 | // Derive forward in batches, scanning after each, until a whole batch turns | ||
| 133 | // up nothing. Every derived key is written to the wallet before the scan, | ||
| 134 | // because the scan asks the wallet what belongs to it. | ||
| 135 | int nTotalDerived = nHDKeySchema == HD_SCHEMA_BIP44 | ||
| 136 | ? (int)(nHDReceiveNext + nHDChangeNext) | ||
| 137 | : (int)nHDNext; | ||
| 138 | int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); | ||
| 139 | int nQuietBatches = 0; | ||
| 140 | while (nTotalDerived < RESTORE_MAX) | ||
| 141 | { | ||
| 142 | // Count wallet transactions, not scan hits. | ||
| 143 | // | ||
| 144 | // ScanForWalletTransactions reports what it added *or updated*, and it | ||
| 145 | // walks the whole chain every time, so a transaction found in the first | ||
| 146 | // batch is reported again by every batch after it. Testing this against | ||
| 147 | // a real payment produced exactly +1 per batch and a loop that ran to | ||
| 148 | // the ceiling, deriving ten thousand addresses to rediscover one coin. | ||
| 149 | size_t nWalletBefore = 0; | ||
| 150 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 151 | nWalletBefore = mapWallet.size(); | ||
| 152 | |||
| 153 | std::string strDeriveError; | ||
| 154 | if (nHDKeySchema == HD_SCHEMA_BIP44) | ||
| 155 | { | ||
| 156 | for (int i = 0; i < RESTORE_BATCH; i++) | ||
| 157 | { | ||
| 158 | CKey key; | ||
| 159 | if (!DeriveHDKey(nHDReceiveNext, key, strDeriveError)) | ||
| 160 | break; | ||
| 161 | if (!AddKey(key)) | ||
| 162 | break; | ||
| 163 | nHDReceiveNext++; | ||
| 164 | nTotalDerived++; | ||
| 165 | } | ||
| 166 | CWalletDB().WriteHDReceiveNext(nHDReceiveNext); | ||
| 167 | |||
| 168 | for (int i = 0; i < RESTORE_BATCH; i++) | ||
| 169 | { | ||
| 170 | CKey key; | ||
| 171 | if (!DeriveHDChangeKey(nHDChangeNext, key, strDeriveError)) | ||
| 172 | break; | ||
| 173 | if (!AddKey(key)) | ||
| 174 | break; | ||
| 175 | nHDChangeNext++; | ||
| 176 | nTotalDerived++; | ||
| 177 | } | ||
| 178 | CWalletDB().WriteHDChangeNext(nHDChangeNext); | ||
| 179 | |||
| 180 | int nSavedSchema = nHDKeySchema; | ||
| 181 | nHDKeySchema = HD_SCHEMA_LEGACY; | ||
| 182 | for (int i = 0; i < RESTORE_BATCH; i++) | ||
| 183 | { | ||
| 184 | CKey key; | ||
| 185 | if (!DeriveHDKey(nHDNext, key, strDeriveError)) | ||
| 186 | break; | ||
| 187 | if (!AddKey(key)) | ||
| 188 | break; | ||
| 189 | nHDNext++; | ||
| 190 | nTotalDerived++; | ||
| 191 | } | ||
| 192 | nHDKeySchema = nSavedSchema; | ||
| 193 | CWalletDB().WriteHDNext(nHDNext); | ||
| 194 | } | ||
| 195 | else | ||
| 196 | { | ||
| 197 | for (int i = 0; i < RESTORE_BATCH; i++) | ||
| 198 | { | ||
| 199 | CKey key; | ||
| 200 | if (!DeriveHDKey(nHDNext, key, strDeriveError)) | ||
| 201 | break; | ||
| 202 | if (!AddKey(key)) | ||
| 203 | break; | ||
| 204 | nHDNext++; | ||
| 205 | nTotalDerived++; | ||
| 206 | } | ||
| 207 | CWalletDB().WriteHDNext(nHDNext); | ||
| 208 | } | ||
| 209 | |||
| 210 | ScanForWalletTransactions(pindexGenesisBlock); | ||
| 211 | |||
| 212 | size_t nWalletAfter = 0; | ||
| 213 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 214 | nWalletAfter = mapWallet.size(); | ||
| 215 | |||
| 216 | if (fnProgress) | ||
| 217 | { | ||
| 218 | size_t nRecoveredNow = nWalletAfter > nWalletStart ? | ||
| 219 | nWalletAfter - nWalletStart : 0; | ||
| 220 | fnProgress(pArg, nTotalDerived, (int)nRecoveredNow); | ||
| 221 | } | ||
| 222 | |||
| 223 | // Stopping needs more than one quiet batch, because this wallet digs | ||
| 224 | // gaps in its own derivation. A restore leaves the receive counter at | ||
| 225 | // the depth it scanned, the key pool then derives KEYPOOL_SIZE more, | ||
| 226 | // and a later spend can use change after that -- so the address used | ||
| 227 | // *after* a restore can sit two hundred indices past the last one used | ||
| 228 | // before it, with nothing in between. | ||
| 229 | // | ||
| 230 | // Measured, on a real wallet with real coin: restore, spend once, and | ||
| 231 | // the coins land at indices 201 and 302. Restoring again with the old | ||
| 232 | // one-batch rule stopped at 201 and reported a single transaction. The | ||
| 233 | // money was derived, covered by the phrase, and invisible. | ||
| 234 | if (nWalletAfter == nWalletBefore) | ||
| 235 | nQuietBatches++; | ||
| 236 | else | ||
| 237 | nQuietBatches = 0; | ||
| 238 | if (nQuietBatches >= RESTORE_QUIET_BATCHES && | ||
| 239 | RestoreScanReachedDepth(nHDKeySchema, | ||
| 240 | nHDReceiveNext, | ||
| 241 | nHDChangeNext, | ||
| 242 | nHDNext, | ||
| 243 | nStopDepth)) | ||
| 244 | break; | ||
| 245 | } | ||
| 246 | |||
| 247 | // The pool is rebuilt from where derivation stopped, so the next address | ||
| 248 | // this wallet hands out is one the phrase can reproduce as well. | ||
| 249 | CRITICAL_BLOCK(cs_keyPool) | ||
| 250 | mapKeyPool.clear(); | ||
| 251 | TopUpKeyPool(); | ||
| 252 | |||
| 253 | size_t nWalletEnd = 0; | ||
| 254 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 255 | nWalletEnd = mapWallet.size(); | ||
| 256 | nRecoveredRet = (int)(nWalletEnd > nWalletStart ? | ||
| 257 | nWalletEnd - nWalletStart : 0); | ||
| 258 | nDerivedRet = nTotalDerived; | ||
| 259 | return true; | ||
| 260 | } | ||
| 261 | |||
| 262 | static void PrintRestoreProgress(void*, int nDerived, int nRecovered) | ||
| 263 | { | ||
| 264 | printf(" %d addresses checked, %d transaction(s) recovered\n", nDerived, nRecovered); | ||
| 265 | fflush(stdout); | ||
| 266 | } | ||
| 267 | |||
| 268 | int CmdRestorePhrase(const std::string& strMnemonic, int nMinDepth) | ||
| 269 | { | ||
| 270 | AttachTerminal(); | ||
| 271 | |||
| 272 | printf("Checking the phrase and the chain.\n"); | ||
| 273 | fflush(stdout); | ||
| 274 | |||
| 275 | std::string strError; | ||
| 276 | int nRecovered = 0, nDerived = 0; | ||
| 277 | if (!RestoreFromPhrase(strMnemonic, nMinDepth, PrintRestoreProgress, NULL, | ||
| 278 | strError, nRecovered, nDerived)) | ||
| 279 | { | ||
| 280 | fprintf(stderr, "Cannot restore: %s\n", strError.c_str()); | ||
| 281 | fprintf(stderr, "Nothing was changed.\n"); | ||
| 282 | return 1; | ||
| 283 | } | ||
| 284 | |||
| 285 | printf("\n"); | ||
| 286 | if (nRecovered > 0) | ||
| 287 | printf("Restored: %d transaction(s) across %d derived addresses.\n", | ||
| 288 | nRecovered, nDerived); | ||
| 289 | else | ||
| 290 | printf("No transactions were found for the first %d addresses of that phrase.\n" | ||
| 291 | "If you expected coins, check the words and the order.\n", nDerived); | ||
| 292 | printf("Start the node normally to see the balance.\n"); | ||
| 293 | fflush(stdout); | ||
| 294 | return 0; | ||
| 295 | } | ||
| 296 | |||
| 297 | int CmdNewAddress() | ||
| 298 | { | ||
| 299 | AttachTerminal(); | ||
| 300 | |||
| 301 | std::vector<unsigned char> vchPubKey = GetKeyFromPool(); | ||
| 302 | if (vchPubKey.empty()) | ||
| 303 | { | ||
| 304 | fprintf(stderr, "Could not get a key from the wallet.\n"); | ||
| 305 | return 1; | ||
| 306 | } | ||
| 307 | |||
| 308 | printf("%s\n", PubKeyToAddress(vchPubKey).c_str()); | ||
| 309 | if (!HaveHDSeed()) | ||
| 310 | printf("(this wallet has no recovery phrase, so only a file backup covers this address)\n"); | ||
| 311 | fflush(stdout); | ||
| 312 | return 0; | ||
| 313 | } | ||
| 314 | |||
| 315 | // Diagnostic: print the addresses a phrase produces, so "the restore found | ||
| 316 | // nothing" can be told apart from "that address was never derived". | ||
| 317 | int CmdShowDerived(int nCount) | ||
| 318 | { | ||
| 319 | AttachTerminal(); | ||
| 320 | |||
| 321 | if (!HaveHDSeed()) | ||
| 322 | { | ||
| 323 | fprintf(stderr, "This wallet has no recovery phrase installed.\n"); | ||
| 324 | return 1; | ||
| 325 | } | ||
| 326 | if (nCount <= 0) | ||
| 327 | nCount = 20; | ||
| 328 | |||
| 329 | std::string strError; | ||
| 330 | for (int i = 0; i < nCount; i++) | ||
| 331 | { | ||
| 332 | CKey key; | ||
| 333 | if (!DeriveHDKey((unsigned int)i, key, strError)) | ||
| 334 | { | ||
| 335 | fprintf(stderr, "derivation failed at %d: %s\n", i, strError.c_str()); | ||
| 336 | return 1; | ||
| 337 | } | ||
| 338 | printf("%6d %s\n", i, PubKeyToAddress(key.GetPubKey()).c_str()); | ||
| 339 | } | ||
| 340 | fflush(stdout); | ||
| 341 | return 0; | ||
| 342 | } | ||
| 343 | |||
| 344 | int CmdRecoveryAudit() | ||
| 345 | { | ||
| 346 | AttachTerminal(); | ||
| 347 | |||
| 348 | WalletRecoveryAudit audit = GetWalletRecoveryAudit(); | ||
| 349 | int64 nTotal = audit.nRecoverableCredit + audit.nLegacyCredit; | ||
| 350 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; | ||
| 351 | |||
| 352 | printf("Wallet recovery audit\n"); | ||
| 353 | printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); | ||
| 354 | if (audit.fHaveSeed) | ||
| 355 | { | ||
| 356 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); | ||
| 357 | printf(" BIP44 coin type: %u (provisional BITFLASH)\n", audit.nCoinType); | ||
| 358 | printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown); | ||
| 359 | printf(" receive/change counters: %u/%u\n", | ||
| 360 | audit.nReceiveNext, audit.nChangeNext); | ||
| 361 | } | ||
| 362 | if (!audit.fDeriveComplete) | ||
| 363 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); | ||
| 364 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); | ||
| 365 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", | ||
| 366 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); | ||
| 367 | printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", | ||
| 368 | FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); | ||
| 369 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | ||
| 370 | printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", | ||
| 371 | FormatMoney(audit.nRecoverableImmatureCredit).c_str(), | ||
| 372 | audit.nRecoverableImmatureTx); | ||
| 373 | printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n", | ||
| 374 | FormatMoney(audit.nLegacyImmatureCredit).c_str(), | ||
| 375 | audit.nLegacyImmatureTx); | ||
| 376 | |||
| 377 | if (!audit.fHaveSeed) | ||
| 378 | { | ||
| 379 | printf("\n"); | ||
| 380 | printf("This wallet has no recovery phrase. A file backup is the only backup.\n"); | ||
| 381 | fflush(stdout); | ||
| 382 | return nTotal + nImmatureTotal > 0 ? 2 : 0; | ||
| 383 | } | ||
| 384 | if (!audit.fDeriveComplete) | ||
| 385 | { | ||
| 386 | printf("\n"); | ||
| 387 | printf("Warning: the audit could not derive every known phrase key, so coverage is incomplete.\n"); | ||
| 388 | fflush(stdout); | ||
| 389 | return 2; | ||
| 390 | } | ||
| 391 | if (audit.nLegacyCredit > 0 || audit.nLegacyImmatureCredit > 0) | ||
| 392 | { | ||
| 393 | printf("\n"); | ||
| 394 | printf("Warning: some coins are on keys the phrase does not reproduce.\n"); | ||
| 395 | printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n"); | ||
| 396 | fflush(stdout); | ||
| 397 | return 2; | ||
| 398 | } | ||
| 399 | |||
| 400 | printf("\n"); | ||
| 401 | if (nTotal + nImmatureTotal > 0) | ||
| 402 | printf("All known wallet balance is covered by the recovery phrase.\n"); | ||
| 403 | else | ||
| 404 | printf("No wallet balance found yet.\n"); | ||
| 405 | fflush(stdout); | ||
| 406 | return 0; | ||
| 407 | } | ||
| 408 | |||
| 409 | int CmdEncryptWallet(const std::string& strPassphrase) | ||
| 410 | { | ||
| 411 | AttachTerminal(); | ||
| 412 | |||
| 413 | string strBackup; | ||
| 414 | string strError; | ||
| 415 | if (!EncryptWallet(strPassphrase, strBackup, strError)) | ||
| 416 | { | ||
| 417 | fprintf(stderr, "Cannot encrypt wallet: %s\n", strError.c_str()); | ||
| 418 | fprintf(stderr, "Nothing was changed.\n"); | ||
| 419 | return 1; | ||
| 420 | } | ||
| 421 | |||
| 422 | printf("Wallet encrypted.\n"); | ||
| 423 | printf("The old unencrypted wallet was moved to:\n"); | ||
| 424 | printf(" %s\n", strBackup.c_str()); | ||
| 425 | printf("That backup still contains private keys in plain text. Move it offline or\n"); | ||
| 426 | printf("delete it after you have a safer backup plan.\n"); | ||
| 427 | printf("Restart Bitflash before using the wallet again.\n"); | ||
| 428 | fflush(stdout); | ||
| 429 | |||
| 430 | // The rewritten wallet.dat is clean; the environment's write-ahead logs are | ||
| 431 | // not, because every plaintext record this wallet ever wrote passed through | ||
| 432 | // them. Closing the environment and dropping the logs is the last step of | ||
| 433 | // encrypting, not an optimisation -- our own backup advice is to copy the | ||
| 434 | // whole directory, and following it otherwise carries the keys along. | ||
| 435 | PurgeDbEnvironmentLogs(); | ||
| 436 | return 0; | ||
| 437 | } | ||
| 438 | |||
| 439 | // Spend, from the command line. | ||
| 440 | // | ||
| 441 | // SendMoney() has been in this tree since 0.1.0 and only the window ever called | ||
| 442 | // it, so a headless node could be paid and could never pay: it held a balance | ||
| 443 | // with no way to move it. Found while trying to prove that change from a wallet | ||
| 444 | // with a recovery phrase lands on a key the phrase can reproduce -- a test that | ||
| 445 | // needs a spend, and there was no way to make one without a screen. | ||
| 446 | // | ||
| 447 | // Deliberately strict, because this moves money and there is nobody to click | ||
| 448 | // "are you sure": the address must decode, the amount must parse and be | ||
| 449 | // positive, and anything else refuses before touching the wallet. | ||
| 450 | int CmdSendTo(const std::string& strArg) | ||
| 451 | { | ||
| 452 | AttachTerminal(); | ||
| 453 | |||
| 454 | if (IsWalletLocked()) | ||
| 455 | { | ||
| 456 | fprintf(stderr, "Wallet is encrypted and locked. Start with /walletpassphrase or /walletpassphrase=@FILE to spend.\n"); | ||
| 457 | return 1; | ||
| 458 | } | ||
| 459 | |||
| 460 | std::string::size_type comma = strArg.rfind(','); | ||
| 461 | if (comma == std::string::npos) | ||
| 462 | { | ||
| 463 | fprintf(stderr, "Usage: -sendto=ADDRESS,AMOUNT (for example -sendto=B7kQ...,1.5)\n"); | ||
| 464 | return 1; | ||
| 465 | } | ||
| 466 | std::string strAddr = strArg.substr(0, comma); | ||
| 467 | std::string strAmount = strArg.substr(comma + 1); | ||
| 468 | |||
| 469 | // Trim, so a quoted argument with stray spaces does not silently become an | ||
| 470 | // invalid address and send nothing. | ||
| 471 | while (!strAddr.empty() && isspace((unsigned char)strAddr[0])) strAddr.erase(0, 1); | ||
| 472 | while (!strAddr.empty() && isspace((unsigned char)strAddr[strAddr.size()-1])) strAddr.erase(strAddr.size()-1); | ||
| 473 | while (!strAmount.empty() && isspace((unsigned char)strAmount[0])) strAmount.erase(0, 1); | ||
| 474 | while (!strAmount.empty() && isspace((unsigned char)strAmount[strAmount.size()-1])) strAmount.erase(strAmount.size()-1); | ||
| 475 | |||
| 476 | uint160 hash160; | ||
| 477 | if (!AddressToHash160(strAddr, hash160)) | ||
| 478 | { | ||
| 479 | fprintf(stderr, "Not a valid address: %s\n", strAddr.c_str()); | ||
| 480 | return 1; | ||
| 481 | } | ||
| 482 | |||
| 483 | int64 nValue = 0; | ||
| 484 | if (!ParseMoney(strAmount.c_str(), nValue) || nValue <= 0) | ||
| 485 | { | ||
| 486 | fprintf(stderr, "Not a valid amount: %s\n", strAmount.c_str()); | ||
| 487 | return 1; | ||
| 488 | } | ||
| 489 | |||
| 490 | std::string strWhy; | ||
| 491 | if (!CanScanWalletTransactions(strWhy)) | ||
| 492 | { | ||
| 493 | // Spending needs the chain: without it the wallet cannot tell which of | ||
| 494 | // its outputs are still unspent, and a transaction built on that guess | ||
| 495 | // is one the network will reject. | ||
| 496 | fprintf(stderr, "The block chain is not loaded, so this wallet cannot tell " | ||
| 497 | "which coins it still has. Start the node and let it sync first.\n"); | ||
| 498 | return 1; | ||
| 499 | } | ||
| 500 | |||
| 501 | CScript scriptPubKey; | ||
| 502 | scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; | ||
| 503 | |||
| 504 | CWalletTx wtx; | ||
| 505 | if (!SendMoney(scriptPubKey, nValue, wtx)) | ||
| 506 | { | ||
| 507 | fprintf(stderr, "The transaction was not created. Usually that means the " | ||
| 508 | "balance is too low once the fee is counted.\n"); | ||
| 509 | return 1; | ||
| 510 | } | ||
| 511 | |||
| 512 | printf("Sent %s to %s\n", FormatMoney(nValue).c_str(), strAddr.c_str()); | ||
| 513 | printf(" transaction %s\n", wtx.GetHash().ToString().c_str()); | ||
| 514 | printf(" it needs to be relayed and mined before the other side sees it.\n"); | ||
| 515 | fflush(stdout); | ||
| 516 | return 0; | ||
| 517 | } | ||