Skip to content

Commit

Permalink
Add wallet.sendMany RPC (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
surangap authored May 25, 2021
1 parent 4a29027 commit f38efc3
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 1 deletion.
140 changes: 139 additions & 1 deletion packages/jellyfish-api-core/__tests__/category/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ContainerAdapterClient } from '../container_adapter_client'
import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers'
import { BigNumber, wallet } from '../../src'
import waitForExpect from 'wait-for-expect'
import { UTXO, ListUnspentOptions, WalletFlag, SendToAddressOptions, Mode } from '../../src/category/wallet'
import { UTXO, ListUnspentOptions, WalletFlag, SendToAddressOptions, Mode, SendManyOptions } from '../../src/category/wallet'

describe('non masternode', () => {
const container = new RegTestContainer()
Expand Down Expand Up @@ -632,3 +632,141 @@ describe('masternode', () => {
})
})
})

describe('sendMany', () => {
// NOTE(surangap): defid side(c++) does not have much tests for sendmany RPC atm.
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

beforeAll(async () => {
await container.start()
await container.waitForReady()
await container.waitForWalletCoinbaseMaturity()
await container.waitForWalletBalanceGTE(101)
})

afterAll(async () => {
await container.stop()
})

// Returns matching utxos for given transaction id and address.
const getMatchingUTXO = async (txId: string, address: string): Promise<UTXO[]> => {
const options: ListUnspentOptions = {
addresses: [address]
}

const utxos: UTXO[] = await client.wallet.listUnspent(1, 9999999, options)
return utxos.filter((utxo) => {
return (utxo.address === address) && (utxo.txid === txId)
})
}

it('should send one address using sendMany', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001 }
const transactionId = await client.wallet.sendMany(amounts)
expect(typeof transactionId).toBe('string')

// generate one block
await container.generate(1)

// check the corresponding UTXO
const utxos = await getMatchingUTXO(transactionId, 'mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
// In this case the we should only have one matching utxo
expect(utxos.length).toStrictEqual(1)
utxos.forEach(utxo => {
expect(utxo.address).toStrictEqual('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
expect(utxo.amount).toStrictEqual(new BigNumber(0.00001))
})
})

it('should send multiple address using sendMany', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 0.00002 }
const transactionId = await client.wallet.sendMany(amounts)
expect(typeof transactionId).toBe('string')

// generate one block
await container.generate(1)

// check the corresponding UTXOs
const utxos = await getMatchingUTXO(transactionId, 'mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
// In this case the we should only have one matching utxo
expect(utxos.length).toStrictEqual(1)
utxos.forEach(utxo => {
expect(utxo.address).toStrictEqual('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
expect(utxo.amount).toStrictEqual(new BigNumber(0.00001))
})

const utxos2 = await getMatchingUTXO(transactionId, 'mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy')
// In this case the we should only have one matching utxo
expect(utxos2.length).toStrictEqual(1)
utxos2.forEach(utxo => {
expect(utxo.address).toStrictEqual('mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy')
expect(utxo.amount).toStrictEqual(new BigNumber(0.00002))
})
})

it('should sendMany with comment', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 0.00002 }
const options: SendManyOptions = {
comment: 'test comment'
}
const transactionId = await client.wallet.sendMany(amounts, [], options)
expect(typeof transactionId).toBe('string')
})

it('should sendMany with replaceable', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 0.00002 }
const options: SendManyOptions = {
replaceable: true
}
const transactionId = await client.wallet.sendMany(amounts, [], options)
expect(typeof transactionId).toBe('string')
})

it('should sendMany with confTarget', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 0.00002 }
const options: SendManyOptions = {
confTarget: 60
}
const transactionId = await client.wallet.sendMany(amounts, [], options)
expect(typeof transactionId).toBe('string')
})

it('should sendMany with estimateMode', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 0.00002 }
const options: SendManyOptions = {
estimateMode: Mode.ECONOMICAL
}
const transactionId = await client.wallet.sendMany(amounts, [], options)
expect(typeof transactionId).toBe('string')
})

