当前位置:网站首页>154 Solana distribution token
154 Solana distribution token
2022-06-21 06:39:00 【Lich Howger】
How to be in Solana To distribute token
Let's look at a sample code
https://github.com/saber-hq/merkle-distributorLet's take a look
pub fn new_distributor(
ctx: Context<NewDistributor>,
_bump: u8,
root: [u8; 32],
max_total_claim: u64,
max_num_nodes: u64,
) -> Result<()> {
let distributor = &mut ctx.accounts.distributor;
distributor.base = ctx.accounts.base.key();
distributor.bump = unwrap_bump!(ctx, "distributor");
distributor.root = root;
distributor.mint = ctx.accounts.mint.key();
distributor.max_total_claim = max_total_claim;
distributor.max_num_nodes = max_num_nodes;
distributor.total_amount_claimed = 0;
distributor.num_nodes_claimed = 0;
Ok(())
}Here is to create a distributor
What's coming in is bump,root,max_total_claim and max_num_ndoes
/// Claims tokens from the [MerkleDistributor].
pub fn claim(
ctx: Context<Claim>,
_bump: u8,
index: u64,
amount: u64,
proof: Vec<[u8; 32]>,
) -> Result<()> {
assert_keys_neq!(ctx.accounts.from, ctx.accounts.to);
let claim_status = &mut ctx.accounts.claim_status;
invariant!(
// This check is redundant, we should not be able to initialize a claim status account at the same key.
!claim_status.is_claimed && claim_status.claimed_at == 0,
DropAlreadyClaimed
);
let claimant_account = &ctx.accounts.claimant;
let distributor = &ctx.accounts.distributor;
invariant!(claimant_account.is_signer, Unauthorized);
// Verify the merkle proof.
let node = anchor_lang::solana_program::keccak::hashv(&[
&index.to_le_bytes(),
&claimant_account.key().to_bytes(),
&amount.to_le_bytes(),
]);
invariant!(
merkle_proof::verify(proof, distributor.root, node.0),
InvalidProof
);
// Mark it claimed and send the tokens.
claim_status.amount = amount;
claim_status.is_claimed = true;
let clock = Clock::get()?;
claim_status.claimed_at = clock.unix_timestamp;
claim_status.claimant = claimant_account.key();
let seeds = [
b"MerkleDistributor".as_ref(),
&distributor.base.to_bytes(),
&[ctx.accounts.distributor.bump],
];
#[allow(deprecated)]
{
vipers::assert_ata!(
ctx.accounts.from,
ctx.accounts.distributor,
distributor.mint
);
}
assert_keys_eq!(ctx.accounts.to.owner, claimant_account.key(), OwnerMismatch);
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.distributor.to_account_info(),
},
)
.with_signer(&[&seeds[..]]),
amount,
)?;
let distributor = &mut ctx.accounts.distributor;
distributor.total_amount_claimed =
unwrap_int!(distributor.total_amount_claimed.checked_add(amount));
invariant!(
distributor.total_amount_claimed <= distributor.max_total_claim,
ExceededMaxClaim
);
distributor.num_nodes_claimed = unwrap_int!(distributor.num_nodes_claimed.checked_add(1));
invariant!(
distributor.num_nodes_claimed <= distributor.max_num_nodes,
ExceededMaxNumNodes
);
emit!(ClaimedEvent {
index,
claimant: claimant_account.key(),
amount
});
Ok(())
}And then there was claim
In fact, the code is really simple
I just want to keep one root And some relevant information
However, it would be good to verify what came in later
It's easy to understand
Then take a look at the complete code
First of all lib.rs
//! A program for distributing tokens efficiently via uploading a [Merkle root](https://en.wikipedia.org/wiki/Merkle_tree).
//!
//! This program is largely based off of [Uniswap's Merkle Distributor](https://github.com/Uniswap/merkle-distributor).
//!
//! # Rationale
//!
//! Although Solana has low fees for executing transactions, it requires staking tokens to pay for storage costs, also known as "rent". These rent costs can add up when sending tokens to thousands or tens of thousands of wallets, making it economically unreasonable to distribute tokens to everyone.
//!
//! The Merkle distributor, pioneered by [Uniswap](https://github.com/Uniswap/merkle-distributor), solves this issue by deriving a 256-bit "root hash" from a tree of balances. This puts the gas cost on the claimer. Solana has the additional advantage of being able to reclaim rent from closed token accounts, so the net cost to the user should be around `0.000010 SOL` (at the time of writing).
//!
//! The Merkle distributor is also significantly easier to manage from an operations perspective, since one does not need to send a transaction to each individual address that may be redeeming tokens.
//!
//! # License
//!
//! The Merkle distributor program and SDK is distributed under the GPL v3.0 license.
use anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES};
use anchor_spl::token::{self, Mint, Token, TokenAccount};
use vipers::prelude::*;
pub mod merkle_proof;
declare_id!("MRKGLMizK9XSTaD1d1jbVkdHZbQVCSnPpYiTw9aKQv8");
/// The [merkle_distributor] program.
#[program]
pub mod merkle_distributor {
use super::*;
/// Creates a new [MerkleDistributor].
/// After creating this [MerkleDistributor], the account should be seeded with tokens via its ATA.
pub fn new_distributor(
ctx: Context<NewDistributor>,
_bump: u8,
root: [u8; 32],
max_total_claim: u64,
max_num_nodes: u64,
) -> Result<()> {
let distributor = &mut ctx.accounts.distributor;
distributor.base = ctx.accounts.base.key();
distributor.bump = unwrap_bump!(ctx, "distributor");
distributor.root = root;
distributor.mint = ctx.accounts.mint.key();
distributor.max_total_claim = max_total_claim;
distributor.max_num_nodes = max_num_nodes;
distributor.total_amount_claimed = 0;
distributor.num_nodes_claimed = 0;
Ok(())
}
/// Claims tokens from the [MerkleDistributor].
pub fn claim(
ctx: Context<Claim>,
_bump: u8,
index: u64,
amount: u64,
proof: Vec<[u8; 32]>,
) -> Result<()> {
assert_keys_neq!(ctx.accounts.from, ctx.accounts.to);
let claim_status = &mut ctx.accounts.claim_status;
invariant!(
// This check is redundant, we should not be able to initialize a claim status account at the same key.
!claim_status.is_claimed && claim_status.claimed_at == 0,
DropAlreadyClaimed
);
let claimant_account = &ctx.accounts.claimant;
let distributor = &ctx.accounts.distributor;
invariant!(claimant_account.is_signer, Unauthorized);
// Verify the merkle proof.
let node = anchor_lang::solana_program::keccak::hashv(&[
&index.to_le_bytes(),
&claimant_account.key().to_bytes(),
&amount.to_le_bytes(),
]);
invariant!(
merkle_proof::verify(proof, distributor.root, node.0),
InvalidProof
);
// Mark it claimed and send the tokens.
claim_status.amount = amount;
claim_status.is_claimed = true;
let clock = Clock::get()?;
claim_status.claimed_at = clock.unix_timestamp;
claim_status.claimant = claimant_account.key();
let seeds = [
b"MerkleDistributor".as_ref(),
&distributor.base.to_bytes(),
&[ctx.accounts.distributor.bump],
];
#[allow(deprecated)]
{
vipers::assert_ata!(
ctx.accounts.from,
ctx.accounts.distributor,
distributor.mint
);
}
assert_keys_eq!(ctx.accounts.to.owner, claimant_account.key(), OwnerMismatch);
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.distributor.to_account_info(),
},
)
.with_signer(&[&seeds[..]]),
amount,
)?;
let distributor = &mut ctx.accounts.distributor;
distributor.total_amount_claimed =
unwrap_int!(distributor.total_amount_claimed.checked_add(amount));
invariant!(
distributor.total_amount_claimed <= distributor.max_total_claim,
ExceededMaxClaim
);
distributor.num_nodes_claimed = unwrap_int!(distributor.num_nodes_claimed.checked_add(1));
invariant!(
distributor.num_nodes_claimed <= distributor.max_num_nodes,
ExceededMaxNumNodes
);
emit!(ClaimedEvent {
index,
claimant: claimant_account.key(),
amount
});
Ok(())
}
}
/// Accounts for [merkle_distributor::new_distributor].
#[derive(Accounts)]
pub struct NewDistributor<'info> {
/// Base key of the distributor.
pub base: Signer<'info>,
/// [MerkleDistributor].
#[account(
init,
seeds = [
b"MerkleDistributor".as_ref(),
base.key().to_bytes().as_ref()
],
bump,
space = 8 + MerkleDistributor::LEN,
payer = payer
)]
pub distributor: Account<'info, MerkleDistributor>,
/// The mint to distribute.
pub mint: Account<'info, Mint>,
/// Payer to create the distributor.
#[account(mut)]
pub payer: Signer<'info>,
/// The [System] program.
pub system_program: Program<'info, System>,
}
/// [merkle_distributor::claim] accounts.
#[derive(Accounts)]
#[instruction(_bump: u8, index: u64)]
pub struct Claim<'info> {
/// The [MerkleDistributor].
#[account(
mut,
address = from.owner
)]
pub distributor: Account<'info, MerkleDistributor>,
/// Status of the claim.
#[account(
init,
seeds = [
b"ClaimStatus".as_ref(),
index.to_le_bytes().as_ref(),
distributor.key().to_bytes().as_ref()
],
bump,
space = 8 + ClaimStatus::LEN,
payer = payer
)]
pub claim_status: Account<'info, ClaimStatus>,
/// Distributor ATA containing the tokens to distribute.
#[account(mut)]
pub from: Account<'info, TokenAccount>,
/// Account to send the claimed tokens to.
#[account(mut)]
pub to: Account<'info, TokenAccount>,
/// Who is claiming the tokens.
#[account(address = to.owner @ ErrorCode::OwnerMismatch)]
pub claimant: Signer<'info>,
/// Payer of the claim.
#[account(mut)]
pub payer: Signer<'info>,
/// The [System] program.
pub system_program: Program<'info, System>,
/// SPL [Token] program.
pub token_program: Program<'info, Token>,
}
/// State for the account which distributes tokens.
#[account]
#[derive(Default)]
pub struct MerkleDistributor {
/// Base key used to generate the PDA.
pub base: Pubkey,
/// Bump seed.
pub bump: u8,
/// The 256-bit merkle root.
pub root: [u8; 32],
/// [Mint] of the token to be distributed.
pub mint: Pubkey,
/// Maximum number of tokens that can ever be claimed from this [MerkleDistributor].
pub max_total_claim: u64,
/// Maximum number of nodes that can ever be claimed from this [MerkleDistributor].
pub max_num_nodes: u64,
/// Total amount of tokens that have been claimed.
pub total_amount_claimed: u64,
/// Number of nodes that have been claimed.
pub num_nodes_claimed: u64,
}
impl MerkleDistributor {
pub const LEN: usize = PUBKEY_BYTES + 1 + 32 + PUBKEY_BYTES + 8 * 4;
}
/// Holds whether or not a claimant has claimed tokens.
///
/// TODO: this is probably better stored as the node that was verified.
#[account]
#[derive(Default)]
pub struct ClaimStatus {
/// If true, the tokens have been claimed.
pub is_claimed: bool,
/// Authority that claimed the tokens.
pub claimant: Pubkey,
/// When the tokens were claimed.
pub claimed_at: i64,
/// Amount of tokens claimed.
pub amount: u64,
}
impl ClaimStatus {
pub const LEN: usize = 1 + PUBKEY_BYTES + 8 + 8;
}
/// Emitted when tokens are claimed.
#[event]
pub struct ClaimedEvent {
/// Index of the claim.
pub index: u64,
/// User that claimed.
pub claimant: Pubkey,
/// Amount of tokens to distribute.
pub amount: u64,
}
/// Error codes.
#[error_code]
pub enum ErrorCode {
#[msg("Invalid Merkle proof.")]
InvalidProof,
#[msg("Drop already claimed.")]
DropAlreadyClaimed,
#[msg("Exceeded maximum claim amount.")]
ExceededMaxClaim,
#[msg("Exceeded maximum number of claimed nodes.")]
ExceededMaxNumNodes,
#[msg("Account is not authorized to execute this instruction")]
Unauthorized,
#[msg("Token account owner did not match intended owner")]
OwnerMismatch,
}
And then there was proof.rs
//! These functions deal with verification of Merkle trees (hash trees).
//! Direct port of https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/MerkleProof.sol
/// Returns true if a `leaf` can be proved to be a part of a Merkle tree
/// defined by `root`. For this, a `proof` must be provided, containing
/// sibling hashes on the branch from the leaf to the root of the tree. Each
/// pair of leaves and each pair of pre-images are assumed to be sorted.
pub fn verify(proof: Vec<[u8; 32]>, root: [u8; 32], leaf: [u8; 32]) -> bool {
let mut computed_hash = leaf;
for proof_element in proof.into_iter() {
if computed_hash <= proof_element {
// Hash(current computed hash + current element of the proof)
computed_hash =
anchor_lang::solana_program::keccak::hashv(&[&computed_hash, &proof_element]).0;
} else {
// Hash(current element of the proof + current computed hash)
computed_hash =
anchor_lang::solana_program::keccak::hashv(&[&proof_element, &computed_hash]).0;
}
}
// Check if the computed hash (root) is equal to the provided root
computed_hash == root
}
meanwhile
Let's take a look at the contract, that is program Do it in the middle sol The transfer
msg!("Transfer SOL");
invoke(
&transfer(signer_info.key,
seller_info.key,
1e9 as u64,
),
&[signer_info.clone(), seller_info.clone()],
);This is called system_instruction Of transfer
Let's take a look at this transfer
pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
let account_metas = vec![
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, false),
];
Instruction::new_with_bincode(
system_program::id(),
&SystemInstruction::Transfer { lamports },
account_metas,
)
}So if it is spl The transfer
What we should call is spl_token::instruction Of transfer
pub fn transfer(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Result<Instruction, ProgramError> {
check_program_account(token_program_id)?;
let data = TokenInstruction::Transfer { amount }.pack();
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*source_pubkey, false));
accounts.push(AccountMeta::new(*destination_pubkey, false));
accounts.push(AccountMeta::new_readonly(
*authority_pubkey,
signer_pubkeys.is_empty(),
));
for signer_pubkey in signer_pubkeys.iter() {
accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
}
Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}this 2 One is different , Different instructions
Don't confuse
One is sol Transfer accounts
One is spl-token Transfer accounts
边栏推荐
- 端午节-简单侧边导航栏
- Innovation project training: data analysis and visualization
- TweenMax不规则几何图形背景带动画js特效
- How powerful are spectral graph neural networks
- easyUI的combox下拉列表的远程数据的绑定方法
- Blasting with burp (ordinary blasting + verification code blasting)
- DDD Practice Manual (4. aggregate aggregate)
- 机器学习之数据归一化(Feature Scaling)
- Issue 13: Flink zero foundation learning route
- 太厉害了MySQL总结的太全面了
猜你喜欢

