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 deriveAddresses function #1974

Merged
merged 7 commits into from
Jan 19, 2023
10 changes: 10 additions & 0 deletions docs/node/CATEGORIES/13-misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ interface misc {
signMessageWithPrivKey (privkey: string, message: string): Promise<string>
}
```

# deriveAddresses

Derives one or more addresses corresponding to an output descriptor.

```ts title="client.misc.deriveAddresses()"
interface misc {
deriveAddresses (descriptor: string, range?: number[]): Promise<string[]>
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { RegTestContainer } from '@defichain/testcontainers/dist/index'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '../../../src'

describe('derive addresses', () => {
const container = new RegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should derive an address without range', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64'
const address = ['bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5']
const promise = await client.misc.deriveAddresses(descriptor)
expect(promise).toStrictEqual(address)
})

it('should derive an address with range', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#kft60nuy'
const address = ['bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy', 'bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq']
const promise = await client.misc.deriveAddresses(descriptor, [1, 2])
expect(promise).toStrictEqual(address)
})

it('should derive an address without range pkh descriptor', async () => {
const descriptor = 'pkh([d6043800/0\'/0\'/18\']03efdee34c0009fd175f3b20b5e5a5517fd5d16746f2e635b44617adafeaebc388)#4ahsl9pk'
const address = ['ms7ruzvL4atCu77n47dStMb3of6iScS8kZ']
const promise = await client.misc.deriveAddresses(descriptor)
expect(promise).toStrictEqual(address)
})

it('should derive an address without range sh descriptor', async () => {
const descriptor = 'sh(wpkh(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/0/*))#e8nc36sh'
const address = ['2NA1PWXse3JjGGMcyjMETTCQnTpsLtQETQW', '2MzzJMkCmixHarCD47sFavseb3uTrPnxKav']
const promise = await client.misc.deriveAddresses(descriptor, [0, 1])
expect(promise).toStrictEqual(address)
})

it('should raise an error if the range is specified for un-ranged descriptor', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64'
const promise = client.misc.deriveAddresses(descriptor, [1, 3])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range should not be specified for an un-ranged descriptor',
method: 'deriveaddresses'
}
})
})

it('should raise an error if there is no range for a range descriptor', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#kft60nuy'
const promise = client.misc.deriveAddresses(descriptor)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range must be specified for a ranged descriptor',
method: 'deriveaddresses'
}
})
})

it('should raise an error if end of range is too high', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)'
const promise = client.misc.deriveAddresses(descriptor, [100000])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range must be specified as end or as [begin,end]',
method: 'deriveaddresses'
}
})
})

it('should raise an error if range is too large', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)'
const promise = client.misc.deriveAddresses(descriptor, [1, 1000000000])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range is too large',
method: 'deriveaddresses'
}
})
})

it('should raise an error if range is less than 0', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)'
const promise = client.misc.deriveAddresses(descriptor, [-1, 2])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range should be greater or equal than 0',
method: 'deriveaddresses'
}
})
})

it('should raise an error if end of range is smaller than the start', async () => {
const descriptor = 'wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)'
const promise = client.misc.deriveAddresses(descriptor, [2, 0])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -8,
message: 'Range specified as [begin,end] must not have begin after end',
method: 'deriveaddresses'
}
})
})

it('should raise an error if there is wrong drescriptor', async () => {
const descriptor = 'descriptor'
const promise = client.misc.deriveAddresses(descriptor, [0, 2])
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toMatchObject({
payload: {
code: -5,
message: 'Missing checksum',
method: 'deriveaddresses'
}
})
})
})
11 changes: 11 additions & 0 deletions packages/jellyfish-api-core/src/category/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,15 @@ export class Misc {
async signMessageWithPrivKey (privkey: string, message: string): Promise<string> {
return await this.client.call('signmessagewithprivkey', [privkey, message], 'number')
}

/**
* Derives one or more addresses corresponding to an output descriptor.
*
* @param {string} descriptor The descriptor.
* @param {number[]} range If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive.
* @return Promise<string[]> the derived addresses
*/
async deriveAddresses (descriptor: string, range?: number[]): Promise<string[]> {
return await this.client.call('deriveaddresses', [descriptor, range].filter(x => x !== undefined), 'number')
}
}