mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Merge pull request #144 from Permissionless-Software-Foundation/ct-unstable
Integrating minimal-slp-wallet. Wallet creation at startup
This commit is contained in:
@@ -78,6 +78,10 @@ npm install
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
This app is intended to be started via a bash shell script. See the environment variables used to configure this app in the [config/env/common.js file](./config/env/common.js).
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
The file layout of this repository follows the file layout of [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture). Understaning the principles laid out this article will help developers navigate the code base.
|
The file layout of this repository follows the file layout of [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture). Understaning the principles laid out this article will help developers navigate the code base.
|
||||||
|
|||||||
Vendored
+45
-10
@@ -43,14 +43,43 @@ export default {
|
|||||||
? process.env.EMAILPASS
|
? process.env.EMAILPASS
|
||||||
: 'emailpassword',
|
: 'emailpassword',
|
||||||
|
|
||||||
|
// BEGIN WALLET CONFIGURATION
|
||||||
|
|
||||||
|
// BCH Mnemonic for generating encryption keys and payment address
|
||||||
|
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||||
|
|
||||||
|
// (Optional) use a wallet file generated by psf-bch-wallet.
|
||||||
|
walletFile: process.env.WALLET_FILE
|
||||||
|
? process.env.WALLET_FILE
|
||||||
|
: `${__dirname.toString()}/../../wallet.json`,
|
||||||
|
|
||||||
|
// Wallet Configuration
|
||||||
|
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
|
||||||
|
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
|
||||||
|
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
|
||||||
|
|
||||||
|
// Wallet API Server
|
||||||
|
// Default web2 server: https://api.fullstack.cash/v5/
|
||||||
|
// Default web3 server: https://free-bch.fullstack.cash/
|
||||||
|
apiServer: process.env.WALLET_INTERFACE === 'web2'
|
||||||
|
? (
|
||||||
|
process.env.APISERVER
|
||||||
|
? process.env.APISERVER
|
||||||
|
: 'https://api.fullstack.cash/v5/'
|
||||||
|
)
|
||||||
|
: 'https://free-bch.fullstack.cash/',
|
||||||
|
|
||||||
|
// Basic Authentication Password for connecting to bch-api through a web 2
|
||||||
|
// REST API over HTTP.
|
||||||
|
authPass: process.env.WALLET_AUTH_PASS
|
||||||
|
? process.env.WALLET_AUTH_PASS
|
||||||
|
: '',
|
||||||
|
|
||||||
// FullStack.cash account information, used for automatic JWT handling.
|
// FullStack.cash account information, used for automatic JWT handling.
|
||||||
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
||||||
authServer: process.env.AUTHSERVER
|
authServer: process.env.AUTHSERVER
|
||||||
? process.env.AUTHSERVER
|
? process.env.AUTHSERVER
|
||||||
: 'https://auth.fullstack.cash',
|
: 'https://auth.fullstack.cash',
|
||||||
apiServer: process.env.APISERVER
|
|
||||||
? process.env.APISERVER
|
|
||||||
: 'https://api.fullstack.cash/v5/',
|
|
||||||
fullstackLogin: process.env.FULLSTACKLOGIN
|
fullstackLogin: process.env.FULLSTACKLOGIN
|
||||||
? process.env.FULLSTACKLOGIN
|
? process.env.FULLSTACKLOGIN
|
||||||
: 'demo@demo.com',
|
: 'demo@demo.com',
|
||||||
@@ -58,8 +87,17 @@ export default {
|
|||||||
? process.env.FULLSTACKPASS
|
? process.env.FULLSTACKPASS
|
||||||
: 'demo',
|
: 'demo',
|
||||||
|
|
||||||
// IPFS settings.
|
// END WALLET CONFIGURATION
|
||||||
useIpfs: process.env.DISABLE_IPFS ? false : true, // Disable IPFS flag
|
|
||||||
|
// BEGIN IPFS CONFIGURATION
|
||||||
|
|
||||||
|
// Debug verbosity level of helia-coord.
|
||||||
|
// 0 = no debug logs. 3 = maximum logs.
|
||||||
|
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||||
|
|
||||||
|
// Enable/Disable the IPFS node at startup. Enabled by default.
|
||||||
|
useIpfs: process.env.DISABLE_IPFS ? false : true,
|
||||||
|
|
||||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||||
// SSL domain used for websocket connection via browsers.
|
// SSL domain used for websocket connection via browsers.
|
||||||
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
|
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
|
||||||
@@ -88,11 +126,6 @@ export default {
|
|||||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
||||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
|
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
|
||||||
|
|
||||||
// BCH Mnemonic for generating encryption keys and payment address
|
|
||||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
|
||||||
|
|
||||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
|
||||||
|
|
||||||
// Settings for production, using external go-ipfs node.
|
// Settings for production, using external go-ipfs node.
|
||||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||||
@@ -110,4 +143,6 @@ export default {
|
|||||||
// v2 Circuit Relay server (FullStack.cash)
|
// v2 Circuit Relay server (FullStack.cash)
|
||||||
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
|
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// END IPFS CONFIGURATION
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -9,17 +9,15 @@ import BCHJS from '@psf/bch-js'
|
|||||||
|
|
||||||
// Load individual adapter libraries.
|
// Load individual adapter libraries.
|
||||||
import IPFSAdapter from './ipfs/index.js'
|
import IPFSAdapter from './ipfs/index.js'
|
||||||
|
|
||||||
import LocalDB from './localdb/index.js'
|
import LocalDB from './localdb/index.js'
|
||||||
import LogsAPI from './logapi.js'
|
import LogsAPI from './logapi.js'
|
||||||
import Passport from './passport.js'
|
import Passport from './passport.js'
|
||||||
import Nodemailer from './nodemailer.js'
|
import Nodemailer from './nodemailer.js'
|
||||||
|
|
||||||
// const { wlogger } = require('./wlogger')
|
// const { wlogger } = require('./wlogger')
|
||||||
import JSONFiles from './json-files.js'
|
import JSONFiles from './json-files.js'
|
||||||
|
|
||||||
import FullStackJWT from './fullstack-jwt.js'
|
import FullStackJWT from './fullstack-jwt.js'
|
||||||
import config from '../../config/index.js'
|
import config from '../../config/index.js'
|
||||||
|
import Wallet from './wallet.adapter.js'
|
||||||
|
|
||||||
class Adapters {
|
class Adapters {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -32,6 +30,7 @@ class Adapters {
|
|||||||
this.jsonFiles = new JSONFiles()
|
this.jsonFiles = new JSONFiles()
|
||||||
this.bchjs = new BCHJS()
|
this.bchjs = new BCHJS()
|
||||||
this.config = config
|
this.config = config
|
||||||
|
this.wallet = new Wallet(localConfig)
|
||||||
|
|
||||||
// Get a valid JWT API key and instance bch-js.
|
// Get a valid JWT API key and instance bch-js.
|
||||||
this.fullStackJwt = new FullStackJWT(config)
|
this.fullStackJwt = new FullStackJWT(config)
|
||||||
@@ -39,14 +38,22 @@ class Adapters {
|
|||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
try {
|
try {
|
||||||
|
let apiToken
|
||||||
if (this.config.getJwtAtStartup) {
|
if (this.config.getJwtAtStartup) {
|
||||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||||
// to all the rest of the apps controllers and adapters.
|
// to all the rest of the apps controllers and adapters.
|
||||||
await this.fullStackJwt.getJWT()
|
apiToken = await this.fullStackJwt.getJWT()
|
||||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||||
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a default instance of minimal-slp-wallet without initializing it
|
||||||
|
// (without retrieving the wallets UTXOs). This instance will be overwritten
|
||||||
|
// if the operator has configured BCH payments.
|
||||||
|
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
|
||||||
|
await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
|
||||||
|
this.bchjs = this.wallet.bchWallet.bchjs
|
||||||
|
|
||||||
// Start the IPFS node.
|
// Start the IPFS node.
|
||||||
// Do not start these adapters if this is an e2e test.
|
// Do not start these adapters if this is an e2e test.
|
||||||
if (this.config.env !== 'test') {
|
if (this.config.env !== 'test') {
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
Wallet adapter. This library instantiates minimal-slp-wallet based on different
|
||||||
|
configuration settings. That single instance of minimal-slp-wallet is then
|
||||||
|
easily passed around the rest of this application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import BchWallet from 'minimal-slp-wallet'
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import config from '../../config/index.js'
|
||||||
|
import JsonFiles from './json-files.js'
|
||||||
|
|
||||||
|
class Wallet {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Encapsulate dependencies
|
||||||
|
this.config = config
|
||||||
|
this.BchWallet = BchWallet
|
||||||
|
this.jsonFiles = new JsonFiles()
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions
|
||||||
|
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used for initializing the wallet, without waiting to update the wallet
|
||||||
|
// UTXOs from the blockchain.
|
||||||
|
// This is useful when the wallet is simply needed to make calls to the blockchain,
|
||||||
|
// and there is no need to hydrate it with UTXO data.
|
||||||
|
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
|
||||||
|
try {
|
||||||
|
// Use the apiToken from the config settings, if one is not passed-in at
|
||||||
|
// run-time.
|
||||||
|
if (!advancedConfig.apiToken) {
|
||||||
|
advancedConfig.apiToken = this.config.apiToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an authentication password is passed in the config, add it.
|
||||||
|
if (this.config.authPass) {
|
||||||
|
advancedConfig.authPass = this.config.authPass
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect and configure different blockchain infrastructure settings.
|
||||||
|
if (this.config.walletInterface === 'web2') {
|
||||||
|
advancedConfig.interface = 'rest-api'
|
||||||
|
advancedConfig.restURL = this.config.apiServer
|
||||||
|
} else {
|
||||||
|
// By default use the web3 Cash Stack (https://cashstack.info)
|
||||||
|
advancedConfig.interface = 'consumer-api'
|
||||||
|
advancedConfig.restURL = this.config.apiServer
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('advancedConfig setting when creating wallet: ', advancedConfig)
|
||||||
|
|
||||||
|
// Instantiate minimal-slp-wallet.
|
||||||
|
if (walletData.mnemonic) {
|
||||||
|
// Instance the wallet using the mnemonic passed in to this function.
|
||||||
|
console.log('Mnemonic provided to wallet library from calling function.')
|
||||||
|
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
|
||||||
|
} else if (this.config.mnemonic) {
|
||||||
|
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
|
||||||
|
console.log('Mnemonic retrieved from config file or environment variable.')
|
||||||
|
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
|
||||||
|
} else {
|
||||||
|
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
|
||||||
|
console.log('New mnemonic generated.')
|
||||||
|
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for wallet to initialize.
|
||||||
|
await this.bchWallet.walletInfoPromise
|
||||||
|
console.log('BCH wallet initialized.')
|
||||||
|
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
|
||||||
|
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
|
||||||
|
|
||||||
|
return this.bchWallet
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in instanceWalletWithoutInitialization()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is used for easier mocking of unit tests.
|
||||||
|
async _instanceWallet (mnemonic, config) {
|
||||||
|
const wallet = new this.BchWallet(mnemonic, config)
|
||||||
|
await wallet.walletInfoPromise
|
||||||
|
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the wallet file, or create one if the file doesn't exist.
|
||||||
|
// Does not instance the wallet. The output of this function is expected to
|
||||||
|
// be passed to instanceWallet().
|
||||||
|
async openWallet (advancedConfig = {}) {
|
||||||
|
try {
|
||||||
|
let walletData
|
||||||
|
|
||||||
|
// Try to open the wallet.json file.
|
||||||
|
try {
|
||||||
|
// console.log('this.config.walletFile: ', this.config.walletFile)
|
||||||
|
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
|
||||||
|
} catch (err) {
|
||||||
|
// Create a new wallet file if one does not already exist.
|
||||||
|
console.log('Wallet file not found. Creating new wallet.json file.')
|
||||||
|
|
||||||
|
// Create a new wallet.
|
||||||
|
// No-Update flag creates wallet without making any network calls.
|
||||||
|
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
|
||||||
|
|
||||||
|
walletData = walletInstance.walletInfo
|
||||||
|
|
||||||
|
// Add the nextAddress property
|
||||||
|
walletData.nextAddress = 1
|
||||||
|
|
||||||
|
// Write the wallet data to the JSON file.
|
||||||
|
await this.jsonFiles.writeJSON(walletData, this.config.walletFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
return walletData
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in openWallet()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of minimal-slp-wallet. Same as
|
||||||
|
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
|
||||||
|
// its UTXOs (wallet balance and tokens).
|
||||||
|
async instanceWallet (walletData = {}, advancedConfig = {}) {
|
||||||
|
try {
|
||||||
|
// Instance the wallet without initialization.
|
||||||
|
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
|
||||||
|
|
||||||
|
// Initialize the wallet
|
||||||
|
await this.bchWallet.initialize()
|
||||||
|
|
||||||
|
return this.bchWallet
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in wallet.js/instanceWallet()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increments the 'nextAddress' property in the wallet file. This property
|
||||||
|
// indicates the HD index that should be used to generate a key pair for
|
||||||
|
// storing funds for Offers.
|
||||||
|
// This function opens the wallet file, increments the nextAddress property,
|
||||||
|
// then saves the change to the wallet file.
|
||||||
|
async incrementNextAddress () {
|
||||||
|
try {
|
||||||
|
const walletData = await this.openWallet()
|
||||||
|
// console.log('original walletdata: ', walletData)
|
||||||
|
walletData.nextAddress++
|
||||||
|
// console.log('walletData finish: ', walletData)
|
||||||
|
await this.jsonFiles.writeJSON(walletData, this.WALLET_FILE)
|
||||||
|
// Update the working instance of the wallet.
|
||||||
|
this.bchWallet.walletInfo.nextAddress++
|
||||||
|
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
|
||||||
|
return walletData.nextAddress
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in incrementNextAddress()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method returns an object that contains a private key WIF, public address,
|
||||||
|
// and the index of the HD wallet that the key pair was generated from.
|
||||||
|
// TODO: Allow input integer. If input is used, use that as the index. If no
|
||||||
|
// input is provided, then call incrementNextAddress().
|
||||||
|
async getKeyPair (hdIndex = 0) {
|
||||||
|
try {
|
||||||
|
if (!hdIndex) {
|
||||||
|
// Increment the HD index and generate a new key pair.
|
||||||
|
hdIndex = await this.incrementNextAddress()
|
||||||
|
}
|
||||||
|
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||||
|
// root seed buffer
|
||||||
|
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
|
||||||
|
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
|
||||||
|
// HDNode of BIP44 account
|
||||||
|
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
|
||||||
|
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||||
|
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
|
||||||
|
console.log('Generating a new key pair for cashAddress: ', cashAddress)
|
||||||
|
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
|
||||||
|
const outObj = {
|
||||||
|
cashAddress,
|
||||||
|
wif,
|
||||||
|
hdIndex
|
||||||
|
}
|
||||||
|
return outObj
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in getKeyPair()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize the wallet by consolidating the UTXOs.
|
||||||
|
async optimize () {
|
||||||
|
const UTXO_THREASHOLD = 7
|
||||||
|
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
|
||||||
|
const dryRunOut = await this.bchWallet.optimize(true)
|
||||||
|
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
|
||||||
|
// Consolidate BCH UTXOs if the count is above the threashold.
|
||||||
|
const txids = await this.bchWallet.optimize()
|
||||||
|
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the balance of the wallet in sats and PSF tokens.
|
||||||
|
// This function is called by the GET /entry/balance controller.
|
||||||
|
async getBalance () {
|
||||||
|
const balance = await this.bchWallet.getBalance()
|
||||||
|
// console.log('balance: ', balance)
|
||||||
|
const tokens = await this.bchWallet.listTokens()
|
||||||
|
// console.log('tokens: ', tokens)
|
||||||
|
// Find the array entry for the PSF token
|
||||||
|
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
|
||||||
|
// console.log('psfTokens: ', psfTokens)
|
||||||
|
let psfBalance = 0
|
||||||
|
if (psfTokens) {
|
||||||
|
psfBalance = psfTokens.qty
|
||||||
|
}
|
||||||
|
const outObj = {
|
||||||
|
satBalance: balance,
|
||||||
|
psfBalance,
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
return outObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wallet
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the Wallet Adapter library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import BchWallet from 'minimal-slp-wallet'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import WalletAdapter from '../../../src/adapters/wallet.adapter.js'
|
||||||
|
import { MockBchWallet } from '../mocks/adapters/wallet.js'
|
||||||
|
|
||||||
|
// Hack to get __dirname back.
|
||||||
|
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||||
|
import * as url from 'url'
|
||||||
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
|
||||||
|
// Global constants
|
||||||
|
const testWalletFile = `${__dirname.toString()}/test-wallet.json`
|
||||||
|
|
||||||
|
describe('#wallet', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
// Delete the test file if it exists.
|
||||||
|
try {
|
||||||
|
deleteFile(testWalletFile)
|
||||||
|
} catch (err) { }
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new WalletAdapter()
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
// Delete the test file if it exists.
|
||||||
|
try {
|
||||||
|
deleteFile(testWalletFile)
|
||||||
|
} catch (err) { }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#_instanceWallet', () => {
|
||||||
|
it('should create a wallet given a mnemonic', async () => {
|
||||||
|
const mnemonic = 'wagon tray learn flat erase laugh lonely rug check captain jacket morning'
|
||||||
|
const result = await uut._instanceWallet(mnemonic)
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.equal(result.walletInfo.mnemonic, mnemonic)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#openWallet', () => {
|
||||||
|
it('should create a new wallet when wallet file does not exist', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
uut.BchWallet = MockBchWallet
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.walletFile = testWalletFile
|
||||||
|
const result = await uut.openWallet()
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.property(result, 'mnemonic')
|
||||||
|
assert.property(result, 'privateKey')
|
||||||
|
assert.property(result, 'publicKey')
|
||||||
|
assert.property(result, 'cashAddress')
|
||||||
|
assert.property(result, 'address')
|
||||||
|
assert.property(result, 'slpAddress')
|
||||||
|
assert.property(result, 'legacyAddress')
|
||||||
|
assert.property(result, 'hdPath')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should open existing wallet file', async () => {
|
||||||
|
// This test case uses the file created in the previous test case.
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.walletFile = testWalletFile
|
||||||
|
const result = await uut.openWallet()
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.property(result, 'mnemonic')
|
||||||
|
assert.property(result, 'privateKey')
|
||||||
|
assert.property(result, 'publicKey')
|
||||||
|
assert.property(result, 'cashAddress')
|
||||||
|
assert.property(result, 'address')
|
||||||
|
assert.property(result, 'slpAddress')
|
||||||
|
assert.property(result, 'legacyAddress')
|
||||||
|
assert.property(result, 'hdPath')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
uut.config.walletFile = ''
|
||||||
|
uut.BchWallet = () => {
|
||||||
|
}
|
||||||
|
await uut.openWallet()
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'this.BchWallet is not a constructor')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#instanceWalletWithoutInitialization', () => {
|
||||||
|
it('should create an instance of BchWallet', async () => {
|
||||||
|
// Create a mock wallet.
|
||||||
|
const mockWallet = new BchWallet()
|
||||||
|
await mockWallet.walletInfoPromise
|
||||||
|
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||||
|
uut.config.authPass = 'fake-auth-pass'
|
||||||
|
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.config.walletFile = testWalletFile
|
||||||
|
const walletData = await uut.openWallet()
|
||||||
|
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
const result = await uut.instanceWalletWithoutInitialization(walletData)
|
||||||
|
// console.log('result: ', result)
|
||||||
|
|
||||||
|
assert.property(result, 'walletInfoPromise')
|
||||||
|
assert.property(result, 'walletInfo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut, '_instanceWallet').rejects(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.instanceWalletWithoutInitialization()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an instance of BchWallet using web2 infra', async () => {
|
||||||
|
// Create a mock wallet.
|
||||||
|
const mockWallet = new BchWallet()
|
||||||
|
await mockWallet.walletInfoPromise
|
||||||
|
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||||
|
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.config.walletFile = testWalletFile
|
||||||
|
const walletData = await uut.openWallet()
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
|
||||||
|
// Force desired code path
|
||||||
|
uut.config.walletInterface = 'web2'
|
||||||
|
const result = await uut.instanceWalletWithoutInitialization(walletData)
|
||||||
|
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.property(result, 'walletInfoPromise')
|
||||||
|
assert.property(result, 'walletInfo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should generate wallet from mnemonic in config', async () => {
|
||||||
|
// Create a mock wallet.
|
||||||
|
const mockWallet = new BchWallet()
|
||||||
|
await mockWallet.walletInfoPromise
|
||||||
|
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||||
|
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.config.walletFile = testWalletFile
|
||||||
|
const walletData = await uut.openWallet()
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
|
||||||
|
const originalConfig = uut.config.mnemonic
|
||||||
|
uut.config.mnemonic = walletData.mnemonic
|
||||||
|
|
||||||
|
const result = await uut.instanceWalletWithoutInitialization({})
|
||||||
|
// console.log('result: ', result)
|
||||||
|
|
||||||
|
uut.config.mnemonic = originalConfig
|
||||||
|
|
||||||
|
assert.property(result, 'walletInfoPromise')
|
||||||
|
assert.property(result, 'walletInfo')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#instanceWallet', () => {
|
||||||
|
it('should create an instance of BchWallet', async () => {
|
||||||
|
// Create a mock wallet.
|
||||||
|
const mockWallet = new BchWallet()
|
||||||
|
await mockWallet.walletInfoPromise
|
||||||
|
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||||
|
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.WALLET_FILE = testWalletFile
|
||||||
|
const walletData = await uut.openWallet()
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
const result = await uut.instanceWallet(walletData)
|
||||||
|
// console.log('result: ', result)
|
||||||
|
|
||||||
|
assert.property(result, 'walletInfoPromise')
|
||||||
|
assert.property(result, 'walletInfo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut, 'instanceWalletWithoutInitialization').rejects(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.instanceWallet()
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an instance of BchWallet using web2 infra', async () => {
|
||||||
|
// Create a mock wallet.
|
||||||
|
const mockWallet = new BchWallet()
|
||||||
|
await mockWallet.walletInfoPromise
|
||||||
|
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||||
|
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.WALLET_FILE = testWalletFile
|
||||||
|
const walletData = await uut.openWallet()
|
||||||
|
// console.log('walletData: ', walletData)
|
||||||
|
|
||||||
|
// Force desired code path
|
||||||
|
uut.config.useFullStackCash = true
|
||||||
|
const result = await uut.instanceWallet(walletData)
|
||||||
|
// console.log('result: ', result)
|
||||||
|
|
||||||
|
assert.property(result, 'walletInfoPromise')
|
||||||
|
assert.property(result, 'walletInfo')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#incrementNextAddress', () => {
|
||||||
|
it('should increment the nextAddress property', async () => {
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.WALLET_FILE = testWalletFile
|
||||||
|
// mock instance of minimal-slp-wallet
|
||||||
|
uut.bchWallet = new MockBchWallet()
|
||||||
|
const result = await uut.incrementNextAddress()
|
||||||
|
assert.equal(result, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
|
||||||
|
await uut.incrementNextAddress()
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getKeyPair', () => {
|
||||||
|
it('should return an object with a key pair', async () => {
|
||||||
|
// Ensure we open the test file, not the production wallet file.
|
||||||
|
uut.WALLET_FILE = testWalletFile
|
||||||
|
// mock instance of minimal-slp-wallet
|
||||||
|
uut.bchWallet = new MockBchWallet()
|
||||||
|
const result = await uut.getKeyPair()
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.property(result, 'cashAddress')
|
||||||
|
assert.property(result, 'wif')
|
||||||
|
assert.property(result, 'hdIndex')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox
|
||||||
|
.stub(uut, 'incrementNextAddress')
|
||||||
|
.rejects(new Error('test error'))
|
||||||
|
await uut.getKeyPair()
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#optimize', () => {
|
||||||
|
it('should call the wallet optimize function', async () => {
|
||||||
|
// mock instance of minimal-slp-wallet
|
||||||
|
uut.bchWallet = new MockBchWallet()
|
||||||
|
sandbox.stub(uut.bchWallet, 'optimize').resolves({ bchUtxoCnt: 10 })
|
||||||
|
const result = await uut.optimize()
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getBalance', () => {
|
||||||
|
it('should get the balance for the wallet', async () => {
|
||||||
|
// mock instance of minimal-slp-wallet
|
||||||
|
uut.bchWallet = new MockBchWallet()
|
||||||
|
// Mock dependencies and force desired code path
|
||||||
|
sandbox.stub(uut.bchWallet, 'getBalance').resolves(41012)
|
||||||
|
sandbox.stub(uut.bchWallet, 'listTokens').resolves([{
|
||||||
|
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
|
ticker: 'PSF',
|
||||||
|
name: 'Permissionless Software Foundation',
|
||||||
|
decimals: 8,
|
||||||
|
tokenType: 1,
|
||||||
|
url: 'psfoundation.cash',
|
||||||
|
qty: 2
|
||||||
|
}])
|
||||||
|
const result = await uut.getBalance()
|
||||||
|
// console.log('result: ', result)
|
||||||
|
// Assert the expected properties exist and have the expected values.
|
||||||
|
assert.equal(result.satBalance, 41012)
|
||||||
|
assert.equal(result.psfBalance, 2)
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteFile = (filepath) => {
|
||||||
|
try {
|
||||||
|
// Delete state if exist
|
||||||
|
fs.unlinkSync(filepath)
|
||||||
|
} catch (err) {
|
||||||
|
// console.error('Error trying to delete file: ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,9 +38,13 @@ describe('#config', () => {
|
|||||||
assert.equal(config.env, 'test')
|
assert.equal(config.env, 'test')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should return test environment config', async () => {
|
it('Should return prod environment config', async () => {
|
||||||
process.env.SVC_ENV = 'prod'
|
process.env.SVC_ENV = 'prod'
|
||||||
|
|
||||||
|
process.env.WALLET_INTERFACE = 'web2'
|
||||||
|
process.env.APISERVER = 'https://api.fullstack.cash/v5/'
|
||||||
|
|
||||||
|
await import('../../../config/env/common.js?foo=bar2')
|
||||||
const importedConfig3 = await import('../../../config/index.js?foo=bar2')
|
const importedConfig3 = await import('../../../config/index.js?foo=bar2')
|
||||||
const config = importedConfig3.default
|
const config = importedConfig3.default
|
||||||
// console.log('config: ', config)
|
// console.log('config: ', config)
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import BCHJS from "@psf/bch-js";
|
||||||
|
|
||||||
|
const mockWallet = {
|
||||||
|
mnemonic: 'course abstract aerobic deer try switch turtle diet fence affair butter top',
|
||||||
|
privateKey: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||||
|
publicKey: '0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc',
|
||||||
|
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||||
|
address: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||||
|
slpAddress: 'simpleledger:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acq9zm7d33',
|
||||||
|
legacyAddress: '1JQj1KcQL7GPKzc1D2PvdUSgw3MbDtrHzi',
|
||||||
|
hdPath: "m/44'/245'/0'/0/0",
|
||||||
|
nextAddress: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockBchWallet {
|
||||||
|
constructor() {
|
||||||
|
this.walletInfoPromise = true;
|
||||||
|
this.walletInfo = mockWallet;
|
||||||
|
this.initialize = async () => {}
|
||||||
|
this.bchjs = new BCHJS();
|
||||||
|
this.burnTokens = async () => {
|
||||||
|
return { success: true, txid: 'txid' };
|
||||||
|
};
|
||||||
|
this.sendTokens = async () => {
|
||||||
|
return 'fakeTxid';
|
||||||
|
};
|
||||||
|
this.getUtxos = async () => { };
|
||||||
|
this.getBalance = async () => { };
|
||||||
|
this.listTokens = async () => { };
|
||||||
|
this.getTxData = async () => {
|
||||||
|
return [{
|
||||||
|
tokenTicker: 'TROUT'
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
this.optimize = async () => { };
|
||||||
|
// Environment variable is used by wallet-balance.unit.js to force an error.
|
||||||
|
if (process.env.NO_UTXO) {
|
||||||
|
this.utxos = {};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.utxos = {
|
||||||
|
utxoStore: {
|
||||||
|
address: 'bitcoincash:qqetvdnlt0p8g27dr44cx7h057kpzly9xse9huc97z',
|
||||||
|
bchUtxos: [
|
||||||
|
{
|
||||||
|
height: 700685,
|
||||||
|
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||||
|
tx_pos: 0,
|
||||||
|
value: 1000,
|
||||||
|
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||||
|
vout: 0,
|
||||||
|
isValid: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
height: 700685,
|
||||||
|
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||||
|
tx_pos: 2,
|
||||||
|
value: 19406,
|
||||||
|
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||||
|
vout: 2,
|
||||||
|
isValid: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
nullUtxos: [],
|
||||||
|
slpUtxos: {
|
||||||
|
type1: {
|
||||||
|
mintBatons: [],
|
||||||
|
tokens: [
|
||||||
|
{
|
||||||
|
'height': 717331,
|
||||||
|
'tx_hash': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
|
||||||
|
'tx_pos': 1,
|
||||||
|
'value': 546,
|
||||||
|
'txid': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
|
||||||
|
'vout': 1,
|
||||||
|
'isSlp': true,
|
||||||
|
'type': 'token',
|
||||||
|
'qty': '10',
|
||||||
|
'tokenId': '600ee24d0f208aebc2bdd2c4ee1b9acb6d57343561442e8676b5bbea311d5a0f',
|
||||||
|
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||||
|
'ticker': 'FLIPS',
|
||||||
|
'name': 'FLIPS',
|
||||||
|
'documentUri': '',
|
||||||
|
'documentHash': '',
|
||||||
|
'decimals': 1,
|
||||||
|
'qtyStr': '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'height': 730597,
|
||||||
|
'tx_hash': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
|
||||||
|
'tx_pos': 1,
|
||||||
|
'value': 546,
|
||||||
|
'txid': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
|
||||||
|
'vout': 1,
|
||||||
|
'isSlp': true,
|
||||||
|
'type': 'token',
|
||||||
|
'qty': '34999991',
|
||||||
|
'tokenId': '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
|
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||||
|
'ticker': 'PSF',
|
||||||
|
'name': 'Permissionless Software Foundation',
|
||||||
|
'documentUri': 'psfoundation.cash',
|
||||||
|
'documentHash': '',
|
||||||
|
'decimals': 8,
|
||||||
|
'qtyStr': '0.34999991'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'height': 730597,
|
||||||
|
'tx_hash': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
|
||||||
|
'tx_pos': 2,
|
||||||
|
'value': 546,
|
||||||
|
'txid': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
|
||||||
|
'vout': 2,
|
||||||
|
'isSlp': true,
|
||||||
|
'type': 'token',
|
||||||
|
'qty': '18898',
|
||||||
|
'tokenId': 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||||
|
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||||
|
'ticker': 'TROUT',
|
||||||
|
'name': "Trout's test token",
|
||||||
|
'documentUri': 'troutsblog.com',
|
||||||
|
'documentHash': '',
|
||||||
|
'decimals': 2,
|
||||||
|
'qtyStr': '188.98'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
nft: {
|
||||||
|
tokens: []
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
tokens: [],
|
||||||
|
mintBatons: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MockBchWallet };
|
||||||
|
|
||||||
|
export { mockWallet };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
MockBchWallet,
|
||||||
|
mockWallet
|
||||||
|
};
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mnemonic": "course abstract aerobic deer try switch turtle diet fence affair butter top",
|
||||||
|
"privateKey": "L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ",
|
||||||
|
"publicKey": "0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc",
|
||||||
|
"cashAddress": "bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00",
|
||||||
|
"address": "bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00",
|
||||||
|
"slpAddress": "simpleledger:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acq9zm7d33",
|
||||||
|
"legacyAddress": "1JQj1KcQL7GPKzc1D2PvdUSgw3MbDtrHzi",
|
||||||
|
"hdPath": "m/44'/245'/0'/0/0",
|
||||||
|
"nextAddress": 1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user