Skip to content

Commit

Permalink
Add SPV claimhtc RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
Jouzo committed Aug 3, 2021
1 parent 3827626 commit 1ac3a09
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
135 changes: 135 additions & 0 deletions packages/jellyfish-api-core/__tests__/category/spv/claimHtlc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { RpcApiError } from '@defichain/jellyfish-api-core'
import { ContainerAdapterClient } from '../../container_adapter_client'

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

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

await container.call('spv_fundaddress', [await container.call('spv_getnewaddress')]) // Funds 1 BTC
})

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

it('should claimHtlc', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

await container.call('spv_sendtoaddress', [htlc.address, 0.1]) // Funds HTLC address
const claimedHtlc = await client.spv.claimHtlc(
htlc.address,
await container.call('spv_getnewaddress'),
{ seed: htlc.seed }
)
expect(typeof claimedHtlc.txid).toStrictEqual('string')
expect(claimedHtlc.sendmessage).toStrictEqual('Success')
})

it('should not claimHtlc when no unspent HTLC outputs found', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

const promise = client.spv.claimHtlc(
htlc.address,
await container.call('spv_getnewaddress'),
{ seed: htlc.seed }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'No unspent HTLC outputs found', code: -4, method: spv_claimhtlc")
})

it('should not claimHtlc when provided seed is not in hex form', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

const promise = client.spv.claimHtlc(
htlc.address,
await container.call('spv_getnewaddress'),
{ seed: 'XXXX' }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Provided seed is not in hex form', code: -5, method: spv_claimhtlc")
})

it('should not claimHtlc when seed provided does not match seed hash in contract', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

const promise = client.spv.claimHtlc(
htlc.address,
await container.call('spv_getnewaddress'),
{ seed: '00' }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Seed provided does not match seed hash in contract', code: -5, method: spv_claimhtlc")
})

it('should not claimHtlc with invalid address', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

const promise = client.spv.claimHtlc(
'XXXX',
await container.call('spv_getnewaddress'),
{ seed: htlc.seed }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Invalid address', code: -5, method: spv_claimhtlc")
})

it('should not claimHtlc when redeem script not found in wallet', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])
const randomAddress = '2Mu4edSkC5gKVwYayfDq2fTFwT6YD4mujSX'

const promise = client.spv.claimHtlc(
randomAddress,
await container.call('spv_getnewaddress'),
{ seed: htlc.seed }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Redeem script not found in wallet', code: -4, method: spv_claimhtlc")
})

it('should not claimHtlc with invalid destination address', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

const promise = client.spv.claimHtlc(
htlc.address,
'XXXX',
{ seed: htlc.seed }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Invalid destination address', code: -5, method: spv_claimhtlc")
})

it('should not claimHtlc with not enough funds to cover fee', async () => {
const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')])
const htlc = await container.call('spv_createhtlc', [pubKeyA, pubKeyB, '10'])

await container.call('spv_sendtoaddress', [htlc.address, 0.00000546]) // Funds HTLC address with dust

const promise = client.spv.claimHtlc(
htlc.address,
await container.call('spv_getnewaddress'),
{ seed: htlc.seed }
)
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow("RpcApiError: 'Not enough funds to cover fee', code: -1, method: spv_claimhtlc")
})
})
21 changes: 21 additions & 0 deletions packages/jellyfish-api-core/src/category/spv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ export class Spv {
async decodeHtlcScript (redeemScript: string): Promise<DecodeHtlcResult> {
return await this.client.call('spv_decodehtlcscript', [redeemScript], 'number')
}

/**
* Claims all coins in HTLC address.
*
* @param {string} scriptAddress HTLC address
* @param {string} destinationAddress Destination address to send HTLC funds to
* @param {ClaimHtlcOptions} options
* @param {string} options.seed HTLC seed
* @param {BigNumber} [options.feeRate=10000] Fee rate in satoshis per KB. Minimum is 1000.
* @return {Promise<SendMessageResult>}
*/
async claimHtlc (scriptAddress: string, destinationAddress: string, options: ClaimHtlcOptions): Promise<SendMessageResult> {
return await this.client.call('spv_claimhtlc', [scriptAddress, destinationAddress, options.seed, options.feeRate], 'bignumber')
}
}

export interface ReceivedByAddressInfo {
Expand Down Expand Up @@ -133,3 +147,10 @@ export interface DecodeHtlcResult {
/** Hex-encoded seed hash */
hash: string
}

export interface ClaimHtlcOptions {
/** HTLC seed */
seed: string
/** Fee rate in satoshis per KB */
feeRate?: BigNumber
}
20 changes: 20 additions & 0 deletions website/docs/jellyfish/api/spv.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,23 @@ interface DecodeHtlcResult {
hash: string
}
```

## claimHtlc

Claims all coins in HTLC address.

```ts title="client.spv.claimHtlc()"
interface spv {
claimHtlc (scriptAddress: string, destinationAddress: string, options: ClaimHtlcOptions): Promise<SendMessageResult>
}

interface ClaimHtlcOptions {
seed: string
feeRate?: BigNumber
}

interface SendMessageResult {
txid: string
sendmessage: string
}
```

0 comments on commit 1ac3a09

Please sign in to comment.