Open source decentralized affiliate program on the Waves blockchain

A decentralized affiliate program based on the Waves blockchain, implemented as part of a Waves Labs grant by the Bettex team.

Post is not sponsored! The program is open source, its use and distribution is free. The use of the program stimulates the development of dApp applications and, in general, promotes decentralization, which is beneficial for every user of the Network.

Open source decentralized affiliate program on the Waves blockchain

The presented dApp for affiliate programs is a template for projects that include affiliate as part of their 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 an ordinary affiliate system that implements registration with a referrer, multi-level accrual of remuneration for referrals and motivation for registering in the system (cashback). The system is a “pure” dApp, that is, the web application interacts directly with the blockchain without its own backend, database, etc.

Techniques are used that can also be useful in many other projects:

  • Calling a smart account on credit 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 against high-frequency automated calling of smart account functions - similar to captcha, but through proof of the use of computing resources.
  • Request to data keys by template.

The application consists of:

  • smart account code in the ride4dapps language (which, as planned, is merged into the main smart account, for which you need to implement the affiliate functionality);
  • js wrapper that implements an abstraction layer over the WAVES NODE REST API;
  • code on the vuejs framework, which is an example of using the library and RIDE code.

Let's describe all the listed features.

Calling a smart account into debt with immediate repayment

Calling InvokeScript requires payment of a fee from the account initiating the transaction. This is not a problem if you are doing a project for blockchain geeks who have a certain amount of WAVES tokens on their account, but if the product is aimed at 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 increases the already considerable threshold for entering the project. We can distribute assets to users who will be allowed to pay for transactions and face the risk of their misuse when automated systems are created to pump liquid assets from our system.

It would be very convenient if it would be possible to call InvokeScript “at the expense of the recipient” (the smart account on which the script is installed), and this possibility exists, although not in an obvious way.

If, inside InvokeScript, a ScriptTransfer is made to the address of the caller, which compensates for the tokens spent on the fee, then such a call will succeed, even if there were no assets on the calling account at the time of the call. This is possible because the check for sufficient tokens is made after the transaction is called, and not before it, so that it is possible to make transactions on credit, provided that they are immediately redeemed.

ScriptTransfer(i.caller, i.fee, unit)

The code below refunds the spent fee using smart account funds. To protect against misuse of this feature, you must use a check that the caller spends the fee in the right 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 call (PoW-captcha) is required.

PoW-captcha

The very idea of ​​proof-of-work captcha is not new and has already been implemented in various projects, including those based on WAVES. The point of the idea is that in order to perform an action that wastes our project's resources, the caller must also spend their own resources, which makes a resource depletion attack quite costly. For a very easy and low-cost validation that the sender of the transaction has solved the PoW problem, there is a transaction id check:

if take(toBase58String(i.transactionId), 3) != “123” then throw(“proof of work failed”) else

In order to conduct a transaction, the caller must choose such parameters so that its base58 code (id) begins with the numbers 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 can be easily modified in an obvious way.

Query data keys by template

In order to use the blockchain as a database, it is vital to have API tools for querying the database as a key-val using templates. Such a toolkit appeared in early July 2019 as a parameter ?matches at the REST API request /addresses/data?matches=regexp. Now, if we need to get more than one key and not all keys at once from the web application, 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 transactions for withdrawal of funds for any given address using the template:

?matches=withdraw_${userAddress}_.*

Now let's analyze the components of the finished solution.

vuejs code

The code is a working demo, close to a real project. It implements login through Waves Keeper and work with the affiliate.js library, with the help of which it registers a user in the system, queries transaction data, and also allows you to withdraw earned funds to the user's account.

Open source decentralized affiliate program on the Waves blockchain

Code on RIDE

Consists of register, fund and withdraw functions.

The register function registers a user in the system. It has two parameters: referer (referer address) and the salt parameter not used in the function code, which is needed to select the transaction id (PoW-captcha task).

The function (like the rest of the functions in this project) uses the borrowing technique, the result of the function is funding the payment of a fee for calling this function. Thanks to this solution, a user who has just created a wallet can immediately work with the system and does not need to be puzzled by the issue of acquiring or receiving an asset that allows him to pay a transaction fee.

The result of the registration function is two records:

${owner)_referer = referer
${referer}_referral_${owner} = owner

This allows forward and backward lookups (referrer of the given user and all referrals of the given user).

The fund function is more of 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 of the 1st, 2nd, 3rd levels, to the “cashback” account and the “change” account (everything that remains during distribution to previous accounts gets here).

Cashback is a means of incentivizing the end user to participate in the referral system. The part of the commission paid by the system in the form of “cashback” can be withdrawn by the user in the same way as rewards for referrals.

When using the referral system, the fund function should be modified, built into the main logic of the smart account on which the system will work. For example, if a referral reward is paid for a bet made, then the fund function should be built into the logic where the bet is made (or another target action is performed for which the reward is paid). There are three levels of referral rewards coded into this feature. If you want to make more or less levels, then this is also corrected in the code. The reward percentage is set by the level1-level3 constants, in the code it is calculated as 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 balance of the account and also creates entries for the purpose of logging the form:

fund_address_txid = address:owner:inc:level:timestamp
Для получения timestamp (текущего времени) используется такая вот связка
func getTimestamp() = {
let block = extract(blockInfoByHeight(height))
toString(block.timestamp)
}

That is, the time of the transaction is the time of the block in which it is located. This is more reliable than using the timestamp from the transaction itself, especially since it is not available from the callable.
The withdraw function withdraws all accumulated rewards to the user's account. Creates entries for logging purposes:

# withdraw log: withdraw_user_txid=amount:timestamp

application

The main part of the application is the affiliate.js library, which is a bridge between the affiliate data models and the WAVES NODE REST API. Implements a framework-independent abstraction layer (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 return data are described in the code. The register function requires additional comments - it starts the transaction id selection cycle 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 required id, and then signs it through Waves Keeper.

DEX affiliate program available at GitHub.com.

Source: habr.com

Add a comment