The 21 BitServ Library (two1.bitserv)

Posted by Tyler Julian

The 21 BitServ Library

The BitServ library adds a simple API for servers to create payable resources in both Flask and Django frameworks. It enables a server (also referred to as a merchant) to receive payment from a client (also referred to as a customer) for a resource. See the bitrequests section for more information on the 402 payment-resource exchange.

Payment Methods

The bitserv library provides a base set of payment\_methods that framework-specific decorators can use to accept different payment types (e.g. OnChain, Payment Channel and BitTransfer types). Payment methods should implement the following methods:

  • get_402_headers() provides a list of headers that the server returns to the client in the initial 402 PAYMENT REQUIRED response.

  • payment_headers() provides a list of headers that the client sends to the server to inform how payment is being made.

  • redeem_payment() runs payment validation and any steps necessary to complete payment processing from the client to the server.

Payment Decorators

Decorators for a framework should ideally expose a similar public interface for wrapping routes and gating resource access contingent on payment. If possible with respect to any framework's design methodology, any bitserv library should expose a payment object with a required method that defines the acceptable price. The payment.required() method should be a function wrapper that can decorate a python function. The argument price should be flexible and accept both a static int type and function type (or any callable type for that matter).

Example API Usage:

# Static Price
@payment.required(100)
def current_temperature():
    return 65

# Dynamic price
@payment.required(lambda req: req.args.get('important_var') * 100)
def current_temperature():
    return 65

Flask

The decorator for the Flask http://flask.pocoo.org/ framework acts by attaching itself to an instance of a flask app and then further injecting wallet functionality with a two1.wallet.Wallet object.

from flask import Flask
from two1.wallet import Wallet
from two1.bitserv.flask import Payment

app = Flask(__name__)
wallet = Wallet()
payment = Payment(app, wallet)

For payment channel negotiation, the decorator also adds an REST API /payment route. This namespace can be configured by changing the endpoint keyword argument during instantiation of the Payment object (e.g. payment = Payment(app, wallet, endpoint='/pay-up'))

Django

The decorator for the Django https://www.djangoproject.com/ framework is an installable django app package. It automagically will search your settings.py file for a WALLET variable and instantiate a new Payment object. This makes usage in any particular module a little simpler, but adds a few extra configuration steps.

setup.py

packages=[
      . . .
    'two1.bitserv.django'
]

settings.py

from two1.wallet import Wallet

INSTALLED_APPS = (
      . . .
   'two1.bitserv.django'
)

WALLET = Wallet()

APPEND_SLASH = False

urls.py

url(r'^payments/', include('two1.bitserv.django.urls'))

views.py

from two1.bitserv.django import payment

To finalize Django setup, be sure to run the following command (or equivalent) to make sure that the database models are properly added to your project.

python manage.py migrate

Payment Server

The PaymentServer is concerned with managing the server side of payment channels. The PaymentServer object is generic enough to be used by various communication protocols, though it is presently only implemented over HTTP REST as a core part of the bitserv library.

The PaymentServer - and to some extent, each PaymentBase method

  • relies on state being maintained by the application. It consumes the models API in order to store and retrieve channel state. The default database implementation uses the sqlite3 standard library to communicate with an SQLite database. The django-specific database implementation provides an adapter that hooks into the django ORM to allow payment methods to keep their data with the rest of the django application.

The PaymentServer also uses a custom wallet.py wrapper to provide added transaction-building functionality. You can set up a barebones payment server by passing it a two1 wallet instance

from two1.wallet import Wallet
from two1.bitserv import PaymentServer

wallet = Wallet()
payment_server = PaymentServer(wallet)

two1.bitserv: module contents

The two1.bitserv module is organized into the following submodules:

two1.bitserv.payment_methods

This module contains methods for making paid HTTP requests to 402-enabled servers.

class two1.bitserv.payment_methods.BitTransfer(wallet, verification_url=None, username=None, seller_account=None)

Bases: two1.bitserv.payment_methods.PaymentBase

Making a payment via 21 BitTransfer protocol.

account_file = '~/.two1/two1.json'
get_402_headers(price, kwargs)

Dict of headers to return in the initial 402 response.

http_402_address = 'Bitcoin-Address'
http_402_price = 'Price'
http_402_username = 'Username'
http_authorization = 'Authorization'
http_payment_data = 'Bitcoin-Transfer'
payment_headers

List of headers to use for payment processing.

redeem_payment(price, request_headers, kwargs)

Verify that the BitTransfer is valid.

  1. Check that amount sent in transfer is correct
  2. Authenticate & verify via 3rd party (21.co) server.
verification_url = 'https://api.21.co/pool/account/{}/bittransfer/'
exception two1.bitserv.payment_methods.DuplicatePaymentError

Bases: two1.bitserv.payment_methods.PaymentError

Raised when attempting to re-use a payment token to purchase a resource.

exception two1.bitserv.payment_methods.InsufficientPaymentError

Bases: two1.bitserv.payment_methods.PaymentError

