Decentralized affiliate program on the Waves blockchain, implemented as part of a Waves Labs grant by the Bettex team.
The post is not advertising! The program has open source code, its use and distribution for free. The use of the program stimulates the development of dApp applications and generally contributes to decentralization, which benefits every web user.

The submitted dApp for affiliate programs is a template for projects involving affiliate as part of its functionality. The code can be used as a template for copying, as a library, or as a set of ideas for technical implementation.
In terms of functionality, this is the usual affiliate system that implements registration with the referrer, multi-level accrual of referral fees and motivation for registration in the system (cashback). The system is a “clean” dApp, that is, the web application interacts directly with the blockchain, without having its own backend, database, etc.
')
Technicians are used, which can also be useful in many other projects:
- Calling a smart account in debt with immediate repayment (at the time of the call, there are no tokens on the account to pay for the call, but they appear there as a result of the call).
- PoW-captcha - protection from high-frequency automated calling functions of a smart account - an analog of captcha, but through evidence of the use of computing resources.
- Request to data keys by pattern.
The application consists of:
- the code of the smart account in the language of ride4dapps (which, according to the idea, is merged in the main smart account, for which it is necessary to implement the affiliate-functionality);
- js-wrappers that implement the level of abstraction over the WAVES NODE REST API;
- code on the vuejs framework, which is an example of the use of the library and RIDE code.
We describe all the listed features.
Call a smart account in debt with immediate repaymentInvokeScript call requires payment of the commission from the account initiating the transaction. This is not a problem if you are doing a project for blockchain geeks who have a certain number of WAVES tokens on their account, but if the product is intended to be used by the masses, this becomes a serious problem. After all, the user must attend to the purchase of WAVES tokens (or another suitable asset that can be used to pay for transactions), which raises the already considerable threshold for entering the project. We can distribute asset to users, which will allow to pay for transactions and face the risk of their misuse, when automated systems are created for siphoning liquid assets from our system.
It would be very convenient if it was possible to make an InvokeScript call “at the expense of the recipient” (the smart account on which the script is installed), and this possibility, although not in an obvious way, exists.
If inside InvokeScript you make ScriptTransfer to the address of the caller, who compensates for the tokens spent on the fee, then the call will succeed, even if there were no assets at the time of the call. This is possible due to the fact that checking the presence of a sufficient number of tokens is performed after calling the transaction, and not before it, so that you can make transactions on credit, provided they are immediately redeemed.
ScriptTransfer (i.caller, i.fee, unit)The code below refund the spent fee at the expense of the smart account. To protect against misuse of this feature, you need to use a check that the caller spends the fee in the required asset and within reasonable limits:
func checkFee(i:Invocation) = { if i.fee > maxFee then throw(“unreasonable large fee”) else if i.feeAssetId != unit then throw(“fee must be in WAVES”) else true }
Also, to protect against malicious and senseless waste of funds, protection against automatic calling (PoW-captcha) is necessary.
Pow-captchaThe idea of ​​proof-of-work captcha is not new and has already been implemented in various projects, including those based on WAVES. The idea is to make the action that consumes the resources of our project, the caller must spend their own resources, which makes the attack on the depletion of resources quite expensive. For very easy and low-cost validation of the fact that the sender of the transaction has solved the PoW task, there is a transaction id check:
if take (toBase58String (i.transactionId), 3)! = “123” then throw (“proof of work failed”) elseIn order to conduct a transaction, the caller must select such parameters so that its base58 code (id) starts at 123, which corresponds to an average of a couple of tens of seconds of processor time and is generally reasonable for our task. If a simpler or more complex PoW is required, then the task is easy to modify in an obvious way.
Request for data keys by patternIn order to use blockchain as a database, it is vital to have API tools for database requests as a key-val for templates. Such a toolkit appeared in early July 2019 as the
? Matches parameter of the REST API request
/ addresses / data? Matches = regexp . Now, if we need to get not one key from the web application and not all keys at once, but only some group, then we can make a selection by the name of the key. For example, in this project, withdrawal transactions are encoded as
withdraw_${userAddress}_${txid}
which allows you to get a list of withdrawal transactions for any given address using a pattern:
?matches=withdraw_${userAddress}_.*
Now we will analyze the components of the finished solution.
Vuejs codeThe code is a working demonstration, close to the real project. He realizes logging in through Waves Keeper and working with the library affiliate.js, with which he registers the user in the system, polls transaction data, and also allows you to withdraw the money earned to the user account.
RIDE codeConsists of the functions register, fund and withdraw.
The register function registers in the user's system. It has two parameters: referer (the address of the referer) and the salt parameter that is not used in the function code, which is needed for matching the transaction id (the PoW-captcha task).
The function (like the other functions from this project) uses the call-in-call technique, the result of the function is the financing of the payment of a fee for calling this function. Thanks to this solution, the user who just created the wallet can immediately work with the system and he does not need to bother with the question of acquiring or receiving an asset that allows him to pay a transaction fee.
The result of the registration function are two entries:
${owner)_referer = referer ${referer}_referral_${owner} = owner
This allows direct and reverse searches (referrer of a given user and all referrals of a given user).
The fund function is rather a template for developing real functionality. In the presented form, it takes all the funds transferred by the transaction and distributes them to the referrer accounts 1, 2, 3 levels, to the cashback account and the change account (everything that remains in the distribution of the previous accounts gets here).
Cashback is a means of encouraging the end user to participate in the referral system. The user can withdraw the paid part of the commission by the system in the form of “cashback” in the same way as rewards for referrals.
When using the referral system, the fund function should be modified, embedded in the main logic of the smart account on which the system will operate. For example, if a referral reward is paid for a bet made, then the fund function must be built into the logic where the bet is made (or another target action is taken for which the reward is paid). This function is coded three levels of referral rewards. If you want to make more or less levels, then this is also correct in the code. The percentage of remuneration is given by the constants level1-level3, in the code it is calculated as the
amount * level / 1000 , that is, the value 1 corresponds to 0.1% (this can also be changed in the code).
The function call changes the account balance and also creates records for the purposes of logging:
fund_address_txid = address:owner:inc:level:timestamp timestamp ( ) func getTimestamp() = { let block = extract(blockInfoByHeight(height)) toString(block.timestamp) }
That is, the transaction time is the time of the block in which it is located. This is safer than using the timestamp from the transaction itself, especially since it is not accessible from callable.
The withdraw function displays all accrued rewards to a user account. Creates entries for logging purposes:
# withdraw log: withdraw_user_txid=amount:timestamp
applicationThe main part of the application is the library affiliate.js, which is a bridge between the affiliate data models and the WAVES NODE REST API. Implements a level of abstraction independent of the framework (any one can be used). Active functions (register, withdraw) assume that Waves Keeper is installed in the system, the library itself does not check this.
Implements methods:
fetchReferralTransactions fetchWithdrawTransactions fetchMyBalance fetchReferrals fetchReferer withdraw register
The functionality of the methods is obvious from the names, the parameters and the returned data are described in the code. Additional comments are required by the register function — it starts the cycle for selecting the transaction id so that it starts at 123 — this is the PoW-captcha described above, which protects against mass registrations. The function finds a transaction with the desired id, and then signs it through Waves Keeper.
The DEX affiliate program is available at
GitHub.com .