How to use crypto to create a “public good” without risk to investors





In the article I will talk about what the Assurance Contract is, also known as a mechanism for providing public and group benefits, and together we will write our own crowdfunding implementation based on Autonomous Agents on the Obyte platform.



Assurance Contract is an organized fundraising mechanism for creating a new public good. A typical example of the use of such contracts may be the following situation: the participants in a summer cottage cooperative decided to build a road for convenient access to their summer cottages. To do this, collect the amount N and transfer it to the contractor for the construction of the road. Many questions arise: where to find a person whom everyone would trust and charge the collection, how to make all potential users of the road invest in its construction, what will happen if the money collected is not enough, etc.



Assurance contracts are very similar in meaning to crowdfunding, only investors are not exposed to risk, as is the case with investors who invest in risky enterprises. Such contracts, as well as crowdfunding, can also have two outcomes - successful and unsuccessful, depending on the achievement of the target for the collected amount. The result of a successful collection will be the receipt by investors, but sometimes not only by them, of a new public good. In case of failure, the money is simply returned to the depositors.



So, for starters, we repeat the basic conditions. The essence of crowdfunding is to raise funds "in a crowd" for a predetermined goal, with a given final amount (not necessarily fixed, it may be dynamic) and an end date (also not always, but usually). When this amount is reached, the round of financing is considered successful and the collection stops, all funds are allocated to achieve the goal. In case of failure, that is, upon reaching the deadline and with insufficient funds raised, all funds will be returned back to the donors.



What problems does cryptocurrency crowdfunding solve? Firstly, it saves investors from having to trust and verify who they send their money to. The code of the autonomous agent for fundraising is open and each investor can make sure that he can withdraw his contribution at any time until the final amount is reached, as well as that all funds will be sent to a pre-selected address to create the good. Secondly, transparent accounting of all funds raised helps to distribute future benefits from the achievement of the goal in proportion to the contribution made.



Another problem that can be solved with the Assurance Contract is the coordination problem. Today we will supplement our last article about the game "Attack 51%" , and we will just solve this problem. Without the use of AA, participants in the game will be at risk of "lack" of the collected amount due to lack of coordination. Then those who have already managed to make an investment will simply lose their money because of other participants who have not shown interest, or simply have not had time to do it (remember that the opposing teams have exactly 24 hours to challenge the current leader). By collecting money through an assurance contract, players at any time will be able to withdraw their money until the necessary amount is collected.



Not applicable in our case, but another useful feature of such contracts is to reduce the impact of the “free rider effect” or “free rider problem”. Since the public good, that is, either inalienable, or difficult to control access to it, the problem of free rider is the main one in the organization of public goods. People do not want to invest in something that others can use for free, or the investor himself, adhering to absolutely rational economic behavior and choosing a “non-investing” strategy, in case of a successful collection, is likely to be able to use the created good.



I will also talk about the option of optimized assurance contract, which increases the chances of success in raising funds, as the rational behavior of the participants will already be the choice of investment strategies, rather than ignoring.



We play the game collectively



For our example, we will take the game “Attack 51%” already known in the last article.

In its current implementation, game participants send money to the game’s AA address. But the algorithm can be improved and the chances of losing money by participants in case of losing their team can be reduced.

For this, any team member can organize crowdfunding with a dynamic target equal to at least 51% of the total pool of the game. As the pool increases, the final crowdfunding target will also shift. And only if successful, the money will be sent to the address of the AA game and the team will immediately become the current leader. If such an event never occurs, then the participants will not lose their money, wasting the pool of the game in vain, but simply taking the money from crowdfunding AA.



The originals of both standalone agents are always available in the online Oscript code editor in the form of templates, just select them from the drop-down menu: “51% attack game” and “Fundraising proxy”.



Before you start writing AA in Oscript, I highly recommend reading the Getting Started Guide (eng) in our documentation to quickly become familiar with the basic principles of writing AA.



Writing a code



First, we write the algorithm: the donor sends bytes to the AA address, receiving in return a crowdfunding tokens in the ratio of 1 to 1. At any time, he can exchange these tokens back for bytes. After receiving the bytes, AA checks that we have reached the goal of raising funds, and if so, then sends all the bytes to the address of the game's AA and receives game tokens (team tokens) in return. The donor can now exchange his crowdfunding tokens for game tokens, which, in the future, in case the team wins, can already be exchanged for bytes through AA games, receiving up to x2 bytes from the original ones.



So let's get started.



The start processing unit of the new fundraising period. Upon receiving the “start” field in the incoming data message to the address of our AA, we will issue our crowdfunding token and write it to state, we will also return it as a response to the caller.



