This commit is contained in:
zramsay 2025-03-25 14:49:05 -04:00
parent cc685f9796
commit ef7aaa8c17
5 changed files with 0 additions and 600 deletions

View File

@ -1,14 +0,0 @@
# Configuration for Sei blockchain integration
# Copy this file to .env and replace with your actual values
# Sei testnet configuration - Atlantic-1
NEXT_PUBLIC_SEI_NETWORK="atlantic-1"
NEXT_PUBLIC_SEI_RPC_URL="https://rpc.atlantic-1.sei.io"
NEXT_PUBLIC_SEI_REST_URL="https://rest.atlantic-1.sei.io"
# WILD token contract address
# Replace with your deployed contract address after deployment
NEXT_PUBLIC_SEI_TOKEN_FACTORY_ADDRESS="sei1..."
# Optional: Wallet for automated tests (use only for testing)
# SEI_TEST_MNEMONIC="your twelve word mnemonic here only for testing"

View File

@ -1,55 +0,0 @@
# Wildlife Token Smart Contract
This directory contains the smart contract code for the Wildlife Token (WILD) used in the Wildlife Sightings application.
## Overview
The `WildlifeToken.sol` contract is an ERC-20 token designed to be deployed on the Sei EVM. It provides functionality for:
- Minting tokens as rewards for wildlife documentation
- Setting different reward amounts for various wildlife species
- Managing authorized distributors who can award tokens
## Deployment Instructions
To deploy this contract on the Sei EVM:
1. Install Hardhat or Truffle as your Ethereum development framework
2. Configure your deployment script to target Sei EVM
3. Deploy with an RPC endpoint for Sei testnet (atlantic-1):
- RPC URL: https://rpc.atlantic-1.sei.io
- Chain ID: 1
## Contract Configuration
Before deployment, you may want to customize:
- The token name and symbol
- Maximum token supply
- Initial token distribution
- Default reward amounts for different species
## Authorizing the Backend
After deployment, you'll need to:
1. Call `setDistributor(backendAddress, true)` to authorize your backend server to distribute tokens
2. Configure the backend with the deployed contract address
3. Update the `.env` file with:
```
NEXT_PUBLIC_SEI_TOKEN_FACTORY_ADDRESS=0x... (your deployed contract address)
```
## Security Considerations
- Only authorized distributors can mint new tokens
- The contract has a capped supply to prevent inflation
- The owner can update reward amounts for different species
## Testing
Before mainnet deployment, thoroughly test the contract on Sei testnet:
1. Test token minting and distribution
2. Verify reward calculations for different species
3. Check authorization controls

View File

