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

feat(jellyfish-api-core): add getCustomTx and decodeCustomTx RPCs #1878

Merged
merged 8 commits into from
Dec 2, 2022
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
36 changes: 36 additions & 0 deletions docs/node/CATEGORIES/07-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,39 @@ interface UTXO {
vout: number
}
```

## getCustomTx

Get detailed information about any custom transaction.

```ts title="client.token.getCustomTx()"
interface token {
getCustomTx (txid: string, blockhash?: string): Promise<GetCustomTxResult | string>
}

interface GetCustomTxResult {
type: string
valid: boolean
results: object
block_height: string
blockhash: string
confirmations: number
}
```

## decodeCustomTx

Get detailed information about any custom transaction from the raw transaction.

```ts title="client.token.decodeCustomTx()"
interface token {
decodeCustomTx (hexstring: string, iswitness?: boolean): Promise<DecodeCustomTxResult | string>
}

interface DecodeCustomTxResult {
txid: string
type: string
valid: boolean
results: object
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '@defichain/jellyfish-api-core'

describe('Get Custom TX', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error if invalid tx', async () => {
const promise = client.token.decodeCustomTx('000')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -22,
message: 'TX decode failed',
method: 'decodecustomtx'
}
})
})

it('should throw error if witness flag is false on witness TX', async () => {
// raw data of 0010c59335d9dcca98531ddb809258829cdfbac88ac953afb8e4e620873b6b7f
const promise = client.token.decodeCustomTx('040000000001012abaf715b5fbed90406a4b40b96c0aa562265568b107d51560f96d0e73dfc9d80100000000fdffffff020000000000000000476a454466547853e8888f0acc092b8c4e60d895e1368e0f20596776f073e9ce4988b2aa0a5a3843160014ffbdc8c27db5abd43917b309e2884f3bf8ea832f0feac243a90300000000b620090000000000160014ffbdc8c27db5abd43917b309e2884f3bf8ea832f00024730440220776dbb86676f26e62e7624b879cc5d6cfba20a8f5aa600950a7da2eac6a600a902204495c5a9123333a75fad85882034cdcb6dd87b15e237a1b20686d320271820f401210275a8747349619132a3a1708a3db1c8d34ea6053d1758c90b4b25bb3f704579fc00000000', false)

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -22,
message: 'TX decode failed',
method: 'decodecustomtx'
}
})
})

it('should fail if coinbase tx', async () => {
// raw data of b7db79537b12d9195cd066b3b753769fe81a40e380a9bb61e7c8405ca8f3649c
const res = await client.token.decodeCustomTx('040000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0503b0632500ffffffff036741bb6e010000001976a91413d4431d52eb3b0d69da2f6b9ff3ca00ff95a13888ac00e06106360000000017a914dd7730517e0e4969b4e43677ff5bee682e53420a87000000000000000000266a24aa21a9edbc2842d4049400181996ac667c3493be2da9e65c5355fd4db50fdb50a836bcd2000120000000000000000000000000000000000000000000000000000000000000000000000000')
expect(res).toStrictEqual('Coinbase transaction. Not a custom transaction.')
})

it('should fail if not custom tx', async () => {
// raw data of 0010c59335d9dcca98531ddb809258829cdfbac88ac953afb8e4e620873b6b7f
const res = await client.token.decodeCustomTx('04000000000102aaac80ae37b370600e2c125f897e45ca51a01bfa45b48f82e1ceb5305709ff4a0100000000ffffffffaaac80ae37b370600e2c125f897e45ca51a01bfa45b48f82e1ceb5305709ff4a0200000000ffffffff02e4f058e70a0000001600148074c1cbadd0c5cd3e328a4a85769296d4f88f9b00ae95980000000000160014b634b8cdb17e196551d5b27f8118f523aa0b07a40002473044022047081709493b10b3e2c92ea1342218d12ba1e6ca8112767f897d7b6687f3655202203639c1a3fa19670e4bbcf5a27f30869ea9528c4bd2fe2131e3d273295b1648430121030ae5092bc2415b1fb565d8ee1cb03afc3dee8ac1c0292106e94f69c60e12b6040247304402201bec5a01af13cdd6bad1631252dde40c7409d69a45e2842483b499b4c7d239a90220447100133f5b1e1b9e42cf499fa5b1316e917c6db0492bcb98b9ad70672e67510121030ae5092bc2415b1fb565d8ee1cb03afc3dee8ac1c0292106e94f69c60e12b60400000000')
expect(res).toStrictEqual('Not a custom transaction')
})

