Skip to content

Commit bad733d

Browse files
authored
feat(jellyfish-transaction-builder): manual input on tx builder (#2163)
<!-- 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?:
1 parent 940e085 commit bad733d

File tree

4 files changed

+181
-51
lines changed

4 files changed

+181
-51
lines changed

packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_account_transfer_domain.test.ts

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DeFiDRpcError, MasterNodeRegTestContainer } from '@defichain/testcontainers'
22
import { getProviders, MockProviders } from '../provider.mock'
3-
import { P2WPKHTransactionBuilder } from '../../src'
3+
import { P2WPKHTransactionBuilder, Prevout } from '../../src'
44
import { fundEllipticPair, sendTransaction } from '../test.utils'
55
import BigNumber from 'bignumber.js'
66
import { Interface, ethers } from 'ethers'
@@ -166,7 +166,17 @@ describe('transferDomain', () => {
166166
}]
167167
}
168168

169-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
169+
const utxos: any[] = await container.call('listunspent', [
170+
1, 9999999, [dvmAddr], true
171+
])
172+
const utxo: Prevout = {
173+
txid: utxos[0].txid,
174+
vout: utxos[0].vout,
175+
value: new BigNumber(utxos[0].amount),
176+
script: dvmScript,
177+
tokenId: utxos[0].tokenId
178+
}
179+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
170180
const promise = sendTransaction(testing.container, txn)
171181

172182
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -223,7 +233,17 @@ describe('transferDomain', () => {
223233
}]
224234
}
225235

226-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
236+
const utxos: any[] = await container.call('listunspent', [
237+
1, 9999999, [dvmAddr], true
238+
])
239+
const utxo: Prevout = {
240+
txid: utxos[0].txid,
241+
vout: utxos[0].vout,
242+
value: new BigNumber(utxos[0].amount),
243+
script: dvmScript,
244+
tokenId: utxos[0].tokenId
245+
}
246+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
227247
const promise = sendTransaction(testing.container, txn)
228248

229249
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -280,7 +300,17 @@ describe('transferDomain', () => {
280300
}]
281301
}
282302

283-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
303+
const utxos: any[] = await container.call('listunspent', [
304+
1, 9999999, [dvmAddr], true
305+
])
306+
const utxo: Prevout = {
307+
txid: utxos[0].txid,
308+
vout: utxos[0].vout,
309+
value: new BigNumber(utxos[0].amount),
310+
script: dvmScript,
311+
tokenId: utxos[0].tokenId
312+
}
313+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
284314
const promise = sendTransaction(testing.container, txn)
285315

286316
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -337,7 +367,17 @@ describe('transferDomain', () => {
337367
}]
338368
}
339369

340-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
370+
const utxos: any[] = await container.call('listunspent', [
371+
1, 9999999, [dvmAddr], true
372+
])
373+
const utxo: Prevout = {
374+
txid: utxos[0].txid,
375+
vout: utxos[0].vout,
376+
value: new BigNumber(utxos[0].amount),
377+
script: dvmScript,
378+
tokenId: utxos[0].tokenId
379+
}
380+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
341381
const promise = sendTransaction(testing.container, txn)
342382

343383
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -394,7 +434,17 @@ describe('transferDomain', () => {
394434
}]
395435
}
396436

397-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
437+
const utxos: any[] = await container.call('listunspent', [
438+
1, 9999999, [dvmAddr], true
439+
])
440+
const utxo: Prevout = {
441+
txid: utxos[0].txid,
442+
vout: utxos[0].vout,
443+
value: new BigNumber(utxos[0].amount),
444+
script: dvmScript,
445+
tokenId: utxos[0].tokenId
446+
}
447+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
398448
const promise = sendTransaction(testing.container, txn)
399449

400450
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -451,7 +501,17 @@ describe('transferDomain', () => {
451501
}]
452502
}
453503

