Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wallet.sendMany RPC #269

Merged
merged 5 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
surangap marked this conversation as resolved.
Show resolved Hide resolved
}

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