Wallet Module
The wallet.mo module provides production-ready wallet functionality with complete transaction lifecycle management.
Overview
The Wallet module is the highest-level module in Hoosat-mo, providing a complete wallet implementation with:
- Address generation using IC threshold ECDSA
- Balance checking via HTTP outcalls
- UTXO fetching and management
- Transaction building, signing, and broadcasting
- Automatic fee calculation and change handling
- Comprehensive error handling
Factory Functions
createMainnetWallet
Creates a wallet instance for Hoosat mainnet.
public func createMainnetWallet(
keyName: Text,
prefix: ?Text
) : WalletInstance
Parameters:
keyName: ECDSA key identifier (e.g.,"dfx_test_key"for local,"key_1"for production)prefix: Optional address prefix (default:"hoosat")
Returns: WalletInstance object with all wallet methods
Example:
let wallet = Wallet.createMainnetWallet("dfx_test_key", ?"hoosat");
createTestnetWallet
Creates a wallet instance for Hoosat testnet.
public func createTestnetWallet(
keyName: Text,
prefix: ?Text
) : WalletInstance
Parameters: Same as createMainnetWallet
Example:
let wallet = Wallet.createTestnetWallet("dfx_test_key", ?"hoosat");
WalletInstance Methods
generateAddress
Generates a new Hoosat address using IC threshold ECDSA.
public func generateAddress(
addressType: ?Nat,
derivationPath: ?Text
) : async Result.Result<AddressInfo, HoosatError>
Parameters:
addressType: Optional address type (null= ECDSA,0= Schnorr,1= ECDSA,2= P2SH)derivationPath: Optional BIP44 derivation path (e.g.,"44'/111111'/0'/0/0")
Returns: #ok(AddressInfo) or #err(HoosatError)
AddressInfo Type:
{
address: Text; // Generated Hoosat address
publicKey: Text; // Hex-encoded public key
addressType: Nat; // Address type used
scriptPublicKey: Text; // Hex-encoded script pubkey
}
Example:
let result = await wallet.generateAddress(null, null);
switch (result) {
case (#ok(info)) {
Debug.print("Address: " # info.address);
Debug.print("PubKey: " # info.publicKey);
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
};
};
getBalance
Fetches the balance for a Hoosat address.
public func getBalance(
address: Text
) : async Result.Result<Nat64, HoosatError>
Parameters:
address: Hoosat address to check
Returns: Balance in sompi (1 HTN = 100,000,000 sompi)
Example:
let result = await wallet.getBalance("hoosat:qp...");
switch (result) {
case (#ok(balance)) {
let htn = Float.fromInt64(Int64.fromNat64(balance)) / 100000000.0;
Debug.print("Balance: " # Float.toText(htn) # " HTN");
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
};
};
sendTransaction
Complete transaction lifecycle: build, sign, and broadcast.
public func sendTransaction(
fromAddress: Text,
toAddress: Text,
amount: Nat64,
fee: ?Nat64,
derivationPath: ?Text
) : async Result.Result<TransactionResult, HoosatError>
Parameters:
fromAddress: Sender's Hoosat addresstoAddress: Recipient's Hoosat addressamount: Amount to send in sompifee: Optional fee in sompi (auto-calculated ifnull)derivationPath: Optional derivation path for signing
Returns: #ok(TransactionResult) or #err(HoosatError)
TransactionResult Type:
{
transactionId: Text; // Network transaction ID
serializedTx: Text; // Hex-encoded signed transaction
fee: Nat64; // Actual fee used
changeAmount: Nat64; // Change returned to sender
}
Example:
let result = await wallet.sendTransaction(
"hoosat:qp...sender",
"hoosat:qp...recipient",
100000000, // 1 HTN
null, // Auto-calculate fee
null // Default derivation
);
switch (result) {
case (#ok(tx)) {
Debug.print("TX ID: " # tx.transactionId);
Debug.print("Fee: " # debug_show(tx.fee) # " sompi");
};
case (#err(#InsufficientFunds(info))) {
Debug.print("Need " # debug_show(info.required) # " sompi");
Debug.print("Have " # debug_show(info.available) # " sompi");
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
};
};
buildTransaction
Builds and signs a transaction without broadcasting.
public func buildTransaction(
fromAddress: Text,
toAddress: Text,
amount: Nat64,
fee: ?Nat64,
derivationPath: ?Text
) : async Result.Result<BuildResult, HoosatError>
Parameters: Same as sendTransaction
Returns: #ok(BuildResult) or #err(HoosatError)
BuildResult Type:
{
serializedTx: Text; // Hex-encoded signed transaction
fee: Nat64; // Fee amount
changeAmount: Nat64; // Change amount
}
Example:
let result = await wallet.buildTransaction(from, to, amount, null, null);
switch (result) {
case (#ok(built)) {
// Inspect or modify before broadcasting
Debug.print("Signed TX: " # built.serializedTx);
// Broadcast later
let txId = await wallet.broadcastSerializedTransaction(built.serializedTx);
};
case (#err(e)) {
Debug.print("Build failed: " # Errors.errorToText(e));
};
};
getUTXOs
Fetches all UTXOs for an address.
public func getUTXOs(
address: Text
) : async Result.Result<[UTXO], HoosatError>
Parameters:
address: Hoosat address
Returns: Array of UTXOs
Example:
let result = await wallet.getUTXOs("hoosat:qp...");
switch (result) {
case (#ok(utxos)) {
Debug.print("UTXOs: " # debug_show(utxos.size()));
for (utxo in utxos.vals()) {
Debug.print("Amount: " # debug_show(utxo.amount));
};
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
};
};
broadcastSerializedTransaction
Broadcasts a pre-built transaction to the network.
public func broadcastSerializedTransaction(
serializedTx: Text
) : async Result.Result<Text, HoosatError>
Parameters:
serializedTx: Hex-encoded signed transaction
Returns: Transaction ID
Example:
let txId = await wallet.broadcastSerializedTransaction(signedTxHex);
switch (txId) {
case (#ok(id)) {
Debug.print("Broadcasted: " # id);
};
case (#err(e)) {
Debug.print("Broadcast failed: " # Errors.errorToText(e));
};
};
getTransactionStatus
Checks transaction confirmation status.
public func getTransactionStatus(
txId: Text
) : async Result.Result<TxStatus, HoosatError>
Parameters:
txId: Transaction ID to check
Returns: Transaction status
TxStatus Type:
{
status: Text; // "confirmed", "pending", or "not_found"
confirmations: Nat; // Number of confirmations
}
Example:
let result = await wallet.getTransactionStatus(txId);
switch (result) {
case (#ok(status)) {
Debug.print("Status: " # status.status);
Debug.print("Confirmations: " # debug_show(status.confirmations));
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
};
};
Complete Example
import Wallet "mo:hoosat-mo/wallet";
import Result "mo:base/Result";
import Errors "mo:hoosat-mo/errors";
import Debug "mo:base/Debug";
actor CompleteWallet {
let wallet = Wallet.createMainnetWallet("dfx_test_key", ?"hoosat");
public func createAndFundWallet() : async ?Text {
// 1. Generate address
let addrResult = await wallet.generateAddress(null, null);
let address = switch (addrResult) {
case (#ok(info)) { info.address };
case (#err(e)) {
Debug.print("Failed to generate address: " # Errors.errorToText(e));
return null;
};
};
Debug.print("Generated address: " # address);
// 2. Check balance
let balResult = await wallet.getBalance(address);
let balance = switch (balResult) {
case (#ok(bal)) { bal };
case (#err(e)) {
Debug.print("Failed to get balance: " # Errors.errorToText(e));
return null;
};
};
Debug.print("Balance: " # debug_show(balance) # " sompi");
if (balance < 200000000) {
Debug.print("Insufficient balance. Please fund: " # address);
return null;
};
// 3. Send transaction
let txResult = await wallet.sendTransaction(
address,
"hoosat:qp..recipient..",
100000000, // 1 HTN
null,
null
);
switch (txResult) {
case (#ok(tx)) {
Debug.print("Transaction sent!");
Debug.print("TX ID: " # tx.transactionId);
Debug.print("Fee: " # debug_show(tx.fee));
Debug.print("Change: " # debug_show(tx.changeAmount));
return ?tx.transactionId;
};
case (#err(e)) {
Debug.print("Transaction failed: " # Errors.errorToText(e));
return null;
};
};
};
};
Best Practices
See the Production Guide for deployment best practices.