454-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
504+
const utxos: any[] = await container.call('listunspent', [
505+
1, 9999999, [dvmAddr], true
506+
])
507+
const utxo: Prevout = {
508+
txid: utxos[0].txid,
509+
vout: utxos[0].vout,
510+
value: new BigNumber(utxos[0].amount),
511+
script: dvmScript,
512+
tokenId: utxos[0].tokenId
513+
}
514+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
455515
const promise = sendTransaction(testing.container, txn)
456516

457517
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -510,7 +570,17 @@ describe('transferDomain', () => {
510570
}]
511571
}
512572

513-
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, { maximumAmount: 50 })
573+
const utxos: any[] = await container.call('listunspent', [
574+
1, 9999999, [dvmAddr], true
575+
])
576+
const utxo: Prevout = {
577+
txid: utxos[0].txid,
578+
vout: utxos[0].vout,
579+
value: new BigNumber(utxos[0].amount),
580+
script: dvmScript,
581+
tokenId: utxos[0].tokenId
582+
}
583+
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, [utxo])
514584
const promise = sendTransaction(testing.container, txn)
515585

516586
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -568,7 +638,17 @@ describe('transferDomain', () => {
568638
}]
569639
}
570640

571-
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, { maximumAmount: 50 })
641+
const utxos: any[] = await container.call('listunspent', [
642+
1, 9999999, [dvmAddr], true
643+
])
644+
const utxo: Prevout = {
645+
txid: utxos[0].txid,
646+
vout: utxos[0].vout,
647+
value: new BigNumber(utxos[0].amount),
648+
script: dvmScript,
649+
tokenId: utxos[0].tokenId
650+
}
651+
const txn = await builder.account.transferDomain(transferDomain, invalidDvmScript, [utxo])
572652
const promise = sendTransaction(testing.container, txn)
573653

574654
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -669,7 +749,17 @@ describe('transferDomain', () => {
669749
}]
670750
}
671751

672-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
752+
const utxos: any[] = await container.call('listunspent', [
753+
1, 9999999, [dvmAddr], true
754+
])
755+
const utxo: Prevout = {
756+
txid: utxos[0].txid,
757+
vout: utxos[0].vout,
758+
value: new BigNumber(utxos[0].amount),
759+
script: dvmScript,
760+
tokenId: utxos[0].tokenId
761+
}
762+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
673763
const promise = sendTransaction(testing.container, txn)
674764

675765
await expect(promise).rejects.toThrow(DeFiDRpcError)
@@ -875,7 +965,17 @@ describe('transferDomain', () => {
875965
]
876966
}
877967

878-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
968+
const utxos: any[] = await container.call('listunspent', [
969+
1, 9999999, [dvmAddr], true
970+
])
971+
const utxo: Prevout = {
972+
txid: utxos[0].txid,
973+
vout: utxos[0].vout,
974+
value: new BigNumber(utxos[0].amount),
975+
script: dvmScript,
976+
tokenId: utxos[0].tokenId
977+
}
978+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
879979
const promise = sendTransaction(testing.container, txn)
880980
await expect(promise).rejects.toThrow(DeFiDRpcError)
881981
await expect(promise).rejects.toThrow('TransferDomain currently only supports a single transfer per transaction')
@@ -935,10 +1035,18 @@ describe('transferDomain', () => {
9351035
}
9361036
}]
9371037
}
938-
// NOTE(canonbrother): `maximumAmount` is a workaround to grab only single vin
939-
// since maximumCount behaviour does not return by provided value
940-
// but catch up total utxos of the tokenId
941-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
1038+
1039+
const utxos: any[] = await container.call('listunspent', [
1040+
1, 9999999, [dvmAddr], true
1041+
])
1042+
const utxo: Prevout = {
1043+
txid: utxos[0].txid,
1044+
vout: utxos[0].vout,
1045+
value: new BigNumber(utxos[0].amount),
1046+
script: dvmScript,
1047+
tokenId: utxos[0].tokenId
1048+
}
1049+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
9421050
const outs = await sendTransaction(container, txn)
9431051
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
9441052
const expectedTransferDomainScript = `6a${encoded}`
@@ -1026,7 +1134,17 @@ describe('transferDomain', () => {
10261134
}]
10271135
}
10281136

