Cashdrop Covenant (Merchant-linked)

This covenant contains the BCH that users collect. Version 2 cashdrop covenants belong to a quest that was linked to a merchant during creation. Aside from a BCH reward, this cashdrop covenants also comes with an additional voucher reward that can be spent on only to the merchant it is linked to.

PARAMETERS

NameTypeDescription
cashdropLATintLatitude coordinate of the cashdrop. Since CashScript does not support decimals, this 5-decimal precision location is converted to an int by multiplying it by 100,000.
cashdropLONintLongitude coordinate of the cashdrop. Since CashScript does not support decimals, this 5-decimal precision location is converted to an int by multiplying it by 100,000.
collectAmountintThe BCH reward amount that gets sent to a collector immediately during collection.
claimAmountintThe BCH value of the voucher reward.
expirationTimestampintThe timestamp when the uncollected cashdrop will be refunded.
questFunderPKHbytes20The cashdrop’s quest funder’s public key hash, where the cashdrop reward will be refunded after expiration.
merchantVaultbytes35The https://www.notion.so/PurelyPeer-7214628c587c4778b429353684f8bf0c?pvs=21 locking bytecode, where the https://www.notion.so/PurelyPeer-7214628c587c4778b429353684f8bf0c?pvs=21 of the voucher will be sent.

FUNCTIONS

  • refund = used to refund the funds to the quest funder in case of a server or network error during distribution. The amount refunded is the original quest amount subtracted by the transaction fee (dust).

Parameters:

None

  • collect = the main function for verifying and sending the BCH and voucher rewards to the collector. It first checks if the user is truly inside the cashdrop’s collect radius, checks if the vouchers are immutable NFTs, before it proceeds with sending of rewards to the collector.

Parameters:

NameTypeDescription
collectorLATintLatitude coordinate of the collector. Since CashScript does not support decimals, this 5-decimal precision location is converted to an int by multiplying it by 100,000.
collectorLONintLongitude coordinate of the collector. Since CashScript does not support decimals, this 5-decimal precision location is converted to an int by multiplying it by 100,000.
collectRadiusintThe current cashdrop radius range for collection.

CONTRACT SCRIPT

pragma cashscript ^0.8.0;

contract CashDropCovenant (
    int cashdropLAT,            // latitude of cashdrop
    int cashdropLON,            // longitude of cashdrop
    int collectAmount,          // total BCH sent to collector after collecting this cashdrop
    int claimAmount,            // total BCH spent to merchant after claiming locked NFT
    int expirationTimestamp,    // uncollected cashdrop expiration time since quest was created
    bytes20 questFunderPKH,     // quest funder/payment addresses' pubkey hash
    bytes35 merchantVault       // merchant vault contract's locking byte code
) {
    /**
        Refund cashdrop reward to quest owner after expiry date.
        Expiry date is X days after quest date of funding
     */
    function refund () {
        require(tx.time >= expirationTimestamp);

        int totalCashdropAmount = claimAmount + collectAmount;
        require(tx.outputs[0].value == totalCashdropAmount);

        bytes25 owner = new LockingBytecodeP2PKH(questFunderPKH);
        require(tx.outputs[0].lockingBytecode == owner);
    }

    /**
        Verifies if collector is within collection radius
     */
    function collect (
        int collectorLAT,          // latitude of collector during collection
        int collectorLON,          // longitude of collector during collection
        int collectRadius,         // cashdrop range where a user can collect a cashdrop
    ) {
        // ==================================
        // constraints on location and BCH reward output
        // ==================================

        /**
            outputs
            0: key NFT to collector address
            1: lock NFT to merchant vault address
            2: quest NFT to quest funder/payment address
            3: BCH reward to collector address
         */
        require(tx.outputs.length >= 4);

        // ensure BCH reward value is correct
        require(tx.outputs[3].value == collectAmount);

        /**
            calculate distance between collector and cashdrop locations
            using trigonometric functions alternatives since trig funcs arent
            supported on cashscript
         */
        int cashdropLATP = cashdropLAT + 9000000;
        int collectorLATP = collectorLAT + 9000000;
        int cashdropLONP = cashdropLON + 18000000;
        int collectorLONP = collectorLON + 18000000;

        int X1 = collectorLONP - cashdropLONP;
        int X2 = abs(X1);
        int mod = X2 % 10;
        
        if (mod < 5) {
            X2 = X2 - mod;
        } else {
            X2 = (X2 - mod) + 10;
        }

        int X3_1 = X2 / 10;
        int X3_2 = X2 - 360;
        int X4 = abs(X3_2) / 10;

        int Y1 = abs(collectorLATP - cashdropLATP);

        int Z1 = max(X3_1, Y1);
        int Z2 = max(X4, Y1);
        
        int calculatedDistance = min(Z2, Z1);

        require(calculatedDistance <= collectRadius);

        // ==================================
        // constraints on NFT outputs
        // ==================================

        bytes outputKeyNftCategory = tx.outputs[0].tokenCategory;
        bytes outputLockNftCategory = tx.outputs[1].tokenCategory;
        bytes outputQuestNftCategory = tx.outputs[2].tokenCategory;

        // lock, key and quest NFTs must be the same category
        require(outputKeyNftCategory == outputLockNftCategory);
        require(outputLockNftCategory == outputQuestNftCategory);

        bytes keyNftCommitment = tx.outputs[0].nftCommitment;
        bytes lockNftCommitment = tx.outputs[1].nftCommitment;

        // commitment of key and lock NFTs must be the same
        require(keyNftCommitment == lockNftCommitment);

        // after checking if both NFTs have the same commitment,
        // we check if their amount in the commitment is > 0 and equal to the claim amount
        bytes keyNftAmountBytes = tx.outputs[0].nftCommitment.split(20)[1];
        int lockedAmount = int(keyNftAmountBytes);

        require(lockedAmount == claimAmount);
        require(lockedAmount > 0);

        // ensure that lock NFT goes to merchant vault contract
        require(tx.outputs[1].lockingBytecode == merchantVault);

        // ensure that quest NFT goes to quest funder/payment address
        bytes25 questFunder = new LockingBytecodeP2PKH(questFunderPKH);
        require(tx.outputs[2].lockingBytecode == questFunder);
    }

}