Money Button Token API Website Example
Article
Reference documentation can be found here:
https://docs.moneybutton.com/docs/api/api-overview.html
https://expressjs.com/
https://nodejs.org/en/docs/
https://ejs.co/
http://www.passportjs.org/
https://mongoosejs.com/docs/guide.html
https://github.com/winstonjs/winston
The finished project is hosted here: https://github.com/big-riz/mbtokens
You might want to use MongoDB Compass for hosting your local database. https://www.mongodb.com/try/download/compass
This is a tutorial for a NodeJS express website rendering pages with ejs. We will use passport for managing user logins.
Our database for storing users will be MongoDB using mongoose to connect.
Winston is for logging.
First, we need to initialize the project. Make sure Node is installed (check the reference documentation for install instructions). Open your project’s directory in the terminal and enter “npm init”. This will create your package.json file.
Now you can install the required dependencies.
The command is “npm install express express-session ejs mongoose passport passport-local-mongoose winston”
Let’s start by creating our project entry point:
index.js
Importing required dependencies
const fs = require('fs')
const express = require("express")
const mongoose = require("mongoose")
const passport = require("passport")
const bodyParser = require("body-parser")
const LocalStrategy = require("passport-local")
const passportLocalMongoose = require("passport-local-mongoose")
const User = require("./models/user")
const https = require('https')
const winston = require("winston")
Initializing the winston logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
],
})
//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}))
}
function logRequest(req, res, next) {
logger.log("info", `Request ${req.ip} ${req.method} ${req.path}`)
next()
}
Setting SSL credentials (https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl)
var credentials = {key: fs.readFileSync("./keys/private-key.pem"), cert: fs.readFileSync("./keys/public-cert.pem")}
Setting mongoose parameters and connecting to the database
mongoose.set('useNewUrlParser', true)
mongoose.set('useFindAndModify', false)
mongoose.set('useCreateIndex', true)
mongoose.set('useUnifiedTopology', true)
mongoose.connect("mongodb://localhost:27017/mbtokens")
Setting express middleware
var app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.set("view engine", "ejs")
app.use( express.static( "public" ) )
app.use(require("express-session")({
secret: "topsecret.pleasechangethis",
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(logRequest)
Setting passport stuff
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
Import required routes… and finally, create the server and listen on port 3001
var routes = require("./routes")
routes(app, passport)
var httpsServer = https.createServer(app);
httpsServer.listen(3001);
Before this will work, we need to create routes.js and /models/user.js.
Now we can create our second file:
routes.js
Importing required dependencies
var User = require("./models/user")
var crypto = require('crypto')
const https = require('https')
Small function to check whether user is authenticated
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect("/login");
}
Set your Money Button Oauth Client Id(https://www.moneybutton.com/settings/apps/create )(Instead of hard-coding this, you can use a .env file)
MB_CLIENT_OAUTH_ID = "xxxxxxxxx"
The routes
module.exports = function (app, passport) {
securestrings = {}
app.get("/", async function (req, res) {
res.render("index", {user: req.user})
})
// more routes will be added here...
}
Two more files, then we can test it.
/models/user.js
This is just the schema for our database object. (Passport will add in the username and password fields)
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const passportLocalMongoose = require('passport-local-mongoose')
const User = new Schema({
initTime: Date,
moneybuttonPaymail: String,
moneybuttonId: Number,
accessToken: String
})
User.plugin(passportLocalMongoose)
module.exports = mongoose.model('User', User)
/views/index.ejs
This is the rendering template for our index/home page. For now, it’s just a link to the login page (which we haven’t added yet)
<html>
<head>
</head>
<body>
<a href="/login">Login</a>
</body>
</html>
Congrats! You have added all of the files we will need to test that this thing can run. Hello world!
To test it, go back to your terminal and type in “node index.js”.
If that doesn’t give you an error, go to your web browser and type in “https://localhost:3001”.
You should be directed to your new index page with the “Login” link.
If you get an error, you might be missing your SSL keys. Make sure /keys/private-key.pem and /keys/public-cert.pem are both there in the keys folder. You will need to self-sign your certificate (https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl)
Let’s make that login page.
In routes.js, underneath your first app.get(), you can add a second app.get() like the one below:
routes.js
app.get("/login", function (req, res) {
randomsecurestring = String(Date.now())+"."+crypto.randomBytes(32).toString('hex')
securestrings[String(req.ip)+String(Date.now())] = randomsecurestring
res.render("login", {randomsecurestring: randomsecurestring, oauthid: MB_CLIENT_OAUTH_ID})
});
Note that this renders the “login” page, so we’ll need to add a login.ejs file to our views.
/views/login.ejs
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<a href="https://www.moneybutton.com/oauth/v1/authorize?response_type=code&client_id=<%= oauthid %>&redirect_uri=https://localhost:3001/moneybuttonredirect&scope=auth.user_identity:read users.profiles:read users.asset:write users.asset:read&state=<%= randomsecurestring %>">Login with Money Button </a>
</body>
</html>
This adds in the first step in our oauth process. When the user clicks that link, they will be redirected to Money Button’s website and asked to login.
Then, they will be redirected back to our website (https://localhost:3001/moneybuttonredirect) with an access code.
Now we need to make the route for /moneybuttonredirect.
routes.js
app.get("/moneybuttonredirect", async function (req, res) {
res.render("oauthredirect", {oauthid: MB_CLIENT_OAUTH_ID})
})
…and the render template for “oauthredirect”
/views/oauthredirect.ejs
<html>
<head>
<meta charset="UTF-8"/>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(function() {
const urlParams = new URLSearchParams(window.location.search);
const myParam = urlParams.get('code');
const secret = urlParams.get('state')
if (myParam){
$.post(
"https://www.moneybutton.com/oauth/v1/token",
{grant_type: "authorization_code", client_id: "<%= oauthid %>", code: myParam, redirect_uri: "https://localhost:3001/moneybuttonredirect"},
function (data) {
const access_token = data.access_token
var mb_id = null
console.log(data)
$.ajaxSetup({
headers: {
"authorization": "Bearer "+access_token
}
});
$.get("https://www.moneybutton.com/api/v1/auth/user_identity", function(data){
console.log(data)
mb_id = data.data.id
$.get("https://www.moneybutton.com/api/v1/users/"+mb_id.toString()+"/profile", function(data){
console.log(data)
document.getElementById("paymail").value = data.data.attributes["primary-paymail"]
document.getElementById("username").value = mb_id
document.getElementById("secret").value = secret
document.getElementById("accessToken").value = access_token
})
})
}
)
}
})
</script>
</head>
<body>
<form action="/registermoneybutton" method="post">
<input type="password" name="password" placeholder="password" class="form-control" id="inputPassword">
<input type="hidden" value="" name="paymail" id="paymail">
<input type="hidden" value="" name="username" id="username">
<input type="hidden" value="" name="secret" id="secret">
<input type="hidden" value="" name="accessToken" id="accessToken">
<button type="submit">Submit</button>
</form>
</body>
</html>
This page will receive the code from the oauth and use it in a request for the access token. Once it receives the access token, it will send another request for the moneybutton user’s id. Then it will send a third request for the primary paymail of the user. It stores the access token, the paymail, and the moneybutton id (as the username) as hidden values in the form. The final value in the form - the password - must be entered by the user. When the user clicks submit, the form data is sent via POST request to localhost:3001/registermoneybutton. Next, we will add the route for that URL.
routes.js
app.post("/registermoneybutton", async function(req, res) {
var password = req.body.password
if (securestrings[String(req.ip)+String(req.body.secret.split('.')[0])] == req.body.secret){
currentUser = await User.findOne({username: req.body.username}).exec()
if (currentUser != undefined){
currentUser.accessToken = req.body.accessToken
currentUser.moneybuttonPaymail = req.body.paymail
currentUser.save()
passport.authenticate("local")(
req, res, function () {
res.redirect("/?success=true")
}
)
}else{
User.register(new User({ username: req.body.username, moneybuttonPaymail: req.body.paymail, moneybuttonId: parseInt(req.body.username), accessToken: req.body.accessToken}),
password,
function (err, user) {
if (err) {
console.log(err);
return res.redirect("/?failure=true");
}
passport.authenticate("local")(
req, res, function () {
res.redirect("/?success=true")
}
);
}
);
}
}
})
First, we check if the secure string matches the one we generated. This is for security. We then check if the user is stored in the database. If it is, we update the access token and paymail of the user and .save(). Now we can authenticate the user with passport. (It just reads the req.body.username and req.body.password). If the user is not already in the database, we register a new user and save the access token etc. Then we can authenticate.
You can test this by restarting your server (ctrl+c in terminal to quit process, then “node index.js” to restart). Go to https://localhost:3001/login
and go through the process. If you end up with /?success=true in the URL bar, you’ve successfully registered.
Ok now here’s the fun stuff.
index.ejs
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<a href="/login">Login</a>
<% if (user != undefined) { %>
<a href="/token/create">Mint Token</a>
<% } %>
</body>
</html>
In EJS, we check if the user is logged in. If they are, we show them the /token/create link.
Now add the route…
routes.js
app.get("/token/create", async function(req, res){
res.render("create", {user: req.user})
})
And the template…
/views/create.ejs
<html>
<head>
<script>
</script>
</head>
<body>
<form action="/token/create" method="post" >
<input name="name" placeholder="name">
<input name="initialSupply" placeholder="1">
<input name="description" placeholder="description">
<input name="avatar" placeholder="avatarwebsite.com/avatar">
<button type="submit">Mint Token</button>
</form>
</body>
</html>
This page will send a POST request to /token/create with the details of the new token the users plans to mint.
Now we must make the POST /token/create route.
routes.js
app.post("/token/create", isLoggedIn, async function(req, res){
const data = JSON.stringify({'protocol': "SFP@0.1", 'name': req.body.name, 'initialSupply': parseInt(req.body.initialSupply), 'description': req.body.description, "avatar": req.body.avatar})
const options = {
hostname: "www.moneybutton.com",
path: "/api/v2/me/assets",
method: 'POST',
headers: {
'content-type': 'application/json',
"Authorization": "Bearer "+req.user.accessToken
}
}
const request = https.request(options, result => {
console.log(`statusCode: ${res.statusCode}`)
console.log(request.url)
result.on('data', d => {
process.stdout.write(d)
console.log(JSON.parse(d.toString()))
res.render("mint", {user: req.user, paymailAlias: JSON.parse(d.toString()).paymailAlias, initialSupply: req.body.initialSupply})
})
})
request.write(data)
request.end()
})
When the initial POST /token/create is received, we send another request to moneybutton to create the asset (no bitcoin transaction involved). Moneybutton then sends us back the paymail address of the asset. Now, we can render the page where the user mints the asset.
/views/mint.ejs
<html>
<head>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://www.moneybutton.com/moneybutton.js"></script>
<script>
$(function() {
moneyButton.render(document.getElementById('mb-button'), {
outputs: [
{
asset: "<%= paymailAlias %>" + "@moneybutton.com",
amount: <%= initialSupply %>,
}
]
})
})
</script>
</head>
<body>
<div id='mb-button'></div>
</body>
</html>
Try it! :)
If you found this useful, you can tip me - 21394@moneybutton.com