Overview
Algorand uses the Ed25519 elliptic curve — the same curve as Solana. An Algorand address is the base32-encoded 32-byte public key with a 4-byte checksum appended.
| Property | Value |
|---|
| Curve | Ed25519 |
| Root Wallet | Solana |
| Address Format | base32(pubkey ‖ checksum[4]) — 58 chars |
| Hashing | SHA-512/256 (checksum only) |
| Serialization | MessagePack |
| Smallest Unit | microAlgo (1 ALGO = 10^6 microAlgo) |
Dependencies
npm install @noble/hashes bs58
Derive Address
Algorand’s address is a 32-byte Ed25519 public key encoded in base32, with a 4-byte checksum from SHA-512/256 of the public key:
import { sha512_256 } from "@noble/hashes/sha512.js";
import bs58 from "bs58";
const BASE32_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
function base32Encode(bytes: Uint8Array): string {
let result = "";
let bits = 0;
let value = 0;
for (const byte of bytes) {
value = (value << 8) | byte;
bits += 8;
while (bits >= 5) {
result += BASE32_ALPHA[(value >>> (bits - 5)) & 0x1f];
bits -= 5;
}
}
if (bits > 0) result += BASE32_ALPHA[(value << (5 - bits)) & 0x1f];
return result;
}
function deriveAlgorandAddress(solanaAddress: string): string {
const pubkey = bs58.decode(solanaAddress);
const checksum = sha512_256(pubkey).slice(28, 32);
const payload = new Uint8Array(36);
payload.set(pubkey, 0);
payload.set(checksum, 32);
return base32Encode(payload);
}
Sign a Message
Sign raw message bytes using the Solana wallet:
async function signAlgorandMessage(message: string): Promise<string> {
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
const messageBytes = new TextEncoder().encode(message);
const { signedMessage } = await dynamicClient.wallets.signMessage({
wallet,
message: bytesToHex(messageBytes),
});
return signedMessage;
}
Sign a Transaction
Use the signing bytes from the Algorand SDK. The SDK prepends the "TX" prefix before signing:
import algosdk from "algosdk";
async function sendAlgorandTransfer(
to: string,
amountAlgo: number,
fromAddress: string,
): Promise<string> {
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "SOL")!;
const client = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
const suggestedParams = await client.getTransactionParams().do();
const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
from: fromAddress,
to,
amount: Math.round(amountAlgo * 1_000_000),
suggestedParams,
});
const bytesToSign = txn.bytesToSign();
const { signedMessage } = await dynamicClient.wallets.signMessage({
wallet,
message: bytesToHex(bytesToSign),
});
const sigBytes = decodeSig(signedMessage);
const signedTxn = txn.attachSignature(bs58.decode(wallet.address), sigBytes);
const result = await client.sendRawTransaction(signedTxn).do();
return result.txId;
}
The Algorand SDK handles the "TX" prefix required before signing. If you implement transaction signing without algosdk, prepend the bytes 0x54 0x58 to the transaction bytes before passing to signMessage.
Verify a Signature
import { ed25519 } from "@noble/curves/ed25519.js";
import bs58 from "bs58";
function verifyAlgorandSignature(
message: string,
signature: string,
solanaAddress: string,
): boolean {
const pubkey = bs58.decode(solanaAddress);
const messageBytes = new TextEncoder().encode(message);
const sigBytes = decodeSig(signature);
return ed25519.verify(sigBytes, messageBytes, pubkey);
}
Check Balance
async function getAlgorandBalance(address: string): Promise<string> {
const res = await fetch(`https://testnet-api.algonode.cloud/v2/accounts/${address}`);
if (!res.ok) return "0";
const data = await res.json();
return (data.amount / 1_000_000).toString();
}
For full implementation details see the JavaScript Algorand guide. The only difference in React Native is the signing call — address derivation, transaction building, and RPC calls are identical.