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

sendTokensToAddress RPC #323

Merged
merged 30 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a2d327b
Quick save
siradji May 31, 2021
c8e693c
Testing complete
siradji May 31, 2021
9094ed3
Minor changes
siradji May 31, 2021
1870cbd
Removed unsued test
siradji May 31, 2021
ec2b3e6
Fix flakiness or regression in blockchain.getChainTips
jingyi2811 Jun 1, 2021
dc58ea5
Merge remote-tracking branch 'origin/jimmy/getChainTips' into siradji…
jingyi2811 Jun 1, 2021
d18426c
Revert back the code
jingyi2811 Jun 1, 2021
4a30b0d
Minor fixes
siradji Jun 2, 2021
12bfb1b
Implemented suggestions from code review
siradji Jun 3, 2021
e2afd2d
Merge branch 'main' into siradji/rcp-sendTokensToAddress
siradji Jun 3, 2021
f0d96b9
Fixed lint issue
siradji Jun 3, 2021
02fd385
Implemented suggestions from code review
siradji Jun 3, 2021
734aa24
Implemented addition test from sendTokensToAddress
siradji Jun 4, 2021
3f8455e
Implemented requested changes
siradji Jun 8, 2021
f682bb7
Additional Tests
siradji Jun 9, 2021
a303da4
Suggestions from code review
siradji Jun 10, 2021
a85e47b
Suggestions from code reviews
siradji Jun 13, 2021
bf4f71a
Suggestions from code review
siradji Jun 15, 2021
2602078
Quick save
siradji Jun 15, 2021
1d7e752
rewrite setup fixture, added selection mode test
canonbrother Jun 15, 2021
e75772a
Clean up
siradji Jun 15, 2021
dc27073
Clean up
siradji Jun 15, 2021
029117e
Minor fixes
siradji Jun 16, 2021
fbc666d
Fixed test failure
siradji Jun 16, 2021
4fa3108
Fixed test failure
siradji Jun 16, 2021
0014c90
Minor fixes
siradji Jun 17, 2021
8392652
Minor fixes
siradji Jun 17, 2021
383e106
Update packages/jellyfish-api-core/src/category/account.ts
siradji Jun 21, 2021
1d81213
Update website/docs/jellyfish/api/account.md
siradji Jun 21, 2021
65b56b0
Test file refactor
siradji Jun 21, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { SelectionModeType /* SendTokensOptions */ } from '../../../src/category/account'

