In the previous article, we looked at several cases of using smart accounts in business — including auctions and loyalty programs.
Today we will talk about how smart accounts and smart assets can increase the transparency and reliability of financial instruments such as options, futures and bills.
OptionAn option is an exchange contract that gives the buyer the right to buy an asset at a certain price or before a certain date, but not obliging it to do so.
')
The option exercise may be as follows:
We use a smart asset for the options themselves as a tool and a smart account for a participant who acts as an exchange and issues options. The participant-exchange promises that it will sell a certain amount of a certain asset at the sellPrice price between the heights of the expirationStart and expirationEnd blocks).
In the smart asset code, we simply check that it is traded only between the specified heights, and we will not check anything else, we will leave all responsibility for observing the rules on the code of the exchange participant.
Smart Asset Code:let expirationStart = 100000 let expirationEnd = 101440 match tx { case some : ExchangeTransaction | TransferTransaction => height > expirationStart && height <= expirationEnd case _ => false }
We assume that the actions take place as follows: the exchange participant sells options to buy an asset, and other participants can transfer these options or trade them. To exercise their right to purchase, the potential buyer must transfer the desired number of options to the seller’s account, that is, the exchange member. Next, he writes information about the perfect transfer to the state of the account of the exchange member and only then ExchangeTransaction under the specified conditions of purchase and sale can pass.
In the smart account code, we must make sure that any ExchangeTransaction passing through it for the final purchase-sale act meets the specified conditions, and the participant buys exactly the number of units that he sent to the account of the exchange participant. The potential buyer must send the correct DataTransaction about the transfer that occurred so that the exchange member can avoid double spending. In this DataTransaction, the buyer puts in a key equal to his address a value equal to the number of options transferred to the account of the exchange participant, that is, the number of asset units he can buy.Smart Account Code: # # sellPrice expirationStart expirationEnd let expirationStart = 100000 let expirationEnd = 101440 let sellPrice = 10000 let amountAsset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' let priceAsset = base58'9jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' #ID - let optionsAsset = base58'7jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' # let this = tx.sender match tx { case dataTx : DataTransaction => # - (ID ) let units = extract(getInteger(dataTx.data, dataTx.data[0].key)) # - let e = transactionById(dataTx.proofs[2]) # match e { case transferTx : TransferTransaction => #, (transferTx.recipient == this) && #, ID dataTx.data[0].key == toBase58String(transferTx.sender.bytes) && sigVerify(dataTx.bodyBytes, dataTx.proofs[0], transferTx.senderPublicKey) && #, (units == transferTx.amount) && #, - (transferTx.assetId == optionsAsset) case _ => false } && size(dataTx.data) == 1 && !isDefined(getInteger(this, dataTx.data[0].key)) && height > expirationStart && height <= expirationEnd case exchangeTx : ExchangeTransaction => #, let correctAssetPair = exchangeTx.sellOrder.assetPair.amountAsset == amountAsset && exchangeTx.sellOrder.assetPair.priceAsset == priceAsset let correctPrice = exchangeTx.sellOrder.price == sellPrice # - let d = transactionById(exchangeTx.proofs[2]) match d{ case dataTx : DataTransaction => let buyOrderSender = dataTx.data[0].key toBase58String(exchangeTx.buyOrder.sender.bytes) == buyOrderSender && exchangeTx.amount == extract(getInteger(dataTx.data, buyOrderSender)) case _ => false } && exchangeTx.sellOrder.sender == this && correctAssetPair && correctPrice && height > expirationStart && height <= expirationEnd case _ => false }
Futures on smart accountsUnlike an option, futures (futures contract) is not a right, but an obligation for a buyer to make an asset purchase at a fixed contract price at some point in the future.
In general, the implementation of futures is similar to the implementation of an option. Here, the smart asset serves as futures.
You also need to make sure that both the buyer and the seller sign a purchase order. Futures is an obligation that must be fulfilled in any case. This means that if the seller or participant refuses his obligations, any participant in the network can send a transaction, thus fulfilling futures.
The smart asset script controls all TransferTransaction and ExchangeTransaction of asset futures, approving them only if the participant-buyer has created an order for the future purchase of asset-futures from the exchange-participant.
This order must be valid and meet the conditions on which the futures are issued. To check the order, you can enter all of its fields into the state of the buyer account, along with the byte representation of the signed order, and then validate from the outside.
Currently, RIDE does not contain the native function of parsing transaction bytes, but includes all the tools necessary for its implementation. Therefore, developers can try to implement this feature on their own.
Multi Signed Account / EscrowAn account with a multi-signature allows several users to jointly manage assets (for example, transactions with assets may be possible only if there are signatures of three users out of four). We can use transaction proofs to create multi-signature accounts in RIDE.
An account with a multi-signature can also be used for an escrow account where money is kept until the parties that have entered into the contract have fulfilled their obligations.
let alicePubKey = base58'5AzfA9UfpWVYiwFwvdr77k6LWupSTGLb14b24oVdEpMM' let bobPubKey = base58'2KwU4vzdgPmKyf7q354H9kSyX9NZjNiq4qbnH2wi2VDF' let cooperPubKey = base58'GbrUeGaBfmyFJjSQb9Z8uTCej5GzjXfRDVGJGrmgt5cD' #, let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey)) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey)) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey)) then 1 else 0 # aliceSigned + bobSigned + cooperSigned >= 2
Token Managed Registry - token curated registry (TCR)On many blockchain platforms, there is a problem of toxic assets. For example, an address on Waves can be created by any address that paid a commission.
The problem of protecting users and the blockchain itself from toxic assets is solved by a token-controlled registry (token curated registry (TCR)) generated by tokens holders.
To vote for adding a specific token to the list, the holder makes a bet equal to his share of tokens of the total number of issued. The token is included in the register if the majority of its holders voted for it.
In our example, we allow the user to add a token to the list for consideration (during the “challenge” period) by the state key key = asset_name only if the current value is count = 0.
Also, the user in the wallet should have a non-zero balance of this token. Then comes the voting period, during which the user can give a vote for each asset in his wallet, but only once, putting a rating from 1 to 10. Users' voices are represented by keys of the form user_address + assetID.
let asset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' let addingStartHeight = 1000 let votingStartHeight = 2000 let votingEndHeight = 3000 # let this = extract(tx.sender) # let address = addressFromPublicKey(tx.proofs[1]) match tx { case t: DataTransaction => if(height > addingStartHeight) then( if(height < votingStartHeight) then( #adding #, let hasTokens = assetBalance(address, asset) > 0 size(t.data) == 1 #, && !isDefined(getInteger(this, toBase58String(asset))) #, - 0 && extract(getInteger(t.data, toBase58String(asset))) == 0 && hasTokens ) else( if(height < votingEndHeight) then ( #voting # let currentAmount = extract(getInteger(this, toBase58String(asset))) let newAmount = extract(getInteger(t.data, toBase58String(asset))) let betString = toBase58String(address.bytes) + toBase58String(asset) #, let noBetBefore = !isDefined(getInteger(this, betString)) let isBetCorrect = extract(getInteger(t.data, betString)) > 0 && extract(getInteger(t.data, betString)) <= 10 #, let hasTokens = assetBalance(address, asset) > 0 # size(t.data) == 2 && isDefined(getInteger(this, toBase58String(asset))) && newAmount == currentAmount + 1 && noBetBefore && isBetCorrect && hasTokens ) else false ) && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1]) ) else false case _ => false }
Subscription feeIn this example, we will consider the use of smart accounts to make regular payments for a product or service at specified intervals - “subscription fees”.
If a user provides a smart account (through transaction details) with TransferTransaction ID with the required amount of transferred funds, he can write to the account state {key: address, value:
true }.
This will mean that the user confirms a subscription to a product or service. When the subscription expires, any user of the network can set the value of
false against the corresponding key in the stack.
let subscriptionPeriod = 44000 let signature = tx.proofs[0] let pk = tx.proofs[1] let requiredAmount = 100000 # let this = extract(tx.sender) match tx { case d: DataTransaction => # let lastPaymentHeight = extract(getInteger(this, d.data[0].key + "_lastPayment")) size(d.data) == 1 && d.data[0].value == "false" && lastPaymentHeight + subscriptionPeriod < height || ( let address = d.data[0].key # - ID, let ttx = transactionById(d.proofs[0]) size(d.data) == 2 && d.data[0].value == "true" && d.data[1].key == address + "_lastPayment" && match ttx { case purchase : TransferTransaction => d.data[1].value == transactionHeightById(purchase.id) && toBase58String(purchase.sender.bytes) == address && purchase.amount == requiredAmount && purchase.recipient == this #, waves && !isDefined(purchase.assetId) case _ => false } ) case _ => false }
VotingSmart accounts can be used to implement voting on the blockchain. An example would be voting for the best ambassadors report within the ambassadors program. State account is used as a platform for recording votes for one or another option.
In this example, only those who have acquired special “voting” tokens are allowed to vote. The participant sends DataTransaction with a pair in advance (key, value) = (purchaseTransactionId, buyTransactionId). Setting another value for this key is prohibited. Using your address and voting option, you can set DataEntry only once. Voting is possible only in the prescribed period.
let asset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' let address = addressFromPublicKey(tx.proofs[1]) let votingStartHeight = 2000 let votingEndHeight = 3000 # let this = extract(tx.sender) match tx { case t: DataTransaction => (height > votingStartHeight && height < votingEndHeight) && #, sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1]) && #, if (t.data[0].key == toBase58String(address.bytes)) then ( # let purchaseTx = transactionById(t.proofs[7]) match purchaseTx { case purchase : TransferTransaction => let correctSender = purchase.sender == t.sender let correctAsset = purchase.assetId == asset let correctPrice = purchase.amount == 1 let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == t.id correctSender && correctAsset && correctPrice && correctProof case _ => false } ) else size(t.data) == 1 && !isDefined(getBinary(this, t.data[0].key)) case _ => false }
Bill of exchangeBill is a written obligation, under which one party must pay another fixed amount at the time of the claim or on a predetermined date.
In our example, a smart account is used, the expiration date of which corresponds to the payment date on the bill.
let expiration = 100000 let amount = 10 let asset = base58'9jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf' let Bob = Address(base58'3NBVqYXrapgJP9atQccdBPAgJPwHDKkh6A8') let Alice = Address(base58'3PNX6XwMeEXaaP1rf5MCk8weYeF7z2vJZBg') match tx { case t: TransferTransaction => (t.assetId == asset)&& (t.amount == amount)&& (t.sender == Bob)&& (t.recipient == Alice)&& (sigVerify(t.bodyBytes, t.proofs[0], t.senderPublicKey))&& (height >= expiration) case _ => false }
DepositDeposit - placement of funds in a bank under certain conditions (term, interest).
In our example, the function of the bank performs a smart account. After a certain number of blocks, which corresponds to the term of the deposit, the user can return their money with interest. The script sets the height of the block (finalHeight), after reaching which the user can withdraw money from the account.
heightUnit is the number of blocks in one unit of time (for example, month, year, etc.). First, we check for a record with a pair (key, value) = (initialTransferTransaction, futureDataTransaction). Then the user must send TransferTransaction with the correct information about the deposit amount and interest accrued during the deposit period. This information is checked against the original TransferTransaction, which is contained in the current TransferTransaction proof. depositDivisor - the number inverse of the deposit share (if the deposit is accepted at 10%, the deposit share is 0.1, and depositDevisor = 1 / 0.1 = 10).
let this = extract(tx.sender) let depositDivisor = 10 let heightUnit = 1000 let finalHeight = 100000 match tx { case e : TransferTransaction => # ID let depositHeight = extract(transactionHeightById(e.proofs[7])) # let purchaseTx = transactionById(e.proofs[7]) match purchaseTx { case deposit : TransferTransaction => let correctSender = deposit.sender == e.sender #, + let correctAmount = deposit.amount + deposit.amount / depositDivisor * (height - depositHeight) / heightUnit == e.amount let correctProof = extract(getBinary(this, toBase58String(deposit.id))) == e.id correctSender && correctProof && correctAmount case _ => false } && finalHeight <= height case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey) }
In the third and final article of this series, we will consider more options for the use of smart assets, including freezing and restricting transactions for specific addresses.