Repository: public-awesome/launchpad
Pull request #459 (2022-11-08 02:07:20)

[Levana] Send Airdrop Tokens After Claim
In this PR:
1) Now sending funds to the sender when they a valid claim for eligible eth address 
2) Refactor of the file structure to separate responsibilities


Patch:
diff --git a/contracts/sg-eth-airdrop/src/ethereum.rs b/contracts/sg-eth-airdrop/ethereum/ethereum.rs
similarity index 100%
rename from contracts/sg-eth-airdrop/src/ethereum.rs
rename to contracts/sg-eth-airdrop/ethereum/ethereum.rs
diff --git a/contracts/sg-eth-airdrop/src/signature_verify.rs b/contracts/sg-eth-airdrop/ethereum/signature_verify.rs
similarity index 73%
rename from contracts/sg-eth-airdrop/src/signature_verify.rs
rename to contracts/sg-eth-airdrop/ethereum/signature_verify.rs
index 902313b9..8a20b6e5 100644
--- a/contracts/sg-eth-airdrop/src/signature_verify.rs
+++ b/contracts/sg-eth-airdrop/ethereum/signature_verify.rs
@@ -1,5 +1,5 @@
 use cosmwasm_std::{Deps, StdError, StdResult};
-use sha2::{Digest, Sha256};
+use sha2::Digest;
 use sha3::Keccak256;
 
 use crate::ethereum::{decode_address, ethereum_address_raw, get_recovery_param};
@@ -8,26 +8,6 @@ use crate::msg::VerifyResponse;
 #[allow(dead_code)]
 pub const VERSION: &str = "crypto-verify-v2";
 
