Wallets
In this tutorial we'll be reviewing how to create, save, recreate, and use a wallet. When you perform a transaction, the SDK will sign the transaction and send it to the network. This cryptographic seal allows us to detect any modifications to your data. Additionally, it provides traceability to your wallet's activity. This is because all of your transactions will be linked to your wallet.
A wallet is used to create and sign transactions; also, the wallet will have a unique address. The nodes will perform validations of the signature and the wallet's existence in the blockchain for those transactions, so it's a requirement to use valid wallets when creating transactions.
This tutorial assumes that you have an existing blockchain set up with ULedger.
In ULedger, a wallet represents an on-chain identity:
- It holds a keypair (public/private key).
- It has a unique address, derived from the public key.
- It can be granted permissions via
authGroups. - It’s used to sign transactions that the network can verify.
In this guide you’ll learn how to:
- Generate a new wallet + mnemonic (recommended).
- Restore a wallet from an existing mnemonic (when you already have one).
- Save the wallet to a
.ukeyfile. - Load a wallet back from disk.
- Recreate a wallet from existing keys or JSON.
After this, you’ll be ready to use your wallet in
👉 Create Transactions with the Go SDK.
1. Prerequisites
Before you start, make sure you have a recent Go version.
In your Go code you’ll mainly use:
import (
"fmt"
"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)
2. UL_Wallet structure (what’s in a wallet?)
The core type is:
type UL_Wallet struct {
Address string `json:"address"`
Enabled bool `json:"enabled"`
Parent string `json:"parent"`
AuthGroups map[string]UL_AuthPermission `json:"authGroups"`
key crypto.ULKey `json:"-"`
}
- Address – derived from the public key (SHA-256 over the hex public key).
- Enabled – can this wallet be used right now?
- Parent – optional parent wallet or owner identifier.
- AuthGroups – permissions grouped by logical name (e.g.
"Leadership"). - key – the in-memory cryptographic key (not serialized directly).
For persistence, the SDK uses an internal WalletData JSON structure that can include:
Address,Enabled,Parent,AuthGroupsMnemonicKeyTypePublicKeyHex- Optional
PrivateKeyHex
This JSON is what ends up in your .ukey files.
3. Generate a new wallet + mnemonic (recommended)
The safest and recommended way to create a wallet is to let the SDK generate a cryptographically-secure BIP-39 mnemonic for you.
The Go SDK supports these entropy values:
wallet.Entropy128(12 words)wallet.Entropy160(15 words)wallet.Entropy192(18 words)wallet.Entropy224(21 words)wallet.Entropy256(24 words, default)
Example:
package main
import (
"fmt"
"log"
"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)
func main() {
passphrase := "" // optional
keyType := crypto.KeyTypeSecp256k1
parent := ""
authGroups := map[string]wallet.UL_AuthPermission{}
w, mnemonic, err := wallet.GenerateNewWallet(
passphrase,
keyType,
parent,
authGroups,
wallet.DefaultEntropy, // defaults to Entropy256
)
if err != nil {
log.Fatalf("failed to generate new wallet: %v", err)
}
fmt.Println("Address:", w.Address)
fmt.Println("Mnemonic (store this safely!):", mnemonic)
}
⚠️ Treat the returned mnemonic as a high-security secret. Store it offline if possible.
4. Restore a wallet from an existing mnemonic (when you already have one)
If you already have a valid BIP-39 mnemonic (for example, generated earlier by the SDK), you can recreate the same wallet.
Example:
package main
import (
"fmt"
"log"
"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/wallet"
)
func main() {
mnemonic := "<paste your BIP-39 mnemonic here>"
passphrase := "" // optional (must match what was used originally)
// Optional but recommended: validate before attempting to restore
if !wallet.ValidateMnemonic(mnemonic) {
log.Fatal("invalid mnemonic: must be valid BIP-39")
}
w, err := wallet.GenerateFromMnemonic(mnemonic, passphrase, crypto.KeyTypeSecp256k1)
if err != nil {
log.Fatalf("failed to generate wallet from mnemonic: %v", err)
}
fmt.Println("Wallet address:", w.Address)
}
GenerateFromMnemonic:
- Validates the mnemonic (BIP-39).
- Derives a seed from the mnemonic + passphrase.
- Creates a keypair for the given
KeyType(e.g.KeyTypeSecp256k1). - Computes the
Addressfrom the public key. - Returns a
UL_Walletthat can sign transactions.
💡 The mnemonic + passphrase is enough to recreate the same wallet later. Losing it means losing access to the underlying keypair.
5. Save a wallet to a .ukey file
Once you have a UL_Wallet, you can persist it with SaveToFile:
// w is your UL_Wallet
// mnemonic is the phrase associated with this wallet (optional but recommended)
includePrivateKey := false // set true only if you really need the private key in the file
if err := w.SaveToFile("wallets/my_wallet.ukey", mnemonic, includePrivateKey); err != nil {
log.Fatalf("failed to save wallet file: %v", err)
}
SaveToFile:
-
Ensures the file name ends with
.ukey. -
Writes a JSON
WalletDatastructure with:Address,Enabled,Parent,AuthGroupsKeyType,PublicKeyHex- Optional
Mnemonic - Optional
PrivateKeyHex(only ifincludePrivateKeyistrue)
-
Uses file mode
0600(owner-only read/write).
🔐 Best practice is to avoid saving the private key when possible. Let the mnemonic be your recovery mechanism.
Example .ukey file
After saving, a wallet file might look like this:
{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow victory alone valve report magnet menu stand avocado online must trophy hire",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D05ACE8256CAA73504F69BF5569D3E6122BEC54D64D7C31C8E6A1E873849440328BCD3216A18849909CBDE6E09DF81E7EC5DC7E17EE1865A63B1",
"privateKeyHex": "2310117A24CD8F086E429D1F52748FEC3EAD5C42E0910187143FE8C63CC0C0C1"
}
A few details worth calling out:
addressis the SHA-256 hash of the lower-cased public key hex.keyTypeis stored as a string (e.g."secp256k1") and mapped internally tocrypto.KeyTypeSecp256k1.mnemonicis present here for convenience and recovery; you can omit it if you don’t want mnemonics in files.privateKeyHexis included in this example, but in production you may prefer to exclude it and rely solely on the mnemonic.
6. Load a wallet from a .ukey file
To reconstruct a wallet from a .ukey file, use LoadFromFile:
loaded, err := wallet.LoadFromFile("wallets/my_wallet.ukey", "")
if err != nil {
log.Fatalf("failed to load wallet: %v", err)
}
fmt.Println("Loaded wallet address:", loaded.Address)
LoadFromFile behavior:
- Reads & parses the
WalletDataJSON. - If
Mnemonicis present → callsGenerateFromMnemonic(mnemonic must be valid BIP-39). - Else if
PrivateKeyHexis present → rebuilds the key from hex. - Else → falls back to public-key-only (no private key → not usable for signing).
The passphrase argument is forwarded to GenerateFromMnemonic when the file contains a mnemonic.
Note: Restoring from mnemonic reconstructs the cryptographic identity (keypair + address). Depending on your file contents and SDK behavior, metadata like
Parent,Enabled, andAuthGroupsmay need to be re-applied.
7. Load from raw JSON (string) with FromJson
If your wallet lives as a raw JSON string (for example, coming from a CLI or an API), you can load it with FromJson:
raw := `{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow victory ...",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D0...",
"privateKeyHex": "2310117A24CD8F08..."
}`
w, err := wallet.FromJson(raw, "")
if err != nil {
log.Fatalf("failed to load wallet from JSON: %v", err)
}
fmt.Println("Address:", w.Address)
This uses the same WalletData structure under the hood and rebuilds the internal crypto.ULKey based on KeyType, PublicKeyHex, and PrivateKeyHex (if present).
8. Import an existing keypair (GetWalletFromHex)
If you already have hex-encoded public and private keys, you can wrap them in a UL_Wallet with GetWalletFromHex:
publicKeyHex := "04f2f0fd15ba3a7f4ba62cd705c4df80..." // uncompressed public key hex
privateKeyHex := "63f6062f2034bcbcc08bae2eaabee8dd..." // private key hex
w, err := wallet.GetWalletFromHex(publicKeyHex, privateKeyHex, crypto.KeyTypeSecp256k1)
if err != nil {
log.Fatalf("failed to import wallet from hex: %v", err)
}
fmt.Println("Imported wallet address:", w.Address)
GetWalletFromHex:
- Validates the hex values.
- Reconstructs the
crypto.ULKey. - Computes the
Addressfrom the public key.
9. Next steps: Register wallets
Everything we’ve done so far lives locally:
- We generated a keypair (and mnemonic).
- We saved it to a
.ukeyfile. - We loaded it back into a
UL_Wallet.
At this point, the wallet is not yet registered on any blockchain. It’s just a cryptographic identity that could be used on one or more ULedger blockchains.
To make a blockchain aware of this wallet (its public key, parent, and authGroups), you send a special transaction of type TX_CREATE_WALLET:
- The payload includes the wallet’s public key, key type, parent, and auth groups.
- The transaction is signed and submitted like any other transaction.
- Once included in a block, nodes can validate this wallet when it signs future transactions.
👉 Continue to: Register a Wallet on a Blockchain (coming next) to see how to build and send a TX_CREATE_WALLET transaction.