Skip to content

Commit

Permalink
Merge a765dde into 394fde3
Browse files Browse the repository at this point in the history
  • Loading branch information
shancht authored Feb 9, 2023
2 parents 394fde3 + a765dde commit 66b16a8
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/node/CATEGORIES/05-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,49 @@ interface wallet {
signMessage (address: string, message: string): Promise<string>
}
```

## encryptWallet

Encrypts the wallet for the first time using a custom ‘passphrase’. Transactions related to private keys will thereafter require a passphrase before execution.

To unlock the wallet, use [walletPassphrase](#walletPassphrase)

```ts title="client.wallet.encryptWallet()"
interface wallet {
encryptWallet (passphrase: string): Promise<string>
}
```

## walletPassphrase

Stores the wallet decryption key in memory for ‘timeout’ seconds. Calling `walletPassphrase` when wallet is unlocked will set a new unlock time that overrides the old setting.

To encrypt the wallet for the first time, use [encryptWallet](#encryptWallet)

```ts title="client.wallet.walletPassphrase()"
interface wallet {
walletPassphrase (passphrase: string, timeout: number): Promise<void>
}
```

## walletPassphraseChange

Changes the wallet passphrase from ‘oldpassphrase’ to ‘newpassphrase’.

```ts title="client.wallet.walletPassphraseChange()"
interface wallet {
walletPassphraseChange (oldpassphrase: string, newpassphrase: string): Promise<void>
}
```

## walletLock

Removes the wallet encryption key from memory, locking the wallet.

After locking the wallet, `walletPassphrase` must be called again to use methods that requires an unlocked wallet.

```ts title="client.wallet.walletLock()"
interface wallet {
walletLock (): Promise<void>
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '@defichain/jellyfish-api-core'

describe('Wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error if passphrase for encryptWallet is empty', async () => {
const promise = client.wallet.encryptWallet('')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet"
)
})

it('should encryptWallet with a given passphrase', async () => {
const promise = await client.wallet.encryptWallet('yourpassphrase')

expect(promise).toStrictEqual(
'wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.'
)
})

it('should throw error when encryptWallet is called again after encryption', async () => {
const promise = client.wallet.encryptWallet('yourpassphrase')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet"
)
})
})

describe('Wallet without masternode', () => {
const container = new RegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error if passphrase for wallet encryption is empty', async () => {
const promise = client.wallet.encryptWallet('')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet"
)
})

it('should encryptWallet with a given passphrase', async () => {
const promise = await client.wallet.encryptWallet('yourpassphrase')

expect(promise).toStrictEqual(
'wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.'
)
})

it('should throw error when encryptWallet is called again after encryption', async () => {
const promise = client.wallet.encryptWallet('yourpassphrase')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet"
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '@defichain/jellyfish-api-core'

describe('Unencrypted wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error when walletLock is called', async () => {
const promise = client.wallet.walletLock()

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: running with an unencrypted wallet, but walletlock was called.', code: -15, method: walletlock"
)
})
})

describe('Encrypted wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

beforeAll(async () => {
await container.start()
await client.wallet.encryptWallet('password')
await client.wallet.walletPassphrase('password', 10000)
})

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

it('should walletLock without failing', async () => {
const method = client.wallet.importPrivKey(await client.wallet.dumpPrivKey(await client.wallet.getNewAddress()))
const promise = client.wallet.walletLock()

await expect(method).resolves.not.toThrow()
await expect(promise).resolves.not.toThrow()
})
})
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('Unencrypted wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error when walletPassphrase is called', async () => {
const promise = client.wallet.walletPassphrase('password', -100)

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase"
)
})
})

describe('Encrypted wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

beforeAll(async () => {
await container.start()
await client.wallet.encryptWallet('password')
})

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

it('should throw error when walletPassphrase is called with a negative timeout', async () => {
const promise = client.wallet.walletPassphrase('password', -100)

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase"
)
})

it('should throw error when walletPassphrase is called without a passphrase', async () => {
const promise = client.wallet.walletPassphrase('', 100)

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase"
)
})

it('should throw error when walletPassphrase is called with a wrong passphrase', async () => {
const promise = client.wallet.walletPassphrase('incorrectpassword', 100)

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase"
)
})

it('should unlock wallet when walletPassphrase is called with the correct passphrase', async () => {
const promise = client.wallet.walletPassphrase('password', 100)

await expect(promise).resolves.not.toThrow()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { RpcApiError } from '@defichain/jellyfish-api-core'

describe('Unencrypted Wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should throw error when walletPassphraseChange is called', async () => {
const promise = client.wallet.walletPassphraseChange('wrongpassword', 'newpassword')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrasechange was called.', code: -15, method: walletpassphrasechange"
)
})
})

describe('Encrypted Wallet on masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

beforeAll(async () => {
await container.start()
await client.wallet.encryptWallet('password')
})

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

it('should throw error when walletPassphraseChange is called with an incorrect passphrase', async () => {
const promise = client.wallet.walletPassphraseChange('wrongpassword', 'newpassword')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrasechange"
)
})

it('should throw error when walletPassphraseChange is called with an empty passphrase', async () => {
const promise = client.wallet.walletPassphraseChange('', 'newpassword')

await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow(
"RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrasechange"
)
})

it('should walletPassphraseChange when the correct old passphrase is provided', async () => {
const promise = client.wallet.walletPassphraseChange('password', 'newpassword')

await expect(promise).resolves.not.toThrow()
})
})
45 changes: 45 additions & 0 deletions packages/jellyfish-api-core/src/category/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,51 @@ export class Wallet {
async signMessage (address: string, message: string): Promise<string> {
return await this.client.call('signmessage', [address, message], 'number')
}

/**
* Encrypts the wallet for the first time using a custom ‘passphrase’.
* Transactions related to private keys will thereafter require a passphrase before execution.
* To unlock wallet, use 'walletpassphrase'
*
* @param {string} passphrase The wallet passphrase. Must be at least 1 character, but should be long.
* @return {Promise<string>}
*/
async encryptWallet (passphrase: string): Promise<string> {
return await this.client.call('encryptwallet', [passphrase], 'number')
}

/**
* Stores the wallet decryption key in memory for ‘timeout’ seconds.
* Calling 'walletpassphrase' when wallet is unlocked will set a new unlock time that overrides the old setting.
*
* @param {string} passphrase The wallet passphrase. Must be at least 1 character, but should be long.
* @param {number} timeout The time to keep the decryption key in seconds; capped at 100000000 (~3 years).
* @return {Promise<>}
*/
async walletPassphrase (passphrase: string, timeout: number): Promise<void> {
return await this.client.call('walletpassphrase', [passphrase, timeout], 'number')
}

/**
* Changes the wallet passphrase from ‘oldpassphrase’ to ‘newpassphrase’.
*
* @param {string} oldpassphrase The old wallet passphrase.
* @param {string} newpassphrase The new wallet passphrase.
* @return {Promise<>}
*/
async walletPassphraseChange (oldpassphrase: string, newpassphrase: string): Promise<void> {
return await this.client.call('walletpassphrasechange', [oldpassphrase, newpassphrase], 'number')
}

/**
* Removes the wallet encryption key from memory, locking the wallet.
* Unlock wallet by calling 'walletpassphrase' to perform wallet-related methods.
*
* @return {Promise<>}
*/
async walletLock (): Promise<void> {
return await this.client.call('walletlock', [], 'number')
}
}

export interface UTXO {
Expand Down

0 comments on commit 66b16a8

Please sign in to comment.