1029-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
1137+
const utxos: any[] = await container.call('listunspent', [
1138+
1, 9999999, [dvmAddr], true
1139+
])
1140+
const utxo: Prevout = {
1141+
txid: utxos[0].txid,
1142+
vout: utxos[0].vout,
1143+
value: new BigNumber(utxos[0].amount),
1144+
script: dvmScript,
1145+
tokenId: utxos[0].tokenId
1146+
}
1147+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
10301148
const outs = await sendTransaction(container, txn)
10311149
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
10321150
const expectedTransferDomainScript = `6a${encoded}`
@@ -1118,7 +1236,17 @@ describe('transferDomain', () => {
11181236
}]
11191237
}
11201238

1121-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
1239+
const utxos: any[] = await container.call('listunspent', [
1240+
1, 9999999, [dvmAddr], true
1241+
])
1242+
const utxo: Prevout = {
1243+
txid: utxos[0].txid,
1244+
vout: utxos[0].vout,
1245+
value: new BigNumber(utxos[0].amount),
1246+
script: dvmScript,
1247+
tokenId: utxos[0].tokenId
1248+
}
1249+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
11221250
const outs = await sendTransaction(container, txn)
11231251
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
11241252
const expectedTransferDomainScript = `6a${encoded}`
@@ -1205,7 +1333,17 @@ describe('transferDomain', () => {
12051333
}]
12061334
}
12071335

1208-
const txn = await builder.account.transferDomain(transferDomain, dvmScript, { maximumAmount: 50 })
1336+
const utxos: any[] = await container.call('listunspent', [
1337+
1, 9999999, [dvmAddr], true
1338+
])
1339+
const utxo: Prevout = {
1340+
txid: utxos[0].txid,
1341+
vout: utxos[0].vout,
1342+
value: new BigNumber(utxos[0].amount),
1343+
script: dvmScript,
1344+
tokenId: utxos[0].tokenId
1345+
}
1346+
const txn = await builder.account.transferDomain(transferDomain, dvmScript, [utxo])
12091347
const outs = await sendTransaction(container, txn)
12101348
const encoded: string = OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain).asBuffer().toString('hex')
12111349
const expectedTransferDomainScript = `6a${encoded}`

packages/jellyfish-transaction-builder/src/provider.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,9 @@ export interface PrevoutProvider {
3030
*
3131
* @param {BigNumber} minBalance of balance combined in a Prevout required for a single transaction.
3232
* required to create transaction.
33-
* @param {ListUnspentOptions} [options]
34-
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
35-
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
36-
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
37-
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
38-
* @param {string} [options.tokenId] default is 'all', filter by token
3933
* @return {Prevout[]} selected all required for creating the transaction
4034
*/
41-
collect: (minBalance: BigNumber, options?: ListUnspentQueryOptions) => Promise<Prevout[]>
35+
collect: (minBalance: BigNumber) => Promise<Prevout[]>
4236
}
4337

