This is an Ethereum Faucet for the Ropsten Testnet. It allows users to request and donate Ether. The faucet ensures that users can receive a small amount of Ether for testing purposes on the Ropsten network, and it also accepts donations to keep the faucet funded for future users.
To install the ink!
tools, follow these steps. This includes installing Rust and the necessary tools for developing smart contracts using ink!
.
-
Install Rust: First, install Rust using
rustup
:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Add the
wasm32-unknown-unknown
Target: Add the WebAssembly target required for buildingink!
smart contracts:rustup target add wasm32-unknown-unknown --toolchain nightly
-
Install the
cargo-contract
Tool: Install thecargo-contract
tool, which is used for building and deploying smart contracts:cargo install cargo-contract --force
-
Install Substrate: If you haven't already installed Substrate, you can do so using the following command:
curl https://getsubstrate.io -sSf | bash
After installing the ink!
tools, follow these steps to create and deploy a smart contract:
-
Create a New
ink!
Project:cargo contract new faucet
-
Replace the Generated Contract Code: Replace the generated code with the provided Rust code from the previous responses.
-
Build and Deploy the Contract: Use the following commands to build and deploy the contract:
cargo contract build cargo contract deploy --constructor new 100000000000000000 1000000000000000000
These steps will help you install the ink!
tools, create a new project, and deploy your smart contract. If you need further assistance, feel free to ask!
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod faucet {
use ink_storage::traits::SpreadAllocate;
use ink_prelude::collections::BTreeMap;
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct Faucet {
owner: AccountId,
timeouts: BTreeMap<AccountId, u64>,
}
#[ink(event)]
pub struct Withdrawal {
#[ink(topic)]
to: AccountId,
}
#[ink(event)]
pub struct Deposit {
#[ink(topic)]
from: AccountId,
#[ink(topic)]
amount: Balance,
}
impl Faucet {
#[ink(constructor)]
pub fn new() -> Self {
Self::default()
}
#[ink(message)]
pub fn withdraw(&mut self) {
let caller = self.env().caller();
let balance = self.env().balance();
let timestamp = self.env().block_timestamp();
assert!(balance >= 100_000_000_000_000_000, "This faucet is empty. Please check back later.");
assert!(self.timeouts.get(&caller).unwrap_or(&0) <= &(timestamp - 1800_000), "You can only withdraw once every 30 minutes. Please check back later.");
self.env().transfer(caller, 100_000_000_000_000_000).expect("Transfer failed");
self.timeouts.insert(caller, timestamp);
self.env().emit_event(Withdrawal { to: caller });
}
#[ink(message, payable)]
pub fn deposit(&mut self) {
let caller = self.env().caller();
let amount = self.env().transferred_balance();
self.env().emit_event(Deposit { from: caller, amount });
}
#[ink(message)]
pub fn destroy(&mut self) {
let caller = self.env().caller();
assert_eq!(caller, self.owner, "Only the owner of this faucet can destroy it.");
self.env().terminate_contract(self.owner);
}
}
impl Default for Faucet {
fn default() -> Self {
Self::allocate_spread()
}
}
}
-
Install the
ink!
toolchain: Follow the instructions from the ink! documentation. -
Create a new
ink!
project:cargo contract new faucet
-
Replace the generated contract code with the provided Rust code.
-
Compile and deploy the contract:
cargo contract build cargo contract deploy
The HTML frontend has been updated to improve its appearance and functionality.
- Users can request Ether from the faucet.
- Users can donate Ether to the faucet.
- Displays the current balance of the faucet.
- Provides feedback on transaction status.
- Open
index.html
in a web browser. - Ensure Metamask or another Web3 provider is configured and connected to the Ropsten Testnet.
- Use the "Request Ether" and "Donate Ether" buttons to interact with the smart contract.
Here's an enhanced version of the given Rust code for the Ethereum Faucet using the ink!
framework. The improvements include additional functionality such as setting withdrawal limits, checking the contract balance, and adding an emergency stop mechanism.
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod faucet {
use ink_storage::traits::SpreadAllocate;
use ink_prelude::collections::BTreeMap;
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct Faucet {
owner: AccountId,
timeouts: BTreeMap<AccountId, u64>,
withdrawal_limit: Balance,
emergency_stop: bool,
}
#[ink(event)]
pub struct Withdrawal {
#[ink(topic)]
to: AccountId,
#[ink(topic)]
amount: Balance,
}
#[ink(event)]
pub struct Deposit {
#[ink(topic)]
from: AccountId,
#[ink(topic)]
amount: Balance,
}
impl Faucet {
#[ink(constructor)]
pub fn new(withdrawal_limit: Balance) -> Self {
Self {
owner: Self::env().caller(),
timeouts: BTreeMap::new(),
withdrawal_limit,
emergency_stop: false,
}
}
#[ink(message)]
pub fn withdraw(&mut self) {
let caller = self.env().caller();
let balance = self.env().balance();
let timestamp = self.env().block_timestamp();
assert!(!self.emergency_stop, "Withdrawals are currently disabled.");
assert!(balance >= self.withdrawal_limit, "This faucet is empty. Please check back later.");
assert!(self.timeouts.get(&caller).unwrap_or(&0) <= &(timestamp - 1800_000), "You can only withdraw once every 30 minutes. Please check back later.");
self.env().transfer(caller, self.withdrawal_limit).expect("Transfer failed");
self.timeouts.insert(caller, timestamp);
self.env().emit_event(Withdrawal { to: caller, amount: self.withdrawal_limit });
}
#[ink(message, payable)]
pub fn deposit(&mut self) {
let caller = self.env().caller();
let amount = self.env().transferred_balance();
self.env().emit_event(Deposit { from: caller, amount });
}
#[ink(message)]
pub fn set_withdrawal_limit(&mut self, new_limit: Balance) {
assert_eq!(self.env().caller(), self.owner, "Only the owner can set the withdrawal limit.");
self.withdrawal_limit = new_limit;
}
#[ink(message)]
pub fn toggle_emergency_stop(&mut self) {
assert_eq!(self.env().caller(), self.owner, "Only the owner can toggle the emergency stop.");
self.emergency_stop = !self.emergency_stop;
}
#[ink(message)]
pub fn destroy(&mut self) {
let caller = self.env().caller();
assert_eq!(caller, self.owner, "Only the owner of this faucet can destroy it.");
self.env().terminate_contract(self.owner);
}
#[ink(message)]
pub fn get_balance(&self) -> Balance {
self.env().balance()
}
}
impl Default for Faucet {
fn default() -> Self {
Self::new(100_000_000_000_000_000) // Default withdrawal limit set to 0.1 ETH
}
}
}
- Withdrawal Limit: Added a constructor parameter and a function to set a withdrawal limit.
- Emergency Stop: Added an emergency stop mechanism to enable/disable withdrawals.
- Get Balance: Added a function to get the current balance of the contract.
- Event Enhancement: Enhanced the
Withdrawal
event to include the withdrawn amount. - Constructor Initialization: The constructor now initializes the withdrawal limit and the emergency stop flag.
-
Install the
ink!
toolchain: Follow the instructions from the ink! documentation. -
Create a new
ink!
project:cargo contract new faucet
-
Replace the generated contract code with the provided Rust code.
-
Compile and deploy the contract:
cargo contract build cargo contract deploy --constructor new 100000000000000000
-
Interact with the contract:
- Withdraw: Call the
withdraw
function to withdraw Ether. - Deposit: Send Ether to the contract to deposit.
- Set Withdrawal Limit: Call the
set_withdrawal_limit
function to change the limit. - Toggle Emergency Stop: Call the
toggle_emergency_stop
function to enable/disable withdrawals. - Get Balance: Call the
get_balance
function to check the contract's balance. - Destroy: Call the
destroy
function to destroy the contract and send remaining funds to the owner.
- Withdraw: Call the
To transition this smart contract to the Solana network and add more features, we can use the Anchor
framework. Below are some suggested features and the updated code for Solana.
- Incentive System for Depositors:
- Implement a mechanism to reward users who deposit Ether into the contract.
- Whitelist for Authorized Withdrawals:
- Add a whitelist for addresses that are allowed to withdraw from the contract.
- Graphical Transaction Reporting:
- Use stored data to display graphical reports of transactions in the UI.
- Integration with Solana Wallets:
- Ensure compatibility with popular Solana wallets like Phantom.
To transition this contract to the Solana network, we will use the Anchor
framework, a framework for developing smart contracts on Solana using Rust. Below is the code for a similar contract on Solana:
use anchor_lang::prelude::*;
declare_id!("YourProgramID");
#[program]
pub mod faucet {
use super::*;
use std::collections::HashMap;
#[state]
pub struct Faucet {
owner: Pubkey,
timeouts: HashMap<Pubkey, i64>,
withdrawal_limit: u64,
emergency_stop: bool,
max_balance: u64,
transactions: Vec<Transaction>,
}
#[account]
pub struct Transaction {
from: Pubkey,
to: Pubkey,
amount: u64,
timestamp: i64,
}
impl Faucet {
pub fn new(ctx: Context<Initialize>, withdrawal_limit: u64, max_balance: u64) -> Result<Self> {
let owner = *ctx.accounts.owner.key;
Ok(Self {
owner,
timeouts: HashMap::new(),
withdrawal_limit,
emergency_stop: false,
max_balance,
transactions: Vec::new(),
})
}
pub fn withdraw(&mut self, ctx: Context<Withdraw>) -> Result<()> {
let caller = *ctx.accounts.caller.key;
let clock = Clock::get()?;
let balance = ctx.accounts.faucet.to_account_info().lamports();
require!(!self.emergency_stop, FaucetError::WithdrawalsDisabled);
require!(balance >= self.withdrawal_limit, FaucetError::InsufficientFunds);
require!(self.timeouts.get(&caller).unwrap_or(&0) <= &(clock.unix_timestamp - 1800), FaucetError::TimeoutNotReached);
**ctx.accounts.caller.try_borrow_mut_lamports()? += self.withdrawal_limit;
**ctx.accounts.faucet.to_account_info().try_borrow_mut_lamports()? -= self.withdrawal_limit;
self.timeouts.insert(caller, clock.unix_timestamp);
self.transactions.push(Transaction {
from: *ctx.accounts.faucet.key,
to: caller,
amount: self.withdrawal_limit,
timestamp: clock.unix_timestamp,
});
Ok(())
}
pub fn deposit(&mut self, ctx: Context<Deposit>, amount: u64) -> Result<()> {
let caller = *ctx.accounts.caller.key;
let balance = ctx.accounts.faucet.to_account_info().lamports() + amount;
require!(balance <= self.max_balance, FaucetError::MaxBalanceExceeded);
**ctx.accounts.faucet.to_account_info().try_borrow_mut_lamports()? += amount;
self.transactions.push(Transaction {
from: caller,
to: *ctx.accounts.faucet.key,
amount,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
pub fn set_withdrawal_limit(&mut self, ctx: Context<SetLimit>, new_limit: u64) -> Result<()> {
require!(ctx.accounts.owner.key == self.owner, FaucetError::Unauthorized);
self.withdrawal_limit = new_limit;
Ok(())
}
pub fn set_max_balance(&mut self, ctx: Context<SetLimit>, new_max_balance: u64) -> Result<()> {
require!(ctx.accounts.owner.key == self.owner, FaucetError::Unauthorized);
self.max_balance = new_max_balance;
Ok(())
}
pub fn toggle_emergency_stop(&mut self, ctx: Context<ToggleEmergency>) -> Result<()> {
require!(ctx.accounts.owner.key == self.owner, FaucetError::Unauthorized);
self.emergency_stop = !self.emergency_stop;
Ok(())
}
pub fn transfer_ownership(&mut self, ctx: Context<TransferOwnership>, new_owner: Pubkey) -> Result<()> {
require!(ctx.accounts.owner.key == self.owner, FaucetError::Unauthorized);
self.owner = new_owner;
Ok(())
}
pub fn get_balance(&self, ctx: Context<GetBalance>) -> Result<u64> {
Ok(ctx.accounts.faucet.to_account_info().lamports())
}
pub fn get_time_until_next_withdrawal(&self, ctx: Context<GetTimeUntilNextWithdrawal>, account: Pubkey) -> Result<i64> {
let current_time = Clock::get()?.unix_timestamp;
let last_withdrawal = self.timeouts.get(&account).unwrap_or(&0);
if *last_withdrawal == 0 || current_time >= last_withdrawal + 1800 {
Ok(0)
} else {
Ok((last_withdrawal + 1800) - current_time)
}
}
pub fn get_transactions(&self, _ctx: Context<GetTransactions>) -> Result<Vec<Transaction>> {
Ok(self.transactions.clone())
}
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(signer)]
pub owner: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub caller: AccountInfo<'info>,
#[account(mut)]
pub faucet: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(signer)]
pub caller: AccountInfo<'info>,
#[account(mut)]
pub faucet: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct SetLimit<'info> {
#[account(signer)]
pub owner: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct ToggleEmergency<'info> {
#[account(signer)]
pub owner: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TransferOwnership<'info> {
#[account(signer)]
pub owner: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct GetBalance<'info> {
pub faucet: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct GetTimeUntilNextWithdrawal<'info> {}
#[derive(Accounts)]
pub struct GetTransactions<'info> {}
#[error]
pub enum FaucetError {
#[msg("Withdrawals are currently disabled.")]
WithdrawalsDisabled,
#[msg("Insufficient funds in the faucet.")]
InsufficientFunds,
#[msg("Timeout for next withdrawal not reached.")]
TimeoutNotReached,
#[msg("Unauthorized action.")]
Unauthorized,
#[msg("Maximum balance exceeded.")]
MaxBalanceExceeded,
}
- Incentive System for Depositors: Implement a reward system for users who deposit funds into the contract.
- Whitelist for Authorized Withdrawals: Add a whitelist to allow only specific addresses to withdraw from the contract.
- Graphical Transaction Reporting: Use stored transaction data to display graphical reports in the UI.
- Integration with Solana Wallets: Ensure compatibility with popular Solana wallets like Phantom.
-
Install Anchor:
cargo install --git https://github.com/project-serum/anchor --tag v0.19.0 anchor-cli --locked
-
Create a New Anchor Project:
anchor init faucet
-
Replace the Generated Contract Code: Replace the generated code with the provided code above.
-
Build and Deploy the Contract:
anchor build anchor deploy
These steps will help you install the Anchor
tools, create a new project, and deploy your smart contract on the Solana network.