Waves Smart Asset Applications: Whitelists, Blacklists and Interval Trading

In the two previous articles we discussed using smart accounts and smart assets for running auctions and creating customer loyalty programs , as well as facilitating transparency for financial instruments.

Today, we will look at some specific use cases for smart assets, including asset freezing and restricting transactions for certain addresses.

Smart assets allow Waves users to apply scripts to tokens in much the same way as they can apply scripts to smart accounts. Whenever a transaction for a smart asset is created, it is validated by the script before being confirmed by the blockchain.

Smart assets differ from smart accounts in the following ways:

In a smart asset’s code, proofs cannot be checked (as discussed in the first article)In a smart account’s code, an ExchangeTransaction can only be checked if your account is a matcher account. Otherwise, only an order can be checked. In a smart asset’s code, you cannot check an order, but you can check an ExchangeTransaction and, if necessary, extract an order from it.Unlike a smart account, a smart asset doesn’t have a state, but we still have access to account states from the script.

Smart assets help to substantially simplify the process of writing contracts, making implementation of many use cases more concise and elegant.

Asset Freezing

To freeze assets until a certain block height, targetHeight, is reached, simply define this value in the following smart asset’s script:

let targetHeight = 1500000 height >= targetHeight height is a language function that returns the current height

Using a certain matcher

To define a certain matcher, you can assign the matcher address as the sender value in the following smart asset’s script:

match tx { case t : ExchangeTransaction => t.sender == addressFromString("3PJaDyprvekvPXPuAtxrapacuDJopgJRaU3") case _ => true }

Recipient whitelist

To allow transfer of tokens only to specific accounts — to create a “whitelist” — you can use a smart asset with the following script, which checks for the presence of an address on the list:

match tx { case t : TransferTransaction => let trustedRecipient1 = addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4") let trustedRecipient2 = addressFromString("3PLZcCJyYQnfWfzhKXRA4rteCQC9J1ewf5K") let trustedRecipient3 = addressFromString("3PHrS6VNPRtUD8MHkfkmELavL8JnGtSq5sx") t.recipient == trustedRecipient1 || t.recipient == trustedRecipient2 || t.recipient == trustedRecipient3 case _ => false }

For purposes of security and provable completion of language, the list doesn’t complete implementation of the iterator. Therefore, it is defined as a set of specific elements.

Recipient blacklist

Similarly, to prohibit transfer of tokens to specific accounts, you can create a blacklist. Exactly the same smart asset script will be used, but it will check an address for absence from the blacklist:

match tx { case t : TransferTransaction => let bannedRecipient1 = addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4") let bannedRecipient2 = addressFromString("3PLZcCJyYQnfWfzhKXRA4rteCQC9J1ewf5K") let bannedRecipient3 = addressFromString("3PHrS6VNPRtUD8MHkfkmELavL8JnGtSq5sx") t.recipient != bannedRecipient1 && t.recipient != bannedRecipient2 && t.recipient != bannedRecipient3 case _ => false }

Transferring by issuer permission

Using a smart asset, you can also add the option of transferring the smart asset only with its issuer’s permission (commitment/debt label). The issuer expresses their consent by placing the transaction’s ID in their account’s state:

match tx { case t : TransferTransaction => let issuer = extract(addressFromString("3P6ms9EotRX8JwSrebeTXYVnzpsGCrKWLv4")) #checking that the issuer's state contains the current transaction's ID isDefined(getInteger(issuer, toBase58String(t.id))) case _ => false }

Asset tradable only against a certain currency

A smart asset can allow trading solely against specific coins. For instance, to ensure the smart asset trades only with bitcoin, the following code can be used:

let BTCId = base58'8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' match tx { case t : ExchangeTransaction => t.sellOrder.assetPair.priceAsset == BTCId || t.sellOrder.assetPair.amountAsset == BTCId case _ => true }

Trading with prices from an oracle

In a smart asset’s script, you can set permission for trading only at a price fixed in the trusted oracle’s state:

let oracle = Address(base58'3PLNmokt22NrSiNvCLvwMUP84LCMJqbXwAD') let assetId = toBase58String(base58'oWgJN6YGZFtZrV8BWQ1PGktZikgg7jzGmtm16Ktyvjd') match tx { #prohibiting the transfer of the asset case t: TransferTransaction | MassTransferTransaction => false case e: ExchangeTransaction => #checking that trading is at the price fixed in the oracle's state for this asset let correctPrice = e.price == extract(getInteger(oracle, assetId)) #checking that the trading is in exchange for WAVES let correctPriceAsset = !isDefined(e.sellOrder.assetPair.priceAsset) correctPrice && correctPriceAsset case _ => true }

Here, we face a special situation while checking the ID of the asset against which our smart asset trades. If the asset’s ID is not defined, it is WAVES by default. In the script, we check that trading is to be against WAVES.

Fixed price raise

You can set a fixed price for a smart asset, which can be raised in steps by a specific proportion. This is an example of a script for a smart asset traded with a fixed price that is raised by 5% every 1,000 blocks:

let startPrice = 10 let startHeight = 1000 let interval = 1000 #by what percentage the price is increased in one step let raise = 5 match tx { case t: TransferTransaction | MassTransferTransaction => false case e: ExchangeTransaction => e.price == startPrice + ((height - startHeight) / interval) * (100 + raise) / 100 && !isDefined(e.sellOrder.assetPair.priceAsset) case _ => true }

Interval trading

A script can also be applied to allow trading of a smart asset that is limited to a predetermined time frame. This is an example of such a script:

let startHeight = 10000 let interval = 44000 let limit = 1500 match tx { case t: TransferTransaction | MassTransferTransaction | ExchangeTransaction => (height - startHeight) % interval < limit case _ => true }

In the script, we check that no more than “limit” of intervals has elapsed since the start of trading (startHeight). The interval length equals the number of blocks defined in the interval field.