Workflow updating files of avaxwallet

This commit is contained in:
RanchiMall Dev 2026-01-12 09:55:43 +00:00
parent 468e72880f
commit 602075ab8e
7 changed files with 15997 additions and 0 deletions

249
avaxwallet/README.md Normal file
View 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
```

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

9993
avaxwallet/lib.avax.js Normal file

File diff suppressed because it is too large Load Diff

2820
avaxwallet/style.css Normal file

File diff suppressed because it is too large Load Diff