Airdrop Manager

The Airdrop Manager contract can be used to distribute tokens to a list of addresses. The contract uses a Merkle tree to store the list of addresses and their corresponding token amounts. The root of the Merkle tree is stored on the contract's campaign and is used to verify the validity of the proofs submitted by the recipients.

Features

  • Merkle tree based distribution

  • Lump sum and/or linear vesting distribution. Two distribution types are supported simultaneously. For instance one could be a lump sum distribution and the other could be a linear vesting distribution.

  • Only one campaign can be active at a time. If there's an error with the current campaign, the owner can take a snapshot of all the tokens claimed up to that point with the Claimed query, end the campaign and then create a new one with the right data.

  • The owner of the contract is the only one who can create campaigns. A different address can be assigned as the owner of the campaign upon creation.

  • The owner of the campaign can top up the campaign with more tokens at any point before the campaign ends.

  • The owner of the campaign or the owner of the contract can end the active campaign at any point before the campaign ends. When a campaign is ended, the owner of the campaign will receive the remaining, unclaimed tokens in the campaign.

Generating airdrop data

Generating the merkle root and proofs can be done by using the js scripts on the src code:

  • First, configure the csv file, which is formatted as formatted (with header) as contract_addr, address, amount.

contract_addr,address,amount
mantra1j28m8g0afvfr23423k5wypfykqrxsu94xhxvxdeyrfc4jkqm7zhqckdf5w,mantra1x5nk33zpglp4ge6q9a8xx3zceqf4g8nvaggjmc,100
mantra1j28m8g0afvfr23423k5wypfykqrxsu94xhxvxdeyrfc4jkqm7zhqckdf5w,mantra1rj2n3hge32n5u6zzw0u7clrys76srapulsvv39,200
mantra1j28m8g0afvfr23423k5wypfykqrxsu94xhxvxdeyrfc4jkqm7zhqckdf5w,mantra18mv5sz7nj2arpsqjc2aeslhh3v475np8ng6tt5,300
mantra1j28m8g0afvfr23423k5wypfykqrxsu94xhxvxdeyrfc4jkqm7zhqckdf5w,mantra16qtk5fnm4se6362yaah0scdmatx0qvp70fhej2,400

node merkle_root.js -> generates the merkle root

node merkle_proof.js -> generates the proofs. Consider tweaking the variables in the script to generate valid proofs according to the csv data.

Use the merkle root when creating an airdrop campaign, and the merkle proofs to claim the airdrop with a given address.

How it works

The Airdrop Manager contract is meant to host a single active airdrop campaign per contract at a given time. When instantiating the contract, an owner can be specified. Only the owner of the contract can create new campaigns.

Once the contract is instantiated, a campaign can be created by the contract owner. When creating a campaign, there's a possibility to appoint a different address as the owner of the campaign. Only the owner of the campaign can top up the campaign.

If there's a mistake on the token distribution, i.e. the airdrop data was incorrect, the campaign can be ended by either the owner of the contract or campaign, returning the unclaimed funds to the owner of the campaign. Then, a new contract can be created on the same contract.

To claim an airdrop, the user needs to provide a valid merkle proof.

Instantiate

Instantiates an instance of the farm manager contract

{
  "owner": "mantra1..."
}
KeyTypeDescription

owner

Option<String>

The owner of the contract. If none is provided, the address instantiating the contract becomes the owner.

ExecuteMsg

ManageCampaign

Manages a campaign based on the action, which can be:

  • CreateCampaign: Creates a new airdrop campaign, making sure all params are correct. Only the contract owner can perform this action.

  • TopUpCampaign: Adds additional funds to the active campaign to be distributed. Only the campaign owner can perform this action.

  • EndCampaign: Ends the campaign. The remaining unclaimed funds are returned the to owner of the campaign. Both the owner of the contract and campaign can perform this action.

Once created, the campaign is stored in the CAMPAIGN item.

