以太坊智能合约和Solidity开发
全面的以太坊智能合约开发示例,包括ERC-20、ERC-721、DeFi协议和硬件钱包集成
💻 Solidity基础和智能合约 solidity
🟢 simple
⭐⭐
Solidity核心概念、数据类型、函数、修饰符、事件和完整的简单智能合约示例
⏱️ 30 min
🏷️ solidity, basics, smart-contracts, ethereum
Prerequisites:
Basic programming concepts, Blockchain fundamentals
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Basic Solidity Concepts and Data Types
contract SolidityBasics {
// State Variables
uint256 public myNumber = 42;
string public myString = "Hello, Solidity!";
bool public myBool = true;
address public owner;
// Integer Types
int256 public myInt = -100;
uint8 public smallUint = 255;
uint256 public largeUint;
// Fixed Size Arrays
uint256[5] public fixedArray = [1, 2, 3, 4, 5];
address[3] public addressArray;
// Dynamic Arrays
uint256[] public dynamicArray;
string[] public stringArray;
// Mappings (Key-Value Pairs)
mapping(address => uint256) public balances;
mapping(string => address) public names;
// Structs (Custom Data Types)
struct Person {
string name;
uint256 age;
bool isStudent;
}
Person public defaultPerson;
mapping(address => Person) public people;
// Enums (Limited Set of Values)
enum State { Created, Locked, Inactive }
State public currentState;
// Constants and Immutables
uint256 public constant MAX_SUPPLY = 1000000;
address public immutable CREATOR;
// Events
event NumberChanged(uint256 oldValue, uint256 newValue);
event PersonAdded(address indexed personAddress, string name, uint256 age);
event StateChanged(State oldState, State newState);
event EtherReceived(address indexed from, uint256 amount);
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
modifier validAddress(address _addr) {
require(_addr != address(0), "Invalid address");
_;
}
modifier costs(uint256 amount) {
require(msg.value >= amount, "Not enough ether sent");
_;
}
// Constructor (runs once during deployment)
constructor() {
owner = msg.sender;
CREATOR = msg.sender;
defaultPerson = Person("Default", 25, false);
currentState = State.Created;
}
// Basic Functions
function setNumber(uint256 _newNumber) public {
uint256 oldValue = myNumber;
myNumber = _newNumber;
emit NumberChanged(oldValue, _newNumber);
}
function getNumber() public view returns (uint256) {
return myNumber;
}
function addPerson(string memory _name, uint256 _age) public validAddress(msg.sender) {
people[msg.sender] = Person(_name, _age, false);
emit PersonAdded(msg.sender, _name, _age);
}
function getPerson(address _addr) public view returns (string memory name, uint256 age, bool isStudent) {
Person memory person = people[_addr];
return (person.name, person.age, person.isStudent);
}
// Array Operations
function addToDynamicArray(uint256 _value) public {
dynamicArray.push(_value);
}
function getDynamicArrayLength() public view returns (uint256) {
return dynamicArray.length;
}
function getDynamicArrayElement(uint256 _index) public view returns (uint256) {
require(_index < dynamicArray.length, "Index out of bounds");
return dynamicArray[_index];
}
// Mapping Operations
function setBalance(address _addr, uint256 _amount) public {
balances[_addr] = _amount;
}
function getBalance(address _addr) public view returns (uint256) {
return balances[_addr];
}
// State Management
function changeState(State _newState) public onlyOwner {
State oldState = currentState;
currentState = _newState;
emit StateChanged(oldState, _newState);
}
// Ether Handling
function deposit() public payable {
require(msg.value > 0, "Must send ether");
emit EtherReceived(msg.sender, msg.value);
}
function withdraw(uint256 _amount) public onlyOwner {
require(address(this).balance >= _amount, "Insufficient contract balance");
payable(owner).transfer(_amount);
}
// Get contract balance
function getBalance() public view returns (uint256) {
return address(this).balance;
}
// Fallback function to receive ether
receive() external payable {
emit EtherReceived(msg.sender, msg.value);
}
// Fallback function for unknown function calls
fallback() external payable {
emit EtherReceived(msg.sender, msg.value);
}
}
// Advanced Example: Simple Voting Contract
contract SimpleVoting {
struct Candidate {
string name;
uint256 voteCount;
bool exists;
}
mapping(string => Candidate) public candidates;
mapping(address => bool) public hasVoted;
string[] public candidateNames;
uint256 public votingEndTime;
bool public votingActive;
event VoteCast(address indexed voter, string candidateName);
event CandidateAdded(string candidateName);
event VotingStarted(uint256 endTime);
event VotingEnded();
modifier onlyWhileActive() {
require(votingActive && block.timestamp < votingEndTime, "Voting is not active");
_;
}
modifier onlyOnce() {
require(!hasVoted[msg.sender], "You have already voted");
_;
}
constructor() {
votingActive = false;
}
function addCandidate(string memory _name) public {
require(!candidates[_name].exists, "Candidate already exists");
require(bytes(_name).length > 0, "Candidate name cannot be empty");
candidates[_name] = Candidate({
name: _name,
voteCount: 0,
exists: true
});
candidateNames.push(_name);
emit CandidateAdded(_name);
}
function startVoting(uint256 _durationInMinutes) public {
require(!votingActive, "Voting is already active");
require(candidateNames.length >= 2, "Need at least 2 candidates");
votingActive = true;
votingEndTime = block.timestamp + (_durationInMinutes * 60);
emit VotingStarted(votingEndTime);
}
function vote(string memory _candidateName) public onlyWhileActive onlyOnce {
require(candidates[_candidateName].exists, "Candidate does not exist");
candidates[_candidateName].voteCount++;
hasVoted[msg.sender] = true;
emit VoteCast(msg.sender, _candidateName);
}
function endVoting() public {
require(votingActive, "Voting is not active");
require(block.timestamp >= votingEndTime, "Voting period has not ended");
votingActive = false;
emit VotingEnded();
}
function getWinner() public view returns (string memory winnerName, uint256 winnerVotes) {
require(!votingActive, "Voting is still active");
uint256 maxVotes = 0;
winnerName = "No candidates";
for (uint256 i = 0; i < candidateNames.length; i++) {
string memory name = candidateNames[i];
if (candidates[name].voteCount > maxVotes) {
maxVotes = candidates[name].voteCount;
winnerName = name;
}
}
winnerVotes = maxVotes;
}
function getCandidateInfo(string memory _name) public view returns (string memory, uint256, bool) {
return (
candidates[_name].name,
candidates[_name].voteCount,
candidates[_name].exists
);
}
function getTotalCandidates() public view returns (uint256) {
return candidateNames.length;
}
}
// Library Example: SafeMath
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
return c;
}
}
// Example using the SafeMath library
contract BankAccount {
using SafeMath for uint256;
mapping(address => uint256) public balances;
event Deposit(address indexed from, uint256 amount);
event Withdrawal(address indexed to, uint256 amount);
function deposit() public payable {
require(msg.value > 0, "Must deposit ether");
balances[msg.sender] = balances[msg.sender].add(msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 _amount) public {
require(_amount <= balances[msg.sender], "Insufficient balance");
balances[msg.sender] = balances[msg.sender].sub(_amount);
payable(msg.sender).transfer(_amount);
emit Withdrawal(msg.sender, _amount);
}
function getBalance() public view returns (uint256) {
return balances[msg.sender];
}
}
💻 ERC-20代币标准实现 solidity
🟡 intermediate
⭐⭐⭐⭐
完整的ERC-20代币合约,包含铸造、销毁、暂停和高级功能如投票权和时间锁转账
⏱️ 45 min
🏷️ erc20, token, solidity, ethereum, smart-contracts
Prerequisites:
Solidity basics, ERC-20 standard, Token economics
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Interface for ERC-20 Token Standard
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// OpenZeppelin's SafeMath library for safe arithmetic operations
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
return c;
}
}
// Advanced ERC-20 Token Contract with Additional Features
contract AdvancedERC20Token is IERC20 {
using SafeMath for uint256;
// Token Metadata
string public name;
string public symbol;
uint8 public decimals;
// Token State Variables
uint256 private _totalSupply;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
// Ownership
address public owner;
address public minter;
// Pausable Feature
bool public paused = false;
// Time-locked Transfers
mapping(address => uint256) public lockedUntil;
mapping(address => uint256) public lockAmount;
// Blacklist
mapping(address => bool) public blacklisted;
// Vesting Schedule
struct VestingSchedule {
uint256 totalAmount;
uint256 releasedAmount;
uint256 startTime;
uint256 duration;
bool isActive;
}
mapping(address => VestingSchedule) public vestingSchedules;
// Voting Rights
mapping(address => uint256) public votingPower;
uint256 public totalVotingPower;
// Events
event TokensMinted(address indexed to, uint256 amount);
event TokensBurned(address indexed from, uint256 amount);
event TokensLocked(address indexed account, uint256 amount, uint256 until);
event TokensUnlocked(address indexed account, uint256 amount);
event AccountBlacklisted(address indexed account);
event AccountWhitelisted(address indexed account);
event VestingCreated(address indexed beneficiary, uint256 amount, uint256 duration);
event VestingClaimed(address indexed beneficiary, uint256 amount);
event Pause();
event Unpause();
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event MinterChanged(address indexed previousMinter, address indexed newMinter);
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
modifier onlyMinter() {
require(msg.sender == minter || msg.sender == owner, "Only minter can call this function");
_;
}
modifier whenNotPaused() {
require(!paused, "Token contract is paused");
_;
}
modifier notBlacklisted(address _account) {
require(!blacklisted[_account], "Account is blacklisted");
_;
}
modifier validAddress(address _account) {
require(_account != address(0), "Invalid address");
_;
}
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
owner = msg.sender;
minter = msg.sender;
if (_initialSupply > 0) {
_mint(msg.sender, _initialSupply);
}
}
// ERC-20 Standard Functions
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(to)
validAddress(to)
returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
validAddress(spender)
returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public override
whenNotPaused
notBlacklisted(from)
notBlacklisted(to)
notBlacklisted(msg.sender)
validAddress(to)
returns (bool) {
uint256 currentAllowance = _allowances[from][msg.sender];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
_transfer(from, to, amount);
_approve(from, msg.sender, currentAllowance - amount);
return true;
}
// Internal Functions
function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
// Check for locked tokens
require(block.timestamp >= lockedUntil[from] || fromBalance - amount >= lockAmount[from],
"ERC20: transfer of locked tokens");
_balances[from] = fromBalance - amount;
_balances[to] = _balances[to].add(amount);
// Update voting power
_updateVotingPower(from);
_updateVotingPower(to);
emit Transfer(from, to, amount);
}
function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _mint(address to, uint256 amount) internal {
require(to != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[to] = _balances[to].add(amount);
_updateVotingPower(to);
emit Transfer(address(0), to, amount);
emit TokensMinted(to, amount);
}
function _burn(address from, uint256 amount) internal {
require(from != address(0), "ERC20: burn from the zero address");
uint256 accountBalance = _balances[from];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
_balances[from] = accountBalance - amount;
_totalSupply = _totalSupply - amount;
_updateVotingPower(from);
emit Transfer(from, address(0), amount);
emit TokensBurned(from, amount);
}
function _updateVotingPower(address account) internal {
uint256 newPower = _balances[account];
uint256 oldPower = votingPower[account];
votingPower[account] = newPower;
totalVotingPower = totalVotingPower.sub(oldPower).add(newPower);
}
// Minting and Burning
function mint(address to, uint256 amount) external onlyMinter validAddress(to) {
require(to != address(0), "Cannot mint to zero address");
_mint(to, amount);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
function burnFrom(address from, uint256 amount) external {
uint256 currentAllowance = _allowances[from][msg.sender];
require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
_burn(from, amount);
_approve(from, msg.sender, currentAllowance - amount);
}
// Pausable Functions
function pause() external onlyOwner {
require(!paused, "Contract is already paused");
paused = true;
emit Pause();
}
function unpause() external onlyOwner {
require(paused, "Contract is not paused");
paused = false;
emit Unpause();
}
// Ownership Management
function transferOwnership(address newOwner) external onlyOwner validAddress(newOwner) {
address oldOwner = owner;
owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
function setMinter(address newMinter) external onlyOwner validAddress(newMinter) {
address oldMinter = minter;
minter = newMinter;
emit MinterChanged(oldMinter, newMinter);
}
// Token Locking
function lockTokens(uint256 amount, uint256 duration) external {
require(amount <= _balances[msg.sender], "Insufficient balance to lock");
lockAmount[msg.sender] = lockAmount[msg.sender].add(amount);
lockedUntil[msg.sender] = lockedUntil[msg.sender].add(duration);
emit TokensLocked(msg.sender, amount, lockedUntil[msg.sender]);
}
function unlockTokens() external {
require(block.timestamp >= lockedUntil[msg.sender], "Tokens are still locked");
require(lockAmount[msg.sender] > 0, "No tokens to unlock");
uint256 amount = lockAmount[msg.sender];
lockAmount[msg.sender] = 0;
emit TokensUnlocked(msg.sender, amount);
}
// Blacklist Management
function blacklist(address account) external onlyOwner validAddress(account) {
blacklisted[account] = true;
emit AccountBlacklisted(account);
}
function whitelist(address account) external onlyOwner validAddress(account) {
blacklisted[account] = false;
emit AccountWhitelisted(account);
}
// Vesting Functions
function createVesting(
address beneficiary,
uint256 amount,
uint256 duration
) external onlyMinter validAddress(beneficiary) {
require(amount > 0, "Vesting amount must be greater than 0");
require(duration > 0, "Vesting duration must be greater than 0");
vestingSchedules[beneficiary] = VestingSchedule({
totalAmount: amount,
releasedAmount: 0,
startTime: block.timestamp,
duration: duration,
isActive: true
});
_mint(beneficiary, amount);
emit VestingCreated(beneficiary, amount, duration);
}
function claimVesting() external {
VestingSchedule storage schedule = vestingSchedules[msg.sender];
require(schedule.isActive, "No active vesting schedule");
uint256 timePassed = block.timestamp - schedule.startTime;
uint256 vestedAmount;
if (timePassed >= schedule.duration) {
vestedAmount = schedule.totalAmount;
} else {
vestedAmount = (schedule.totalAmount * timePassed) / schedule.duration;
}
uint256 claimableAmount = vestedAmount - schedule.releasedAmount;
require(claimableAmount > 0, "No tokens to claim");
schedule.releasedAmount = vestedAmount;
if (timePassed >= schedule.duration) {
schedule.isActive = false;
}
emit VestingClaimed(msg.sender, claimableAmount);
}
// View Functions
function getVestingInfo(address beneficiary) external view returns (
uint256 totalAmount,
uint256 releasedAmount,
uint256 vestedAmount,
uint256 claimableAmount,
uint256 startTime,
uint256 duration,
bool isActive
) {
VestingSchedule memory schedule = vestingSchedules[beneficiary];
uint256 timePassed = block.timestamp - schedule.startTime;
uint256 _vestedAmount;
if (timePassed >= schedule.duration) {
_vestedAmount = schedule.totalAmount;
} else {
_vestedAmount = (schedule.totalAmount * timePassed) / schedule.duration;
}
uint256 _claimableAmount = _vestedAmount - schedule.releasedAmount;
return (
schedule.totalAmount,
schedule.releasedAmount,
_vestedAmount,
_claimableAmount,
schedule.startTime,
schedule.duration,
schedule.isActive
);
}
function getLockedTokens(address account) external view returns (uint256 amount, uint256 until) {
return (lockAmount[account], lockedUntil[account]);
}
// Batch Operations
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts)
external
whenNotPaused
returns (bool) {
require(recipients.length == amounts.length, "Arrays must have same length");
require(recipients.length > 0, "Arrays cannot be empty");
for (uint256 i = 0; i < recipients.length; i++) {
require(recipients[i] != address(0), "Invalid recipient address");
require(!blacklisted[recipients[i]], "Recipient is blacklisted");
_transfer(msg.sender, recipients[i], amounts[i]);
}
return true;
}
function batchApprove(address[] calldata spenders, uint256[] calldata amounts)
external
whenNotPaused
returns (bool) {
require(spenders.length == amounts.length, "Arrays must have same length");
require(spenders.length > 0, "Arrays cannot be empty");
for (uint256 i = 0; i < spenders.length; i++) {
require(spenders[i] != address(0), "Invalid spender address");
require(!blacklisted[spenders[i]], "Spender is blacklisted");
_approve(msg.sender, spenders[i], amounts[i]);
}
return true;
}
}
💻 ERC-721 NFT合约和元数据 solidity
🟡 intermediate
⭐⭐⭐⭐
完整的ERC-721非同质化代币实现,包含IPFS元数据、版税系统和市场集成
⏱️ 50 min
🏷️ erc721, nft, solidity, ethereum, smart-contracts
Prerequisites:
Solidity basics, ERC-721 standard, IPFS, Metadata standards
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// ERC-721 Non-Fungible Token Standard
interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// ERC-721 Metadata Extension
interface IERC721Metadata is IERC721 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// ERC-721 Enumerable Extension
interface IERC721Enumerable is IERC721 {
function totalSupply() external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
}
// OpenZeppelin libraries
library Counters {
struct Counter {
uint256 _value;
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
counter._value += 1;
}
function decrement(Counter storage counter) internal {
counter._value = counter._value > 0 ? counter._value - 1 : 0;
}
}
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
}
library Strings {
function toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}
// NFT Metadata Structure
struct NFTMetadata {
string name;
string description;
string image;
string external_url;
string animation_url;
string background_color;
string youtube_url;
Attribute[] attributes;
}
struct Attribute {
string trait_type;
string value;
uint256 display_type; // 0: string, 1: number, 2: boost_percentage
}
// Royalty Structure
struct RoyaltyInfo {
address recipient;
uint256 percentage; // Percentage in basis points (10000 = 100%)
}
// Advanced ERC-721 NFT Contract
contract AdvancedNFT is IERC721, IERC721Metadata, IERC721Enumerable {
using Counters for Counters.Counter;
using Strings for uint256;
using Address for address;
// Token State Variables
Counters.Counter private _tokenIdCounter;
string private _name;
string private _symbol;
string private _baseTokenURI;
// Mappings
mapping(uint256 => address) private _owners;
mapping(address => uint256) private _balances;
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Owner mappings for enumeration
mapping(address => uint256[]) private _ownedTokens;
mapping(uint256 => uint256) private _ownedTokensIndex;
mapping(uint256 => uint256) private _allTokensIndex;
uint256[] private _allTokens;
// NFT-specific data
mapping(uint256 => NFTMetadata) private _tokenMetadata;
mapping(uint256 => RoyaltyInfo) private _royalties;
mapping(uint256 => bool) private _exists;
mapping(uint256 => bool) private _burned;
// Contract metadata
address public owner;
address public minter;
uint256 public maxSupply;
uint256 public mintPrice;
bool public mintingActive;
// Marketplace integration
mapping(uint256 => bool) public _forSale;
mapping(uint256 => uint256) public _salePrice;
mapping(uint256 => address) public _saleCurrency; // address(0) for ETH
// Events
event TokenMinted(address indexed to, uint256 indexed tokenId);
event TokenBurned(uint256 indexed tokenId);
event MetadataUpdated(uint256 indexed tokenId);
event RoyaltySet(uint256 indexed tokenId, address recipient, uint256 percentage);
event TokenListed(uint256 indexed tokenId, uint256 price, address currency);
event TokenSold(uint256 indexed tokenId, address indexed from, address indexed to, uint256 price);
event MintingStateChanged(bool active);
event MintPriceChanged(uint256 newPrice);
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
modifier onlyMinter() {
require(msg.sender == minter || msg.sender == owner, "Only minter can call this function");
_;
}
modifier whenMintingActive() {
require(mintingActive, "Minting is not active");
_;
}
modifier validToken(uint256 tokenId) {
require(_exists[tokenId] && !_burned[tokenId], "Token does not exist");
_;
}
modifier onlyTokenOwner(uint256 tokenId) {
require(ownerOf(tokenId) == msg.sender, "Only token owner can call this function");
_;
}
constructor(
string memory name_,
string memory symbol_,
string memory baseTokenURI_,
uint256 maxSupply_,
uint256 mintPrice_
) {
_name = name_;
_symbol = symbol_;
_baseTokenURI = baseTokenURI_;
maxSupply = maxSupply_;
mintPrice = mintPrice_;
owner = msg.sender;
minter = msg.sender;
mintingActive = false;
}
// ERC-721 Standard Functions
function name() external view override returns (string memory) {
return _name;
}
function symbol() external view override returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) external view override validToken(tokenId) returns (string memory) {
string memory baseURI = _baseTokenURI;
return bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, tokenId.toString()))
: "";
}
function balanceOf(address owner) external view override returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address");
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view override validToken(tokenId) returns (address) {
return _owners[tokenId];
}
function approve(address to, uint256 tokenId) external override validToken(tokenId) {
address owner = ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
"ERC721: approve caller is not owner nor approved for all");
_approve(to, tokenId);
}
function getApproved(uint256 tokenId) public view override validToken(tokenId) returns (address) {
return _tokenApprovals[tokenId];
}
function setApprovalForAll(address operator, bool approved) external override {
require(operator != msg.sender, "ERC721: approve to caller");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function isApprovedForAll(address owner, address operator) public view override returns (bool) {
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) external override validToken(tokenId) {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) external override {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override validToken(tokenId) {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(from, to, tokenId, data);
}
// ERC-721 Enumerable Functions
function totalSupply() public view override returns (uint256) {
return _allTokens.length;
}
function tokenByIndex(uint256 index) public view override returns (uint256) {
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
// Internal Functions
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
function _transfer(address from, address to, uint256 tokenId) internal validToken(tokenId) {
require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId);
// Clear approvals
_approve(address(0), tokenId);
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
// Update owned tokens list
_removeTokenFromOwnerEnumeration(from, tokenId);
_addTokenToOwnerEnumeration(to, tokenId);
emit Transfer(from, to, tokenId);
}
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists[tokenId], "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1;
_owners[tokenId] = to;
_exists[tokenId] = true;
_addTokenToOwnerEnumeration(to, tokenId);
_addTokenToAllTokensEnumeration(tokenId);
emit Transfer(address(0), to, tokenId);
}
function _burn(uint256 tokenId) internal validToken(tokenId) {
address owner = ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
// Clear approvals
_approve(address(0), tokenId);
_balances[owner] -= 1;
_burned[tokenId] = true;
// Remove from owner enumeration
_removeTokenFromOwnerEnumeration(owner, tokenId);
// Note: Keep in _allTokens for totalSupply consistency
emit Transfer(owner, address(0), tokenId);
emit TokenBurned(tokenId);
}
function _approve(address to, uint256 tokenId) internal validToken(tokenId) {
_tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
require(_exists[tokenId], "ERC721: operator query for nonexistent token");
address owner = ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch {
return false;
}
}
return true;
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal {
// Can be overridden by child contracts
}
// Enumeration helpers
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = balanceOf(to);
_ownedTokens[to].push(tokenId);
_ownedTokensIndex[tokenId] = length;
}
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
uint256 lastTokenIndex = balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId;
_ownedTokensIndex[lastTokenId] = tokenIndex;
}
delete _ownedTokensIndex[tokenId];
_ownedTokens[from].pop();
}
// NFT-specific Functions
function mintNFT(address to, NFTMetadata memory metadata) external onlyMinter whenMintingActive returns (uint256) {
require(_tokenIdCounter.current() < maxSupply, "Max supply reached");
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_mint(to, tokenId);
_setTokenMetadata(tokenId, metadata);
emit TokenMinted(to, tokenId);
return tokenId;
}
function mintBatch(address to, uint256 amount, NFTMetadata[] memory metadata) external onlyMinter whenMintingActive returns (uint256[] memory) {
require(_tokenIdCounter.current() + amount <= maxSupply, "Max supply reached");
require(amount == metadata.length, "Metadata array length mismatch");
uint256[] memory tokenIds = new uint256[](amount);
for (uint256 i = 0; i < amount; i++) {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_mint(to, tokenId);
_setTokenMetadata(tokenId, metadata[i]);
tokenIds[i] = tokenId;
emit TokenMinted(to, tokenId);
}
return tokenIds;
}
function burn(uint256 tokenId) external onlyTokenOwner(tokenId) {
_burn(tokenId);
}
function setTokenMetadata(uint256 tokenId, NFTMetadata memory metadata) external onlyTokenOwner(tokenId) {
_setTokenMetadata(tokenId, metadata);
emit MetadataUpdated(tokenId);
}
function _setTokenMetadata(uint256 tokenId, NFTMetadata memory metadata) internal {
_tokenMetadata[tokenId] = metadata;
}
function getTokenMetadata(uint256 tokenId) external view validToken(tokenId) returns (NFTMetadata memory) {
return _tokenMetadata[tokenId];
}
function setRoyalty(uint256 tokenId, address recipient, uint256 percentage) external onlyTokenOwner(tokenId) {
require(percentage <= 10000, "Royalty percentage cannot exceed 100%");
_royalties[tokenId] = RoyaltyInfo(recipient, percentage);
emit RoyaltySet(tokenId, recipient, percentage);
}
function getRoyalty(uint256 tokenId) external view validToken(tokenId) returns (address recipient, uint256 percentage) {
RoyaltyInfo memory royalty = _royalties[tokenId];
return (royalty.recipient, royalty.percentage);
}
// Marketplace Functions
function listForSale(uint256 tokenId, uint256 price, address currency) external onlyTokenOwner(tokenId) {
_forSale[tokenId] = true;
_salePrice[tokenId] = price;
_saleCurrency[tokenId] = currency;
emit TokenListed(tokenId, price, currency);
}
function removeFromSale(uint256 tokenId) external onlyTokenOwner(tokenId) {
_forSale[tokenId] = false;
_salePrice[tokenId] = 0;
_saleCurrency[tokenId] = address(0);
}
function buyToken(uint256 tokenId) external payable validToken(tokenId) {
require(_forSale[tokenId], "Token not for sale");
address seller = ownerOf(tokenId);
require(msg.sender != seller, "Cannot buy your own token");
address currency = _saleCurrency[tokenId];
uint256 price = _salePrice[tokenId];
if (currency == address(0)) {
require(msg.value >= price, "Insufficient ETH sent");
payable(seller).transfer(price);
}
// Handle royalty
(address royaltyRecipient, uint256 royaltyPercentage) = getRoyalty(tokenId);
if (royaltyRecipient != address(0) && royaltyPercentage > 0) {
uint256 royaltyAmount = (price * royaltyPercentage) / 10000;
if (currency == address(0)) {
payable(royaltyRecipient).transfer(royaltyAmount);
}
}
_transfer(seller, msg.sender, tokenId);
// Remove from sale
_forSale[tokenId] = false;
_salePrice[tokenId] = 0;
_saleCurrency[tokenId] = address(0);
emit TokenSold(tokenId, seller, msg.sender, price);
}
// View Functions
function getSaleInfo(uint256 tokenId) external view returns (bool forSale, uint256 price, address currency) {
return (_forSale[tokenId], _salePrice[tokenId], _saleCurrency[tokenId]);
}
function tokensOfOwner(address owner) external view returns (uint256[] memory) {
uint256 balance = balanceOf(owner);
uint256[] memory result = new uint256[](balance);
for (uint256 i = 0; i < balance; i++) {
result[i] = tokenOfOwnerByIndex(owner, i);
}
return result;
}
function exists(uint256 tokenId) external view returns (bool) {
return _exists[tokenId] && !_burned[tokenId];
}
// Admin Functions
function setBaseURI(string memory baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function setMintingActive(bool active) external onlyOwner {
mintingActive = active;
emit MintingStateChanged(active);
}
function setMintPrice(uint256 newPrice) external onlyOwner {
mintPrice = newPrice;
emit MintPriceChanged(newPrice);
}
function setMinter(address newMinter) external onlyOwner {
minter = newMinter;
}
function transferOwnership(address newOwner) external onlyOwner {
owner = newOwner;
}
// Emergency Functions
function emergencyWithdraw() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
// Interface for ERC721 Receiver
interface IERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
💻 DeFi自动做市商(AMM)协议 solidity
🔴 complex
⭐⭐⭐⭐⭐
完整的AMM实现,包含流动性池、交换功能、流动性挖矿和治理代币
⏱️ 60 min
🏷️ defi, amm, liquidity, yield-farming, solidity
Prerequisites:
Advanced Solidity, DeFi concepts, Mathematical modeling, Gas optimization
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Interfaces for DeFi protocols
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
}
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
// Library for safe math operations
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
return c;
}
}
// Library for Uniswap-style calculations
library UniswapV2Library {
using SafeMath for uint256;
// Minimum amount out calculation
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256 amountOut) {
require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
uint256 numerator = amountIn.mul(reserveOut);
uint256 denominator = reserveIn.add(amountIn);
amountOut = numerator / denominator;
}
// Minimum amount in calculation
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256 amountIn) {
require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
uint256 numerator = reserveIn.mul(amountOut);
uint256 denominator = reserveOut.sub(amountOut);
amountIn = numerator / denominator;
}
// K calculation (constant product)
function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) internal pure returns (uint256 amountB) {
require(amountA > 0, "UniswapV2Library: INSUFFICIENT_AMOUNT");
require(reserveA > 0 && reserveB > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
amountB = amountA.mul(reserveB) / reserveA;
}
}
// Liquidity Pool structure
struct LiquidityPool {
address token0;
address token1;
uint256 reserve0;
uint256 reserve1;
uint256 totalLiquidity;
address feeTo;
uint256 feeNumerator; // Fee numerator (0-10000 basis points)
bool initialized;
}
// Position structure for liquidity providers
struct Position {
uint256 liquidity;
uint256 token0Amount;
uint256 token1Amount;
uint256 lastUpdateTime;
uint256 unclaimedFees0;
uint256 unclaimedFees1;
}
// Farming position structure
struct FarmingPosition {
uint256 amount;
uint256 startTime;
uint256 rewardDebt;
}
// Advanced AMM Protocol with Yield Farming
class AdvancedAMM {
using SafeMath for uint256;
using UniswapV2Library for uint256;
// State variables
address public owner;
address public governanceToken;
address public WETH;
// Pools and positions
mapping(bytes32 => LiquidityPool) public pools;
mapping(address => mapping(bytes32 => Position)) public positions;
mapping(address => mapping(bytes32 => FarmingPosition)) public farmingPositions;
// Global state
uint256 public totalValueLocked;
uint256 public governanceRate; // Governance tokens per second
// Fee configuration
uint256 public protocolFeeNumerator = 3; // 0.3% protocol fee
uint256 public liquidityProviderFeeNumerator = 25; // 0.25% LP fee
// Events
event PoolCreated(bytes32 indexed poolId, address indexed token0, address indexed token1);
event LiquidityAdded(bytes32 indexed poolId, address indexed provider, uint256 amount0, uint256 amount1, uint256 liquidity);
event LiquidityRemoved(bytes32 indexed poolId, address indexed provider, uint256 amount0, uint256 amount1, uint256 liquidity);
event Swap(bytes32 indexed poolId, address indexed user, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut);
event FarmingPositionCreated(bytes32 indexed poolId, address indexed user, uint256 amount);
event Harvested(bytes32 indexed poolId, address indexed user, uint256 governanceReward);
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
modifier validPool(bytes32 poolId) {
require(pools[poolId].initialized, "Pool does not exist");
_;
}
modifier validTokens(address token0, address token1) {
require(token0 != address(0) && token1 != address(0), "Invalid token addresses");
require(token0 != token1, "Tokens must be different");
_;
}
constructor(address _governanceToken, address _WETH) {
owner = msg.sender;
governanceToken = _governanceToken;
WETH = _WETH;
governanceRate = 1 ether / (365 days); // 1 token per day distributed across all farms
}
// Pool management
function createPool(address token0, address token1) external validTokens(token0, token1) returns (bytes32 poolId) {
// Ensure tokens are in correct order (address ordering)
if (token0 > token1) {
(token0, token1) = (token1, token0);
}
poolId = keccak256(abi.encodePacked(token0, token1));
require(!pools[poolId].initialized, "Pool already exists");
pools[poolId] = LiquidityPool({
token0: token0,
token1: token1,
reserve0: 0,
reserve1: 0,
totalLiquidity: 0,
feeTo: msg.sender,
feeNumerator: 30, // 0.3%
initialized: true
});
emit PoolCreated(poolId, token0, token1);
return poolId;
}
// Liquidity management
function addLiquidity(
bytes32 poolId,
uint256 amount0Desired,
uint256 amount1Desired,
uint256 amount0Min,
uint256 amount1Min,
address to
) external payable validPool(poolId) returns (uint256 amount0, uint256 amount1, uint256 liquidity) {
LiquidityPool storage pool = pools[poolId];
// Handle ETH input
if (pool.token0 == WETH || pool.token1 == WETH) {
require(msg.value > 0, "Must send ETH for ETH pool");
if (pool.token0 == WETH) {
amount0Desired = msg.value;
IWETH(WETH).deposit{value: msg.value}();
IWETH(WETH).transfer(address(this), msg.value);
} else {
amount1Desired = msg.value;
IWETH(WETH).deposit{value: msg.value}();
IWETH(WETH).transfer(address(this), msg.value);
}
}
// Calculate optimal amount if pool has no liquidity
if (pool.reserve0 == 0 && pool.reserve1 == 0) {
amount0 = amount0Desired;
amount1 = amount1Desired;
} else {
uint256 amount1Optimal = amount0Desired.mul(pool.reserve1) / pool.reserve0;
if (amount1Optimal <= amount1Desired) {
require(amount1Optimal >= amount1Min, "Insufficient amount1");
amount1 = amount1Optimal;
amount0 = amount0Desired;
} else {
uint256 amount0Optimal = amount1Desired.mul(pool.reserve0) / pool.reserve1;
require(amount0Optimal <= amount0Desired && amount0Optimal >= amount0Min, "Insufficient amount0");
amount0 = amount0Optimal;
amount1 = amount1Desired;
}
}
// Transfer tokens from user
if (pool.token0 != WETH) {
IERC20(pool.token0).transferFrom(msg.sender, address(this), amount0);
}
if (pool.token1 != WETH) {
IERC20(pool.token1).transferFrom(msg.sender, address(this), amount1);
}
// Calculate liquidity to mint
if (pool.totalLiquidity == 0) {
liquidity = amount0.mul(amount1).sqrt();
} else {
liquidity = amount0.mul(pool.totalLiquidity) / pool.reserve0;
}
// Update pool state
pool.reserve0 = pool.reserve0.add(amount0);
pool.reserve1 = pool.reserve1.add(amount1);
pool.totalLiquidity = pool.totalLiquidity.add(liquidity);
// Update user position
Position storage position = positions[to][poolId];
position.liquidity = position.liquidity.add(liquidity);
position.token0Amount = position.token0Amount.add(amount0);
position.token1Amount = position.token1Amount.add(amount1);
position.lastUpdateTime = block.timestamp;
totalValueLocked = totalValueLocked.add(amount0).add(amount1);
emit LiquidityAdded(poolId, to, amount0, amount1, liquidity);
}
function removeLiquidity(
bytes32 poolId,
uint256 liquidity,
uint256 amount0Min,
uint256 amount1Min,
address to
) external validPool(poolId) returns (uint256 amount0, uint256 amount1) {
LiquidityPool storage pool = pools[poolId];
Position storage position = positions[msg.sender][poolId];
require(position.liquidity >= liquidity, "Insufficient liquidity");
require(pool.totalLiquidity > 0, "No liquidity in pool");
// Calculate amounts to receive
amount0 = liquidity.mul(pool.reserve0) / pool.totalLiquidity;
amount1 = liquidity.mul(pool.reserve1) / pool.totalLiquidity;
require(amount0 >= amount0Min && amount1 >= amount1Min, "Slippage exceeded");
// Update pool state
pool.reserve0 = pool.reserve0.sub(amount0);
pool.reserve1 = pool.reserve1.sub(amount1);
pool.totalLiquidity = pool.totalLiquidity.sub(liquidity);
// Update user position
position.liquidity = position.liquidity.sub(liquidity);
position.token0Amount = position.token0Amount.sub(amount0);
position.token1Amount = position.token1Amount.sub(amount1);
// Add unclaimed fees
if (position.unclaimedFees0 > 0 || position.unclaimedFees1 > 0) {
amount0 = amount0.add(position.unclaimedFees0);
amount1 = amount1.add(position.unclaimedFees1);
position.unclaimedFees0 = 0;
position.unclaimedFees1 = 0;
}
// Transfer tokens to user
if (pool.token0 == WETH) {
IWETH(WETH).withdraw(amount0);
payable(to).transfer(amount0);
} else {
IERC20(pool.token0).transfer(to, amount0);
}
if (pool.token1 == WETH) {
IWETH(WETH).withdraw(amount1);
payable(to).transfer(amount1);
} else {
IERC20(pool.token1).transfer(to, amount1);
}
totalValueLocked = totalValueLocked.sub(amount0).sub(amount1);
emit LiquidityRemoved(poolId, msg.sender, amount0, amount1, liquidity);
}
// Swapping
function swap(
bytes32 poolId,
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to
) external payable validPool(poolId) returns (uint256[] memory amounts) {
LiquidityPool storage pool = pools[poolId];
require(path.length >= 2, "Invalid path");
// Handle ETH input
uint256 ethAmount = 0;
if (msg.value > 0) {
require(pool.token0 == WETH || pool.token1 == WETH, "ETH not supported in this pool");
ethAmount = msg.value;
IWETH(WETH).deposit{value: ethAmount}();
IWETH(WETH).transfer(address(this), ethAmount);
amountIn = ethAmount;
} else {
// Transfer input token
IERC20(path[0]).transferFrom(msg.sender, address(this), amountIn);
}
amounts = new uint256[](path.length);
// Single pool swap
if (path.length == 2) {
amounts = _swapSinglePool(poolId, path[0], path[1], amountIn, to);
} else {
// Multi-hop swap (simplified)
amounts = _swapMultiHop(path, amountIn, to);
}
require(amounts[amounts.length - 1] >= amountOutMin, "Slippage exceeded");
// Refund unused ETH
if (msg.value > 0 && ethAmount > 0) {
uint256 refund = msg.value - ethAmount;
if (refund > 0) {
payable(msg.sender).transfer(refund);
}
}
emit Swap(poolId, msg.sender, path[0], amounts[0], path[path.length - 1], amounts[amounts.length - 1]);
}
function _swapSinglePool(
bytes32 poolId,
address tokenIn,
address tokenOut,
uint256 amountIn,
address to
) internal returns (uint256[] memory amounts) {
LiquidityPool storage pool = pools[poolId];
require(
(tokenIn == pool.token0 && tokenOut == pool.token1) ||
(tokenIn == pool.token1 && tokenOut == pool.token0),
"Invalid token pair"
);
uint256 reserve0 = pool.reserve0;
uint256 reserve1 = pool.reserve1;
require(reserve0 > 0 && reserve1 > 0, "Insufficient liquidity");
uint256 amountOut;
bool isToken0In = tokenIn == pool.token0;
if (isToken0In) {
amountOut = amountIn.getAmountOut(reserve0, reserve1);
require(amountOut < reserve1, "Insufficient liquidity");
pool.reserve0 = reserve0.add(amountIn);
pool.reserve1 = reserve1.sub(amountOut);
} else {
amountOut = amountIn.getAmountOut(reserve1, reserve0);
require(amountOut < reserve0, "Insufficient liquidity");
pool.reserve1 = reserve1.add(amountIn);
pool.reserve0 = reserve0.sub(amountOut);
}
// Transfer output token to user
if (tokenOut == WETH) {
IWETH(WETH).withdraw(amountOut);
payable(to).transfer(amountOut);
} else {
IERC20(tokenOut).transfer(to, amountOut);
}
amounts = new uint256[](2);
amounts[0] = amountIn;
amounts[1] = amountOut;
return amounts;
}
function _swapMultiHop(address[] calldata path, uint256 amountIn, address to) internal returns (uint256[] memory amounts) {
// Simplified multi-hop implementation
// In production, you'd implement proper multi-hop routing
amounts = new uint256[](path.length);
amounts[0] = amountIn;
for (uint256 i = 0; i < path.length - 1; i++) {
bytes32 poolId = _getPoolId(path[i], path[i + 1]);
address nextRecipient = (i == path.length - 2) ? to : address(this);
uint256[] memory hopAmounts = _swapSinglePool(poolId, path[i], path[i + 1], amounts[i], nextRecipient);
amounts[i + 1] = hopAmounts[1];
}
return amounts;
}
// Yield farming
function createFarmingPosition(bytes32 poolId, uint256 amount) external validPool(poolId) {
Position storage position = positions[msg.sender][poolId];
FarmingPosition storage farmingPosition = farmingPositions[msg.sender][poolId];
require(position.liquidity >= amount, "Insufficient liquidity");
// Update existing farming position or create new one
if (farmingPosition.amount > 0) {
_harvest(poolId, msg.sender);
}
farmingPosition.amount = farmingPosition.amount.add(amount);
farmingPosition.startTime = block.timestamp;
farmingPosition.rewardDebt = _calculatePendingRewards(poolId, msg.sender);
emit FarmingPositionCreated(poolId, msg.sender, amount);
}
function harvest(bytes32 poolId) external validPool(poolId) {
_harvest(poolId, msg.sender);
}
function _harvest(bytes32 poolId, address user) internal {
FarmingPosition storage farmingPosition = farmingPositions[user][poolId];
if (farmingPosition.amount == 0) return;
uint256 pendingRewards = _calculatePendingRewards(poolId, user);
require(pendingRewards > 0, "No rewards to harvest");
farmingPosition.rewardDebt = farmingPosition.rewardDebt.add(pendingRewards);
// Transfer governance tokens
IERC20(governanceToken).transfer(user, pendingRewards);
emit Harvested(poolId, user, pendingRewards);
}
function _calculatePendingRewards(bytes32 poolId, address user) internal view returns (uint256) {
LiquidityPool storage pool = pools[poolId];
FarmingPosition storage farmingPosition = farmingPositions[user][poolId];
if (farmingPosition.amount == 0) return 0;
uint256 timePassed = block.timestamp - farmingPosition.startTime;
uint256 totalPoolLiquidity = pool.totalLiquidity;
if (totalPoolLiquidity == 0) return 0;
// Calculate rewards based on position size and time
uint256 rewards = (governanceRate * timePassed * farmingPosition.amount) / totalPoolLiquidity;
return rewards - farmingPosition.rewardDebt;
}
// Utility functions
function _getPoolId(address token0, address token1) internal pure returns (bytes32) {
if (token0 > token1) {
(token0, token1) = (token1, token0);
}
return keccak256(abi.encodePacked(token0, token1));
}
function getPoolInfo(bytes32 poolId) external view returns (
address token0,
address token1,
uint256 reserve0,
uint256 reserve1,
uint256 totalLiquidity,
uint256 feeNumerator
) {
LiquidityPool storage pool = pools[poolId];
return (
pool.token0,
pool.token1,
pool.reserve0,
pool.reserve1,
pool.totalLiquidity,
pool.feeNumerator
);
}
function getPosition(bytes32 poolId, address user) external view returns (
uint256 liquidity,
uint256 token0Amount,
uint256 token1Amount,
uint256 unclaimedFees0,
uint256 unclaimedFees1
) {
Position storage position = positions[user][poolId];
return (
position.liquidity,
position.token0Amount,
position.token1Amount,
position.unclaimedFees0,
position.unclaimedFees1
);
}
function getFarmingPosition(bytes32 poolId, address user) external view returns (
uint256 amount,
uint256 startTime,
uint256 pendingRewards
) {
FarmingPosition storage farmingPosition = farmingPositions[user][poolId];
uint256 rewards = _calculatePendingRewards(poolId, user);
return (
farmingPosition.amount,
farmingPosition.startTime,
rewards
);
}
// sqrt function for liquidity calculation
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
// Admin functions
function setGovernanceRate(uint256 newRate) external onlyOwner {
governanceRate = newRate;
}
function setProtocolFee(uint256 newFeeNumerator) external onlyOwner {
require(newFeeNumerator <= 1000, "Fee too high"); // Max 10%
protocolFeeNumerator = newFeeNumerator;
}
function setLiquidityProviderFee(uint256 newFeeNumerator) external onlyOwner {
require(newFeeNumerator <= 1000, "Fee too high"); // Max 10%
liquidityProviderFeeNumerator = newFeeNumerator;
}
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
if (token == address(0)) {
payable(owner).transfer(amount);
} else {
IERC20(token).transfer(owner, amount);
}
}
}
// Math utilities for sqrt calculation
library Math {
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
💻 硬件钱包集成和多签 javascript
🔴 complex
⭐⭐⭐⭐⭐
与Ledger和Trezor硬件钱包的集成、多签钱包和安全密钥管理
⏱️ 45 min
🏷️ hardware-wallet, ledger, trezor, multi-sig, ethereum
Prerequisites:
JavaScript, Ethereum, Web3, Hardware wallet concepts
// Hardware Wallet Integration with Ethereum
// Supports Ledger, Trezor, and other hardware wallets
// Implements multi-signature schemes and secure key management
const { ethers } = require('ethers');
const TransportWebHID = require('@ledgerhq/hw-transport-webhid');
const AppEth = require('@ledgerhq/hw-app-eth');
const TransportWebUSB = require('@ledgerhq/hw-transport-webusb');
// Hardware Wallet Manager Class
class HardwareWalletManager {
constructor() {
this.connectedWallets = new Map();
this.supportedWallets = ['ledger', 'trezor', 'metamask', 'walletconnect'];
this.currentProvider = null;
}
// Initialize connection with hardware wallet
async connectHardwareWallet(walletType) {
try {
let transport;
let wallet;
switch (walletType.toLowerCase()) {
case 'ledger':
transport = await this._connectLedger();
wallet = new AppEth(transport);
break;
case 'trezor':
wallet = await this._connectTrezor();
break;
default:
throw new Error(`Unsupported wallet type: ${walletType}`);
}
const address = await wallet.getAddress(0, false, false);
const walletInfo = {
type: walletType,
address: address.address,
wallet: wallet,
transport: transport,
connectedAt: new Date()
};
this.connectedWallets.set(walletType, walletInfo);
return walletInfo;
} catch (error) {
throw new Error(`Failed to connect ${walletType}: ${error.message}`);
}
}
// Connect to Ledger hardware wallet
async _connectLedger() {
try {
// Try WebHID first (more modern)
let transport = await TransportWebHID.create();
// Fallback to WebUSB if WebHID fails
if (!transport) {
transport = await TransportWebUSB.create();
}
if (!transport) {
throw new Error('Ledger device not found. Please unlock your device and ensure it's connected.');
}
return transport;
} catch (error) {
throw new Error(`Ledger connection failed: ${error.message}`);
}
}
// Connect to Trezor hardware wallet
async _connectTrezor() {
try {
const TrezorConnect = require('@trezor/connect-web');
await TrezorConnect.init({
lazyLoad: true,
manifest: {
email: '[email protected]',
appUrl: 'https://your-app-url.com'
}
});
const result = await TrezorConnect.ethereumGetAddress({
path: "m/44'/60'/0'/0/0",
showOnDevice: true
});
if (!result.success) {
throw new Error(result.payload.error);
}
return {
type: 'trezor',
address: result.payload.address,
getBalance: this._getTrezorBalance.bind(this),
signTransaction: this._signWithTrezor.bind(this),
signMessage: this._signMessageWithTrezor.bind(this)
};
} catch (error) {
throw new Error(`Trezor connection failed: ${error.message}`);
}
}
// Get address from hardware wallet
async getAddress(walletType, derivationPath = "m/44'/60'/0'/0/0") {
const walletInfo = this.connectedWallets.get(walletType);
if (!walletInfo) {
throw new Error(`Wallet ${walletType} not connected`);
}
try {
if (walletType === 'ledger') {
const result = await walletInfo.wallet.getAddress(derivationPath, false, false);
return result.address;
} else if (walletType === 'trezor') {
const TrezorConnect = require('@trezor/connect-web');
const result = await TrezorConnect.ethereumGetAddress({
path: derivationPath,
showOnDevice: true
});
return result.payload.address;
}
} catch (error) {
throw new Error(`Failed to get address from ${walletType}: ${error.message}`);
}
}
// Sign transaction with hardware wallet
async signTransaction(walletType, txData, chainId = 1) {
const walletInfo = this.connectedWallets.get(walletType);
if (!walletInfo) {
throw new Error(`Wallet ${walletType} not connected`);
}
try {
if (walletType === 'ledger') {
return await this._signWithLedger(walletInfo.wallet, txData, chainId);
} else if (walletType === 'trezor') {
return await this._signWithTrezor(txData, chainId);
}
} catch (error) {
throw new Error(`Failed to sign transaction with ${walletType}: ${error.message}`);
}
}
// Sign with Ledger device
async _signWithLedger(wallet, txData, chainId) {
// Parse transaction data
const ethersTx = ethers.utils.parseTransaction(txData);
// Get derivation path
const derivationPath = "m/44'/60'/0'/0/0";
// Prepare signature data for Ledger
const resolution = await wallet.resolveTransaction(
ethersTx.raw,
{
erc20: true,
nft: true
}
);
// Sign transaction
const signature = await wallet.signTransaction(
derivationPath,
ethersTx.raw,
resolution
);
// Format signature for Ethereum
const { v, r, s } = signature;
// Create signed transaction
const signedTx = ethers.utils.serializeTransaction({
...ethersTx,
v: v,
r: r,
s: s
});
return signedTx;
}
// Sign with Trezor device
async _signWithTrezor(txData, chainId) {
const TrezorConnect = require('@trezor/connect-web');
const ethersTx = ethers.utils.parseTransaction(txData);
const result = await TrezorConnect.ethereumSignTransaction({
path: "m/44'/60'/0'/0/0",
transaction: {
to: ethersTx.to,
value: ethersTx.value.toString(),
data: ethersTx.data || '',
chainId: chainId,
nonce: ethersTx.nonce,
gasLimit: ethersTx.gasLimit.toString(),
gasPrice: ethersTx.gasPrice?.toString() || '0',
maxPriorityFeePerGas: ethersTx.maxPriorityFeePerGas?.toString() || null,
maxFeePerGas: ethersTx.maxFeePerGas?.toString() || null
}
});
if (!result.success) {
throw new Error(result.payload.error);
}
// Combine signature components
const signedTx = ethers.utils.serializeTransaction({
...ethersTx,
v: parseInt(result.payload.v, 16),
r: result.payload.r,
s: result.payload.s
});
return signedTx;
}
// Sign message with hardware wallet
async signMessage(walletType, message, derivationPath = "m/44'/60'/0'/0/0") {
const walletInfo = this.connectedWallets.get(walletType);
if (!walletInfo) {
throw new Error(`Wallet ${walletType} not connected`);
}
try {
if (walletType === 'ledger') {
const messageHex = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message));
const signature = await walletInfo.wallet.signPersonalMessage(
derivationPath,
messageHex
);
return {
signature: '0x' + signature.r + signature.s + signature.v.toString(16),
message: message,
address: await this.getAddress(walletType, derivationPath)
};
} else if (walletType === 'trezor') {
const TrezorConnect = require('@trezor/connect-web');
const result = await TrezorConnect.ethereumSignMessage({
path: derivationPath,
message: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message)),
hex: true
});
if (!result.success) {
throw new Error(result.payload.error);
}
return {
signature: result.payload.signature,
message: message,
address: result.payload.address
};
}
} catch (error) {
throw new Error(`Failed to sign message with ${walletType}: ${error.message}`);
}
}
// Disconnect wallet
async disconnectWallet(walletType) {
const walletInfo = this.connectedWallets.get(walletType);
if (walletInfo) {
if (walletInfo.transport) {
await walletInfo.transport.close();
}
this.connectedWallets.delete(walletType);
}
}
// Get wallet info
getWalletInfo(walletType) {
const walletInfo = this.connectedWallets.get(walletType);
if (walletInfo) {
return {
type: walletInfo.type,
address: walletInfo.address,
connectedAt: walletInfo.connectedAt
};
}
return null;
}
// List all connected wallets
listConnectedWallets() {
const wallets = {};
for (const [type, info] of this.connectedWallets) {
wallets[type] = {
type: info.type,
address: info.address,
connectedAt: info.connectedAt
};
}
return wallets;
}
}
// Multi-Signature Wallet Implementation
class MultiSigWallet {
constructor(provider, contractAddress, owners, requiredSignatures = 2) {
this.provider = provider;
this.contractAddress = contractAddress;
this.owners = owners;
this.requiredSignatures = requiredSignatures;
this.walletManager = new HardwareWalletManager();
// Initialize contract interface
this.contract = new ethers.Contract(
contractAddress,
MULTI_SIG_ABI,
provider
);
}
// Create transaction proposal
async proposeTransaction(to, value, data, walletType) {
try {
// Get nonce for this wallet
const nonce = await this.contract.nonce();
// Create transaction data
const txData = {
to: to,
value: value || '0',
data: data || '0x',
nonce: nonce
};
// Sign with hardware wallet
const signature = await this.walletManager.signTransaction(walletType, txData);
// Create proposal
const proposal = {
id: ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(
['address', 'uint256', 'bytes', 'uint256'],
[to, value, data, nonce]
)),
to: to,
value: value,
data: data,
nonce: nonce,
signatures: [signature],
proposers: [await this.walletManager.getAddress(walletType)]
};
return proposal;
} catch (error) {
throw new Error(`Failed to propose transaction: ${error.message}`);
}
}
// Sign existing proposal
async signProposal(proposal, walletType) {
try {
// Check if already signed
const signerAddress = await this.walletManager.getAddress(walletType);
if (proposal.signers && proposal.signers.includes(signerAddress)) {
throw new Error('Already signed this proposal');
}
// Create signature
const signature = await this.walletManager.signTransaction(walletType, {
to: proposal.to,
value: proposal.value,
data: proposal.data,
nonce: proposal.nonce
});
// Add signature to proposal
proposal.signatures.push(signature);
if (!proposal.signers) {
proposal.signers = [];
}
proposal.signers.push(signerAddress);
if (!proposal.proposers) {
proposal.proposers = [];
}
proposal.proposers.push(signerAddress);
return proposal;
} catch (error) {
throw new Error(`Failed to sign proposal: ${error.message}`);
}
}
// Execute transaction if enough signatures collected
async executeTransaction(proposal) {
try {
if (proposal.signatures.length < this.requiredSignatures) {
throw new Error(`Need ${this.requiredSignatures} signatures, got ${proposal.signatures.length}`);
}
// Submit to multi-sig contract
const tx = await this.contract.executeTransaction(
proposal.to,
proposal.value,
proposal.data,
proposal.signatures
);
return await tx.wait();
} catch (error) {
throw new Error(`Failed to execute transaction: ${error.message}`);
}
}
}
// Multi-Sig Contract ABI (simplified)
const MULTI_SIG_ABI = [
"function executeTransaction(address to, uint256 value, bytes data, bytes[] signatures) external",
"function nonce() view returns (uint256)",
"function getOwners() view returns (address[])",
"function getRequiredSignatures() view returns (uint256)"
];
// Example usage and testing functions
async function demonstrateHardwareWalletIntegration() {
console.log('=== Hardware Wallet Integration Demo ===');
const walletManager = new HardwareWalletManager();
try {
// Connect to Ledger
console.log('\n1. Connecting to Ledger...');
const ledgerInfo = await walletManager.connectHardwareWallet('ledger');
console.log(`Connected to Ledger: ${ledgerInfo.address}`);
// Connect to Trezor
console.log('\n2. Connecting to Trezor...');
const trezorInfo = await walletManager.connectHardwareWallet('trezor');
console.log(`Connected to Trezor: ${trezorInfo.address}`);
// Get balances
console.log('\n3. Checking balances...');
const provider = new ethers.providers.InfuraProvider('mainnet', process.env.INFURA_PROJECT_ID);
const ledgerBalance = await provider.getBalance(ledgerInfo.address);
const trezorBalance = await provider.getBalance(trezorInfo.address);
console.log(`Ledger balance: ${ethers.utils.formatEther(ledgerBalance)} ETH`);
console.log(`Trezor balance: ${ethers.utils.formatEther(trezorBalance)} ETH`);
// Sign a message
console.log('\n4. Signing messages...');
const message = "Hello, Hardware Wallet!";
const ledgerSignature = await walletManager.signMessage('ledger', message);
console.log(`Ledger signature: ${ledgerSignature.signature}`);
const trezorSignature = await walletManager.signMessage('trezor', message);
console.log(`Trezor signature: ${trezorSignature.signature}`);
// Create and sign a transaction (example)
console.log('\n5. Creating and signing transactions...');
const txData = {
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: ethers.utils.parseEther('0.001'),
gasLimit: 21000,
gasPrice: ethers.utils.parseUnits('20', 'gwei')
};
const ledgerTx = await walletManager.signTransaction('ledger', txData);
console.log(`Ledger transaction signed: ${ledgerTx.substring(0, 50)}...`);
// Multi-sig example
console.log('\n6. Multi-signature wallet example...');
const owners = [
ledgerInfo.address,
trezorInfo.address,
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6' // Third owner
];
const multiSigContractAddress = '0x1234567890123456789012345678901234567890'; // Example address
const multiSig = new MultiSigWallet(provider, multiSigContractAddress, owners, 2);
// Create proposal
const proposal = await multiSig.proposeTransaction(
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
ethers.utils.parseEther('0.1'),
'0x',
'ledger'
);
console.log(`Multi-sig proposal created: ${proposal.id}`);
console.log(`Signatures: ${proposal.signatures.length}/${multiSig.requiredSignatures}`);
// Add second signature
const signedProposal = await multiSig.signProposal(proposal, 'trezor');
console.log(`Second signature added. Total: ${signedProposal.signatures.length}`);
if (signedProposal.signatures.length >= multiSig.requiredSignatures) {
console.log('Enough signatures collected - ready to execute');
// const result = await multiSig.executeTransaction(signedProposal);
// console.log(`Transaction executed: ${result.transactionHash}`);
}
// Disconnect wallets
console.log('\n7. Disconnecting wallets...');
await walletManager.disconnectWallet('ledger');
await walletManager.disconnectWallet('trezor');
} catch (error) {
console.error('Error in demonstration:', error.message);
}
}
// Key Management Utilities
class KeyManager {
constructor() {
this.encryptionKey = null;
this.encryptedKeys = new Map();
}
// Generate secure random key
generateSecureKey() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
// Encrypt private key
async encryptPrivateKey(privateKey, password) {
const encoder = new TextEncoder();
const data = encoder.encode(privateKey);
// Derive key from password
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode('hardware-wallet-salt'),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
// Encrypt
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
data
);
return {
encrypted: Array.from(new Uint8Array(encrypted)),
iv: Array.from(iv)
};
}
// Decrypt private key
async decryptPrivateKey(encryptedData, password) {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// Derive key from password
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode('hardware-wallet-salt'),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
// Decrypt
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(encryptedData.iv)
},
key,
new Uint8Array(encryptedData.encrypted)
);
return decoder.decode(decrypted);
}
}
// Export classes for use in other modules
module.exports = {
HardwareWalletManager,
MultiSigWallet,
KeyManager,
demonstrateHardwareWalletIntegration
};
// Run demonstration if called directly
if (require.main === module) {
demonstrateHardwareWalletIntegration();
}