Content Paywall
Overview
Pay-Per-View Content
import { Transaction, PrivateKey, P2PKH, Script, OP, Hash } from '@bsv/sdk'
/**
* Pay-Per-View Content Manager
*
* Manage single payment access to content
*/
class PayPerViewManager {
private contentPublisher: PrivateKey
private accessTokens: Map<string, AccessToken> = new Map()
constructor(publisherKey: PrivateKey) {
this.contentPublisher = publisherKey
}
/**
* Purchase content access
*/
async purchaseAccess(params: {
userKey: PrivateKey
contentId: string
price: number
utxo: {
txid: string
vout: number
satoshis: number
script: Script
}
}): Promise<{
tx: Transaction
accessToken: AccessToken
}> {
try {
console.log('Processing content purchase')
console.log('Content ID:', params.contentId)
console.log('Price:', params.price, 'satoshis')
const tx = new Transaction()
// Add user's payment input
tx.addInput({
sourceTXID: params.utxo.txid,
sourceOutputIndex: params.utxo.vout,
unlockingScriptTemplate: new P2PKH().unlock(params.userKey),
sequence: 0xffffffff
})
// Payment output to publisher
tx.addOutput({
satoshis: params.price,
lockingScript: new P2PKH().lock(this.contentPublisher.toPublicKey().toHash())
})
// OP_RETURN with access proof
const accessProof = this.createAccessProof(
params.contentId,
params.userKey.toPublicKey().toAddress()
)
const opReturnScript = new Script()
opReturnScript.writeOpCode(OP.OP_FALSE)
opReturnScript.writeOpCode(OP.OP_RETURN)
opReturnScript.writeBin(Buffer.from('CONTENT_ACCESS', 'utf8'))
opReturnScript.writeBin(Buffer.from(params.contentId, 'utf8'))
opReturnScript.writeBin(Buffer.from(accessProof, 'hex'))
tx.addOutput({
satoshis: 0,
lockingScript: opReturnScript
})
// Change output
const fee = 500
const change = params.utxo.satoshis - params.price - fee
if (change >= 546) {
tx.addOutput({
satoshis: change,
lockingScript: new P2PKH().lock(params.userKey.toPublicKey().toHash())
})
}
await tx.sign()
// Generate access token
const accessToken: AccessToken = {
tokenId: this.generateTokenId(),
contentId: params.contentId,
userAddress: params.userKey.toPublicKey().toAddress(),
paymentTxid: tx.id('hex'),
accessProof,
createdAt: Date.now(),
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
status: 'active'
}
this.accessTokens.set(accessToken.tokenId, accessToken)
console.log('Access purchased successfully')
console.log('Access token:', accessToken.tokenId)
return { tx, accessToken }
} catch (error) {
throw new Error(`Purchase failed: ${error.message}`)
}
}
/**
* Verify access token
*/
verifyAccess(tokenId: string, contentId: string): AccessVerification {
const token = this.accessTokens.get(tokenId)
if (!token) {
return {
valid: false,
reason: 'Token not found'
}
}
if (token.contentId !== contentId) {
return {
valid: false,
reason: 'Token not valid for this content'
}
}
if (token.status !== 'active') {
return {
valid: false,
reason: `Token status: ${token.status}`
}
}
if (Date.now() > token.expiresAt) {
token.status = 'expired'
return {
valid: false,
reason: 'Token expired'
}
}
return {
valid: true,
token
}
}
/**
* Revoke access
*/
revokeAccess(tokenId: string): void {
const token = this.accessTokens.get(tokenId)
if (token) {
token.status = 'revoked'
console.log('Access revoked:', tokenId)
}
}
/**
* Create access proof
*/
private createAccessProof(contentId: string, userAddress: string): string {
const data = `${contentId}:${userAddress}:${Date.now()}`
return Hash.sha256(Buffer.from(data, 'utf8')).toString('hex')
}
/**
* Generate token ID
*/
private generateTokenId(): string {
return `TOKEN-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}
}
interface AccessToken {
tokenId: string
contentId: string
userAddress: string
paymentTxid: string
accessProof: string
createdAt: number
expiresAt: number
status: 'active' | 'expired' | 'revoked'
}
interface AccessVerification {
valid: boolean
token?: AccessToken
reason?: string
}
/**
* Usage Example
*/
async function payPerViewExample() {
const publisher = PrivateKey.fromRandom()
const manager = new PayPerViewManager(publisher)
const user = PrivateKey.fromRandom()
// User purchases access
const { accessToken } = await manager.purchaseAccess({
userKey: user,
contentId: 'ARTICLE-001',
price: 1000, // 1000 satoshis
utxo: {
txid: 'user-utxo...',
vout: 0,
satoshis: 10000,
script: new P2PKH().lock(user.toPublicKey().toHash())
}
})
console.log('Access token:', accessToken)
// Verify access
const verification = manager.verifyAccess(accessToken.tokenId, 'ARTICLE-001')
if (verification.valid) {
console.log('Access granted!')
// Deliver content...
} else {
console.log('Access denied:', verification.reason)
}
}Metered Content Access
Time-Limited Access
Related Examples
See Also
Last updated
