Esteban 3 months ago
parent
commit
b8de945711
  1. 14
      .eslintrc.js
  2. BIN
      .github/banner.png
  3. BIN
      .github/developer.png
  4. BIN
      .github/theme-1.png
  5. 2
      .gitignore
  6. 21
      LICENSE
  7. 3
      code/README.md
  8. 264
      code/index.js
  9. 3
      code/serve.js
  10. 55
      code/utilities/convert.js
  11. 69
      docs/001-qrcode-examples.md
  12. 3
      docs/003-setup-nano-node.md
  13. 12
      docs/README.md
  14. 39
      package.json
  15. 135
      test/index.js

14
.eslintrc.js

@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: [
'airbnb-base',
],
parserOptions: {
ecmaVersion: 12,
},
rules: {},
};

BIN
.github/banner.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

BIN
.github/developer.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

BIN
.github/theme-1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

2
.gitignore vendored

@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
/node_modules
package-lock.json

21
LICENSE

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Fresh Web Designs, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
code/README.md

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
# NANO BLOCKCHAIN ADAPTER
Work in progress.

264
code/index.js

@ -1,264 +0,0 @@ @@ -1,264 +0,0 @@
const Axios = require('axios');
const Moment = require('moment');
const Server = require('@fwd/api');
const Convert = require('./utilities/convert');
const NanoClient = require("@dev-ptera/nano-node-rpc").NanoClient;
const Performance = require('perf_hooks').performance;
const Nano = {
url: "http://[::1]:7076",
NanoNode() {
return new NanoClient({ url: this.url })
},
serve: (port, config) => {
Server.limits = config.limits || false;
Server.serve([
{ path: '/hash/:hash', action: async (req) => await Nano.rpc.hash(req.params.hash) },
{ path: '/:address/pending', action: async (req) => await Nano.rpc.pending(req.params.address) },
{ path: '/:address/balance', action: async (req) => await Nano.rpc.balance(req.params.address) },
{ path: '/:address/account', action: async (req) => await Nano.rpc.account(req.params.address) },
{ path: '/:address/history', action: async (req) => await Nano.rpc.history(req.params.address, req.query.count) },
{ path: '/:address/history/:amount', action: async (req) => await Nano.payment(req.params.address, req.params.amount) },
{ path: '/', action: async (req) => "It worked!" }
]);
Server.start(port || 8080, null, config);
},
/**
* Big Number Conversion API
**/
Convert,
toRaw: Convert.toRaw,
fromRaw: Convert.fromRaw,
/**
* Live Price (CoinMarketCap)
**/
async price(currency, config) {
let data;
currency = currency || 'USD';
const symbol = config && config.symbol ? config.symbol.toUpperCase() : 'NANO';
if (typeof config === 'object' && (config.apiKey || config.key)) {
const headers = { headers: { 'X-CMC_PRO_API_KEY': config.apiKey || config.key } };
let price = await server.http.get(`https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=${symbol}${currency !== 'USD' ? `&convert=${currency}` : ''}`, headers);
data = {
symbol: symbol.toUpperCase(),
currency: currency.toUpperCase(),
price: price.data.data[symbol].quote[currency].price,
timestamp: price.data.status.timestamp,
};
} else {
let price = await Axios.get(`https://nano.to/price?symbol=${symbol}&currency=${currency}`);
data = price.data;
}
return config && config.timestamp ? data : data.price;
},
async username(username) {
var username = await server.http.get(`${nano_api_uri}/${username}/username`)
return username.data
},
async known(hash) {
var known = await server.http.get(`${nano_api_uri}/known`)
return known.data
},
async hash(hash) {
var hash = await server.http.get(`${nano_api_uri}/hash/${hash}`)
return hash.data
},
async account(address) {
var account = await server.http.get(`${nano_api_uri}/${address}/account`)
return account.data
},
async pending(address, count) {
var pending = await server.http.get(`${nano_api_uri}/${address}/pending?count=${count || 50}`)
return pending.data
},
async history(address, count) {
var history = await server.http.get(`${nano_api_uri}/${address}/history?count=${count || 50}`)
return history.data
},
async balance(address) {
var data = await server.http.get(`${nano_api_uri}/balance`)
},
async payment(address, amount) {
if (!address) return new Error("First parameter, NANO address is missing.")
if (!amount) return new Error("Second parameter, NANO amount is missing.")
var history = await Nano.history(address)
history = history.find(a => a.amount == amount || a.amount == `${amount}0`) || false
if (history && history.hash) {
return history
}
var pending = await Nano.pending(address)
pending = pending.find(a => a.amount == amount || a.amount == `${amount}0`) || false
if (pending && pending.hash) {
return pending
}
return false
},
rpc: {
hash(hash) {
return new Promise(async (resolve, reject) => {
this.NanoNode().block(hash)
.then(block => resolve(block))
.catch(e => {
console.log(e)
resolve({ error: e.message })
});
})
},
account(address, config) {
return new Promise(async (resolve, reject) => {
var _config = { "json_block": true, "account": address }
if (config) Object.keys(config).map(a => _config[a] = config[a])
this.NanoNode()._send('account_info', _config)
.then(account => resolve(account))
.catch(e => resolve({ error: e.message }));
})
},
pending(address, count, config) {
return new Promise(async (resolve, reject) => {
var _config = { "json_block": true, "account": address, "count": count || 50, "source": true }
if (config) Object.keys(config).map(a => _config[a] = config[a])
this.NanoNode()._send('pending', _config)
.then(pending => {
var response = []
Object.keys(pending.blocks).map(hash => {
response.push({
hash: hash,
amount: pending.blocks[hash].amount,
source: pending.blocks[hash].source,
})
})
response = response.map(a => {
a.amount_raw = a.amount
a.amount = Nano.fromRaw(a.amount)
a.hash_url = `https://nano.to/hash/${a.hash}`
return a
})
return resolve(response)
})
.catch(e => resolve({ error: e.message }));
})
},
history(address, count, config) {
return new Promise(async (resolve, reject) => {
var _config = { "json_block": true, "account": address, "count": count || 50 }
if (config) Object.keys(config).map(a => _config[a] = config[a])
this.NanoNode()._send('account_history', _config)
.then(account => {
account.history = account.history.map(a => {
a.amount_raw = a.amount
a.amount = a.amount ? Nano.fromRaw(a.amount) : a.amount
a.timestamp = Moment.unix(a.timestamp || a.local_timestamp)
a.hash_url = `https://nano.to/hash/${a.hash}`
a.account_url = `https://nano.to/${a.account}/account`
delete a.local_timestamp
return a
})
resolve(account.history)
})
.catch(e => resolve({ error: e.message }));
})
},
balance(address) {
return new Promise(async (resolve, reject) => {
this.NanoNode().account_balance(address)
.then(balance => resolve(balance))
.catch(e => resolve({ error: e.message }));
})
},
send(config) {
return new Promise(async (resolve) => {
try {
config.source = config.source || config.account || config.address
config.recipient = config.recipient || config.to
config.seed = config.seed || config.password || config.secret
config.amount = config.amount
// fetch
if (!config.source) return resolve({ error: `Error: Config source wallet required.` })
if (!config.seed) return resolve({ error: `Error: Config wallet seed required.` })
if (!config.recipient) return resolve({ error: `Error: Config recipient required.` })
if (!config.amount) return resolve({ error: `Error: Config amount required.` })
var start = Performance.now();
var balance = await Nano.account(config.source)
if (config.amount == "max" || config.amount == "all") config.amount = _balance.nano
if (String(config.amount).includes('$')) {
config.amount = parseFloat(config.amount.replace('$', '')) / balance.usd
}
var new_balance = parseFloat(balance.nano) - parseFloat(config.amount)
new_balance = Nano.toRaw(new_balance)
if (parseFloat(balance.usd) < parseFloat(config.amount)) {
return resolve({ error: `Error: Wallet does not have enough money. ${balance.nano} NANO, $${balance.usd}` })
}
var block = await client._send('block_create', {
"json_block": true,
"type": "state",
"previous": balance.frontier,
"account": config.source,
"representative": balance.representative,
"balance": new_balance,
"link": config.recipient,
"key": config.seed
})
// TOOD subtype flag
var hash = await client._send('process', { "json_block": "true", "block": block.block })
var end = Performance.now();
resolve({
duration: (((end - start) / 1000) / 60) + ' minutes',
url: `https://nano.to/hash/${hash.hash}`,
hash, block
})
} catch (e) { resolve({ error: e.message }) }
})
},
},
}
module.exports = Nano;