@ -1,104 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title WildlifeToken
* @dev ERC20 token for rewarding wildlife documentation contributions
* To be deployed on Sei Atlantic-1 testnet EVM
*/
contract WildlifeToken is ERC20Capped, Ownable {
// Events
event TokensAwarded(address indexed recipient, uint256 amount, string species);
// Reward amounts for different wildlife types
mapping(string => uint256) public speciesRewards;
// List of authorized reward distributors
mapping(address => bool) public authorizedDistributors;
// Maximum token supply: 100 million tokens
uint256 private constant MAX_SUPPLY = 100_000_000 * 10**18;
/**
* @dev Constructor that sets up the Wildlife Token
*/
constructor()
ERC20("Wildlife Conservation Token", "WILD")
ERC20Capped(MAX_SUPPLY)
Ownable(msg.sender)
{
// Initialize with base reward values (in tokens, will be multiplied by decimals)
speciesRewards["default"] = 10 * 10**18; // Default reward
// Rare species receive higher rewards
speciesRewards["elephant"] = 25 * 10**18;
speciesRewards["tiger"] = 25 * 10**18;
speciesRewards["rhino"] = 30 * 10**18;
speciesRewards["panda"] = 25 * 10**18;
speciesRewards["gorilla"] = 25 * 10**18;
speciesRewards["whale"] = 25 * 10**18;
speciesRewards["dolphin"] = 15 * 10**18;
speciesRewards["snow leopard"] = 30 * 10**18;
speciesRewards["eagle"] = 15 * 10**18;
// Set contract creator as an authorized distributor
authorizedDistributors[msg.sender] = true;
// Mint initial supply for rewards pool (10% of total supply)
_mint(msg.sender, MAX_SUPPLY / 10);
}
/**
* @dev Add or update a distributor authorization
* @param distributor Address to authorize or deauthorize
* @param authorized True to authorize, false to deauthorize
*/
function setDistributor(address distributor, bool authorized) external onlyOwner {
authorizedDistributors[distributor] = authorized;
}
/**
* @dev Set the reward amount for a specific species
* @param species Name of the wildlife species
* @param amount Reward amount in tokens
*/
function setSpeciesReward(string calldata species, uint256 amount) external onlyOwner {
speciesRewards[species] = amount;
}
/**
* @dev Award tokens to a user for documenting wildlife
* @param recipient Address of the recipient
* @param species Name of the wildlife species documented
* @return Amount of tokens awarded
*/
function awardTokens(address recipient, string calldata species) external returns (uint256) {
// Check if caller is an authorized distributor
require(authorizedDistributors[msg.sender], "Not authorized to distribute tokens");
// Get reward amount for the species (or default if not set)
uint256 rewardAmount = speciesRewards[species];
if (rewardAmount == 0) {
rewardAmount = speciesRewards["default"];
}
// Mint tokens to the recipient
_mint(recipient, rewardAmount);
// Emit event for tracking
emit TokensAwarded(recipient, rewardAmount, species);
return rewardAmount;
}
/**
* @dev Override _mint to respect the cap
*/
function _mint(address account, uint256 amount) internal override(ERC20, ERC20Capped) {
ERC20Capped._mint(account, amount);
}
}

View File

@ -1,75 +0,0 @@
# Wildlife Token CosmWasm Contract
This directory contains the CosmWasm smart contract code for the Wildlife Token (WILD) used in the Wildlife Sightings application.
## Overview
The `wildlife_token.rs` contract is a CW20-compatible token designed for the Sei blockchain. It builds on the standard CW20 token implementation and adds functionality for:
- Minting tokens as rewards for wildlife documentation
- Setting different reward amounts for various wildlife species
- Managing authorized distributors who can award tokens
## Deployment Instructions
To deploy this contract on the Sei Atlantic-1 testnet:
1. Set up a Rust development environment with CosmWasm
2. Compile the contract to Wasm
3. Deploy the compiled contract using a Sei wallet with:
```
sei-cli tx wasm store wildlife_token.wasm --from <your_wallet> --chain-id atlantic-1 --gas-prices 0.01usei
```
4. Instantiate the deployed contract:
```
sei-cli tx wasm instantiate <code_id> '{"name":"Wildlife Conservation Token","symbol":"WILD","decimals":6}' --from <your_wallet> --label "Wildlife Token" --chain-id atlantic-1
```
## Contract Configuration
The contract is pre-configured with:
- Token name: "Wildlife Conservation Token"
- Token symbol: "WILD"
- Decimals: 6
- Initial supply: 10% of max supply (10,000,000 WILD)
- Default reward: 10 WILD per sighting
- Rare species rewards: 15-30 WILD depending on rarity
## Authorizing the Backend
After deployment, you'll need to:
1. Call the `add_distributor` function to authorize your backend server to distribute tokens
2. Configure the backend with the deployed contract address
3. Update the `.env` file with:
```
NEXT_PUBLIC_SEI_TOKEN_FACTORY_ADDRESS=sei1... (your deployed contract address)
```
## Testing
Before using on the testnet, thoroughly test the contract using:
1. Unit tests for contract functionality
2. Test token minting and distribution on the Sei testnet
3. Verify reward calculations for different species
## Contract Messages
The contract supports the following messages:
### Execute Messages
- Standard CW20 messages for transfers, allowances, etc.
- `set_species_reward`: Set the reward amount for a specific species
- `award_tokens`: Mint and send tokens to a user who documented wildlife
- `add_distributor`: Authorize an address to award tokens
- `remove_distributor`: Remove distributor authorization
### Query Messages
- Standard CW20 balance and token info queries
- `species_reward`: Get the reward amount for a specific species
- `is_distributor`: Check if an address is authorized to award tokens
- `all_species_rewards`: List all configured species and their reward amounts