Raised when the amount paid is less than the payment required.

exception two1.bitserv.payment_methods.InvalidPaymentParameterError

Bases: two1.bitserv.payment_methods.PaymentError

Raised when an incorrect or malformed payment parameter is provided.

class two1.bitserv.payment_methods.OnChain(wallet, db=None, db_dir=None)

Bases: two1.bitserv.payment_methods.PaymentBase

Making a payment on the bitcoin blockchain.

DUST_LIMIT = 3000
get_402_headers(price, kwargs)

Dict of headers to return in the initial 402 response.

http_402_address = 'Bitcoin-Address'
http_402_price = 'Price'
http_payment_data = 'Bitcoin-Transaction'
lock = <_thread.lock object>
payment_headers

List of headers to use for payment processing.

redeem_payment(price, request_headers, kwargs)

Validate the transaction and broadcast it to the blockchain.

class two1.bitserv.payment_methods.PaymentBase

Bases: object

Base class for payment methods.

get_402_headers(price, kwargs)

Derived dict of headers to return in the initial 402 response.

Parameters:price – Endpoint price in satoshis
Returns:Dict of headers that the server uses to inform the client how to remit payment for the resource. Example: {‘address’: ‘1MDxJYsp4q4P46RiigaGzrdyi3dsNWCTaR’, ‘price’: 500}
Return type:(dict)
payment_headers

Derived list of headers to use for payment processing.

Returns:List of required headers that a client should present in order to redeem a payment of this method. Example: [‘Bitcoin-Transaction’]
Return type:(list)
redeem_payment(price, request_headers, payment_headers)

Derived method for processing and validating payment.

Parameters:
  • request_headers (dict) – Headers sent by client with their request.
  • payment_headers (dict) – Required headers to verify the client’s request against.
Returns:

Whether or not the payment is valid.

Return type:

(boolean)

Raises:
should_redeem(request_headers)

Method for checking if we should use a derived payment method.

exception two1.bitserv.payment_methods.PaymentBelowDustLimitError

Bases: two1.bitserv.payment_methods.PaymentError

Raised when the paid amount is less than the bitcoin network dust limit.

class two1.bitserv.payment_methods.PaymentChannel(server, endpoint_path)

Bases: two1.bitserv.payment_methods.PaymentBase

Making a payment within a payment channel.

get_402_headers(price, kwargs)

Dict of headers to return in the initial 402 response.

http_402_micro_server = 'Bitcoin-Payment-Channel-Server'
http_402_price = 'Price'
http_payment_token = 'Bitcoin-Payment-Channel-Token'
payment_headers

List of headers to use for payment processing.

redeem_payment(price, request_headers, kwargs)

Validate the micropayment and redeem it.

exception two1.bitserv.payment_methods.PaymentError

Bases: Exception

Generic error for exceptions encountered during payment validation.

exception two1.bitserv.payment_methods.ServerError

Bases: Exception

Raised when an error is received from a remote server on a request.

exception two1.bitserv.payment_methods.TransactionBroadcastError

Bases: two1.bitserv.payment_methods.PaymentError

Raised when broadcasting a transaction to the bitcoin network fails.

two1.bitserv.payment_server

This module implements the server side of payment channels.

exception two1.bitserv.payment_server.BadTransactionError

Bases: two1.bitserv.payment_server.PaymentServerError

Raised when an incorrect or malformed transaction is provided by a client.

exception two1.bitserv.payment_server.ChannelClosedError

Bases: two1.bitserv.payment_server.PaymentServerError

Raised when attempting to access a channel that has been closed.

class two1.bitserv.payment_server.Lock

Bases: contextlib.ContextDecorator

An inter-thread lock decorator.

exception two1.bitserv.payment_server.PaymentChannelNotFoundError

Bases: two1.bitserv.payment_server.PaymentServerError

Raised when attempting to access a channel that does not exist.

class two1.bitserv.payment_server.PaymentServer(wallet, db=None, blockchain=None, zeroconf=False, sync_period=600, db_dir=None)

Bases: object

Payment channel handling.

This class handles the server-side implementation of payment channels from handshake to channel close. It also implements the ability for an API server to redeem micropayments made within the channel.

DEFAULT_TWENTYONE_BLOCKCHAIN_URL = 'https://blockchain.21.co/blockchain/bitcoin'

Default mainnet blockchain URL.

DEFAULT_TWENTYONE_TESTNET_BLOCKCHAIN_URL = 'https://blockchain.21.co/blockchain/testnet3'

Default testnet blockchain URL.

DUST_LIMIT = 3000

Minimum payment amount (dust limit) for any transaction output.

EXP_TIME_BUFFER = 259200

Buffer time before expiration (in sec) in which to broadcast payment.

MIN_EXP_TIME = 345600

Minimum expiration time (in sec) for a payment channel refund.

MIN_TX_FEE = 30000

Minimum transaction fee for payment channel deposit/payment.

PROTOCOL_VERSION = 2

Payment channel protocol version.

