An Escrow contract defines the conditional transaction between two transacting parties through an Escrow account.
The Multi-party Escrow (MPE) smart contract API and the payment channel together enable payments in the SingularityNet platform with a minimal number of on-Blockchain interactions between AI Consumers and AI service providers.
The MPE contract comprises two main functions, which includes:
A set of the simple (“atomic”) unidirectional payment channels between clients and service providers and support functions for controlling these channels.
Note: Any one can deposit and withdraw their AGIX tokens into a Multi-Party Escrow, (which have not been escrowed at the moment).
Whenever the sender and the receiver enter into an contract, a channel is created. A payment channel is a tool that enables off-chain transactions between parties without the delay imposed by Blockchain block formation and without compromising the transactional security.
If you are familiar with the concept of payment channels, ignore this section.
The core logical building block of the Multi-Party Escrow is a simple (“Atomic”) unidirectional payment channel. To learn more about the details of how to implement the Escrow contract for unidirectional payment channel, click on this link SimpleEscrow.sol file here.
It is understood that the payment channel is on the Blockchain. So, in order to prevent direct updating on the Blockchain regularly, the payment channel state is maintained in the storage.
Daemon maintains the channel state off chain as block operations involve gas cost and are slow between parties without imposing any delay by the Blockchain block formation times and compromising on transactional security.
Let us consider the simple unidirectional payment channel, the main logic is as follows:
Note: The receiver can withdraw from the channel (same as claim) only using the authorized amount by the sender. Whenever a signature is made on a certain format which should be signed by the private key of Kevin, Jack then verifies whether the signature was authentic to Kevin, based on the agreed format.
Consider the following
If Kevin is buying services from the Kevin, they both need to enter in to a formal agreement with each other.A channel is created.
Note: Each channel is unique to a combination of client identity (sender), service identity (recipient),Organization Id and the daemon group identity.
Channel ID | 1 | The channel ID created is 1 on Chain |
---|---|---|
Nonce | 0 | Initially the Nonce is 0 |
Full amount | 100 Cogs | Amount Kevin has put into the channel is 100 Cogs |
Authorized Amount | 0 | The Authorized amount is zero, because no services has been used for the first time. |
Signature | Nil | No signature is required to be sent. |
Channel ID | 1 | The channel ID 1 is now updated off chain |
---|---|---|
Nonce | 0 | Initially the Nonce is 0 |
Full amount | 100 Cogs | Amount Kevin has put into the channel is 100 Cogs |
Authorized Amount | 1 | The Authorized amount is zero. |
Signature | 1 | No signature is required to be sent. |
Channel ID | 1 | The channel ID 1 is now updated off chain |
---|---|---|
Nonce | 0 | Initially the Nonce is 0 |
Full amount | 100 Cogs | Amount Kevin has put into the channel is 100 Cogs |
Authorized Amount | 2 Cogs | The Authorized amount is two. |
Signature | 2 Cogs | Signature is required for two. |
Channel ID | 1 | The channel ID created is 1 |
---|---|---|
Nonce | 1 | Initially the Nonce was 0 but now it is 1 |
Full amount | 98 Cogs | Amount signed by Jack was for two cogs. The full amount in the channel is 98. |
Authorized Amount | 0 | The Authorized amount is two. |
Signature | 0 | No signature is required to be sent |
Note: Claims are always on-chain transaction and the Nonce gets incremented when claims are made.
The same process follows for future calls authorizations of cogs.
With the following functions the client can postpone the expiration time of the channel and can add funds to the channel at any time and can also claim all funds from the channel after the expiration time is reached.
function channelExtend(uint256 channel_id, uint256 new_expiration);
function channelAddFunds(uint256 channel_id, uint256 amount);
function channelExtendAndAddFunds(uint256 channel_id, uint256 new_expiration, uint256 amount);
function channelClaimTimeout(uint256 channel_id);
The Sender can claim the funds after the expiry date
function channelClaimTimeout(uint256 channel_id);
With the following function, the recipient can claim funds from the channel
function channelClaim(uint256 channelId, uint256 amount, uint8 v, bytes32 r, bytes32 s, bool isSendback)
It should be noted that v
, r
, s
are parts of the signature. The recipent should present the signature for the following message [MPEContractAdress, channelId, nonce, amount]
. It should be noted that [MPEContractAdress, channel_id, nonce]
is the full ID of the “atomic” channel.
The recipient has two possibilities:
(is_sendback==true)
- “close” the channel and send the remainder back to the sender.(is_sendback==false)
- “close/reopen”. We transfer the claimed amount to the recipient, but instead of sending the remainder back to the sender we simple change the nonce of the channel. By doing this we close the old atomic channel [MPEContractAdress, channel_id, old_nonce]
and open the new one [MPEContractAdress, channel_id, new_nonce]
.channelClaim
). It can inform the client that the nonce
of the channel has changed, and it can start accepting calls from the client with a new nonce
. It can be shown that it is secure for both the client and the server if the transaction is accepted by the Blockchain before the expiration date of the channel. Similarly, the client doesn’t need to wait for a confirmation from the Blockchain after sending the channelExtendAndAddFunds
call. It makes the Multi-Party Escrow functional, even on a very slow Ethereum network.nonce
in the channel prevents a race between the channelExtendAndAddFunds
and channelClaim
. If the client sends the channelExtendAndAddFunds
request and at the same time the
server sends a channelClaim
request, they can continue to work without receiving confirmation from the Blockchain. In this case it also does not matter which request will be accepted first (as channelClaim
can only change the nonce
, and cannot create a new Payment Channel structure).The Client does not have to maintain the state of the last amount it had signed The client can request the last state of the given payment channel from the server. _ The server is not able to forge this state, because it was signed by the client (of course the client should check its own signature). _ The server is obviously interested in saving and sending the last state, otherwise it loses money.
This section describes how the client communicates with the SingularityNET services using the Multi-Party Escrow payment channels without storing state of the payment channel. The client needs to store the Ethereum identity as follows:
Note: A unique gRPC method is available in the daemon helps return the state of the channel (see: https://github.com/singnet/snet-cli/blob/master/snet_cli/resources/proto/state_service.proto).
The client does not necessarily require a special call request to know the last state of the channel from the daemon.
The daemon can return the state of the channel in the response to any non-authorized call.
The client receives the following information from the daemon:
Note: The two last values are not available in current version, if implemented, can calculate the unspent_amount in the case that current_nonce != | Blockchain_nonce. |
Example Assume that the server performs a close/reopen procedure for the channel. The client can proceed without confirmation from the Blockchain, because the server does not need to be dependent, or the client ensures that the request is mined before expiration of the channel.
Before considering the above scenario, define the following parameters
Blockchain_nonce - nonce of the channel in the Blockchain |
Blockchain_value - value of the channel in the Blockchain |
It is known that the daemon starts the close/reopen procedure only after the previous channelClaim request was mined. This means that the current_nonce, at maximum, is one point ahead of the | Blockchain_nonce. |
In each case, the client can verify their signature is authentic and considers the following two numbers:
Simple case current_nonce == | Blockchain_nonce |
unspent_amount = | Blockchain_value - current_signed_amount |
Complex casecurrent_nonce != | Blockchain_nonce |
Taking into account our assumptions, we know that current_nonce = | Blockchain_nonce + 1. |
unspent_amount = | Blockchain_value - oldnonce_signed_amount - current_signed_amount |
Note: The server can send smaller oldnonce_signed_amount (not the actually last one which was used for channelClaim), But the server trust that the money available is actually more in the channel, which means that a likely attack has occurred through unspent_amount, which lead us believe that there are less tokens than the actuals, and therefore the future calls need be rejected instantly (or force us to call channelAddFunds).
Last modified on : 15-Oct-24