View File

@ -1,352 +0,0 @@
// wildlife_token.rs - CosmWasm contract for Wildlife Token (WILD)
// This is a simplified example of a CosmWasm contract for the Sei blockchain
// To be deployed on Atlantic-1 testnet
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
Addr,
};
use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg};
use cw20_base::contract::{execute as cw20_execute, query as cw20_query};
use cw20_base::msg::{ExecuteMsg as Cw20ExecuteMsg, QueryMsg as Cw20QueryMsg};
use cw20_base::state::{MinterData, TokenInfo, TOKEN_INFO};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// Custom messages for our Wildlife Token contract
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// Standard CW20 messages
Transfer { recipient: String, amount: Uint128 },
Burn { amount: Uint128 },
Send { contract: String, amount: Uint128, msg: Binary },
IncreaseAllowance { spender: String, amount: Uint128, expires: Option<u64> },
DecreaseAllowance { spender: String, amount: Uint128, expires: Option<u64> },
TransferFrom { owner: String, recipient: String, amount: Uint128 },
BurnFrom { owner: String, amount: Uint128 },
SendFrom { owner: String, contract: String, amount: Uint128, msg: Binary },
// Custom Wildlife Token messages
SetSpeciesReward { species: String, amount: Uint128 },
AwardTokens { recipient: String, species: String },
AddDistributor { address: String },
RemoveDistributor { address: String },
}
// State for tracking distributors and species rewards
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct WildlifeState {
pub distributors: Vec<Addr>,
pub species_rewards: HashMap<String, Uint128>,
}
// Initialize the contract
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
// Initialize CW20 token
let token_info = TokenInfo {
name: "Wildlife Conservation Token".to_string(),
symbol: "WILD".to_string(),
decimals: 6,
total_supply: Uint128::zero(),
mint: Some(MinterData {
minter: info.sender.clone(),
cap: Some(Uint128::from(100_000_000_000_000u128)), // 100 million tokens with 6 decimals
}),
};
TOKEN_INFO.save(deps.storage, &token_info)?;
// Initialize wildlife-specific state
let mut species_rewards = HashMap::new();
// Set default reward
species_rewards.insert("default".to_string(), Uint128::from(10_000_000u128)); // 10 WILD
// Set rewards for rare species
species_rewards.insert("elephant".to_string(), Uint128::from(25_000_000u128));
species_rewards.insert("tiger".to_string(), Uint128::from(25_000_000u128));
species_rewards.insert("rhino".to_string(), Uint128::from(30_000_000u128));
species_rewards.insert("panda".to_string(), Uint128::from(25_000_000u128));
species_rewards.insert("gorilla".to_string(), Uint128::from(25_000_000u128));
species_rewards.insert("whale".to_string(), Uint128::from(25_000_000u128));
species_rewards.insert("dolphin".to_string(), Uint128::from(15_000_000u128));
species_rewards.insert("snow leopard".to_string(), Uint128::from(30_000_000u128));
species_rewards.insert("eagle".to_string(), Uint128::from(15_000_000u128));
let wildlife_state = WildlifeState {
distributors: vec![info.sender.clone()],
species_rewards,
};
WILDLIFE_STATE.save(deps.storage, &wildlife_state)?;
// Mint initial supply for rewards pool (10% of max supply)
let initial_supply = Uint128::from(10_000_000_000_000u128);
cw20_execute(
deps,
_env,
info,
Cw20ExecuteMsg::Mint {
recipient: info.sender.to_string(),
amount: initial_supply,
},
)?;
Ok(Response::default()
.add_attribute("method", "instantiate")
.add_attribute("token_name", "Wildlife Conservation Token")
.add_attribute("token_symbol", "WILD")
.add_attribute("initial_supply", initial_supply))
}
// Execute function for handling messages
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
// For standard CW20 operations, use the base implementation
ExecuteMsg::Transfer { recipient, amount } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::Transfer { recipient, amount })
}
ExecuteMsg::Burn { amount } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::Burn { amount })
}
ExecuteMsg::Send { contract, amount, msg } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::Send { contract, amount, msg })
}
ExecuteMsg::IncreaseAllowance { spender, amount, expires } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::IncreaseAllowance { spender, amount, expires })
}
ExecuteMsg::DecreaseAllowance { spender, amount, expires } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::DecreaseAllowance { spender, amount, expires })
}
ExecuteMsg::TransferFrom { owner, recipient, amount } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::TransferFrom { owner, recipient, amount })
}
ExecuteMsg::BurnFrom { owner, amount } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::BurnFrom { owner, amount })
}
ExecuteMsg::SendFrom { owner, contract, amount, msg } => {
cw20_execute(deps, env, info, Cw20ExecuteMsg::SendFrom { owner, contract, amount, msg })
}
// Custom wildlife token operations
ExecuteMsg::SetSpeciesReward { species, amount } => {
// Only contract owner can set rewards
let token_info = TOKEN_INFO.load(deps.storage)?;
if let Some(mint) = token_info.mint {
if mint.minter != info.sender {
return Err(cosmwasm_std::StdError::generic_err("Unauthorized"));
}
} else {
return Err(cosmwasm_std::StdError::generic_err("No minter set"));
}
let mut state = WILDLIFE_STATE.load(deps.storage)?;
state.species_rewards.insert(species.clone(), amount);
WILDLIFE_STATE.save(deps.storage, &state)?;
Ok(Response::new()
.add_attribute("action", "set_species_reward")
.add_attribute("species", species)
.add_attribute("amount", amount.to_string()))
}
ExecuteMsg::AwardTokens { recipient, species } => {
// Check if caller is an authorized distributor
let state = WILDLIFE_STATE.load(deps.storage)?;
if !state.distributors.contains(&info.sender) {
return Err(cosmwasm_std::StdError::generic_err("Not authorized to distribute tokens"));
}
// Get reward amount for the species (or default if not set)
let species_lower = species.to_lowercase();
let reward_amount = match state.species_rewards.get(&species_lower) {
Some(amount) => *amount,
None => match state.species_rewards.get("default") {
Some(amount) => *amount,
None => return Err(cosmwasm_std::StdError::generic_err("No default reward set")),
},
};
// Get recipient address
let recipient_addr = deps.api.addr_validate(&recipient)?;
// Mint tokens to the recipient
cw20_execute(
deps,
env,
info,
Cw20ExecuteMsg::Mint {
recipient: recipient_addr.to_string(),
amount: reward_amount,
},
)?;
Ok(Response::new()
.add_attribute("action", "award_tokens")
.add_attribute("recipient", recipient)
.add_attribute("species", species)
.add_attribute("amount", reward_amount.to_string()))
}
ExecuteMsg::AddDistributor { address } => {
// Only contract owner can add distributors
let token_info = TOKEN_INFO.load(deps.storage)?;
if let Some(mint) = token_info.mint {
if mint.minter != info.sender {
return Err(cosmwasm_std::StdError::generic_err("Unauthorized"));
}
} else {
return Err(cosmwasm_std::StdError::generic_err("No minter set"));
}
let addr = deps.api.addr_validate(&address)?;
let mut state = WILDLIFE_STATE.load(deps.storage)?;
if !state.distributors.contains(&addr) {
state.distributors.push(addr.clone());
WILDLIFE_STATE.save(deps.storage, &state)?;
}
Ok(Response::new()
.add_attribute("action", "add_distributor")
.add_attribute("address", address))
}
ExecuteMsg::RemoveDistributor { address } => {
// Only contract owner can remove distributors
let token_info = TOKEN_INFO.load(deps.storage)?;
if let Some(mint) = token_info.mint {
if mint.minter != info.sender {
return Err(cosmwasm_std::StdError::generic_err("Unauthorized"));
}
} else {
return Err(cosmwasm_std::StdError::generic_err("No minter set"));
}
let addr = deps.api.addr_validate(&address)?;
let mut state = WILDLIFE_STATE.load(deps.storage)?;
state.distributors.retain(|x| x != &addr);
WILDLIFE_STATE.save(deps.storage, &state)?;
Ok(Response::new()
.add_attribute("action", "remove_distributor")
.add_attribute("address", address))
}
}
}
// Query function for reading contract state
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
// For standard CW20 queries, use the base implementation
QueryMsg::Balance { address } => cw20_query(deps, _env, Cw20QueryMsg::Balance { address }),
QueryMsg::TokenInfo {} => cw20_query(deps, _env, Cw20QueryMsg::TokenInfo {}),
QueryMsg::Minter {} => cw20_query(deps, _env, Cw20QueryMsg::Minter {}),
QueryMsg::Allowance { owner, spender } => {
cw20_query(deps, _env, Cw20QueryMsg::Allowance { owner, spender })
}
QueryMsg::AllAllowances { owner, start_after, limit } => {
cw20_query(deps, _env, Cw20QueryMsg::AllAllowances { owner, start_after, limit })
}
QueryMsg::AllAccounts { start_after, limit } => {
cw20_query(deps, _env, Cw20QueryMsg::AllAccounts { start_after, limit })
}
// Custom wildlife token queries
QueryMsg::SpeciesReward { species } => {
let state = WILDLIFE_STATE.load(deps.storage)?;
let species_lower = species.to_lowercase();
let reward = match state.species_rewards.get(&species_lower) {
Some(amount) => *amount,
None => match state.species_rewards.get("default") {
Some(amount) => *amount,
None => Uint128::zero(),
},
};
to_binary(&SpeciesRewardResponse { species, reward })
}
QueryMsg::IsDistributor { address } => {
let state = WILDLIFE_STATE.load(deps.storage)?;
let addr = deps.api.addr_validate(&address)?;
let is_distributor = state.distributors.contains(&addr);
to_binary(&IsDistributorResponse { address, is_distributor })
}
QueryMsg::AllSpeciesRewards {} => {
let state = WILDLIFE_STATE.load(deps.storage)?;
let rewards: Vec<SpeciesReward> = state.species_rewards
.iter()
.map(|(species, amount)| SpeciesReward {
species: species.clone(),
reward: *amount,
})
.collect();
to_binary(&AllSpeciesRewardsResponse { rewards })
}
}
}
// Custom query messages
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
// Standard CW20 queries
Balance { address: String },
TokenInfo {},
Minter {},
Allowance { owner: String, spender: String },
AllAllowances { owner: String, start_after: Option<String>, limit: Option<u32> },
AllAccounts { start_after: Option<String>, limit: Option<u32> },
// Custom Wildlife Token queries
SpeciesReward { species: String },
IsDistributor { address: String },
AllSpeciesRewards {},
}
// Query response types
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SpeciesRewardResponse {
pub species: String,
pub reward: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IsDistributorResponse {
pub address: String,
pub is_distributor: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SpeciesReward {
pub species: String,
pub reward: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AllSpeciesRewardsResponse {
pub rewards: Vec<SpeciesReward>,
}
// Instantiate message
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub name: String,
pub symbol: String,
pub decimals: u8,
}
// State variable for wildlife-specific data
pub const WILDLIFE_STATE: Item<WildlifeState> = Item::new("wildlife_state");