Workflow updating files of avaxwallet
This commit is contained in:
parent
468e72880f
commit
602075ab8e
249
avaxwallet/README.md
Normal file
249
avaxwallet/README.md
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# Avax Web Wallet - Technical Documentation
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Avalanche Multi-Chain Wallet is a web-based cryptocurrency wallet that supports multiple blockchain networks including Avalanche (AVAX), FLO, and Bitcoin (BTC). The wallet provides comprehensive functionality for address generation, transaction management, balance checking, and transaction history viewing.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- **Multi-Chain Support**: AVAX, FLO, and BTC address generation from a single private key
|
||||||
|
- **Transaction History**: Paginated transaction viewing with smart caching
|
||||||
|
- **Address Search**: Persistent search history with IndexedDB storage
|
||||||
|
- **URL Sharing**: Direct link sharing for addresses and transaction hashes
|
||||||
|
- **Real-Time Data**: Live balance updates and transaction status checking
|
||||||
|
- **Responsive Design**: Mobile-first responsive interface
|
||||||
|
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### System Architecture
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ Frontend Layer │
|
||||||
|
├────────────────────────────────────────────────────────────┤
|
||||||
|
│ index.html │ style.css │ JavaScript Modules │
|
||||||
|
├──────────────┼─────────────┼───────────────────────────────┤
|
||||||
|
│ │ │ • avaxCrypto.js │
|
||||||
|
│ │ │ • avaxBlockchainAPI.js │
|
||||||
|
│ │ │ • avaxSearchDB.js │
|
||||||
|
│ │ │ • lib.avax.js │
|
||||||
|
└──────────────┴─────────────┴───────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Storage Layer │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ IndexedDB │ LocalStorage │ Session Storage │
|
||||||
|
│ • Address History │ • Theme Prefs │ • Temp Data │
|
||||||
|
│ • Search Cache │ • User Settings │ • Form State │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Blockchain Layer │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ AVAX C-Chain │ FLO Network │ Bitcoin Network │
|
||||||
|
│ • RPC Endpoints │ • Address Gen │ • Address Gen │
|
||||||
|
│ • Transaction │ • Key Derivation│ • Key Derivation │
|
||||||
|
│ • Balance Query │ │ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### 1. Cryptographic Engine (`avaxCrypto.js`)
|
||||||
|
|
||||||
|
The cryptographic engine handles multi-chain address generation and key management.
|
||||||
|
|
||||||
|
#### Key Functions
|
||||||
|
```javascript
|
||||||
|
// Generate multi-chain addresses from private key
|
||||||
|
async generateMultiChain(privateKey = null)
|
||||||
|
|
||||||
|
// Create new random wallet
|
||||||
|
generateNewID()
|
||||||
|
|
||||||
|
// Hash generation utilities
|
||||||
|
hashID(str)
|
||||||
|
tmpID()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Supported Private Key Formats
|
||||||
|
- **AVAX**: 64-character hexadecimal
|
||||||
|
- **FLO**: Base58 format starting with 'R'
|
||||||
|
- **BTC**: WIF format starting with 'K', or 'L'
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Blockchain API Layer (`avaxBlockchainAPI.js`)
|
||||||
|
|
||||||
|
Handles all blockchain interactions and RPC communications.
|
||||||
|
|
||||||
|
#### Core Functions
|
||||||
|
```javascript
|
||||||
|
// Balance retrieval
|
||||||
|
async getBalanceRPC(address)
|
||||||
|
|
||||||
|
// Transaction history with pagination
|
||||||
|
async fetchAvalancheTxHistory(address, page, limit)
|
||||||
|
|
||||||
|
// Transaction preparation for sending
|
||||||
|
async prepareAvalancheTransaction(privateKey, to, amount)
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
weiToAvax(weiAmount)
|
||||||
|
formatTransactionTime(timestamp)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### RPC Configuration
|
||||||
|
```javascript
|
||||||
|
const RPC_ENDPOINTS = "https://go.getblock.io/.../ext/bc/C/rpc";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Data Persistence (`avaxSearchDB.js`)
|
||||||
|
|
||||||
|
IndexedDB wrapper for persistent storage of searched addresses and metadata.
|
||||||
|
|
||||||
|
#### Database Schema
|
||||||
|
```sql
|
||||||
|
-- Object Store: searchedAddresses
|
||||||
|
{
|
||||||
|
address: string (Primary Key),
|
||||||
|
balance: number,
|
||||||
|
timestamp: number (Indexed),
|
||||||
|
sourceInfo: {
|
||||||
|
originalPrivateKey: string,
|
||||||
|
originalAddress: string,
|
||||||
|
blockchain: string,
|
||||||
|
derivedAvaxAddress: string
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API Methods
|
||||||
|
```javascript
|
||||||
|
class SearchedAddressDB {
|
||||||
|
async init()
|
||||||
|
async saveSearchedAddress(address, balance, timestamp, sourceInfo)
|
||||||
|
async getSearchedAddresses()
|
||||||
|
async deleteSearchedAddress(address)
|
||||||
|
async clearAllSearchedAddresses()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Wallet Generation
|
||||||
|
|
||||||
|
#### `generateWallet()`
|
||||||
|
Generates a new multi-chain wallet with random private keys.
|
||||||
|
|
||||||
|
**Returns:** Promise resolving to wallet object
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
AVAX: { address: string, privateKey: string },
|
||||||
|
FLO: { address: string, privateKey: string },
|
||||||
|
BTC: { address: string, privateKey: string }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Address Recovery
|
||||||
|
|
||||||
|
#### `recoverWallet()`
|
||||||
|
Recovers wallet addresses from an existing private key.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `privateKey` (string): Valid AVAX/FLO/BTC private key
|
||||||
|
|
||||||
|
**Returns:** Promise resolving to wallet object (same structure as generateWallet)
|
||||||
|
|
||||||
|
### Transaction Management
|
||||||
|
|
||||||
|
#### `loadTransactions()`
|
||||||
|
Loads transaction history for a given address with smart pagination.
|
||||||
|
|
||||||
|
**Process Flow:**
|
||||||
|
1. Input validation (address/private key)
|
||||||
|
2. Address derivation (if private key provided)
|
||||||
|
3. Balance retrieval
|
||||||
|
4. Transaction history fetching (50 transactions initially)
|
||||||
|
5. Pagination setup (3-5 pages based on transaction count)
|
||||||
|
6. UI updates and data persistence
|
||||||
|
|
||||||
|
#### `sendTransaction()`
|
||||||
|
Prepares and broadcasts a transaction to the Avalanche network.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `privateKey` (string): Sender's private key
|
||||||
|
- `recipientAddress` (string): Recipient's AVAX address
|
||||||
|
- `amount` (string): Amount in AVAX
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
```
|
||||||
|
Input Validation → Gas Estimation → User Confirmation → Transaction Signing → Broadcast
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search Functionality
|
||||||
|
|
||||||
|
#### `handleSearch()`
|
||||||
|
Unified search handler supporting both address and transaction hash lookup.
|
||||||
|
|
||||||
|
**Search Types:**
|
||||||
|
- `address`: Loads balance and transaction history
|
||||||
|
- `hash`: Retrieves transaction details from blockchain
|
||||||
|
|
||||||
|
#### URL Parameter Support
|
||||||
|
- `?address=0x...` - Direct address loading
|
||||||
|
- `?hash=0x...` - Direct transaction hash loading
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
### Private Key Handling
|
||||||
|
- **No Storage**: Private keys are never stored in any form
|
||||||
|
- **Memory Clearing**: Variables containing keys are nullified after use
|
||||||
|
- **Input Validation**: Strict format validation before processing
|
||||||
|
- **Error Handling**: Secure error messages without key exposure
|
||||||
|
|
||||||
|
### URL Security
|
||||||
|
- **Address-Only URLs**: Only public addresses included in shareable URLs
|
||||||
|
- **No Private Data**: Private keys never included in URL parameters
|
||||||
|
- **State Management**: Secure browser history handling
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### Smart Pagination
|
||||||
|
```javascript
|
||||||
|
// Initial load: Fetch 50 transactions
|
||||||
|
// Analyze count to determine pages (3-5)
|
||||||
|
// Cache data for instant navigation
|
||||||
|
// Lazy load additional pages on demand
|
||||||
|
|
||||||
|
const isFirstLoad = currentPage === 1 && allTransactions.length === 0;
|
||||||
|
const fetchCount = isFirstLoad ? 50 : 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caching Strategy
|
||||||
|
- **Transaction Cache**: Store 50+ transactions for fast pagination
|
||||||
|
- **Balance Cache**: Cache balance data in IndexedDB
|
||||||
|
- **Address History**: Persistent search history with timestamps
|
||||||
|
|
||||||
|
### UI Optimizations
|
||||||
|
- **Lazy Loading**: Progressive content loading
|
||||||
|
- **Debounced Inputs**: Prevent excessive API calls
|
||||||
|
- **Responsive Images**: Optimized for mobile devices
|
||||||
|
- **CSS Grid/Flexbox**: Efficient layout rendering
|
||||||
|
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```
|
||||||
|
avalanche-wallet/
|
||||||
|
├── index.html # Main application
|
||||||
|
├── style.css # Stylesheet
|
||||||
|
├── avaxCrypto.js # Cryptographic functions
|
||||||
|
├── avaxBlockchainAPI.js # Blockchain integration
|
||||||
|
├── avaxSearchDB.js # Data persistence
|
||||||
|
├── lib.avax.js # External libraries
|
||||||
|
```
|
||||||
|
|
||||||
224
avaxwallet/avaxBlockchainAPI.js
Normal file
224
avaxwallet/avaxBlockchainAPI.js
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
const RPC_URL =
|
||||||
|
"https://go.getblock.io/2e411fe60c824a94951fddbe9e7922ea/ext/bc/C/rpc";
|
||||||
|
|
||||||
|
function weiToAvax(weiAmount) {
|
||||||
|
try {
|
||||||
|
if (!weiAmount || weiAmount === "0" || weiAmount === 0) {
|
||||||
|
return "0.000000";
|
||||||
|
}
|
||||||
|
const weiString = weiAmount.toString();
|
||||||
|
|
||||||
|
let wei;
|
||||||
|
if (weiString.startsWith("0x")) {
|
||||||
|
// Hexadecimal input
|
||||||
|
wei = BigInteger(weiString.substring(2), 16);
|
||||||
|
} else {
|
||||||
|
// Decimal input
|
||||||
|
wei = BigInteger(weiString, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avaxDecimals = Math.pow(10, 18);
|
||||||
|
const result = (parseFloat(wei.toString()) / avaxDecimals).toFixed(6);
|
||||||
|
|
||||||
|
console.log("Wei conversion:", weiAmount, "->", result, "AVAX");
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error converting Wei to AVAX:", error, "Input:", weiAmount);
|
||||||
|
return "0.000000";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch balance via RPC
|
||||||
|
async function getBalanceRPC(address) {
|
||||||
|
try {
|
||||||
|
const body = {
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "eth_getBalance",
|
||||||
|
params: [address, "latest"],
|
||||||
|
};
|
||||||
|
const resp = await fetch(RPC_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
const j = await resp.json();
|
||||||
|
|
||||||
|
if (j.error) {
|
||||||
|
throw new Error(j.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
avax: weiToAvax(j.result),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching balance:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get transaction count (nonce)
|
||||||
|
async function getTransactionCount(address) {
|
||||||
|
try {
|
||||||
|
const body = {
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "eth_getTransactionCount",
|
||||||
|
params: [address, "latest"],
|
||||||
|
};
|
||||||
|
const resp = await fetch(RPC_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
const j = await resp.json();
|
||||||
|
|
||||||
|
if (j.error) {
|
||||||
|
throw new Error(j.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(j.result, 16);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching nonce:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current gas price
|
||||||
|
async function getGasPrice() {
|
||||||
|
try {
|
||||||
|
const body = {
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "eth_gasPrice",
|
||||||
|
params: [],
|
||||||
|
};
|
||||||
|
const resp = await fetch(RPC_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
const j = await resp.json();
|
||||||
|
|
||||||
|
if (j.error) {
|
||||||
|
throw new Error(j.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return j.result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching gas price:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare transaction data for Avalanche C-Chain
|
||||||
|
async function prepareAvalancheTransaction(
|
||||||
|
privateKey,
|
||||||
|
recipientAddress,
|
||||||
|
amountInAvax
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// Validate inputs
|
||||||
|
if (!privateKey || !recipientAddress || !amountInAvax) {
|
||||||
|
throw new Error(
|
||||||
|
"Missing required parameters: privateKey, recipientAddress, or amount"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recipientAddress.startsWith("0x") || recipientAddress.length !== 42) {
|
||||||
|
throw new Error("Invalid recipient address format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseFloat(amountInAvax) <= 0) {
|
||||||
|
throw new Error("Amount must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
const wallet = await avaxCrypto.generateMultiChain(privateKey);
|
||||||
|
privateKey = wallet.AVAX.privateKey;
|
||||||
|
console.log(privateKey);
|
||||||
|
|
||||||
|
if (privateKey.length !== 64 || !/^[0-9a-fA-F]+$/.test(privateKey)) {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid private key format. Must be 64 hexadecimal characters."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get AVAX sender address from private key (works with FLO/BTC/AVAX private keys)
|
||||||
|
|
||||||
|
const senderAddress = wallet.AVAX.address;
|
||||||
|
|
||||||
|
// Check sender balance
|
||||||
|
const balance = await getBalanceRPC(senderAddress);
|
||||||
|
const balanceAvax = parseFloat(balance.avax);
|
||||||
|
const amount = parseFloat(amountInAvax);
|
||||||
|
|
||||||
|
// Get current gas price and calculate fee
|
||||||
|
const gasPrice = await getGasPrice();
|
||||||
|
const gasLimit = 21000; // Standard gas limit for a simple AVAX transfer
|
||||||
|
|
||||||
|
const gasPriceBN = ethers.BigNumber.from(gasPrice);
|
||||||
|
const gasLimitBN = ethers.BigNumber.from(gasLimit);
|
||||||
|
const gasFee = parseFloat(
|
||||||
|
ethers.utils.formatEther(gasPriceBN.mul(gasLimitBN))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if balance is sufficient for amount + gas fee
|
||||||
|
if (balanceAvax < amount + gasFee) {
|
||||||
|
throw new Error(
|
||||||
|
`Insufficient balance for transaction. You have ${
|
||||||
|
balance.avax
|
||||||
|
} AVAX but need ${
|
||||||
|
amount + gasFee
|
||||||
|
} AVAX (amount + gas fee). Please lower the amount to proceed.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get transaction count (nonce)
|
||||||
|
const nonce = await getTransactionCount(senderAddress);
|
||||||
|
|
||||||
|
// Return prepared transaction data
|
||||||
|
return {
|
||||||
|
senderAddress: senderAddress,
|
||||||
|
recipientAddress: recipientAddress,
|
||||||
|
amount: amountInAvax,
|
||||||
|
nonce: nonce,
|
||||||
|
gasPrice: gasPrice,
|
||||||
|
gasLimit: gasLimit,
|
||||||
|
chainId: 43114,
|
||||||
|
balance: balance.avax,
|
||||||
|
cleanPrivateKey: privateKey,
|
||||||
|
rpcUrl: RPC_URL,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Transaction preparation error:", error);
|
||||||
|
throw new Error(`Transaction preparation failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch transaction history with pagination support
|
||||||
|
async function fetchAvalancheTxHistory(address, page = 1, pageSize = 10) {
|
||||||
|
try {
|
||||||
|
const url = `https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&page=${page}&offset=${pageSize}&sort=desc`;
|
||||||
|
|
||||||
|
const resp = await fetch(url);
|
||||||
|
const data = await resp.json();
|
||||||
|
|
||||||
|
if (data.status !== "1") {
|
||||||
|
if (data.message === "No transactions found") {
|
||||||
|
return { transactions: [], hasMore: false };
|
||||||
|
}
|
||||||
|
throw new Error(data.message || "Failed to fetch transaction history");
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactions = data.result || [];
|
||||||
|
const hasMore = transactions.length === pageSize;
|
||||||
|
|
||||||
|
return {
|
||||||
|
transactions: transactions,
|
||||||
|
hasMore: hasMore,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching transaction history:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
avaxwallet/avaxCrypto.js
Normal file
162
avaxwallet/avaxCrypto.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
(function (EXPORTS) {
|
||||||
|
"use strict";
|
||||||
|
const avaxCrypto = EXPORTS;
|
||||||
|
|
||||||
|
// Generate a new random key
|
||||||
|
function generateNewID() {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
key.setCompressed(true);
|
||||||
|
return {
|
||||||
|
floID: key.getBitcoinAddress(),
|
||||||
|
pubKey: key.getPubKeyHex(),
|
||||||
|
privKey: key.getBitcoinWalletImportFormat(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Object.defineProperties(avaxCrypto, {
|
||||||
|
newID: {
|
||||||
|
get: () => generateNewID(),
|
||||||
|
},
|
||||||
|
hashID: {
|
||||||
|
value: (str) => {
|
||||||
|
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), {
|
||||||
|
asBytes: true,
|
||||||
|
});
|
||||||
|
bytes.unshift(bitjs.pub);
|
||||||
|
var hash = Crypto.SHA256(
|
||||||
|
Crypto.SHA256(bytes, {
|
||||||
|
asBytes: true,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
asBytes: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tmpID: {
|
||||||
|
get: () => {
|
||||||
|
let bytes = Crypto.util.randomBytes(20);
|
||||||
|
bytes.unshift(bitjs.pub);
|
||||||
|
var hash = Crypto.SHA256(
|
||||||
|
Crypto.SHA256(bytes, {
|
||||||
|
asBytes: true,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
asBytes: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Multi-chain Generator (BTC, FLO, AVAX) ---
|
||||||
|
avaxCrypto.generateMultiChain = async function (inputWif) {
|
||||||
|
const versions = {
|
||||||
|
BTC: { pub: 0x00, priv: 0x80 },
|
||||||
|
FLO: { pub: 0x23, priv: 0xa3 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const origBitjsPub = bitjs.pub;
|
||||||
|
const origBitjsPriv = bitjs.priv;
|
||||||
|
const origBitjsCompressed = bitjs.compressed;
|
||||||
|
const origCoinJsCompressed = coinjs.compressed;
|
||||||
|
|
||||||
|
bitjs.compressed = true;
|
||||||
|
coinjs.compressed = true;
|
||||||
|
|
||||||
|
let privKeyHex;
|
||||||
|
let compressed = true;
|
||||||
|
|
||||||
|
// --- Decode input or generate new ---
|
||||||
|
if (typeof inputWif === "string" && inputWif.trim().length > 0) {
|
||||||
|
const hexOnly = /^[0-9a-fA-F]+$/.test(inputWif.trim());
|
||||||
|
if (hexOnly && (inputWif.length === 64 || inputWif.length === 128)) {
|
||||||
|
privKeyHex =
|
||||||
|
inputWif.length === 128 ? inputWif.substring(0, 64) : inputWif;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const decode = Bitcoin.Base58.decode(inputWif);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
privKeyHex = Crypto.util.bytesToHex(key);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Invalid WIF, generating new key:", e);
|
||||||
|
const newKey = generateNewID();
|
||||||
|
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
privKeyHex = Crypto.util.bytesToHex(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newKey = generateNewID();
|
||||||
|
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
privKeyHex = Crypto.util.bytesToHex(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Derive addresses for each chain ---
|
||||||
|
const result = { BTC: {}, FLO: {}, AVAX: {} };
|
||||||
|
|
||||||
|
// BTC
|
||||||
|
bitjs.pub = versions.BTC.pub;
|
||||||
|
bitjs.priv = versions.BTC.priv;
|
||||||
|
const pubKeyBTC = bitjs.newPubkey(privKeyHex);
|
||||||
|
result.BTC.address = coinjs.bech32Address(pubKeyBTC).address;
|
||||||
|
result.BTC.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||||
|
|
||||||
|
// FLO
|
||||||
|
bitjs.pub = versions.FLO.pub;
|
||||||
|
bitjs.priv = versions.FLO.priv;
|
||||||
|
const pubKeyFLO = bitjs.newPubkey(privKeyHex);
|
||||||
|
result.FLO.address = bitjs.pubkey2address(pubKeyFLO);
|
||||||
|
result.FLO.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||||
|
|
||||||
|
// AVAX
|
||||||
|
try {
|
||||||
|
const ecKey = new Bitcoin.ECKey(privKeyHex);
|
||||||
|
|
||||||
|
// Get the uncompressed public key (should start with 04 and be 65 bytes total)
|
||||||
|
const pubKeyFull = ecKey.getPubKeyHex();
|
||||||
|
|
||||||
|
// Already uncompressed, remove 04 prefix
|
||||||
|
let pubKeyUncompressed = pubKeyFull.substring(2);
|
||||||
|
|
||||||
|
// Convert to bytes
|
||||||
|
const pubKeyBytes = Crypto.util.hexToBytes(pubKeyUncompressed);
|
||||||
|
|
||||||
|
// Use Keccak-256 hash of the uncompressed public key
|
||||||
|
const hash = keccak_256.array(pubKeyBytes);
|
||||||
|
|
||||||
|
// Take the last 20 bytes for the address
|
||||||
|
const addressBytes = hash.slice(-20);
|
||||||
|
result.AVAX.address = "0x" + Crypto.util.bytesToHex(addressBytes);
|
||||||
|
result.AVAX.privateKey = privKeyHex;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating AVAX address:", error);
|
||||||
|
console.error("Private key:", privKeyHex);
|
||||||
|
result.AVAX.address = "Error generating address";
|
||||||
|
result.AVAX.privateKey = privKeyHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore
|
||||||
|
bitjs.pub = origBitjsPub;
|
||||||
|
bitjs.priv = origBitjsPriv;
|
||||||
|
bitjs.compressed = origBitjsCompressed;
|
||||||
|
coinjs.compressed = origCoinJsCompressed;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})("object" === typeof module ? module.exports : (window.avaxCrypto = {}));
|
||||||
105
avaxwallet/avaxSearchDB.js
Normal file
105
avaxwallet/avaxSearchDB.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
class SearchedAddressDB {
|
||||||
|
constructor() {
|
||||||
|
this.dbName = "AvaxWalletDB";
|
||||||
|
this.version = 1;
|
||||||
|
this.storeName = "searchedAddresses";
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open(this.dbName, this.version);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
this.db = request.result;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
request.onupgradeneeded = (event) => {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||||
|
const store = db.createObjectStore(this.storeName, {
|
||||||
|
keyPath: "address",
|
||||||
|
});
|
||||||
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSearchedAddress(
|
||||||
|
avaxAddress,
|
||||||
|
balance,
|
||||||
|
timestamp = Date.now(),
|
||||||
|
sourceInfo = null
|
||||||
|
) {
|
||||||
|
if (!this.db) await this.init();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||||
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
const getRequest = store.get(avaxAddress);
|
||||||
|
getRequest.onsuccess = () => {
|
||||||
|
const existingRecord = getRequest.result;
|
||||||
|
let finalSourceInfo = sourceInfo;
|
||||||
|
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
|
||||||
|
finalSourceInfo = existingRecord.sourceInfo;
|
||||||
|
} else if (
|
||||||
|
existingRecord &&
|
||||||
|
existingRecord.sourceInfo &&
|
||||||
|
sourceInfo === null
|
||||||
|
) {
|
||||||
|
finalSourceInfo = existingRecord.sourceInfo;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
address: avaxAddress,
|
||||||
|
balance,
|
||||||
|
timestamp,
|
||||||
|
formattedBalance: `${balance} AVAX`,
|
||||||
|
sourceInfo: finalSourceInfo,
|
||||||
|
};
|
||||||
|
const putRequest = store.put(data);
|
||||||
|
putRequest.onsuccess = () => resolve();
|
||||||
|
putRequest.onerror = () => reject(putRequest.error);
|
||||||
|
};
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSearchedAddresses() {
|
||||||
|
if (!this.db) await this.init();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = this.db.transaction([this.storeName], "readonly");
|
||||||
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
const index = store.index("timestamp");
|
||||||
|
const request = index.getAll();
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const results = request.result.sort(
|
||||||
|
(a, b) => b.timestamp - a.timestamp
|
||||||
|
);
|
||||||
|
resolve(results);
|
||||||
|
};
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSearchedAddress(avaxAddress) {
|
||||||
|
if (!this.db) await this.init();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||||
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
const request = store.delete(avaxAddress);
|
||||||
|
request.onsuccess = () => resolve();
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearAllSearchedAddresses() {
|
||||||
|
if (!this.db) await this.init();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||||
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
const request = store.clear();
|
||||||
|
request.onsuccess = () => resolve();
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
2444
avaxwallet/index.html
Normal file
2444
avaxwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
9993
avaxwallet/lib.avax.js
Normal file
9993
avaxwallet/lib.avax.js
Normal file
File diff suppressed because it is too large
Load Diff
2820
avaxwallet/style.css
Normal file
2820
avaxwallet/style.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user