Elysia Tools
Navigation mobile
Blockchain
Contrats Intelligents Ethereum avec Solidity
Exemples complets de développement de contrats intelligents Ethereum incluant ERC-20, ERC-721, protocoles DeFi et intégration de portefeuilles matériels
Exemples
Entrées de cette collection
Fondamentaux Solidity et Contrats Intelligents
Concepts de base Solidity, types de données, fonctions, modificateurs, événements et exemple complet de contrat simple
Difficulté
4/10
Temps estimé
30 min
Étiquettes
solidity, basics, smart-contracts, ethereum
Prérequis
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];
}
}Implémentation Standard ERC-20
Contrat ERC-20 complet avec minting, burning, pausing et fonctionnalités avancées comme droits de vote et transferts temporairement bloqués
Difficulté
7/10
Temps estimé
45 min
Étiquettes
erc20, token, solidity, ethereum, smart-contracts
Prérequis
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;
}
}Contrat NFT ERC-721 avec Métadonnées
Implémentation ERC-721 complète avec métadonnées IPFS, système de redevances et intégration marketplace
Difficulté
8/10
Temps estimé
50 min
Étiquettes
erc721, nft, solidity, ethereum, smart-contracts
Prérequis
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);
}Protocole AMM DeFi (Market Maker Automatisé)
Implémentation AMM complète avec pools de liquidité, fonctionnalité swap, yield farming et tokens de gouvernance
Difficulté
9/10
Temps estimé
60 min
Étiquettes
defi, amm, liquidity, yield-farming, solidity
Prérequis
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;
}
}
}Intégration Portefeuilles Matériels et Multi-Sig
Intégration avec portefeuilles matériels Ledger et Trezor, portefeuilles multi-signature et gestion sécurisée des clés
Difficulté
9/10
Temps estimé
45 min
Étiquettes
hardware-wallet, ledger, trezor, multi-sig, ethereum
Prérequis
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();
}Outils
Outils souvent utilisés avec cet exemple
Associé