/koa-ilp

Koa middleware to create paid HTTP endpoints in seconds

Primary LanguageJavaScript

Koa ILP

Koa middleware to charge for requests with ILP

Koa-ILP uses HTTP-ILP to charge for requests.

// first, we start with an ordinary Koa app
const Koa = require('koa')
const router = require('koa-router')()
const parser = require('koa-bodyparser')()
const app = new Koa()

// next, we import koa-ilp and automagically get a plugin using ilp-plugin. We
// construct our ilp middleware from the plugin.
const Ilp = require('koa-ilp')
const plugin = require('ilp-plugin')()
const ilp = new Ilp({ plugin })

// ENDPOINT 1: Hello

// Our first endpoint, '/hello', has a price of 1000 (in base units of our ledger.
// by default, ilp-plugin connects us to the XRP testnet so these are XRP drops).
// If we were connected to a dollar ledger with a scale of 9, these would be 1000
// nano-dollars.
const price = 1000

// We use ilp.options to create an options endpoint which returns payment instructions,
// enabling clients to fund a token on our server using interledger.
router.options('/hello', ilp.options({ price }))

// Next we use ilp.paid to create an actual paid endpoint. The route's code won't be
// run unless the client has paid 1000 XRP drops to us first.
router.post('/hello', ilp.paid({ price }), async ctx => {
  ctx.body = { message: 'Hello World!' }
})

// ENDPOINT 2: Random

// Our next endpoint, '/random', charges for random bytes. The price depends on how
// many bytes are requested, so the price is a function instead of an integer.
const PRICE_PER_RANDOM_BYTE = 20
const priceFunction = ctx => {
  return (+ctx.query.size || 16) * PRICE_PER_RANDOM_BYTE
}

// ilp.paid and ilp.options take a priceFunction in place of a static price. Otherwise
// this code works the same as the /hello endpoint.
router.options('/random', ilp.options({ price: priceFunction }))
router.get('/random', ilp.paid({ price: priceFunction }), async ctx => {
  ctx.body = crypto.randomBytes(ctx.query.size)
})

// ENDPOINT 3: File

// Our next endpoint, '/file/:file', charges a user to read a file off our machine.
// This depends on file size too, but reading a file is an async operation. We can
// define an async price function this time.
const fs = require('fs-extra')
const PRICE_PER_BYTE = 10
const asyncPriceFunction = async ctx => {
  const stats = await fs.stat(ctx.params.file)
  return stats.size * PRICE_PER_BYTE
}

// The async price function is passed in in place of the price. Otherwise this code
// functions the same as /hello and /random.
router.options('/file/:file', ilp.options({ price: asyncPriceFunction }))
router.get('/file/:file', ilp.paid({ price: asyncPriceFunction }), async ctx => {
  const file = await fs.readFile(ctx.params.file)
  ctx.body = file.toString('utf8')
})

app
  .use(parser)
  .use(router.routes())
  .use(router.allowedMethods())
  .listen(8080)