describe('SendTokenToAddress', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

let addrA: string
let addrB: string
let addrCForward: string
let addrCCrumbs: string
let addrCPie: string
fuxingloh marked this conversation as resolved.
Show resolved Hide resolved

async function setup (): Promise<void> {
addrA = await container.call('getnewaddress')
addrB = await container.call('getnewaddress')
addrCForward = await container.call('getnewaddress')
addrCCrumbs = await container.call('getnewaddress')
addrCPie = await container.call('getnewaddress')
canonbrother marked this conversation as resolved.
Show resolved Hide resolved

const tokenaddrA = await container.call('getnewaddress')
const tokenaddrB = await container.call('getnewaddress')
const tokenAddrC = await container.call('getnewaddress')
await createToken(tokenaddrA, 'ANT', 100)
await createToken(tokenaddrB, 'BAT', 100)
await createToken(tokenAddrC, 'CAT', 200)

await container.call('accounttoaccount', [tokenaddrA, { [addrA]: `${100}@ANT` }])
await container.call('accounttoaccount', [tokenaddrB, { [addrB]: `${100}@BAT` }])
await container.call('accounttoaccount', [tokenAddrC, { [addrCForward]: `${49}@CAT` }])
await container.call('accounttoaccount', [tokenAddrC, { [addrCCrumbs]: `${3}@CAT` }])
await container.call('accounttoaccount', [tokenAddrC, { [addrCPie]: `${104}@CAT` }])
await container.generate(1)
}

async function createToken (address: string, symbol: string, amount: number): Promise<void> {
const metadata = {
symbol,
name: symbol,
isDAT: true,
mintable: true,
tradeable: true,
collateralAddress: address
}

await container.waitForWalletBalanceGTE(amount)
await container.call('createtoken', [metadata])
await container.generate(1)

await container.call('utxostoaccount', [{ [address]: `${amount.toString()}@0` }])
await container.generate(1)

await container.call('minttokens', [`${amount.toString()}@${symbol}`])
await container.generate(1)
}

function accToNum (acc: string): number {
return Number(acc.split('@')[0])
}

it('should sendTokensToAddress with selectionMode pie', async () => {
const accPieBefore = (await client.account.getAccount(addrCPie))[0]
fuxingloh marked this conversation as resolved.
Show resolved Hide resolved
const addrReceiver = await container.call('getnewaddress')
const hex = await client.account.sendTokensToAddress({}, { [addrReceiver]: ['4@CAT'] }, {
selectionMode: SelectionModeType.PIE
})

expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

const accPieAfter = (await client.account.getAccount(addrCPie))[0]
expect(accToNum(accPieAfter)).toStrictEqual(accToNum(accPieBefore) - 4)

const accReceiver = (await client.account.getAccount(addrReceiver))[0]
expect(accReceiver).toStrictEqual('4.00000000@CAT')
})

it('should sendTokensToAddress with selectionMode crumbs', async () => {
const accCrumbsBefore = (await client.account.getAccount(addrCCrumbs))[0]

const addrReceiver = await container.call('getnewaddress')
const hex = await client.account.sendTokensToAddress({}, { [addrReceiver]: ['2@CAT'] }, {
selectionMode: SelectionModeType.CRUMBS
})
expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

const accCrumbsAfter = (await client.account.getAccount(addrCCrumbs))[0]
expect(accToNum(accCrumbsAfter)).toStrictEqual(accToNum(accCrumbsBefore) - 2)

const accReceiver = (await client.account.getAccount(addrReceiver))[0]
expect(accReceiver).toStrictEqual('2.00000000@CAT')
})

// NOTE(canonrother): "selectionMode: forward" picks the address name order by ASC, its hard to test as address is random generated
it.skip('should sendTokensToAddress with selectionMode forward', async () => {
const accForwardBefore = (await client.account.getAccount(addrCCrumbs))[0]

const addrReceiver = await client.wallet.getNewAddress()
siradji marked this conversation as resolved.
Show resolved Hide resolved
const hex = await client.account.sendTokensToAddress({}, { [addrReceiver]: ['6@CAT'] }, {
selectionMode: SelectionModeType.FORWARD
})
expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

const accForwardAfter = (await client.account.getAccount(addrCCrumbs))[0]
expect(accToNum(accForwardAfter)).toStrictEqual(accToNum(accForwardBefore) - 6)

const accReceiver = (await client.account.getAccount(addrReceiver))[0]
expect(accReceiver).toStrictEqual('6.00000000@CAT')
})

it('should sendTokensToAddress with multiple receiver tokens', async () => {
const address = await container.call('getnewaddress')
siradji marked this conversation as resolved.
Show resolved Hide resolved
const hex = await client.account.sendTokensToAddress({}, { [address]: ['2@ANT', '5@CAT', '10@BAT'] })

expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

expect(await client.account.getAccount(address)).toStrictEqual(['2.00000000@ANT', '10.00000000@BAT', '5.00000000@CAT'])
siradji marked this conversation as resolved.
Show resolved Hide resolved
})

it('should sendTokensToAddress with source address', async () => {
const addrReceiver = await container.call('getnewaddress')
const hex = await client.account.sendTokensToAddress({ [addrA]: ['10@ANT'] }, { [addrReceiver]: ['10@ANT'] })

expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

expect(await client.account.getAccount(addrReceiver)).toStrictEqual(['10.00000000@ANT'])
})

it('should fail and throw an exception when no receiver address', async () => {
await expect(client.account.sendTokensToAddress({}, {})).rejects.toThrow('zero amounts in "to" param')
})

it('should throw an error when insufficient fund', async () => {
const promise = client.account.sendTokensToAddress({}, { [await container.call('getnewaddress')]: ['500@CAT'] })

await expect(promise).rejects.toThrow('Not enough balance on wallet accounts, call utxostoaccount to increase it.')
})

it('should throw an error when sending different tokens', async () => {
const promise = client.account.sendTokensToAddress({ [addrA]: ['10@ANT'] }, { [await container.call('getnewaddress')]: ['10@BAT'] })

await expect(promise).rejects.toThrow('RpcApiError: \'Test AnyAccountsToAccountsTx execution failed:\nsum of inputs (from) != sum of outputs (to)\', code: -32600, method: sendtokenstoaddress')
})

it('should throw an error when sending different amount', async () => {
const promise = client.account.sendTokensToAddress({ [addrA]: ['10@ANT'] }, { [await container.call('getnewaddress')]: ['20@ANT'] })

await expect(promise).rejects.toThrow('RpcApiError: \'Test AnyAccountsToAccountsTx execution failed:\nsum of inputs (from) != sum of outputs (to)\', code: -32600, method: sendtokenstoaddress')
})
})
31 changes: 31 additions & 0 deletions packages/jellyfish-api-core/src/category/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export enum TxType {
AUTO_AUTH_PREP = 'A'
}