3
code/serve.js

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
const Nano = require('./index.js');
Nano.serve()

55
code/utilities/convert.js

@ -1,55 +0,0 @@ @@ -1,55 +0,0 @@
const Big = require('big.js');
// Configure Big to never show exponential notation.
Big.NE = -31;
Big.PE = 39;
// const MEGA_IN_RAW = new big('0.000000000000000000000000000001'); // old
// const RAW_MAX_AMOUNT = new big('340282366920938463463374607431768211455'); // important number
const RAW_IN_MEGA = new Big('1000000000000000000000000000000');
const MEGA_MIN_AMOUNT = new Big('1').div(RAW_IN_MEGA);
const RAW_MAX_AMOUNT = new Big('133248298000000000000000000000000000000'); // max NANO supply
const MEGA_MAX_AMOUNT = new Big(RAW_MAX_AMOUNT).div(RAW_IN_MEGA);
module.exports = {
fromRaw(raw) {
if (raw === undefined) return new Error('First parameter, raw amount is missing.');
if (!parseInt(raw)) return new Error('First parameter, raw amount must be a whole number.');
let rawBig;
try {
rawBig = new Big(raw);
if (rawBig.lt(0)) return new Error('First parameter, raw amount must not be negative.');
if (rawBig.gt(RAW_MAX_AMOUNT)) return new Error('First parameter, raw amount is too large.');
return rawBig.div(RAW_IN_MEGA).toString();
} catch (error) {
throw new Error('The raw amount is invalid.');
}
},
toRaw(mega) {
if (mega === undefined) return new Error('First parameter, NANO amount is missing.');
if (!parseFloat(mega)) return new Error('First parameter, NANO amount must be a number.');
mega = parseFloat(mega);
let megaBig = Big;
try {
megaBig = new Big(mega);
if (megaBig.lt(0)) return new Error('First parameter, NANO amount must not be negative.');
if (megaBig.lt(MEGA_MIN_AMOUNT)) return new Error('First parameter, NANO amount is too small.');
if (megaBig.gt(MEGA_MAX_AMOUNT)) return new Error('First parameter, NANO amount is too large.');
return megaBig.times(RAW_IN_MEGA).toString();
} catch (error) {
return new Error('The NANO amount is invalid.');
}
},
};

