Skip to content

Commit

Permalink
Merge pull request #138 from Bitcoin-com/cgc-sweep
Browse files Browse the repository at this point in the history
v8.8.0
  • Loading branch information
Gabriel Cardona committed Aug 21, 2019
2 parents 303c114 + b3677d3 commit 6d068da
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 16 deletions.
1 change: 1 addition & 0 deletions lib/Address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export class Address {
address: string | string[]
): Promise<AddressDetailsResult | AddressDetailsResult[]> {
try {

// Handle single address.
if (typeof address === "string") {
const response: AxiosResponse = await axios.get(
Expand Down
124 changes: 119 additions & 5 deletions lib/Util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
// imports
import axios, { AxiosResponse } from "axios"
import { AddressDetailsResult, AddressUtxoResult, utxo } from "bitcoin-com-rest"
import * as bcl from "bitcoincashjs-lib"
import { Address } from "./Address"
import { REST_URL } from "./BITBOX"
import { BitcoinCash } from "./BitcoinCash"
import { ECPair } from "./ECPair"
import { RawTransactions } from "./RawTransactions"
import { TransactionBuilder } from "./TransactionBuilder"

export interface AddressDetails {
isvalid: boolean
Expand All @@ -15,10 +23,17 @@ export interface AddressDetails {

export class Util {
public restURL: string
public address: Address
public ecPair: ECPair
public bitcoinCash: BitcoinCash
public rawTransactions: RawTransactions
constructor(restURL: string = REST_URL) {
this.restURL = restURL
this.address = new Address(restURL)
this.ecPair = new ECPair(this.address)
this.bitcoinCash = new BitcoinCash(this.address)
this.rawTransactions = new RawTransactions(restURL)
}

public async validateAddress(
address: string | string[]
): Promise<AddressDetails | AddressDetails[]> {
Expand All @@ -29,7 +44,6 @@ export class Util {
`${this.restURL}util/validateAddress/${address}`
)
return response.data

// Array of blocks.
} else if (Array.isArray(address)) {
// Dev note: must use axios.post for unit test stubbing.
Expand All @@ -39,14 +53,114 @@ export class Util {
addresses: address
}
)

return response.data
}

throw new Error(`Input must be a string or array of strings.`)
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
// Sweep a private key in compressed WIF format and sends funds to another
// address.
// Passing in optional balanceOnly flag will return just the balance without
// actually moving the funds.
// Or 0 if no funds are found, otherwise:
// Returns an object containing the amount of BCH swept from address,
// and the txid of the generated transaction that swept the funds.
async sweep(wif: string, toAddr: string, balanceOnly: boolean = false) {
try {
// Input validation
if (!wif || wif === "") {
throw new Error(
`wif private key must be included in Compressed WIF format.`
)
}
// Input validation
if (!balanceOnly) {
if (!toAddr || toAddr === "") {
throw new Error(
`Address to receive swept funds must be included unless balanceOnly flag is true.`
)
}
}
// Generate a keypair from the WIF.
const keyPair: bcl.ECPair = this.ecPair.fromWIF(wif)

// Generate the public address associated with the private key.
const fromAddr: string = this.ecPair.toCashAddress(keyPair)

// Check the BCH balance of that public address.
const details = (await this.address.details(
fromAddr
)) as AddressDetailsResult
const balance: number = details.balance

// If balance is zero or balanceOnly flag is passed in, exit.
if (balance === 0 || balanceOnly) return balance

// Get UTXOs associated with public address.
const u = (await this.address.utxo(fromAddr)) as AddressUtxoResult
const utxos: utxo[] = u.utxos

// Prepare to generate a transaction to sweep funds.

const transactionBuilder: TransactionBuilder = new TransactionBuilder(
this.address.detectAddressNetwork(fromAddr)
)
let originalAmount: number = 0

// Add all UTXOs to the transaction inputs.
for (let i: number = 0; i < utxos.length; i++) {
const utxo: utxo = utxos[i]
originalAmount = originalAmount + utxo.satoshis
transactionBuilder.addInput(utxo.txid, utxo.vout)
}

if (originalAmount < 1)
throw new Error(`Original amount is zero. No BCH to send.`)

// get byte count to calculate fee. paying 1.1 sat/byte
const byteCount: number = this.bitcoinCash.getByteCount(
{ P2PKH: utxos.length },
{ P2PKH: 1 }
)
const fee: number = Math.ceil(1.1 * byteCount)

// amount to send to receiver. It's the original amount - 1 sat/byte for tx size
const sendAmount: number = originalAmount - fee

// add output w/ address and amount to send
transactionBuilder.addOutput(
this.address.toLegacyAddress(toAddr),
sendAmount
)

// Loop through each input and sign it with the private key.
let redeemScript: undefined
for (let i: number = 0; i < utxos.length; i++) {
const utxo = utxos[i]
transactionBuilder.sign(
i,
keyPair,
redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.satoshis
)
}

// build tx
const tx: any = transactionBuilder.build()

// output rawhex
const hex: string = tx.toHex()

// Broadcast the transaction to the BCH network.
let txid: string = await this.rawTransactions.sendRawTransaction(hex)
return txid
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
20 changes: 10 additions & 10 deletions lib/interfaces/vendors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,20 @@ declare module "bitcoin-com-rest" {
cashAddress: string
}

export interface utxo {
txid: string
vout: number
amount: number
satoshis: number
height: number
confirmations: number
}

export interface AddressUtxoResult {
legacyAddress: string
cashAddress: string
scriptPubKey: string
utxos: [
{
txid: string
vout: number
amount: number
satoshis: number
height: number
confirmations: number
}
]
utxos: utxo[]
}

export interface AddressUnconfirmedResult {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitbox-sdk",
"version": "8.7.0",
"version": "8.8.0",
"description": "BITBOX SDK for Bitcoin Cash",
"author": "Gabriel Cardona <gabriel@bitcoin.com>",
"contributors": [
Expand Down
15 changes: 15 additions & 0 deletions test/integration/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,19 @@ describe(`#util`, () => {
}
})
})

describe("#sweep", () => {
it("should return balance only", async () => {
const wif = "L287yGQj4DB4fbUKSV7DMHsyGQs1qh2E3EYJ21P88mXNKaFvmNWk"
const result = await bitbox.Util.sweep(wif, "abc123", true)
console.log(`result: ${result}`)
})

it("should sweep funds", async () => {
const wif = "L287yGQj4DB4fbUKSV7DMHsyGQs1qh2E3EYJ21P88mXNKaFvmNWk"
const toAddr = "bitcoincash:qqjes5sxwneywmnzqndvs6p3l9rp55a2ug0e6e6s0a"
const result = await bitbox.Util.sweep(wif, toAddr)
console.log(`result: ${result}`)
})
})
})

0 comments on commit 6d068da

Please sign in to comment.