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

Added SPV claimHtlc RPC #521

Merged
merged 1 commit into from
Aug 4, 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
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
}
```