69
docs/001-qrcode-examples.md

@ -1,69 +0,0 @@ @@ -1,69 +0,0 @@
# QR Code API
**Note: This API endpoint is for Nano.to Usernames only. You must lease a [NANO Username](https://nano.to) before using these endpoints.**
**Basic Usage**
Use this URL anywhere an HTML Image src goes.
```
https://nano.to/Moon/QrCode
```
Pass more options with URL params.
```
https://nano.to/Moon/QrCode?image=200&amout=50
```
**HTML Link & Image Example**
```html
<a href="https://nano.to/Moon">
<img style="max-width: 100px" src="https://nano.to/Moon/QrCode?image=100&amout=50"/>
</a>
```
<img style="max-width: 100px" src="https://nano.to/Moon/QrCode?image=100&amout=50"/>
**Natrium Deep Link & Image Example**
```
<a href="nano:nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o">
<img style="max-width: 100px" src="https://nano.to/Moon/QrCode?image=300&amout=100"/>
</a>
```
**Autofill Amount**
```html
<img style="max-width: 100px" src="https://nano.to/Moon/qrcode?amount=0.3925&width=400"/>
```
### Markdown Examples
```markdown
![NANO Tip](https://nano.to/Moon/qrcode)
```
![Tip NANO](https://nano.to/Moon/QrCode?image=250&amout=50&icon=2)
### Customize Icons
```
https://nano.to/Moon/QrCode?image=250&amout=50&icon=3
```
```
<img style="max-width: 100px" src="https://nano.to/Moon/QrCode?image=250&amout=50&icon=3"/>
```
<img style="max-width: 100px" src="https://nano.to/Moon/QrCode?image=250&amout=50&icon=3"/>

3
docs/003-setup-nano-node.md

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
## Coming Soon
This page is under construction.

12
docs/README.md

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
## Built with Nano.to
This page is under construction.
**Bounty: 1 NANO per PR**
Create a Pull Request with the following added to this README.md.
- Title
- Screenshot (Host in .github folder)
- Brief Description
- Link

39
package.json

@ -1,39 +0,0 @@ @@ -1,39 +0,0 @@
{
"main": "code/index.js",
"name": "@fwd/nano",
"author": {
"name": "Fresh Web Designs, LLC",
"email": "hello@fwd.dev"
},
"bugs": {
"url": "https://github.com/formsend/nano/issues"
},
"bundleDependencies": false,
"dependencies": {
"@dev-ptera/nano-node-rpc": "^2.0.2",
"@fwd/api": "github:fwd/api",
"axios": "^0.22.0",
"big.js": "^6.1.1",
"moment": "^2.29.1",
"nano-node-rpc": "^0.3.1",
"nanocurrency": "^2.5.0",
"qrcode": "^1.4.4"
},
"deprecated": false,
"description": "A super simple NANO crypto currency blockchain API.",
"homepage": "https://github.com/formsend/nano",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/formsend/nano.git"
},
"version": "0.0.1",
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2"
},
"scripts": {
"test": "node test/index.js"
}
}