export enum SelectionModeType {
PIE = 'pie',
CRUMBS = 'crumbs',
FORWARD = 'forward'
}

type AccountRegexType = `${number}@${string}`
siradji marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand Down Expand Up @@ -311,6 +317,23 @@ export class Account {
): Promise<number> {
return await this.client.call('accounthistorycount', [owner, options], 'number')
}

/**
* Creates a transfer transaction from your accounts balances.
*
* @param {AddressBalances} from The source defi address is the key, the value is amount in amount amount@token format
* @param {AddressBalances} to The defi address is the key, the value is amount in amount amount@token format
* @param {SendTokensOptions} [options]
siradji marked this conversation as resolved.
Show resolved Hide resolved
* @param {SelectionModeType} [options.selectionMode] Account selection mode. If "from" param is empty, it will auto select.
* @return {Promise<string>}
*/
async sendTokensToAddress (
from: AddressBalances,
to: AddressBalances,
options: SendTokensOptions = { selectionMode: SelectionModeType.PIE }
): Promise<string> {
return await this.client.call('sendtokenstoaddress', [from, to, options.selectionMode], 'number')
}
}

export interface AccountPagination {
Expand Down Expand Up @@ -387,3 +410,11 @@ export interface AccountHistoryCountOptions {
txtype?: TxType | string
no_rewards?: boolean
}

export interface AddressBalances {
[key: string]: AccountRegexType[]
}

export interface SendTokensOptions {
selectionMode: SelectionModeType
}
31 changes: 31 additions & 0 deletions website/docs/jellyfish/api/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,34 @@ interface AccountHistoryCountOptions {
no_rewards?: boolean
}
```

## sendTokensToAddress

Creates a transfer transaction from your accounts balances.

```ts title="client.account.sendTokensToAddress()"

interface Account {
sendTokensToAddress (
from: AddressBalances,
to: AddressBalances,
options: SendTokensOptions = {selectionMode: SelectionModeType}
siradji marked this conversation as resolved.
Show resolved Hide resolved
): Promise<string>
}

type AccountRegexType = `${number}@${string}`

enum SelectionModeType {
PIE = 'pie',
CRUMBS = 'crumbs',
FORWARD = 'forward'
}

interface AddressBalances {
[key: string]: AccountRegexType[]
}

interface SendTokensOptions {
selectionMode: SelectionModeType
}
```