mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 09:02:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91a7106818 | ||
|
|
44e671dfbe | ||
|
|
979fb481de | ||
|
|
66253ed5a6 | ||
|
|
6ef5ac0f7c | ||
|
|
21c322d0df | ||
|
|
1b0e9ef0ff | ||
|
|
78776bee93 | ||
|
|
a6e3155f37 | ||
|
|
47f60bccde | ||
|
|
d0a3b27682 | ||
|
|
372d1b099d | ||
|
|
dbfaf9c4ab | ||
|
|
eaa08f74e7 | ||
|
|
9ac620ee64 | ||
|
|
ac0a4b0407 | ||
|
|
686012037f | ||
|
|
cfb7b9f779 | ||
|
|
06e5af4b5d | ||
|
|
249e93a3f4 | ||
|
|
761a2d226c | ||
|
|
5e5e636831 | ||
|
|
2adc2dfebb | ||
|
|
f9bc44ccb7 | ||
|
|
298b8f56ff | ||
|
|
139da8cf7b | ||
|
|
08e03c14d8 | ||
|
|
7da010e7af | ||
|
|
7b6eebe7c7 | ||
|
|
843f5eacef | ||
|
|
416e302ca5 | ||
|
|
1c66da70b6 | ||
|
|
59e92adf0b | ||
|
|
f369411ccb | ||
|
|
0504cf7b8d | ||
|
|
88ae47cade | ||
|
|
d144b13500 | ||
|
|
30d58bc337 | ||
|
|
651ae3f6ca | ||
|
|
04cd90f0f4 | ||
|
|
552ee31b79 | ||
|
|
affa9619bb | ||
|
|
39bff131af | ||
|
|
a84fecfe41 | ||
|
|
0736c9f2a1 | ||
|
|
aa898b31e7 | ||
|
|
29c3381cc5 | ||
|
|
f7ba6bb3ae | ||
|
|
62056e1ac4 | ||
|
|
a60e2fa2da | ||
|
|
37fe679800 | ||
|
|
4250d841c5 | ||
|
|
31079afc35 | ||
|
|
62b44d5058 | ||
|
|
150295cb43 | ||
|
|
5de2f71ea5 | ||
|
|
50a1d60229 | ||
|
|
f3e25a0338 | ||
|
|
640c582724 | ||
|
|
b472fa2673 | ||
|
|
ea83335fd2 | ||
|
|
3050819784 | ||
|
|
4ca9b1c00b | ||
|
|
bbc1a45a9e |
@@ -4,6 +4,7 @@ wallet-info.txt
|
||||
wallet.json
|
||||
integration-test-sweet.sh
|
||||
integration-test-ss.sh
|
||||
integration-test-bitfinex.sh
|
||||
|
||||
docs/
|
||||
coverage/
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
[](https://www.npmjs.com/package/@psf/bch-js)
|
||||
[](https://npmjs.org/package/@psf/bch-js)
|
||||
[](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
|
||||
[](https://github.com/feross/standard)
|
||||
[](https://github.com/feross/standard) [](https://gitter.im/Permissionless-Software-Foundation/bch-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
[bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) blockchains. It can be used for free, but requires an account on [FullStack.cash](https://fullstack.cash) for increased rate limits. Learn more from [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) about Full Stack Bitcoin Cash development.
|
||||
[bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) and eCash (XEC) blockchains. bch-js contains a toolbox of handy tools, and an easy API for talking with [bch-api REST API](https://github.com/Permissionless-Software-Foundation/bch-api). [FullStack.cash](https://fullstack.cash) offers paid cloud access to bch-api. You can run your own infrastructure by following documentation on [CashStack.info](https://cashstack.info).
|
||||
|
||||
### Quick Start Videos:
|
||||
|
||||
@@ -23,6 +23,7 @@ Here are two YouTube walk-through videos to help you get started:
|
||||
- [FullStack.cash](https://fullstack.cash) - cloud-based infrastructure for application developers.
|
||||
- [FullStack.cash Account](https://fullstack.cash/login) - Get your API key to unlock increased rate limits.
|
||||
- [Permissionless Software Foundation](https://psfoundation.cash) - The organization that maintains this library.
|
||||
- [CashStack.info](https://cashstack.info) - bch-js is part of the Cash Stack, a JavaScript framework for writing web 2 and web 3 business applications.
|
||||
|
||||
### Quick Notes
|
||||
|
||||
@@ -42,10 +43,10 @@ the [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) RES
|
||||
- ABC Mainnet REST API server: https://abc.fullstack.cash/v5/
|
||||
- Check server status: https://metrics.fullstack.cash
|
||||
|
||||
### API Key
|
||||
### API Key (JWT Token)
|
||||
|
||||
The [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API hosted by [FullStack.cash](https://fullstack.cash) uses JWT tokens to pay for increased
|
||||
rate limits when interacting with the back end server. See [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) if you want to understand the system-as-a-whole. The JWT token can be fed to bch-js _implicitly_ or _explicitly_.
|
||||
rate limits when interacting with the back end server. See [this article](https://cashstack.info) if you want to understand the system-as-a-whole. The JWT token can be fed to bch-js _implicitly_ or _explicitly_.
|
||||
|
||||
- Implicitly: bch-js will detect your JWT token if you set the `BCHJSTOKEN` environment variable.
|
||||
- Explicitly: You can directly feed in the JWT token with the `apiToken` property when instantiating the library. Here is an example:
|
||||
@@ -58,38 +59,25 @@ let bchjs = new BCHJS({
|
||||
})
|
||||
```
|
||||
|
||||
### Gatsby
|
||||
### Gatsby & Web Apps
|
||||
|
||||
bch-js is included in this [gatsby-ipfs-template](https://github.com/Permissionless-Software-Foundation/gatsby-ipfs-template) for building uncensorable web apps that can interact with the blockchain. When building a Gatsby (or other front-end app that uses Webpack), you'll need to add these lines to your `gatsby-node.js` file, as per [this issue](https://github.com/gatsbyjs/gatsby/issues/564):
|
||||
[minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is a minimal wallet 'engine' that incorporates bch-js. It's compiled with Browserify for front end apps.
|
||||
|
||||
```
|
||||
exports.onCreateWebpackConfig = ({ actions }) => {
|
||||
actions.setWebpackConfig({
|
||||
node: {
|
||||
fs: 'empty'
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
[gatsby-theme-bch-wallet](https://github.com/Permissionless-Software-Foundation/gatsby-theme-bch-wallet) is a Gatsby Theme and [bch-wallet-starter](https://github.com/Permissionless-Software-Foundation/bch-wallet-starter) is a Gatsby Starter for building web wallets using minimal-slp-wallet.
|
||||
|
||||
This is because the new IPFS class in bch-js uses the fs library for uploading files, which is not supported by Gatsby.
|
||||
|
||||
We also provide [minimal-slp-wallet-web](https://www.npmjs.com/package/minimal-slp-wallet-web) as a basic Bitcoin Cash wallet with SLP support, for front end projects. bch-js is encapsulated inside the instantiation of the library Class.
|
||||
[This gist](https://gist.github.com/christroutner/6cb9d1b615f3f9363af79723157bc434) shows how to include minimal-slp-wallet into a basic web page without using a framework.
|
||||
|
||||
## Features
|
||||
|
||||
- [ECMAScript 2017 standard JavaScript](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_-_ECMAScript_2017) used instead of TypeScript. Works
|
||||
natively with node.js v10 or higher.
|
||||
|
||||
- Full SLP tokens support: bch-js has full support for all SLP token functionality, including send, mint, and genesis transactions. It also fully support all aspects of [non-fugible tokans (NFTs)](https://www.youtube.com/watch?v=vvlpYUx6HRs).
|
||||
- Full SLP tokens support: bch-js has full support for all SLP token functionality, including send, mint, and genesis transactions. It also fully supports all aspects of [non-fugible tokans (NFTs)](https://www.youtube.com/watch?v=vvlpYUx6HRs).
|
||||
|
||||
- [Semantic Release](https://github.com/semantic-release/semantic-release) for
|
||||
continuous delivery using semantic versioning.
|
||||
|
||||
- [Greenkeeper](https://greenkeeper.io/) automatic dependency management for
|
||||
automatically maintaining the latest, most secure dependencies.
|
||||
|
||||
- [IPFS uploads](https://ipfs.io) of all files and dependencies, to backup
|
||||
- [IPFS](https://ipfs.io) and [Radicle](https://radicle.xyz) uploads of all files and dependencies, to backup
|
||||
dependencies in case they are ever inaccessible from GitHub or npm.
|
||||
|
||||
## Documentation:
|
||||
@@ -109,14 +97,26 @@ live in the same repository. To generate the documentation:
|
||||
Have questions? Need help? Join our community support
|
||||
[Telegram channel](https://t.me/bch_js_toolkit)
|
||||
|
||||
## IPFS Releases
|
||||
## Donate
|
||||
|
||||
This open source software is developed and maintained by the [Permissionless Software Foundation](https://psfoundation.cash). If this library provides value to you, please consider making a donation to support the PSF developers:
|
||||
|
||||
<div align="center">
|
||||
<img src="./img/donation-qr.png" />
|
||||
<p>bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr</p>
|
||||
</div>
|
||||
|
||||
|
||||
## IPFS & Radicle Releases
|
||||
|
||||
Copies of this repository are also published on [IPFS](https://ipfs.io).
|
||||
|
||||
- v4.5.4: QmWv3pxJy3MH8vU5nLUVyzqFxfNKXLQQEnz1rgStNuQijd
|
||||
- v6.2.10: `bafybeifsioj3ba77u2763nsyuzq53gtbdxsnqpoipvdl4immj6ytznjaoy`
|
||||
- (with dependencies, node v14.18.2 and npm v8.8.0): `bafybeihfendd4oj6uxvvecm7sluobwwhpb5wdcxhvhmx56e667nxdncd4a`
|
||||
|
||||
They are also posted to the Radicle:
|
||||
- v6.2.10: `rad:git:hnrkkroqnbfwj6uxpfjuhspoxnfm4i8e6oqwy`
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE.md)
|
||||
|
||||
test
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Generated
+865
-2091
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -12,7 +12,7 @@
|
||||
"test": "nyc mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
|
||||
"test:integration": "npm run test:integration:bchn",
|
||||
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
|
||||
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
|
||||
+30
-1
@@ -133,7 +133,7 @@ class Address {
|
||||
* @api Address.toEcashAddress() toEcashAddress()
|
||||
* @apiName toEcashAddress
|
||||
* @apiGroup Address
|
||||
* @apiDescription Convert legacy to cashAddress format
|
||||
* @apiDescription Convert legacy to eCash (XEC) format
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* // mainnet
|
||||
@@ -158,6 +158,35 @@ class Address {
|
||||
return ecashAddress.split(':')[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Address.toEtokenAddress() toEtokenAddress()
|
||||
* @apiName toEtokenAddress
|
||||
* @apiGroup Address
|
||||
* @apiDescription Convert legacy to eToken (XEC) format
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* // mainnet
|
||||
* bchjs.Address.toEtokenAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2')
|
||||
* // etoken:qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
|
||||
*
|
||||
* // mainnet no prefix
|
||||
* bchjs.Address.toEtokenAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2', false)
|
||||
* // qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
|
||||
*
|
||||
*/
|
||||
toEtokenAddress (address, prefix = true) {
|
||||
const decoded = this._decode(address)
|
||||
|
||||
const etokenAddress = cashaddr.encode(
|
||||
'etoken',
|
||||
decoded.type,
|
||||
decoded.hash
|
||||
)
|
||||
|
||||
if (prefix) return etokenAddress
|
||||
return etokenAddress.split(':')[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Address.ecashtoCashAddress() ecashtoCashAddress()
|
||||
* @apiName ecashtoCashAddress
|
||||
|
||||
+6
-1
@@ -7,6 +7,7 @@ const axios = require('axios')
|
||||
|
||||
// Local libraries.
|
||||
const Blockchain = require('./blockchain')
|
||||
// const Address = require('./address')
|
||||
|
||||
// let _this
|
||||
|
||||
@@ -32,7 +33,9 @@ class ElectrumX {
|
||||
}
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.blockchain = new Blockchain(config)
|
||||
// this.address = new Address(config)
|
||||
|
||||
// _this = this
|
||||
}
|
||||
@@ -44,7 +47,7 @@ class ElectrumX {
|
||||
* @apiDescription Return a list of uxtos for an address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxo = await bchjs.Electrumx.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
|
||||
* console.log(utxo);
|
||||
@@ -104,6 +107,8 @@ class ElectrumX {
|
||||
*/
|
||||
async utxo (address) {
|
||||
try {
|
||||
// console.log(`electrumx.js/utxo() restURL: ${this.restURL}`)
|
||||
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
const response = await axios.get(
|
||||
|
||||
+13
-13
@@ -30,19 +30,19 @@ class Price {
|
||||
}
|
||||
|
||||
// This endpoint is deprecated. Documentation removed.
|
||||
async current (currency = 'usd') {
|
||||
try {
|
||||
const response = await this.axios.get(
|
||||
`https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}`
|
||||
)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
return response.data.price
|
||||
} catch (err) {
|
||||
if (err.response && err.response.data) throw err.response.data
|
||||
else throw err
|
||||
}
|
||||
}
|
||||
// async current (currency = 'usd') {
|
||||
// try {
|
||||
// const response = await this.axios.get(
|
||||
// `https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}`
|
||||
// )
|
||||
// // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
//
|
||||
// return response.data.price
|
||||
// } catch (err) {
|
||||
// if (err.response && err.response.data) throw err.response.data
|
||||
// else throw err
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @api price.getUsd() getUsd()
|
||||
|
||||
+158
-1
@@ -41,7 +41,7 @@ class PsfSlpIndexer {
|
||||
this.rawTransaction = new RawTransaction(config)
|
||||
this.slpUtils = new SlpUtils(config)
|
||||
|
||||
// _this = this
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,6 +194,7 @@ class PsfSlpIndexer {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
async tokenStats (tokenId, withTxHistory = false) {
|
||||
@@ -288,6 +289,8 @@ class PsfSlpIndexer {
|
||||
*/
|
||||
async tx (txid) {
|
||||
try {
|
||||
// console.log('txid: ', txid)
|
||||
|
||||
// Handle single address.
|
||||
if (typeof txid === 'string') {
|
||||
const response = await axios.post(
|
||||
@@ -295,8 +298,11 @@ class PsfSlpIndexer {
|
||||
{ txid },
|
||||
this.axiosOptions
|
||||
)
|
||||
// console.log('response: ', response)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input txid must be a string.')
|
||||
} catch (error) {
|
||||
// console.log('error: ', error)
|
||||
@@ -316,6 +322,7 @@ class PsfSlpIndexer {
|
||||
|
||||
// Check if this txid belongs to a blacklisted token.
|
||||
const isInBlacklist = await this.checkBlacklist(txid)
|
||||
// console.log('isInBlacklist: ', isInBlacklist)
|
||||
|
||||
// Get the TX Details from the full node.
|
||||
const txDetails = await this.rawTransaction.getTxData(txid)
|
||||
@@ -368,6 +375,156 @@ class PsfSlpIndexer {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api PsfSlpIndexer.getTokenData() getTokenData()
|
||||
* @apiName Token Data
|
||||
* @apiGroup PSF SLP
|
||||
* @apiDescription Get mutable and immutable data if the token contains them.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let tokenData = await bchjs.PsfSlpIndexer.getTokenData('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
|
||||
* console.log(tokenData)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* {
|
||||
* genesisData: {
|
||||
* type: 1,
|
||||
* ticker: 'TROUT',
|
||||
* name: "Trout's test token",
|
||||
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
* documentUri: 'troutsblog.com',
|
||||
* documentHash: '',
|
||||
* decimals: 2,
|
||||
* mintBatonIsActive: true,
|
||||
* tokensInCirculationBN: '100098953386',
|
||||
* tokensInCirculationStr: '100098953386',
|
||||
* blockCreated: 622414,
|
||||
* totalBurned: '1046614',
|
||||
* totalMinted: '100100000000'
|
||||
* ]
|
||||
* },
|
||||
* immutableData :{
|
||||
* issuer:"FullStack.cash.",
|
||||
* website:"https://fullstack.cash/",
|
||||
* dateCreated:"2022-01-11"
|
||||
* },
|
||||
* mutableData :{
|
||||
* "tokenIcon":"https://gateway.ipfs.io/ipfs/bafybeiehitanirn5gmhqjg44xrmdtomn4n5lu5yjoepsvgpswk5mggaw6i/LP_logo-1.png",
|
||||
* "about":"Mutable data managed with npm package: https://www.npmjs.com/package/slp-mutable-data"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async getTokenData (tokenId, withTxHistory = false) {
|
||||
try {
|
||||
const url = `${this.restURL}psf/slp/token/data`
|
||||
// console.log(`url: ${url}`)
|
||||
|
||||
// Handle single address.
|
||||
if (typeof tokenId === 'string') {
|
||||
const response = await axios.post(
|
||||
// 'https://bchn.fullstack.cash/v5/psf/slp/token/data/',
|
||||
url,
|
||||
{ tokenId, withTxHistory },
|
||||
this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
throw new Error('Input tokenId must be a string.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api PsfSlpIndexer.getTokenData2() getTokenData2()
|
||||
* @apiName Token Data
|
||||
* @apiGroup PSF SLP
|
||||
* @apiDescription Get token icon and other media associated with a token.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let tokenData = await bchjs.PsfSlpIndexer.getTokenData2('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
|
||||
* console.log(tokenData)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* {
|
||||
* tokenStats: {
|
||||
* type: 1,
|
||||
* ticker: 'CTAIA006',
|
||||
* name: 'CTAIA006 - AI Art by Chris Troutner',
|
||||
* tokenId: '0e4543f820699294ab57e02ee2b1815a8bbc7b17a4333e4a138034e4b2324a61',
|
||||
* documentUri: 'ipfs://bafybeia5yuq7rg6jmwquako7t277cwrobcunz7cumqrv4wn6bgfvthemku',
|
||||
* documentHash: '78a00e9db312b8fff4e5c37cf592be83e6bab7f3bd5a54c9545bad5d4f3ee0f5',
|
||||
* decimals: 0,
|
||||
* mintBatonIsActive: false,
|
||||
* tokensInCirculationBN: '1',
|
||||
* tokensInCirculationStr: '1',
|
||||
* blockCreated: 757507,
|
||||
* totalBurned: '0',
|
||||
* totalMinted: '1'
|
||||
* },
|
||||
* mutableData: {
|
||||
* tokenIcon: 'https://bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm.ipfs.w3s.link/whale-night-sky-01.png',
|
||||
* fullSizedUrl: '',
|
||||
* about: 'This NFT was created using the PSF Token Studio at https://nft-creator.fullstack.cash',
|
||||
* userData: ''
|
||||
* },
|
||||
* immutableData: {
|
||||
* issuer: 'http://psfoundation.cash',
|
||||
* website: 'https://nft-creator.fullstack.cash',
|
||||
* dateCreated: '9/12/2022, 5:17:38 PM',
|
||||
* userData: '{\n' +
|
||||
* ' "title": "CTAIA006 - AI Art by Chris Troutner",\n' +
|
||||
* ' "about": "AI generated art. Generated from DALL-E at https://labs.openai.com",\n' +
|
||||
* ' "prompt": "whale swimming through a sky full of stars",\n' +
|
||||
* ' "algorithm": "DALL-E (stable diffusion)",\n' +
|
||||
* ' "set": "1-of-2"\n' +
|
||||
* '}'
|
||||
* },
|
||||
* tokenIcon: 'https://bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm.ipfs.w3s.link/whale-night-sky-01.png',
|
||||
* fullSizedUrl: '',
|
||||
* optimizedTokenIcon: 'https://p2wdb-gateway-678.fullstack.cash/ipfs/bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm/whale-night-sky-01.png',
|
||||
* optimizedFullSizedUrl: '',
|
||||
* iconRepoCompatible: false,
|
||||
* ps002Compatible: true
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async getTokenData2 (tokenId) {
|
||||
try {
|
||||
const url = `${this.restURL}psf/slp/token/data2`
|
||||
// console.log(`url: ${url}`)
|
||||
|
||||
// Handle single address.
|
||||
if (typeof tokenId === 'string') {
|
||||
const response = await axios.post(
|
||||
// 'https://bchn.fullstack.cash/v5/psf/slp/token/data/',
|
||||
url,
|
||||
{ tokenId },
|
||||
this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input tokenId must be a string.')
|
||||
} catch (error) {
|
||||
console.log('error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PsfSlpIndexer
|
||||
|
||||
+15
-6
@@ -381,6 +381,8 @@ class RawTransactions {
|
||||
// Appends the BCH address to the inputs of the transaction.
|
||||
async getTxData (txid) {
|
||||
try {
|
||||
// console.log('getTxData() txid: ', txid)
|
||||
|
||||
if (typeof txid !== 'string') {
|
||||
throw new Error(
|
||||
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
|
||||
@@ -391,17 +393,24 @@ class RawTransactions {
|
||||
const txDetails = await this.getRawTransaction(txid, true)
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
const inAddrs = await this._getInputAddrs(txDetails)
|
||||
// console.log(`inAddrs: ${JSON.stringify(inAddrs, null, 2)}`)
|
||||
try {
|
||||
const inAddrs = await this._getInputAddrs(txDetails)
|
||||
// console.log(`inAddrs: ${JSON.stringify(inAddrs, null, 2)}`)
|
||||
|
||||
// Add the input address to the transaction data.
|
||||
for (let i = 0; i < inAddrs.length; i++) {
|
||||
txDetails.vin[i].address = inAddrs[i].address
|
||||
txDetails.vin[i].value = inAddrs[i].value
|
||||
// Add the input address to the transaction data.
|
||||
for (let i = 0; i < inAddrs.length; i++) {
|
||||
txDetails.vin[i].address = inAddrs[i].address
|
||||
txDetails.vin[i].value = inAddrs[i].value
|
||||
}
|
||||
} catch (err) {
|
||||
// Coinbase transactions will throw an error. Just ignore them and
|
||||
// pass back the raw transaction data.
|
||||
/* exit quietly */
|
||||
}
|
||||
|
||||
return txDetails
|
||||
} catch (error) {
|
||||
// console.log('error: ', error)
|
||||
if (error.error) throw new Error(error.error)
|
||||
|
||||
// This case handles rate limit errors.
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ class Nft1 {
|
||||
// Loop through the tokenUtxos array and find the minting baton.
|
||||
let mintBatonUtxo
|
||||
for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
if (tokenUtxos[i].utxoType === 'minting-baton') {
|
||||
if (tokenUtxos[i].utxoType === 'minting-baton' || tokenUtxos[i].type === 'baton') {
|
||||
mintBatonUtxo = tokenUtxos[i]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,11 +375,14 @@ class TokenType1 {
|
||||
if (!Array.isArray(tokenUtxos)) {
|
||||
throw new Error('tokenUtxos must be an array.')
|
||||
}
|
||||
if (!mintQty) {
|
||||
throw new Error('mintQty must be a positive number.')
|
||||
}
|
||||
|
||||
// Loop through the tokenUtxos array and find the minting baton.
|
||||
let mintBatonUtxo
|
||||
for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
if (tokenUtxos[i].utxoType === 'minting-baton') {
|
||||
if (tokenUtxos[i].utxoType === 'minting-baton' || tokenUtxos[i].type === 'baton') {
|
||||
mintBatonUtxo = tokenUtxos[i]
|
||||
}
|
||||
}
|
||||
@@ -395,7 +398,7 @@ class TokenType1 {
|
||||
if (!tokenId) {
|
||||
throw new Error('tokenId property not found in mint-baton UTXO.')
|
||||
}
|
||||
if (!decimals) {
|
||||
if (!decimals && decimals !== 0) {
|
||||
throw new Error('decimals property not found in mint-baton UTXO.')
|
||||
}
|
||||
|
||||
@@ -404,6 +407,8 @@ class TokenType1 {
|
||||
baseQty = Math.floor(baseQty)
|
||||
baseQty = baseQty.toString()
|
||||
|
||||
if (isNaN(baseQty)) throw new Error('baseQty is non a number!')
|
||||
|
||||
// Signal that the baton should be passed or detroyed.
|
||||
let batonVout = 2
|
||||
if (destroyBaton) batonVout = null
|
||||
|
||||
@@ -41,6 +41,7 @@ class Transaction {
|
||||
* })()
|
||||
*/
|
||||
async get (txid) {
|
||||
// console.log('transaction.get() txid: ', txid)
|
||||
return await this.psfSlpIndexer.tx(txid)
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -281,16 +281,17 @@ class Util {
|
||||
|
||||
// Array of blocks.
|
||||
} else if (Array.isArray(address)) {
|
||||
const options = {
|
||||
const options1 = {
|
||||
method: 'POST',
|
||||
url: `${this.restURL}util/validateAddress`,
|
||||
data: {
|
||||
addresses: address
|
||||
},
|
||||
headers: {
|
||||
authorization: `Token ${this.apiToken}`
|
||||
}
|
||||
// headers: {
|
||||
// authorization: `Token ${this.apiToken}`
|
||||
// }
|
||||
}
|
||||
const options = Object.assign({}, options1, this.axiosOptions)
|
||||
const response = await axios(options)
|
||||
|
||||
return response.data
|
||||
|
||||
+100
-10
@@ -10,6 +10,7 @@ const Electrumx = require('./electrumx')
|
||||
const Slp = require('./slp/slp')
|
||||
const PsfSlpIndexer = require('./psf-slp-indexer')
|
||||
const BigNumber = require('bignumber.js')
|
||||
const Blockchain = require('./blockchain')
|
||||
|
||||
class UTXO {
|
||||
constructor (config = {}) {
|
||||
@@ -18,6 +19,7 @@ class UTXO {
|
||||
this.slp = new Slp(config)
|
||||
this.psfSlpIndexer = new PsfSlpIndexer(config)
|
||||
this.BigNumber = BigNumber
|
||||
this.blockchain = new Blockchain(config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +82,10 @@ class UTXO {
|
||||
}
|
||||
|
||||
// Ensure the address is a BCH address.
|
||||
const addr = this.slp.Address.toCashAddress(address)
|
||||
let addr = address
|
||||
if (!addr.includes('ecash')) {
|
||||
addr = this.slp.Address.toCashAddress(address)
|
||||
}
|
||||
|
||||
// Get the UTXOs associated with the address.
|
||||
const utxoData = await this.electrumx.utxo(addr)
|
||||
@@ -125,6 +130,7 @@ class UTXO {
|
||||
thisUtxo.qty = thisSlpUtxo.qty
|
||||
thisUtxo.tokenId = thisSlpUtxo.tokenId
|
||||
thisUtxo.address = thisSlpUtxo.address
|
||||
thisUtxo.tokenType = thisSlpUtxo.tokenType
|
||||
|
||||
break
|
||||
}
|
||||
@@ -151,16 +157,38 @@ class UTXO {
|
||||
|
||||
// Get token UTXOs
|
||||
let type1TokenUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'token'
|
||||
x => x.isSlp === true && x.type === 'token' && x.tokenType === 1
|
||||
)
|
||||
|
||||
// Hydrate the UTXOs with additional token data.
|
||||
type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
|
||||
|
||||
// Collect and hydrate any type1 baton UTXOs
|
||||
const bchUtxos = utxos.filter(x => x.isSlp === false)
|
||||
const type1BatonUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'baton'
|
||||
let type1BatonUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 1
|
||||
)
|
||||
type1BatonUtxos = await this.hydrateTokenData(type1BatonUtxos)
|
||||
|
||||
// Collect and hydrate NFT Group tokens
|
||||
let nftGroupTokenUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'token' && x.tokenType === 129
|
||||
)
|
||||
nftGroupTokenUtxos = await this.hydrateTokenData(nftGroupTokenUtxos)
|
||||
|
||||
// Collect and hydrate any Group baton UTXOs
|
||||
let groupBatonUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 129
|
||||
)
|
||||
groupBatonUtxos = await this.hydrateTokenData(groupBatonUtxos)
|
||||
|
||||
// Collect and hydrate NFT child tokens
|
||||
let nftChildTokenUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'token' && x.tokenType === 65
|
||||
)
|
||||
nftChildTokenUtxos = await this.hydrateTokenData(nftChildTokenUtxos)
|
||||
|
||||
// Isolate any UTXOs that are marked null by the SLP indexer.
|
||||
const nullUtxos = utxos.filter(x => x.isSlp === null)
|
||||
|
||||
const outObj = {
|
||||
@@ -171,7 +199,13 @@ class UTXO {
|
||||
tokens: type1TokenUtxos,
|
||||
mintBatons: type1BatonUtxos
|
||||
},
|
||||
nft: {} // Allocated for future support of NFT spec.
|
||||
group: {
|
||||
tokens: nftGroupTokenUtxos,
|
||||
mintBatons: groupBatonUtxos
|
||||
},
|
||||
nft: {
|
||||
tokens: nftChildTokenUtxos
|
||||
}
|
||||
},
|
||||
nullUtxos
|
||||
}
|
||||
@@ -225,11 +259,16 @@ class UTXO {
|
||||
thisUtxo.documentHash = genData[0].tokenData.documentHash
|
||||
thisUtxo.decimals = genData[0].tokenData.decimals
|
||||
|
||||
// Calculate the real token quantity
|
||||
const qty = new BigNumber(thisUtxo.qty).dividedBy(
|
||||
10 ** parseInt(thisUtxo.decimals)
|
||||
)
|
||||
thisUtxo.qtyStr = qty.toString()
|
||||
if (thisUtxo.type !== 'baton') {
|
||||
// Calculate the real token quantity
|
||||
const qty = new BigNumber(thisUtxo.qty).dividedBy(
|
||||
10 ** parseInt(thisUtxo.decimals)
|
||||
)
|
||||
thisUtxo.qtyStr = qty.toString()
|
||||
|
||||
// tokenQty is property expected by SLP.tokentype1.js library
|
||||
thisUtxo.tokenQty = thisUtxo.qtyStr
|
||||
}
|
||||
}
|
||||
|
||||
return utxoAry
|
||||
@@ -302,6 +341,57 @@ class UTXO {
|
||||
|
||||
return utxos[largestIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.isValid() isValid()
|
||||
* @apiName isValid
|
||||
* @apiGroup UTXO
|
||||
* @apiDescription Validate that UTXO exists and is still spendable.
|
||||
*
|
||||
* Given a UTXO, this method will return true if the UTXO is still in the
|
||||
* mempool and still valid for spending. It will return false if the UTXO
|
||||
* has been spent.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const utxos = await bchjs.Utxo.get('bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z');
|
||||
* const isValid = bchjs.Utxo.isValid(utxos.bchUtxos[0])
|
||||
* console.log(isValid);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* true
|
||||
*/
|
||||
async isValid (utxo) {
|
||||
try {
|
||||
// console.log('utxo: ', utxo)
|
||||
|
||||
// Convert different properties from different indexers
|
||||
const txid = utxo.txid || utxo.tx_hash
|
||||
const vout = utxo.vout | utxo.tx_pos
|
||||
|
||||
// Query the full node
|
||||
const txOut = await this.blockchain.getTxOut(txid, vout, true)
|
||||
// console.log('txOut: ', txOut)
|
||||
|
||||
// Simplify results to either true or false.
|
||||
let isValid = null
|
||||
if (txOut === null) {
|
||||
isValid = false
|
||||
} else {
|
||||
isValid = true
|
||||
}
|
||||
|
||||
return isValid
|
||||
} catch (err) {
|
||||
console.log('Error in Utxo.isValid()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UTXO
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Integration tests for the psf-slp-indexer.js library, specific to the eCash
|
||||
blockchain.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
|
||||
// Local libraries
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
let bchjs
|
||||
|
||||
describe('#psf-slp-indexer', () => {
|
||||
beforeEach(async () => {
|
||||
// Introduce a delay so that the BVT doesn't trip the rate limits.
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
|
||||
describe('#balance', () => {
|
||||
it('should get token balance for an ecash address', async () => {
|
||||
const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09'
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.balance(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result.balance, 'utxos')
|
||||
assert.property(result.balance, 'txs')
|
||||
assert.property(result.balance, 'balances')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Promise-based sleep function
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
Integration tests for the utxo.js library.
|
||||
*/
|
||||
|
||||
// const assert = require('chai').assert
|
||||
const assert = require('chai').assert
|
||||
|
||||
// const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS()
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#UTXO', () => {
|
||||
beforeEach(async () => {
|
||||
@@ -14,25 +14,23 @@ describe('#UTXO', () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
|
||||
})
|
||||
|
||||
/*
|
||||
describe('#get', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09'
|
||||
// const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'bchUtxos')
|
||||
assert.property(result[0], 'nullUtxos')
|
||||
assert.property(result[0], 'slpUtxos')
|
||||
assert.isArray(result[0].bchUtxos)
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
// assert.isArray(result)
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'bchUtxos')
|
||||
assert.property(result, 'nullUtxos')
|
||||
assert.property(result, 'slpUtxos')
|
||||
assert.isArray(result.bchUtxos)
|
||||
assert.isArray(result.nullUtxos)
|
||||
})
|
||||
})
|
||||
*/
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Integration tests for the psf-slp-indexer.js library
|
||||
Integration tests for the psf-slp-indexer.js library, specific to the BCH
|
||||
blockchain.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
|
||||
// Local libraries
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
let bchjs
|
||||
|
||||
@@ -124,6 +127,55 @@ describe('#psf-slp-indexer', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should get token data', async () => {
|
||||
const tokenId =
|
||||
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
|
||||
|
||||
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'genesisData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.property(result, 'mutableData')
|
||||
|
||||
assert.isObject(result.genesisData)
|
||||
assert.isString(result.immutableData)
|
||||
assert.isString(result.mutableData)
|
||||
})
|
||||
|
||||
it('should get token data with a transaction history', async () => {
|
||||
const tokenId = '43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d'
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId, true)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isArray(result.genesisData.txs)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
it('should get token data', async () => {
|
||||
const tokenId =
|
||||
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
|
||||
|
||||
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData2(tokenId)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'tokenStats')
|
||||
assert.property(result, 'mutableData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.property(result, 'tokenIcon')
|
||||
assert.property(result, 'fullSizedUrl')
|
||||
assert.property(result, 'optimizedTokenIcon')
|
||||
assert.property(result, 'optimizedFullSizedUrl')
|
||||
assert.property(result, 'iconRepoCompatible')
|
||||
assert.property(result, 'ps002Compatible')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Promise-based sleep function
|
||||
|
||||
@@ -24,5 +24,14 @@ describe('#Transaction', () => {
|
||||
assert.property(result.txData, 'vout')
|
||||
assert.equal(result.txData.isValidSlp, false)
|
||||
})
|
||||
|
||||
it('should handle a coinbase transaction', async () => {
|
||||
const txid = 'cca1d2dd3a533d2f501448dec03face2cb2814afd59a533a611e9a2909f2302b'
|
||||
|
||||
const details = await bchjs.Transaction.get(txid)
|
||||
// console.log(`details: ${JSON.stringify(details, null, 2)}`)
|
||||
|
||||
assert.property(details.txData, 'txid')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,10 +83,8 @@ describe('#UTXO', () => {
|
||||
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
|
||||
})
|
||||
|
||||
// TODO: NFTs are currently not identified as different than normal BCH UTXOs.
|
||||
// The psf-slp-indexer needs to be updated to fix this issue.
|
||||
it('should handle minting batons', async () => {
|
||||
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
|
||||
it('should handle Type1 minting batons', async () => {
|
||||
const addr = 'simpleledger:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpv4awvv7m7'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -105,13 +103,82 @@ describe('#UTXO', () => {
|
||||
assert.equal(result.slpUtxos.type1.tokens.length, 0)
|
||||
})
|
||||
|
||||
it('should handle Group NFTs', async () => {
|
||||
const addr = 'bitcoincash:qrnghwrfgccf3s5e9wnglzxegcnhje9rkcwv2eka33'
|
||||
it('should filter Group tokens and mint batons', async () => {
|
||||
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.nullUtxos.length, 0)
|
||||
assert.isAbove(result.slpUtxos.group.tokens.length, 0)
|
||||
assert.isAbove(result.slpUtxos.group.mintBatons.length, 0)
|
||||
})
|
||||
|
||||
it('should filter NFTs', async () => {
|
||||
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(result.slpUtxos.nft.tokens.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isValid', () => {
|
||||
it('should return true for valid UTXO with fullnode properties', async () => {
|
||||
const utxo = {
|
||||
txid: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
|
||||
vout: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return true for valid UTXO with fulcrum properties', async () => {
|
||||
const utxo = {
|
||||
tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
|
||||
tx_pos: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return true for valid UTXO with fullnode properties', async () => {
|
||||
const utxo = {
|
||||
txid: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
|
||||
vout: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should return true for valid UTXO with fulcrum properties', async () => {
|
||||
const utxo = {
|
||||
tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
|
||||
tx_pos: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should process output of Utxo.get()', async () => {
|
||||
const utxo = await bchjs.Utxo.get('bitcoincash:qr4yscpw9jgq8ltajfeknpj32kamkf9knujffcdhyq')
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo.bchUtxos[0])
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,14 +7,14 @@ describe('#price', () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
|
||||
})
|
||||
|
||||
describe('#current', () => {
|
||||
describe('#single currency', () => {
|
||||
it('should get current price for single currency', async () => {
|
||||
const result = await bchjs.Price.current('usd')
|
||||
assert.notEqual(0, result)
|
||||
})
|
||||
})
|
||||
})
|
||||
// describe('#current', () => {
|
||||
// describe('#single currency', () => {
|
||||
// it('should get current price for single currency', async () => {
|
||||
// const result = await bchjs.Price.current('usd')
|
||||
// assert.notEqual(0, result)
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#getUsd', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
@@ -33,6 +33,7 @@ describe('#price', () => {
|
||||
assert.isNumber(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBchUsd', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
const result = await bchjs.Price.getBchUsd()
|
||||
|
||||
@@ -935,4 +935,44 @@ describe('#address.js', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#toEcashAddress', () => {
|
||||
it('should convert a BCH address to an eCash address', () => {
|
||||
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
|
||||
|
||||
const result = bchjs.Address.toEcashAddress(cashAddr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'ecash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s8f6wu02p')
|
||||
})
|
||||
|
||||
it('should convert a BCH address to an eCash address without a prefix', () => {
|
||||
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
|
||||
|
||||
const result = bchjs.Address.toEcashAddress(cashAddr, false)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'qr24z7q6s26d9wr078lr2pxsmxg22cyn9s8f6wu02p')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#toEtokenAddress', () => {
|
||||
it('should convert a BCH address to an eToken address', () => {
|
||||
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
|
||||
|
||||
const result = bchjs.Address.toEtokenAddress(cashAddr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'etoken:qr24z7q6s26d9wr078lr2pxsmxg22cyn9sfhnv2gwk')
|
||||
})
|
||||
|
||||
it('should convert a BCH address to an eToken address without a prefix', () => {
|
||||
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
|
||||
|
||||
const result = bchjs.Address.toEtokenAddress(cashAddr, false)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'qr24z7q6s26d9wr078lr2pxsmxg22cyn9sfhnv2gwk')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -144,10 +144,71 @@ const tokenData01 = {
|
||||
qty: {}
|
||||
}
|
||||
|
||||
const tokenData = {
|
||||
genesisData: tokenStats.tokenData,
|
||||
immutableData: 'ipfs://bafybeidzfi3sictag4noy746gniyiwwcgio565xuzelmjfwxnga6qyiyai',
|
||||
mutableData: 'ipfs://bafybeie6t5uyupddc7azms737xg4hxrj7i5t5ov3lb5g2qeehaujj6ak64'
|
||||
}
|
||||
|
||||
const tokenMedia01 = {
|
||||
tokenStats: {
|
||||
type: 65,
|
||||
ticker: 'TEST02',
|
||||
name: 'How to Send BCH and Tokens',
|
||||
tokenId: 'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b',
|
||||
documentUri: 'ipfs://bafybeigjujbb55wqr5lns7z7vleg5cfp4yjwqp4swisxpgxl4xsp5tngai',
|
||||
documentHash: '845f803d8dd3c2b6be94a6cf3a445b54c36f2e01bd5c9ab4c23f37dbd531489e',
|
||||
decimals: 0,
|
||||
mintBatonIsActive: false,
|
||||
tokensInCirculationBN: '0',
|
||||
tokensInCirculationStr: '0',
|
||||
blockCreated: 740089,
|
||||
totalBurned: '1',
|
||||
totalMinted: '1',
|
||||
parentGroupId: 'c9c425f2c6352697c6665a53e035cbad8a44c4b1e36491a1838dc4655479aa09'
|
||||
},
|
||||
mutableData: {
|
||||
updated: '2022-05-14T15:27:44.429Z',
|
||||
tokenIcon: 'https://bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy.ipfs.dweb.link/send-bch-token-icon.png',
|
||||
tokenInfo: 'https://token.fullstack.cash/?tokenid=d9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b',
|
||||
description: 'This is an NFT representing a video. This is a test token.',
|
||||
issuer: 'Chris Troutner',
|
||||
forSale: false,
|
||||
display: true,
|
||||
currentOwner: {
|
||||
bchAddr: 'bitcoincash:qqy7jcrm3vmtqs96r878hy2kx90mn84f25ujqw9z5h',
|
||||
name: 'Chris Troutner',
|
||||
contactEmail: 'chris.troutner@gmail.com'
|
||||
},
|
||||
content: {
|
||||
youtube: 'https://youtu.be/WZRwkLPtkaI',
|
||||
rumble: 'https://rumble.com/v14n00h-how-to-send-bch-and-tokens.html',
|
||||
odysee: 'https://odysee.com/@trout:5/how-to-send-bch-tokens:0',
|
||||
lbry: 'lbry://@trout#5/how-to-send-bch-tokens#0',
|
||||
ipfs: 'bafybeigf3ky5i6fyxwk5bjmtsr6urqmlx4zq2lqathgguytey67iinh4be',
|
||||
filecoin: 'https://bafybeigf3ky5i6fyxwk5bjmtsr6urqmlx4zq2lqathgguytey67iinh4be.ipfs.dweb.link/send-bch-2022-02-20_08.22.45.mp4'
|
||||
}
|
||||
},
|
||||
immutableData: {
|
||||
creationDate: '2022-05-13T21:32:11.619Z',
|
||||
issuer: 'Chris Troutner',
|
||||
license: 'https://bafybeidnkhjfsbihp4gquwqrs6y35jfpcriafymceszwvkundjkwk546pi.ipfs.dweb.link/copyright.txt',
|
||||
name: 'How to Send BCH and Tokens'
|
||||
},
|
||||
tokenIcon: 'https://bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy.ipfs.dweb.link/send-bch-token-icon.png',
|
||||
fullSizedUrl: '',
|
||||
optimizedTokenIcon: 'https://p2wdb-gateway-678.fullstack.cash/ipfs/bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy/send-bch-token-icon.png',
|
||||
optimizedFullSizedUrl: '',
|
||||
iconRepoCompatible: false,
|
||||
ps002Compatible: true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
tokenStats,
|
||||
txData,
|
||||
balance,
|
||||
status,
|
||||
tokenData01
|
||||
tokenData01,
|
||||
tokenData,
|
||||
tokenMedia01
|
||||
}
|
||||
|
||||
+12
-12
@@ -19,18 +19,18 @@ describe('#price', () => {
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#current', () => {
|
||||
it('should get current price for single currency', async () => {
|
||||
sandbox
|
||||
.stub(bchjs.Price.axios, 'get')
|
||||
.resolves({ data: { price: 24905 } })
|
||||
|
||||
const result = await bchjs.Price.current('usd')
|
||||
// console.log(result)
|
||||
|
||||
assert.isNumber(result)
|
||||
})
|
||||
})
|
||||
// describe('#current', () => {
|
||||
// it('should get current price for single currency', async () => {
|
||||
// sandbox
|
||||
// .stub(bchjs.Price.axios, 'get')
|
||||
// .resolves({ data: { price: 24905 } })
|
||||
//
|
||||
// const result = await bchjs.Price.current('usd')
|
||||
// // console.log(result)
|
||||
//
|
||||
// assert.isNumber(result)
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#getUsd', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
|
||||
@@ -336,4 +336,103 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should GET token data', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenData })
|
||||
|
||||
const tokenId =
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
assert.property(result, 'genesisData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.property(result, 'mutableData')
|
||||
|
||||
assert.isObject(result.genesisData)
|
||||
assert.isString(result.immutableData)
|
||||
assert.isString(result.mutableData)
|
||||
|
||||
assert.property(result.genesisData, 'type')
|
||||
assert.property(result.genesisData, 'ticker')
|
||||
assert.property(result.genesisData, 'name')
|
||||
assert.property(result.genesisData, 'tokenId')
|
||||
assert.property(result.genesisData, 'documentUri')
|
||||
assert.property(result.genesisData, 'documentHash')
|
||||
assert.property(result.genesisData, 'decimals')
|
||||
assert.property(result.genesisData, 'mintBatonIsActive')
|
||||
assert.property(result.genesisData, 'tokensInCirculationBN')
|
||||
assert.property(result.genesisData, 'tokensInCirculationStr')
|
||||
assert.property(result.genesisData, 'blockCreated')
|
||||
assert.property(result.genesisData, 'totalBurned')
|
||||
assert.property(result.genesisData, 'totalMinted')
|
||||
})
|
||||
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const tokenId = 12345
|
||||
|
||||
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(err.message, 'Input tokenId must be a string.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').throws(new Error('test error'))
|
||||
|
||||
const tokenId =
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
|
||||
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle request error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
const testErr = new Error()
|
||||
testErr.response = { data: { status: 422 } }
|
||||
sandbox.stub(axios, 'post').throws(testErr)
|
||||
|
||||
const tokenId =
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
|
||||
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
it('should get token data', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenMedia01 })
|
||||
|
||||
const tokenId =
|
||||
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
|
||||
|
||||
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData2(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'tokenStats')
|
||||
assert.property(result, 'mutableData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.property(result, 'tokenIcon')
|
||||
assert.property(result, 'fullSizedUrl')
|
||||
assert.property(result, 'optimizedTokenIcon')
|
||||
assert.property(result, 'optimizedFullSizedUrl')
|
||||
assert.property(result, 'iconRepoCompatible')
|
||||
assert.property(result, 'ps002Compatible')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -174,4 +174,36 @@ describe('#utxo', () => {
|
||||
assert.equal(result.nullUtxos.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isValid', () => {
|
||||
it('should return false if getTxOut() returns null', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves(null)
|
||||
|
||||
const utxo = {
|
||||
tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
|
||||
tx_pos: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should return true if getTxOut() returns non-null output', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves({ a: 'b' })
|
||||
|
||||
const utxo = {
|
||||
tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
|
||||
tx_pos: 0
|
||||
}
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo)
|
||||
// console.log('result: ', result
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user