-#[allow(dead_code)]
-pub fn query_verify_cosmos(
-    deps: Deps,
-    message: &[u8],
-    signature: &[u8],
-    public_key: &[u8],
-) -> StdResult<VerifyResponse> {
-    // Hashing
-    let hash = Sha256::digest(message);
-
-    // Verification
-    let result = deps
-        .api
-        .secp256k1_verify(hash.as_ref(), signature, public_key);
-    match result {
-        Ok(verifies) => Ok(VerifyResponse { verifies }),
-        Err(err) => Err(err.into()),
-    }
-}
-
 pub fn query_verify_ethereum_text(
     deps: Deps,
     message: &str,
diff --git a/contracts/sg-eth-airdrop/schema/instantiate_msg.json b/contracts/sg-eth-airdrop/schema/instantiate_msg.json
index df7039ec..88c1f034 100644
--- a/contracts/sg-eth-airdrop/schema/instantiate_msg.json
+++ b/contracts/sg-eth-airdrop/schema/instantiate_msg.json
@@ -5,7 +5,7 @@
   "required": [
     "addresses",
     "admin",
-    "amount",
+    "airdrop_amount",
     "claim_msg_plaintext",
     "minter_code_id",
     "minter_page"
@@ -20,9 +20,9 @@
     "admin": {
       "$ref": "#/definitions/Addr"
     },
-    "amount": {
+    "airdrop_amount": {
       "type": "integer",
-      "format": "uint64",
+      "format": "uint128",
       "minimum": 0.0
     },
     "claim_msg_plaintext": {
diff --git a/contracts/sg-eth-airdrop/src/contract.rs b/contracts/sg-eth-airdrop/src/contract.rs
index 72dfd45b..817260dd 100644
--- a/contracts/sg-eth-airdrop/src/contract.rs
+++ b/contracts/sg-eth-airdrop/src/contract.rs
@@ -1,23 +1,19 @@
 #[cfg(not(feature = "library"))]
 use crate::error::ContractError;
 use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
-use crate::signature_verify::query_verify_ethereum_text;
 use crate::state::{Config, CONFIG};
 
 use cosmwasm_std::entry_point;
-use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, StdResult, WasmMsg};
+use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, StdResult};
 use cw2::set_contract_version;
 use cw_utils::parse_reply_instantiate_data;
-use sg_std::{Response, SubMsg};
+use sg_std::Response;
 use whitelist_generic::helpers::WhitelistGenericContract;
-use whitelist_generic::msg::ExecuteMsg as WGExecuteMsg;
-use whitelist_generic::msg::InstantiateMsg as WGInstantiateMsg;
-const INIT_WHITELIST_REPLY_ID: u64 = 1;
 
-// version info for migration info
-const CONTRACT_NAME: &str = "crates.io:sg-eth-airdrop";
-const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
-const GENERIC_WHITELIST_LABEL: &str = "Generic Whitelist for Airdrop";
+use crate::build_msg::{build_bank_message, build_whitelist_instantiate_msg};
+use crate::computation::compute_valid_eth_sig;
+use crate::constants::{CONTRACT_NAME, CONTRACT_VERSION, INIT_WHITELIST_REPLY_ID};
+use crate::responses::{get_add_eligible_eth_response, get_remove_eligible_eth_response};
 
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn instantiate(
@@ -30,26 +26,14 @@ pub fn instantiate(
 
     let cfg = Config {
         admin: info.sender.clone(),
-        claim_msg_plaintext: msg.claim_msg_plaintext,
-        amount: msg.amount,
-        minter_page: msg.minter_page,
+        claim_msg_plaintext: msg.clone().claim_msg_plaintext,
+        airdrop_amount: msg.airdrop_amount,
+        minter_page: msg.clone().minter_page,
         whitelist_address: None,
     };
     CONFIG.save(deps.storage, &cfg)?;
 
-    let whitelist_instantiate_msg = WGInstantiateMsg {
-        addresses: msg.addresses,
-        mint_discount_bps: Some(0),
-        per_address_limit: 1,
-    };
-    let wasm_msg = WasmMsg::Instantiate {
-        code_id: msg.minter_code_id,
-        admin: Some(env.contract.address.to_string()),
-        funds: info.funds,
-        label: GENERIC_WHITELIST_LABEL.to_string(),
-        msg: to_binary(&whitelist_instantiate_msg)?,
-    };
-    let submsg = SubMsg::reply_on_success(wasm_msg, INIT_WHITELIST_REPLY_ID);
+    let whitelist_instantiate_msg = build_whitelist_instantiate_msg(env, msg);
 
     let res = Response::new();
     Ok(res
@@ -57,7 +41,7 @@ pub fn instantiate(
         .add_attribute("contract_name", CONTRACT_NAME)
         .add_attribute("contract_version", CONTRACT_VERSION)
         .add_attribute("sender", info.sender)
-        .add_submessage(submsg))
+        .add_submessage(whitelist_instantiate_msg))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
@@ -71,8 +55,10 @@ pub fn execute(
         ExecuteMsg::ClaimAirdrop {
             eth_address,
             eth_sig,
-        } => claim_airdrop(deps, eth_address, eth_sig, info),
-        ExecuteMsg::AddEligibleEth { eth_addresses } => add_eligible_eth(deps, eth_addresses, info),
+        } => claim_airdrop(deps, info, eth_address, eth_sig),
+        ExecuteMsg::AddEligibleEth { eth_addresses } => {
+            get_add_eligible_eth_response(deps, info, eth_addresses)
+        }
     }
 }
 
@@ -87,55 +73,29 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
 
 fn claim_airdrop(
     deps: DepsMut,
+    info: MessageInfo,
     eth_address: String,
     eth_sig: String,
-    info: MessageInfo,
 ) -> Result<Response, ContractError> {
     let config = CONFIG.load(deps.storage)?;
 
-    let plaintext_msg = str::replace(
-        &config.claim_msg_plaintext,
-        "{wallet}",
-        info.sender.as_ref(),
-    );
-    let is_eligible = airdrop_check_eligible(deps.as_ref(), eth_address.clone())?;
-    let eth_sig_hex = hex::decode(eth_sig).unwrap();
+    let is_eligible = airdrop_check_eligible(deps.as_ref(), eth_address.clone()).unwrap();
     let valid_eth_signature =
-        query_verify_ethereum_text(deps.as_ref(), &plaintext_msg, &eth_sig_hex, &eth_address)
-            .unwrap();
+        compute_valid_eth_sig(&deps, info.clone(), &config, eth_sig, eth_address.clone());
 
-    let mut res = Response::new();
+    let (mut res, mut claimed_amount) = (Response::new(), 0);
     if is_eligible && valid_eth_signature.verifies {
-        res = remove_eth_address_from_eligible(deps, eth_address).unwrap();
+        res = get_remove_eligible_eth_response(deps, eth_address).unwrap();
+        res = res.add_submessage(build_bank_message(info, config.airdrop_amount));
+        claimed_amount = config.airdrop_amount;
     }
     Ok(res
-        .add_attribute("amount", config.amount.to_string())
+        .add_attribute("claimed_amount", claimed_amount.to_string())
         .add_attribute("valid_eth_sig", valid_eth_signature.verifies.to_string())
-        .add_attribute("is_eligible", is_eligible.to_string())
+        .add_attribute("eligible_at_request", is_eligible.to_string())
         .add_attribute("minter_page", &config.minter_page))
 }
 
-fn remove_eth_address_from_eligible(
-    deps: DepsMut,
-    eth_address: String,
-) -> Result<Response, ContractError> {
-    let config = CONFIG.load(deps.storage)?;
-    let whitelist_address = match config.whitelist_address {
-        Some(address) => address,
-        None => return Err(ContractError::WhitelistContractNotSet {}),
-    };
-
-    let execute_msg = WGExecuteMsg::RemoveAddresses {
-        addresses: vec![eth_address],
-    };
-    let mut res = Response::new();
-    println!("about to remove here");
-    res = res.add_message(
-        WhitelistGenericContract(deps.api.addr_validate(&whitelist_address)?).call(execute_msg)?,
-    );
-    Ok(res)
-}
-
 fn airdrop_check_eligible(deps: Deps, eth_address: String) -> StdResult<bool> {
     let config = CONFIG.load(deps.storage)?;
     match config.whitelist_address {
@@ -147,29 +107,6 @@ fn airdrop_check_eligible(deps: Deps, eth_address: String) -> StdResult<bool> {
     }
 }
 
-fn add_eligible_eth(
-    deps: DepsMut,
-    addresses: Vec<String>,
-    info: MessageInfo,
-) -> Result<Response, ContractError> {
-    let config = CONFIG.load(deps.storage)?;
-    if info.sender != config.admin {
-        return Err(ContractError::Unauthorized {
-            sender: info.sender,
-        });
-    }
-    let whitelist_address = match config.whitelist_address {
-        Some(address) => address,
-        None => return Err(ContractError::WhitelistContractNotSet {}),
-    };
-    let execute_msg = WGExecuteMsg::AddAddresses { addresses };
-    let mut res = Response::new();
-    res = res.add_message(
-        WhitelistGenericContract(deps.api.addr_validate(&whitelist_address)?).call(execute_msg)?,
-    );
-    Ok(res)
-}
-
 #[allow(dead_code)]
 #[allow(unused_variables)]
 fn airdrop_check_valid(deps: Deps, env: Env, msg: QueryMsg) -> bool {
diff --git a/contracts/sg-eth-airdrop/src/helpers.rs b/contracts/sg-eth-airdrop/src/helpers.rs
deleted file mode 100644
index 8b137891..00000000
--- a/contracts/sg-eth-airdrop/src/helpers.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/contracts/sg-eth-airdrop/src/helpers/build_msg.rs b/contracts/sg-eth-airdrop/src/helpers/build_msg.rs
new file mode 100644
index 00000000..453c681e
--- /dev/null
+++ b/contracts/sg-eth-airdrop/src/helpers/build_msg.rs
@@ -0,0 +1,55 @@
+use crate::constants::{GENERIC_WHITELIST_LABEL, INIT_WHITELIST_REPLY_ID, NATIVE_DENOM};
+#[cfg(not(feature = "library"))]
+use crate::msg::InstantiateMsg;
+use cosmwasm_std::{coins, BankMsg};
+use cosmwasm_std::{to_binary, DepsMut, Env, MessageInfo, StdResult, WasmMsg};
+use sg_std::{CosmosMsg, StargazeMsgWrapper, SubMsg};
+use whitelist_generic::helpers::WhitelistGenericContract;
+use whitelist_generic::msg::ExecuteMsg as WGExecuteMsg;
+use whitelist_generic::msg::InstantiateMsg as WGInstantiateMsg;
+
+pub fn build_whitelist_instantiate_msg(
+    env: Env,
+    msg: InstantiateMsg,
+) -> cosmwasm_std::SubMsg<StargazeMsgWrapper> {
+    let whitelist_instantiate_msg = WGInstantiateMsg {
+        addresses: msg.addresses,
+        mint_discount_bps: Some(0),
+        per_address_limit: 1,
+    };
+    let wasm_msg = WasmMsg::Instantiate {
+        code_id: msg.minter_code_id,
+        admin: Some(env.contract.address.to_string()),
+        funds: vec![],
+        label: GENERIC_WHITELIST_LABEL.to_string(),
+        msg: to_binary(&whitelist_instantiate_msg).unwrap(),
+    };
+    SubMsg::reply_on_success(wasm_msg, INIT_WHITELIST_REPLY_ID)
+}
+
+pub fn build_bank_message(info: MessageInfo, airdrop_amount: u128) -> SubMsg {
+    SubMsg::new(BankMsg::Send {
+        to_address: info.sender.to_string(),
+        amount: coins(airdrop_amount, NATIVE_DENOM),
+    })
+}
+
+pub fn build_add_eth_eligible_msg(
+    deps: DepsMut,
+    addresses: Vec<String>,
+    whitelist_address: String,
+) -> StdResult<CosmosMsg> {
+    let execute_msg = WGExecuteMsg::AddAddresses { addresses };
+    WhitelistGenericContract(deps.api.addr_validate(&whitelist_address)?).call(execute_msg)
+}
+
+pub fn build_remove_eth_eligible_msg(
+    deps: DepsMut,
+    eth_address: String,
+    whitelist_address: String,
+) -> StdResult<CosmosMsg> {
+    let execute_msg = WGExecuteMsg::RemoveAddresses {
+        addresses: vec![eth_address],
+    };
+    WhitelistGenericContract(deps.api.addr_validate(&whitelist_address)?).call(execute_msg)
+}
diff --git a/contracts/sg-eth-airdrop/src/helpers/computation.rs b/contracts/sg-eth-airdrop/src/helpers/computation.rs
new file mode 100644
index 00000000..89e808d9
--- /dev/null
+++ b/contracts/sg-eth-airdrop/src/helpers/computation.rs
@@ -0,0 +1,25 @@
+use cosmwasm_std::{DepsMut, MessageInfo};
+
+use crate::msg::VerifyResponse;
+use crate::signature_verify::query_verify_ethereum_text;
+use crate::state::Config;
+
+pub fn compute_valid_eth_sig(
+    deps: &DepsMut,
+    info: MessageInfo,
+    config: &Config,
+    eth_sig: String,
+    eth_address: String,
+) -> VerifyResponse {
+    let plaintext_msg = compute_plaintext_msg(config, info);
+    let eth_sig_hex = hex::decode(eth_sig).unwrap();
+    query_verify_ethereum_text(deps.as_ref(), &plaintext_msg, &eth_sig_hex, &eth_address).unwrap()
+}
+
+pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String {
+    str::replace(
+        &config.claim_msg_plaintext,
+        "{wallet}",
+        info.sender.as_ref(),
+    )
+}
diff --git a/contracts/sg-eth-airdrop/src/helpers/constants.rs b/contracts/sg-eth-airdrop/src/helpers/constants.rs
new file mode 100644
index 00000000..98c8c17f
--- /dev/null
+++ b/contracts/sg-eth-airdrop/src/helpers/constants.rs
@@ -0,0 +1,5 @@
+pub const GENERIC_WHITELIST_LABEL: &str = "Generic Whitelist for Airdrop";
+pub const NATIVE_DENOM: &str = "ustars";
+pub const CONTRACT_NAME: &str = "crates.io:sg-eth-airdrop";
+pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
+pub const INIT_WHITELIST_REPLY_ID: u64 = 1;
diff --git a/contracts/sg-eth-airdrop/src/helpers/responses.rs b/contracts/sg-eth-airdrop/src/helpers/responses.rs
new file mode 100644
index 00000000..65f0d837
--- /dev/null
+++ b/contracts/sg-eth-airdrop/src/helpers/responses.rs
@@ -0,0 +1,43 @@
+use crate::error::ContractError;
+use crate::state::CONFIG;
+use cosmwasm_std::{DepsMut, MessageInfo};
+use sg_std::Response;
+
+use crate::build_msg::{build_add_eth_eligible_msg, build_remove_eth_eligible_msg};
+
+pub fn get_add_eligible_eth_response(
+    deps: DepsMut,
+    info: MessageInfo,
+    addresses: Vec<String>,
+) -> Result<Response, ContractError> {
+    let config = CONFIG.load(deps.storage)?;
+    if info.sender != config.admin {
+        return Err(ContractError::Unauthorized {
+            sender: info.sender,
+        });
+    }
+    let whitelist_address = match config.whitelist_address {
+        Some(address) => address,
+        None => return Err(ContractError::WhitelistContractNotSet {}),
+    };
+    let mut res = Response::new();
+    let add_eth_msg = build_add_eth_eligible_msg(deps, addresses, whitelist_address)?;
+    res = res.add_message(add_eth_msg);
+    Ok(res)
+}
+
+pub fn get_remove_eligible_eth_response(
+    deps: DepsMut,
+    eth_address: String,
+) -> Result<Response, ContractError> {
+    let config = CONFIG.load(deps.storage)?;
+    let whitelist_address = match config.whitelist_address {
+        Some(address) => address,
+        None => return Err(ContractError::WhitelistContractNotSet {}),
+    };
+
+    let mut res = Response::new();
+    let remove_eth_msg = build_remove_eth_eligible_msg(deps, eth_address, whitelist_address)?;
+    res = res.add_message(remove_eth_msg);
+    Ok(res)
+}
diff --git a/contracts/sg-eth-airdrop/src/lib.rs b/contracts/sg-eth-airdrop/src/lib.rs
index 303764ae..328761e5 100644
--- a/contracts/sg-eth-airdrop/src/lib.rs
+++ b/contracts/sg-eth-airdrop/src/lib.rs
@@ -1,11 +1,22 @@
 pub mod contract;
 mod error;
-mod ethereum;
-pub mod helpers;
+
 #[cfg(test)]
 mod integration_tests;
 pub mod msg;
-mod signature_verify;
+
+#[path = "./helpers/build_msg.rs"]
+pub mod build_msg;
+#[path = "./helpers/computation.rs"]
+pub mod computation;
+#[path = "./helpers/constants.rs"]
+pub mod constants;
+#[path = "./helpers/responses.rs"]
+pub mod responses;
 pub mod state;
 
 pub use crate::error::ContractError;
+#[path = "../ethereum/ethereum.rs"]
+mod ethereum;
+#[path = "../ethereum/signature_verify.rs"]
+mod signature_verify;
diff --git a/contracts/sg-eth-airdrop/src/msg.rs b/contracts/sg-eth-airdrop/src/msg.rs
index 4becffcd..2b4b23a4 100644
--- a/contracts/sg-eth-airdrop/src/msg.rs
+++ b/contracts/sg-eth-airdrop/src/msg.rs
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
 pub struct InstantiateMsg {
     pub admin: Addr,
     pub claim_msg_plaintext: String,
-    pub amount: u64,
+    pub airdrop_amount: u128,
     pub minter_page: String,
     pub addresses: Vec<String>,
     pub minter_code_id: u64,
diff --git a/contracts/sg-eth-airdrop/src/state.rs b/contracts/sg-eth-airdrop/src/state.rs
index 6d712b99..b3f0ac23 100644
--- a/contracts/sg-eth-airdrop/src/state.rs
+++ b/contracts/sg-eth-airdrop/src/state.rs
@@ -8,7 +8,7 @@ pub struct Config {
     pub admin: Addr,
     pub claim_msg_plaintext: String,
     pub minter_page: String,
-    pub amount: u64,
+    pub airdrop_amount: u128,
     pub whitelist_address: Option<String>,
 }
 


Test patch:
diff --git a/contracts/sg-eth-airdrop/src/integration_tests.rs b/contracts/sg-eth-airdrop/src/integration_tests.rs
index ffd6b0f3..1ae08dd0 100644
--- a/contracts/sg-eth-airdrop/src/integration_tests.rs
+++ b/contracts/sg-eth-airdrop/src/integration_tests.rs
@@ -1,16 +1,14 @@
 use async_std::task;
-use cosmwasm_std::{Addr, Attribute};
+use cosmwasm_std::{coins, Addr, Attribute, Coin, Uint128};
 use cw_multi_test::error::Error;
-use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor};
+use cw_multi_test::{AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg};
 use ethers_core::k256::ecdsa::SigningKey;
 use ethers_core::types::H160;
+use std::str;
 
 use sg_multi_test::StargazeApp;
 use sg_std::{self, StargazeMsgWrapper};
 
-// use sg_std::StargazeMsgWrapper;
-use std::str;
-
 use crate::contract::reply;
 use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
 
@@ -23,7 +21,9 @@ extern crate whitelist_generic;
 const OWNER: &str = "admin0001";
 const AIRDROP_CONTRACT: &str = "contract0";
 const STARGAZE_WALLET_01: &str = "0xstargaze_wallet_01";
+const STARGAZE_WALLET_02: &str = "0xstargaze_wallet_02";
 const CONTRACT_CONFIG_PLAINTEXT: &str = "My Stargaze address is {wallet} and I want a Winter Pal.";
+const NATIVE_DENOM: &str = "ustars";
 
 fn custom_mock_app() -> StargazeApp {
     StargazeApp::default()
@@ -48,15 +48,24 @@ pub fn whitelist_generic_contract() -> Box<dyn Contract<StargazeMsgWrapper>> {
     Box::new(contract)
 }
 
-fn get_instantiate_contract(addresses: Vec<String>) -> StargazeApp {
+fn get_instantiate_contract(addresses: Vec<String>, funds_amount: u128) -> StargazeApp {
     let mut app = custom_mock_app();
+    app.sudo(SudoMsg::Bank({
+        BankSudo::Mint {
+            to_address: OWNER.to_string(),
+            amount: coins(funds_amount, NATIVE_DENOM),
+        }
+    }))
+    .map_err(|err| println!("{:?}", err))
+    .ok();
+
     let sg_eth_id = app.store_code(contract());
     let whitelist_code_id = app.store_code(whitelist_generic_contract());
     assert_eq!(sg_eth_id, 1);
     let msg: InstantiateMsg = InstantiateMsg {
         admin: Addr::unchecked(OWNER),
         claim_msg_plaintext: Addr::unchecked(CONTRACT_CONFIG_PLAINTEXT).into_string(),
-        amount: 3000,
+        airdrop_amount: 3000,
         minter_page: "http://levana_page/airdrop".to_string(),
         addresses,
         minter_code_id: whitelist_code_id,
@@ -65,7 +74,7 @@ fn get_instantiate_contract(addresses: Vec<String>) -> StargazeApp {
         sg_eth_id,
         Addr::unchecked(OWNER),
         &msg,
-        &[],
+        &coins(funds_amount, NATIVE_DENOM),
         "sg-eg-airdrop",
         Some(Addr::unchecked(OWNER).to_string()),
     )
@@ -114,7 +123,7 @@ fn get_msg_plaintext(wallet_address: String) -> String {
 
 #[test]
 fn test_instantiate() {
-    get_instantiate_contract(vec![]);
+    get_instantiate_contract(vec![], 10000);
 }
 
 #[test]
@@ -124,7 +133,7 @@ fn test_instantiate_with_addresses() {
         "addr2".to_string(),
         "addr3".to_string(),
     ];
-    let app = get_instantiate_contract(addresses);
+    let app = get_instantiate_contract(addresses, 10000);
     let sg_eth_addr = Addr::unchecked(AIRDROP_CONTRACT);
 
     let query_msg = QueryMsg::AirdropEligible {
@@ -148,7 +157,7 @@ fn test_instantiate_with_addresses() {
 
 #[test]
 fn test_not_authorized_add_eth() {
-    let mut app = get_instantiate_contract(vec![]);
+    let mut app = get_instantiate_contract(vec![], 10000);
 
     let fake_admin = Addr::unchecked("fake_admin");
     let sg_eth_addr = Addr::unchecked(AIRDROP_CONTRACT);
@@ -164,7 +173,7 @@ fn test_not_authorized_add_eth() {
 
 #[test]
 fn test_authorized_add_eth() {
-    let mut app = get_instantiate_contract(vec![]);
+    let mut app = get_instantiate_contract(vec![], 10000);
     let sg_eth_addr = Addr::unchecked(AIRDROP_CONTRACT);
 
     let eth_address = Addr::unchecked("testing_addr");
@@ -178,7 +187,7 @@ fn test_authorized_add_eth() {
 
 #[test]
 fn test_add_eth_and_verify() {
-    let mut app = get_instantiate_contract(vec![]);
+    let mut app = get_instantiate_contract(vec![], 10000);
     let sg_eth_addr = Addr::unchecked(AIRDROP_CONTRACT);
 
     let eth_address_str = Addr::unchecked("testing_addr").to_string();
@@ -215,7 +224,7 @@ fn test_valid_eth_sig_claim() {
     let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
     let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
 
-    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()]);
+    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()], 10000);
 
     let claim_message = ExecuteMsg::ClaimAirdrop {
         eth_address: eth_addr_str,
@@ -230,7 +239,7 @@ fn test_valid_eth_sig_claim() {
             value: "contract0".to_string(),
         },
         Attribute {
-            key: "amount".to_string(),
+            key: "claimed_amount".to_string(),
             value: "3000".to_string(),
         },
         Attribute {
@@ -238,7 +247,7 @@ fn test_valid_eth_sig_claim() {
             value: "true".to_string(),
         },
         Attribute {
-            key: "is_eligible".to_string(),
+            key: "eligible_at_request".to_string(),
             value: "true".to_string(),
         },
         Attribute {
@@ -255,7 +264,7 @@ fn test_invalid_eth_sig_claim() {
     let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
     let (_, eth_sig_str_2, _, _) = get_wallet_and_sig(claim_plaintext.clone());
 
-    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()]);
+    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()], 10000);
 
     let claim_message = ExecuteMsg::ClaimAirdrop {
         eth_address: eth_addr_str,
@@ -270,15 +279,15 @@ fn test_invalid_eth_sig_claim() {
             value: "contract0".to_string(),
         },
         Attribute {
-            key: "amount".to_string(),
-            value: "3000".to_string(),
+            key: "claimed_amount".to_string(),
+            value: "0".to_string(),
         },
         Attribute {
             key: "valid_eth_sig".to_string(),
             value: "false".to_string(),
         },
         Attribute {
-            key: "is_eligible".to_string(),
+            key: "eligible_at_request".to_string(),
             value: "true".to_string(),
         },
         Attribute {
@@ -294,7 +303,7 @@ fn test_can_not_claim_twice() {
     let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
     let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
 
-    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()]);
+    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()], 10000);
     let claim_message = ExecuteMsg::ClaimAirdrop {
         eth_address: eth_addr_str,
         eth_sig: eth_sig_str,
@@ -310,7 +319,7 @@ fn test_can_not_claim_twice() {
             value: "contract0".to_string(),
         },
         Attribute {
-            key: "amount".to_string(),
+            key: "claimed_amount".to_string(),
             value: "3000".to_string(),
         },
         Attribute {
@@ -318,7 +327,7 @@ fn test_can_not_claim_twice() {
             value: "true".to_string(),
         },
         Attribute {
-            key: "is_eligible".to_string(),
+            key: "eligible_at_request".to_string(),
             value: "true".to_string(),
         },
         Attribute {
@@ -335,15 +344,15 @@ fn test_can_not_claim_twice() {
             value: "contract0".to_string(),
         },
         Attribute {
-            key: "amount".to_string(),
-            value: "3000".to_string(),
+            key: "claimed_amount".to_string(),
+            value: "0".to_string(),
         },
         Attribute {
             key: "valid_eth_sig".to_string(),
             value: "true".to_string(),
         },
         Attribute {
-            key: "is_eligible".to_string(),
+            key: "eligible_at_request".to_string(),
             value: "false".to_string(),
         },
         Attribute {
@@ -353,3 +362,186 @@ fn test_can_not_claim_twice() {
     ];
     assert_eq!(res.events[1].attributes, expected_attributes);
 }
+
+#[test]
+fn test_claim_one_valid_airdrop() {
+    let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
+    let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
+
+    let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01);
+
+    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()], 10000);
+
+    let balances = app
+        .wrap()
+        .query_all_balances(stargaze_wallet_01.clone())
+        .unwrap();
+    assert_eq!(balances, []);
+
+    let claim_message = ExecuteMsg::ClaimAirdrop {
+        eth_address: eth_addr_str,
+        eth_sig: eth_sig_str,
+    };
+
+    let _ = execute_contract_with_msg(claim_message, &mut app, stargaze_wallet_01.clone()).unwrap();
+
+    let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap();
+    let expected_balance = [Coin {
+        denom: NATIVE_DENOM.to_string(),
+        amount: Uint128::new(3000),
+    }];
+    assert_eq!(balances, expected_balance)
+}
+
+#[test]
+fn test_claim_twice_receive_funds_once() {
+    let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
+    let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
+
+    let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01);
+
+    let mut app = get_instantiate_contract(vec![eth_addr_str.clone()], 10000);
+    let balances = app
+        .wrap()
+        .query_all_balances(stargaze_wallet_01.clone())
+        .unwrap();
+    assert_eq!(balances, []);
+
+    let claim_message = ExecuteMsg::ClaimAirdrop {
+        eth_address: eth_addr_str,
+        eth_sig: eth_sig_str,
+    };
+    let _ = execute_contract_with_msg(claim_message.clone(), &mut app, stargaze_wallet_01.clone())
+        .unwrap();
+
+    let balances = app
+        .wrap()
+        .query_all_balances(stargaze_wallet_01.clone())
+        .unwrap();
+    let expected_balance = [Coin {
+        denom: NATIVE_DENOM.to_string(),
+        amount: Uint128::new(3000),
+    }];
+    assert_eq!(balances, expected_balance);
+
+    let _ = execute_contract_with_msg(claim_message, &mut app, stargaze_wallet_01.clone()).unwrap();
+
+    let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap();
+    let expected_balance = [Coin {
+        denom: NATIVE_DENOM.to_string(),
+        amount: Uint128::new(3000),
+    }];
+    assert_eq!(balances, expected_balance);
+}
+
+#[test]
+fn test_ineligible_does_not_receive_funds() {
+    let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
+    let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone());
+
+    let mut app = get_instantiate_contract(vec![eth_addr_str], 10000);
+
+    let stargaze_wallet_02 = Addr::unchecked(STARGAZE_WALLET_02);
+    let balances = app
+        .wrap()
+        .query_all_balances(stargaze_wallet_02.clone())
+        .unwrap();
+    assert_eq!(balances, []);
+
+    let claim_plaintext_2 = &get_msg_plaintext(STARGAZE_WALLET_02.to_string());
+    let (_, eth_sig_str_2, _, eth_addr_str_2) = get_wallet_and_sig(claim_plaintext_2.clone());
+
+    let claim_message = ExecuteMsg::ClaimAirdrop {
+        eth_address: eth_addr_str_2,
+        eth_sig: eth_sig_str_2,
+    };
+    let _ = execute_contract_with_msg(claim_message, &mut app, stargaze_wallet_02.clone()).unwrap();
+
+    let balances = app.wrap().query_all_balances(stargaze_wallet_02).unwrap();
+    let expected_balance = [];
+    assert_eq!(balances, expected_balance)
+}
+
+#[test]
+fn test_one_eth_claim_two_stargaze_addresses_invalid() {
+    let wallet_1 = LocalWallet::new(&mut thread_rng());
+    let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01);
+    let stargaze_wallet_02 = Addr::unchecked(STARGAZE_WALLET_02);
+
+    let claim_plaintext_1 = &get_msg_plaintext(STARGAZE_WALLET_01.to_string());
+    let eth_sig_str_1 = task::block_on(get_signature(wallet_1.clone(), claim_plaintext_1))
+        .unwrap()
+        .to_string();
+    let eth_address = wallet_1.address();
+    let eth_addr_str_1 = format!("{:?}", eth_address);
+
+    let mut app = get_instantiate_contract(vec![eth_addr_str_1.clone()], 10000);
+
+    // claim with eth address 1, stargaze wallet 1
+    let claim_message = ExecuteMsg::ClaimAirdrop {
+        eth_address: eth_addr_str_1.clone(),
+        eth_sig: eth_sig_str_1,
+    };
+
+    let res = execute_contract_with_msg(claim_message, &mut app, stargaze_wallet_01).unwrap();
+
+    let expected_attributes = [
+        Attribute {
+            key: "_contract_addr".to_string(),
+            value: "contract0".to_string(),
+        },
+        Attribute {
+            key: "claimed_amount".to_string(),
+            value: "3000".to_string(),
+        },
+        Attribute {
+            key: "valid_eth_sig".to_string(),
+            value: "true".to_string(),
+        },
+        Attribute {
+            key: "eligible_at_request".to_string(),
+            value: "true".to_string(),
+        },
+        Attribute {
+            key: "minter_page".to_string(),
+            value: "http://levana_page/airdrop".to_string(),
+        },
+    ];
+    assert_eq!(res.events[1].attributes, expected_attributes);
+
+    let claim_plaintext_2 = &get_msg_plaintext(STARGAZE_WALLET_02.to_string());
+    let eth_sig_str_2 = task::block_on(get_signature(wallet_1, claim_plaintext_2))
+        .unwrap()
+        .to_string();
+
+    // claim with eth address 1, stargaze wallet 2
+    let claim_message = ExecuteMsg::ClaimAirdrop {
+        eth_address: eth_addr_str_1,
+        eth_sig: eth_sig_str_2,
+    };
+    let res_2 = execute_contract_with_msg(claim_message, &mut app, stargaze_wallet_02).unwrap();
+
+    let expected_attributes = [
+        Attribute {
+            key: "_contract_addr".to_string(),
+            value: "contract0".to_string(),
+        },
+        Attribute {
+            key: "claimed_amount".to_string(),
+            value: "0".to_string(),
+        },
+        Attribute {
+            key: "valid_eth_sig".to_string(),
+            value: "true".to_string(),
+        },
+        Attribute {
+            key: "eligible_at_request".to_string(),
+            value: "false".to_string(),
+        },
+        Attribute {
+            key: "minter_page".to_string(),
+            value: "http://levana_page/airdrop".to_string(),
+        },
+    ];
+    assert_eq!(res_2.events[1].attributes, expected_attributes);
+}