Workflow updating files of algorandwallet
This commit is contained in:
parent
bf6d0e50e6
commit
f12b64d7dc
474
algorandwallet/README.md
Normal file
474
algorandwallet/README.md
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
# Algorand Web Wallet - Technical Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Algorand Multi-Chain Wallet is a web-based cryptocurrency wallet that supports multiple blockchain networks including Algorand (ALGO), 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**: ALGO, 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
|
||||||
|
- **Transaction Filtering**: Filter transactions by All, Received, or Sent
|
||||||
|
- **Minimum Balance Validation**: Ensures Algorand minimum balance requirements are met
|
||||||
|
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### System Architecture
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ Frontend Layer │
|
||||||
|
├────────────────────────────────────────────────────────────┤
|
||||||
|
│ index.html │ style.css │ JavaScript Modules │
|
||||||
|
├──────────────┼─────────────┼───────────────────────────────┤
|
||||||
|
│ │ │ • algoCrypto.js │
|
||||||
|
│ │ │ • algoBlockchainAPI.js │
|
||||||
|
│ │ │ • algoSearchDB.js │
|
||||||
|
│ │ │ • lib.algo.js │
|
||||||
|
└──────────────┴─────────────┴───────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Storage Layer │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ IndexedDB │ LocalStorage │ Session Storage │
|
||||||
|
│ • Address History │ • Theme Prefs │ • Temp Data │
|
||||||
|
│ • Search Cache │ • User Settings │ • Form State │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Blockchain Layer │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ALGO Network │ FLO Network │ Bitcoin Network │
|
||||||
|
│ • Algod API │ • Address Gen │ • Address Gen │
|
||||||
|
│ • Indexer API │ • Key Derivation│ • Key Derivation │
|
||||||
|
│ • Transaction │ │ │
|
||||||
|
│ • Balance Query │ │ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### 1. Cryptographic Engine (`algoCrypto.js`)
|
||||||
|
|
||||||
|
The cryptographic engine handles multi-chain address generation and key management using Ed25519 for Algorand.
|
||||||
|
|
||||||
|
#### Key Functions
|
||||||
|
```javascript
|
||||||
|
// Generate multi-chain addresses from private key
|
||||||
|
async generateMultiChain(inputWif = null)
|
||||||
|
|
||||||
|
// Build Algorand payment transaction
|
||||||
|
buildPaymentTx(params)
|
||||||
|
|
||||||
|
// Sign Algorand transaction
|
||||||
|
signAlgo(txBytes, algoPrivateKey)
|
||||||
|
|
||||||
|
// Create complete signed transaction
|
||||||
|
createSignedPaymentTx(params, privateKey)
|
||||||
|
|
||||||
|
// Hash generation utilities
|
||||||
|
hashID(str)
|
||||||
|
tmpID()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Supported Private Key Formats
|
||||||
|
- **ALGO**: 64-character hexadecimal (Ed25519 seed)
|
||||||
|
- **FLO**: Base58 format starting with 'R'
|
||||||
|
- **BTC**: WIF format starting with 'K' or 'L'
|
||||||
|
|
||||||
|
#### Algorand Address Generation
|
||||||
|
Algorand addresses are generated using Ed25519 cryptography:
|
||||||
|
1. Generate 32-byte seed (or use provided private key)
|
||||||
|
2. Derive Ed25519 public key from seed
|
||||||
|
3. Hash public key with SHA-512/256
|
||||||
|
4. Append 4-byte checksum
|
||||||
|
5. Encode to Base32 (58 characters)
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Blockchain API Layer (`algoBlockchainAPI.js`)
|
||||||
|
|
||||||
|
Handles all blockchain interactions with Algorand network using public RPC endpoints.
|
||||||
|
|
||||||
|
#### Core Functions
|
||||||
|
```javascript
|
||||||
|
// Balance retrieval
|
||||||
|
async getBalance(address)
|
||||||
|
|
||||||
|
// Transaction history with pagination
|
||||||
|
async getTransactions(address, options = {})
|
||||||
|
|
||||||
|
// Transaction parameters for sending
|
||||||
|
async getTransactionParams()
|
||||||
|
|
||||||
|
// Send signed transaction
|
||||||
|
async sendTransaction(signedTxnBytes)
|
||||||
|
|
||||||
|
// Wait for transaction confirmation
|
||||||
|
async waitForConfirmation(txId, timeout = 10)
|
||||||
|
|
||||||
|
// Get single transaction by ID
|
||||||
|
async getTransaction(txId)
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
formatAlgo(microAlgos)
|
||||||
|
parseAlgo(algo)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### RPC Configuration
|
||||||
|
```javascript
|
||||||
|
const ALGOD_URL = 'https://mainnet-api.4160.nodely.dev';
|
||||||
|
const INDEXER_URL = 'https://mainnet-idx.4160.nodely.dev';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API Response Structure
|
||||||
|
```javascript
|
||||||
|
// Balance Response
|
||||||
|
{
|
||||||
|
address: string,
|
||||||
|
balance: number, // in microAlgos
|
||||||
|
balanceAlgo: number, // in ALGO
|
||||||
|
minBalance: number, // minimum required balance
|
||||||
|
pendingRewards: number,
|
||||||
|
rewards: number,
|
||||||
|
status: string,
|
||||||
|
totalAppsOptedIn: number,
|
||||||
|
totalAssetsOptedIn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction Response
|
||||||
|
{
|
||||||
|
transactions: Array,
|
||||||
|
nextToken: string | null,
|
||||||
|
hasMore: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Data Persistence (`algoSearchDB.js`)
|
||||||
|
|
||||||
|
IndexedDB wrapper for persistent storage of searched addresses and metadata.
|
||||||
|
|
||||||
|
#### Database Schema
|
||||||
|
```sql
|
||||||
|
-- Object Store: searchedAddresses
|
||||||
|
{
|
||||||
|
id: number (Primary Key, Auto-increment),
|
||||||
|
algoAddress: string (Indexed),
|
||||||
|
btcAddress: string | null,
|
||||||
|
floAddress: string | null,
|
||||||
|
balance: number,
|
||||||
|
timestamp: number (Indexed),
|
||||||
|
formattedBalance: string,
|
||||||
|
isFromPrivateKey: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API Methods
|
||||||
|
```javascript
|
||||||
|
class SearchedAddressDB {
|
||||||
|
async init()
|
||||||
|
async saveSearchedAddress(algoAddress, balance, timestamp, sourceInfo)
|
||||||
|
async getSearchedAddresses()
|
||||||
|
async deleteSearchedAddress(id)
|
||||||
|
async clearAllSearchedAddresses()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Wallet Generation
|
||||||
|
|
||||||
|
#### `generateWallet()`
|
||||||
|
Generates a new multi-chain wallet with random private keys.
|
||||||
|
|
||||||
|
**Returns:** Promise resolving to wallet object
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
ALGO: { address: string, privateKey: string },
|
||||||
|
FLO: { address: string, privateKey: string },
|
||||||
|
BTC: { address: string, privateKey: string }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```javascript
|
||||||
|
const wallet = await algoCrypto.generateMultiChain();
|
||||||
|
console.log(wallet.ALGO.address); // "ABCD...XYZ" (58 chars)
|
||||||
|
console.log(wallet.ALGO.privateKey); // 64-char hex
|
||||||
|
```
|
||||||
|
|
||||||
|
### Address Recovery
|
||||||
|
|
||||||
|
#### `recoverWallet()`
|
||||||
|
Recovers wallet addresses from an existing private key.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `privateKey` (string): Valid ALGO/FLO/BTC private key
|
||||||
|
|
||||||
|
**Validation:**
|
||||||
|
- Hex keys: 64 or 128 characters (0-9, a-f, A-F)
|
||||||
|
- WIF keys: 51-52 characters (Base58)
|
||||||
|
- Rejects: Algorand addresses, transaction IDs, invalid formats
|
||||||
|
|
||||||
|
**Returns:** Promise resolving to wallet object (same structure as generateWallet)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```javascript
|
||||||
|
const wallet = await algoCrypto.generateMultiChain(privateKey);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transaction Management
|
||||||
|
|
||||||
|
#### `searchAlgoAddress()`
|
||||||
|
Loads balance and transaction history for a given address or private key.
|
||||||
|
|
||||||
|
**Process Flow:**
|
||||||
|
1. Input validation (address/private key)
|
||||||
|
2. Address derivation (if private key provided)
|
||||||
|
3. Balance retrieval with minimum balance check
|
||||||
|
4. Transaction history fetching (10 transactions per page)
|
||||||
|
5. Pagination setup with next token
|
||||||
|
6. UI updates and data persistence
|
||||||
|
|
||||||
|
**Supported Inputs:**
|
||||||
|
- Algorand address (58 characters, Base32)
|
||||||
|
- Private key (hex or WIF format)
|
||||||
|
|
||||||
|
#### `sendAlgo()`
|
||||||
|
Prepares and broadcasts a transaction to the Algorand network.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `privateKey` (string): Sender's private key
|
||||||
|
- `recipientAddress` (string): Recipient's ALGO address (58 chars)
|
||||||
|
- `amount` (string): Amount in ALGO
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
```
|
||||||
|
Input Validation → Balance Check → Minimum Balance Validation →
|
||||||
|
User Confirmation → Transaction Building → Signing → Broadcast →
|
||||||
|
Confirmation Wait
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Checks:**
|
||||||
|
1. Private key format validation
|
||||||
|
2. Recipient address format (58 characters)
|
||||||
|
3. Amount validation (positive number)
|
||||||
|
4. Sufficient balance check
|
||||||
|
5. Minimum balance requirement (account must retain minBalance + fee)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```javascript
|
||||||
|
// Minimum balance validation
|
||||||
|
const remainingBalance = currentBalance - amountMicroAlgos - fee;
|
||||||
|
if (remainingBalance < minBalance) {
|
||||||
|
throw new Error(`Insufficient balance. Account must maintain minimum balance of ${minBalance / 1000000} ALGO`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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=ABCD...XYZ` - Direct address loading
|
||||||
|
- `?hash=TXID...` - Direct transaction hash loading
|
||||||
|
|
||||||
|
#### Transaction Filtering
|
||||||
|
|
||||||
|
**Filter Types:**
|
||||||
|
- `all`: Show all transactions
|
||||||
|
- `received`: Show only incoming transactions
|
||||||
|
- `sent`: Show only outgoing transactions
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
```javascript
|
||||||
|
function filterTransactions(type) {
|
||||||
|
currentTxFilter = type;
|
||||||
|
currentPage = 1; // Reset to first page
|
||||||
|
displayCurrentPage();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Transaction Structure
|
||||||
|
|
||||||
|
### Algorand Transaction Format
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
from: string, // Sender address
|
||||||
|
to: string, // Recipient address
|
||||||
|
amount: number, // Amount in microAlgos
|
||||||
|
fee: number, // Fee in microAlgos
|
||||||
|
firstRound: number, // First valid round
|
||||||
|
lastRound: number, // Last valid round
|
||||||
|
genesisID: string, // Network genesis ID
|
||||||
|
genesisHash: string, // Network genesis hash (Base64)
|
||||||
|
note: Uint8Array // Optional note (max 1KB)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MessagePack Encoding
|
||||||
|
Algorand uses MessagePack encoding for transactions:
|
||||||
|
```javascript
|
||||||
|
// Encoding process
|
||||||
|
1. Build transaction object
|
||||||
|
2. Encode with MessagePack
|
||||||
|
3. Prepend "TX" prefix for signing
|
||||||
|
4. Sign with Ed25519
|
||||||
|
5. Create signed transaction envelope
|
||||||
|
6. Encode for broadcasting
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- **Local Processing**: All cryptographic operations happen client-side
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### Transaction Security
|
||||||
|
- **Confirmation Modal**: User must confirm transaction details before sending
|
||||||
|
- **Balance Validation**: Ensures sufficient balance including fees
|
||||||
|
- **Minimum Balance Check**: Prevents account from going below minimum
|
||||||
|
- **Network Validation**: Verifies transaction parameters from network
|
||||||
|
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### Smart Pagination
|
||||||
|
```javascript
|
||||||
|
// Initial load: Fetch 10 transactions
|
||||||
|
// Use nextToken for subsequent pages
|
||||||
|
// Cache data for instant navigation
|
||||||
|
// Lazy load additional pages on demand
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
limit: 10,
|
||||||
|
next: txNextToken,
|
||||||
|
txType: currentTxFilter === 'all' ? null : 'pay'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caching Strategy
|
||||||
|
- **Transaction Cache**: Store transactions with next token for pagination
|
||||||
|
- **Balance Cache**: Cache balance data in IndexedDB
|
||||||
|
- **Address History**: Persistent search history with timestamps
|
||||||
|
- **Recent Searches**: Quick access to last 10 searched addresses
|
||||||
|
|
||||||
|
### UI Optimizations
|
||||||
|
- **Lazy Loading**: Progressive content loading
|
||||||
|
- **Loading States**: Visual feedback during API calls
|
||||||
|
- **Debounced Inputs**: Prevent excessive API calls
|
||||||
|
- **Responsive Images**: Optimized for mobile devices
|
||||||
|
- **CSS Grid/Flexbox**: Efficient layout rendering
|
||||||
|
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Errors and Solutions
|
||||||
|
|
||||||
|
#### 1. Balance Below Minimum
|
||||||
|
```javascript
|
||||||
|
Error: "balance below min"
|
||||||
|
Solution: Account must maintain minimum balance (typically 0.1 ALGO)
|
||||||
|
Implementation: Validate remainingBalance >= minBalance before sending
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Invalid Address Format
|
||||||
|
```javascript
|
||||||
|
Error: "Invalid address format"
|
||||||
|
Solution: Algorand addresses must be exactly 58 characters (Base32)
|
||||||
|
Validation: /^[A-Z2-7]{58}$/.test(address)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Transaction Not Found
|
||||||
|
```javascript
|
||||||
|
Error: "Transaction not found: 404"
|
||||||
|
Solution: Transaction may not be indexed yet or invalid hash
|
||||||
|
Wait: 4-8 seconds for transaction to be indexed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Insufficient Balance
|
||||||
|
```javascript
|
||||||
|
Error: "Insufficient balance"
|
||||||
|
Solution: Account balance must cover amount + fee + minimum balance
|
||||||
|
Formula: balance >= amount + fee + minBalance
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Network Information
|
||||||
|
|
||||||
|
### Algorand Mainnet
|
||||||
|
- **Block Time**: ~4 seconds
|
||||||
|
- **Minimum Fee**: 0.001 ALGO (1000 microAlgos)
|
||||||
|
- **Minimum Balance**: 0.1 ALGO (100,000 microAlgos)
|
||||||
|
- **Address Format**: Base32, 58 characters
|
||||||
|
- **Transaction ID**: Base32, 52 characters
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
```javascript
|
||||||
|
// Algod API (node operations)
|
||||||
|
https://mainnet-api.4160.nodely.dev
|
||||||
|
|
||||||
|
// Indexer API (historical data)
|
||||||
|
https://mainnet-idx.4160.nodely.dev
|
||||||
|
|
||||||
|
// Explorer
|
||||||
|
https://algoexplorer.io
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
```
|
||||||
|
algorand-wallet/
|
||||||
|
├── index.html # Main application
|
||||||
|
├── style.css # Stylesheet
|
||||||
|
├── algoCrypto.js # Cryptographic functions
|
||||||
|
├── algoBlockchainAPI.js # Blockchain integration
|
||||||
|
├── algoSearchDB.js # Data persistence
|
||||||
|
├── lib.algo.js # External libraries (Bitcoin.js)
|
||||||
|
├── algo_favicon.png # Favicon
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### External Libraries
|
||||||
|
```html
|
||||||
|
<!-- Ed25519 cryptography -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
|
||||||
|
|
||||||
|
<!-- SHA-512/256 hashing -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/js-sha512@0.8.0/build/sha512.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Bitcoin.js for BTC/FLO support -->
|
||||||
|
<script src="lib.algo.js"></script>
|
||||||
|
|
||||||
|
<!-- Font Awesome icons -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- Inter font -->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
200
algorandwallet/algoBlockchainAPI.js
Normal file
200
algorandwallet/algoBlockchainAPI.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
(function(GLOBAL) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ALGOD_URL = 'https://mainnet-api.4160.nodely.dev';
|
||||||
|
const INDEXER_URL = 'https://mainnet-idx.4160.nodely.dev';
|
||||||
|
|
||||||
|
const algoAPI = {};
|
||||||
|
|
||||||
|
// Get account balance and info
|
||||||
|
algoAPI.getBalance = async function(address) {
|
||||||
|
const response = await fetch(`${ALGOD_URL}/v2/accounts/${address}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch balance: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: data.address,
|
||||||
|
balance: data.amount, // in microAlgos
|
||||||
|
balanceAlgo: data.amount / 1000000, // in ALGO
|
||||||
|
minBalance: data['min-balance'],
|
||||||
|
pendingRewards: data['pending-rewards'],
|
||||||
|
rewards: data.rewards,
|
||||||
|
status: data.status,
|
||||||
|
totalAppsOptedIn: data['total-apps-opted-in'] || 0,
|
||||||
|
totalAssetsOptedIn: data['total-assets-opted-in'] || 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get transaction history with pagination
|
||||||
|
algoAPI.getTransactions = async function(address, options = {}) {
|
||||||
|
const limit = options.limit || 10;
|
||||||
|
const nextToken = options.next || null;
|
||||||
|
const txType = options.txType || null;
|
||||||
|
|
||||||
|
let url = `${INDEXER_URL}/v2/accounts/${address}/transactions?limit=${limit}`;
|
||||||
|
|
||||||
|
if (nextToken) {
|
||||||
|
url += `&next=${nextToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txType) {
|
||||||
|
url += `&tx-type=${txType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch transactions: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Format transactions
|
||||||
|
const transactions = (data.transactions || []).map(tx => {
|
||||||
|
// Check for payment transaction or asset transfer
|
||||||
|
const paymentTx = tx['payment-transaction'];
|
||||||
|
const assetTx = tx['asset-transfer-transaction'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tx.id,
|
||||||
|
type: tx['tx-type'],
|
||||||
|
roundTime: tx['round-time'],
|
||||||
|
confirmedRound: tx['confirmed-round'],
|
||||||
|
fee: tx.fee,
|
||||||
|
sender: tx.sender,
|
||||||
|
// Get receiver from payment or asset transfer
|
||||||
|
receiver: paymentTx?.receiver || assetTx?.receiver || null,
|
||||||
|
amount: paymentTx?.amount || 0,
|
||||||
|
amountAlgo: (paymentTx?.amount || 0) / 1000000,
|
||||||
|
// Asset transfer details
|
||||||
|
assetId: assetTx?.['asset-id'] || null,
|
||||||
|
assetAmount: assetTx?.amount || 0,
|
||||||
|
note: tx.note ? atob(tx.note) : null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
transactions,
|
||||||
|
nextToken: data['next-token'] || null,
|
||||||
|
hasMore: !!data['next-token']
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get transaction parameters (needed for sending)
|
||||||
|
algoAPI.getTransactionParams = async function() {
|
||||||
|
const response = await fetch(`${ALGOD_URL}/v2/transactions/params`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch tx params: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
fee: data.fee || data['min-fee'],
|
||||||
|
firstRound: data['last-round'],
|
||||||
|
lastRound: data['last-round'] + 1000,
|
||||||
|
genesisId: data['genesis-id'],
|
||||||
|
genesisHash: data['genesis-hash']
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send signed transaction
|
||||||
|
algoAPI.sendTransaction = async function(signedTxnBytes) {
|
||||||
|
const response = await fetch(`${ALGOD_URL}/v2/transactions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-binary'
|
||||||
|
},
|
||||||
|
body: signedTxnBytes
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.message || `Failed to send transaction: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
txId: data.txId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for transaction confirmation
|
||||||
|
algoAPI.waitForConfirmation = async function(txId, timeout = 10) {
|
||||||
|
const startRound = (await algoAPI.getTransactionParams()).firstRound;
|
||||||
|
let currentRound = startRound;
|
||||||
|
|
||||||
|
while (currentRound < startRound + timeout) {
|
||||||
|
// Check if transaction is confirmed
|
||||||
|
const response = await fetch(`${INDEXER_URL}/v2/transactions/${txId}`);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.transaction && data.transaction['confirmed-round']) {
|
||||||
|
return {
|
||||||
|
confirmed: true,
|
||||||
|
round: data.transaction['confirmed-round'],
|
||||||
|
txId: txId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 4 seconds (Algorand block time)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 4000));
|
||||||
|
currentRound++;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Transaction confirmation timeout');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get single transaction by ID
|
||||||
|
algoAPI.getTransaction = async function(txId) {
|
||||||
|
const response = await fetch(`${INDEXER_URL}/v2/transactions/${txId}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Transaction not found: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const tx = data.transaction;
|
||||||
|
|
||||||
|
// Check for payment transaction or asset transfer
|
||||||
|
const paymentTx = tx['payment-transaction'];
|
||||||
|
const assetTx = tx['asset-transfer-transaction'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tx.id,
|
||||||
|
type: tx['tx-type'],
|
||||||
|
roundTime: tx['round-time'],
|
||||||
|
confirmedRound: tx['confirmed-round'],
|
||||||
|
fee: tx.fee,
|
||||||
|
sender: tx.sender,
|
||||||
|
// Get receiver from payment or asset transfer
|
||||||
|
receiver: paymentTx?.receiver || assetTx?.receiver || null,
|
||||||
|
amount: paymentTx?.amount || 0,
|
||||||
|
amountAlgo: (paymentTx?.amount || 0) / 1000000,
|
||||||
|
// Asset transfer details
|
||||||
|
assetId: assetTx?.['asset-id'] || null,
|
||||||
|
assetAmount: assetTx?.amount || 0,
|
||||||
|
note: tx.note ? atob(tx.note) : null
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format ALGO amount for display
|
||||||
|
algoAPI.formatAlgo = function(microAlgos) {
|
||||||
|
return (microAlgos / 1000000).toFixed(6);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse ALGO to microAlgos
|
||||||
|
algoAPI.parseAlgo = function(algo) {
|
||||||
|
return Math.floor(parseFloat(algo) * 1000000);
|
||||||
|
};
|
||||||
|
|
||||||
|
GLOBAL.algoAPI = algoAPI;
|
||||||
|
|
||||||
|
})(typeof window !== 'undefined' ? window : global);
|
||||||
401
algorandwallet/algoCrypto.js
Normal file
401
algorandwallet/algoCrypto.js
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
(function (EXPORTS) {
|
||||||
|
"use strict";
|
||||||
|
const algoCrypto = EXPORTS;
|
||||||
|
|
||||||
|
function hexToBytes(hex) {
|
||||||
|
const bytes = [];
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(bytes) {
|
||||||
|
return Array.from(bytes)
|
||||||
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA-512/256 using js-sha512 library (loaded from CDN)
|
||||||
|
function sha512_256(data) {
|
||||||
|
return new Uint8Array(sha512.sha512_256.array(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base32 decode (for Algorand addresses)
|
||||||
|
function base32Decode(str) {
|
||||||
|
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||||
|
let bits = 0;
|
||||||
|
let value = 0;
|
||||||
|
let output = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const idx = ALPHABET.indexOf(str[i].toUpperCase());
|
||||||
|
if (idx === -1) continue;
|
||||||
|
|
||||||
|
value = (value << 5) | idx;
|
||||||
|
bits += 5;
|
||||||
|
|
||||||
|
if (bits >= 8) {
|
||||||
|
output.push((value >>> (bits - 8)) & 0xFF);
|
||||||
|
bits -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal MessagePack encoder for Algorand transactions
|
||||||
|
const msgpack = {
|
||||||
|
encode: function(obj) {
|
||||||
|
const parts = [];
|
||||||
|
this._encode(obj, parts);
|
||||||
|
const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
|
||||||
|
const result = new Uint8Array(totalLength);
|
||||||
|
let offset = 0;
|
||||||
|
for (const part of parts) {
|
||||||
|
result.set(part, offset);
|
||||||
|
offset += part.length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
_encode: function(value, parts) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
parts.push(new Uint8Array([0xc0]));
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
parts.push(new Uint8Array([value ? 0xc3 : 0xc2]));
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
if (Number.isInteger(value)) {
|
||||||
|
if (value >= 0) {
|
||||||
|
if (value < 128) {
|
||||||
|
parts.push(new Uint8Array([value]));
|
||||||
|
} else if (value < 256) {
|
||||||
|
parts.push(new Uint8Array([0xcc, value]));
|
||||||
|
} else if (value < 65536) {
|
||||||
|
parts.push(new Uint8Array([0xcd, value >> 8, value & 0xff]));
|
||||||
|
} else if (value < 4294967296) {
|
||||||
|
parts.push(new Uint8Array([0xce, value >> 24, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]));
|
||||||
|
} else {
|
||||||
|
// 64-bit unsigned
|
||||||
|
const hi = Math.floor(value / 4294967296);
|
||||||
|
const lo = value >>> 0;
|
||||||
|
parts.push(new Uint8Array([0xcf,
|
||||||
|
hi >> 24, (hi >> 16) & 0xff, (hi >> 8) & 0xff, hi & 0xff,
|
||||||
|
lo >> 24, (lo >> 16) & 0xff, (lo >> 8) & 0xff, lo & 0xff
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value >= -32) {
|
||||||
|
parts.push(new Uint8Array([value & 0xff]));
|
||||||
|
} else if (value >= -128) {
|
||||||
|
parts.push(new Uint8Array([0xd0, value & 0xff]));
|
||||||
|
} else if (value >= -32768) {
|
||||||
|
parts.push(new Uint8Array([0xd1, (value >> 8) & 0xff, value & 0xff]));
|
||||||
|
} else {
|
||||||
|
parts.push(new Uint8Array([0xd2, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
const encoded = new TextEncoder().encode(value);
|
||||||
|
if (encoded.length < 32) {
|
||||||
|
parts.push(new Uint8Array([0xa0 | encoded.length]));
|
||||||
|
} else if (encoded.length < 256) {
|
||||||
|
parts.push(new Uint8Array([0xd9, encoded.length]));
|
||||||
|
} else {
|
||||||
|
parts.push(new Uint8Array([0xda, encoded.length >> 8, encoded.length & 0xff]));
|
||||||
|
}
|
||||||
|
parts.push(encoded);
|
||||||
|
} else if (value instanceof Uint8Array) {
|
||||||
|
if (value.length < 256) {
|
||||||
|
parts.push(new Uint8Array([0xc4, value.length]));
|
||||||
|
} else {
|
||||||
|
parts.push(new Uint8Array([0xc5, value.length >> 8, value.length & 0xff]));
|
||||||
|
}
|
||||||
|
parts.push(value);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (value.length < 16) {
|
||||||
|
parts.push(new Uint8Array([0x90 | value.length]));
|
||||||
|
} else {
|
||||||
|
parts.push(new Uint8Array([0xdc, value.length >> 8, value.length & 0xff]));
|
||||||
|
}
|
||||||
|
for (const item of value) {
|
||||||
|
this._encode(item, parts);
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
const keys = Object.keys(value).filter(k => value[k] !== undefined && value[k] !== null).sort();
|
||||||
|
if (keys.length < 16) {
|
||||||
|
parts.push(new Uint8Array([0x80 | keys.length]));
|
||||||
|
} else {
|
||||||
|
parts.push(new Uint8Array([0xde, keys.length >> 8, keys.length & 0xff]));
|
||||||
|
}
|
||||||
|
for (const key of keys) {
|
||||||
|
this._encode(key, parts);
|
||||||
|
this._encode(value[key], parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build Algorand payment transaction
|
||||||
|
algoCrypto.buildPaymentTx = function(params) {
|
||||||
|
const { from, to, amount, fee, firstRound, lastRound, genesisId, genesisHash, note } = params;
|
||||||
|
|
||||||
|
// Decode addresses to get public keys (remove checksum)
|
||||||
|
const fromDecoded = base32Decode(from);
|
||||||
|
const toDecoded = base32Decode(to);
|
||||||
|
const fromPubKey = fromDecoded.slice(0, 32);
|
||||||
|
const toPubKey = toDecoded.slice(0, 32);
|
||||||
|
|
||||||
|
// Decode genesis hash from base64
|
||||||
|
const genesisHashBytes = new Uint8Array(atob(genesisHash).split('').map(c => c.charCodeAt(0)));
|
||||||
|
|
||||||
|
// Build transaction object
|
||||||
|
const tx = {
|
||||||
|
amt: amount,
|
||||||
|
fee: fee,
|
||||||
|
fv: firstRound,
|
||||||
|
gen: genesisId,
|
||||||
|
gh: genesisHashBytes,
|
||||||
|
lv: lastRound,
|
||||||
|
rcv: toPubKey,
|
||||||
|
snd: fromPubKey,
|
||||||
|
type: 'pay'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
tx.note = new TextEncoder().encode(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encode transaction for signing (with "TX" prefix)
|
||||||
|
algoCrypto.encodeTxForSigning = function(tx) {
|
||||||
|
const encoded = msgpack.encode(tx);
|
||||||
|
const prefix = new TextEncoder().encode('TX');
|
||||||
|
const result = new Uint8Array(prefix.length + encoded.length);
|
||||||
|
result.set(prefix, 0);
|
||||||
|
result.set(encoded, prefix.length);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encode signed transaction for broadcasting
|
||||||
|
algoCrypto.encodeSignedTx = function(tx, signature) {
|
||||||
|
const signedTx = {
|
||||||
|
sig: signature,
|
||||||
|
txn: tx
|
||||||
|
};
|
||||||
|
return msgpack.encode(signedTx);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Complete function to build, sign, and encode transaction
|
||||||
|
algoCrypto.createSignedPaymentTx = async function(params, privateKey) {
|
||||||
|
// Build the transaction
|
||||||
|
const tx = this.buildPaymentTx(params);
|
||||||
|
|
||||||
|
// Encode for signing
|
||||||
|
const txForSigning = this.encodeTxForSigning(tx);
|
||||||
|
|
||||||
|
// Sign the transaction
|
||||||
|
const signature = await this.signAlgo(txForSigning, privateKey);
|
||||||
|
|
||||||
|
// Encode the signed transaction
|
||||||
|
const signedTxBytes = this.encodeSignedTx(tx, signature);
|
||||||
|
|
||||||
|
return signedTxBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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(algoCrypto, {
|
||||||
|
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, ALGO) ---
|
||||||
|
algoCrypto.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 trimmedInput = inputWif.trim();
|
||||||
|
const hexOnly = /^[0-9a-fA-F]+$/.test(trimmedInput);
|
||||||
|
|
||||||
|
if (hexOnly && (trimmedInput.length === 64 || trimmedInput.length === 128)) {
|
||||||
|
privKeyHex =
|
||||||
|
trimmedInput.length === 128 ? trimmedInput.substring(0, 64) : trimmedInput;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const decode = Bitcoin.Base58.decode(trimmedInput);
|
||||||
|
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 = 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 = bytesToHex(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Generate new key if no input
|
||||||
|
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 = bytesToHex(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Derive addresses for each chain ---
|
||||||
|
const result = { BTC: {}, FLO: {}, ALGO: {} };
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// ALGO
|
||||||
|
try {
|
||||||
|
const privBytes = hexToBytes(privKeyHex.substring(0, 64));
|
||||||
|
const seed = new Uint8Array(privBytes.slice(0, 32));
|
||||||
|
|
||||||
|
// Generate Ed25519 keypair from seed
|
||||||
|
const keyPair = nacl.sign.keyPair.fromSeed(seed);
|
||||||
|
const pubKey = keyPair.publicKey;
|
||||||
|
|
||||||
|
// Algorand uses SHA-512/256 (32 bytes output) for checksum
|
||||||
|
const hashResult = sha512_256(pubKey);
|
||||||
|
const checksum = hashResult.slice(28, 32);
|
||||||
|
const addressBytes = new Uint8Array([...pubKey, ...checksum]);
|
||||||
|
|
||||||
|
// Base32 encode the address
|
||||||
|
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||||
|
let bits = 0;
|
||||||
|
let value = 0;
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < addressBytes.length; i++) {
|
||||||
|
value = (value << 8) | addressBytes[i];
|
||||||
|
bits += 8;
|
||||||
|
|
||||||
|
while (bits >= 5) {
|
||||||
|
output += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
|
||||||
|
bits -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bits > 0) {
|
||||||
|
output += BASE32_ALPHABET[(value << (5 - bits)) & 31];
|
||||||
|
}
|
||||||
|
|
||||||
|
const algoAddress = output;
|
||||||
|
// Algorand private key format: seed (32 bytes) + publicKey (32 bytes) = 64 bytes total (128 hex chars)
|
||||||
|
const seedHex = bytesToHex(seed);
|
||||||
|
const pubKeyHexFull = bytesToHex(pubKey);
|
||||||
|
const algoPrivateKey = seedHex + pubKeyHexFull;
|
||||||
|
|
||||||
|
result.ALGO.address = algoAddress;
|
||||||
|
result.ALGO.privateKey = algoPrivateKey;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating ALGO address:", error);
|
||||||
|
result.ALGO.address = "Error generating address";
|
||||||
|
result.ALGO.privateKey = privKeyHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitjs.pub = origBitjsPub;
|
||||||
|
bitjs.priv = origBitjsPriv;
|
||||||
|
bitjs.compressed = origBitjsCompressed;
|
||||||
|
coinjs.compressed = origCoinJsCompressed;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sign Algo Transaction
|
||||||
|
algoCrypto.signAlgo = async function (txBytes, algoPrivateKey) {
|
||||||
|
const privKeyOnly = algoPrivateKey.substring(0, 64);
|
||||||
|
const privBytes = hexToBytes(privKeyOnly);
|
||||||
|
const seed = new Uint8Array(privBytes.slice(0, 32));
|
||||||
|
|
||||||
|
const keypair = nacl.sign.keyPair.fromSeed(seed);
|
||||||
|
|
||||||
|
let txData;
|
||||||
|
if (typeof txBytes === 'string') {
|
||||||
|
txData = new Uint8Array(atob(txBytes).split('').map(c => c.charCodeAt(0)));
|
||||||
|
} else {
|
||||||
|
txData = new Uint8Array(txBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = nacl.sign.detached(txData, keypair.secretKey);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
};
|
||||||
|
|
||||||
|
})("object" === typeof module ? module.exports : (window.algoCrypto = {}));
|
||||||
120
algorandwallet/algoSearchDB.js
Normal file
120
algorandwallet/algoSearchDB.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
class SearchedAddressDB {
|
||||||
|
constructor() {
|
||||||
|
this.dbName = "AlgoWalletDB";
|
||||||
|
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: "id",
|
||||||
|
autoIncrement: true
|
||||||
|
});
|
||||||
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
||||||
|
store.createIndex("algoAddress", "algoAddress", { unique: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSearchedAddress(
|
||||||
|
algoAddress,
|
||||||
|
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 index = store.index("algoAddress");
|
||||||
|
|
||||||
|
// Check if address already exists
|
||||||
|
const getRequest = index.getAll(algoAddress);
|
||||||
|
getRequest.onsuccess = () => {
|
||||||
|
const existingRecords = getRequest.result;
|
||||||
|
|
||||||
|
if (existingRecords.length > 0) {
|
||||||
|
// Address exists, update the existing record
|
||||||
|
const existingRecord = existingRecords[0];
|
||||||
|
const updatedData = {
|
||||||
|
...existingRecord,
|
||||||
|
balance,
|
||||||
|
timestamp,
|
||||||
|
formattedBalance: `${balance.toFixed(6)} ALGO`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const putRequest = store.put(updatedData);
|
||||||
|
putRequest.onsuccess = () => resolve();
|
||||||
|
putRequest.onerror = () => reject(putRequest.error);
|
||||||
|
} else {
|
||||||
|
// Address doesn't exist, create new record
|
||||||
|
const data = {
|
||||||
|
algoAddress,
|
||||||
|
btcAddress: sourceInfo?.btcAddress || null,
|
||||||
|
floAddress: sourceInfo?.floAddress || null,
|
||||||
|
balance,
|
||||||
|
timestamp,
|
||||||
|
formattedBalance: `${balance.toFixed(6)} ALGO`,
|
||||||
|
isFromPrivateKey: !!(sourceInfo?.btcAddress || sourceInfo?.floAddress),
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = store.add(data);
|
||||||
|
addRequest.onsuccess = () => resolve();
|
||||||
|
addRequest.onerror = () => reject(addRequest.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.slice(0, 10));
|
||||||
|
};
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSearchedAddress(id) {
|
||||||
|
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(id);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
algorandwallet/algo_favicon.png
Normal file
BIN
algorandwallet/algo_favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
1886
algorandwallet/index.html
Normal file
1886
algorandwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
11473
algorandwallet/lib.algo.js
Normal file
11473
algorandwallet/lib.algo.js
Normal file
File diff suppressed because it is too large
Load Diff
3272
algorandwallet/style.css
Normal file
3272
algorandwallet/style.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user