4438
/**

packages/jellyfish-transaction-builder/src/txn/txn_builder.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@defichain/jellyfish-transaction'
1111
import { SignInputOption, TransactionSigner } from '@defichain/jellyfish-transaction-signature'
1212
import BigNumber from 'bignumber.js'
13-
import { EllipticPairProvider, FeeRateProvider, ListUnspentQueryOptions, Prevout, PrevoutProvider } from '../provider'
13+
import { EllipticPairProvider, FeeRateProvider, Prevout, PrevoutProvider } from '../provider'
1414
import { calculateFeeP2WPKH } from './txn_fee'
1515
import { TxnBuilderError, TxnBuilderErrorType } from './txn_builder_error'
1616
import { EllipticPair } from '@defichain/jellyfish-crypto'
@@ -45,16 +45,10 @@ export abstract class P2WPKHTxnBuilder {
4545

4646
/**
4747
* @param {BigNumber} minBalance to collect, required to form a transaction
48-
* @param {ListUnspentOptions} [options]
49-
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
50-
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
51-
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
52-
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
53-
* @param {string} [options.tokenId] default is 'all', filter by token
5448
* @return {Promise<Prevouts>}
5549
*/
56-
protected async collectPrevouts (minBalance: BigNumber, options?: ListUnspentQueryOptions): Promise<Prevouts> {
57-
const prevouts = await this.prevoutProvider.collect(minBalance, options)
50+
protected async collectPrevouts (minBalance: BigNumber): Promise<Prevouts> {
51+
const prevouts = await this.prevoutProvider.collect(minBalance)
5852
if (prevouts.length === 0) {
5953
throw new TxnBuilderError(TxnBuilderErrorType.NO_PREVOUTS,
6054
'no prevouts available to create a transaction'
@@ -104,21 +98,30 @@ export abstract class P2WPKHTxnBuilder {
10498
* @param {OP_DEFI_TX} opDeFiTx to create
10599
* @param {Script} changeScript to send unspent to after deducting the fees
106100
* @param {BigNumber} [outValue=0] for the opDeFiTx, usually always be 0.
107-
* @param {ListUnspentOptions} [options]
108-
* @param {number} [options.minimumAmount] default = 0, minimum value of each UTXO
109-
* @param {number} [options.maximumAmount] default is 'unlimited', maximum value of each UTXO
110-
* @param {number} [options.maximumCount] default is 'unlimited', maximum number of UTXOs
111-
* @param {number} [options.minimumSumAmount] default is 'unlimited', minimum sum value of all UTXOs
112-
* @param {string} [options.tokenId] default is 'all', filter by token
101+
* @param {Prevout[]} [utxos=[]] provide it if you want to spent specific UTXOs
113102
*/
114103
async createDeFiTx (
115104
opDeFiTx: OP_DEFI_TX,
116105
changeScript: Script,
117106
outValue: BigNumber = new BigNumber('0'),
118-
options: ListUnspentQueryOptions = {}
107+
utxos: Prevout[] = []
119108
): Promise<TransactionSegWit> {
120109
const minFee = outValue.plus(0.001) // see JSDoc above
121-
const { prevouts, vin, total } = await this.collectPrevouts(minFee, options)
110+
111+
let prevouts: Prevout[] = []
112+
let vin: Vin[] = []
113+
let total: BigNumber = new BigNumber('0')
114+
115+
if (utxos.length > 0) {
116+
({ prevouts, vin, total } = joinPrevouts(utxos))
117+
if (minFee.gt(total)) {
118+
throw new TxnBuilderError(TxnBuilderErrorType.MIN_BALANCE_NOT_ENOUGH,
119+
'not enough balance after combing all prevouts'
120+
)
121+
}
122+
} else {
123+
({ prevouts, vin, total } = await this.collectPrevouts(minFee))
124+
}
122125

123126
const deFiOut: Vout = {
124127
value: outValue,

packages/jellyfish-transaction-builder/src/txn/txn_builder_account.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '@defichain/jellyfish-transaction'
66
import { P2WPKHTxnBuilder } from './txn_builder'
77
import { TxnBuilderError, TxnBuilderErrorType } from './txn_builder_error'
8-
import { ListUnspentQueryOptions } from '../provider'
8+
import { Prevout } from '../provider'
99

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

157-
async transferDomain (transferDomain: TransferDomain, changeScript: Script, options: ListUnspentQueryOptions = {}): Promise<TransactionSegWit> {
152+
async transferDomain (transferDomain: TransferDomain, changeScript: Script, utxos: Prevout[] = []): Promise<TransactionSegWit> {
158153
return await super.createDeFiTx(
159154
OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain),
160155
changeScript,
161156
new BigNumber(0),
162-
options
157+
utxos
163158
)
164159
}
165160
}

0 commit comments

Comments
 (0)