Skip to content

Commit

Permalink
feat(jellyfish-transaction-builder): manual input on tx builder (#2163)
Browse files Browse the repository at this point in the history
<!--  Thanks for sending a pull request! -->

#### What this PR does / why we need it:
- support manual input on transaction builder
- rm ListUnspentOptions 

#### Which issue(s) does this PR fixes?:
<!--
(Optional) Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Additional comments?:
  • Loading branch information
canonbrother committed Oct 13, 2023
1 parent 940e085 commit bad733d
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DeFiDRpcError, MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { getProviders, MockProviders } from '../provider.mock'
import { P2WPKHTransactionBuilder } from '../../src'
import { P2WPKHTransactionBuilder, Prevout } from '../../src'
import { fundEllipticPair, sendTransaction } from '../test.utils'
import BigNumber from 'bignumber.js'
import { Interface, ethers } from 'ethers'
Expand Down Expand Up @@ -166,7 +166,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -223,7 +233,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -280,7 +300,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -337,7 +367,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -394,7 +434,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -451,7 +501,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -510,7 +570,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -568,7 +638,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -669,7 +749,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)

await expect(promise).rejects.toThrow(DeFiDRpcError)
Expand Down Expand Up @@ -875,7 +965,17 @@ describe('transferDomain', () => {
]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const promise = sendTransaction(testing.container, txn)
await expect(promise).rejects.toThrow(DeFiDRpcError)
await expect(promise).rejects.toThrow('TransferDomain currently only supports a single transfer per transaction')
Expand Down Expand Up @@ -935,10 +1035,18 @@ describe('transferDomain', () => {
}
}]
}
// NOTE(canonbrother): `maximumAmount` is a workaround to grab only single vin
// since maximumCount behaviour does not return by provided value
// but catch up total utxos of the tokenId
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })

const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const outs = await sendTransaction(container, txn)
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
const expectedTransferDomainScript = `6a${encoded}`
Expand Down Expand Up @@ -1026,7 +1134,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const outs = await sendTransaction(container, txn)
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
const expectedTransferDomainScript = `6a${encoded}`
Expand Down Expand Up @@ -1118,7 +1236,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const outs = await sendTransaction(container, txn)
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
const expectedTransferDomainScript = `6a${encoded}`
Expand Down Expand Up @@ -1205,7 +1333,17 @@ describe('transferDomain', () => {
}]
}

const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
const utxos: any[] = await container.call('listunspent', [
1, 9999999, [dvmAddr], true
])
const utxo: Prevout = {
txid: utxos[0].txid,
vout: utxos[0].vout,
value: new BigNumber(utxos[0].amount),
script: dvmScript,
tokenId: utxos[0].tokenId
}
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
const outs = await sendTransaction(container, txn)
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
const expectedTransferDomainScript = `6a${encoded}`
Expand Down
8 changes: 1 addition & 7 deletions packages/jellyfish-transaction-builder/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,9 @@ export interface PrevoutProvider {
*
* @param {BigNumber} minBalance of balance combined in a Prevout required for a single transaction.
* required to create transaction.
* @param {ListUnspentOptions} [options]
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
* @param {string} [options.tokenId] default is 'all', filter by token
* @return {Prevout[]} selected all required for creating the transaction
*/
collect: (minBalance: BigNumber, options?: ListUnspentQueryOptions) => Promise<Prevout[]>
collect: (minBalance: BigNumber) => Promise<Prevout[]>
}