close(deposit_txid, deposit_txid_signature)

Close a payment channel.

Parameters:
  • deposit_txid (string) – string representation of the deposit transaction hash. This is used to look up the payment channel.
  • deposit_txid_signature (two1.bitcoin.Signature) – a signature consisting solely of the deposit_txid to verify the authenticity of the close request.
identify()

Query the payment server information.

Returns:
a key-value store that contains the merchant’s public key
and other custom config.
Return type:(dict)
lock = <two1.bitserv.payment_server.Lock object>

Thread and process lock for database access.

open(deposit_tx, redeem_script)

Open a payment channel.

Parameters:
  • deposit_tx (string) – signed deposit transaction which pays to a 2 of 2 multisig script hash.
  • redeem_script (string) – the redeem script that comprises the script hash so that the merchant can verify.
Returns:

deposit transaction id

Return type:

(string)

receive_payment(deposit_txid, payment_tx)

Receive and process a payment within the channel.

The customer makes a payment in the channel by sending the merchant a half-signed payment transaction. The merchant signs the other half of the transaction and saves it in its records (but does not broadcast it or send it to the customer). The merchant responds with 200 to verify that the payment was handled successfully.

Parameters:
  • deposit_txid (string) – string representation of the deposit transaction hash. This is used to look up the payment channel.
  • payment_tx (string) – half-signed payment transaction from a customer.
Returns:

payment transaction id

Return type:

(string)

redeem(payment_txid)

Determine the validity and amount of a payment.

Parameters:payment_txid (string) – the hash in hexadecimal of the payment transaction, often referred to as the transaction id.
Returns:pmt_amount – value in satoshis of the incremental payment.
Return type:int
Raises:PaymentError – reason why payment is not redeemable.
status(deposit_txid)

Get a payment channel’s current status.

Parameters:deposit_txid (string) – string representation of the deposit transaction hash. This is used to look up the payment channel.
sync()

Sync the state of all payment channels.

exception two1.bitserv.payment_server.PaymentServerError

Bases: Exception

Generic exception for payment channel processing errors.

exception two1.bitserv.payment_server.RedeemPaymentError

Bases: two1.bitserv.payment_server.PaymentServerError

Raised when the payment server fails to redeem a payment.

exception two1.bitserv.payment_server.TransactionVerificationError

Bases: two1.bitserv.payment_server.PaymentServerError

Raised when the server fails to verify the validity of a transaction.

two1.bitserv.flask

Flask bitserv payment library for selling 402 API endpoints.

exception two1.bitserv.flask.decorator.BadParametersError

Bases: two1.bitserv.flask.decorator.PaymentAPIError

Raised when a client provides incorrect endpoint parameters.

class two1.bitserv.flask.decorator.Channel(server)

Bases: flask.views.MethodView

REST interface for managing payment channels.

delete(deposit_txid)

Close a payment channel.

Parameters:deposit_txid (string) – initial signed deposit transaction id.
Params (json):
signature (string): deposit_txid signed by customer’s private key.
Response (json) 2xx:
payment_txid (string): final payment channel transaction id.
get(deposit_txid)

Return the merchant’s public key or info about a channel.

methods = ['DELETE', 'GET', 'POST', 'PUT']
post()

Open a payment channel.

Params (json):
deposit_tx (string): serialized deposit transaction. redeem_script (string): serialized redeem script.
Response (json) 2xx:
deposit_txid (string): deposit transaction id.
put(deposit_txid)

Receive payments inside a payment channel.

Parameters:deposit_txid (string) – initial signed deposit transaction id.
Params (json):
payment_tx (string): half-signed serialized payment transaction.
class two1.bitserv.flask.decorator.Payment(app, wallet, allowed_methods=None, zeroconf=False, sync_period=600, endpoint='/payment', db_dir=None, username=None)

Bases: object

Class to store merchant settings.

contains_payment(price, request_headers, kwargs)

Validate the payment information received in the request headers.

Parameters:
  • price (int) – The price the user must pay for the resource.
  • request_headers (dict) – Headers sent by client with their request.
  • args (keyword) – Any other headers needed to verify payment.
Returns:

True if payment is valid,

False if no payment attached (402 initiation).

Return type:

(bool)

Raises:

BadRequest – If request is malformed.

required(price, **kwargs)

API route decorator to request payment for a resource.

This function stores the resource price in a closure. It will verify the validity of a payment, and allow access to the resource if the payment is successfully accepted.

exception two1.bitserv.flask.decorator.PaymentAPIError

Bases: Exception

Generic error for exceptions encountered during payment negotiation.

exception two1.bitserv.flask.decorator.PaymentRequiredException(description=None, response=None)

Bases: werkzeug.exceptions.HTTPException

Payment required exception.

code = 402
get_body(context)

402 response body.

get_headers(context)

402 response headers.

two1.bitserv.flask.decorator.flask_channel_adapter(app, server, endpoint='/payment')

Initialize the Flask views with RESTful access to the Channel.