To maintain high security, you can generate and submit transactions without exposing your private spend key to an internet-connected device by using a combination of view-only and offline wallets.
Core Concepts
- View-only wallet: Uses the
privateViewKey to scan for received funds. It cannot determine which funds are spent or sign transactions because it lacks the private spend key. - Offline wallet: Contains the private spend key to sign transactions and determine spent funds, but is not connected to a daemon, meaning it cannot scan for new funds or broadcast transactions.
Transaction Workflow
To sign and submit a transaction securely, follow these steps:
- Scan: Use a view-only wallet to scan for received funds.
- Export Outputs: Export outputs from the view-only wallet to the offline wallet.
- Export Key Images: Export key images from the offline wallet back to the view-only wallet.
- Create Unsigned TX: Create an unsigned transaction using the view-only wallet.
- Sign: Sign the transaction using the offline wallet.
- Broadcast: Broadcast the signed transaction using the view-only wallet or any daemon.
// 1. View-only wallet scans for funds
let viewOnlyWallet = await moneroTs.createWalletFull({
path: "my_view_only_wallet",
networkType: moneroTs.MoneroNetworkType.STAGENET,
primaryAddress: "55bcxMRhBWea6xxsot8moF1rdPprjJR2x4mfnNnTGgBJFgXa4gWXmWAYdUBKiRcJxy9AUAGJEg28DejvWdJU2VgUDrUvCHG",
privateViewKey: "03d463f03ae547b11dfdf194a07ff82c14a3c6a3de559bd89c9a5e8dc5e9ae02",
restoreHeight: 573936,
server: {uri: "http://localhost:38081", username: "superuser", password: "abctesting123"}
});
await viewOnlyWallet.sync();
// 2. Offline wallet created with seed
let offlineWallet = await moneroTs.createWalletFull({
path: "my_offline_wallet",
networkType: moneroTs.MoneroNetworkType.STAGENET,
seed: "spying swept ashtray going hence jester swagger cease spying unusual..."
});
// 3. Transfer data between wallets
let outputsHex = await viewOnlyWallet.exportOutputs();
await offlineWallet.importOutputs(outputsHex);
let keyImageResult = await offlineWallet.exportKeyImages();
await viewOnlyWallet.importKeyImages(keyImageResult.getKeyImages(), keyImageResult.getOffset());
// 4. Create unsigned transaction
let unsignedTx = await viewOnlyWallet.createTx({
accountIndex: 0,
address: "56j5AskbiNeeb2UAnS85qpey93GYs4VWB78hazZKGdsKCGHvEXUD6nuMQqXaiiY8SwMWsmtAEXS9kA2ko7hgNtGHKsEWyhv",
amount: 250000000000n
});
// 5. Sign transaction offline
let signedTxSet = await offlineWallet.signTxs(unsignedTx.getTxSet().getUnsignedTxHex());
// 6. Broadcast via view-only wallet
let txHashes = await viewOnlyWallet.submitTxs(signedTxSet.getSignedTxHex());