LES in-protocol payments can be sent using multiple payment technologies or even multiple currencies. These are realized as plug-in modules. The payment sender module generates a "proof of payment" that the receiver module can interpret and verify. These proofs can be transmitted through an LES connection or LESTALK UDP messages.
Note: the design allows using multiple currencies on both the client and server side. Each payment module uses one specific currency but it can handle value conversions on both ends. In the first implementation we won't allow this though, the first payment module should only handle ETH. This makes value calculations a lot easier for requestPayment
and paymentCost
. Expected transaction costs have to be considered but that can be very simple too (hardcoded gas cost, user specified gas price).
func receivePayment(from enode.ID, proofOfPayment, oldMeta []byte) (value uint64, newMeta []byte, err error)
Interprets proofOfPayment
and returns value
which is the estimated value of the current payment. The receiver is responsible for cashing cheques in time. It should also take estimated future transaction costs into account and ensure that the long-term sum of returned values stays close to the net amount of money cashed (minus transaction costs). If the currency preferred by the token sale mechanism is not the same as the received currency then value
should be nominated in the former. In this case the receiver should either inform the server operator about the currency risk and the source of conversion rates or take care of conversions automatically and take responsibility for estimating the conversion rates when receiving payments (maybe even use a derivative market to hedge against price changes between receiving the proof of payment and actually converting the cashed coins).
The tags oldMeta
and newMeta
are generated and interpreted by the receiver. Payment processing is stateful and in order to avoid getting the state of token balances and the payment receiver out of sync, these tags can hold a reference to the latest payment added to the client account or even hold the entire internal state of the receiver related to that client. value
should represent the amount received since the state referenced by oldMeta
.
Note that this function can access the local state database (the receiver runs on a full node so it has the entire recent state available) but it should not block for a long time. proofOfPayment
should contain enough information to tell the value of the transfer without any further network retrievals.
func requestPayment(from enode.ID, value uint64, meta []byte) uint64
Returns the necessary amount to be sent (nominated in the currency of payment) in order for the server to receive the given expected value (nominated in the preferred currency of the token sale). In case of a simple one-way payment channel and no currency conversion the return value is usually the same as the value
parameter. For the first payment request (or the first one after cashing a cheque) the return value also includes the expected transaction cost of cashing the next one.
func info() []byte
Returns information about the parameters and capabilities/restrictions of the payment method that the payment sender module on the client side can interpret and decide whether the other side is suitable for receiving its transactions.
func pay(to common.Address, amount, maxCost uint64, oldMeta []byte) (proofOfPayment, newMeta []byte, cost uint64)
Calculates the expected cost (nominated in the client's preferred currency) of sending the given amount
to the specified address and returns it in the cost
field. If cost <= maxCost
then then payment is approved and proofOfPayment
is generated (otherwise it is nil
). The tags oldMeta
and newMeta
serve the same purpose as in receivePayment
.
The token sale module is responsible for handling the sale, limitation and expiraton of service tokens. It has a preferred currency in which token prices are nominated but it does not handle any external currencies, it leaves that to the payment receiver module(s) and believes the received value amounts reported by them. Since reported values are always converted to the preferred currency of the token sale the sale module only maintains a "preferred currency balance" (pcBalance
) in addition to the token balance and it is the only external currency type this module should know about. pcBalance
and the internal state references (receiverMeta
tags) of payment receiver modules are stored in the meta
tag of the client balance entries stored by the server API.
func connection(id enode.ID, requestedCapacity uint64, stayConnected time.Duration, paymentModule []string, connect bool) -> (availableCapacity, tokenBalance, tokensMissing uint64, paymentRequired []uint64)
Lets clientPool
calculate the amount of tokens necessary for the requested connection. If the current tokenBalance
is less than the necessary amount then tokensMissing
is positive (zero otherwise). In this case the requestPayment
function of the specified payment module(s) is called in order to calculate paymentRequired
. If tokensMissing
is zero and connect
is true then the connection is established or the capacity is updated if the connection already exists. Note that the connect
parameter is useful only if the request is sent through an LES connection.
func deposit(from enode.ID, paymentModule string, proofOfPayment []byte) (value uint64, err error)
Deposits a payment to the client's balance. Note that the amount is not converted to service tokens automatically.
First the meta
field of the client balance entry is retrieved. pcBalance
and the relevant receiverMeta
tag are extracted, then receivePayment
of the specified payment module is called. The returned value
is added to pcBalance
and the old receiverMeta
is replaced by the returned newMeta
. Then the client balance meta
entry is reconstructed and stored with an addBalance
API call (where the value
field is zero since we are not adding service tokens yet).
func buyTokens(from enode.ID, currencyID string, maxSpent, minExpected uint64) (spent, required, received, currencyBalance, tokenBalance uint64)
Performs the token sale if possible and calls addBalance
to update both the token balance and pcBalance
. If the conversion was successful then required
is zero, otherwise spent
and received
are zero. If the available rate is better than expected by the client then the entire maxSpent
amount is converted and received
will be higher than minExpected
.
func paymentInfo(paymentModule []string) map[string][]byte
Returns the info()
data packages of the payment modules that are both requested and available.