The original client asked BlockCypher to propose a Bitcoin transaction, signed the returned digests locally, and sent the result back for broadcast. Its two branches targeted legacy P2PKH and native P2WPKH.
Follow the boundaries
The private key stays in the local process, but the service supplies the material being signed. Keeping a key local does not establish that the proposed inputs, outputs, fee, and signing semantics match the intended payment. Those must be checked before signing.
Read the historical client
require('dotenv').config();
import { ECPairFactory } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';
const axios = require('axios').default;
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
const env = {
['development']: {
network: bitcoin.networks.testnet,
blockCypherApi: 'https://api.blockcypher.com/v1/btc/test3',
explorer: 'https://live.blockcypher.com/btc-testnet/tx',
},
['production']: {
network: bitcoin.networks.bitcoin,
blockCypherApi: 'https://api.blockcypher.com/v1/btc/main',
explorer: 'https://live.blockcypher.com/btc/tx',
},
};
const { network, blockCypherApi, explorer } = env[process.env.NODE_ENV || 'development'];
/**
* Prepare wallet from a Wallet Import Format (WIF) private key
* Each network -> Each address has own private key.
*/
export const initKeyPair = (WIF = process.env.WALLET_PRIVATE_KEY) => {
if (!WIF) throw new Error('WALLET_PRIVATE_KEY is not set');
// Decode WIF private key
const keyPair = ECPair.fromWIF(WIF, network);
return {
keyPair,
publicKey: keyPair.publicKey,
};
};
// Native Segwit P2WPKH, bech32 address type with 42 chars. Start with tb1q or bc1q
// Ex: tb1q2fzh9f7ss8eu56fnv2zhr56a0wd3g64xp7d4xk
export const getNativeSegwitP2WPKH = (publicKeyBuffer: Buffer) => {
return bitcoin.payments.p2wpkh({
pubkey: publicKeyBuffer,
network: network,
});
};
// P2PKH type with 34 chars
// Ex: mo1xu79z1354YicBa5EXwMh7HrCAfRdPSW
export const getLegacyP2PKH = (publicKeyBuffer: Buffer) => {
return bitcoin.payments.p2pkh({ pubkey: publicKeyBuffer, network: network });
};
/**
* Send BTC with BlockCypher.
* Support 2 address types: Native Segwit P2WPKH, Legacy P2PKH
* https://www.blockcypher.com/dev/bitcoin/data/samples/create-tx.html
* https://gist.github.com/matthieu/b07c5ba27bc99188a15f
* https://www.blockcypher.com/dev/bitcoin/#restful-resources
* Usage:
* ** For Legacy P2PKH address type
* await sendBitcoinWithBlockCypher(true, 'tb1qpcvt26nnu4s75p2ur5nflvwcqq3p38yh2ny438', 1000);
* Res: From (Legacy P2PKH) mo1xu79z1354YicBa5EXwMh7HrCAfRdPSW to tb1qpcvt26nnu4s75p2ur5nflvwcqq3p38yh2ny438 amount 0.00001 BTC
* Transaction hash: https://live.blockcypher.com/btc-testnet/tx/dd6f9246e9e8d62739cdc2708680da9f010956692bd5a0792036183371b8bd70
*
* ** For Native Segwit P2WPKH address type
* await sendBitcoinWithBlockCypher(false, 'tb1qpcvt26nnu4s75p2ur5nflvwcqq3p38yh2ny438', 1000);
* From (Native Segwit P2WPKH) tb1q2fzh9f7ss8eu56fnv2zhr56a0wd3g64xp7d4xk to tb1qpcvt26nnu4s75p2ur5nflvwcqq3p38yh2ny438 amount 0.00001 BTC
* Transaction hash: https://live.blockcypher.com/btc-testnet/tx/742baa3da4e1b9968755e4be43745f3e5039b8be373dc512485b4be12154ace9
*/
export const sendBitcoinWithBlockCypher = async (useLegacyP2PKH: boolean, to: string, amount = 1000) => {
const { keyPair, publicKey } = initKeyPair();
let senderPayment = getLegacyP2PKH(publicKey);
let senderType = 'Legacy P2PKH';
if (!useLegacyP2PKH) {
senderPayment = getNativeSegwitP2WPKH(publicKey);
senderType = 'Native Segwit P2WPKH';
}
const sender = senderPayment.address;
console.log(`From (${senderType}) ${sender} to ${to} amount ${(amount / 1e8).toFixed(8)} BTC `);
// Init config
const txInOut = {
inputs: [{ addresses: [sender] }],
outputs: [{ addresses: [to], value: amount }],
};
try {
// Generate tx input, output for signing
const txInfo = (await axios.post(`${blockCypherApi}/txs/new`, txInOut)).data;
// Sign tx
txInfo.pubkeys = [];
txInfo.signatures = txInfo.tosign.map((tosign: string) => {
// Public key in Hex format with 66 chars
txInfo.pubkeys.push(publicKey.toString('hex'));
const signature = keyPair.sign(Buffer.from(tosign, 'hex'));
// Legacy P2PKH Signature: SIGHASH_NONE with 142 chars
let finalSig = bitcoin.script.signature
.encode(signature, bitcoin.Transaction.SIGHASH_NONE)
.toString('hex')
.slice(0, -2);
if (!useLegacyP2PKH) {
// Native Segwit P2WPKH Signature: SIGHASH_ALL with 144 chars end with '01'
finalSig = bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL).toString('hex');
}
return finalSig;
});
try {
const res = await axios.post(`${blockCypherApi}/txs/send`, txInfo);
if (res.data) {
console.log('Transaction hash:', `${explorer}/${res.data.tx.hash}`);
}
} catch (e) {
console.error('Broadcast tx error:', e.message, e.response?.data.errors);
}
} catch (e) {
console.error('Create tx error:', e.message, e.response?.data.errors);
}
};
The source uses different signature-encoding treatment for the two branches and labels one with SIGHASH_NONE while removing its final byte. That code/comment combination should not be treated as proof of the service’s signing contract. This revision does not establish current API compatibility or successful execution for either branch.
A returned hash is a stage result
Constructing a proposal, producing signatures, accepting a broadcast, and obtaining confirmations are distinct outcomes. Error handling also matters: the old helper logs failures without returning a structured success/failure result to the caller.
The comments’ address examples and transaction links are historical public data. No private key, API credential, payment, signature, or broadcast was used here. Source: Gist revision.