├── assets └── image │ ├── Dunnottar-Castle.jpg │ ├── Somewhere-in-Romania.jpg │ ├── Valley-of-Ten-Peaks1.jpg │ ├── Solitude-in-the-Olympics1.jpg │ ├── 8480323243_79c94b8479_b-620x620.jpg │ └── Campo-Andaluz-Andalusian-Countryside.jpg ├── index.js ├── deploy.sh ├── config └── dbconfig.js ├── lib ├── debug.js ├── WcMiddleware.js ├── http-error.js ├── handlers │ ├── authentication.js │ ├── balance.js │ ├── faucet.js │ ├── home.js │ ├── addcredits.js │ └── pay.js ├── create-server.js ├── create-app.js └── webcredits.js ├── package.json ├── LICENSE ├── bin └── server.js ├── README.md └── SPEC.md /assets/image/Dunnottar-Castle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/Dunnottar-Castle.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/webcredits') 2 | module.exports.createServer = require('./lib/create-server') 3 | -------------------------------------------------------------------------------- /assets/image/Somewhere-in-Romania.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/Somewhere-in-Romania.jpg -------------------------------------------------------------------------------- /assets/image/Valley-of-Ten-Peaks1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/Valley-of-Ten-Peaks1.jpg -------------------------------------------------------------------------------- /assets/image/Solitude-in-the-Olympics1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/Solitude-in-the-Olympics1.jpg -------------------------------------------------------------------------------- /assets/image/8480323243_79c94b8479_b-620x620.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/8480323243_79c94b8479_b-620x620.jpg -------------------------------------------------------------------------------- /assets/image/Campo-Andaluz-Andalusian-Countryside.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melvincarvalho/402/master/assets/image/Campo-Andaluz-Andalusian-Countryside.jpg -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git add -u 4 | git commit -m "$1" 5 | git push origin master 6 | 7 | git checkout gh-pages 8 | git merge master 9 | git push origin gh-pages 10 | 11 | git checkout master 12 | 13 | bower install 14 | -------------------------------------------------------------------------------- /config/dbconfig.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var config = {}; 4 | config.storage = 'credit.db'; 5 | config.dialect = 'mysql'; 6 | config.host = 'localhost'; 7 | config.database = 'webcredits'; 8 | config.username = 'me'; 9 | config.password = ''; 10 | config.wallet = 'https://localhost/wallet/test#this'; 11 | config.currency = 'https://w3id.org/cc#bit'; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug') 2 | 3 | exports.handlers = debug('wc:handlers') 4 | exports.errors = debug('wc:errors') 5 | exports.authentication = debug('wc:authentication') 6 | exports.server = debug('wc:server') 7 | exports.balance = debug('wc:balance') 8 | exports.home = debug('wc:home') 9 | exports.insert = debug('wc:insert') 10 | exports.ledger = debug('wc:ledger') 11 | exports.today = debug('wc:today') 12 | exports.tx = debug('wc:tx') 13 | -------------------------------------------------------------------------------- /lib/WcMiddleware.js: -------------------------------------------------------------------------------- 1 | module.exports = WcMiddleware 2 | 3 | var express = require('express') 4 | var authentication = require('./handlers/authentication') 5 | 6 | function WcMiddleware (corsSettings) { 7 | var router = express.Router('/') 8 | 9 | 10 | router.use('/', authentication) 11 | //router.get('/', home) 12 | 13 | // Errors 14 | //router.use(errorPages) 15 | 16 | // TODO: in the process of being deprecated 17 | // Convert json-ld and nquads to turtle 18 | // router.use('/*', parse.parseHandler) 19 | 20 | return router 21 | } 22 | -------------------------------------------------------------------------------- /lib/http-error.js: -------------------------------------------------------------------------------- 1 | module.exports = HTTPError 2 | 3 | function HTTPError (status, message) { 4 | if (!(this instanceof HTTPError)) { 5 | return new HTTPError(status, message) 6 | } 7 | 8 | // Error.captureStackTrace(this, this.constructor) 9 | this.name = this.constructor.name 10 | 11 | // If status is an object it will be of the form: 12 | // {status: , message: } 13 | if (typeof status === 'number') { 14 | this.message = message || 'Error occurred' 15 | this.status = status 16 | } else { 17 | var err = status 18 | this.message = message || err.message 19 | this.status = err.status || err.code === 'ENOENT' ? 404 : 500 20 | } 21 | } 22 | require('util').inherits(module.exports, Error) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "402", 3 | "version": "0.1.1", 4 | "description": "402 payments", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/melvincarvalho/402.git" 12 | }, 13 | "keywords": [ 14 | "402", 15 | "payments" 16 | ], 17 | "author": "Melvin Carvalho (https://melvincarvalho.com/#me)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/melvincarvalho/402/issues" 21 | }, 22 | "homepage": "https://github.com/melvincarvalho/402#readme", 23 | "dependencies": { 24 | "body-parser": "^1.15.1", 25 | "commander": "^2.9.0", 26 | "cors": "^2.7.1", 27 | "dateformat": "^1.0.12", 28 | "express": "^4.13.4", 29 | "express-session": "^1.13.0", 30 | "http-error": "0.0.6", 31 | "sequelize": "^3.23.2", 32 | "vhost": "^3.0.2", 33 | "webcredits": "^0.1.10", 34 | "webid": "^0.3.7" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Melvin Carvalho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/handlers/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var webid = require('webid/tls') 4 | var debug = require('../debug').authentication 5 | var error = require('../http-error') 6 | 7 | function handler (req, res, next) { 8 | 9 | var certificate = req.connection.getPeerCertificate() 10 | console.log(certificate) 11 | // Certificate is empty? skip 12 | if (certificate === null || Object.keys(certificate).length === 0) { 13 | console.log('No client certificate found in the request. Did the user click on a cert?') 14 | setEmptySession(req) 15 | return next() 16 | } 17 | 18 | // Verify webid 19 | webid.verify(certificate, function (err, result) { 20 | if (err) { 21 | console.log('Error processing certificate: ' + err.message) 22 | setEmptySession(req) 23 | return next(error(403, 'Forbidden')) 24 | } 25 | req.session.userId = result 26 | req.session.identified = true 27 | console.log('Identified user: ' + result) 28 | res.set('User', req.session.userId) 29 | return next() 30 | }) 31 | } 32 | 33 | function setEmptySession (req) { 34 | req.session.userId = '' 35 | req.session.identified = false 36 | } 37 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // requires 4 | var program = require('commander') 5 | var wc = require('../') 6 | var express = require('express') 7 | 8 | function bin(argv) { 9 | 10 | program 11 | .option('-c, --currency ', 'Currency') 12 | .option('-p, --port ', 'Port', parseInt) 13 | .option('-d, --database ', 'Database') 14 | .option('-w, --wallet ', 'Wallet') 15 | .option('--key ', 'Key') 16 | .option('--cert ', 'Cert') 17 | .parse(argv) 18 | 19 | var app 20 | try { 21 | app = wc.createServer(program) 22 | } catch (e) { 23 | if (e.code === 'EACCES') { 24 | console.log('You need root privileges to start on this port') 25 | return 1 26 | } 27 | if (e.code === 'EADDRINUSE') { 28 | console.log('The port ' + argv.port + ' is already in use') 29 | return 1 30 | } 31 | console.log(e.message) 32 | console.log(e.stack) 33 | return 1 34 | } 35 | 36 | try { 37 | app.listen(program.port, function () { 38 | console.log('Server started on port ' + program.port) 39 | }) 40 | } catch (e) { 41 | throw new Error(e) 42 | } 43 | 44 | } 45 | 46 | // If one import this file, this is a module, otherwise a library 47 | if (require.main === module) { 48 | bin(process.argv) 49 | } 50 | 51 | module.exports = bin 52 | -------------------------------------------------------------------------------- /lib/handlers/balance.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var debug = require('../debug').insert 4 | var wc = require('webcredits') 5 | var fs = require('fs') 6 | 7 | function handler(req, res) { 8 | 9 | var origin = req.headers.origin; 10 | if (origin) { 11 | res.setHeader('Access-Control-Allow-Origin', origin); 12 | } 13 | 14 | var defaultCurrency = res.locals.config.currency || 'https://w3id.org/cc#bit'; 15 | 16 | var source = req.body.source; 17 | var destination = req.body.destination; 18 | var currency = req.body.currency || defaultCurrency; 19 | var amount = req.body.amount; 20 | var timestamp = null; 21 | var description = req.body.description; 22 | var context = req.body.context; 23 | 24 | 25 | var source = req.session.userId 26 | 27 | if (!req.session.userId) { 28 | res.send('must be authenticated') 29 | return 30 | } 31 | 32 | 33 | var config = require('../../config/dbconfig.js'); 34 | 35 | var sequelize = wc.setupDB(config); 36 | wc.getBalance(source, sequelize, config, function(err, ret){ 37 | if (err) { 38 | console.error(err); 39 | } else { 40 | console.log(ret); 41 | if (ret === null) { 42 | ret = 0 43 | } 44 | res.status(200) 45 | res.header('Content-Type', 'text/html'); 46 | res.write('Balance : ' + Math.round(ret).toString()); 47 | res.write('
\n'); 48 | res.write('Home') 49 | res.end() 50 | } 51 | sequelize.close(); 52 | }); 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /lib/create-server.js: -------------------------------------------------------------------------------- 1 | module.exports = createServer 2 | 3 | // requires 4 | var Sequelize = require('sequelize') 5 | var express = require('express') 6 | var bodyParser = require('body-parser') 7 | var https = require('https') 8 | var fs = require('fs') 9 | 10 | var wc = require('../') 11 | var createApp = require('./create-app') 12 | 13 | 14 | /** 15 | * server function 16 | * @param {Object} config [description] 17 | */ 18 | function createServer(argv) { 19 | // vars 20 | var sequelize 21 | 22 | var config = wc.getConfig() 23 | 24 | var defaultCurrency = 'https://w3id.org/cc#bit' 25 | var defaultDatabase = 'webcredits' 26 | var defaultWallet = 'https://localhost/wallet/test#this' 27 | 28 | config.currency = argv.currency || config.currency || defaultCurrency 29 | config.database = argv.database || config.database || defaultDatabase 30 | config.wallet = argv.wallet || config.wallet || defaultWallet 31 | config.key = argv.key || null 32 | config.cert = argv.cert || null 33 | 34 | var port = argv.port 35 | 36 | // run main 37 | sequelize = wc.setupDB(config) 38 | 39 | var app = express() 40 | wcApp = createApp(null, sequelize, config) 41 | app.use('/', wcApp) 42 | 43 | var defaultPort = 11077 44 | port = port || defaultPort 45 | 46 | console.log(config) 47 | 48 | var key 49 | try { 50 | key = fs.readFileSync(config.key) 51 | } catch (e) { 52 | throw new Error('Can\'t find SSL key in ' + config.key) 53 | } 54 | 55 | var cert 56 | try { 57 | cert = fs.readFileSync(config.cert) 58 | } catch (e) { 59 | throw new Error('Can\'t find SSL cert in ' + config.cert) 60 | } 61 | 62 | var credentials = { 63 | key: key, 64 | cert: cert, 65 | requestCert: true 66 | } 67 | 68 | server = https.createServer(credentials, app) 69 | 70 | return server 71 | 72 | } 73 | -------------------------------------------------------------------------------- /lib/handlers/faucet.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var debug = require('../debug').insert 4 | var wc = require('webcredits') 5 | var fs = require('fs') 6 | 7 | function handler(req, res) { 8 | 9 | var origin = req.headers.origin; 10 | if (origin) { 11 | res.setHeader('Access-Control-Allow-Origin', origin); 12 | } 13 | 14 | var defaultCurrency = res.locals.config.currency || 'https://w3id.org/cc#bit'; 15 | 16 | var source = req.body.source; 17 | var destination = req.body.destination; 18 | var currency = req.body.currency || defaultCurrency; 19 | var amount = req.body.amount; 20 | var timestamp = null; 21 | var description = req.body.description; 22 | var context = req.body.context; 23 | 24 | 25 | var source = req.session.userId 26 | 27 | if (!req.session.userId) { 28 | res.send('must be authenticated') 29 | return 30 | } 31 | 32 | var faucetURI = 'https://w3id.org/cc#faucet' 33 | 34 | var config = require('../../config/dbconfig.js'); 35 | 36 | var sequelize = wc.setupDB(config); 37 | wc.getBalance(faucetURI, sequelize, config, function(err, ret){ 38 | if (err) { 39 | console.error(err); 40 | } else { 41 | console.log(ret); 42 | if (ret === null) { 43 | ret = 0 44 | } 45 | var payout = Math.floor(ret / 100.0) 46 | res.status(200) 47 | res.header('Content-Type', 'text/html'); 48 | res.write('Faucet Balance : ' + Math.round(ret).toString()); 49 | res.write('
\n'); 50 | res.write('Next payout : ' + payout) 51 | res.write('
\n'); 52 | res.write('
Which year was the web invented?') 53 | res.write('
\n'); 54 | res.write(' ') 55 | res.write('
\n'); 56 | res.write('
') 57 | res.end() 58 | } 59 | sequelize.close(); 60 | }); 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /lib/handlers/home.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var debug = require('../debug').insert 4 | var wc = require('webcredits') 5 | var fs = require('fs') 6 | 7 | function handler(req, res) { 8 | 9 | var origin = req.headers.origin; 10 | if (origin) { 11 | res.setHeader('Access-Control-Allow-Origin', origin); 12 | } 13 | 14 | var defaultCurrency = res.locals.config.currency || 'https://w3id.org/cc#bit'; 15 | 16 | var source = req.body.source; 17 | var destination = req.body.destination; 18 | var currency = req.body.currency || defaultCurrency; 19 | var amount = req.body.amount; 20 | var timestamp = null; 21 | var description = req.body.description; 22 | var context = req.body.context; 23 | 24 | 25 | var source = req.session.userId 26 | 27 | if (!req.session.userId) { 28 | res.send('Must be authenticated via WebID. Get a webid NOW!') 29 | return 30 | } 31 | 32 | var faucetURI = 'https://w3id.org/cc#faucet' 33 | 34 | var config = require('../../config/dbconfig.js'); 35 | 36 | var sequelize = wc.setupDB(config); 37 | wc.getBalance(faucetURI, sequelize, config, function(err, ret){ 38 | if (err) { 39 | console.error(err); 40 | } else { 41 | console.log(ret); 42 | if (ret === null) { 43 | ret = 0 44 | } 45 | var payout = Math.abs(ret / 100.0) 46 | res.status(200) 47 | res.header('Content-Type', 'text/html'); 48 | res.write('Welcome to HTTP 402 test. Options:'); 49 | res.write('
\n'); 50 | res.write('See your balance ') 51 | res.write('
\n'); 52 | res.write('Access a paid resource (cost=25 credits, will be returned to the faucet)') 53 | res.write('
\n'); 54 | res.write('Visit the faucet if you dont have enough credits') 55 | res.write('
\n'); 56 | res.end() 57 | } 58 | sequelize.close(); 59 | }); 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/create-app.js: -------------------------------------------------------------------------------- 1 | module.exports = createApp 2 | 3 | var express = require('express') 4 | var session = require('express-session') 5 | var uuid = require('node-uuid') 6 | var cors = require('cors') 7 | var vhost = require('vhost') 8 | var path = require('path') 9 | var WcMiddleware = require('./WcMiddleware') 10 | var Sequelize = require('sequelize'); 11 | var express = require('express'); 12 | var program = require('commander'); 13 | var bodyParser = require('body-parser'); 14 | var https = require('https'); 15 | var fs = require('fs'); 16 | var wc = require('../') 17 | 18 | var pay = require('./handlers/pay') 19 | var balance = require('./handlers/balance') 20 | var faucet = require('./handlers/faucet') 21 | var addcredits = require('./handlers/addcredits') 22 | var home = require('./handlers/home') 23 | 24 | 25 | 26 | var corsSettings = cors({ 27 | methods: [ 28 | 'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE' 29 | ], 30 | exposedHeaders: 'User, Location, Link, Vary, Last-Modified, ETag, Accept-Patch, Updates-Via, Allow, Content-Length', 31 | credentials: true, 32 | maxAge: 1728000, 33 | origin: true 34 | }) 35 | 36 | function createApp (argv, sequelize, config) { 37 | var app = express() 38 | 39 | // Session 40 | var sessionSettings = { 41 | secret: uuid.v1(), 42 | saveUninitialized: false, 43 | resave: false 44 | } 45 | sessionSettings.cookie = { 46 | secure: true 47 | } 48 | 49 | app.use(session(sessionSettings)) 50 | app.use('/', WcMiddleware(corsSettings)) 51 | 52 | app.use( bodyParser.json() ) // to support JSON-encoded bodies 53 | app.use(bodyParser.urlencoded({ // to support URL-encoded bodies 54 | extended: true 55 | })) 56 | 57 | app.use(function(req,res, next) { 58 | res.locals.sequelize = sequelize; 59 | res.locals.config = config; 60 | next(); 61 | }); 62 | 63 | app.get('/pay', pay) 64 | app.get('/balance', balance) 65 | app.get('/faucet', faucet) 66 | app.post('/addcredits', addcredits) 67 | app.get('/', home) 68 | 69 | return app 70 | } 71 | -------------------------------------------------------------------------------- /lib/handlers/addcredits.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var debug = require('../debug').insert 4 | var wc = require('webcredits') 5 | var fs = require('fs') 6 | 7 | function handler(req, res) { 8 | 9 | var origin = req.headers.origin; 10 | if (origin) { 11 | res.setHeader('Access-Control-Allow-Origin', origin); 12 | } 13 | 14 | var defaultCurrency = res.locals.config.currency || 'https://w3id.org/cc#bit'; 15 | 16 | var source = req.body.source; 17 | var destination = req.body.destination; 18 | var currency = req.body.currency || defaultCurrency; 19 | var amount = req.body.amount; 20 | var timestamp = null; 21 | var description = req.body.description; 22 | var context = req.body.context; 23 | 24 | 25 | var source = req.session.userId 26 | 27 | if (!req.session.userId) { 28 | res.send('must be authenticated') 29 | return 30 | } 31 | 32 | var faucetURI = 'https://w3id.org/cc#faucet' 33 | 34 | var config = require('../../config/dbconfig.js'); 35 | 36 | var sequelize = wc.setupDB(config); 37 | wc.getBalance(faucetURI, sequelize, config, function(err, ret){ 38 | if (err) { 39 | console.error(err); 40 | } else { 41 | console.log(ret); 42 | if (ret === null) { 43 | ret = 0 44 | } 45 | 46 | var payout = Math.floor(ret / 100.0) 47 | 48 | res.header('Content-Type', 'text/html') 49 | res.status(200) 50 | res.write('Balance : ' + ret.toString()); 51 | res.write('
\n') 52 | res.write('you chose year : ' + req.body.year); 53 | res.write('
\n') 54 | 55 | if (req.body.year === '1989') { 56 | res.write('Correct!') 57 | 58 | var credit = {}; 59 | 60 | credit["https://w3id.org/cc#source"] = faucetURI 61 | credit["https://w3id.org/cc#amount"] = payout 62 | credit["https://w3id.org/cc#currency"] = 'https://w3id.org/cc#bit' 63 | credit["https://w3id.org/cc#destination"] = req.session.userId 64 | 65 | 66 | wc.insert(credit, res.locals.sequelize, res.locals.config, function(err, ret) { 67 | if (err) { 68 | res.write(err); 69 | } else { 70 | res.write('
\n') 71 | res.write(payout + ' has been added to your balance') 72 | res.write('
\n') 73 | } 74 | res.write('
\n') 75 | 76 | res.end() 77 | 78 | }); 79 | 80 | } else { 81 | res.write('Wrong answer. Please go back and try again.') 82 | } 83 | } 84 | sequelize.close(); 85 | }); 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /lib/handlers/pay.js: -------------------------------------------------------------------------------- 1 | module.exports = handler 2 | 3 | var debug = require('../debug').insert 4 | var wc = require('webcredits') 5 | var fs = require('fs') 6 | 7 | function handler(req, res) { 8 | 9 | var origin = req.headers.origin; 10 | if (origin) { 11 | res.setHeader('Access-Control-Allow-Origin', origin); 12 | } 13 | 14 | var defaultCurrency = res.locals.config.currency || 'https://w3id.org/cc#bit'; 15 | 16 | var source = req.body.source; 17 | var destination = req.body.destination; 18 | var currency = req.body.currency || defaultCurrency; 19 | var amount = req.body.amount; 20 | var timestamp = null; 21 | var description = req.body.description; 22 | var context = req.body.context; 23 | 24 | 25 | 26 | 27 | if (!req.session.userId) { 28 | res.send('must be authenticated') 29 | return 30 | } 31 | 32 | var source = req.session.userId 33 | 34 | if (!req.session.userId) { 35 | res.send('must be authenticated') 36 | return 37 | } 38 | 39 | 40 | var config = require('../../config/dbconfig.js'); 41 | var cost = 25 42 | var faucetURI = 'https://w3id.org/cc#faucet' 43 | 44 | var sequelize = wc.setupDB(config); 45 | wc.getBalance(source, sequelize, config, function(err, ret){ 46 | if (err) { 47 | console.error(err); 48 | } else { 49 | console.log(ret); 50 | 51 | if (ret > cost) { 52 | 53 | var credit = {}; 54 | 55 | credit["https://w3id.org/cc#source"] = req.session.userId 56 | credit["https://w3id.org/cc#amount"] = cost 57 | credit["https://w3id.org/cc#currency"] = 'https://w3id.org/cc#bit' 58 | credit["https://w3id.org/cc#destination"] = faucetURI 59 | 60 | 61 | wc.insert(credit, res.locals.sequelize, res.locals.config, function(err, ret) { 62 | if (err) { 63 | res.write(err); 64 | } else { 65 | var images = [ 66 | './assets/image/8480323243_79c94b8479_b-620x620.jpg', 67 | './assets/image/Campo-Andaluz-Andalusian-Countryside.jpg', 68 | './assets/image/Dunnottar-Castle.jpg', 69 | './assets/image/Solitude-in-the-Olympics1.jpg', 70 | './assets/image/Somewhere-in-Romania.jpg', 71 | './assets/image/Valley-of-Ten-Peaks1.jpg' 72 | ] 73 | 74 | 75 | var index = Math.floor(Math.random() * 6) 76 | var img = fs.readFileSync(images[index]) 77 | res.writeHead(200, {'Content-Type': 'image/gif' }); 78 | res.end(img, 'binary'); 79 | } 80 | 81 | }); 82 | 83 | 84 | } else { 85 | res.statusCode = 402; 86 | res.send('HTTP 402! This resource has paid access control!') 87 | } 88 | 89 | 90 | } 91 | sequelize.close(); 92 | }); 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | **Note: This repository contains experimental work from 2016 implementing HTTP 402 with Solid. We are now exploring modern HTTP 402 implementations and standards for R&D purposes.** 4 | 5 | For today's hackday I am going to try and start to build an access controlled "route" in solid-server, which grants access to a resource based on a credit system. If you have credits, it will show you the resource. If not you will get a 402. This builds a bit more functionality on the code written for the previous [hack day](http://melvincarvalho.github.io/markdown-editor/?uri=https://melvin.databox.me/.markdown/hackday/route.txt). I'll try and incorporate the work from the web payments WG [http://w3c.github.io/webpayments-http-api/](http://w3c.github.io/webpayments-http-api/). 6 | 7 | ## Modern HTTP 402 Implementations (2025) 8 | 9 | The HTTP 402 "Payment Required" status code, long dormant since HTTP/1.1, has seen significant adoption in 2025. Here are notable projects and standards: 10 | 11 | ### x402 Protocol 12 | - **Description**: Open protocol by Coinbase that enables blockchain-based payments over HTTP 13 | - **Partners**: Cloudflare, Visa, Google, AWS, Anthropic, Circle, Vercel 14 | - **Documentation**: https://x402.gitbook.io/x402/core-concepts/client-server 15 | - **Foundation**: x402 Foundation (Coinbase + Cloudflare) 16 | - **Use Cases**: AI agent payments, API monetization, autonomous commerce 17 | - **Stats**: 1.38M+ transactions, $1.48M+ volume (as of Oct 2025) 18 | 19 | ### Cloudflare Pay Per Crawl 20 | - **Description**: HTTP 402 implementation for AI crawler monetization 21 | - **Documentation**: https://developers.cloudflare.com/agents/x402/ 22 | - **Features**: Per-request pricing for AI bots accessing web content 23 | - **Scale**: 1 billion+ HTTP 402 responses daily 24 | 25 | ### L402 Protocol (Lightning Labs) 26 | - **Description**: Lightning Network-based HTTP 402 implementation (formerly LSAT) 27 | - **GitHub**: https://github.com/lightninglabs/L402 28 | - **Documentation**: https://docs.lightning.engineering/the-lightning-network/l402 29 | - **Tools**: Aperture (reverse proxy), LangChainL402 (AI integration) 30 | - **Use Cases**: Pay-per-use APIs, micropayments, Lightning Loop/Pool 31 | 32 | ### Related Projects 33 | - **Aperture**: L402 reverse HTTP proxy for paid APIs 34 | - **@bus402/x402-sandbox**: NPM package for local x402 testing 35 | - **Visa TAP**: Trusted Agent Protocol supporting x402 36 | - **PING Token**: First token experiment on x402 protocol (Base chain) 37 | 38 | ### Resources 39 | - MDN HTTP 402 Documentation: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/402 40 | - awesome-L402: https://github.com/Fewsats/awesome-L402 41 | 42 | 43 | ## Components 44 | 45 | The demo contains 4 components 46 | 47 | * /pay -- the resource that costs 25 credits to view 48 | * /balance -- shows your balance 49 | * /faucet -- a small app that will see your balance 50 | * /home -- navigation and instructions 51 | 52 | Each was deployed as a custom route with its own handler. 53 | 54 | ## Installation 55 | 56 | Installation is via 57 | 58 | git clone https://github.com/melvincarvalho/402.git 59 | 60 | Then run 61 | 62 | npm install 63 | 64 | 65 | ```bash 66 | $ bin/server.js --port 8443 --ssl-key path/to/ssl-key.pem --ssl-cert path/to/ssl-cert.pem 67 | # server running on https://localhost:8443/ 68 | ``` 69 | 70 | ##### How do I get the --ssl-key and the --ssl-cert? 71 | You need an SSL certificate you get this from your domain provider or for free from [Let's Encrypt!](https://letsencrypt.org/getting-started/). 72 | 73 | If you don't have one yet, or you just want to test `solid`, generate a certificate 74 | ``` 75 | $ openssl genrsa 2048 > ../localhost.key 76 | $ openssl req -new -x509 -nodes -sha256 -days 3650 -key ../localhost.key -subj '/CN=*.localhost' > ../localhost.cert 77 | ``` 78 | 79 | ## Faucet 80 | 81 | Using [webcredits](https://webcredits.org/) it is possible to set up a faucet using 82 | 83 | credit create 84 | 85 | credit genesis 86 | 87 | credit insert https://w3id.org/cc#coinbase 50000 '' https://w3id.org/cc#faucet 88 | 89 | 90 | ## Demo 91 | 92 | [Demo](https://webcredits.org:3000/) 93 | -------------------------------------------------------------------------------- /SPEC.md: -------------------------------------------------------------------------------- 1 | # HTTP 402 Payment Required - Open Standards Specification 2 | 3 | **Version:** 0.1.0 4 | **Status:** Draft 5 | **Date:** 2025-10-28 6 | **Authors:** Melvin Carvalho 7 | 8 | ## Abstract 9 | 10 | This specification defines a minimal, open standards-based protocol for implementing HTTP 402 "Payment Required" responses. It adheres to W3C and IETF standards, is blockchain-agnostic, and avoids proprietary dependencies. The specification leverages existing HTTP authentication frameworks (RFC 7235) and is compatible with multiple payment methods including Bitcoin, Lightning Network, and web-based payment systems. 11 | 12 | ## 1. Introduction 13 | 14 | ### 1.1 Purpose 15 | 16 | HTTP 402 "Payment Required" was reserved in HTTP/1.1 (RFC 7231) but never standardized. This specification provides a minimal, interoperable protocol for implementing pay-per-request HTTP resources using open web standards. 17 | 18 | ### 1.2 Design Principles 19 | 20 | 1. **Open Standards**: Use W3C and IETF specifications exclusively 21 | 2. **Blockchain Agnostic**: Support any payment method or currency 22 | 3. **Standards Compliant Headers**: Follow RFC 6648 (no "X-" prefix) 23 | 4. **Minimal Specification**: Keep the protocol simple and extensible 24 | 5. **Privacy Preserving**: Stateless, no mandatory account/session tracking 25 | 6. **Decentralized**: No required third-party intermediaries 26 | 27 | ## 2. Protocol Overview 28 | 29 | ### 2.1 Request Flow 30 | 31 | ``` 32 | Client Server 33 | | | 34 | |---(1) GET /resource---------->| 35 | | | 36 | |<--(2) 402 Payment Required----| 37 | | WWW-Authenticate | 38 | | Payment-Info | 39 | | | 40 | |---(3) GET /resource---------->| 41 | | Authorization | 42 | | Payment-Proof | 43 | | | 44 | |<--(4) 200 OK------------------| 45 | | Resource Content | 46 | ``` 47 | 48 | ### 2.2 Status Codes 49 | 50 | - **402 Payment Required**: Resource requires payment 51 | - **200 OK**: Payment verified, resource provided 52 | - **400 Bad Request**: Invalid payment proof format 53 | - **402 Payment Required** (retry): Payment verification failed 54 | 55 | ## 3. HTTP Headers 56 | 57 | ### 3.1 Server Response Headers 58 | 59 | #### 3.1.1 WWW-Authenticate 60 | 61 | **Specification:** RFC 7235 62 | 63 | Indicates the authentication scheme and payment challenge. 64 | 65 | ``` 66 | WWW-Authenticate: Payment realm="resource-access", 67 | method="bitcoin:lightning", 68 | amount="1000", 69 | currency="sat" 70 | ``` 71 | 72 | **Parameters:** 73 | - `realm` (required): Protection space identifier 74 | - `method` (required): Payment method identifier (see Section 4) 75 | - `amount` (required): Payment amount in specified currency 76 | - `currency` (required): Currency or unit identifier 77 | 78 | #### 3.1.2 Payment-Info 79 | 80 | Provides additional payment details in JSON format. 81 | 82 | ``` 83 | Payment-Info: {"invoice":"lnbc...", "address":"bc1q..."} 84 | ``` 85 | 86 | **Fields** (payment method specific): 87 | - Lightning: `invoice` (BOLT-11 invoice) 88 | - Bitcoin: `address` (on-chain address), `amount_btc` 89 | - WebCredits: `recipient` (URI), `currency` 90 | 91 | ### 3.2 Client Request Headers 92 | 93 | #### 3.2.1 Authorization 94 | 95 | **Specification:** RFC 7235 96 | 97 | Contains payment authentication credentials. 98 | 99 | ``` 100 | Authorization: Payment 101 | ``` 102 | 103 | Where `` is base64-encoded payment method-specific data. 104 | 105 | #### 3.2.2 Payment-Proof 106 | 107 | Contains payment verification data (transaction ID, preimage, signature, etc.). 108 | 109 | ``` 110 | Payment-Proof: {"preimage":"a1b2c3...", "type":"lightning"} 111 | ``` 112 | 113 | **Common Fields:** 114 | - `type` (required): Payment method identifier 115 | - `preimage`: Lightning payment preimage 116 | - `txid`: Bitcoin transaction ID 117 | - `signature`: Cryptographic signature 118 | - `timestamp`: Payment timestamp (ISO 8601) 119 | 120 | ## 4. Payment Method Identifiers 121 | 122 | ### 4.1 Identifier Format 123 | 124 | Payment methods use URI-like identifiers following W3C Payment Method Identifiers patterns: 125 | 126 | ``` 127 | payment-method = scheme ":" [ sub-method ] 128 | ``` 129 | 130 | ### 4.2 Registered Methods 131 | 132 | | Identifier | Description | Specification | 133 | |------------|-------------|---------------| 134 | | `bitcoin` | Bitcoin on-chain | BIP-21, BIP-70 | 135 | | `bitcoin:lightning` | Lightning Network | BOLT-11 | 136 | | `webcredits` | WebCredits protocol | [WebCredits Spec] | 137 | | `w3c:payment-request` | W3C Payment Request API | W3C PR API | 138 | 139 | ### 4.3 Method Registration 140 | 141 | New payment methods should: 142 | 1. Use descriptive, non-proprietary names 143 | 2. Reference open specifications 144 | 3. Be blockchain/platform agnostic 145 | 4. Document the payment-proof format 146 | 147 | ## 5. Payment Proof Formats 148 | 149 | ### 5.1 Lightning Network 150 | 151 | ```json 152 | { 153 | "type": "bitcoin:lightning", 154 | "preimage": "hex-encoded-preimage", 155 | "invoice": "lnbc...", 156 | "timestamp": "2025-10-28T12:00:00Z" 157 | } 158 | ``` 159 | 160 | **Verification:** 161 | 1. Hash preimage with SHA-256 162 | 2. Compare to invoice payment_hash 163 | 3. Verify amount matches requirement 164 | 165 | ### 5.2 Bitcoin On-Chain 166 | 167 | ```json 168 | { 169 | "type": "bitcoin", 170 | "txid": "transaction-id", 171 | "vout": 0, 172 | "confirmations": 1, 173 | "timestamp": "2025-10-28T12:00:00Z" 174 | } 175 | ``` 176 | 177 | **Verification:** 178 | 1. Query blockchain for transaction 179 | 2. Verify output address and amount 180 | 3. Check minimum confirmations 181 | 182 | ### 5.3 WebCredits 183 | 184 | ```json 185 | { 186 | "type": "webcredits", 187 | "transaction": "https://example.org/tx/123", 188 | "signature": "base64-signature", 189 | "timestamp": "2025-10-28T12:00:00Z" 190 | } 191 | ``` 192 | 193 | **Verification:** 194 | 1. Fetch transaction document 195 | 2. Verify cryptographic signature 196 | 3. Check amount and recipient 197 | 198 | ## 6. Security Considerations 199 | 200 | ### 6.1 Replay Protection 201 | 202 | Servers SHOULD implement one or more: 203 | - Nonce-based challenges (include nonce in WWW-Authenticate) 204 | - Timestamp validation (reject old proofs) 205 | - Payment-proof uniqueness tracking 206 | - Invoice/address single-use enforcement 207 | 208 | ### 6.2 Privacy 209 | 210 | - Servers MUST NOT require user accounts 211 | - Payment methods SHOULD support pseudonymous payments 212 | - Servers SHOULD NOT log identifying information beyond payment verification 213 | - TLS (HTTPS) MUST be used for all communications 214 | 215 | ### 6.3 Amount Verification 216 | 217 | Clients MUST verify: 218 | 1. Payment amount matches server requirement 219 | 2. Currency/unit is as expected 220 | 3. Recipient address is correct 221 | 222 | Servers MUST verify: 223 | 1. Payment amount meets or exceeds requirement 224 | 2. Payment is confirmed/settled 225 | 3. Payment proof is cryptographically valid 226 | 227 | ## 7. Error Handling 228 | 229 | ### 7.1 Error Response Format 230 | 231 | ```json 232 | { 233 | "error": "payment_verification_failed", 234 | "description": "Lightning payment preimage does not match invoice hash", 235 | "retry": true 236 | } 237 | ``` 238 | 239 | ### 7.2 Common Error Codes 240 | 241 | | Error Code | Description | HTTP Status | 242 | |------------|-------------|-------------| 243 | | `payment_insufficient` | Amount too low | 402 | 244 | | `payment_verification_failed` | Invalid proof | 402 | 245 | | `payment_expired` | Payment window expired | 402 | 246 | | `payment_method_unsupported` | Method not accepted | 400 | 247 | | `malformed_proof` | Invalid proof format | 400 | 248 | 249 | ## 8. Examples 250 | 251 | ### 8.1 Lightning Network Payment 252 | 253 | **Request 1: Initial Request** 254 | ```http 255 | GET /premium-content HTTP/1.1 256 | Host: example.org 257 | ``` 258 | 259 | **Response 1: Payment Required** 260 | ```http 261 | HTTP/1.1 402 Payment Required 262 | WWW-Authenticate: Payment realm="premium-content", 263 | method="bitcoin:lightning", 264 | amount="1000", 265 | currency="sat" 266 | Payment-Info: {"invoice":"lnbc1500n1..."} 267 | Content-Type: application/json 268 | 269 | { 270 | "error": "payment_required", 271 | "message": "This resource requires payment of 1000 satoshis" 272 | } 273 | ``` 274 | 275 | **Request 2: With Payment Proof** 276 | ```http 277 | GET /premium-content HTTP/1.1 278 | Host: example.org 279 | Authorization: Payment dGVzdC1jcmVkZW50aWFscw== 280 | Payment-Proof: {"type":"bitcoin:lightning","preimage":"a1b2c3d4...","invoice":"lnbc1500n1..."} 281 | ``` 282 | 283 | **Response 2: Success** 284 | ```http 285 | HTTP/1.1 200 OK 286 | Content-Type: text/html 287 | 288 | 289 | ...premium content... 290 | ``` 291 | 292 | ### 8.2 Bitcoin On-Chain Payment 293 | 294 | **Response: Payment Required** 295 | ```http 296 | HTTP/1.1 402 Payment Required 297 | WWW-Authenticate: Payment realm="api-access", 298 | method="bitcoin", 299 | amount="0.0001", 300 | currency="BTC" 301 | Payment-Info: {"address":"bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"} 302 | ``` 303 | 304 | **Request: With Payment Proof** 305 | ```http 306 | GET /api/data HTTP/1.1 307 | Host: api.example.org 308 | Authorization: Payment dGVzdA== 309 | Payment-Proof: {"type":"bitcoin","txid":"a1b2c3...","vout":0,"confirmations":1} 310 | ``` 311 | 312 | ## 9. Implementation Considerations 313 | 314 | ### 9.1 Server Implementation 315 | 316 | Servers implementing this specification: 317 | 318 | 1. MUST respond with 402 and WWW-Authenticate header 319 | 2. MUST specify payment method, amount, and currency 320 | 3. SHOULD provide Payment-Info with method-specific details 321 | 4. MUST verify payment proofs cryptographically 322 | 5. SHOULD implement replay protection 323 | 6. MAY support multiple payment methods 324 | 325 | ### 9.2 Client Implementation 326 | 327 | Clients implementing this specification: 328 | 329 | 1. MUST handle 402 responses 330 | 2. MUST parse WWW-Authenticate and Payment-Info headers 331 | 3. MUST verify payment details before sending funds 332 | 4. MUST construct valid payment proofs 333 | 5. SHOULD support common payment methods 334 | 6. MAY cache payment proofs for resource re-access 335 | 336 | ### 9.3 Facilitator Services (Optional) 337 | 338 | Third-party services MAY provide: 339 | - Payment verification APIs 340 | - Transaction monitoring 341 | - Multi-method payment gateways 342 | - Rate limiting and fraud prevention 343 | 344 | Facilitators MUST NOT: 345 | - Require proprietary protocols 346 | - Act as mandatory intermediaries 347 | - Violate decentralization principles 348 | 349 | ## 10. Extensibility 350 | 351 | ### 10.1 Custom Payment Methods 352 | 353 | Implementations MAY define custom payment methods following these guidelines: 354 | 355 | 1. Use descriptive, lowercase identifiers 356 | 2. Document the payment-proof format 357 | 3. Specify verification procedures 358 | 4. Maintain backward compatibility 359 | 5. Avoid proprietary dependencies 360 | 361 | ### 10.2 Additional Headers 362 | 363 | Implementations MAY define additional headers for: 364 | - Rate limiting information 365 | - Payment history 366 | - Service-level agreements 367 | - Multi-resource bundling 368 | 369 | New headers MUST: 370 | - Follow RFC 6648 (no "X-" prefix) 371 | - Be documented publicly 372 | - Remain optional 373 | 374 | ## 11. Conformance 375 | 376 | An implementation conforms to this specification if it: 377 | 378 | 1. Uses HTTP 402 status code per RFC 7231 379 | 2. Uses WWW-Authenticate header per RFC 7235 380 | 3. Supports at least one payment method from Section 4.2 381 | 4. Implements payment verification per Section 5 382 | 5. Follows security considerations in Section 6 383 | 384 | ## 12. References 385 | 386 | ### 12.1 Normative References 387 | 388 | - **RFC 7231**: HTTP/1.1 Semantics and Content 389 | - **RFC 7235**: HTTP/1.1 Authentication 390 | - **RFC 6648**: Deprecating the "X-" Prefix 391 | - **RFC 3986**: URI Generic Syntax 392 | 393 | ### 12.2 Informative References 394 | 395 | - **W3C Payment Request API**: https://www.w3.org/TR/payment-request/ 396 | - **W3C Payment Method Identifiers**: https://www.w3.org/TR/payment-method-id/ 397 | - **BOLT-11**: Lightning Invoice Protocol 398 | - **BIP-21**: Bitcoin URI Scheme 399 | - **WebCredits**: https://webcredits.org/ 400 | 401 | ## 13. Acknowledgments 402 | 403 | This specification builds upon work from: 404 | - W3C Web Payments Working Group 405 | - IETF HTTP Working Group 406 | - Lightning Labs (L402 protocol concepts) 407 | - Coinbase (x402 protocol concepts) 408 | - WebCredits community 409 | 410 | ## Appendix A: Changelog 411 | 412 | ### Version 0.1.0 (2025-10-28) 413 | - Initial draft specification 414 | - Core protocol definition 415 | - Lightning, Bitcoin, and WebCredits payment methods 416 | - Security considerations 417 | 418 | --- 419 | 420 | **License:** This specification is released into the public domain (CC0 1.0 Universal). 421 | -------------------------------------------------------------------------------- /lib/webcredits.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = { 4 | insert : insert, 5 | setupDB : setupDB, 6 | createDB : createDB, 7 | createTables : createTables, 8 | balance : balance, 9 | getBalance : getBalance, 10 | genesis : genesis, 11 | genesisInit : genesisInit, 12 | reputation : reputation, 13 | getReputation : getReputation, 14 | getConfig : getConfig, 15 | getCredit : getCredit, 16 | today : today 17 | }; 18 | 19 | 20 | // requires 21 | var Sequelize = require('sequelize'); 22 | var jsonld = require('jsonld'); 23 | var crypto = require('crypto'); 24 | var promises = jsonld.promises; 25 | var dateFormat = require('dateformat'); 26 | 27 | /** 28 | * setup database 29 | * @param {string} dialect type of db mysql|sqlite 30 | * @param {string} storage file used for sqlite, default ./credit.db 31 | * @return {Object} sequelize db object 32 | */ 33 | function setupDB(config) { 34 | var sequelize; 35 | var defaultStorage = 'credit.db'; 36 | 37 | if (config.dialect === 'sqlite') { 38 | if (!config.storage) { 39 | config.storage = defaultStorage; 40 | } 41 | 42 | sequelize = new Sequelize(config.database, config.username, config.password, { 43 | host: config.host, 44 | dialect: config.dialect, 45 | storage: config.storage, 46 | logging: false 47 | }); 48 | } else { 49 | sequelize = new Sequelize(config.database, config.username, config.password, { 50 | host: config.host, 51 | dialect: config.dialect, 52 | logging: false 53 | }); 54 | } 55 | return sequelize; 56 | } 57 | 58 | 59 | /** 60 | * gets the current config 61 | * @return {Object} The config 62 | */ 63 | function getConfig() { 64 | var config = require("../config/dbconfig"); 65 | return config; 66 | } 67 | 68 | /** 69 | * create tables 70 | * @param {Object} sequelize db object 71 | * @param {Object} config config object 72 | * @param {Object} callback callback 73 | */ 74 | function createTables(sequelize, config, callback) { 75 | 76 | if (!config.wallet) { 77 | config.wallet = null; 78 | } 79 | 80 | var coinbase = 'https://w3id.org/cc#coinbase'; 81 | var currency = config.currency || 'https://w3id.org/cc#bit'; 82 | var wallet = config.wallet; 83 | var initial = 1000000; 84 | 85 | 86 | var create_credit = 'CREATE TABLE Credit ( \ 87 | `@id` TEXT, \ 88 | `source` TEXT, \ 89 | `amount` REAL, \ 90 | `currency` VARCHAR(255) DEFAULT \''+ currency +'\', \ 91 | `destination` TEXT, \ 92 | `timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, \ 93 | `context` TEXT, \ 94 | `description` TEXT, \ 95 | `wallet` TEXT \ 96 | );'; 97 | 98 | var create_ledger = 'CREATE TABLE Ledger ( \ 99 | `source` TEXT, \ 100 | `amount` REAL, \ 101 | `currency` VARCHAR(255) DEFAULT \''+ currency +'\', \ 102 | `wallet` TEXT \ 103 | );'; 104 | 105 | var create_genesis = 'CREATE TABLE Genesis ( \ 106 | `source` TEXT, \ 107 | `amount` REAL, \ 108 | `currency` VARCHAR(255) DEFAULT \''+ currency +'\', \ 109 | `wallet` TEXT \ 110 | );' 111 | 112 | sequelize.query(create_credit).then(function(res) { 113 | }).then(function(){ 114 | sequelize.query(create_ledger); 115 | }).then(function(){ 116 | sequelize.query(create_genesis); 117 | }).then(function(){ 118 | console.log('Sucessfully created tables!'); 119 | }).then(function() { 120 | //sequelize.close(); 121 | callback(null, 'complete'); 122 | }).catch(function(err){ 123 | console.log('Failed to create tables.', err); 124 | //sequelize.close(); 125 | callback(err); 126 | }); 127 | } 128 | 129 | /** 130 | * get credit 131 | * @param {Object} credit the web credit 132 | * @param {Object} sequelize the DB connection 133 | * @param {Object} config the config 134 | * @param {Object} callback callback 135 | * @return {Object} the web credit if exists 136 | */ 137 | function getCredit(credit, sequelize, config, callback) { 138 | 139 | if (!config.wallet) { 140 | config.wallet = null; 141 | } 142 | 143 | var coinbase = 'https://w3id.org/cc#coinbase'; 144 | var currency = config.currency || 'https://w3id.org/cc#bit'; 145 | var wallet = config.wallet; 146 | 147 | var sql = 'Select * from Credit where description = :description and wallet = :wallet and currency = :currency ;'; 148 | 149 | sequelize.query(sql, { replacements: { currency: currency, wallet: config.wallet, description: credit["https://w3id.org/cc#description"] } }).then(function(res) { 150 | return res; 151 | }).catch(function(err){ 152 | console.log('Not found.', err); 153 | callback(err); 154 | }).then(function(res) { 155 | if (res[0][0]) { 156 | console.log(res[0][0]); 157 | //sequelize.close(); 158 | callback(null, res[0][0]); 159 | } 160 | }); 161 | } 162 | 163 | 164 | /** 165 | * createDB function 166 | * @param {Object} config config 167 | * @param {Object} callback callback 168 | */ 169 | function createDB(config, callback) { 170 | // vars 171 | var sequelize; 172 | 173 | // run main 174 | sequelize = setupDB(config); 175 | createTables(sequelize, config, callback); 176 | } 177 | 178 | /** 179 | * get balance 180 | * @param {String} source the source 181 | * @param {Object} sequelize sequelize object 182 | * @param {Object} config config 183 | * @param {Function} callback callback 184 | */ 185 | function getBalance(source, sequelize, config, callback) { 186 | 187 | if (!config.wallet) { 188 | config.wallet = null; 189 | } 190 | 191 | var coinbase = 'https://w3id.org/cc#coinbase'; 192 | var currency = config.currency || 'https://w3id.org/cc#bit'; 193 | var wallet = config.wallet; 194 | var initial = 1000000; 195 | 196 | 197 | var balanceSql = 'Select sum(amount) amount from Ledger where source = :source and wallet = :wallet and currency = :currency ;'; 198 | 199 | sequelize.query(balanceSql, { replacements: { currency: currency, wallet: config.wallet, source: source } }).then(function(res) { 200 | return res; 201 | }).catch(function(err){ 202 | console.log('Balance Failed.', err); 203 | callback(err); 204 | }).then(function(res) { 205 | if (res[0][0]) { 206 | //console.log(res[0][0].amount); 207 | //sequelize.close(); 208 | callback(null, res[0][0].amount); 209 | } 210 | }); 211 | } 212 | 213 | /** 214 | * genesis initialization 215 | * @param {Object} config config 216 | * @param {Function} callback callback 217 | */ 218 | function genesisInit(sequelize, config, callback) { 219 | 220 | if (!config.wallet) { 221 | config.wallet = null; 222 | } 223 | 224 | var coinbase = 'https://w3id.org/cc#coinbase'; 225 | var currency = config.currency || 'https://w3id.org/cc#bit'; 226 | var wallet = config.wallet; 227 | var initial = 1000000; 228 | 229 | var coinbaseSql = 'Insert into Ledger values ( \''+ coinbase +'\', '+ initial +', \''+ currency +'\', :wallet );'; 230 | var genesisSql = 'Insert into Genesis values ( \''+ coinbase +'\', '+ initial +', \''+ currency +'\', :wallet );'; 231 | 232 | sequelize.query(coinbaseSql, { replacements: { wallet: config.wallet } }).then(function(res) { 233 | }).then(function(){ 234 | sequelize.query(genesisSql, { replacements: { wallet: config.wallet } }); 235 | }).then(function(){ 236 | console.log('Genesis successful!'); 237 | }).catch(function(err){ 238 | console.log('Genesis Failed.', err); 239 | callback(err); 240 | }).then(function() { 241 | callback(null, 'complete'); 242 | //sequelize.close(); 243 | }); 244 | } 245 | 246 | /** 247 | * genesis 248 | * @param {Object} config config 249 | * @param {Function} callback callback 250 | */ 251 | function genesis(sequelize, config, callback) { 252 | genesisInit(sequelize, config, callback); 253 | } 254 | 255 | /** 256 | * get balance 257 | * @param {String} source the source 258 | * @param {Object} sequelize sequelize object 259 | * @param {Object} config config 260 | * @param {Function} callback callback 261 | */ 262 | function balance(source, config, callback) { 263 | // vars 264 | var sequelize; 265 | 266 | // run main 267 | sequelize = setupDB(config); 268 | var res = getBalance(source, sequelize, config, callback); 269 | } 270 | 271 | 272 | /** 273 | * get balance 274 | * @param {String} source the source 275 | * @param {Object} sequelize sequelize object 276 | * @param {Object} config config 277 | * @param {Function} callback callback 278 | */ 279 | function getReputation(source, sequelize, config, callback) { 280 | 281 | if (!config.wallet) { 282 | config.wallet = null; 283 | } 284 | 285 | var coinbase = 'https://w3id.org/cc#coinbase'; 286 | var currency = config.currency || 'https://w3id.org/cc#bit'; 287 | var wallet = config.wallet; 288 | var initial = 1000000; 289 | 290 | 291 | var coinbaseSql = 'Select sum(amount) amount from Credit where destination = :source and wallet = :wallet and currency = :currency ;'; 292 | 293 | sequelize.query(coinbaseSql, { replacements: { currency: currency, wallet: wallet, source: source } }).then(function(res) { 294 | return res; 295 | }).catch(function(err){ 296 | console.log('Reputation Failed.', err); 297 | callback(err); 298 | }).then(function(res) { 299 | if (res[0][0]) { 300 | callback(null, res[0][0].amount); 301 | //sequelize.close(); 302 | } 303 | }); 304 | 305 | } 306 | 307 | /** 308 | * Today's credits 309 | * @param {Object} credit a web credit 310 | * @param {Object} sequelize db connection 311 | * @param {Object} config config 312 | * @param {Function} callback callback 313 | */ 314 | function today(source, sequelize, config, callback) { 315 | 316 | if (!config.wallet) { 317 | config.wallet = null; 318 | } 319 | 320 | var coinbase = 'https://w3id.org/cc#coinbase'; 321 | var currency = config.currency || 'https://w3id.org/cc#bit'; 322 | var wallet = config.wallet; 323 | var initial = 1000000; 324 | 325 | var coinbaseSql = 'Select sum(amount) amount from Credit where destination = :source and wallet = :wallet and DATE(`timestamp`) = CURDATE() and currency = :currency ;'; 326 | 327 | sequelize.query(coinbaseSql, { replacements: { currency: currency, wallet: config.wallet, source: source } }).then(function(res) { 328 | return res; 329 | }).catch(function(err){ 330 | console.log('Today Failed.', err); 331 | callback(err); 332 | }).then(function(res) { 333 | if (res[0][0]) { 334 | console.log(res[0][0].amount); 335 | callback(null, res[0][0].amount); 336 | //sequelize.close(); 337 | } 338 | }); 339 | } 340 | 341 | /** 342 | * reputation function 343 | * @param {Object} config [description] 344 | */ 345 | function reputation(source, config) { 346 | // vars 347 | var sequelize; 348 | 349 | // run main 350 | sequelize = setupDB(config); 351 | var res = getReputation(source, sequelize, config); 352 | } 353 | 354 | /** 355 | * Insert into webcredits 356 | * @param {Object} credit a web credit 357 | * @param {Object} sequelize db connection 358 | * @param {Object} config config 359 | * @param {Function} callback callback 360 | */ 361 | function insert(credit, sequelize, config, callback) { 362 | 363 | if (!config.wallet) { 364 | config.wallet = null; 365 | } 366 | 367 | var coinbase = 'https://w3id.org/cc#coinbase'; 368 | var currency = config.currency || 'https://w3id.org/cc#bit'; 369 | var wallet = config.wallet; 370 | var initial = 1000000; 371 | 372 | 373 | // main 374 | console.log('source : ' + credit["https://w3id.org/cc#source"]); 375 | console.log('amount : ' + credit["https://w3id.org/cc#amount"]); 376 | console.log('unit : ' + credit["https://w3id.org/cc#currency"]); 377 | console.log('destination : ' + credit["https://w3id.org/cc#destination"]); 378 | console.log('description : ' + credit["https://w3id.org/cc#description"]); 379 | console.log('timestamp : ' + credit["https://w3id.org/cc#timestamp"]); 380 | console.log('wallet : ' + config.wallet); 381 | 382 | 383 | credit["https://w3id.org/cc#description"] = credit["https://w3id.org/cc#description"] || null; 384 | credit["https://w3id.org/cc#timestamp"] = credit["https://w3id.org/cc#timestamp"] || null; 385 | credit["https://w3id.org/cc#context"] = credit["https://w3id.org/cc#context"] || null; 386 | config.wallet = config.wallet || null; 387 | 388 | var existsSql = "SELECT * FROM Credit where source = '"+ credit["https://w3id.org/cc#source"] + "' and destination = " + ":destination" + " and amount = " + credit["https://w3id.org/cc#amount"]; 389 | existsSql += " and description = :description "; 390 | existsSql += " and timestamp = :timestamp "; 391 | existsSql += " and wallet = :wallet "; 392 | existsSql += " and context = :context "; 393 | 394 | console.log(existsSql); 395 | 396 | sequelize.query(existsSql, { replacements: { description: credit["https://w3id.org/cc#description"], 397 | timestamp: credit["https://w3id.org/cc#timestamp"], "destination" : credit["https://w3id.org/cc#destination"], wallet: config.wallet, "context": credit["https://w3id.org/cc#context"] } }).then(function(res) { 398 | console.log('checking if row exists'); 399 | console.log(res); 400 | if (res[0][0]) { 401 | console.log('row exists'); 402 | throw ('row exists'); 403 | } else { 404 | console.log('row does not exist'); 405 | console.log('Getting balance'); 406 | var balanceSql = "SELECT * FROM Ledger where source = '" + credit["https://w3id.org/cc#source"] + "' and wallet = :wallet "; 407 | 408 | return sequelize.query(balanceSql, { replacements: { wallet: config.wallet } }); 409 | } 410 | }).then(function(res){ 411 | if (res[0][0] && res[0][0].amount) { 412 | console.log('balance is ' + res[0][0].amount); 413 | if (res[0][0].amount >= credit["https://w3id.org/cc#amount"]) { 414 | console.log('funds available'); 415 | 416 | 417 | if (credit["https://w3id.org/cc#timestamp"]) { 418 | credit["https://w3id.org/cc#timestamp"] = credit["https://w3id.org/cc#timestamp"].replace(' ', 'T'); 419 | if (credit["https://w3id.org/cc#timestamp"].charAt(credit["https://w3id.org/cc#timestamp"].length-1) != 'Z') { 420 | credit["https://w3id.org/cc#timestamp"] += 'Z'; 421 | } 422 | } else { 423 | credit["https://w3id.org/cc#timestamp"] = new Date().toISOString(); 424 | } 425 | 426 | 427 | var doc = { 428 | "https://w3id.org/cc#timestamp": { "@value" : credit["https://w3id.org/cc#timestamp"], "@type" : "http://www.w3.org/2001/XMLSchema#dateTime" } , 429 | "https://w3id.org/cc#source": { "@id": credit["https://w3id.org/cc#source"] }, 430 | "https://w3id.org/cc#amount": { "@value" : credit["https://w3id.org/cc#amount"], "@type" : "http://www.w3.org/2001/XMLSchema#decimal" } , 431 | "https://w3id.org/cc#destination": { "@id": credit["https://w3id.org/cc#destination"] }, 432 | "https://w3id.org/cc#currency": { "@id": credit["https://w3id.org/cc#currency"] }, 433 | "@type": "https://w3id.org/cc#Credit" 434 | }; 435 | console.log(doc); 436 | return promises.normalize(doc, {format: 'application/nquads'}); 437 | 438 | } else { 439 | throw ('not enough funds'); 440 | } 441 | } else { 442 | throw ('could not find balance'); 443 | } 444 | }).then(function(doc){ 445 | console.log('Sucessfully normalized doc to json ld!'); 446 | var hash = crypto.createHash('sha256').update(doc).digest('base64'); 447 | console.log(hash); 448 | 449 | var id = 'ni:///sha-256;' + new Buffer(hash).toString('base64').replace('+', '-').replace('/', '_').replace('=', ''); 450 | credit['@id'] = id; 451 | console.log(credit); 452 | 453 | 454 | 455 | var insertSql = "INSERT INTO Credit(\`@id\`, `source`, `destination`, `amount`, `timestamp`, `currency`"; 456 | if (credit["https://w3id.org/cc#description"]) insertSql += ", `description`"; 457 | if (credit["https://w3id.org/cc#context"]) insertSql += ", `context`"; 458 | if (config.wallet) insertSql += ", `wallet`"; 459 | insertSql += ") values ( '" + credit['@id'] + "', '"+ credit["https://w3id.org/cc#source"] + "' , " + ":destination" + " , " + credit["https://w3id.org/cc#amount"]; 460 | insertSql += " , '" + dateFormat(credit["https://w3id.org/cc#timestamp"], 'yyyy-mm-dd hh:MM:ss.l') + "'" + " , '" + credit["https://w3id.org/cc#currency"] + "'"; 461 | if (credit["https://w3id.org/cc#description"]) insertSql+= " , '" + credit["https://w3id.org/cc#description"] + "'"; 462 | if (credit["https://w3id.org/cc#context"]) insertSql+= " , '" + credit["https://w3id.org/cc#context"] + "'"; 463 | if (config.wallet) insertSql+= " , '" + config.wallet + "'"; 464 | insertSql += " )"; 465 | 466 | console.log(insertSql); 467 | 468 | return sequelize.query(insertSql, {replacements: { "destination" : credit["https://w3id.org/cc#destination"] } }); 469 | 470 | }).then(function(res){ 471 | console.log('decrementing source'); 472 | var decrementSql = "UPDATE Ledger set amount = amount - " + credit["https://w3id.org/cc#amount"] + " where source = '"+ credit["https://w3id.org/cc#source"] + "' and wallet = :wallet"; 473 | return sequelize.query(decrementSql, { replacements: { wallet: config.wallet } }); 474 | 475 | }).then(function(res){ 476 | console.log('incrementing or creating destination'); 477 | var checkSql = "SELECT * from Ledger where `source` = :destination and wallet = :wallet"; 478 | return sequelize.query(checkSql, { replacements: { wallet: config.wallet , "destination" : credit["https://w3id.org/cc#destination"] }}); 479 | }).then(function(res){ 480 | var incrementSql; 481 | if (res[0][0] && !isNaN(res[0][0].amount)) { 482 | if (config.wallet) { 483 | incrementSql = "UPDATE Ledger set `amount` = `amount` + " + credit["https://w3id.org/cc#amount"] + " where `source` = :destination and wallet = '"+ config.wallet +"'"; 484 | } else { 485 | incrementSql = "UPDATE Ledger set `amount` = `amount` + " + credit["https://w3id.org/cc#amount"] + " where `source` = `source` = :destination"; 486 | } 487 | } else { 488 | if (config.wallet) { 489 | incrementSql = "INSERT into Ledger (`source`, `amount`, `wallet`) values (:destination, "+credit["https://w3id.org/cc#amount"] +", '"+ config.wallet +"')"; 490 | } else { 491 | incrementSql = "INSERT into Ledger (`source`, `amount`) values (:destination, "+credit["https://w3id.org/cc#amount"] +")"; 492 | } 493 | } 494 | console.log(incrementSql); 495 | return sequelize.query(incrementSql, {replacements: { "destination" : credit["https://w3id.org/cc#destination"] }} ); 496 | 497 | }).then(function() { 498 | console.log('Complete'); 499 | //sequelize.close(); 500 | callback(null, 'Complete'); 501 | // hook 502 | 503 | }).catch(function(err){ 504 | console.log('Failed to insert credit.', err); 505 | callback(err); 506 | }); 507 | } 508 | --------------------------------------------------------------------------------