PyG教程(4):自定义数据集

五层参考模型各层总结

Does intelligence need a body

C语言程序设计——三子棋(学期小作业)

数据库有用户更改密码问题

Regular expression Basics

How to access MySQL database through JDBC? Hand to hand login interface (illustration + complete code)

TweenMax示波器3d动画

FPGA - 7 Series FPGA selectio -02- introduction to source language

如何限制内网网速
随机推荐
nametuple的源码为什么要使用.replace(‘,‘, ‘ ‘).split()而不是.split(‘,‘)
不给糖就捣蛋svg万圣节js特效
Summary of each layer of the five layer reference model
Module 14 - 15: network application communication test
Innovation project training: Data crawling
154-Solana分发token
PyG教程(6):自定义消息传递网络
【基于栈的二叉树中序遍历】二叉树的中序遍历+栈,O(h)的空间复杂度
FPGA - 7 Series FPGA selectio -02- introduction to source language
MSF intranet penetration
出现ConcurrentModificationException这个异常报错,怎么处理?
User defined thread pool
C语言实现模拟银行存取款管理系统课程设计(纯C语言版)
Butler-Volmer 公式的由来
如何限制内网网速
创新项目实训:数据爬取
Issue 6: which mainstream programming language should college students choose
The database has the problem of user changing password
基于C#的ArcEngine二次开发57:每用户订阅上的所有人SID 不存在
Markdown mathematical grammar [detailed summary]