Contracts with arbitration
Contracts with arbitration are peer-to-peer free-form textual contracts coupled with simple smart contracts that safely lock the money for the duration of the contract and allow disputes to be resolved by mutually agreed arbiters. In other words, a contract with arbitration is a prosaic contract plus a smart contract that guarantees that the parties faithfully follow the contract.
If you are developing a bot that makes contract offers, here is an outline of the contract's lifecycle:
Your bot (offerer) receives a user's address in chat from a user.
The bot then sends a contract with arbitration offer to the user. In the offer, it sets the user's address as the other party of the contract, amount of the contract, and an arbiter's address of your choosing. You can also choose the role for your bot in this contract (buyer or seller).
The user reviews the offer in their wallet and accepts the contract.
Your bot receives the user's acceptance, generates a new shared address with smart contract definition, and initiates signing and posting of a special
data
unit with the hash of the agreed contract text. The unit is to be signed by the shared address (shared between the bot and the user) and each party's signature means that they agree with the contract's text. Your bot signs first, then waits for the user to sign the transaction.After the unit has been posted, the buyer side of the contract should pay to this contract's address the full amount that was specified in the contract.
As soon as the funds are locked on the contract, the seller of the goods/services starts fulfilling their obligations.
When all the work is done and the buyer is satisfied with the result, they can release the funds from the contract sending them to the seller's address. Also at any time the seller can refund the full amount back to the buyer. The smart contract ensures that in the absence of any arbiter decision each party can send the contract's funds only to the other party: buyer to the seller to release the funds after the work is done, or seller to the buyer to refund. Either outcome completes the contract.
In case of a disagreement, the user can open a dispute and invoke the arbiter by clicking the corresponding button in their wallet, or your bot can call an API method to open a dispute. The plaintiff has to pay for the arbiter's service before the arbiter starts looking into the case. After the contract status becomes
in_dispute
, both parties have to wait for the arbiter to post a resolution unit. After the arbiter has studied the contract and the evidence provided by the parties, they make a decision and post the resolution unit. Then, the winning side can claim the funds from the contract.
Note that in order to open a dispute from the bot, your bot should implement the corresponding API (see below). After you open a dispute, the arbiter will set a fee for their service and the arbstore will send a payment request to your bot. Your bot should be able to handle such requests and decide about paying them, either autonomously, or forward them to a human operator.
All the API methods available for your bot are located in ocore/arbiter_contract.js file.
Example
There is an example script for a part of the API functionality in tools/arbiter_contract_example.js inside headless-wallet
repository, you can start by modifying it to your needs:
Basically, all you need to do is calling the exported methods of arbiter_contract.js
module and reacting to arbiter_contract_*
events on the event bus.
API reference:
createAndSend(contractObj, callback(contract){})
This method creates a contract from the provided object and sends it to the peer.
contractObj
should contain all the fields needed to assemble a new contract offer. The function is then sends the offer to thecontractObj.peer_device_address
via chat. Thecontract
received in the callback function hashash
field, it is the contract's hash which acts as the primary key when referencing contracts.contractObj
required fields:title
- contract title,string
text
- contract text,string
arbiter_address
- the address of the picked arbiter,string
amount
- amount inasset
to be paid to the seller,int
asset
- asset in which the payment should be done, can benull
or"base"
for Bytes, or any other asset ID,string
peer_address
- address of the other side of the contract,string
my_address
- your address,string
me_is_payer
- flag specifying if you are the buyer or seller,bool
peer_device_address
- the device address of the other side of the contract,string
ttl
- Time-To-Live, in hours, till which the contract can be accepted, then it expires,float
cosigners
- array of your wallet cosigners that would be used when posting contract signature unit, leave empty if not multi-sig or when you want to use all of the cosigners,array of device addresses
my_contact_info
- free-form text with your contact info so the peer and arbiter can contact you, it is important to note that if you are sending contract offers from a bot, then arbiter can only pair with your bot and not you, therefore you should put your own wallet's pairing code here or other contacts by which you (as a human) can be reached,string
contract
object supplied to your callback will mostly be identical to the contractObj that you provided to this function, but with some additional fields populated, such ashash, creation_date, my_pairing_code.
\createSharedAddressAndPostUnit(contract_hash, walletInstance, callback(error, contract){})
This method is used to react to the peer accepting your offer. It creates a new shared address with smart contract definition and posts the unit into the DAG.
walletInstance
is a reference to wallet that hassendMultiPayment()
method with an appropriate signer inside. For bots, it is usually the mainheadlessWallet
module. If theerror
argument for the callback isnull
, it means that the unit was posted successfully, thecontract
object is returned with the updated fields.\pay(contract_hash, walletInstance, arrSigningDeviceAddresses, callback(error, contract, unit){})
This method pays to the contract if you are the buyer side. If you use a multi-signature wallet, then fill the
arrSigningDeviceAddresses
array with signing device addresses, otherwise provide an empty array. Theunit
argument in the callback function is the unit ID in case the payment succeeded.\complete(contract_hash, walletInstance, arrSigningDeviceAddresses, callback(error, contract, unit){})
This method sends the funds locked on the contract to the peer. Depending on your role, this action performs either a successful completion (you are a buyer), or a refund (you are the seller).\
respond(contract_hash, status, signedMessageBase64, signer, callback(error, contract){})
If you allow users to make offers, use this function after receiving a contract offer, usually as a reaction to the event
arbiter_contract_offer
. To get a better idea how to constructsignedMessageBase64
andsigner
arguments for this function, you can take a look at an example of how GUI wallet is invoking it: https://github.com/byteball/obyte-gui-wallet/blob/95d3354de9f9156fd06ccc63c0a4cac8c94f2510/src/js/services/correspondentService.js#L669\openDispute(contract_hash, callback(error, response, contract){})
This function raises a dispute by making an HTTPS request to the contract arbiter's current ArbStore web address. Callback is provided with an error, if any, http response and the updated contract object.\
appeal(contract_hash, callback(error, response, contract){})
This method is similar to the previous one, but is used when you are not satisfied with the arbiter's decision. Also makes an HTTPS request to the ArbStore, but this time calls to ArbStore moderator, who can penalize the arbiter, in case they find the arbiter's decision / actions inappropriate.\
getByHash(contract_hash, callback(contract){})
/getBySharedAddress(address, callback(contract){})
/getAllBy*()
These are all similar functions to retrieve one or many contracts by specific criteria. To get a detailed overview of all the methods you can look at exported methods of
arbiter_contract.js
module.
Event reference
Almost every event is supplied with additional arguments that you can use in your event handler functions. For examples, see the code snippet above.
arbiter_contract_offer : (contract_hash)
- this event is fired when core received new contract with arbitration offer from any correspondent.arbiter_contract_response_received : (contract)
- fired when user responds to your contract offer. To get their response, check thestatus
field of the contract, it should be either"accepted"
or"declined"
.arbiter_contract_update : (contract, field, value, unit, winner, isPrivate)
- this is the most common event that you will react to. It is raised in many situations, like:changed status of a contract:
field = "status"
(contract wasrevoked
, or putin_dispute
,in_appeal
, waspaid
,completed
, etc.). This is the full list of the contract statuses:'pending', 'revoked', 'accepted', 'signed', 'declined', 'paid', 'in_dispute', 'dispute_resolved', 'in_appeal', 'appeal_approved', 'appeal_declined', 'cancelled', 'completed'
unit with contract hash was posted:
field = "unit"
Last updated