Transactions can be generated without exposing the private spend key to an internet-connected device using view-only and offline wallets.
A view-only wallet uses the private view key to scan for received funds, but does not have the private spend key to determine which funds are spent or sign transactions.
An offline wallet has the private spend key to determine which funds are spent and sign transactions, but is not connected to any daemon to scan for received funds or broadcast transactions.
To sign and submit transactions using view-only and offline wallets:
- Use a view-only wallet to scan for received funds.
- Export outputs from the view-only wallet to an offline wallet.
- Export key images from the offline wallet to the view-only wallet.
- Create an unsigned transaction using the view-only wallet.
- Sign the transaction using the offline wallet.
- Broadcast the signed transaction using the view-only wallet or any daemon.
The following code demonstrates creating, signing, and submitting transactions using view-only and offline wallets:
import moneroTs from "monero-ts";
// create and sync view-only wallet without spend key
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();
// create offline wallet
let offlineWallet = await moneroTs.createWalletFull({
path: "my_offline_wallet",
networkType: moneroTs.MoneroNetworkType.STAGENET,
seed: "spying swept ashtray going hence jester swagger cease spying unusual..."
});
// export outputs from view-only wallet
let outputsHex = await viewOnlyWallet.exportOutputs();
// import outputs to offline wallet
await offlineWallet.importOutputs(outputsHex);
// export key images from offline wallet
let keyImages = await offlineWallet.exportKeyImages();
// import key images to view-only wallet
await viewOnlyWallet.importKeyImages(keyImages);
// create unsigned tx using view-only wallet
let unsignedTx = await viewOnlyWallet.createTx({
accountIndex: 0,
address: "56j5AskbiNeeb2UAnS85qpey93GYs4VWB78hazZKGdsKCGHvEXUD6nuMQqXaiiY8SwMWsmtAEXS9kA2ko7hgNtGHKsEWyhv",
amount: 250000000000n // 0.25 XMR
});
// describe unsigned tx set to confirm details
let describedTxSet = await offlineWallet.describeTxSet(unsignedTx.getTxSet());
let fee = describedTxSet.getTxs()[0].getFee(); // "Are you sure you want to send... ?"
// sign tx using offline wallet
let signedTxSet = await offlineWallet.signTxs(unsignedTx.getTxSet().getUnsignedTxHex());
// submit signed tx using view-only wallet
let txHashes = await viewOnlyWallet.submitTxs(signedTxSet.getSignedTxHex());