it('should sendMany with fee substracted from mentioned recipients', async () => {
const amounts: Record<string, number> = { mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU: 0.00001, mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy: 10.5 }
const subtractFeeFrom: string [] = ['mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy']
const transactionId = await client.wallet.sendMany(amounts, subtractFeeFrom)
expect(typeof transactionId).toBe('string')

// generate one block
await container.generate(1)

// check the corresponding UTXOs
const utxos = await getMatchingUTXO(transactionId, 'mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
// In this case the we should only have one matching utxo
expect(utxos.length).toStrictEqual(1)
utxos.forEach(utxo => {
expect(utxo.address).toStrictEqual('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU')
// amount should be equal to 0.00001
expect(utxo.amount).toStrictEqual(new BigNumber(0.00001))
})

const utxos2 = await getMatchingUTXO(transactionId, 'mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy')
// In this case the we should only have one matching utxo
expect(utxos2.length).toStrictEqual(1)
utxos2.forEach(utxo => {
expect(utxo.address).toStrictEqual('mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy')
// amount should be less than 10.5
expect(utxo.amount.isLessThan(10.5)).toBe(true)
})
})
})
43 changes: 43 additions & 0 deletions packages/jellyfish-api-core/src/category/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,42 @@ export class Wallet {
async listAddressGroupings (): Promise<any[][][]> {
return await this.client.call('listaddressgroupings', [], 'bignumber')
}

/**
* Send given amounts to multiple given address and return a transaction id.
*
* @param {Record<string, number>} amounts Dictionary/map with individual addresses and amounts
* @param {string[]} subtractfeefrom Array of addresses from which fee needs to be deducted.
* @param {SendManyOptions} options
* @param {string} [options.comment] A comment
* @param {boolean} [options.replaceable] Allow this transaction to be replaced by a transaction with higher fees via BIP 125
* @param {number} [options.confTarget] Confirmation target (in blocks)
* @param {Mode} [options.estimateMode] The fee estimate mode, must be one of (Mode.UNSET, Mode.ECONOMICAL, Mode.CONSERVATIVE)
* @return {Promise<string>} hex string of the transaction
*/
async sendMany (
amounts: Record<string, number>,
subtractfeefrom: string [] = [],
options: SendManyOptions = {}): Promise<string> {
const {
comment = '',
replaceable = false,
confTarget = 6,
estimateMode = Mode.UNSET
} = options

const dummy: string = '' // Must be set to '' for backward compatibality.
const minconf: number = 0 // Ignored dummy value

return await this.client.call(
'sendmany',
[
dummy, amounts, minconf, comment, subtractfeefrom,
replaceable, confTarget, estimateMode
],
'bignumber'
)
}
}

export interface UTXO {
Expand Down Expand Up @@ -263,6 +299,13 @@ export interface SendToAddressOptions {
avoidReuse?: boolean
}

export interface SendManyOptions {
comment?: string
replaceable?: boolean
confTarget?: number
estimateMode?: Mode
}

export interface CreateWalletResult {
name: string
warning: string
Expand Down
23 changes: 23 additions & 0 deletions website/docs/jellyfish/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,26 @@ interface wallet {
listAddressGroupings (): Promise<any[][][]>
}
```

## sendMany

Send given amounts to multiple given address and return a transaction id.

```ts title="client.wallet.sendMany()"
interface wallet {
async sendMany (amounts: Record<string, number> , subtractfeefrom: string [] = [], options: SendManyOptions = {}): Promise<string>
}

interface SendManyOptions {
comment?: string
replaceable?: boolean
confTarget?: number
estimateMode?: Mode
}

enum Mode {
UNSET = 'UNSET',
ECONOMICAL = 'ECONOMICAL',
CONSERVATIVE = 'CONSERVATIVE'
}
```

0 comments on commit f38efc3

Please sign in to comment.