Lints src/wallet and test/wallet (#1600)

* lint src/wallet/index

* lint generateFaucetWallet

* lint tests

* respond to comments

* change max-lines-per-function to 40

* remove * import

* fix TS issues
This commit is contained in:
Mayukha Vadari
2021-09-13 15:45:32 -04:00
parent 3f29e5781d
commit fe919315d4
5 changed files with 244 additions and 157 deletions

View File

@@ -2,14 +2,14 @@ module.exports = {
root: true, root: true,
// Make ESLint compatible with TypeScript // Make ESLint compatible with TypeScript
parser: "@typescript-eslint/parser", parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
// Enable linting rules with type information from our tsconfig // Enable linting rules with type information from our tsconfig
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
project: ["./tsconfig.eslint.json"], project: ['./tsconfig.eslint.json'],
// Allow the use of imports / ES modules // Allow the use of imports / ES modules
sourceType: "module", sourceType: 'module',
ecmaFeatures: { ecmaFeatures: {
// Enable global strict mode // Enable global strict mode
@@ -24,75 +24,79 @@ module.exports = {
}, },
plugins: [], plugins: [],
extends: ["@xrplf/eslint-config/base", "plugin:mocha/recommended"], extends: ['@xrplf/eslint-config/base', 'plugin:mocha/recommended'],
rules: { rules: {
// Certain rippled APIs require snake_case naming // Certain rippled APIs require snake_case naming
"@typescript-eslint/naming-convention": [ '@typescript-eslint/naming-convention': [
"error", 'error',
{ {
selector: "interface", selector: 'interface',
format: ["PascalCase", 'snake_case'], format: ['PascalCase', 'snake_case'],
}, },
], ],
"max-statements": ["warn", 25], 'max-lines-per-function': [
"id-length": ["error", { exceptions: ["_"] }], // exception for lodash 'warn',
{ max: 40, skipBlankLines: 'true', skipComments: 'true' },
],
'max-statements': ['warn', 25],
'id-length': ['error', { exceptions: ['_'] }], // exception for lodash
}, },
overrides: [ overrides: [
{ {
files: ["test/**/*.ts"], files: ['test/**/*.ts'],
rules: { rules: {
// Removed the max for test files and test helper files, since tests usually need to import more things // Removed the max for test files and test helper files, since tests usually need to import more things
"import/max-dependencies": "off", 'import/max-dependencies': 'off',
// describe blocks count as a function in Mocha tests, and can be insanely long // describe blocks count as a function in Mocha tests, and can be insanely long
"max-lines-per-function": "off", 'max-lines-per-function': 'off',
// Tests can be very long turns off max-line count // Tests can be very long turns off max-line count
"max-lines": "off", 'max-lines': 'off',
// We have lots of statements in tests // We have lots of statements in tests
"max-statements": "off", 'max-statements': 'off',
// We have lots of magic numbers in tests // We have lots of magic numbers in tests
"no-magic-number": "off", 'no-magic-number': 'off',
"@typescript-eslint/no-magic-numbers": "off", '@typescript-eslint/no-magic-numbers': 'off',
// We need to test things without type guards sometimes // We need to test things without type guards sometimes
"@typescript-eslint/no-unsafe-assignment": "off", '@typescript-eslint/no-unsafe-assignment': 'off',
"@typescript-eslint/no-unsafe-call": "off", '@typescript-eslint/no-unsafe-call': 'off',
"@typescript-eslint/consistent-type-assertions": "off", '@typescript-eslint/consistent-type-assertions': 'off',
// We need to mess with internal things to generate certain testing situations // We need to mess with internal things to generate certain testing situations
"@typescript-eslint/no-unsafe-member-access": "off", '@typescript-eslint/no-unsafe-member-access': 'off',
// We need to be able to import xrpl-local // We need to be able to import xrpl-local
"node/no-extraneous-import": [ 'node/no-extraneous-import': [
"error", 'error',
{ {
allowModules: ["xrpl-local"], allowModules: ['xrpl-local'],
}, },
], ],
// Tests are already in 2 callbacks, so max 3 is pretty restrictive // Tests are already in 2 callbacks, so max 3 is pretty restrictive
"max-nested-callbacks": "off", 'max-nested-callbacks': 'off',
}, },
}, },
{ {
files: ["test/models/*.ts"], files: ['test/models/*.ts'],
rules: { rules: {
"@typescript-eslint/consistent-type-assertions": "off", '@typescript-eslint/consistent-type-assertions': 'off',
"@typescript-eslint/no-explicit-any": "off", '@typescript-eslint/no-explicit-any': 'off',
}, },
}, },
{ {
files: [".eslintrc.js", "jest.config.js"], files: ['.eslintrc.js', 'jest.config.js'],
rules: { rules: {
// Removed no-commonjs requirement as eslint must be in common js format // Removed no-commonjs requirement as eslint must be in common js format
"import/no-commonjs": "off", 'import/no-commonjs': 'off',
// Removed this as eslint prevents us from doing this differently // Removed this as eslint prevents us from doing this differently
"import/unambiguous": "off", 'import/unambiguous': 'off',
}, },
}, },
], ],
}; }

View File

@@ -1,40 +1,43 @@
import https = require('https') import { IncomingMessage } from 'http'
import { request as httpsRequest, RequestOptions } from 'https'
import { isValidClassicAddress } from 'ripple-address-codec' import { isValidClassicAddress } from 'ripple-address-codec'
import type { Client } from '..' import type { Client } from '..'
import { errors } from '../common'
import { RippledError, XRPLFaucetError } from '../common/errors' import { RippledError, XRPLFaucetError } from '../common/errors'
import { GeneratedAddress } from '../utils/generateAddress' import { GeneratedAddress } from '../utils/generateAddress'
import Wallet from '.' import Wallet from '.'
export interface FaucetWallet { interface FaucetWallet {
account: GeneratedAddress account: GeneratedAddress
amount: number amount: number
balance: number balance: number
} }
export enum FaucetNetwork { // eslint-disable-next-line no-shadow -- Not previously declared
enum FaucetNetwork {
Testnet = 'faucet.altnet.rippletest.net', Testnet = 'faucet.altnet.rippletest.net',
Devnet = 'faucet.devnet.rippletest.net', Devnet = 'faucet.devnet.rippletest.net',
} }
const INTERVAL_SECONDS = 1 // Interval to check an account balance // Interval to check an account balance
const MAX_ATTEMPTS = 20 // Maximum attempts to retrieve a balance const INTERVAL_SECONDS = 1
// Maximum attempts to retrieve a balance
const MAX_ATTEMPTS = 20
// /**
// Generates a random wallet with some amount of XRP (usually 1000 XRP). * Generates a random wallet with some amount of XRP (usually 1000 XRP).
// *
// @param client - Client. * @param client - Client.
// @param wallet - An existing XRPL Wallet to fund, if undefined, a new Wallet will be created. * @param wallet - An existing XRPL Wallet to fund, if undefined, a new Wallet will be created.
// @returns A Wallet on the Testnet or Devnet that contains some amount of XRP. * @returns A Wallet on the Testnet or Devnet that contains some amount of XRP.
// @throws When either Client isn't connected or unable to fund wallet address. * @throws When either Client isn't connected or unable to fund wallet address.
// z */
async function generateFaucetWallet( async function generateFaucetWallet(
client: Client, client: Client,
wallet?: Wallet, wallet?: Wallet,
): Promise<Wallet | void> { ): Promise<Wallet | undefined> {
if (!client.isConnected()) { if (!client.isConnected()) {
throw new RippledError('Client not connected, cannot call faucet') throw new RippledError('Client not connected, cannot call faucet')
} }
@@ -46,7 +49,7 @@ async function generateFaucetWallet(
: Wallet.generate() : Wallet.generate()
// Create the POST request body // Create the POST request body
const body: Uint8Array | undefined = new TextEncoder().encode( const postBody = new TextEncoder().encode(
JSON.stringify({ JSON.stringify({
destination: fundWallet.classicAddress, destination: fundWallet.classicAddress,
}), }),
@@ -59,83 +62,32 @@ async function generateFaucetWallet(
// Check the address balance is not undefined and is a number // Check the address balance is not undefined and is a number
const startingBalance = const startingBalance =
addressToFundBalance && !isNaN(Number(addressToFundBalance)) addressToFundBalance && !Number.isNaN(Number(addressToFundBalance))
? Number(addressToFundBalance) ? Number(addressToFundBalance)
: 0 : 0
const faucetUrl = getFaucetUrl(client)
// Options to pass to https.request // Options to pass to https.request
const options = { const options = getOptions(client, postBody)
hostname: faucetUrl,
port: 443,
path: '/accounts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': body ? body.length : 0,
},
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = https.request(options, (response) => { const request = httpsRequest(options, (response) => {
const chunks: any[] = [] const chunks: Uint8Array[] = []
response.on('data', (d) => { response.on('data', (data) => chunks.push(data))
chunks.push(d) // eslint-disable-next-line @typescript-eslint/no-misused-promises -- not actually misused, different resolve/reject
}) response.on('end', async () =>
response.on('end', async () => { onEnd(
const body = Buffer.concat(chunks).toString() response,
chunks,
// "application/json; charset=utf-8" client,
if (response.headers['content-type']?.startsWith('application/json')) { startingBalance,
const faucetWallet: FaucetWallet = JSON.parse(body) fundWallet,
const classicAddress = faucetWallet.account.classicAddress resolve,
reject,
if (classicAddress) { ),
try { )
// Check at regular interval if the address is enabled on the XRPL and funded
const isFunded = await hasAddressBalanceIncreased(
client,
classicAddress,
startingBalance,
)
if (isFunded) {
resolve(fundWallet)
} else {
reject(
new XRPLFaucetError(
`Unable to fund address with faucet after waiting ${
INTERVAL_SECONDS * MAX_ATTEMPTS
} seconds`,
),
)
}
} catch (err) {
if (err instanceof Error) {
reject(new XRPLFaucetError(err.message))
}
reject(err)
}
} else {
reject(
new XRPLFaucetError(
`The faucet account classic address is undefined`,
),
)
}
} else {
reject({
statusCode: response.statusCode,
contentType: response.headers['content-type'],
body,
})
}
})
}) })
// POST the body // POST the body
request.write(body || '') request.write(postBody)
request.on('error', (error) => { request.on('error', (error) => {
reject(error) reject(error)
@@ -145,12 +97,104 @@ async function generateFaucetWallet(
}) })
} }
function getOptions(client: Client, postBody: Uint8Array): RequestOptions {
return {
hostname: getFaucetUrl(client),
port: 443,
path: '/accounts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': postBody.length,
},
}
}
// eslint-disable-next-line max-params -- Helper function created for organizational purposes
async function onEnd(
response: IncomingMessage,
chunks: Uint8Array[],
client: Client,
startingBalance: number,
fundWallet: Wallet,
resolve: (wallet?: Wallet) => void,
reject: (err: ErrorConstructor | Error | unknown) => void,
): Promise<void> {
const body = Buffer.concat(chunks).toString()
// "application/json; charset=utf-8"
if (response.headers['content-type']?.startsWith('application/json')) {
await processSuccessfulResponse(
client,
body,
startingBalance,
fundWallet,
resolve,
reject,
)
} else {
reject(
new XRPLFaucetError(
`Content type is not \`application/json\`: ${JSON.stringify({
statusCode: response.statusCode,
contentType: response.headers['content-type'],
body,
})}`,
),
)
}
}
// eslint-disable-next-line max-params -- Only used as a helper function
async function processSuccessfulResponse(
client: Client,
body: string,
startingBalance: number,
fundWallet: Wallet,
resolve: (wallet?: Wallet) => void,
reject: (err: ErrorConstructor | Error | unknown) => void,
): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- We know this is safe and correct
const faucetWallet: FaucetWallet = JSON.parse(body)
const classicAddress = faucetWallet.account.classicAddress
if (!classicAddress) {
reject(new XRPLFaucetError(`The faucet account is undefined`))
return
}
try {
// Check at regular interval if the address is enabled on the XRPL and funded
const isFunded = await hasAddressBalanceIncreased(
client,
classicAddress,
startingBalance,
)
if (isFunded) {
resolve(fundWallet)
} else {
reject(
new XRPLFaucetError(
`Unable to fund address with faucet after waiting ${
INTERVAL_SECONDS * MAX_ATTEMPTS
} seconds`,
),
)
}
} catch (err) {
if (err instanceof Error) {
reject(new XRPLFaucetError(err.message))
}
reject(err)
}
}
/** /**
* Retrieves an XRPL address XRP balance. * Retrieves an XRPL address XRP balance.
* *
* @param client - Client. * @param client - Client.
* @param address - XRPL address. * @param address - XRPL address.
* @returns * @returns The address's balance of XRP.
*/ */
async function getAddressXrpBalance( async function getAddressXrpBalance(
client: Client, client: Client,
@@ -166,7 +210,12 @@ async function getAddressXrpBalance(
) )
return xrpBalance[0].value return xrpBalance[0].value
} catch (err) { } catch (err) {
return `Unable to retrieve ${address} balance. Error: ${err}` if (err instanceof Error) {
throw new XRPLFaucetError(
`Unable to retrieve ${address} balance. Error: ${err.message}`,
)
}
throw err
} }
} }
@@ -185,12 +234,13 @@ async function hasAddressBalanceIncreased(
): Promise<boolean> { ): Promise<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let attempts = MAX_ATTEMPTS let attempts = MAX_ATTEMPTS
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- Not actually misused here, different resolve
const interval = setInterval(async () => { const interval = setInterval(async () => {
if (attempts < 0) { if (attempts < 0) {
clearInterval(interval) clearInterval(interval)
resolve(false) resolve(false)
} else { } else {
attempts-- attempts -= 1
} }
try { try {
@@ -201,11 +251,14 @@ async function hasAddressBalanceIncreased(
} }
} catch (err) { } catch (err) {
clearInterval(interval) clearInterval(interval)
reject( if (err instanceof Error) {
new errors.XRPLFaucetError( reject(
`Unable to check if the address ${address} balance has increased. Error: ${err}`, new XRPLFaucetError(
), `Unable to check if the address ${address} balance has increased. Error: ${err.message}`,
) ),
)
}
reject(err)
} }
}, INTERVAL_SECONDS * 1000) }, INTERVAL_SECONDS * 1000)
}) })
@@ -217,7 +270,7 @@ async function hasAddressBalanceIncreased(
* @param client - Client. * @param client - Client.
* @returns A {@link FaucetNetwork}. * @returns A {@link FaucetNetwork}.
*/ */
export function getFaucetUrl(client: Client) { function getFaucetUrl(client: Client): FaucetNetwork | undefined {
const connectionUrl = client.connection.getUrl() const connectionUrl = client.connection.getUrl()
// 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server // 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server
@@ -233,3 +286,9 @@ export function getFaucetUrl(client: Client) {
} }
export default generateFaucetWallet export default generateFaucetWallet
const _private = {
FaucetNetwork,
getFaucetUrl,
}
export { _private }

View File

@@ -11,23 +11,36 @@ import {
import ECDSA from '../common/ecdsa' import ECDSA from '../common/ecdsa'
import { ValidationError } from '../common/errors' import { ValidationError } from '../common/errors'
import { Transaction } from '../models/transactions'
import { signOffline } from '../transaction/sign' import { signOffline } from '../transaction/sign'
import { SignOptions } from '../transaction/types' import { SignOptions } from '../transaction/types'
const DEFAULT_ALGORITHM: ECDSA = ECDSA.ed25519
const DEFAULT_DERIVATION_PATH = "m/44'/144'/0'/0/0"
function hexFromBuffer(buffer: Buffer): string {
return buffer.toString('hex').toUpperCase()
}
/** /**
* A utility for deriving a wallet composed of a keypair (publicKey/privateKey). * A utility for deriving a wallet composed of a keypair (publicKey/privateKey).
* A wallet can be derived from either a seed, mnemnoic, or entropy (array of random numbers). * A wallet can be derived from either a seed, mnemnoic, or entropy (array of random numbers).
* It provides functionality to sign/verify transactions offline. * It provides functionality to sign/verify transactions offline.
*/ */
class Wallet { class Wallet {
readonly publicKey: string public readonly publicKey: string
readonly privateKey: string public readonly privateKey: string
readonly classicAddress: string public readonly classicAddress: string
readonly seed?: string public readonly seed?: string
private static readonly defaultAlgorithm: ECDSA = ECDSA.ed25519
private static readonly defaultDerivationPath: string = "m/44'/144'/0'/0/0"
constructor(publicKey: string, privateKey: string, seed?: string) { /**
* Creates a new Wallet.
*
* @param publicKey - The public key for the account.
* @param privateKey - The private key used for signing transactions for the account.
* @param seed - (Optional) The seed used to derive the account keys.
*/
public constructor(publicKey: string, privateKey: string, seed?: string) {
this.publicKey = publicKey this.publicKey = publicKey
this.privateKey = privateKey this.privateKey = privateKey
this.classicAddress = deriveAddress(publicKey) this.classicAddress = deriveAddress(publicKey)
@@ -40,7 +53,7 @@ class Wallet {
* @param algorithm - The digital signature algorithm to generate an address for. * @param algorithm - The digital signature algorithm to generate an address for.
* @returns A new Wallet derived from a generated seed. * @returns A new Wallet derived from a generated seed.
*/ */
static generate(algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet { public static generate(algorithm: ECDSA = DEFAULT_ALGORITHM): Wallet {
const seed = generateSeed({ algorithm }) const seed = generateSeed({ algorithm })
return Wallet.fromSeed(seed) return Wallet.fromSeed(seed)
} }
@@ -52,9 +65,9 @@ class Wallet {
* @param algorithm - The digital signature algorithm to generate an address for. * @param algorithm - The digital signature algorithm to generate an address for.
* @returns A Wallet derived from a seed. * @returns A Wallet derived from a seed.
*/ */
static fromSeed( public static fromSeed(
seed: string, seed: string,
algorithm: ECDSA = Wallet.defaultAlgorithm, algorithm: ECDSA = DEFAULT_ALGORITHM,
): Wallet { ): Wallet {
return Wallet.deriveWallet(seed, algorithm) return Wallet.deriveWallet(seed, algorithm)
} }
@@ -65,10 +78,11 @@ class Wallet {
* @param mnemonic - A string consisting of words (whitespace delimited) used to derive a wallet. * @param mnemonic - A string consisting of words (whitespace delimited) used to derive a wallet.
* @param derivationPath - The path to derive a keypair (publicKey/privateKey) from a seed (that was converted from a mnemonic). * @param derivationPath - The path to derive a keypair (publicKey/privateKey) from a seed (that was converted from a mnemonic).
* @returns A Wallet derived from a mnemonic. * @returns A Wallet derived from a mnemonic.
* @throws ValidationError if unable to derive private key from mnemonic input.
*/ */
static fromMnemonic( public static fromMnemonic(
mnemonic: string, mnemonic: string,
derivationPath: string = Wallet.defaultDerivationPath, derivationPath: string = DEFAULT_DERIVATION_PATH,
): Wallet { ): Wallet {
const seed = mnemonicToSeedSync(mnemonic) const seed = mnemonicToSeedSync(mnemonic)
const masterNode = fromSeed(seed) const masterNode = fromSeed(seed)
@@ -79,8 +93,8 @@ class Wallet {
) )
} }
const publicKey = Wallet.hexFromBuffer(node.publicKey) const publicKey = hexFromBuffer(node.publicKey)
const privateKey = Wallet.hexFromBuffer(node.privateKey) const privateKey = hexFromBuffer(node.privateKey)
return new Wallet(publicKey, `00${privateKey}`) return new Wallet(publicKey, `00${privateKey}`)
} }
@@ -91,9 +105,9 @@ class Wallet {
* @param algorithm - The digital signature algorithm to generate an address for. * @param algorithm - The digital signature algorithm to generate an address for.
* @returns A Wallet derived from an entropy. * @returns A Wallet derived from an entropy.
*/ */
static fromEntropy( public static fromEntropy(
entropy: Uint8Array | number[], entropy: Uint8Array | number[],
algorithm: ECDSA = Wallet.defaultAlgorithm, algorithm: ECDSA = DEFAULT_ALGORITHM,
): Wallet { ): Wallet {
const options = { const options = {
entropy: Uint8Array.from(entropy), entropy: Uint8Array.from(entropy),
@@ -103,13 +117,16 @@ class Wallet {
return Wallet.deriveWallet(seed, algorithm) return Wallet.deriveWallet(seed, algorithm)
} }
private static hexFromBuffer(buffer: Buffer): string { /**
return buffer.toString('hex').toUpperCase() * Derive a Wallet from a seed.
} *
* @param seed - The seed used to derive the wallet.
* @param algorithm - The algorithm used to do the derivation.
* @returns A Wallet derived from the seed.
*/
private static deriveWallet( private static deriveWallet(
seed: string, seed: string,
algorithm: ECDSA = Wallet.defaultAlgorithm, algorithm: ECDSA = DEFAULT_ALGORITHM,
): Wallet { ): Wallet {
const { publicKey, privateKey } = deriveKeypair(seed, { algorithm }) const { publicKey, privateKey } = deriveKeypair(seed, { algorithm })
return new Wallet(publicKey, privateKey, seed) return new Wallet(publicKey, privateKey, seed)
@@ -122,8 +139,8 @@ class Wallet {
* @param options - Options to include for signing. * @param options - Options to include for signing.
* @returns A signed transaction. * @returns A signed transaction.
*/ */
signTransaction( public signTransaction(
transaction: any, // TODO: transaction should be typed with Transaction type. transaction: Transaction,
options: SignOptions = { signAs: '' }, options: SignOptions = { signAs: '' },
): string { ): string {
return signOffline(this, JSON.stringify(transaction), options) return signOffline(this, JSON.stringify(transaction), options)
@@ -136,7 +153,7 @@ class Wallet {
* @param signedTransaction - A signed transaction (hex string of signTransaction result) to be verified offline. * @param signedTransaction - A signed transaction (hex string of signTransaction result) to be verified offline.
* @returns Returns true if a signedTransaction is valid. * @returns Returns true if a signedTransaction is valid.
*/ */
verifyTransaction(signedTransaction: string): boolean { public verifyTransaction(signedTransaction: string): boolean {
const tx = decode(signedTransaction) const tx = decode(signedTransaction)
const messageHex: string = encodeForSigning(tx) const messageHex: string = encodeForSigning(tx)
const signature = tx.TxnSignature const signature = tx.TxnSignature
@@ -147,14 +164,19 @@ class Wallet {
* Gets an X-address in Testnet/Mainnet format. * Gets an X-address in Testnet/Mainnet format.
* *
* @param tag - A tag to be included within the X-address. * @param tag - A tag to be included within the X-address.
* @param test - A boolean to indicate if X-address should be in Testnet (true) or Mainnet (false) format. * @param isTestnet - A boolean to indicate if X-address should be in Testnet (true) or Mainnet (false) format.
* @returns An X-address. * @returns An X-address.
*/ */
getXAddress(tag: number | false = false, test = false): string { public getXAddress(tag: number | false = false, isTestnet = false): string {
return classicAddressToXAddress(this.classicAddress, tag, test) return classicAddressToXAddress(this.classicAddress, tag, isTestnet)
} }
getClassicAddress(): string { /**
* Gets the classic address of the account this wallet represents.
*
* @returns A classic address.
*/
public getClassicAddress(): string {
return deriveAddress(this.publicKey) return deriveAddress(this.publicKey)
} }
} }

View File

@@ -1,8 +1,9 @@
import { assert } from 'chai' import { assert } from 'chai'
import { getFaucetUrl, FaucetNetwork } from '../src/wallet/generateFaucetWallet' import { _private } from '../../src/wallet/generateFaucetWallet'
import { setupClient, teardownClient } from '../setupClient'
import { setupClient, teardownClient } from './setupClient' const { FaucetNetwork, getFaucetUrl } = _private
describe('Get Faucet URL', function () { describe('Get Faucet URL', function () {
beforeEach(setupClient) beforeEach(setupClient)

View File

@@ -1,6 +1,7 @@
import { assert } from 'chai' import { assert } from 'chai'
import ECDSA from '../../src/common/ecdsa' import ECDSA from '../../src/common/ecdsa'
import { Payment } from '../../src/models/transactions'
import Wallet from '../../src/wallet' import Wallet from '../../src/wallet'
/** /**
@@ -155,7 +156,7 @@ describe('Wallet', function () {
const address = 'rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc' const address = 'rhvh5SrgBL5V8oeV9EpDuVszeJSSCEkbPc'
it('signs a transaction offline', function () { it('signs a transaction offline', function () {
const txJSON = { const txJSON: Payment = {
TransactionType: 'Payment', TransactionType: 'Payment',
Account: address, Account: address,
Destination: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r', Destination: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',