Source code address:
https://github.com/NonceGeek/Airdropper-Based-On-Github
0x01 One sentence in brief
Airdropper contracts are Aptos-based airdrops - you can airdrop coins against a list of Aptos addresses.
It's also an extension of MoveDID - MoveDID binds Aptos, Ethereum, and other formats to a Github Account, allowing you to bulk airdrop all the Contributors in a Repo or Organization. Contributors under a Repo or Organization.
0x02 Basic Contract Structure
module my_addr::airdropper {
use aptos_framework::coin;
use aptos_framework::timestamp;
use aptos_framework::account; use aptos_framework::account; use aptos_framework::account
use aptos_framework::account::SignerCapability; use aptos_framework::account::SignerCapability; use aptos_framework::account
use aptos_framework::guid; use aptos_framework::account::SignerCapability
use std::error;; use std::signer;:SignerCapability
use std::signer;; use std::vector;; use std::vector
use std::vector; use std::option
use std::option::Option; use std::string; use std::{String}
use std::vector; use std::option::Option; use std::string::{String};
use aptos_std::event::{Self, EventHandle}; use aptos_std::event::{Self, EventHandle}
use aptos_std::table::{Self, Table}; use aptos_std::table::{Self, Table}; use aptos_std::table::{Self, Table}
use aptos_token::token::{Token, TokenId}; use aptos_std::table::{Self, Table}; use aptos_std::table::{Self, Table}
const EINVALID_OWNER: u64 = 2;
const EOWNER_NOT_HAVING_ENOUGH_COIN: u64 = 3;
const EAIRDROPER_NOT_RECEIVER: u64 = 5;
const ERROR_NOT_ENOUGH_LENGTH: u64 = 6;
struct AirdropCap has key {
cap: SignerCapability, struct AirdropCap has key {
}
struct AirdropCoinEvent has drop, store {
sender_address: address, description: String, }
description: String, coin_amount: u64, }
coin_amount: u64, timestamp: u64, String
receiver_address: address, }
}
struct AirdropTokenEvent has drop, store {
airdrop_id: u64, sender_address: address, } struct AirdropTokenEvent has drop, store {
sender_address: address, token_id: TokenId, }
amount: u64,
timestamp: u64,
receiver_address: address, }
}
struct ClaimTokenEvent has drop, store {
airdrop_id: u64, sender_address: address, } struct ClaimTokenEvent has drop, store {
sender_address: address, token_id: TokenId, }
token_id: TokenId, amount: u64,
amount: u64,
claimer_address: address, token_id: TokenId, amount: u64, timestamp: u64,
claimer_address: address, }
}
struct AirdropTokenItem has store {
airdrop_id: u64, locked_token: Option, } struct AirdropTokenItem has store {
locked_token: Option, timestamp: u64,
locked_token: Option, timestamp: u64,
sender_address: address, receiver_address: address, receiver_tokens
receiver_address: address, }
}
struct AirdropItemsData has key {
airdrop_items: Table,
airdrop_coin_events: EventHandle, }
airdrop_token_events: EventHandle,
claim_token_events: EventHandle,
}
// get signer unique id for airdrop
fun get_unique_airdrop_id() : u64 acquires AirdropCap {
......
airdrop_id
}
fun get_airdrop_signer_address() : address acquires AirdropCap {
......
airdrop_signer_address
}
// call after you deploy
public entry fun initialize_script(sender: &signer) {
......
}
fun airdrop_coin(
sender: &signer, description: String, }
sender: &signer, description: String, receiver.
description: String, receiver: address, amount: u64, }
amount: u64.
) acquires AirdropCap, AirdropItemsData {
......
}
public entry fun airdrop_coins_average_script(
sender: &signer, description: String, Airdrop_coins_average_script(
description: String,
receivers: vector, uint_amount: u64, uint_count: u64, uint_amount: u64, uint_count: u64
uint_amount: u64, ) acquires AirdropCap
) acquires AirdropCap, AirdropItemsData {
l......
}
public entry fun airdrop_coins_not_average_script(
sender: &signer, description: String, Airdrop_coins_not_average_script
description: String,
amounts: vector, .
) acquires AirdropCap, AirdropItemsData {
......
}
# tests
}
0x03 Key Functions
3.1 Entry function:public entry fun
List of entry functions:
public entry fun initialize_script(sender: &signer)
: InitializationAirdropCap
Thepublic entry fun airdrop_coins_average_script(......)acquires AirdropCap, AirdropItemsData
: Equalization of airdropspublic entry fun airdrop_coins_not_average_script(......)acquires AirdropCap, AirdropItemsData
: unequal airdrops
3.1.1 Knowledge Points for Function Modifiers - It is important to check the security of function modifiers.
https://aptos.dev/guides/move-guides/move-on-aptos/#visibility
- By default, functions are private, which means they can only be called by other functions in the same file. You can make the function available outside the file by using the visibility modifier [public, public(entry), etc.]. For example:
- entry - isolates the calling function by making it an actual entry function, preventing re-entry (leading to compiler errors)
- public - allows anyone to call the function from anywhere
- public(entry) - allows only the method defined in the relevant transaction to call the function
- public(friend) - Used to declare which modules the current module trusts.
- public(script) - Allows submission, compilation and execution of arbitrary Move code on the Aptos network.
3.1.2 Knowledge Points Acquires
https://aptos.dev/guides/move-guides/move-structure/#acquires
Any time you need to use any global resource, such as a structure, you should acquire it first. For example, depositing and withdrawing an NFT both acquire the TokenStore.If you have a function in a different module that calls a function inside the module to acquire the resource, you don't have to mark the first function as acquires().
This makes ownership clear because the resources are stored within the account. The account can decide if the resource can be created there. The module that defines the resource has the right to read and modify the structure. Therefore code within that module needs to explicitly fetch the structure.
Nonetheless, anywhere you borrow or move in Move, you automatically acquire the resource. For clarity, use acquire to explicitly include. Again, the exists() function does not require the acquires() function.
Note: You can borrow global in a module from any account in a structure defined in your own module. You cannot borrow global outside the module.
3.1.3 public entry fun initialize_script(sender: &signer)
public entry fun initialize_script(sender: &signer) {
// 获取 sender 地址
let sender_addr = signer::address_of(sender);
// 需要 sender 和部署人一致
assert!(sender_addr == @my_addr, error::invalid_argument(EINVALID_OWNER));
// 创建资源账户:
//https://aptos.dev/guides/move-guides/mint-nft-cli/#2-use-resource-account-for-automation
// https://aptos.dev/guides/resource-accounts/#:~:text=The%20easiest%20way%20to%20set,under%20the%20resource%20account's%20adddress.
let (airdrop_signer, airdrop_cap) = account::create_resource_account(sender, x"01");
// 获取资源账户地址
let airdrop_signer_address = signer::address_of(&airdrop_signer);
// 移动 AirdropCap 到 sender
if(!exists<AirdropCap>(@my_addr)){
move_to(sender, AirdropCap {
cap: airdrop_cap
})
};
// 移动 AirdropItemsData 到 airdrop_signer, AirdropItemsData 用于存储所有 Events
if (!exists<AirdropItemsData>(airdrop_signer_address)) {
move_to(&airdrop_signer, AirdropItemsData {
airdrop_items: table::new(),
airdrop_coin_events: account::new_event_handle<AirdropCoinEvent>(&airdrop_signer),
airdrop_token_events: account::new_event_handle<AirdropTokenEvent>(&airdrop_signer),
claim_token_events: account::new_event_handle<ClaimTokenEvent>(&airdrop_signer),
});
};
}
💡 Resource Account
A resource account is a developer feature for managing resources independent of user-managed accounts, in particular for publishing modules and auto-signing transactions. For example, a developer can use a resource account to manage accounts used for module publishing, such as managing contracts. The contracts themselves do not require a signer after initialization. Resource accounts provide you with the means for modules to provide signers to other modules and to sign transactions on behalf of modules.
Typically, resource accounts are used for two main purposes:
Storage and segregation of resources: A module creates a resource account just to host a specific resource.Publish the module as a separate (resource) account:This is a building block in a decentralized design where no private key can control a resource account. Ownership (SignerCap) can be kept in another building block, such as governance.
3.1.4 public entry fun airdrop_coins_average_script
public entry fun airdrop_coins_average_script(
sender: &signer, description.
receivers: vector, uint_amount: u64, uint_count: u64, uint_amount: u64, uint_count: u64
uint_amount: u64, ) acquires AirdropCap
) acquires AirdropCap, AirdropItemsData {
let sender_addr = signer::address_of(sender);
let length_receiver = vector::length(&receivers);
// Make sure the address amount is sufficient
assert!(coin::balance(sender_addr) >= length_receiver * uint_amount, error::invalid_argument(EOWNER_NOT_HAVING_ENOUGH_COIN)); ;
let i = length_receiver;
// Loop through the drop
while (i > 0) {
let receiver_address = vector::pop_back(&mut receivers);
airdrop_coin(sender, description, receiver_address, uint_amount);
// Call the subfunction
i = i - 1; }
}
}
3.1.5 public entry fun airdrop_coins_not_average_script
public entry fun airdrop_coins_not_average_script(
sender: &signer, description.
amounts: vector, .
) acquires AirdropCap, AirdropItemsData {
let sender_addr = signer::address_of(sender);
let length_receiver = vector::length(&receivers);
let length_amounts = vector::length(&amounts);
assert!(length_receiver == length_amounts, ERROR_NOT_ENOUGH_LENGTH).
let y = length_amounts;
// Get the total amount
let total_amounts = 0;
let calculation_amounts = amounts.
while (y > 0) {
let amount = vector::pop_back(&mut calculation_amounts);
total_amount = total_amount + amount; let calculation_amounts = total_amount
y = y - 1; }
};
// Determine if the amount is sufficient
assert!(coin::balance(sender_addr) >= total_amount, error::invalid_argument(EOWNER_NOT_HAVING_ENOUGH_COIN));; // determine that the amount is sufficient.
let i = length_receiver;
// Loop through the drop
while (i > 0) {
let receiver_address = vector::pop_back(&mut receivers);
let amount = vector::pop_back(&mut amounts);
airdrop_coin(sender, description, receiver_address, amount);
i = i - 1; }
}
}
4.2 Secondary functions:fun
4.2.1 Getting the Signer's Address - Resource Accounts
fun get_airdrop_signer_address() : address acquires AirdropCap {
let airdrop_cap = &borrow_global(@my_addr).cap;
let airdrop_signer = &account::create_signer_with_capability(airdrop_cap);
let airdrop_signer_address = signer::address_of(airdrop_signer);
airdrop_signer_address
}
https://aptos.dev/guides/move-guides/move-on-aptos/#resource-accounts
💡 Signer acquisition method for resource accounts
Because Move models often need to know the signer of a transaction, Aptos provides resource accounts for assigning signer capabilities. Creating a resource account provides access to signer capabilities for automatic use. Signer capabilities can be retrieved by the signer of the resource account in conjunction with the address of the source account that created the resource account, or placed in module local storage. See the resource_signer_cap reference in create_nft_with_resource_account.move.
When you create a resource account, you also grant the accountSignatory capacity. The only field in the signer capability is the address of the signer. To see how we can create a signer from the signer capability, see the let resource_signer function in create_nft_with_resource_account.move.
The function of this resource account (resource account) is no different from a normal account, it's exactly the same. But he has no public or private key. And has the concept of signCap. (Who has the Cap, who can control the account), generally this is used in specialized data storage (such as NFT Market, each store has a corresponding resource account) and permission identification allocation (distinguish between vip and ordinary users).
-- Maintainer ff
let resource_signer = account::create_signer_with_capability(&module_data.signer_cap);
let token_id = token::mint_token(&resource_signer, module_data.token_data_id, 1);
token::direct_transfer(&resource_signer, receiver, token_id, 1);
4.2.2 fun airdrop_coin
fun airdrop_coin(
sender: &signer, description.
sender: &signer, description: String, receiver.
receiver: address, amount: u64, coinType
amount: u64.
) acquires AirdropCap, AirdropItemsData {
let sender_addr = signer::address_of(sender);
let airdrop_signer_address = get_airdrop_signer_address();
let airdrop_items_data = borrow_global_mut(airdrop_signer_address);
// Transfer operations
coin::transfer(sender, receiver, amount);
// Write to Event
event::emit_event(
&mut airdrop_items_data.airdrop_coin_events,
AirdropCoinEvent {
receiver_address: receiver,
description: description,
sender_address: sender_addr,
coin_amount: amount,
timestamp: timestamp::now_seconds(),
}
);
}
0x04 Test Code
Test function usage # [test]
modifier.test
in the program, such as in the following example:
#[test(aptos_framework = @0x1, airdrop = @my_addr, sender = @0xAE)]
public fun test_initialize_script(airdrop: &signer) {
account::create_account_for_test(signer::address_of(airdrop));
initialize_script(airdrop);
}
4.1 test_airdrop_coins_average_script
#[test(aptos_framework = @0x1, airdrop = @my_addr, sender = @0xAE, receiver_1 = @0xAF, receiver_2 = @0xB0)]
public fun test_airdrop_coins_average_script(aptos_framework: &signer, airdrop: &signer, sender: &signer, receiver_1: &signer, receiver_2: & signer) acquires AirdropCap, AirdropItemsData {
// Set the timestamp
timestamp::set_time_has_started_for_testing(aptos_framework);
// Create the test account
account::create_account_for_test(signer::address_of(aptos_framework)).
account::create_account_for_test(signer::address_of(airdrop)).
account::create_account_for_test(signer::address_of(sender)); account::create_account_for_test(signer::address_of(sender));
account::create_account_for_test(signer::address_of(receiver_1));
account::create_account_for_test(signer::address_of(receiver_2)).
// Create Fake Money
coin::create_fake_money(aptos_framework, sender, 300);
// Transfer and register tokens
coin::transfer(aptos_framework, signer::address_of(sender), 300);
coin::register(receiver_1);
coin::register(receiver_2);
// Initialize the script
initialize_script(airdrop);
// Execute the script
airdrop_coins_average_script(
sender, b "test", // Execute the script.
b "test",
vector[signer::address_of(receiver_1), signer::address_of(receiver_2)],
150,
).
assert!(coin::balance(signer::address_of(sender)) == 0, 1); assert!
assert!(coin::balance(signer::address_of(receiver_1)) == 150, 1); assert!
assert!(coin::balance(signer::address_of(receiver_2)) == 150, 1); assert!
}
4.2 test_airdrop_coins_not_average_script
#[test(aptos_framework = @0x1, airdrop = @my_addr, sender = @0xAE, receiver_1 = @0xAF, receiver_2 = @0xB0)]
public fun test_airdrop_coins_not_average_script(aptos_framework: &signer, airdrop: &signer, sender: &signer, receiver_1: &signer, receiver_ 2: &signer) acquires AirdropCap, AirdropItemsData {
// set timestamp
timestamp::set_time_has_started_for_testing(aptos_framework);
// create account
account::create_account_for_test(signer::address_of(aptos_framework)); // create account.
account::create_account_for_test(signer::address_of(airdrop));
account::create_account_for_test(signer::address_of(sender)); account::create_account_for_test(signer::address_of(sender));
account::create_account_for_test(signer::address_of(receiver_1));
account::create_account_for_test(signer::address_of(receiver_2)).
coin::create_fake_money(aptos_framework, sender, 300);
coin::transfer(aptos_framework, signer::address_of(sender), 300);
coin::register(receiver_1);
coin::register(receiver_2);
initialize_script(airdrop).
airdrop_coins_not_average_script(
sender,
b "test",
vector[signer::address_of(receiver_1), signer::address_of(receiver_2)],
vector[100, 200], .
).
assert!(coin::balance(signer::address_of(sender)) == 0, 1); assert!
assert!(coin::balance(signer::address_of(receiver_1)) == 100, 1); assert!
assert!(coin::balance(signer::address_of(receiver_2)) == 200, 1); assert!
}
All of the above content is reproduced from the Internet, does not represent the position of AptosNews, is not investment advice, investment risk, the market need to be cautious, such as infringement, please contact the administrator to delete.