Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add streaming payments tutorial #2

Merged
merged 6 commits into from
Oct 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const IlpPacket = require('ilp-packet')
const Plugin = require('ilp-plugin-xrp-escrow')
const crypto = require('crypto')
const fetch = require('node-fetch')
const uuid = require('uuid/v4')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

const plugin = new Plugin({
secret: 'sndb5JDdyWiHZia9zv44zSr2itRy1',
account: 'rGtqDAJNTDMLaNNfq1RVYgPT8onFMj19Aj',
server: 'wss://s.altnet.rippletest.net:51233',
prefix: 'test.crypto.xrp.'
})

let counter = 0

function sendTransfer (obj) {
obj.id = uuid()
obj.from = plugin.getAccount()
// to
obj.ledger = plugin.getInfo().prefix
// amount
// executionCondition
obj.expiresAt = new Date(new Date().getTime() + 1000000).toISOString()
return plugin.sendTransfer(obj).then(function () {
return obj.id
})
}

plugin.connect().then(function () {
return fetch('http://localhost:8000/')
}).then(function (inRes) {
inRes.body.pipe(process.stdout)
const payHeaderParts = inRes.headers.get('Pay').split(' ')
console.log(payHeaderParts)
// e.g. Pay: 1 test.crypto.xrp.asdfaqefq3f.26wrgevaew SkTcFTZCBKgP6A6QOUVcwWCCgYIP4rJPHlIzreavHdU
setInterval(function () {
const ilpPacket = IlpPacket.serializeIlpPayment({
account: payHeaderParts[1] + '.' + (++counter),
amount: '1',
data: ''
})
const fulfillmentGenerator = hmac(Buffer.from(payHeaderParts[2], 'base64'), 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
sendTransfer({
to: payHeaderParts[1],
amount: '1',
executionCondition: base64(condition),
ilp: base64(ilpPacket)
}).then(function () {
// console.log('transfer sent')
}).catch(function (err) {
console.error(err.message)
})
}, 500)
})
58 changes: 58 additions & 0 deletions client2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const IlpPacket = require('ilp-packet')
const Plugin = require('ilp-plugin-payment-channel-framework')
const crypto = require('crypto')
const fetch = require('node-fetch')
const uuid = require('uuid/v4')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

const plugin = new Plugin({
server: 'btp+ws://:@localhost:9000/'
})

let counter = 0

function sendTransfer (obj) {
obj.id = uuid()
obj.from = plugin.getAccount()
// to
obj.ledger = plugin.getInfo().prefix
// amount
// executionCondition
obj.expiresAt = new Date(new Date().getTime() + 1000000).toISOString()
// console.log('calling sendTransfer!', obj)
return plugin.sendTransfer(obj).then(function () {
return obj.id
})
}

plugin.connect().then(function () {
console.log('plugin connected')
return fetch('http://localhost:8000/')
}).then(function (inRes) {
inRes.body.pipe(process.stdout)
const payHeaderParts = inRes.headers.get('Pay').split(' ')
console.log(payHeaderParts)
// e.g. Pay: 1 test.crypto.xrp.asdfaqefq3f.26wrgevaew SkTcFTZCBKgP6A6QOUVcwWCCgYIP4rJPHlIzreavHdU
setInterval(function () {
const ilpPacket = IlpPacket.serializeIlpPayment({
account: payHeaderParts[1] + '.' + (++counter),
amount: '1',
data: ''
})
const fulfillmentGenerator = hmac(Buffer.from(payHeaderParts[2], 'base64'), 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
sendTransfer({
to: payHeaderParts[1],
amount: '1',
executionCondition: base64(condition),
ilp: base64(ilpPacket)
}).then(function () {
// console.log('transfer sent')
}).catch(function (err) {
console.error(err.message)
})
}, 1)
})
4 changes: 4 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ first time. The main programming language used is JavaScript.
## Tutorials

* [The Letter Shop](./letter-shop)
* [Streaming Payments](./streaming-payments)
* [Trustlines](./trustlines)

## Versioning

Expand All @@ -20,6 +22,8 @@ Interledger Requests For Comments (IL-RFCs):
* [IL-RFC-15, draft 1](https://interledger.org/rfcs/0015-ilp-addresses/draft-1.html): Interledger Addresses
* [IL-RFC-22, draft 1](https://interledger.org/rfcs/0022-hashed-timelock-agreements/draft-1.html): Hashed Time Lock Agreements
* [IL-RFC-19, draft 1](https://interledger.org/rfcs/0019-glossary/draft-1.html): Glossary
* [IL-RFC-16, draft 3](https://interledger.org/rfcs/0016-pre-shared-key/draft-3.html): Pre-Shared Key (PSK)
* [IL-RFC-23, draft 2](https://interledger.org/rfcs/0023-bilateral-transfer-protocol/draft-2.html): Bilateral Transfer Protocol (BTP)

The software you will build during these tutorials will be compatible with software
written by other developers, on several levels:
Expand Down
56 changes: 56 additions & 0 deletions shop2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const IlpPacket = require('ilp-packet')
const http = require('http')
const crypto = require('crypto')
const Plugin = require('ilp-plugin-xrp-escrow')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (preimage) { return crypto.createHash('sha256').update(preimage).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

let users = {}

const plugin = new Plugin({
secret: 'ssGjGT4sz4rp2xahcDj87P71rTYXo',
account: 'rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW',
server: 'wss://s.altnet.rippletest.net:51233',
prefix: 'test.crypto.xrp.'
})

plugin.connect().then(function () {
plugin.on('incoming_prepare', function (transfer) {
const ilpPacket = Buffer.from(transfer.ilp, 'base64')
const ilpPacketContents = IlpPacket.deserializeIlpPayment(ilpPacket)
const parts = ilpPacketContents.account.split('.')
// 0: test, 1: crypto, 2: xrp, 3: rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW, 4: userId, 5: paymentId
if (parts.length < 6 || typeof users[parts[4]] === 'undefined' || ilpPacketContents.amount !== transfer.amount) {
plugin.rejectIncomingTransfer(transfer.id, {}).catch(function () {})
} else {
const { secret, res } = users[parts[4]]
const fulfillmentGenerator = hmac(secret, 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
if (transfer.executionCondition === base64(condition)) {
plugin.fulfillCondition(transfer.id, base64(fulfillment)).then(function () {
const letter = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('')[(Math.floor(Math.random() * 26))]
res.write(letter)
}).catch(function (err) {
console.error(err.message)
})
} else {
console.log('no match!', { secret, fulfillment, condition, transfer })
}
}
})

http.createServer(function (req, res) {
const secret = crypto.randomBytes(32)
const user = base64(crypto.randomBytes(8))
users[user] = { secret, res }
console.log('user! writing head', user)
res.writeHead(200, {
'Pay': [ 1, plugin.getAccount() + '.' + user, base64(secret) ].join(' ')
})
// Flush the headers in a first TCP packet:
res.socket.write(res._header)
res._headerSent = true
}).listen(8000)
})
69 changes: 69 additions & 0 deletions shop3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const IlpPacket = require('ilp-packet')
const http = require('http')
const crypto = require('crypto')
const Plugin = require('ilp-plugin-payment-channel-framework')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

let users = {}
const store = {}
const plugin = new Plugin({
listener: {
port: 9000
},
incomingSecret: '',
maxBalance: '1000000000',
prefix: 'example.letter-shop.mytrustline.',
info: {
currencyScale: 9,
currencyCode: 'XRP',
prefix: 'example.letter-shop.mytrustline.',
connectors: []
},
_store: { // in-memory store for demo purposes
get: (k) => store[k],
put: (k, v) => { store[k] = v },
del: (k) => delete store[k]
}
})

plugin.connect().then(function () {
plugin.on('incoming_prepare', function (transfer) {
const ilpPacket = Buffer.from(transfer.ilp, 'base64')
const ilpPacketContents = IlpPacket.deserializeIlpPayment(ilpPacket)
const parts = ilpPacketContents.account.split('.')
// 0: test, 1: crypto, 2: xrp, 3: rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW, 4: userId, 5: paymentId
if (parts.length < 6 || typeof users[parts[4]] === 'undefined' || ilpPacketContents.amount !== transfer.amount) {
plugin.rejectIncomingTransfer(transfer.id, {}).catch(function () {})
} else {
const { secret, res } = users[parts[4]]
const fulfillmentGenerator = hmac(secret, 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
if (transfer.executionCondition === base64(condition)) {
plugin.fulfillCondition(transfer.id, base64(fulfillment)).then(function () {
const letter = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('')[(Math.floor(Math.random() * 26))]
res.write(letter)
}).catch(function (err) {
console.error(err.message)
})
} else {
console.log('no match!', { secret, fulfillment, condition, transfer })
}
}
})

http.createServer(function (req, res) {
const secret = crypto.randomBytes(32)
const user = base64(crypto.randomBytes(8))
users[user] = { secret, res }
console.log('user! writing head', user)
res.writeHead(200, {
'Pay': [ 1, plugin.getAccount() + '.' + user, base64(secret) ].join(' ')
})
// Flush the headers in a first TCP packet:
res.socket.write(res._header)
res._headerSent = true
}).listen(8000)
})
Loading