The original Dogecoin note consisted of a TypeScript utility for testnet. It generated a mnemonic-derived wallet, constructed a legacy P2PKH payment, fetched UTXOs and previous transactions, then built and signed a PSBT.
The recorded source
Read the historical Dogecoin utility
/**
* https://forum.dogecoin.org/d/60-doge-testnet-tooling-faucet--public-electrs-api--block-explorer--web-wallet
* Wallet: https://testnet-wallet.doge.toys
* Explorer: https://doge-testnet-explorer.qed.me
* API: https://doge-electrs-testnet-demo.qed.me
* Faucet: https://faucet.doge.toys
* Libs:
* - "@dogiwallet/dogecoinjs-lib": "^0.1.4",
* - "bip32": "^5.0.0-rc.0",
* - "bip39": "^3.1.0",
* - "ecpair": "^3.0.0",
* - "tiny-secp256k1": "^2.2.3"
*/
import * as dogecoin from '@dogiwallet/dogecoinjs-lib';
import axios from 'axios';
import BIP32Factory from 'bip32';
import * as bip39 from 'bip39';
import { ECPairFactory } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
const ECPair = ECPairFactory(ecc);
const ONE_DOGE = 100_000_000; // 1 DOGE in satoshis
const network = dogecoin.networks.dogecoinTestnet;
/**
* Create a new Dogecoin Wallet (KeyPair) and return the WIF private key and public address
*/
export function createDogeWallet() {
// Generate new key pair
// const keyPair = ECPair.makeRandom({ network: dogecoin.networks.dogecoinTestnet });
// Get WIF private key
// const privateKeyWIF = keyPair.toWIF();
const bip32 = BIP32Factory(ecc);
const mnemonic = bip39.generateMnemonic();
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed, network);
const child = root.derivePath(`m/44'/${network === dogecoin.networks.dogecoinTestnet ? '1' : '3'}'/0'/0/0`); // 1' for testnet, 3' for mainnet
const privateKeyWIF = child.toWIF();
const keyPair = ECPair.fromWIF(privateKeyWIF, network);
// Get public address
const { address } = getLegacyP2PKH(Buffer.from(keyPair.publicKey));
return {
privateKey: privateKeyWIF,
address,
mnemonic,
};
}
// P2PKH type with 34 chars
// Ex: nVaCZYVpHi1hVXqCUmJ6noogUKXwMkMC4z
export const getLegacyP2PKH = (publicKeyBuffer: Buffer) => {
return dogecoin.payments.p2pkh({ pubkey: publicKeyBuffer, network: network });
};
export const initKeyPair = (WIF) => {
if (!WIF) throw new Error('WALLET_PRIVATE_KEY is not set');
const keyPair = ECPair.fromWIF(WIF, network);
const signer: dogecoin.Signer = {
publicKey: Buffer.from(keyPair.publicKey),
sign: (hash: Buffer) => Buffer.from(keyPair.sign(hash)),
};
return {
signer,
publicKey: Buffer.from(keyPair.publicKey),
};
};
export const transferDoge = async () => {
const { signer } = initKeyPair(process.env.DOGE_PRIVATE_WIF);
const { address: sender } = getLegacyP2PKH(signer.publicKey);
console.log('Signer address:', sender);
const to = sender;
const amount = ONE_DOGE;
const api = 'https://doge-electrs-testnet-demo.qed.me';
interface IUTXO {
txid: string;
vout: number;
value: number;
}
const getUTXO = async (address: string): Promise<IUTXO[]> => {
const restURL = `${api}/address/${address}/utxo`;
const res = await axios.get(restURL);
return res.data;
};
const getTxHex = async (txId: string): Promise<string> => {
const restURL = `${api}/tx/${txId}/hex`;
const res = await axios.get(restURL);
return res.data;
};
const broadcast = async (txHex: string) => {
try {
console.log('Broadcasting tx:', txHex);
const res = await axios.post(`${api}/tx`, txHex);
return res.data;
} catch (e) {
console.error('Broadcast tx error:', e.message, e.response?.data);
}
};
const utxos = await getUTXO(sender);
if (!utxos.length) throw new Error('No UTXO found');
const totalUnspent = utxos.reduce((sum, { value }) => sum + value, 0);
// Build transaction
/**
* @description Estimate fee for a transaction
* For a simple P2PKH transaction:
* - Input size: ~148 bytes per input
* - Output size: ~34 bytes per output
* - Overhead: ~10 bytes
*/
const estimateFee = (numInputs, numOutputs, feeRate) => {
const txSize = 10 + numInputs * 148 + numOutputs * 34;
return txSize * feeRate;
};
const feeRate = 1_000; // sat/vB
const maximumFeeRate = feeRate * 1.5;
const psbt = new dogecoin.Psbt({ network, maximumFeeRate });
const inputs: any[] = [];
for (const utxo of utxos) {
const input = { hash: utxo.txid, index: utxo.vout, sequence: 0xffffffff };
const hex = await getTxHex(utxo.txid);
input['nonWitnessUtxo'] = Buffer.from(hex, 'hex');
inputs.push(input);
}
psbt.addInputs(inputs);
psbt.addOutput({ address: to, value: amount });
// Validate final output with fee
const remainBal = totalUnspent - amount;
const numInputs = utxos.length;
const numOutputs = 2; // to + change
const finalFee = estimateFee(numInputs, numOutputs, feeRate);
console.info(`Inputs: ${numInputs}, Outputs: ${numOutputs}, FeeRate: ${feeRate}, Estimated Fee: ${finalFee}`);
const DUST_THRESHOLD = ONE_DOGE;
if (totalUnspent < finalFee + amount + DUST_THRESHOLD) {
throw `Total less than fee + amount + DUST_THRESHOLD: ${totalUnspent} < ${finalFee} + ${amount} + ${DUST_THRESHOLD}`;
}
psbt.addOutput({ address: sender, value: remainBal - finalFee });
// Sign transaction
psbt.signAllInputs(signer);
psbt.finalizeAllInputs();
// Broadcast transaction
const tx = psbt.extractTransaction();
const txId = await broadcast(tx.toHex());
txId && console.log(`Transaction successfully broadcasted! TxId is https://doge-testnet-explorer.qed.me/tx/${txId}`);
};
The comments record @dogiwallet/dogecoinjs-lib 0.1.4, a bip32 5 release candidate, bip39 3.1.0, ecpair 3.0.0, and tiny-secp256k1 2.2.3 ranges. This is source context, not a newly validated package combination.
Derivation and transfer have different boundaries
The wallet helper returns a WIF private key and mnemonic alongside the address. That object contains secrets even when it is convenient for a local experiment; it should not be treated as ordinary log or UI data. The transfer helper instead loads WIF from DOGE_PRIVATE_WIF. No key value appears in the article.
The network is explicitly Dogecoin testnet, and the example destination equals the sender. Public faucet/API links in code comments are historical references, not verified current services.
Estimate, sign, then inspect the actual result
The helper selects every discovered UTXO and estimates size from fixed P2PKH input/output constants. Its fee rate and one-DOGE dust threshold are example policy assumptions, not universal network rules. The final signed transaction and actual policy need separate checks.
The broadcast helper catches an error and returns no explicit failure object. A log or returned transaction ID also does not establish confirmation. No mnemonic, wallet, signature, network request, or transaction was generated for this revision. Source: Gist revision.