/**
Expand Down
37 changes: 20 additions & 17 deletions packages/jellyfish-transaction-builder/src/txn/txn_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@defichain/jellyfish-transaction'
import { SignInputOption, TransactionSigner } from '@defichain/jellyfish-transaction-signature'
import BigNumber from 'bignumber.js'
import { EllipticPairProvider, FeeRateProvider, ListUnspentQueryOptions, Prevout, PrevoutProvider } from '../provider'
import { EllipticPairProvider, FeeRateProvider, Prevout, PrevoutProvider } from '../provider'
import { calculateFeeP2WPKH } from './txn_fee'
import { TxnBuilderError, TxnBuilderErrorType } from './txn_builder_error'
import { EllipticPair } from '@defichain/jellyfish-crypto'
Expand Down Expand Up @@ -45,16 +45,10 @@ export abstract class P2WPKHTxnBuilder {

/**
* @param {BigNumber} minBalance to collect, required to form a transaction
* @param {ListUnspentOptions} [options]
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
* @param {string} [options.tokenId] default is 'all', filter by token
* @return {Promise<Prevouts>}
*/
protected async collectPrevouts (minBalance: BigNumber, options?: ListUnspentQueryOptions): Promise<Prevouts> {
const prevouts = await this.prevoutProvider.collect(minBalance, options)
protected async collectPrevouts (minBalance: BigNumber): Promise<Prevouts> {
const prevouts = await this.prevoutProvider.collect(minBalance)
if (prevouts.length === 0) {
throw new TxnBuilderError(TxnBuilderErrorType.NO_PREVOUTS,
'no prevouts available to create a transaction'
Expand Down Expand Up @@ -104,21 +98,30 @@ export abstract class P2WPKHTxnBuilder {
* @param {OP_DEFI_TX} opDeFiTx to create
* @param {Script} changeScript to send unspent to after deducting the fees
* @param {BigNumber} [outValue=0] for the opDeFiTx, usually always be 0.
* @param {ListUnspentOptions} [options]
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
* @param {string} [options.tokenId] default is 'all', filter by token
* @param {Prevout[]} [utxos=[]] provide it if you want to spent specific UTXOs
*/
async createDeFiTx (
opDeFiTx: OP_DEFI_TX,
changeScript: Script,
outValue: BigNumber = new BigNumber('0'),
options: ListUnspentQueryOptions = {}
utxos: Prevout[] = []
): Promise<TransactionSegWit> {
const minFee = outValue.plus(0.001) // see JSDoc above
const { prevouts, vin, total } = await this.collectPrevouts(minFee, options)

let prevouts: Prevout[] = []
let vin: Vin[] = []
let total: BigNumber = new BigNumber('0')

if (utxos.length > 0) {
({ prevouts, vin, total } = joinPrevouts(utxos))
if (minFee.gt(total)) {
throw new TxnBuilderError(TxnBuilderErrorType.MIN_BALANCE_NOT_ENOUGH,
'not enough balance after combing all prevouts'
)
}
} else {
({ prevouts, vin, total } = await this.collectPrevouts(minFee))
}

const deFiOut: Vout = {
value: outValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@defichain/jellyfish-transaction'
import { P2WPKHTxnBuilder } from './txn_builder'
import { TxnBuilderError, TxnBuilderErrorType } from './txn_builder_error'
import { ListUnspentQueryOptions } from '../provider'
import { Prevout } from '../provider'

export class TxnBuilderAccount extends P2WPKHTxnBuilder {
/**
Expand Down Expand Up @@ -145,21 +145,16 @@ export class TxnBuilderAccount extends P2WPKHTxnBuilder {
*
* @param {TransferDomain} transferDomain txn to create
* @param {Script} changeScript to send unspent to after deducting the (converted + fees)
* @param {ListUnspentOptions} [options]
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
* @param {string} [options.tokenId] default is 'all', filter by token
* @param {Prevout[]} utxos to be speficically provided if needed
* @returns {Promise<TransactionSegWit>}
*/

async transferDomain (transferDomain: TransferDomain, changeScript: Script, options: ListUnspentQueryOptions = {}): Promise<TransactionSegWit> {
async transferDomain (transferDomain: TransferDomain, changeScript: Script, utxos: Prevout[] = []): Promise<TransactionSegWit> {
return await super.createDeFiTx(
OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain),
changeScript,
new BigNumber(0),
options
utxos
)
}
}

0 comments on commit bad733d

Please sign in to comment.