feat: Implement initial Algorand wallet application including UI, blockchain API integration, and cryptographic functionalities.
This commit is contained in:
commit
93d25d6d33
183
algoBlockchainAPI.js
Normal file
183
algoBlockchainAPI.js
Normal file
@ -0,0 +1,183 @@
|
||||
(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 => ({
|
||||
id: tx.id,
|
||||
type: tx['tx-type'],
|
||||
roundTime: tx['round-time'],
|
||||
confirmedRound: tx['confirmed-round'],
|
||||
fee: tx.fee,
|
||||
sender: tx.sender,
|
||||
// Payment transaction details
|
||||
receiver: tx['payment-transaction']?.receiver || null,
|
||||
amount: tx['payment-transaction']?.amount || 0,
|
||||
amountAlgo: (tx['payment-transaction']?.amount || 0) / 1000000,
|
||||
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;
|
||||
|
||||
return {
|
||||
id: tx.id,
|
||||
type: tx['tx-type'],
|
||||
roundTime: tx['round-time'],
|
||||
confirmedRound: tx['confirmed-round'],
|
||||
fee: tx.fee,
|
||||
sender: tx.sender,
|
||||
receiver: tx['payment-transaction']?.receiver || null,
|
||||
amount: tx['payment-transaction']?.amount || 0,
|
||||
amountAlgo: (tx['payment-transaction']?.amount || 0) / 1000000,
|
||||
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
algoCrypto.js
Normal file
401
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
algoSearchDB.js
Normal file
120
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
algo_favicon.png
Normal file
BIN
algo_favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
1722
index.html
Normal file
1722
index.html
Normal file
File diff suppressed because it is too large
Load Diff
11473
lib.algo.js
Normal file
11473
lib.algo.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user