-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathreader.js
More file actions
243 lines (213 loc) · 7.61 KB
/
Copy pathreader.js
File metadata and controls
243 lines (213 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// ----------------------------------------------------------------------------
// reader.js
// Enuma Sprites PoC
//
// Copyright (c) 2018 Enuma Technologies Limited.
// https://www.enuma.io/
// ----------------------------------------------------------------------------
const {curry, assoc, assocPath, last, isNil, identity} = require('ramda')
const {threadP} = require('sprites-channels/fp.js')
const assert = require('assert')
const {inspect} = require('util')
const Web3Eth = require('web3-eth')
const low = require('lowdb')
const LowMem = require('lowdb/adapters/Memory')
const Paywall = require('./paywall.js')
const Sprites = require('sprites-channels')
const ChannelState = require('sprites-channels/channel-state.js')
const Reader = {
make(opts={}) {
return {
db: low(new LowMem()),
sprites: Sprites.make(),
...opts
}
},
/**
* Initialize the sprites client based on the configuration
* returned by Paywall.config()
* */
withPaywall: curry(async (config, rdr) => {
const {publisher, preimageManager, reg, token} = config
const {sprites} = rdr
const readerWithPaywall = {
...rdr,
publisher,
sprites: Sprites.withWeb3Contracts({
...sprites, preimageManager, reg, token
})
}
const chId = last(await sprites.offChainReg.with(publisher))
const maybeWithChannel = isNil(chId) ? identity : Paywall.channel(chId)
return maybeWithChannel(readerWithPaywall)
}),
/**
* Checks if the configuration contains addresses for
* all the required contracts and those addresses contain
* some bytecode, otherwise calls to such contracts might
* just silently succeed.
* */
validatePaywall: curry(async (publisherConfig, rdr) => {
const {web3Provider} = rdr.sprites
const eth = new Web3Eth(web3Provider)
const hasCode = async (contractName) => {
if (!(contractName in publisherConfig))
throw new Error(
`No address for the "${contractName}" contract in config:\n` +
inspect(publisherConfig))
const addr = publisherConfig[contractName]
if (await eth.getCode(addr) === '0x')
throw new Error(
`No code found for contract "${contractName}"` +
` at address ${addr}`)
}
return Promise.all(['reg', 'preimageManager', 'token'].map(hasCode))
}),
/**
* Retrieve the receipts for the already bought articles
*/
library: async (rdr) =>
rdr.db.value(),
saveReceipt: curry(async (receipt, rdr) => {
await rdr.db.set(receipt.articleId, receipt).write()
return rdr
}),
/**
* Approve the Sprites channel registry to deposit tokens up to
* the specified `amount` into an open payment channel with
* any number of publishers.
*
* Requires a signature.
* */
approve: curry(async (amount, rdr) => {
const {sprites} = rdr
return {
...rdr,
sprites: await Sprites.approve(amount, sprites)
}
}),
/**
* Stake some tokens with a publisher by opening a payment
* channel and depositing `amount` tokens into it.
*
* Requires a signature.
* */
firstDeposit: curry(async (amount, rdr) => {
const {publisher, sprites} = rdr
return {
...rdr,
sprites: await threadP(sprites,
Sprites.createWithDeposit(publisher, amount),
Sprites.channelState,
Sprites.save)
}
}),
deposit: curry(async (amount, rdr) => {
const {sprites} = rdr
return {
...rdr,
sprites: await threadP(sprites,
Sprites.deposit(amount),
Sprites.channelState,
Sprites.save)
}
}),
/**
* Order an article.
*
* Paywall should return a signed `{invoice}` in exchange.
* */
order: curry((articleId, rdr) => {
const {sprites: {chId}} = rdr
return {
...rdr,
order: {
articleId, chId
}
}
}),
/**
* Pay for an article invoice by signing it.
*
* Returns a `{payment}` suitable for `Paywall.processPayment`.
* */
pay: curry(async (invoice, rdr) => {
const {xforms, chId, round, sigs} = invoice
const [_buyerSig, sellerSig] = sigs
assert(sellerSig,
`Invoice should have a signature:\n` + inspect(invoice))
const sprite = await threadP(rdr.sprites,
assoc('chId', chId),
Sprites.channelState,
Sprites.transition(xforms),
Sprites.withSigs(sigs))
assert(sprite.channel.round === round,
`Invoice round doesn't match latest channel state:\n` +
inspect(invoice) + '\n' +
inspect(sprite.channel))
assert(ChannelState.checkAvailSigs(sprite.channel),
`Invalid signatures:\n`
+ inspect(invoice) + '\n' +
inspect(sprite.channel))
const signedSprite = await Sprites.sign(sprite)
const payment = {...invoice, sigs: signedSprite.channel.sigs}
return {...rdr, sprites: signedSprite, payment}
}),
processReceipt: curry(async (paymentReceipt, rdr) => {
const {xforms, chId, round, sigs} = paymentReceipt.payment
const [buyerSig, sellerSig] = sigs
assert(sellerSig,
`Receipt should have a seller signature:\n`
+ inspect(paymentReceipt))
assert(buyerSig,
`Receipt should have a buyer signature:\n`
+ inspect(paymentReceipt))
const sprites = await threadP(rdr.sprites,
assoc('chId', chId),
Sprites.channelState,
Sprites.transition(xforms),
Sprites.withSigs(sigs))
assert(sprites.channel.round === round,
`Receipt round doesn't match latest channel state:\n` +
inspect(paymentReceipt) + '\n' +
inspect(sprites.channel))
assert(ChannelState.checkAvailSigs(sprites.channel),
`Invalid signatures:\n`
+ inspect(paymentReceipt) + '\n' +
inspect(sprites.channel))
await Sprites.save(sprites)
const {payment, ...receipt} = paymentReceipt
await Reader.saveReceipt(receipt, rdr)
return {...rdr, sprites, receipt}
}),
/**
* Request withdrawal of the current off-chain channel balance.
*
* @param reader
* @return {{withdrawalRequest}}
* */
requestWithdraw: curry(async (rdr) => {
const sprites0 = await Sprites.channelState(rdr.sprites)
const ownIdx = Sprites.ownIdx(sprites0)
const balance = ChannelState.balance(ownIdx, sprites0.channel)
const xforms = [['withdraw', ownIdx, balance]]
const sprites = await threadP(
sprites0,
Sprites.transition(xforms),
Sprites.sign)
const {chId, channel: {round, sigs}} = sprites
const withdrawalRequest = {chId, xforms, round, sigs}
return {...rdr, sprites, withdrawalRequest}
}),
withdraw: curry(async (withdrawal, rdr) => {
const {chId, round, xforms, sigs} = withdrawal
const rdr0 = await Paywall.channel(chId, rdr)
const sprites = await threadP(rdr0.sprites,
Sprites.transition(xforms),
Sprites.withSigs(sigs),
Sprites.save,
Sprites.updateAndWithdraw)
return {...rdr, sprites}
})
}
module.exports = Reader