{
  "manage_campaign": {
    "action": {
      "create_campaign": {
        "params": {
          "owner": "mantra1...",
          "name": "Airdrop",
          "description": "This is an airdrop description.",
          "reward_asset": {
            "denom": "uom",
            "amount": "888888888888"
          },
          "distribution_type": [
            {
              "lump_sum": {
                "percentage": "0.25",
                "start_time": 1571797419,
                "end_time": 1572402219
              }
            },
            {
              "linear_vesting": {
                "percentage": "0.75",
                "start_time": 1572402219,
                "end_time": 1573007019
              }
            }
          ],
          "cliff_duration": 86400,
          "start_time": 1571797419,
          "end_time": 1573007019,
          "merkle_root": "b32b978b07b56e8f10de1f098390407017daa61c90da6a6875ca0f2d655b6107"
        }
      }
    }
  }
}
KeyTypeDescription

owner

Option<String>

The campaign owner. If none is provided, the sender of the message will the owner.

name

String

The name of the campaign. The name cannot be empty nor above 50 chars in length.

description

String

The description of the campaign. The description cannot be empty nor above 500 chars in length.

reward_asset

Coin

The asset to be distributed as reward by the campaign

distribution_type

Vec<DistributionType>

The distribution types. It can be two at most, and it can be a combination of LumpSum and LinearVesting. The percentage of both distributions needs to be equal to 100%, and their start time needs to be in the future.

cliff_duration

Option<u64>

If set, the duration of the cliff, in seconds.

start_time

u64

The campaign start time (unix timestamp), in seconds.

end_time

u64

The campaign end timestamp (unix timestamp), in seconds.

merkle_root

String

The campaign merkle root.

Note: the reward_asset must be sent with this transaction.

Claim

Claims rewards from a campaign.

{
  "claim" {
    "total_claimable_amount": "88888",
    "receiver": "mantra1...",
    "proof": [
        "1b20d6e1fa2e464d3a94fabdf28add25b6152663aa19efe1b6da2f28f50412cd",
        "27c16d1dd47dedab5c9d394d02b507e3abd01ef11f753ffc3148b04bd1aa0487"
    ]
  }
}
KeyTypeDescription

total_claimable_amount

Uint128

The total claimable amount from the campaign.

receiver

Option<String>

The receiver address of the claimed rewards. If not set, the sender of the message will be the receiver. This is useful for allowing a contract to do the claim operation on behalf of a user.

proof

Vec<String>

A Vector of all necessary proofs for the merkle root verification, hex-encoded.

UpdateOwnership(::cw_ownable::Action)

Implements cw_ownable. Updates the contract's ownership. ::cw_ownable::Action can be TransferOwnership, AcceptOwnership and RenounceOwnership.

Note: This is a cw_ownable message.

Propose to transfer the contract's ownership to another account, optionally with an expiry time. Can only be called by the contract's current owner. Any existing pending ownership transfer is overwritten.

{
  "update_ownership": {
    "transfer_ownership": {
      "new_owner": "mantra1...",
      "expiry": {
        "at_height": "424242424242"
      }
    }
  }
}
KeyTypeDescription

new_owner

String

The new owner proposed,

expiry

Option<Expiration>

Optional expiration time parameter.

QueryMsg

Campaign

Get the airdrop campaign.

{
  "campaign": {}
}

Rewards

Get the rewards for a specific campaign and receiver address.

{
  "rewards": {
    "total_claimable_amount": "8888",
    "receiver": "mantra1...",
    "proof": [
      "1b20d6e1fa2e464d3a94fabdf28add25b6152663aa19efe1b6da2f28f50412cd",
      "27c16d1dd47dedab5c9d394d02b507e3abd01ef11f753ffc3148b04bd1aa0487"
    ]
  }
}
KeyTypeDescription

total_claimable_amount

Uint128

The total claimable amount for the campaign.

receiver

String

The address to get the rewards for.

proof

Vec<String>

A Vector with the necessary proofs for the merkle root verification, hex-encoded.

Claimed

Get the total amount of tokens claimed on the campaign.

{
  "claimed": {
    "address": "mantra1...",
    "start_from": "mantra1...",
    "limit": 50
  }
}
KeyTypeDescription

address

Option<String>

If provided, it will return the tokens claimed by the specified address.

start_from

Option<String>

If provided, the address to start querying from. Used for paginating results.

limit

Option<u8>

The maximum number of items to return. If not set, the default value is used. Used for paginating results.

Last updated