135
test/index.js

@ -1,135 +0,0 @@ @@ -1,135 +0,0 @@
const axios = require("axios");
const server = require('@fwd/server')
var tests = [
/*
* NANO
*/
{
name: 'NANO.TO - Home',
url: 'https://nano.to/',
check: (content) => typeof content !== undefined,
},
{
name: 'NANO.TO - Search',
url: 'https://nano.to/search/esteban',
check: (content) => content[0].text == "esteban",
},
{
name: 'NANO.TO - Search',
url: 'https://nano.to/search/2983923472398473',
check: (content) => content[0].label == "available",
},
{
name: 'NANO.TO - Price',
url: 'https://nano.to/price',
check: (content) => typeof content.price !== undefined,
},
{
name: 'NANO.TO - Username',
url: 'https://nano.to/name/moon',
check: (content) => typeof content.expires !== undefined,
},
{
name: 'NANO.TO - Account',
url: 'https://nano.to/pending/moon',
check: (content) => (typeof content == "array" || typeof content == "object"),
},
{
name: 'NANO.TO - History',
url: 'https://nano.to/history/moon',
check: (content) => (typeof content == "array" || typeof content == "object"),
},
{
name: 'NANO.TO - Hash',
url: 'https://nano.to/hash/A341FBD3942B411D98BAC16241E5BC149DBE0D54D9BB23A873BC2A2C2B92B113',
check: (content) => typeof content.account_url !== undefined,
},
{
name: 'NANO.TO - Payment',
url: 'https://nano.to/payment/nano_37y6iq8m1zx9inwkkcgqh34kqsihzpjfwgp9jir8xpb9jrcwhkmoxpo61f4o/0.02143',
check: (content) => typeof content.message !== undefined,
},
{
name: 'NANO.TO - Qr Code',
url: 'https://nano.to/qrcode/Moon',
check: (content) => typeof content !== undefined,
},
{
name: 'NANO.TO - Qr Code w/ Amount',
url: 'https://nano.to/qrcode/Moon/0.02143',
check: (content) => typeof content !== undefined,
},
{
name: 'NANO.TO - Qr Code w/ Random Amount',
url: `https://nano.to/qrcode/Moon/${Math.random()}`,
check: (content) => typeof content !== undefined,
},
]
async function check(group) {
var _status = []
var index = 0
for (var website of ( group ? tests.filter(a => a.url.toLowerCase().includes(group.toLowerCase())) : tests )) {
try {
var startTime = new Date()
var url = website && website.url ? website.url : website
var results = await axios.get(`https://${url.replace('http://', '').replace('https://', '')}`, {
timeout: 10000
});
if (website.check) {
if (website.check(results.data)) {
var endTime = new Date()
var elapsed = endTime - startTime
_status.push(`${website && website.name ? website.name : website}: ${parseInt(elapsed) <= 2000 ? 'OK' : 'SLOW'} (${elapsed} ms)`)
} else {
_status.push(`${website && website.name ? website.name : website}: Error (${endTime - startTime} ms)`)
}
// return
} else {
var endTime = new Date()
var elapsed = endTime - startTime
if (results.data.length > 100) {
_status.push(`${website && website.name ? website.name : website}: ${elapsed <= 1000 ? 'OK' : 'SLOW'} (${endTime - startTime} ms)`)
} else {
_status.push(`${website && website.name ? website.name : website}: Error (${endTime - startTime} ms)`)
}
}
} catch (e) {
_status.push(`${website && website.name ? website.name : website}: Error`)
}
console.log(_status[index])
index++
}
return _status
}
if (require.main === module) {
// called directly
;(async () => await check())()
} else {
// required as a module
module.exports = check
}
Loading…
Cancel
Save