{ // start a new fundraising period if: `{trigger.data.start AND !$asset}`, messages: [ { app: 'asset', payload: { is_private: false, is_transferrable: true, auto_destroy: false, fixed_denominations: false, issued_by_definer_only: true, cosigned_by_definer: false, spender_attested: false } }, { app: 'state', state: `{ var[response_unit || '_status'] = 'open'; var['asset'] = response_unit; response['asset'] = response_unit; }` } ] },
      
      





Processing deposits from donors. First, we eliminate “junk” transactions with very small amounts, which are not enough even for a commission. Next, check the basic conditions that the game is not completed and collecting is possible. Full block listing:



 { // contribute if: `{trigger.output[[asset=base]] >= 1e5 AND $asset}`, init: `{ if (var[$destination_aa]['finished']) bounce('game over'); $amount = trigger.output[[asset=base]] - 2000; // to account for fees we need to respond now and to refund bytes or pay shares later $total_raised = var['total_raised'] + $amount; $missing_amount = ceil((balance[$destination_aa][base] + $total_raised)*0.51) - var[$destination_aa]['team_' || $team || '_amount']; $bDone = ($total_raised > $missing_amount); }`, messages: [ { app: 'payment', payload: { asset: "{$asset}", outputs: [{address: "{trigger.address}", amount: "{$amount}"}] } }, { if: `{$bDone}`, app: 'payment', payload: { asset: "base", outputs: [{address: "{$destination_aa}", amount: "{$total_raised}"}] } }, { if: `{$bDone}`, app: 'data', payload: { team: "{$team}" } }, { app: 'state', state: `{ if ($bDone) var[$asset || '_status'] = 'raised'; else var['total_raised'] = $total_raised; }` } ] },
      
      





Immediately after the initial checks, the main logic of our AA goes:



$total_raised = var['total_raised'] + $amount;



- here we summarize the amount just received to the total amount of funds on the agent.



$missing_amount = ceil((balance[$destination_aa][base] + $total_raised)*0.51) - var[$destination_aa]['team_' || $team || '_amount'];



- check that the new amount of funds raised is enough to win the game. Note the reference to the balance and state of the variables of another AA: balance[$destination_aa][base]



and var[$destination_aa]['team_' || $team || '_amount']



var[$destination_aa]['team_' || $team || '_amount']



var[$destination_aa]['team_' || $team || '_amount']



.



We do all this in the init



block, which is called every time before processing transaction messages. The answer to the incoming transaction, obviously, will be the sending of AA tokens in a one-to-one ratio relative to the received bytes (the first block of the messages array). The second and third blocks will be executed only if the local variable $ bDone is set to true (it is set in the init block). In them, we will send all funds from the address of this AA to the address of the AA game, receiving in return game tokens. In the last block of messages, we simply update the status, setting the necessary statuses and the amount collected.



Processing received tokens from the AA game, they already lie on the balance of our AA, we only need to change the state of the agent:



 { // received team asset if: `{trigger.output[[asset=var[$destination_aa]['team_' || $team || '_asset']]] AND $asset}`, messages: [ { app: 'state', state: `{ var[$asset || '_status'] = 'done'; var['asset'] = false; var['total_raised'] = false; }` } ] },
      
      





Refand of investments by players. We allow you to refuse to participate at any time and take your bytes back. To do this, the player sends tokens of this AA, and we send him the same bytes in return transaction:



 { // refund if: `{$asset AND trigger.output[[asset=$asset]] > 0}`, init: `{ $amount = trigger.output[[asset=$asset]]; }`, messages: [ { app: 'payment', payload: { asset: "base", outputs: [{address: "{trigger.address}", amount: "{$amount}"}] } }, { app: 'state', state: `{ var['total_raised'] -= $amount; }` } ] },
      
      





Exchange of our AA tokens for game tokens. We simply send game tokens that lie on the balance of our AA (in the case of passing the if block) in an amount equal to the number of “our” tokens received.



 { // pay the obtained team asset in exchange for the issued asset if: `{ $in_asset = trigger.output[[asset!=base]].asset; var[$in_asset || '_status'] == 'done' }`, messages: [ { app: 'payment', payload: { asset: "{var[$destination_aa]['team_' || $team || '_asset']}", outputs: [{address: "{trigger.address}", amount: "{trigger.output[[asset=$in_asset]]}"}] } }, ] }
      
      





Agent code is ready, here is a complete listing:



Full agent code
 { /* This is a fundraising proxy AA. It allows to raise money up to a specific target. If the target is reached, the money is forwarded to another AA, otherwise the money is refunded. This specific example raises money for challenging the current candidate winner in 51% attack game. The target is a moving target as other teams may be adding contributions at the same time. Contributors get shares of the proxy in exchange for Bytes. They can exchange the shares back to the same amount of Bytes any time before the target is reached. As soon as the target is reached, the raised funds are forwarded to the game and the proxy receives the shares of the team in exchange. Then, the contributors can exchange the shares of the proxy for the shares of the team. */ init: `{ $asset = var['asset']; $destination_aa = 'WWHEN5NDHBI2UF4CLJ7LQ7VAW2QELMD7'; $team = 'VF5UVKDSOXPMITMDGYXEIGUJSQBRAMMN'; }`, messages: { cases: [ { // start a new fundraising period if: `{trigger.data.start AND !$asset}`, messages: [ { app: 'asset', payload: { is_private: false, is_transferrable: true, auto_destroy: false, fixed_denominations: false, issued_by_definer_only: true, cosigned_by_definer: false, spender_attested: false } }, { app: 'state', state: `{ var[response_unit || '_status'] = 'open'; var['asset'] = response_unit; response['asset'] = response_unit; }` } ] }, { // contribute if: `{trigger.output[[asset=base]] >= 1e5 AND $asset}`, init: `{ if (var[$destination_aa]['finished']) bounce('game over'); $amount = trigger.output[[asset=base]] - 2000; // to account for fees we need to respond now and to refund bytes or pay shares later $total_raised = var['total_raised'] + $amount; $missing_amount = ceil((balance[$destination_aa][base] + $total_raised)*0.51) - var[$destination_aa]['team_' || $team || '_amount']; $bDone = ($total_raised > $missing_amount); }`, messages: [ { app: 'payment', payload: { asset: "{$asset}", outputs: [{address: "{trigger.address}", amount: "{$amount}"}] } }, { if: `{$bDone}`, app: 'payment', payload: { asset: "base", outputs: [{address: "{$destination_aa}", amount: "{$total_raised}"}] } }, { if: `{$bDone}`, app: 'data', payload: { team: "{$team}" } }, { app: 'state', state: `{ if ($bDone) var[$asset || '_status'] = 'raised'; else var['total_raised'] = $total_raised; }` } ] }, { // received team asset if: `{trigger.output[[asset=var[$destination_aa]['team_' || $team || '_asset']]] AND $asset}`, messages: [ { app: 'state', state: `{ var[$asset || '_status'] = 'done'; var['asset'] = false; var['total_raised'] = false; }` } ] }, { // refund if: `{$asset AND trigger.output[[asset=$asset]] > 0}`, init: `{ $amount = trigger.output[[asset=$asset]]; }`, messages: [ { app: 'payment', payload: { asset: "base", outputs: [{address: "{trigger.address}", amount: "{$amount}"}] } }, { app: 'state', state: `{ var['total_raised'] -= $amount; }` } ] }, { // pay the obtained team asset in exchange for the issued asset if: `{ $in_asset = trigger.output[[asset!=base]].asset; var[$in_asset || '_status'] == 'done' }`, messages: [ { app: 'payment', payload: { asset: "{var[$destination_aa]['team_' || $team || '_asset']}", outputs: [{address: "{trigger.address}", amount: "{trigger.output[[asset=$in_asset]]}"}] } }, ] } ] } }
      
      







We did not implement the most trivial algorithm, because our target is dynamic, which is not typical of a typical assurance contract. In them, most often the target amount is fixed, as well as the deadline time. This will not require major changes in the code, so let us leave this as an exercise for the reader.



The best strategy is to invest!



And now back to the free rider problem. In the case of the game, we have no such problem, because only crowdfunding token holders can win. But solving the free-rider problem will help in the case of the game. We can "stimulate" people to invest. For this, the fundraising organizer, who is also the creator of AA, can lay down a small compensation, which he will pay out of his pocket if the purpose of the collection is not achieved. In the case of a successful collection, the organizer will pick up a small percentage. That is, we introduce a third party who, in order to obtain benefits, will cover the risk of unsuccessful collection.



Applying such an “optimized crowdfunding”, the previously rational behavior of “non-investing” (in general, for any public goods to be free-rider is always the most economically profitable strategy) may become less rational than investing. This may be especially relevant in cases of creating group goods in which a certain circle of persons is involved. This does not completely eliminate the stowaway problem, but makes participation a rational behavior.



The code that implements the “optimized assurance contract” will also be left as an interesting exercise for those who wish.



All Articles