it('should return custom tx data', async () => {
// raw data of 407bee741668a7406b9b49c1560fe5b15148f86aad031781d0a7e4b6cb9763d0
const res = await client.token.decodeCustomTx('040000000001012abaf715b5fbed90406a4b40b96c0aa562265568b107d51560f96d0e73dfc9d80100000000fdffffff020000000000000000476a454466547853e8888f0acc092b8c4e60d895e1368e0f20596776f073e9ce4988b2aa0a5a3843160014ffbdc8c27db5abd43917b309e2884f3bf8ea832f0feac243a90300000000b620090000000000160014ffbdc8c27db5abd43917b309e2884f3bf8ea832f00024730440220776dbb86676f26e62e7624b879cc5d6cfba20a8f5aa600950a7da2eac6a600a902204495c5a9123333a75fad85882034cdcb6dd87b15e237a1b20686d320271820f401210275a8747349619132a3a1708a3db1c8d34ea6053d1758c90b4b25bb3f704579fc00000000')
expect(res).toMatchObject({
txid: '407bee741668a7406b9b49c1560fe5b15148f86aad031781d0a7e4b6cb9763d0',
type: 'DepositToVault',
valid: true,
results: {
vaultId: '43385a0aaab28849cee973f0766759200f8e36e195d8604e8c2b09cc0a8f88e8',
from: 'bcrt1ql77u3snakk4agwghkvy79zz080uw4qe0jqw2vh',
amount: '157.24692202@15'
}
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '@defichain/jellyfish-api-core'
import BigNumber from 'bignumber.js'

describe('Get Custom TX', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error if transaction is not in block', async () => {
const promise = client.token.getCustomTx('751e8020ed618d6a7d5ffeb1a4df6f0e4ccd344cdd72c5ab400bef63ad99df1a', await container.getBestBlockHash())

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -5,
message: 'No such transaction found in the provided block.',
method: 'getcustomtx'
}
})
})

it('should throw error if transaction is not in mempool', async () => {
const promise = client.token.getCustomTx('751e8020ed618d6a7d5ffeb1a4df6f0e4ccd344cdd72c5ab400bef63ad99df1a')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -5,
message: 'No such mempool, wallet or blockchain transaction.',
method: 'getcustomtx'
}
})
})

it('should throw error if incorrect blockhash', async () => {
const promise = client.token.getCustomTx('751e8020ed618d6a7d5ffeb1a4df6f0e4ccd344cdd72c5ab400bef63ad99df1a', '751e8020ed618d6a7d5ffeb1a4df6f0e4ccd344cdd72c5ab400bef63ad99df1a')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -5,
message: 'Block hash not found',
method: 'getcustomtx'
}
})
})

it('should throw error if not custom tx', async () => {
const UTXO = (await client.wallet.listUnspent())[0]
const fromAddress = UTXO.address
const amount = UTXO.amount.toNumber()

const txid = await container.call('sendutxosfrom', [fromAddress, await container.getNewAddress(), amount])
const promise = await client.token.getCustomTx(txid, await container.getBestBlockHash())

expect(promise).toStrictEqual('Not a custom transaction')
})

it('should return custom transaction details - createToken', async () => {
const txid = await client.token.createToken({
symbol: 'TEST',
name: 'TEST',
isDAT: true,
mintable: true,
tradeable: true,
collateralAddress: await container.getNewAddress()
})
await container.generate(1)

const result = await client.token.getCustomTx(txid, await container.getBestBlockHash())

expect(result).toMatchObject({
type: 'CreateToken',
valid: true,
results: {
creationTx: txid,
name: 'TEST',
symbol: 'TEST',
isDAT: true,
mintable: true,
tradeable: true,
finalized: false
},
blockHeight: await client.blockchain.getBlockCount(),
blockhash: await client.blockchain.getBestBlockHash(),
confirmations: 1
})
})

it('should return custom transaction details - createLoanScheme', async () => {
const txid = await client.loan.createLoanScheme({
minColRatio: 105,
interestRate: new BigNumber(5),
id: 'LOAN105'
})
await container.generate(1)

const result = await client.token.getCustomTx(txid, await container.getBestBlockHash())

expect(result).toMatchObject({
type: 'LoanScheme',
valid: true,
results: {
id: 'LOAN105',
interestrate: 5,
mincolratio: 105,
updateHeight: 0
},
blockHeight: await client.blockchain.getBlockCount(),
blockhash: await client.blockchain.getBestBlockHash(),
confirmations: 1
})
})
})
41 changes: 41 additions & 0 deletions packages/jellyfish-api-core/src/category/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,31 @@ export class Token {
async burnTokens (amounts: string, from: string, context?: string, utxos: UTXO[] = []): Promise<string> {
return await this.client.call('burntokens', [{ amounts, from, context }, utxos], 'number')
}

/**
* Get detailed information about any custom transaction.
*
* @param {string} txid Transaction hash
* @param {string} [blockhash] (for confirmed transactions) Hash of the block of the transaction
* @return {Promise<GetCustomTxResult | string>} Inferred custom transaction data, or error message
*/
async getCustomTx (txid: string, blockhash?: string): Promise<GetCustomTxResult | string> {
return await this.client.call('getcustomtx', [txid, blockhash], 'number')
}

/**
* Get detailed information about any custom transaction from the raw transaction.
*
* @param {string} hexstring Serialised custom transaction data
* @param {boolean} [iswitness] is the transaction a serialised witness transaction
* @return {Promise<DecodeCustomTxResult | string>} Inferred custom transaction data, or error message
*/
async decodeCustomTx (hexstring: string, iswitness?: boolean): Promise<DecodeCustomTxResult | string> {
if (iswitness === undefined) {
return await this.client.call('decodecustomtx', [hexstring], 'number')
}
return await this.client.call('decodecustomtx', [hexstring, iswitness], 'number')
}
}

export interface TokenResult {
Expand Down Expand Up @@ -167,3 +192,19 @@ export interface UTXO {
txid: string
vout: number
}

export interface GetCustomTxResult {
type: string
valid: boolean
results: object
blockHeight: string
blockhash: string
confirmations: number
}

export interface DecodeCustomTxResult {
txid: string
type: string
valid: boolean
results: object
}