packages/jellyfish-transaction/src/tx_composer.ts
import BigNumber from 'bignumber.js'
import { SmartBuffer } from 'smart-buffer'
import { BufferComposer, ComposableBuffer, readCompactSize, writeCompactSize } from '@defichain/jellyfish-buffer'
import { Script, Transaction, TransactionSegWit, Vin, Vout, Witness, WitnessScript } from './tx'
import { OP_CODES, OPCode } from './script'
import { dSHA256 } from '@defichain/jellyfish-crypto'
/**
* USE CTransaction AT YOUR OWN RISK.
* The TransactionBuilder has safety logic built-in to prevent overspent, CTransaction is its raw counter part.
*
* Composable Transaction, C stands for Composable.
* Immutable by design, it implements the Transaction interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*/
export class CTransaction extends ComposableBuffer<Transaction> implements Transaction {
public get version (): number {
return this.data.version
}
public get vin (): Vin[] {
return this.data.vin
}
public get vout (): Vout[] {
return this.data.vout
}
public get lockTime (): number {
return this.data.lockTime
}
composers (tx: Transaction): BufferComposer[] {
return [
ComposableBuffer.uInt32(() => tx.version, v => tx.version = v),
ComposableBuffer.compactSizeArray<Vin>(() => tx.vin, v => tx.vin = v, v => new CVin(v)),
ComposableBuffer.compactSizeArray<Vout>(() => tx.vout, v => tx.vout = v, v => {
if (tx.version < 4) {
return new CVoutV2(v)
}
return new CVoutV4(v)
}),
ComposableBuffer.uInt32(() => tx.lockTime, v => tx.lockTime = v)
]
}
/**
* TransactionId is the double SHA256 of transaction buffer.
* TxId are usually presented in BE order, this method return TxId in BE order.
*
* @return string transaction id
*/
public get txId (): string {
const buffer: SmartBuffer = new SmartBuffer()
this.toBuffer(buffer)
const hash = dSHA256(buffer.toBuffer())
return hash.reverse().toString('hex')
}
}
/**
* Composable Vin, C stands for Composable.
* Immutable by design, it implements the Vin interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*/
export class CVin extends ComposableBuffer<Vin> implements Vin {
/**
* 32 bytes, 64 in hex
*/
public get txid (): string {
return this.data.txid
}
public get index (): number {
return this.data.index
}
public get script (): Script {
return this.data.script
}
public get sequence (): number {
return this.data.sequence
}
composers (vin: Vin): BufferComposer[] {
return [
// defid returns txid in BE order hence we need to reverse it
ComposableBuffer.hexBEBufferLE(32, () => vin.txid, v => vin.txid = v),
ComposableBuffer.uInt32(() => vin.index, v => vin.index = v),
ComposableBuffer.single<Script>(() => vin.script, v => vin.script = v, v => new CScript(v)),
ComposableBuffer.uInt32(() => vin.sequence, v => vin.sequence = v)
]
}
}
/**
* Composable Vout, C stands for Composable.
* Immutable by design, it implements the Vout interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*
* This is Transaction V2 buffer composition
*/
export class CVoutV2 extends ComposableBuffer<Vout> implements Vout {
public get value (): BigNumber {
return this.data.value
}
public get script (): Script {
return this.data.script
}
public get tokenId (): number {
return 0x00
}
composers (vout: Vout): BufferComposer[] {
return [
ComposableBuffer.satoshiAsBigNumber(() => vout.value, v => vout.value = v),
ComposableBuffer.single<Script>(() => vout.script, v => vout.script = v, v => new CScript(v))
]
}
}
/**
* Composable Vout, C stands for Composable.
* Immutable by design, it implements the Vout interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*
* This is Transaction V4 buffer composition
*/
export class CVoutV4 extends ComposableBuffer<Vout> implements Vout {
public get value (): BigNumber {
return this.data.value
}
public get script (): Script {
return this.data.script
}
public get tokenId (): number {
return this.data.tokenId
}
composers (vout: Vout): BufferComposer[] {
return [
ComposableBuffer.satoshiAsBigNumber(() => vout.value, v => vout.value = v),
ComposableBuffer.single<Script>(() => vout.script, v => vout.script = v, v => new CScript(v)),
ComposableBuffer.varInt(() => vout.tokenId, v => vout.tokenId = v)
]
}
}
/**
* Composable Script, C stands for Composable.
* Immutable by design, it implements the Script interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*
* This wraps the OPCode built in composer.
*/
export class CScript extends ComposableBuffer<Script> implements Script {
public get stack (): OPCode[] {
return this.data.stack
}
composers (script: Script): BufferComposer[] {
return [
{
fromBuffer: (buffer: SmartBuffer): void => {
script.stack = OP_CODES.fromBuffer(buffer)
},
toBuffer: (buffer: SmartBuffer): void => {
OP_CODES.toBuffer(script.stack, buffer)
}
}
]
}
}
/**
* USE CTransactionSegWit AT YOUR OWN RISK.
* The TransactionBuilder has safety logic built-in to prevent overspent, CTransactionSegWit is its raw counter part.
*
* Composable TransactionSegWit, C stands for Composable.
* Immutable by design, it implements the TransactionSegWit interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*
* @see https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
* @see https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
* @see https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
*/
export class CTransactionSegWit extends ComposableBuffer<TransactionSegWit> implements TransactionSegWit {
public get version (): number {
return this.data.version
}
public get marker (): number {
return this.data.marker
}
public get flag (): number {
return this.data.flag
}
public get vin (): Vin[] {
return this.data.vin
}
public get vout (): Vout[] {
return this.data.vout
}
public get witness (): Witness[] {
return this.data.witness
}
public get lockTime (): number {
return this.data.lockTime
}
composers (tx: TransactionSegWit): BufferComposer[] {
return [
ComposableBuffer.uInt32(() => tx.version, v => tx.version = v),
ComposableBuffer.uInt8(() => tx.marker, v => tx.marker = v),
ComposableBuffer.uInt8(() => tx.flag, v => tx.flag = v),
ComposableBuffer.compactSizeArray<Vin>(() => tx.vin, v => tx.vin = v, v => new CVin(v)),
ComposableBuffer.compactSizeArray<Vout>(() => tx.vout, v => tx.vout = v, v => {
if (tx.version < 4) {
return new CVoutV2(v)
}
return new CVoutV4(v)
}),
ComposableBuffer.array<Witness>(() => tx.witness, v => tx.witness = v, v => new CWitness(v), () => tx.vin.length),
ComposableBuffer.uInt32(() => tx.lockTime, v => tx.lockTime = v)
]
}
/**
* TransactionId is the double SHA256 of transaction buffer.
* TxId are usually presented in BE order, this method return TxId in BE order.
*
* @return string transaction id
*/
public get txId (): string {
return new CTransaction(this).txId
}
}
/**
* Composable Witness, C stands for Composable.
* Immutable by design, it implements the Witness interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*/
export class CWitness extends ComposableBuffer<Witness> implements Witness {
public get scripts (): WitnessScript[] {
return this.data.scripts
}
composers (wit: Witness): BufferComposer[] {
return [
ComposableBuffer.compactSizeArray<WitnessScript>(() => wit.scripts, v => wit.scripts = v, v => new CWitnessScript(v))
]
}
}
/**
* Composable WitnessScript, C stands for Composable.
* Immutable by design, it implements the WitnessScript interface for convenience.
* Bi-directional fromBuffer, toBuffer deep composer.
*
* This just wraps the WitnessScript with (n = VarUInt, + n Bytes).
*/
export class CWitnessScript extends ComposableBuffer<WitnessScript> implements WitnessScript {
public get hex (): string {
return this.data.hex
}
composers (script: WitnessScript): BufferComposer[] {
return [
{
fromBuffer: (buffer: SmartBuffer): void => {
const len = readCompactSize(buffer)
script.hex = buffer.readString(len, 'hex')
},
toBuffer: (buffer: SmartBuffer): void => {
writeCompactSize(script.hex.length / 2, buffer)
buffer.writeString(script.hex, 'hex')
}
}
]
}
}