try file restore
This commit is contained in:
450
rin/wallet/cmd/PROPER_SELF_CUSTODY_SOLUTION.md
Normal file
450
rin/wallet/cmd/PROPER_SELF_CUSTODY_SOLUTION.md
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
# Proper Self-Custody Wallet Solution for RinCoin
|
||||||
|
|
||||||
|
## ❌ The Problem
|
||||||
|
|
||||||
|
You're absolutely right - the wallet file backup method is NOT user-friendly for self-custody:
|
||||||
|
- Not a simple 12-word phrase
|
||||||
|
- Large binary files
|
||||||
|
- Can't easily write down or memorize
|
||||||
|
- Not compatible with standard wallet UX
|
||||||
|
|
||||||
|
## ✅ The REAL Solution: BIP39 Mnemonic with Key Derivation
|
||||||
|
|
||||||
|
**We need to implement BIP39 support in the web wallet layer, NOT rely on RinCoin's RPC!**
|
||||||
|
|
||||||
|
### How It Works:
|
||||||
|
|
||||||
|
```
|
||||||
|
User's 12 Words (BIP39)
|
||||||
|
↓ (in web wallet)
|
||||||
|
Master Seed (BIP32)
|
||||||
|
↓ (derive keys)
|
||||||
|
Private Keys for Addresses
|
||||||
|
↓ (import to RinCoin)
|
||||||
|
RinCoin Wallet (via importprivkey)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Insight:
|
||||||
|
|
||||||
|
**The web wallet handles BIP39/BIP32 derivation, then imports the derived keys into RinCoin!**
|
||||||
|
|
||||||
|
## 🎯 Implementation Architecture
|
||||||
|
|
||||||
|
### Layer 1: Web Wallet (BIP39 Support)
|
||||||
|
|
||||||
|
The browser extension/web wallet handles:
|
||||||
|
- BIP39 mnemonic generation and validation
|
||||||
|
- BIP32 key derivation
|
||||||
|
- Address generation
|
||||||
|
- Key management
|
||||||
|
|
||||||
|
### Layer 2: RinCoin Node (Key Storage Only)
|
||||||
|
|
||||||
|
The RinCoin node just stores imported keys:
|
||||||
|
- Receives derived private keys via `importprivkey`
|
||||||
|
- Tracks balances and transactions
|
||||||
|
- Signs transactions when needed
|
||||||
|
|
||||||
|
## 🔧 Technical Implementation
|
||||||
|
|
||||||
|
### 1. Backup Flow (Generate Mnemonic)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Web Wallet
|
||||||
|
import * as bip39 from 'bip39';
|
||||||
|
import { BIP32Factory } from 'bip32';
|
||||||
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
// Generate new wallet with mnemonic
|
||||||
|
async function createNewWallet() {
|
||||||
|
// 1. Generate BIP39 mnemonic (12 or 24 words)
|
||||||
|
const mnemonic = bip39.generateMnemonic(128); // 12 words
|
||||||
|
|
||||||
|
// 2. Convert to seed
|
||||||
|
const seed = await bip39.mnemonicToSeed(mnemonic);
|
||||||
|
|
||||||
|
// 3. Create master key
|
||||||
|
const root = bip32.fromSeed(seed);
|
||||||
|
|
||||||
|
// 4. Derive keys for RinCoin (using Litecoin derivation path)
|
||||||
|
// m/44'/2'/0'/0/0 (Litecoin path, which RinCoin is based on)
|
||||||
|
const masterPath = "m/44'/2'/0'/0";
|
||||||
|
|
||||||
|
// 5. Generate first 20 addresses
|
||||||
|
const addresses = [];
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const child = root.derivePath(`${masterPath}/${i}`);
|
||||||
|
const privateKey = child.toWIF();
|
||||||
|
const address = deriveRinCoinAddress(child.publicKey);
|
||||||
|
|
||||||
|
addresses.push({
|
||||||
|
index: i,
|
||||||
|
address: address,
|
||||||
|
privateKey: privateKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Import keys to RinCoin node
|
||||||
|
for (const addr of addresses) {
|
||||||
|
await importPrivateKeyToNode(addr.privateKey, addr.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Return mnemonic to user (SHOW ONCE, USER MUST WRITE DOWN)
|
||||||
|
return {
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
addresses: addresses.map(a => a.address)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import single key to RinCoin
|
||||||
|
async function importPrivateKeyToNode(privateKey, label) {
|
||||||
|
return await rpcCall('importprivkey', [privateKey, label, false]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Restore Flow (From Mnemonic)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In Web Wallet
|
||||||
|
async function restoreWalletFromMnemonic(mnemonic) {
|
||||||
|
// 1. Validate mnemonic
|
||||||
|
if (!bip39.validateMnemonic(mnemonic)) {
|
||||||
|
throw new Error('Invalid mnemonic phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Convert to seed
|
||||||
|
const seed = await bip39.mnemonicToSeed(mnemonic);
|
||||||
|
|
||||||
|
// 3. Create master key
|
||||||
|
const root = bip32.fromSeed(seed);
|
||||||
|
|
||||||
|
// 4. Create new wallet on node
|
||||||
|
await rpcCall('createwallet', [walletName, false, false]);
|
||||||
|
|
||||||
|
// 5. Derive and import keys with gap limit
|
||||||
|
const masterPath = "m/44'/2'/0'/0";
|
||||||
|
let consecutiveUnused = 0;
|
||||||
|
const GAP_LIMIT = 20;
|
||||||
|
|
||||||
|
for (let i = 0; consecutiveUnused < GAP_LIMIT; i++) {
|
||||||
|
const child = root.derivePath(`${masterPath}/${i}`);
|
||||||
|
const privateKey = child.toWIF();
|
||||||
|
const address = deriveRinCoinAddress(child.publicKey);
|
||||||
|
|
||||||
|
// Import key
|
||||||
|
await importPrivateKeyToNode(privateKey, `addr_${i}`);
|
||||||
|
|
||||||
|
// Check if address has been used (has transactions)
|
||||||
|
const hasTransactions = await checkAddressUsage(address);
|
||||||
|
|
||||||
|
if (hasTransactions) {
|
||||||
|
consecutiveUnused = 0; // Reset counter
|
||||||
|
} else {
|
||||||
|
consecutiveUnused++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Rescan blockchain to find all transactions
|
||||||
|
await rpcCall('rescanblockchain', []);
|
||||||
|
|
||||||
|
// 7. Get balance
|
||||||
|
const balance = await rpcCall('getbalance', []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
balance: balance,
|
||||||
|
message: 'Wallet restored successfully!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Address Derivation for RinCoin
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
// RinCoin uses Litecoin's address format
|
||||||
|
function deriveRinCoinAddress(publicKey) {
|
||||||
|
// Use Litecoin network parameters (RinCoin is Litecoin-based)
|
||||||
|
const rincoinNetwork = {
|
||||||
|
messagePrefix: '\x19RinCoin Signed Message:\n',
|
||||||
|
bech32: 'rin', // For SegWit addresses (rin1q...)
|
||||||
|
bip32: {
|
||||||
|
public: 0x019da462, // Litecoin's public key version
|
||||||
|
private: 0x019d9cfe // Litecoin's private key version
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x30, // Litecoin pubkey hash (for legacy addresses)
|
||||||
|
scriptHash: 0x32, // Litecoin script hash
|
||||||
|
wif: 0xb0 // Litecoin WIF
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate SegWit address (rin1q...)
|
||||||
|
const { address } = bitcoin.payments.p2wpkh({
|
||||||
|
pubkey: publicKey,
|
||||||
|
network: rincoinNetwork
|
||||||
|
});
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 User Experience Design
|
||||||
|
|
||||||
|
### Creating New Wallet:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Create RinCoin Wallet │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ [Generate New Wallet] │
|
||||||
|
│ │
|
||||||
|
│ ↓ │
|
||||||
|
│ │
|
||||||
|
│ 🔐 Your Recovery Phrase: │
|
||||||
|
│ ┌───────────────────────────┐ │
|
||||||
|
│ │ witch collapse practice │ │
|
||||||
|
│ │ feed shame open despair │ │
|
||||||
|
│ │ creek road again ice least │ │
|
||||||
|
│ └───────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ⚠️ WRITE THIS DOWN! │
|
||||||
|
│ These 12 words are the ONLY │
|
||||||
|
│ way to recover your wallet. │
|
||||||
|
│ │
|
||||||
|
│ ☐ I have written down my │
|
||||||
|
│ recovery phrase │
|
||||||
|
│ │
|
||||||
|
│ [Continue] ─────────────────→ │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restoring Wallet:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Restore RinCoin Wallet │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Enter your 12-word recovery │
|
||||||
|
│ phrase: │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────┐ │
|
||||||
|
│ │ 1. witch 7. despair │ │
|
||||||
|
│ │ 2. collapse 8. creek │ │
|
||||||
|
│ │ 3. practice 9. road │ │
|
||||||
|
│ │ 4. feed 10. again │ │
|
||||||
|
│ │ 5. shame 11. ice │ │
|
||||||
|
│ │ 6. open 12. least │ │
|
||||||
|
│ └───────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [Restore Wallet] │
|
||||||
|
│ │
|
||||||
|
│ ↓ │
|
||||||
|
│ │
|
||||||
|
│ 🔄 Restoring wallet... │
|
||||||
|
│ • Deriving keys... ✓ │
|
||||||
|
│ • Importing to node... ✓ │
|
||||||
|
│ • Scanning blockchain... ⏳ │
|
||||||
|
│ │
|
||||||
|
│ ✅ Restored! │
|
||||||
|
│ Balance: 1059.00 RIN │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Security Features
|
||||||
|
|
||||||
|
### 1. Mnemonic Only (No Key Storage)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// NEVER store the mnemonic permanently!
|
||||||
|
// Only derive keys when needed
|
||||||
|
|
||||||
|
class SecureWallet {
|
||||||
|
// User enters mnemonic each session
|
||||||
|
async unlockWallet(mnemonic) {
|
||||||
|
this.root = await deriveRootKey(mnemonic);
|
||||||
|
// Keep in memory only
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear on logout
|
||||||
|
lockWallet() {
|
||||||
|
this.root = null;
|
||||||
|
// Clear all key material from memory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive key on-demand
|
||||||
|
async getPrivateKey(index) {
|
||||||
|
if (!this.root) throw new Error('Wallet locked');
|
||||||
|
return this.root.derivePath(`m/44'/2'/0'/0/${index}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Optional Encryption
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For convenience, allow encrypted storage
|
||||||
|
async function saveEncryptedMnemonic(mnemonic, password) {
|
||||||
|
const encrypted = await encrypt(mnemonic, password);
|
||||||
|
localStorage.setItem('encrypted_wallet', encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadEncryptedMnemonic(password) {
|
||||||
|
const encrypted = localStorage.getItem('encrypted_wallet');
|
||||||
|
return await decrypt(encrypted, password);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Complete Implementation Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// rin-web-wallet.js
|
||||||
|
|
||||||
|
import * as bip39 from 'bip39';
|
||||||
|
import { BIP32Factory } from 'bip32';
|
||||||
|
import * as ecc from 'tiny-secp256k1';
|
||||||
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
|
|
||||||
|
const bip32 = BIP32Factory(ecc);
|
||||||
|
|
||||||
|
class RinWebWallet {
|
||||||
|
constructor(rpcUrl, rpcUser, rpcPassword) {
|
||||||
|
this.rpcUrl = rpcUrl;
|
||||||
|
this.rpcUser = rpcUser;
|
||||||
|
this.rpcPassword = rpcPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new wallet with mnemonic
|
||||||
|
async createWallet(walletName) {
|
||||||
|
// Generate mnemonic
|
||||||
|
const mnemonic = bip39.generateMnemonic(128); // 12 words
|
||||||
|
|
||||||
|
// Create wallet on node
|
||||||
|
await this.rpcCall('createwallet', [walletName, false, false]);
|
||||||
|
|
||||||
|
// Import initial addresses
|
||||||
|
await this.importAddressesFromMnemonic(mnemonic, walletName, 20);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
message: 'WRITE DOWN THESE 12 WORDS!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore wallet from mnemonic
|
||||||
|
async restoreWallet(mnemonic, walletName) {
|
||||||
|
// Validate
|
||||||
|
if (!bip39.validateMnemonic(mnemonic)) {
|
||||||
|
throw new Error('Invalid mnemonic');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create wallet on node
|
||||||
|
await this.rpcCall('createwallet', [walletName, false, false]);
|
||||||
|
|
||||||
|
// Import addresses with gap limit
|
||||||
|
await this.importAddressesFromMnemonic(mnemonic, walletName, 100);
|
||||||
|
|
||||||
|
// Rescan blockchain
|
||||||
|
await this.rpcCall('rescanblockchain', [], walletName);
|
||||||
|
|
||||||
|
const balance = await this.rpcCall('getbalance', [], walletName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
balance: balance
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: Import addresses from mnemonic
|
||||||
|
async importAddressesFromMnemonic(mnemonic, walletName, count) {
|
||||||
|
const seed = await bip39.mnemonicToSeed(mnemonic);
|
||||||
|
const root = bip32.fromSeed(seed);
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const child = root.derivePath(`m/44'/2'/0'/0/${i}`);
|
||||||
|
const privateKey = child.toWIF();
|
||||||
|
|
||||||
|
// Import to node
|
||||||
|
await this.rpcCall('importprivkey', [
|
||||||
|
privateKey,
|
||||||
|
`address_${i}`,
|
||||||
|
false // Don't rescan yet
|
||||||
|
], walletName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPC call helper
|
||||||
|
async rpcCall(method, params = [], wallet = null) {
|
||||||
|
const url = wallet ? `${this.rpcUrl}/wallet/${wallet}` : this.rpcUrl;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Basic ' + btoa(`${this.rpcUser}:${this.rpcPassword}`)
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: method,
|
||||||
|
method: method,
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.error) throw new Error(data.error.message);
|
||||||
|
return data.result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const wallet = new RinWebWallet(
|
||||||
|
'http://localhost:9556',
|
||||||
|
'rinrpc',
|
||||||
|
'745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create new
|
||||||
|
const { mnemonic } = await wallet.createWallet('my_wallet');
|
||||||
|
console.log('SAVE THESE WORDS:', mnemonic);
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
await wallet.restoreWallet('witch collapse practice feed shame open despair creek road again ice least', 'restored_wallet');
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Benefits of This Approach
|
||||||
|
|
||||||
|
1. **✅ Standard BIP39** - Compatible with hardware wallets, other software
|
||||||
|
2. **✅ 12-word backup** - Easy to write down, memorize, store
|
||||||
|
3. **✅ Self-custody** - User controls the mnemonic
|
||||||
|
4. **✅ Cross-platform** - Restore on any device
|
||||||
|
5. **✅ Secure** - Industry-standard cryptography
|
||||||
|
6. **✅ User-friendly** - Matches MetaMask/Trust Wallet UX
|
||||||
|
7. **✅ Works with RinCoin** - Uses `importprivkey` for each address
|
||||||
|
|
||||||
|
## 🚀 Next Steps
|
||||||
|
|
||||||
|
1. Implement BIP39/BIP32 support in web wallet
|
||||||
|
2. Test key derivation with RinCoin addresses
|
||||||
|
3. Create beautiful UI for mnemonic generation/restore
|
||||||
|
4. Add encrypted local storage option
|
||||||
|
5. Implement proper gap limit scanning
|
||||||
|
6. Add address book and transaction history
|
||||||
|
|
||||||
|
## 📚 Required Libraries
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install bip39 bip32 tiny-secp256k1 bitcoinjs-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for Python backend:
|
||||||
|
```bash
|
||||||
|
pip install mnemonic bip32utils bitcoinlib
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**YES! You CAN create a secure, self-custody RinCoin wallet with easy mnemonic restore!**
|
||||||
|
|
||||||
|
The key is implementing BIP39 support in your web wallet layer, not relying on RinCoin's limited RPC support. 🎉
|
||||||
260
rin/wallet/cmd/WALLET_RESTORATION_REALITY.md
Normal file
260
rin/wallet/cmd/WALLET_RESTORATION_REALITY.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# RinCoin Wallet Restoration: What Actually Works
|
||||||
|
|
||||||
|
## ❌ The Problem: RinCoin Doesn't Support xprv Key Imports
|
||||||
|
|
||||||
|
After extensive testing, we've discovered that **RinCoin does NOT support direct xprv extended private key imports**.
|
||||||
|
|
||||||
|
### What We Tried (All Failed):
|
||||||
|
- ✗ `sethdseed` with xprv key → "Invalid private key"
|
||||||
|
- ✗ `importprivkey` with xprv key → "Invalid private key encoding"
|
||||||
|
- ✗ Direct HD seed restoration → Not supported
|
||||||
|
|
||||||
|
### Why This Doesn't Work:
|
||||||
|
RinCoin, despite being based on Litecoin, appears to have limited RPC support for:
|
||||||
|
- BIP39 mnemonic seed phrases
|
||||||
|
- Extended private key (xprv) imports
|
||||||
|
- HD wallet seed restoration via RPC
|
||||||
|
|
||||||
|
## ✅ What DOES Work: Full Wallet Dump Restoration
|
||||||
|
|
||||||
|
The **ONLY reliable method** is using the complete wallet dump file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./restore_wallet.sh /home/db/rin_wallet_backups/rin_wallet_backup_20250929_221522.txt restored_wallet
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why This Works:
|
||||||
|
- Uses `importwallet` RPC method (widely supported)
|
||||||
|
- Imports ALL individual private keys from the dump
|
||||||
|
- Blockchain rescans and finds all transactions
|
||||||
|
- **Balance is restored: 1059.00155276 RIN ✓**
|
||||||
|
|
||||||
|
### The Address Issue:
|
||||||
|
The restored wallet shows **different addresses** but **same balance**:
|
||||||
|
- Original: `rin1q...` (SegWit addresses)
|
||||||
|
- Restored: `R...` (Legacy P2PKH addresses)
|
||||||
|
|
||||||
|
**Why?** The wallet dump contains individual private keys without HD structure, so `importwallet` creates a legacy wallet.
|
||||||
|
|
||||||
|
## 🎯 Practical Solutions for User-Friendly Wallets
|
||||||
|
|
||||||
|
Since direct key import doesn't work, here are realistic approaches:
|
||||||
|
|
||||||
|
### Option 1: Wallet File Backup/Restore (BEST)
|
||||||
|
|
||||||
|
**For same device or direct file transfer:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup
|
||||||
|
tar -czf ~/rin_wallet_backup.tar.gz /mnt/data/docker_vol/rincoin/rincoin-node/data/main/
|
||||||
|
|
||||||
|
# Restore
|
||||||
|
tar -xzf ~/rin_wallet_backup.tar.gz -C /new/path/data/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- ✅ Perfect restoration - same addresses, same structure
|
||||||
|
- ✅ Fast - no blockchain rescan needed
|
||||||
|
- ✅ Works 100% reliably
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- ✗ Requires file transfer (not just a text string)
|
||||||
|
- ✗ User must handle binary wallet files
|
||||||
|
|
||||||
|
### Option 2: Full Dump File Restoration (CURRENT)
|
||||||
|
|
||||||
|
**For text-based backup:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup
|
||||||
|
./dump_wallet.sh # Creates text file with all keys
|
||||||
|
|
||||||
|
# Restore
|
||||||
|
./restore_wallet.sh /path/to/backup.txt restored_wallet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- ✅ Text file - easier to store/transfer
|
||||||
|
- ✅ Works reliably
|
||||||
|
- ✅ Balance fully restored
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- ✗ Changes address format (rin1q... → R...)
|
||||||
|
- ✗ Large file (~6000 lines for active wallet)
|
||||||
|
- ✗ Slower (requires blockchain rescan)
|
||||||
|
|
||||||
|
### Option 3: Web Wallet with Server-Side Management (RECOMMENDED)
|
||||||
|
|
||||||
|
**For browser extension/web wallet:**
|
||||||
|
|
||||||
|
Instead of user managing keys directly, implement server-side wallet management:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// User creates wallet
|
||||||
|
const walletId = await createWallet(username, password);
|
||||||
|
// Server stores encrypted wallet file
|
||||||
|
|
||||||
|
// User restores wallet
|
||||||
|
const wallet = await restoreWallet(username, password);
|
||||||
|
// Server loads encrypted wallet file
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- ✅ User-friendly - just username/password
|
||||||
|
- ✅ No key management complexity
|
||||||
|
- ✅ Perfect restoration every time
|
||||||
|
- ✅ Can sync across devices
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- ✗ Requires trust in server
|
||||||
|
- ✗ Need secure server infrastructure
|
||||||
|
|
||||||
|
### Option 4: Hybrid Approach (BALANCED)
|
||||||
|
|
||||||
|
**Combine wallet file + optional manual backup:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Primary: Encrypted wallet file stored locally/cloud
|
||||||
|
saveEncryptedWallet(walletFile, userPassword);
|
||||||
|
|
||||||
|
// Secondary: Export full dump for disaster recovery
|
||||||
|
exportFullDump(); // User downloads text file "just in case"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- ✅ User-friendly primary method (file sync)
|
||||||
|
- ✅ Manual backup option for advanced users
|
||||||
|
- ✅ Best of both worlds
|
||||||
|
|
||||||
|
## 🔧 Technical Implementation for Web Wallet
|
||||||
|
|
||||||
|
### Current Reality Check:
|
||||||
|
```javascript
|
||||||
|
// ❌ This WON'T work (RinCoin doesn't support it):
|
||||||
|
async function restoreFromMnemonic(mnemonic) {
|
||||||
|
await rpc('sethdseed', [true, mnemonic]); // FAILS
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ This WON'T work either:
|
||||||
|
async function restoreFromXprv(xprv) {
|
||||||
|
await rpc('importprivkey', [xprv]); // FAILS
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ This DOES work:
|
||||||
|
async function restoreFromDumpFile(dumpFilePath) {
|
||||||
|
await rpc('createwallet', [walletName, false, false]);
|
||||||
|
await rpc('importwallet', [dumpFilePath]); // WORKS!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Web Wallet Architecture:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class RinWebWallet {
|
||||||
|
// Primary method: Wallet file management
|
||||||
|
async backupWallet() {
|
||||||
|
// Get wallet directory from node
|
||||||
|
const walletDir = await getWalletDirectory();
|
||||||
|
// Create encrypted archive
|
||||||
|
const encrypted = await encryptWalletFiles(walletDir, userPassword);
|
||||||
|
// Store locally/cloud
|
||||||
|
await saveBackup(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreWallet(encryptedBackup, password) {
|
||||||
|
// Decrypt backup
|
||||||
|
const walletFiles = await decrypt(encryptedBackup, password);
|
||||||
|
// Restore to node's wallet directory
|
||||||
|
await restoreWalletFiles(walletFiles);
|
||||||
|
// Load wallet
|
||||||
|
await rpc('loadwallet', [walletName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary method: Dump file for advanced users
|
||||||
|
async exportDumpFile() {
|
||||||
|
const dumpFile = await rpc('dumpwallet', ['/tmp/backup.txt']);
|
||||||
|
return downloadFile(dumpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
async importDumpFile(dumpFilePath) {
|
||||||
|
await rpc('createwallet', [walletName]);
|
||||||
|
await rpc('importwallet', [dumpFilePath]);
|
||||||
|
// Note: Addresses will be different format but balance same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 User Experience Design
|
||||||
|
|
||||||
|
### For Browser Extension:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ RinCoin Wallet │
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ [Create New Wallet] │
|
||||||
|
│ │
|
||||||
|
│ [Restore from Backup] │
|
||||||
|
│ ↓ │
|
||||||
|
│ • Upload wallet file │ ← Primary method
|
||||||
|
│ • Import dump file │ ← Fallback method
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Flow:
|
||||||
|
```
|
||||||
|
1. User clicks "Backup Wallet"
|
||||||
|
2. Extension prompts for password
|
||||||
|
3. Creates encrypted wallet file
|
||||||
|
4. Downloads: "rincoin-wallet-backup-2025-09-29.enc"
|
||||||
|
5. Shows: "✓ Backup created! Store this file safely."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore Flow:
|
||||||
|
```
|
||||||
|
1. User clicks "Restore Wallet"
|
||||||
|
2. User uploads "rincoin-wallet-backup-2025-09-29.enc"
|
||||||
|
3. Extension prompts for backup password
|
||||||
|
4. Restores wallet files to node
|
||||||
|
5. Shows: "✓ Wallet restored! Balance: 1059.00 RIN"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Recommendations
|
||||||
|
|
||||||
|
### For MVP (Minimum Viable Product):
|
||||||
|
1. **Use wallet file backup/restore** - Most reliable
|
||||||
|
2. **Encrypt with user password** - Security
|
||||||
|
3. **Store locally in browser storage** - Simple start
|
||||||
|
4. **Add cloud sync later** - Future enhancement
|
||||||
|
|
||||||
|
### For Production:
|
||||||
|
1. **Primary: Encrypted wallet file sync**
|
||||||
|
2. **Secondary: Optional dump file export**
|
||||||
|
3. **Security: End-to-end encryption**
|
||||||
|
4. **UX: Hide complexity from users**
|
||||||
|
|
||||||
|
## ⚠️ Important Disclaimers
|
||||||
|
|
||||||
|
### For Users:
|
||||||
|
- The xprv key in your dump file **cannot be used** for quick restoration
|
||||||
|
- You **must use the full dump file** or wallet directory
|
||||||
|
- Different restoration methods may show **different addresses** (but same balance)
|
||||||
|
|
||||||
|
### For Developers:
|
||||||
|
- RinCoin's RPC API has **limited HD wallet support**
|
||||||
|
- Extended key imports (xprv/xpub) are **not supported**
|
||||||
|
- BIP39 mnemonic restoration is **not available via RPC**
|
||||||
|
- The only reliable method is **`importwallet` with full dump**
|
||||||
|
|
||||||
|
## 🚀 Moving Forward
|
||||||
|
|
||||||
|
For a user-friendly RinCoin wallet/browser extension, **don't try to mimic MetaMask's 12-word restore**. Instead:
|
||||||
|
|
||||||
|
1. **Accept the limitation**: RinCoin doesn't support simple key restoration
|
||||||
|
2. **Design around it**: Use wallet file backups
|
||||||
|
3. **Make it simple**: Hide the complexity with good UX
|
||||||
|
4. **Be honest**: Tell users they need to backup their wallet file
|
||||||
|
|
||||||
|
The technology constraint is real, but good UX design can still make it user-friendly! 🎨
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# RinCoin Wallet Restoration from Master Seed Script
|
|
||||||
# Restores a wallet from just the master private key (xprv...)
|
|
||||||
# Usage: ./restore_from_seed.sh "xprv9s21ZrQH143K3bjynHVk6hBTZLmV9wjqWScL3UyENBYK6RaFo75zu5jnWQtBi932zKbD7c2WARWLJNjBbE3Td2Cc44ym3dmp343qKKFXwxS" [wallet_name]
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
if [[ $# -lt 1 ]] || [[ $# -gt 2 ]]; then
|
|
||||||
echo "Usage: $0 \"<master_private_key>\" [wallet_name]"
|
|
||||||
echo "Example: $0 \"xprv9s21ZrQH143K3bjynHVk6hBTZLmV9wjqWScL3UyENBYK6RaFo75zu5jnWQtBi932zKbD7c2WARWLJNjBbE3Td2Cc44ym3dmp343qKKFXwxS\""
|
|
||||||
echo "Example: $0 \"xprv9s21ZrQH143K3bjynHVk6hBTZLmV9wjqWScL3UyENBYK6RaFo75zu5jnWQtBi932zKbD7c2WARWLJNjBbE3Td2Cc44ym3dmp343qKKFXwxS\" my_restored_wallet"
|
|
||||||
echo ""
|
|
||||||
echo "To get the master key from your backup file:"
|
|
||||||
echo "grep 'extended private masterkey' ~/rin_wallet_backups/rin_wallet_backup_*.txt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MASTER_KEY="$1"
|
|
||||||
if [[ $# -eq 2 ]]; then
|
|
||||||
WALLET_NAME="$2"
|
|
||||||
else
|
|
||||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
||||||
WALLET_NAME="restored_from_seed_${TIMESTAMP}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# RPC Configuration
|
|
||||||
RPC_USER="rinrpc"
|
|
||||||
RPC_PASSWORD="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
|
|
||||||
RPC_HOST="localhost"
|
|
||||||
RPC_PORT="9556"
|
|
||||||
|
|
||||||
# Validate master key format
|
|
||||||
if [[ ! "$MASTER_KEY" =~ ^xprv[a-zA-Z0-9]+ ]]; then
|
|
||||||
echo "Error: Invalid master key format. Must start with 'xprv'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if RIN node is running
|
|
||||||
if ! pgrep -f "rincoind" > /dev/null; then
|
|
||||||
echo "Error: RinCoin daemon is not running. Start it first."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Checking if wallet '$WALLET_NAME' already exists..."
|
|
||||||
|
|
||||||
# First try to load existing wallet
|
|
||||||
LOAD_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "loadwallet", "method": "loadwallet", "params": ["'$WALLET_NAME'"]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT")
|
|
||||||
|
|
||||||
echo "Load response: $LOAD_RESPONSE"
|
|
||||||
|
|
||||||
if echo "$LOAD_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ Existing wallet '$WALLET_NAME' loaded successfully"
|
|
||||||
else
|
|
||||||
echo "Creating new wallet for seed restoration..."
|
|
||||||
|
|
||||||
# Create a new HD wallet (same format as main wallet)
|
|
||||||
CREATE_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "createwallet", "method": "createwallet", "params": ["'$WALLET_NAME'", false, false, "", false, false, true]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT")
|
|
||||||
|
|
||||||
if echo "$CREATE_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ New wallet '$WALLET_NAME' created successfully"
|
|
||||||
else
|
|
||||||
echo "Error creating wallet: $CREATE_RESPONSE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Setting HD seed from master private key..."
|
|
||||||
|
|
||||||
# First, try to import the master key directly
|
|
||||||
IMPORT_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "importprivkey", "method": "importprivkey", "params": ["'$MASTER_KEY'", "", false]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME")
|
|
||||||
|
|
||||||
if echo "$IMPORT_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ Master key imported successfully"
|
|
||||||
else
|
|
||||||
echo "Import response: $IMPORT_RESPONSE"
|
|
||||||
|
|
||||||
# Alternative: Try sethdseed with newkeypool=true (flush old keys and set new seed)
|
|
||||||
SEED_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "sethdseed", "method": "sethdseed", "params": [true, "'$MASTER_KEY'"]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME")
|
|
||||||
|
|
||||||
if echo "$SEED_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ HD seed set successfully"
|
|
||||||
else
|
|
||||||
echo "Error setting HD seed: $SEED_RESPONSE"
|
|
||||||
echo "Note: The master key format might not be compatible with this method"
|
|
||||||
echo "Try using the full wallet dump restoration instead"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Rescan the blockchain to find existing transactions
|
|
||||||
echo "Rescanning blockchain to find existing transactions..."
|
|
||||||
RESCAN_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "rescanblockchain", "method": "rescanblockchain", "params": []}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME")
|
|
||||||
|
|
||||||
if echo "$RESCAN_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ Blockchain rescan completed"
|
|
||||||
else
|
|
||||||
echo "Warning: Blockchain rescan failed or is still in progress: $RESCAN_RESPONSE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating addresses from seed..."
|
|
||||||
|
|
||||||
# Generate some addresses to populate the wallet
|
|
||||||
for i in {1..10}; do
|
|
||||||
ADDRESS_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "getnewaddress", "method": "getnewaddress", "params": []}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME")
|
|
||||||
|
|
||||||
if echo "$ADDRESS_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo " Generated address $i"
|
|
||||||
else
|
|
||||||
echo "Warning: Failed to generate address $i"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ Wallet restored from master key successfully!"
|
|
||||||
echo "Wallet name: $WALLET_NAME"
|
|
||||||
echo ""
|
|
||||||
echo "Important notes:"
|
|
||||||
echo "- The wallet has been restored and blockchain rescan has been initiated"
|
|
||||||
echo "- It may take several minutes for the rescan to complete and funds to appear"
|
|
||||||
echo "- Monitor the node logs for rescan progress"
|
|
||||||
echo "- Run: ./rin/wallet/cmd/check_balance.sh to check current balance"
|
|
||||||
echo ""
|
|
||||||
echo "To manually trigger rescan again (if needed):"
|
|
||||||
echo "curl -s -u \"$RPC_USER:$RPC_PASSWORD\" -H \"Content-Type: application/json\" -d '{\"jsonrpc\": \"2.0\", \"id\": \"rescanblockchain\", \"method\": \"rescanblockchain\", \"params\": []}' \"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME\""
|
|
||||||
echo ""
|
|
||||||
echo "Check balance with:"
|
|
||||||
echo "curl -s -u \"$RPC_USER:$RPC_PASSWORD\" -H \"Content-Type: application/json\" -d '{\"jsonrpc\": \"2.0\", \"id\": \"getbalance\", \"method\": \"getbalance\", \"params\": []}' \"http://$RPC_HOST:$RPC_PORT/wallet/$WALLET_NAME\""
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# RinCoin Wallet Restoration Script
|
|
||||||
# Restores a wallet from a dump file on a new RinCoin node.
|
|
||||||
# Prerequisites: RinCoin node running, backup file available.
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
if [[ $# -lt 1 ]] || [[ $# -gt 2 ]]; then
|
|
||||||
echo "Usage: $0 <path_to_backup_file> [wallet_name]"
|
|
||||||
echo "Example: $0 ~/rin_wallet_backups/rin_wallet_backup_20230923.txt"
|
|
||||||
echo "Example: $0 ~/rin_wallet_backups/rin_wallet_backup_20230923.txt my_restored_wallet"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BACKUP_FILE="$1"
|
|
||||||
NEW_WALLET_NAME="${2:-restored_main}" # Use provided name or default
|
|
||||||
|
|
||||||
# RPC Configuration
|
|
||||||
RPC_USER="rinrpc"
|
|
||||||
RPC_PASSWORD="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
|
|
||||||
RPC_HOST="localhost"
|
|
||||||
RPC_PORT="9556"
|
|
||||||
|
|
||||||
# Verify backup file exists
|
|
||||||
if [[ ! -f "$BACKUP_FILE" ]]; then
|
|
||||||
echo "Error: Backup file '$BACKUP_FILE' not found."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if RIN node is running
|
|
||||||
if ! pgrep -f "rincoind" > /dev/null; then
|
|
||||||
echo "Error: RinCoin daemon is not running. Start it first."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy backup file to daemon's data directory
|
|
||||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
||||||
DAEMON_BACKUP_FILE="/data/restore_backup_${TIMESTAMP}.txt"
|
|
||||||
SYSTEM_BACKUP_FILE="/mnt/data/docker_vol/rincoin/rincoin-node/data/restore_backup_${TIMESTAMP}.txt"
|
|
||||||
|
|
||||||
echo "Copying backup file to daemon directory..."
|
|
||||||
cp "$BACKUP_FILE" "$SYSTEM_BACKUP_FILE"
|
|
||||||
|
|
||||||
echo "Backup file copied to: $DAEMON_BACKUP_FILE"
|
|
||||||
|
|
||||||
echo "Creating new wallet and importing keys..."
|
|
||||||
|
|
||||||
# Create a new wallet to avoid conflicts (match original wallet settings)
|
|
||||||
CREATE_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "createwallet", "method": "createwallet", "params": ["'$NEW_WALLET_NAME'", false, true, "", false, false, true]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT")
|
|
||||||
|
|
||||||
if echo "$CREATE_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ New wallet '$NEW_WALLET_NAME' created successfully"
|
|
||||||
else
|
|
||||||
echo "Error creating wallet: $CREATE_RESPONSE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Import the dump file
|
|
||||||
echo "Importing wallet from backup file..."
|
|
||||||
IMPORT_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"jsonrpc": "2.0", "id": "importwallet", "method": "importwallet", "params": ["'$DAEMON_BACKUP_FILE'"]}' \
|
|
||||||
"http://$RPC_HOST:$RPC_PORT/wallet/$NEW_WALLET_NAME")
|
|
||||||
|
|
||||||
if echo "$IMPORT_RESPONSE" | grep -q '"error":null'; then
|
|
||||||
echo "✓ Wallet imported successfully"
|
|
||||||
else
|
|
||||||
echo "Error importing wallet: $IMPORT_RESPONSE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
echo "Cleaning up temporary files..."
|
|
||||||
rm -f "$SYSTEM_BACKUP_FILE"
|
|
||||||
|
|
||||||
echo "✅ Wallet restored successfully!"
|
|
||||||
echo "New wallet name: $NEW_WALLET_NAME"
|
|
||||||
echo ""
|
|
||||||
echo "Verify with:"
|
|
||||||
echo "curl -s -u \"$RPC_USER:$RPC_PASSWORD\" -H \"Content-Type: application/json\" -d '{\"jsonrpc\": \"2.0\", \"id\": \"getbalance\", \"method\": \"getbalance\", \"params\": []}' \"http://$RPC_HOST:$RPC_PORT/wallet/$NEW_WALLET_NAME\""
|
|
||||||
echo ""
|
|
||||||
echo "Or check balance with:"
|
|
||||||
echo "./rin/wallet/cmd/check_balance.sh"
|
|
||||||
echo ""
|
|
||||||
echo "To use this wallet as default, update scripts to use wallet name: $NEW_WALLET_NAME"
|
|
||||||
|
|
||||||
178
rin/wallet/cmd/restore_wallet_file.sh
Normal file
178
rin/wallet/cmd/restore_wallet_file.sh
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# RinCoin Wallet File Restoration Script
|
||||||
|
# This is the RECOMMENDED restoration method for user-friendly wallets!
|
||||||
|
# Restores a wallet from a complete wallet directory backup
|
||||||
|
#
|
||||||
|
# This preserves:
|
||||||
|
# - All addresses (EXACT format - rin1q...)
|
||||||
|
# - HD wallet structure
|
||||||
|
# - Transaction history
|
||||||
|
# - All wallet metadata
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -lt 1 ]] || [[ $# -gt 2 ]]; then
|
||||||
|
echo "Usage: $0 <backup_file.tar.gz> [new_wallet_name]"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 ~/rin_wallet_file_backup_main_20250929.tar.gz"
|
||||||
|
echo " $0 ~/rin_wallet_file_backup_main_20250929.tar.gz restored_main"
|
||||||
|
echo ""
|
||||||
|
echo "This restores a wallet from a complete wallet directory backup."
|
||||||
|
echo "The wallet will have IDENTICAL addresses to the original."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$1"
|
||||||
|
NEW_WALLET_NAME="${2:-}"
|
||||||
|
|
||||||
|
# Wallet paths
|
||||||
|
DATA_DIR="/mnt/data/docker_vol/rincoin/rincoin-node/data"
|
||||||
|
|
||||||
|
# RPC Configuration
|
||||||
|
RPC_USER="rinrpc"
|
||||||
|
RPC_PASSWORD="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
|
||||||
|
RPC_HOST="localhost"
|
||||||
|
RPC_PORT="9556"
|
||||||
|
|
||||||
|
echo "RinCoin Wallet File Restoration"
|
||||||
|
echo "================================"
|
||||||
|
echo "Backup file: $BACKUP_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if backup file exists
|
||||||
|
if [[ ! -f "$BACKUP_FILE" ]]; then
|
||||||
|
echo "Error: Backup file not found: $BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if RIN node is running
|
||||||
|
if ! pgrep -f "rincoind" > /dev/null; then
|
||||||
|
echo "Error: RinCoin daemon is not running. Start it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract wallet name from archive
|
||||||
|
echo "Examining backup file..."
|
||||||
|
ORIGINAL_WALLET_NAME=$(tar -tzf "$BACKUP_FILE" | head -1 | cut -d/ -f1)
|
||||||
|
|
||||||
|
if [[ -z "$ORIGINAL_WALLET_NAME" ]]; then
|
||||||
|
echo "Error: Could not determine wallet name from backup file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Original wallet name: $ORIGINAL_WALLET_NAME"
|
||||||
|
|
||||||
|
# Determine target wallet name
|
||||||
|
if [[ -z "$NEW_WALLET_NAME" ]]; then
|
||||||
|
NEW_WALLET_NAME="$ORIGINAL_WALLET_NAME"
|
||||||
|
echo "Target wallet name: $NEW_WALLET_NAME (same as original)"
|
||||||
|
else
|
||||||
|
echo "Target wallet name: $NEW_WALLET_NAME (renamed)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_WALLET_DIR="$DATA_DIR/$NEW_WALLET_NAME"
|
||||||
|
|
||||||
|
# Check if target already exists
|
||||||
|
if [[ -d "$TARGET_WALLET_DIR" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Warning: Wallet '$NEW_WALLET_NAME' already exists at: $TARGET_WALLET_DIR"
|
||||||
|
read -p "Do you want to overwrite it? (yes/no): " CONFIRM
|
||||||
|
if [[ "$CONFIRM" != "yes" ]]; then
|
||||||
|
echo "Restoration cancelled."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to unload if loaded
|
||||||
|
echo "Unloading existing wallet..."
|
||||||
|
curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc": "2.0", "id": "unloadwallet", "method": "unloadwallet", "params": ["'$NEW_WALLET_NAME'"]}' \
|
||||||
|
"http://$RPC_HOST:$RPC_PORT" > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Remove existing
|
||||||
|
echo "Removing existing wallet directory..."
|
||||||
|
rm -rf "$TARGET_WALLET_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract backup
|
||||||
|
echo ""
|
||||||
|
echo "Extracting backup..."
|
||||||
|
cd "$DATA_DIR"
|
||||||
|
tar -xzf "$BACKUP_FILE"
|
||||||
|
|
||||||
|
# Rename if necessary
|
||||||
|
if [[ "$ORIGINAL_WALLET_NAME" != "$NEW_WALLET_NAME" ]]; then
|
||||||
|
mv "$ORIGINAL_WALLET_NAME" "$NEW_WALLET_NAME"
|
||||||
|
echo "✓ Wallet renamed from '$ORIGINAL_WALLET_NAME' to '$NEW_WALLET_NAME'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify extraction
|
||||||
|
if [[ ! -d "$TARGET_WALLET_DIR" ]]; then
|
||||||
|
echo "❌ Error: Wallet directory was not created"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Wallet files extracted successfully"
|
||||||
|
|
||||||
|
# Load wallet
|
||||||
|
echo ""
|
||||||
|
echo "Loading wallet into RinCoin node..."
|
||||||
|
LOAD_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc": "2.0", "id": "loadwallet", "method": "loadwallet", "params": ["'$NEW_WALLET_NAME'"]}' \
|
||||||
|
"http://$RPC_HOST:$RPC_PORT")
|
||||||
|
|
||||||
|
if echo "$LOAD_RESPONSE" | grep -q '"error":null'; then
|
||||||
|
echo "✓ Wallet loaded successfully"
|
||||||
|
else
|
||||||
|
echo "Error loading wallet: $LOAD_RESPONSE"
|
||||||
|
echo ""
|
||||||
|
echo "The wallet files are restored, but RinCoin couldn't load them."
|
||||||
|
echo "You may need to restart the RinCoin daemon."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get wallet info
|
||||||
|
echo ""
|
||||||
|
echo "Verifying wallet..."
|
||||||
|
BALANCE_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc": "2.0", "id": "getbalance", "method": "getbalance", "params": []}' \
|
||||||
|
"http://$RPC_HOST:$RPC_PORT/wallet/$NEW_WALLET_NAME")
|
||||||
|
|
||||||
|
BALANCE=$(echo "$BALANCE_RESPONSE" | grep -o '"result":[0-9.]*' | cut -d: -f2)
|
||||||
|
|
||||||
|
echo "Current balance: $BALANCE RIN"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show addresses for verification
|
||||||
|
echo "Restored addresses:"
|
||||||
|
ADDRESSES_RESPONSE=$(curl -s -u "$RPC_USER:$RPC_PASSWORD" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc": "2.0", "id": "listreceivedbyaddress", "method": "listreceivedbyaddress", "params": [0, true, true]}' \
|
||||||
|
"http://$RPC_HOST:$RPC_PORT/wallet/$NEW_WALLET_NAME")
|
||||||
|
|
||||||
|
echo "$ADDRESSES_RESPONSE" | grep -o '"address":"[^"]*"' | cut -d'"' -f4 | head -5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Wallet restored successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Restoration Summary:"
|
||||||
|
echo " Wallet name: $NEW_WALLET_NAME"
|
||||||
|
echo " Balance: $BALANCE RIN"
|
||||||
|
echo " Status: Ready to use"
|
||||||
|
echo ""
|
||||||
|
echo "💡 IMPORTANT:"
|
||||||
|
echo " - All addresses are IDENTICAL to the original wallet"
|
||||||
|
echo " - Transaction history is fully preserved"
|
||||||
|
echo " - No blockchain rescan needed"
|
||||||
|
echo " - This is the PERFECT restoration method!"
|
||||||
|
echo ""
|
||||||
|
echo "🔧 Commands:"
|
||||||
|
echo " Check balance:"
|
||||||
|
echo " curl -s -u \"$RPC_USER:$RPC_PASSWORD\" -d '{\"jsonrpc\": \"2.0\", \"id\": \"getbalance\", \"method\": \"getbalance\"}' \"http://$RPC_HOST:$RPC_PORT/wallet/$NEW_WALLET_NAME\""
|
||||||
|
echo ""
|
||||||
|
echo " List wallets:"
|
||||||
|
echo " ./list_wallets.sh"
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Simple seed restoration
|
|
||||||
MASTER_KEY="$1"
|
|
||||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
||||||
WALLET_NAME="seed_restore_${TIMESTAMP}"
|
|
||||||
|
|
||||||
echo "Creating wallet: $WALLET_NAME"
|
|
||||||
echo "Master key: $MASTER_KEY"
|
|
||||||
|
|
||||||
curl -s -u "rinrpc:745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"jsonrpc\": \"2.0\", \"id\": \"createwallet\", \"method\": \"createwallet\", \"params\": [\"$WALLET_NAME\", false, true, \"\", false, false, true]}" \
|
|
||||||
"http://localhost:9556"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Setting seed..."
|
|
||||||
|
|
||||||
curl -s -u "rinrpc:745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"jsonrpc\": \"2.0\", \"id\": \"sethdseed\", \"method\": \"sethdseed\", \"params\": [true, \"$MASTER_KEY\"]}" \
|
|
||||||
"http://localhost:9556/wallet/$WALLET_NAME"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Done! Wallet name: $WALLET_NAME"
|
|
||||||
Reference in New Issue
Block a user