Money Button Token API Website Example

Article
bigriz

1 month ago by bigriz

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