How to: offer a free trial for your dApp
There are several topics that prompt expressions of visible pain when raised in conversation with decentralized application developers. Chief among these topics are:
- Account key handling. Developers cannot ask users for their private keys/seed phrases, but the user always has to sign transactions.
- The need to pay fees for each transaction. How do you explain to users that each transaction requires a fee to be paid in the native platform token? More importantly, where will users get the tokens for their first transactions?
These problems lead to a very high acquisition cost per user. For example, one of the most popular dApps in the Waves ecosystem had a customer acquisition cost of around $80 — with lifetime value (LTV) of less than $10! The conversion process was spoiled by the barriers involved in the two issues described above.
The first problem is often solved with browser extensions like MetaMask and Waves Keeper. However, this solution is not particularly user-friendly and requires a lot of effort, leading to the development of Signer for the Waves ecosystem. The Signer does not force the installation of browser extensions. This article by Vladimir Zhuravlev discusses this, and how to integrate Waves Signer into your application.
What about the second problem? Many dApp creators do not care about this issue, though, of course, users have to find tokens for fees from somewhere. Others require users to pay with plastic cards during registration/authorization, which reduces conversion rates.
I will show you how to solve the problem of fees and how to make a dApp that does not require the user to acquire any native tokens. It allows you to offer trial periods for the dApp or free initial interactions. There are two ways to go about this in Waves. We will cover both.
If your dApp includes the use of an internal token, which all users need to access its functionality, then you can use Waves’ transaction sponsoring mechanism. Users will pay fees in your token, but since miners receive fees in WAVES alone, WAVES will be debited from the account that issued the token and set up sponsoring. Let’s review these steps because this is an important point to understand:
- The user pays a transaction fee in your custom token
- You, as the token issuer, receive these tokens
- WAVES tokens are deducted from your account in the required amount and are paid to miners
The question that has likely arisen right away is, how many tokens will the user pay, and how many WAVES will be debited from the sponsor’s account?
Answer: the asset issuer sets this ratio themselves. At the time of sponsorship, the creator of the token decides how many of his tokens correspond to the minimum fee (0.001 WAVES or 100,000 Wavelets, since code, requires us to deal with the minimum unit). Let’s look at some examples and code to make things clearer.
To enable sponsorship, you must send a transaction called
Sponsorship. You can do this using the UI in Waves.Exchange or you can do it manually or with code using waves-transactions. The code might look as follows:
The most important parameter in the transaction is
minSponsoredAssetFee, which sets the equivalence of
100 A tokens to
0.001 WAVES. Thus, in order to make a
Transfer transaction, the user will have to attach
100 A tokens as the fee.
It is important to realize some of the limitations of sponsorship. Sponsored tokens can only be used as fees for
InvokeScript transaction types. Only the account that issued a token can sponsor it. You will not be able to sponsor tokens issued by other accounts.
Before you enable sponsorship, you must understand the following important points:
- The user can pay with sponsored tokens for transactions that do not involve this token. For example, an account with
Btokens in its balance can send
Btokens and attach
Atokens as fees.
- The user may not pay only the minimum transaction fee. For example, if a user has
100,000of your tokens, and you set the
100, the user will nevertheless be able to pay all of his
100,000tokens as the fee for a single transaction. You will receive
100,000 Atokens, and the miner will receive
1,000 WAVESfrom your account (
100,000/100 = 1,000).
Fees due dApp
Disclaimer: This feature is NOT documented and may stop working in the future. I DO NOT recommend using this method, but it is good to know.
The second option only works for InvokeScript transactions and is pretty simple. The advantage over sponsoring tokens is that you don’t need to use an internal token for your dApp.
Any user can call the dApp with
0 WAVES in their account. The transaction will succeed if the dApp transfers WAVES tokens to the user as a result of the invocation. This is easier to understand with an example.
Let’s say there’s dApp that either sends or does not send
1 WAVES to its caller, based on his address. (This example is completely unsafe and is provided only for explanation purposes. Never use this logic in your decentralized applications!)
If you call this function from an account with
0 WAVES balance, specifying fee as 0.005 Waves, the script will run. If the condition
takeRight(invoke.caller.bytes, 4) == base16'ABCD' returns TRUE, then the transaction will be considered valid and will go into the blockchain. The user will first receive
1 WAVES as a result of the call, and then the 0.005 fee will be deducted from his balance.
As a result, the user will have
0.995 WAVES in his account.
Attentive readers will have already recognized that the script above is unsafe because you can call it as many times as you want — if the account does not “win”
1 WAVES, then the transaction will not go to the blockchain and no fees are incurred. If the account does “win”, it will receive
0.995 WAVES. It’s effectively a lottery with free tickets.
The correct approach is to include additional conditions to check whether the user has the right to call the script for free or not — for example, to use a white-list.
I’d also like to note that if in the future, the contract execution model in Waves changes and transactions with exceptions (
throw ()) are also written to the blockchain, then the use of this functionality will become impossible.
A discussion of how the functionality described above works, and possible development paths, can be found in this issue on GitHub.
I recommend that both solutions are used only for trials of products. As a developer, you always have to check all boundary conditions, because improper use can lead to loss of funds.