Fundamentos de Desarrollo Web3 Blockchain: Smart Contracts, DApps y DeFi
Este artículo es una introducción exhaustiva a los fundamentos del desarrollo Web3 Blockchain, incluyendo Smart Contracts, DApps y DeFi con ejemplos prácticos.
En Resumen
Web3 es la próxima generación de Internet, construida sobre tecnología Blockchain, que habilita aplicaciones descentralizadas (DApps) que funcionan sin instancias centrales.
Descripción Técnica Compacta
Web3 es un protocolo de Internet descentralizado que se basa en tecnología Blockchain y otorga a los usuarios control sobre sus datos e identidades digitales.
Componentes principales:
Fundamentos de Blockchain
- Distributed Ledger: Almacenamiento de datos descentralizado
- Consensus Mechanisms: Proof of Work, Proof of Stake
- Cryptography: Funciones hash, Firmas digitales
- Smart Contracts: Contratos autoejecutable
- Gas Fees: Costos de transacción
Ecosistema Ethereum
- EVM: Ethereum Virtual Machine
- Solidity: Lenguaje de programación para Smart Contracts
- Web3.js: Biblioteca JavaScript para Web3
- Hardhat: Entorno de desarrollo
- OpenZeppelin: Bibliotecas de Smart Contracts seguras
Finanzas Descentralizadas (DeFi)
- DEX: Intercambios descentralizados
- Lending: Plataformas de crédito descentralizadas
- Yield Farming: Provisión de liquidez
- Staking: Validación de tokens
- Governance: Toma de decisiones descentralizada
Puntos Clave para Evaluación
- Web3: Internet descentralizado basado en Blockchain
- Blockchain: Base de datos descentralizada con seguridad criptográfica
- Smart Contracts: Contratos autoejecutable en la Blockchain
- DApps: Aplicaciones descentralizadas sin instancia central
- DeFi: Servicios financieros descentralizados
- Ethereum: Plataforma líder de Smart Contracts
- Solidity: Lenguaje de programación para Smart Contracts
- Gas: Costos de transacción en la Blockchain
- Relevante para IHK: Desarrollo moderno de Blockchain y arquitectura
Componentes Clave
- Blockchain Technology: Distributed Ledger, mecanismos de consenso
- Smart Contracts: Contratos autoejecutable con lógica de negocio
- DApps: Aplicaciones frontend descentralizadas
- DeFi Protocols: Servicios financieros en la Blockchain
- Wallet Integration: MetaMask, WalletConnect
- Web3 Libraries: Web3.js, Ethers.js
- Development Tools: Hardhat, Truffle, Remix
- Security: Auditorías, mejores prácticas, escaneo de vulnerabilidades
Ejemplos Prácticos
1. Smart Contract con Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title DeFiToken
* @dev Token ERC20 con funcionalidades extendidas para aplicaciones DeFi
* @author Web3 Developer
*/
contract DeFiToken is ERC20, ERC20Burnable, ReentrancyGuard, Ownable {
using SafeMath for uint256;
// Events
event TokensMinted(address indexed to, uint256 amount);
event TokensBurned(address indexed from, uint256 amount);
event LiquidityAdded(address indexed provider, uint256 tokenAmount, uint256 ethAmount);
event LiquidityRemoved(address indexed provider, uint256 tokenAmount, uint256 ethAmount);
event RewardClaimed(address indexed claimant, uint256 rewardAmount);
// State variables
uint256 public constant MAX_SUPPLY = 1000000000 * 10**18; // 1 Billion tokens
uint256 public constant INITIAL_SUPPLY = 100000000 * 10**18; // 100 Million tokens
uint256 public constant REWARD_RATE = 1 * 10**16; // 0.01 tokens per block
uint256 public totalLiquidity;
uint256 public totalRewardsDistributed;
uint256 public lastRewardBlock;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public liquidityProvided;
mapping(address => uint256) public depositTimestamp;
// Arrays para Staking Pools
struct Pool {
uint256 totalStaked;
uint256 rewardRate;
uint256 lockPeriod;
uint256 apr;
bool active;
}
mapping(uint256 => Pool) public pools;
mapping(address => mapping(uint256 => uint256)) public userStakes;
mapping(address => mapping(uint256 => uint256)) public userStakeTimestamp;
uint256 public poolCount;
// Governance
struct Proposal {
uint256 id;
address proposer;
string description;
uint256 voteCount;
uint256 quorum;
uint256 deadline;
bool executed;
mapping(address => bool) hasVoted;
}
mapping(uint256 => Proposal) public proposals;
uint256 public proposalCount;
mapping(address => uint256) public votingPower;
// Modifiers
modifier validAddress(address _addr) {
require(_addr != address(0), "Invalid address");
_;
}
modifier sufficientBalance(address _account, uint256 _amount) {
require(balanceOf(_account) >= _amount, "Insufficient balance");
_;
}
modifier sufficientLiquidity(uint256 _amount) {
require(address(this).balance >= _amount, "Insufficient ETH liquidity");
_;
}
modifier onlyWhenPaused() {
require(!paused(), "Contract is paused");
_;
}
// Constructor
constructor(
string memory _name,
string memory _symbol,
uint256 _initialMint
) ERC20(_name, _symbol) {
require(_initialMint <= INITIAL_SUPPLY, "Initial mint exceeds limit");
_mint(msg.sender, _initialMint);
lastRewardBlock = block.number;
// Crear pool de staking inicial
_createPool(100, 30 days, 10); // 100% APR, 30 días de bloqueo
}
/**
* @dev Acuña nuevos tokens (solo propietario)
*/
function mint(address _to, uint256 _amount)
external
onlyOwner
validAddress(_to)
returns (bool)
{
require(totalSupply() + _amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(_to, _amount);
emit TokensMinted(_to, _amount);
return true;
}
/**
* @dev Añade liquidez al pool
*/
function addLiquidity(uint256 _tokenAmount)
external
payable
nonReentrant
sufficientBalance(msg.sender, _tokenAmount)
returns (uint256 liquidityTokens)
{
require(_tokenAmount > 0 && msg.value > 0, "Invalid amounts");
// Calcula tokens de liquidez
uint256 ethReserve = address(this).balance - msg.value;
uint256 tokenReserve = totalSupply() - _tokenAmount;
if (ethReserve == 0 || tokenReserve == 0) {
liquidityTokens = _tokenAmount;
} else {
liquidityTokens = min(
(msg.value * tokenReserve) / ethReserve,
_tokenAmount
);
}
require(liquidityTokens > 0, "Insufficient liquidity minted");
// Transfiere tokens y ETH
_transfer(msg.sender, address(this), _tokenAmount);
// Actualiza liquidez del usuario
liquidityProvided[msg.sender] += liquidityTokens;
depositTimestamp[msg.sender] = block.timestamp;
// Actualiza liquidez total
totalLiquidity += liquidityTokens;
// Actualiza recompensas
_updateReward(msg.sender);
emit LiquidityAdded(msg.sender, _tokenAmount, msg.value);
return liquidityTokens;
}
/**
* @dev Retira liquidez del pool
*/
function removeLiquidity(uint256 _liquidityAmount)
external
nonReentrant
returns (uint256 ethAmount, uint256 tokenAmount)
{
require(_liquidityAmount > 0, "Invalid amount");
require(liquidityProvided[msg.sender] >= _liquidityAmount, "Insufficient liquidity");
// Calcula proporciones
uint256 totalLiquidityTokens = totalLiquidity;
uint256 ethReserve = address(this).balance;
uint256 tokenReserve = balanceOf(address(this));
ethAmount = (ethReserve * _liquidityAmount) / totalLiquidityTokens;
tokenAmount = (tokenReserve * _liquidityAmount) / totalLiquidityTokens;
require(ethAmount > 0 && tokenAmount > 0, "Insufficient withdraw amount");
// Actualiza liquidez del usuario
liquidityProvided[msg.sender] -= _liquidityAmount;
totalLiquidity -= _liquidityAmount;
// Actualiza recompensas
_updateReward(msg.sender);
_claimRewards(msg.sender);
// Transfiere ETH y tokens
payable(msg.sender).transfer(ethAmount);
_transfer(address(this), msg.sender, tokenAmount);
emit LiquidityRemoved(msg.sender, tokenAmount, ethAmount);
return (ethAmount, tokenAmount);
}
/**
* @dev Crea nuevo pool de staking
*/
function createPool(uint256 _apr, uint256 _lockPeriod) external onlyOwner {
_createPool(_apr, _lockPeriod, 0);
}
function _createPool(uint256 _apr, uint256 _lockPeriod, uint256 _rewardRate) internal {
pools[poolCount] = Pool({
totalStaked: 0,
rewardRate: _rewardRate > 0 ? _rewardRate : REWARD_RATE,
lockPeriod: _lockPeriod,
apr: _apr,
active: true
});
poolCount++;
}
/**
* @dev Hace stake de tokens en el pool
*/
function stake(uint256 _poolId, uint256 _amount)
external
nonReentrant
sufficientBalance(msg.sender, _amount)
returns (bool)
{
require(_poolId < poolCount, "Invalid pool");
require(pools[_poolId].active, "Pool not active");
require(_amount > 0, "Invalid amount");
// Actualiza recompensas antes del stake
_updatePoolRewards(_poolId);
_updateUserPoolRewards(_poolId, msg.sender);
// Transfiere tokens al contrato
_transfer(msg.sender, address(this), _amount);
// Actualiza staking
userStakes[msg.sender][_poolId] += _amount;
userStakeTimestamp[msg.sender][_poolId] = block.timestamp;
pools[_poolId].totalStaked += _amount;
// Actualiza poder de voto
votingPower[msg.sender] += _amount;
return true;
}
/**
* @dev Retira stake de tokens del pool
*/
function unstake(uint256 _poolId, uint256 _amount)
external
nonReentrant
returns (bool)
{
require(_poolId < poolCount, "Invalid pool");
require(userStakes[msg.sender][_poolId] >= _amount, "Insufficient stake");
Pool storage pool = pools[_poolId];
// Verifica período de bloqueo
uint256 stakeTime = userStakeTimestamp[msg.sender][_poolId];
require(
block.timestamp >= stakeTime + pool.lockPeriod,
"Tokens still locked"
);
// Actualiza recompensas antes del unstake
_updatePoolRewards(_poolId);
_updateUserPoolRewards(_poolId, msg.sender);
// Actualiza staking
userStakes[msg.sender][_poolId] -= _amount;
pool.totalStaked -= _amount;
// Actualiza poder de voto
votingPower[msg.sender] -= _amount;
// Transfiere tokens de vuelta
_transfer(address(this), msg.sender, _amount);
return true;
}
/**
* @dev Reclama recompensas del pool
*/
function claimRewards(uint256 _poolId) external nonReentrant returns (uint256) {
require(_poolId < poolCount, "Invalid pool");
require(userStakes[msg.sender][_poolId] > 0, "No stake in pool");
// Actualiza recompensas
_updatePoolRewards(_poolId);
_updateUserPoolRewards(_poolId, msg.sender);
uint256 reward = rewards[msg.sender];
require(reward > 0, "No rewards to claim");
// Resetea recompensas
rewards[msg.sender] = 0;
// Acuña nuevos tokens como recompensa
_mint(msg.sender, reward);
emit RewardClaimed(msg.sender, reward);
return reward;
}
/**
* @dev Crea propuesta de governance
*/
function createProposal(string memory _description, uint256 _quorum)
external
returns (uint256)
{
require(votingPower[msg.sender] >= 1000 * 10**18, "Insufficient voting power");
require(bytes(_description).length > 0, "Empty description");
Proposal storage proposal = proposals[proposalCount];
proposal.id = proposalCount;
proposal.proposer = msg.sender;
proposal.description = _description;
proposal.voteCount = 0;
proposal.quorum = _quorum;
proposal.deadline = block.timestamp + 7 days;
proposal.executed = false;
proposalCount++;
return proposalCount - 1;
}
/**
* @dev Vota por una propuesta
*/
function vote(uint256 _proposalId) external {
require(_proposalId < proposalCount, "Invalid proposal");
require(block.timestamp <= proposals[_proposalId].deadline, "Voting ended");
require(!proposals[_proposalId].hasVoted[msg.sender], "Already voted");
require(votingPower[msg.sender] > 0, "No voting power");
proposals[_proposalId].hasVoted[msg.sender] = true;
proposals[_proposalId].voteCount += votingPower[msg.sender];
}
/**
* @dev Ejecuta propuesta
*/
function executeProposal(uint256 _proposalId) external onlyOwner {
require(_proposalId < proposalCount, "Invalid proposal");
require(block.timestamp > proposals[_proposalId].deadline, "Voting not ended");
require(!proposals[_proposalId].executed, "Already executed");
require(
proposals[_proposalId].voteCount >= proposals[_proposalId].quorum,
"Quorum not reached"
);
proposals[_proposalId].executed = true;
}
/**
* @dev Funciones de actualización de recompensas
*/
function _updateReward(address _account) internal {
if (_account == address(0)) return;
rewardPerTokenStored = rewardPerToken();
lastRewardBlock = block.number;
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
function _updatePoolRewards(uint256 _poolId) internal {
// Implementa lógica de recompensas específica del pool
// Esta es una versión simplificada
}
function _updateUserPoolRewards(uint256 _poolId, address _account) internal {
// Implementa recompensas de pool específicas del usuario
}
/**
* @dev Funciones auxiliares
*/
function rewardPerToken() public view returns (uint256) {
if (totalLiquidity == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored.add(
((block.number.sub(lastRewardBlock)).mul(REWARD_RATE)).mul(1e18).div(totalLiquidity)
);
}
function earned(address _account) public view returns (uint256) {
return liquidityProvided[_account]
.mul(rewardPerToken().sub(userRewardPerTokenPaid[_account]))
.div(1e18)
.add(rewards[_account]);
}
function getPoolInfo(uint256 _poolId)
external
view
returns (
uint256 totalStaked,
uint256 rewardRate,
uint256 lockPeriod,
uint256 apr,
bool active
)
{
require(_poolId < poolCount, "Invalid pool");
Pool storage pool = pools[_poolId];
return (pool.totalStaked, pool.rewardRate, pool.lockPeriod, pool.apr, pool.active);
}
function getUserStake(uint256 _poolId, address _user)
external
view
returns (uint256 amount, uint256 timestamp)
{
require(_poolId < poolCount, "Invalid pool");
return (userStakes[_user][_poolId], userStakeTimestamp[_user][_poolId]);
}
function getProposalInfo(uint256 _proposalId)
external
view
returns (
address proposer,
string memory description,
uint256 voteCount,
uint256 quorum,
uint256 deadline,
bool executed,
bool hasVoted
)
{
require(_proposalId < proposalCount, "Invalid proposal");
Proposal storage proposal = proposals[_proposalId];
return (
proposal.proposer,
proposal.description,
proposal.voteCount,
proposal.quorum,
proposal.deadline,
proposal.executed,
proposal.hasVoted[msg.sender]
);
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Funciones de emergencia
*/
function emergencyPause() external onlyOwner {
_pause();
}
function emergencyUnpause() external onlyOwner {
_unpause();
}
function emergencyWithdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
/**
* @dev Fallback para transferencias de ETH
*/
receive() external payable {
// Acepta ETH para liquidez
}
fallback() external payable {
// Acepta ETH para liquidez
}
}
2. Frontend de DApp con React y Web3.js
// src/components/Web3Provider.jsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import Web3 from 'web3';
import { Contract } from 'web3-eth-contract';
import DeFiTokenABI from '../contracts/DeFiToken.json';
const Web3Context = createContext();
export const useWeb3 = () => {
const context = useContext(Web3Context);
if (!context) {
throw new Error('useWeb3 must be used within a Web3Provider');
}
return context;
};
const Web3Provider = ({ children }) => {
const [web3, setWeb3] = useState(null);
const [account, setAccount] = useState(null);
const [contract, setContract] = useState(null);
const [networkId, setNetworkId] = useState(null);
const [balance, setBalance] = useState('0');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const CONTRACT_ADDRESS = process.env.REACT_APP_CONTRACT_ADDRESS;
const SUPPORTED_NETWORKS = {
1: 'Ethereum Mainnet',
3: 'Ropsten Testnet',
4: 'Rinkeby Testnet',
5: 'Goerli Testnet',
42: 'Kovan Testnet',
137: 'Polygon Mainnet',
80001: 'Polygon Mumbai'
};
// Initialize Web3
useEffect(() => {
const initWeb3 = async () => {
try {
// Check if MetaMask is installed
if (typeof window.ethereum !== 'undefined') {
const web3Instance = new Web3(window.ethereum);
setWeb3(web3Instance);
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
setAccount(accounts[0]);
await setupContract(web3Instance);
await getNetworkInfo(web3Instance);
await updateBalance(web3Instance, accounts[0]);
}
// Setup event listeners
setupEventListeners(web3Instance);
} else {
setError('MetaMask is not installed. Please install MetaMask to use this DApp.');
}
} catch (error) {
console.error('Web3 initialization error:', error);
setError('Failed to initialize Web3. Please check your wallet connection.');
} finally {
setLoading(false);
}
};
initWeb3();
}, []);
const setupEventListeners = (web3Instance) => {
// Account change
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
setAccount(null);
setBalance('0');
} else {
setAccount(accounts[0]);
updateBalance(web3Instance, accounts[0]);
}
});
// Network change
window.ethereum.on('chainChanged', () => {
window.location.reload();
});
// Connect/Disconnect
window.ethereum.on('connect', (connectInfo) => {
console.log('Connected to blockchain:', connectInfo);
});
window.ethereum.on('disconnect', (error) => {
console.error('Disconnected from blockchain:', error);
setAccount(null);
setBalance('0');
});
};
const setupContract = async (web3Instance) => {
try {
const contractInstance = new web3Instance.eth.Contract(
DeFiTokenABI.abi,
CONTRACT_ADDRESS
);
setContract(contractInstance);
} catch (error) {
console.error('Contract setup error:', error);
setError('Failed to setup smart contract.');
}
};
const getNetworkInfo = async (web3Instance) => {
try {
const networkId = await web3Instance.eth.net.getId();
setNetworkId(networkId);
if (!SUPPORTED_NETWORKS[networkId]) {
setError(`Unsupported network. Please switch to a supported network.`);
}
} catch (error) {
console.error('Network info error:', error);
}
};
const updateBalance = async (web3Instance, accountAddress) => {
try {
const balanceWei = await web3Instance.eth.getBalance(accountAddress);
const balanceEth = web3Instance.utils.fromWei(balanceWei, 'ether');
setBalance(balanceEth);
} catch (error) {
console.error('Balance update error:', error);
}
};
const connectWallet = async () => {
try {
if (typeof window.ethereum !== 'undefined') {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
setAccount(accounts[0]);
await updateBalance(web3, accounts[0]);
}
}
} catch (error) {
console.error('Wallet connection error:', error);
setError('Failed to connect wallet.');
}
};
const disconnectWallet = () => {
setAccount(null);
setBalance('0');
};
const switchNetwork = async (networkId) => {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${networkId.toString(16)}` }]
});
} catch (error) {
console.error('Network switch error:', error);
setError('Failed to switch network.');
}
};
const value = {
web3,
account,
contract,
networkId,
balance,
loading,
error,
supportedNetworks: SUPPORTED_NETWORKS,
connectWallet,
disconnectWallet,
switchNetwork,
refreshBalance: () => updateBalance(web3, account)
};
return (
<Web3Context.Provider value={value}>
{children}
</Web3Context.Provider>
);
};
export default Web3Provider;
// src/components/TokenDashboard.jsx
import React, { useState, useEffect } from 'react';
import { useWeb3 } from './Web3Provider';
import { Card, CardHeader, CardContent, CardActions } from './UI/Card';
import Button from './UI/Button';
import Input from './UI/Input';
import Alert from './UI/Alert';
import LoadingSpinner from './UI/LoadingSpinner';
const TokenDashboard = () => {
const { web3, account, contract, balance, refreshBalance } = useWeb3();
const [tokenBalance, setTokenBalance] = useState('0');
const [totalSupply, setTotalSupply] = useState('0');
const [stakingData, setStakingData] = useState([]);
const [liquidityData, setLiquidityData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(null);
// Form states
const [stakeAmount, setStakeAmount] = useState('');
const [liquidityTokenAmount, setLiquidityTokenAmount] = useState('');
const [liquidityEthAmount, setLiquidityEthAmount] = useState('');
// Load token data
useEffect(() => {
if (contract && account) {
loadTokenData();
}
}, [contract, account]);
const loadTokenData = async () => {
try {
setLoading(true);
setError(null);
const [
tokenBalanceWei,
totalSupplyWei,
poolCount,
userLiquidity,
userRewards
] = await Promise.all([
contract.methods.balanceOf(account).call(),
contract.methods.totalSupply().call(),
contract.methods.poolCount().call(),
contract.methods.liquidityProvided(account).call(),
contract.methods.earned(account).call()
]);
setTokenBalance(web3.utils.fromWei(tokenBalanceWei, 'ether'));
setTotalSupply(web3.utils.fromWei(totalSupplyWei, 'ether'));
setLiquidityData({
provided: web3.utils.fromWei(userLiquidity, 'ether'),
rewards: web3.utils.fromWei(userRewards, 'ether')
});
// Load staking data
const pools = [];
for (let i = 0; i < poolCount; i++) {
const [poolInfo, userStake] = await Promise.all([
contract.methods.getPoolInfo(i).call(),
contract.methods.getUserStake(i, account).call()
]);
pools.push({
id: i,
...poolInfo,
userStake: {
amount: web3.utils.fromWei(userStake.amount, 'ether'),
timestamp: userStake.timestamp
}
});
}
setStakingData(pools);
} catch (error) {
console.error('Token data load error:', error);
setError('Failed to load token data.');
} finally {
setLoading(false);
}
};
const handleStake = async (poolId, amount) => {
try {
setLoading(true);
setError(null);
const amountWei = web3.utils.toWei(amount, 'ether');
const tx = await contract.methods.stake(poolId, amountWei).send({
from: account,
gas: '300000'
});
setSuccess(`Successfully staked ${amount} tokens! Transaction: ${tx.transactionHash}`);
setStakeAmount('');
await loadTokenData();
await refreshBalance();
} catch (error) {
console.error('Stake error:', error);
setError('Failed to stake tokens.');
} finally {
setLoading(false);
}
};
const handleUnstake = async (poolId, amount) => {
try {
setLoading(true);
setError(null);
const amountWei = web3.utils.toWei(amount, 'ether');
const tx = await contract.methods.unstake(poolId, amountWei).send({
from: account,
gas: '300000'
});
setSuccess(`Successfully unstaked ${amount} tokens! Transaction: ${tx.transactionHash}`);
await loadTokenData();
await refreshBalance();
} catch (error) {
console.error('Unstake error:', error);
setError('Failed to unstake tokens.');
} finally {
setLoading(false);
}
};
const handleClaimRewards = async (poolId) => {
try {
setLoading(true);
setError(null);
const tx = await contract.methods.claimRewards(poolId).send({
from: account,
gas: '200000'
});
const rewards = await contract.methods.claimRewards(poolId).call({ from: account });
setSuccess(`Successfully claimed ${web3.utils.fromWei(rewards, 'ether')} tokens!`);
await loadTokenData();
} catch (error) {
console.error('Claim rewards error:', error);
setError('Failed to claim rewards.');
} finally {
setLoading(false);
}
};
const handleAddLiquidity = async () => {
try {
setLoading(true);
setError(null);
const tokenAmountWei = web3.utils.toWei(liquidityTokenAmount, 'ether');
const ethAmountWei = web3.utils.toWei(liquidityEthAmount, 'ether');
// Approve token transfer
await contract.methods.approve(CONTRACT_ADDRESS, tokenAmountWei).send({
from: account,
gas: '100000'
});
// Add liquidity
const tx = await contract.methods.addLiquidity(tokenAmountWei).send({
from: account,
value: ethAmountWei,
gas: '300000'
});
setSuccess(`Successfully added liquidity! Transaction: ${tx.transactionHash}`);
setLiquidityTokenAmount('');
setLiquidityEthAmount('');
await loadTokenData();
await refreshBalance();
} catch (error) {
console.error('Add liquidity error:', error);
setError('Failed to add liquidity.');
} finally {
setLoading(false);
}
};
const handleRemoveLiquidity = async (amount) => {
try {
setLoading(true);
setError(null);
const amountWei = web3.utils.toWei(amount, 'ether');
const tx = await contract.methods.removeLiquidity(amountWei).send({
from: account,
gas: '300000'
});
setSuccess(`Successfully removed liquidity! Transaction: ${tx.transactionHash}`);
await loadTokenData();
await refreshBalance();
} catch (error) {
console.error('Remove liquidity error:', error);
setError('Failed to remove liquidity.');
} finally {
setLoading(false);
}
};
if (loading && !tokenBalance) {
return <LoadingSpinner />;
}
return (
<div className="token-dashboard">
<div className="dashboard-header">
<h1>DeFi Token Dashboard</h1>
<div className="account-info">
<p><strong>Account:</strong> {account}</p>
<p><strong>ETH Balance:</strong> {balance} ETH</p>
<p><strong>Token Balance:</strong> {tokenBalance} DEFI</p>
</div>
</div>
{error && <Alert type="error" message={error} />}
{success && <Alert type="success" message={success} />}
{/* Token Info */}
<Card className="token-info-card">
<CardHeader>
<h2>Token Information</h2>
</CardHeader>
<CardContent>
<div className="info-grid">
<div className="info-item">
<label>Total Supply</label>
<span>{totalSupply} DEFI</span>
</div>
<div className="info-item">
<label>Your Balance</label>
<span>{tokenBalance} DEFI</span>
</div>
<div className="info-item">
<label>Liquidity Provided</label>
<span>{liquidityData?.provided || '0'} DEFI</span>
</div>
<div className="info-item">
<label>Pending Rewards</label>
<span>{liquidityData?.rewards || '0'} DEFI</span>
</div>
</div>
</CardContent>
</Card>
{/* Staking Pools */}
<Card className="staking-card">
<CardHeader>
<h2>Staking Pools</h2>
</CardHeader>
<CardContent>
{stakingData.map((pool) => (
<div key={pool.id} className="staking-pool">
<div className="pool-header">
<h3>Pool #{pool.id + 1}</h3>
<span className={`pool-status ${pool.active ? 'active' : 'inactive'}`}>
{pool.active ? 'Active' : 'Inactive'}
</span>
</div>
<div className="pool-info">
<div className="pool-stat">
<label>APR</label>
<span>{pool.apr}%</span>
</div>
<div className="pool-stat">
<label>Lock Period</label>
<span>{pool.lockPeriod / 86400} days</span>
</div>
<div className="pool-stat">
<label>Total Staked</label>
<span>{web3.utils.fromWei(pool.totalStaked, 'ether')} DEFI</span>
</div>
<div className="pool-stat">
<label>Your Stake</label>
<span>{pool.userStake.amount} DEFI</span>
</div>
</div>
<div className="pool-actions">
{pool.active && (
<>
<div className="stake-form">
<Input
type="number"
placeholder="Amount to stake"
value={stakeAmount}
onChange={(e) => setStakeAmount(e.target.value)}
min="0"
step="0.01"
/>
<Button
onClick={() => handleStake(pool.id, stakeAmount)}
disabled={!stakeAmount || parseFloat(stakeAmount) <= 0}
>
Stake
</Button>
</div>
{pool.userStake.amount > '0' && (
<>
<Button
variant="secondary"
onClick={() => handleUnstake(pool.id, pool.userStake.amount)}
>
Unstake All
</Button>
<Button
variant="outline"
onClick={() => handleClaimRewards(pool.id)}
>
Claim Rewards
</Button>
</>
)}
</>
)}
</div>
</div>
))}
</CardContent>
</Card>
{/* Liquidity Management */}
<Card className="liquidity-card">
<CardHeader>
<h2>Liquidity Management</h2>
</CardHeader>
<CardContent>
<div className="liquidity-form">
<h3>Add Liquidity</h3>
<div className="form-row">
<Input
type="number"
placeholder="Token amount"
value={liquidityTokenAmount}
onChange={(e) => setLiquidityTokenAmount(e.target.value)}
min="0"
step="0.01"
/>
<Input
type="number"
placeholder="ETH amount"
value={liquidityEthAmount}
onChange={(e) => setLiquidityEthAmount(e.target.value)}
min="0"
step="0.01"
/>
</div>
<Button
onClick={handleAddLiquidity}
disabled={!liquidityTokenAmount || !liquidityEthAmount || loading}
>
Add Liquidity
</Button>
</div>
{liquidityData?.provided > '0' && (
<div className="remove-liquidity">
<h3>Remove Liquidity</h3>
<div className="form-row">
<Input
type="number"
placeholder="Amount to remove"
min="0"
max={liquidityData.provided}
step="0.01"
/>
<Button
variant="danger"
onClick={() => handleRemoveLiquidity(liquidityData.provided)}
>
Remove All
</Button>
</div>
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default TokenDashboard;
// src/components/GovernancePanel.jsx
import React, { useState, useEffect } from 'react';
import { useWeb3 } from './Web3Provider';
import { Card, CardHeader, CardContent, CardActions } from './UI/Card';
import Button from './UI/Button';
import Input from './UI/Input';
import Alert from './UI/Alert';
const GovernancePanel = () => {
const { web3, account, contract } = useWeb3();
const [proposals, setProposals] = useState([]);
const [votingPower, setVotingPower] = useState('0');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(null);
// Form states
const [proposalDescription, setProposalDescription] = useState('');
const [proposalQuorum, setProposalQuorum] = useState('');
useEffect(() => {
if (contract && account) {
loadGovernanceData();
}
}, [contract, account]);
const loadGovernanceData = async () => {
try {
setLoading(true);
setError(null);
const [proposalCount, userVotingPower] = await Promise.all([
contract.methods.proposalCount().call(),
contract.methods.votingPower(account).call()
]);
setVotingPower(web3.utils.fromWei(userVotingPower, 'ether'));
// Load proposals
const proposalList = [];
for (let i = 0; i < proposalCount; i++) {
const proposalInfo = await contract.methods.getProposalInfo(i).call();
proposalList.push({
id: i,
proposer: proposalInfo[0],
description: proposalInfo[1],
voteCount: web3.utils.fromWei(proposalInfo[2], 'ether'),
quorum: web3.utils.fromWei(proposalInfo[3], 'ether'),
deadline: new Date(proposalInfo[4] * 1000),
executed: proposalInfo[5],
hasVoted: proposalInfo[6]
});
}
setProposals(proposalList.reverse()); // Newest first
} catch (error) {
console.error('Governance data load error:', error);
setError('Failed to load governance data.');
} finally {
setLoading(false);
}
};
const handleCreateProposal = async () => {
try {
setLoading(true);
setError(null);
const quorumWei = web3.utils.toWei(proposalQuorum, 'ether');
const tx = await contract.methods.createProposal(
proposalDescription,
quorumWei
).send({
from: account,
gas: '200000'
});
setSuccess(`Proposal created successfully! Transaction: ${tx.transactionHash}`);
setProposalDescription('');
setProposalQuorum('');
await loadGovernanceData();
} catch (error) {
console.error('Create proposal error:', error);
setError('Failed to create proposal.');
} finally {
setLoading(false);
}
};
const handleVote = async (proposalId) => {
try {
setLoading(true);
setError(null);
const tx = await contract.methods.vote(proposalId).send({
from: account,
gas: '100000'
});
setSuccess(`Vote cast successfully! Transaction: ${tx.transactionHash}`);
await loadGovernanceData();
} catch (error) {
console.error('Vote error:', error);
setError('Failed to cast vote.');
} finally {
setLoading(false);
}
};
const handleExecuteProposal = async (proposalId) => {
try {
setLoading(true);
setError(null);
const tx = await contract.methods.executeProposal(proposalId).send({
from: account,
gas: '100000'
});
setSuccess(`Proposal executed successfully! Transaction: ${tx.transactionHash}`);
await loadGovernanceData();
} catch (error) {
console.error('Execute proposal error:', error);
setError('Failed to execute proposal.');
} finally {
setLoading(false);
}
};
const canVote = (proposal) => {
return !proposal.hasVoted &&
votingPower > '0' &&
new Date() < proposal.deadline &&
!proposal.executed;
};
const canExecute = (proposal) => {
return !proposal.executed &&
new Date() > proposal.deadline &&
proposal.voteCount >= proposal.quorum;
};
return (
<div className="governance-panel">
<div className="governance-header">
<h1>Governance</h1>
<div className="voting-info">
<p><strong>Your Voting Power:</strong> {votingPower} DEFI</p>
</div>
</div>
{error && <Alert type="error" message={error} />}
{success && <Alert type="success" message={success} />}
{/* Create Proposal */}
<Card className="create-proposal-card">
<CardHeader>
<h2>Create Proposal</h2>
</CardHeader>
<CardContent>
<div className="proposal-form">
<div className="form-group">
<label>Proposal Description</label>
<textarea
value={proposalDescription}
onChange={(e) => setProposalDescription(e.target.value)}
placeholder="Describe your proposal..."
rows={4}
/>
</div>
<div className="form-group">
<label>Quorum (tokens required)</label>
<Input
type="number"
value={proposalQuorum}
onChange={(e) => setProposalQuorum(e.target.value)}
placeholder="1000000"
min="0"
step="0.01"
/>
</div>
</div>
</CardContent>
<CardActions>
<Button
onClick={handleCreateProposal}
disabled={!proposalDescription || !proposalQuorum || votingPower < '1000'}
>
Create Proposal
</Button>
</CardActions>
</Card>
{/* Proposals List */}
<Card className="proposals-card">
<CardHeader>
<h2>Proposals</h2>
</CardHeader>
<CardContent>
{proposals.length === 0 ? (
<p>No proposals yet.</p>
) : (
<div className="proposals-list">
{proposals.map((proposal) => (
<div key={proposal.id} className="proposal-item">
<div className="proposal-header">
<h3>Proposal #{proposal.id + 1}</h3>
<div className="proposal-status">
{proposal.executed ? (
<span className="status-executed">Executed</span>
) : new Date() > proposal.deadline ? (
<span className="status-expired">Expired</span>
) : (
<span className="status-active">Active</span>
)}
</div>
</div>
<div className="proposal-content">
<p className="proposal-description">{proposal.description}</p>
<div className="proposal-meta">
<div className="meta-item">
<label>Proposer:</label>
<span>{proposal.proposer}</span>
</div>
<div className="meta-item">
<label>Deadline:</label>
<span>{proposal.deadline.toLocaleDateString()}</span>
</div>
<div className="meta-item">
<label>Vote Count:</label>
<span>{proposal.voteCount} / {proposal.quorum}</span>
</div>
</div>
<div className="vote-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{
width: `${Math.min((parseFloat(proposal.voteCount) / parseFloat(proposal.quorum)) * 100, 100)}%`
}}
/>
</div>
<span className="progress-text">
{Math.round((parseFloat(proposal.voteCount) / parseFloat(proposal.quorum)) * 100)}%
</span>
</div>
</div>
<div className="proposal-actions">
{canVote(proposal) && (
<Button onClick={() => handleVote(proposal.id)}>
Vote
</Button>
)}
{canExecute(proposal) && (
<Button variant="success" onClick={() => handleExecuteProposal(proposal.id)}>
Execute
</Button>
)}
{proposal.hasVoted && (
<span className="voted-indicator">✓ Voted</span>
)}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default GovernancePanel;
// src/App.jsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Web3Provider from './components/Web3Provider';
import TokenDashboard from './components/TokenDashboard';
import GovernancePanel from './components/GovernancePanel';
import Navbar from './components/Navbar';
import './App.css';
function App() {
return (
<Web3Provider>
<Router>
<div className="App">
<Navbar />
<main className="main-content">
<Routes>
<Route path="/" element={<TokenDashboard />} />
<Route path="/governance" element={<GovernancePanel />} />
</Routes>
</main>
</div>
</Router>
</Web3Provider>
);
}
export default App;
El Web3Provider encapsula toda la lógica de conexión a la red de Ethereum. Verifica si MetaMask está instalado y solicita acceso a la cuenta del usuario mediante eth_requestAccounts. Una vez conectado, obtiene el identificador de red y saldo, configura listeners para cambios de cuenta o red, y expone la instancia de Web3 y el contrato inteligente a través de un contexto.
El TokenDashboard permite a los usuarios interactuar con funciones DeFi. Recupera el saldo de tokens, suministro total, información de pools de staking y datos de liquidez. Los usuarios pueden hacer stake con cantidades específicas, deshacer stake, reclamar recompensas, y agregar o quitar liquidez. El componente maneja conversiones entre wei y ether utilizando las utilidades de Web3.
El GovernancePanel implementa las funciones de gobernanza del protocolo. Los usuarios con poder de voto pueden crear propuestas, votar por propuestas activas y ejecutar propuestas que han
3. Protocolo DeFi con Python y Web3.py
# defi_protocol.py
import json
import time
from web3 import Web3
from web3.contract import Contract
from web3.middleware import geth_poa_middleware
from eth_account import Account
from eth_utils import to_checksum_address
from decimal import Decimal
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DeFiProtocol:
"""
DeFi Protocol implementation for automated trading and liquidity management
"""
def __init__(self, provider_url, private_key=None):
"""
Initialize DeFi Protocol
Args:
provider_url: Web3 provider URL
private_key: Private key for transactions (optional)
"""
self.w3 = Web3(Web3.HTTPProvider(provider_url))
# Inject POA middleware for testnets
if self.w3.eth.chain_id in [3, 4, 5, 42, 80001]:
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
self.account = None
if private_key:
self.account = Account.from_key(private_key)
self.w3.eth.default_account = self.account.address
# Contract addresses and ABIs
self.contracts = {}
self.load_contracts()
# Gas settings
self.gas_price = self.w3.eth.gas_price
self.gas_limit = 300000
def load_contracts(self):
"""Load contract ABIs and addresses"""
# Example contract ABIs (would normally load from files)
erc20_abi = [
{
"constant": True,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function"
},
{
"constant": False,
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
},
{
"constant": False,
"inputs": [
{"name": "_spender", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "approve",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}
]
uniswap_router_abi = [
{
"constant": False,
"inputs": [
{"name": "amountOutMin", "type": "uint256"},
{"name": "path", "type": "address[]"},
{"name": "to", "type": "address"},
{"name": "deadline", "type": "uint256"}
],
"name": "swapExactETHForTokens",
"outputs": [{"name": "amounts", "type": "uint256[]"}],
"type": "function"
},
{
"constant": False,
"inputs": [
{"name": "amountIn", "type": "uint256"},
{"name": "amountOutMin", "type": "uint256"},
{"name": "path", "type": "address[]"},
{"name": "to", "type": "address"},
{"name": "deadline", "type": "uint256"}
],
"name": "swapExactTokensForTokens",
"outputs": [{"name": "amounts", "type": "uint256[]"}],
"type": "function"
},
{
"constant": True,
"inputs": [
{"name": "amountIn", "type": "uint256"},
{"name": "reserveIn", "type": "uint256"},
{"name": "reserveOut", "type": "uint256"}
],
"name": "getAmountOut",
"outputs": [{"name": "amountOut", "type": "uint256"}],
"type": "function"
}
]
# Contract addresses (example for Ethereum Mainnet)
self.contract_addresses = {
'WETH': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
'USDC': '0xA0b86a33E6441E7C1A785D1A97b7327c2c0c0c0c',
'DAI': '0x6B175474E89094C44Da98b954EedeAC495271d0F',
'UNISWAP_ROUTER': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
'UNISWAP_FACTORY': '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
}
# Initialize contracts
for name, address in self.contract_addresses.items():
if name in ['WETH', 'USDC', 'DAI']:
self.contracts[name] = self.w3.eth.contract(
address=address,
abi=erc20_abi
)
elif name == 'UNISWAP_ROUTER':
self.contracts[name] = self.w3.eth.contract(
address=address,
abi=uniswap_router_abi
)
def get_balance(self, token_symbol, address=None):
"""
Get token balance for address
Args:
token_symbol: Token symbol (e.g., 'ETH', 'USDC', 'DAI')
address: Address to check (default: current account)
Returns:
Token balance as Decimal
"""
if address is None:
if not self.account:
raise ValueError("No account specified")
address = self.account.address
if token_symbol == 'ETH':
balance_wei = self.w3.eth.get_balance(address)
return Decimal(balance_wei) / Decimal(10**18)
if token_symbol not in self.contracts:
raise ValueError(f"Unknown token: {token_symbol}")
contract = self.contracts[token_symbol]
balance_wei = contract.functions.balanceOf(address).call()
decimals = self.get_token_decimals(token_symbol)
return Decimal(balance_wei) / Decimal(10**decimals)
def get_token_decimals(self, token_symbol):
"""Get token decimals"""
decimals_map = {
'WETH': 18,
'USDC': 6,
'DAI': 18
}
return decimals_map.get(token_symbol, 18)
def approve_token(self, token_symbol, spender_address, amount):
"""
Approve token spending
Args:
token_symbol: Token symbol to approve
spender_address: Address to approve for
amount: Amount to approve (in human-readable format)
Returns:
Transaction hash
"""
if not self.account:
raise ValueError("No account configured")
if token_symbol not in self.contracts:
raise ValueError(f"Unknown token: {token_symbol}")
contract = self.contracts[token_symbol]
decimals = self.get_token_decimals(token_symbol)
amount_wei = int(Decimal(amount) * Decimal(10**decimals))
# Build transaction
transaction = contract.functions.approve(
spender_address,
amount_wei
).build_transaction({
'from': self.account.address,
'gas': self.gas_limit,
'gasPrice': self.gas_price,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
# Sign and send transaction
signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
# Wait for confirmation
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt.status == 1:
logger.info(f"Token approved: {tx_hash.hex()}")
return tx_hash.hex()
else:
raise Exception("Transaction failed")
def swap_eth_for_tokens(self, eth_amount, token_symbol, slippage_percent=1):
"""
Swap ETH for tokens on Uniswap
Args:
eth_amount: Amount of ETH to swap
token_symbol: Target token symbol
slippage_percent: Maximum slippage percentage
Returns:
Transaction hash
"""
if not self.account:
raise ValueError("No account configured")
if token_symbol not in self.contract_addresses:
raise ValueError(f"Unknown token: {token_symbol}")
router = self.contracts['UNISWAP_ROUTER']
token_address = self.contract_addresses[token_symbol]
# Calculate minimum amount out with slippage
amount_out = self.get_swap_amount_out(eth_amount, 'ETH', token_symbol)
min_amount_out = int(amount_out * (1 - slippage_percent / 100))
# Build transaction
eth_amount_wei = int(Decimal(eth_amount) * Decimal(10**18))
deadline = int(time.time()) + 300 # 5 minutes
transaction = router.functions.swapExactETHForTokens(
min_amount_out,
[self.contract_addresses['WETH'], token_address],
self.account.address,
deadline
).build_transaction({
'from': self.account.address,
'value': eth_amount_wei,
'gas': self.gas_limit,
'gasPrice': self.gas_price,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
# Sign and send transaction
signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
# Wait for confirmation
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt.status == 1:
logger.info(f"ETH swapped for {token_symbol}: {tx_hash.hex()}")
return tx_hash.hex()
else:
raise Exception("Transaction failed")
def swap_tokens_for_eth(self, token_symbol, token_amount, slippage_percent=1):
"""
Swap tokens for ETH on Uniswap
Args:
token_symbol: Token symbol to swap
token_amount: Amount of tokens to swap
slippage_percent: Maximum slippage percentage
Returns:
Transaction hash
"""
if not self.account:
raise ValueError("No account configured")
if token_symbol not in self.contract_addresses:
raise ValueError(f"Unknown token: {token_symbol}")
router = self.contracts['UNISWAP_ROUTER']
token_address = self.contract_addresses[token_symbol]
# Approve token spending
self.approve_token(token_symbol, self.contract_addresses['UNISWAP_ROUTER'], token_amount)
# Calculate minimum amount out with slippage
amount_out = self.get_swap_amount_out(token_amount, token_symbol, 'ETH')
min_amount_out = int(amount_out * (1 - slippage_percent / 100))
# Build transaction
decimals = self.get_token_decimals(token_symbol)
token_amount_wei = int(Decimal(token_amount) * Decimal(10**decimals))
deadline = int(time.time()) + 300 # 5 minutes
transaction = router.functions.swapExactTokensForTokens(
token_amount_wei,
min_amount_out,
[token_address, self.contract_addresses['WETH']],
self.account.address,
deadline
).build_transaction({
'from': self.account.address,
'gas': self.gas_limit,
'gasPrice': self.gas_price,
'nonce': self.w3.eth.get_transaction_count(self.account.address)
})
# Sign and send transaction
signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
# Wait for confirmation
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt.status == 1:
logger.info(f"{token_symbol} swapped for ETH: {tx_hash.hex()}")
return tx_hash.hex()
else:
raise Exception("Transaction failed")
def get_swap_amount_out(self, amount_in, token_in, token_out):
"""
Calculate amount out for swap (simplified)
Args:
amount_in: Amount to swap
token_in: Input token symbol
token_out: Output token symbol
Returns:
Expected amount out
"""
# This is a simplified calculation
# In production, you would query the actual pool reserves
# Example exchange rates (would be fetched from DEX)
exchange_rates = {
('ETH', 'USDC'): 2000, # 1 ETH = 2000 USDC
('ETH', 'DAI'): 2000, # 1 ETH = 2000 DAI
('USDC', 'ETH'): 0.0005, # 1 USDC = 0.0005 ETH
('DAI', 'ETH'): 0.0005, # 1 DAI = 0.0005 ETH
}
rate = exchange_rates.get((token_in, token_out))
if rate is None:
raise ValueError(f"No exchange rate for {token_in} -> {token_out}")
return Decimal(amount_in) * Decimal(rate)
def add_liquidity(self, token_symbol, token_amount, eth_amount):
"""
Add liquidity to Uniswap pool
Args:
token_symbol: Token symbol to add
token_amount: Amount of tokens to add
eth_amount: Amount of ETH to add
Returns:
Transaction hash
"""
if not self.account:
raise ValueError("No account configured")
# This would implement actual Uniswap liquidity addition
# For now, it's a placeholder
logger.info(f"Adding liquidity: {token_amount} {token_symbol} + {eth_amount} ETH")
# In production, you would:
# 1. Approve token spending
# 2. Call Uniswap router's addLiquidityETH function
# 3. Handle transaction and receipt
return "0xplaceholder"
def remove_liquidity(self, token_symbol, liquidity_amount):
"""
Remove liquidity from Uniswap pool
Args:
token_symbol: Token symbol of the pool
liquidity_amount: Amount of liquidity tokens to remove
Returns:
Transaction hash
"""
if not self.account:
raise ValueError("No account configured")
# This would implement actual Uniswap liquidity removal
logger.info(f"Removing liquidity: {liquidity_amount} from {token_symbol} pool")
# In production, you would:
# 1. Call Uniswap router's removeLiquidityETH function
# 2. Handle transaction and receipt
return "0xplaceholder"
def get_portfolio_value(self, address=None):
"""
Calculate total portfolio value in USD
Args:
address: Address to calculate for (default: current account)
Returns:
Portfolio value in USD
"""
if address is None:
if not self.account:
raise ValueError("No account specified")
address = self.account.address
# Get balances
eth_balance = self.get_balance('ETH', address)
usdc_balance = self.get_balance('USDC', address)
dai_balance = self.get_balance('DAI', address)
# Calculate USD value (simplified)
# In production, you would fetch real prices from oracles
eth_price_usd = 2000 # Example price
dai_price_usd = 1.0 # DAI is pegged to USD
portfolio_value = (
eth_balance * eth_price_usd +
usdc_balance +
dai_balance * dai_price_usd
)
return {
'total_value_usd': portfolio_value,
'breakdown': {
'ETH': {'amount': eth_balance, 'value_usd': eth_balance * eth_price_usd},
'USDC': {'amount': usdc_balance, 'value_usd': usdc_balance},
'DAI': {'amount': dai_balance, 'value_usd': dai_balance * dai_price_usd}
}
}
def monitor_gas_prices(self):
"""
Monitor current gas prices
Returns:
Current gas price in Gwei
"""
gas_price_wei = self.w3.eth.gas_price
gas_price_gwei = gas_price_wei / 10**9
return {
'gas_price_wei': gas_price_wei,
'gas_price_gwei': gas_price_gwei,
'estimated_eth_cost': gas_price_wei * self.gas_limit / 10**18
}
def execute_arbitrage(self, token_symbol, min_profit_percent=1):
"""
Execute arbitrage opportunity (simplified)
Args:
token_symbol: Token to arbitrage
min_profit_percent: Minimum profit percentage required
Returns:
Transaction hash if arbitrage executed
"""
# This is a simplified arbitrage implementation
# In production, you would:
# 1. Monitor prices across multiple DEXs
# 2. Calculate profit opportunities
# 3. Execute trades when profitable
logger.info(f"Checking arbitrage opportunities for {token_symbol}")
# Example: Check price difference between two DEXs
# This is placeholder logic
price_dex1 = self.get_swap_amount_out(1, 'ETH', token_symbol)
price_dex2 = self.get_swap_amount_out(1, 'ETH', token_symbol) * 1.02 # 2% difference
profit_percent = ((price_dex2 - price_dex1) / price_dex1) * 100
if profit_percent >= min_profit_percent:
logger.info(f"Arbitrage opportunity found: {profit_percent:.2f}% profit")
# Execute arbitrage trades
return "0xarbitrage_placeholder"
return None
# Usage example
def main():
"""Example usage of DeFi Protocol"""
# Initialize protocol
provider_url = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
private_key = "YOUR_PRIVATE_KEY" # Be careful with private keys!
protocol = DeFiProtocol(provider_url, private_key)
# Get portfolio value
portfolio = protocol.get_portfolio_value()
print(f"Portfolio Value: ${portfolio['total_value_usd']:.2f}")
# Check gas prices
gas_info = protocol.monitor_gas_prices()
print(f"Gas Price: {gas_info['gas_price_gwei']:.2f} Gwei")
# Swap ETH for USDC
try:
tx_hash = protocol.swap_eth_for_tokens(0.1, 'USDC', slippage_percent=1)
print(f"Swap transaction: {tx_hash}")
except Exception as e:
print(f"Swap failed: {e}")
# Check for arbitrage opportunities
arbitrage_tx = protocol.execute_arbitrage('USDC', min_profit_percent=1)
if arbitrage_tx:
print(f"Arbitrage executed: {arbitrage_tx}")
if __name__ == "__main__":
main()
En esta sección implementamos un protocolo DeFi completo que integra la biblioteca Web3.py para interactuar con contratos inteligentes y DEXs como Uniswap. La clase DeFiProtocol encapsula toda la lógica necesaria para gestionar carteras de activos digitales, ejecutar swaps de tokens y analizar oportunidades de arbitraje.
El constructor inicializa la conexión a Ethereum a través de Web3 y configura los contratos inteligentes que necesitamos. El middleware POA se inyecta automáticamente para redes de prueba como Ropsten, Rinkeby, Goerli, Kovan y Mumbai.
La gestión de balances se simplifica mediante el método get_balance(), que distingue entre ETH (consultado directamente desde el blockchain) y tokens ERC20 (consultados mediante llamadas a funciones de contrato). Cada token tiene un número específico de decimales que debe considerarse al convertir entre unidades wei y unidades legibles.
Los swaps se manejan a través de dos métodos complementarios: swap_eth_for_tokens() y swap_tokens_for_eth(). Ambos construyen transacciones, las firman con la clave privada y esperan confirmación en la cadena. El parámetro slippage_percent protege contra cambios de precio significativos durante la ejecución de la transacción.
El método get_portfolio_value() calcula el valor total de la cartera sumando los valores en USD de todos los activos. Para un protocolo de producción, usarías oráculos de precios como Chainlink en lugar de valores codificados.
La gestión de liquidez mediante add_liquidity() y remove_liquidity() son placeholders que muestran dónde iría la lógica real. Una implementación completa llamaría a los métodos del router de Uniswap con los parámetros correctos de piscina.
Finalmente, execute_arbitrage() detecta oportunidades de ganancia comprando en una piscina de liquidez a un precio más bajo y vendiendo en otra a un precio más alto. Este flujo simplificado puede extenderse para monitorear múltiples DEXs en tiempo real y ejecutar trades cuando la ganancia supere un umbral mínimo.
Arquitectura Web3
Ecosistema DeFi
graph TD
A[User] --> B[Wallet]
B --> C[DApp Frontend]
C --> D[Web3.js/Ethers.js]
D --> E[Smart Contracts]
E --> F[DeFi Protocols]
F --> G[DEX]
F --> H[Lending]
F --> I[Yield Farming]
F --> J[Liquidity Pools]
E --> K[Blockchain]
K --> L[Ethereum]
K --> M[Polygon]
K --> N[BSC]
O[Oracles] --> E
P[Chainlink] --> O
Q[Band Protocol] --> O
R[Governance] --> E
S[DAO] --> R
T[Token Holders] --> R
Seguridad en Smart Contracts
Vulnerabilidades comunes
| Vulnerabilidad | Descripción | Medida de protección |
|---|---|---|
| Reentrancy | Llamadas recursivas antes de actualizar estado | ReentrancyGuard, Checks-Effects-Interactions |
| Integer Overflow | Desbordamiento/subdesbordamiento aritmético | SafeMath, Solidity 0.8+ |
| Control de acceso | Llamadas a funciones no autorizadas | Modifiers, Role-based access |
| Front-Running | Manipulación del orden de transacciones | Commit-reveal schemes |
| Manipulación de Oracles | Alteración de feeds de precios | Oracles descentralizados |
Comparación de protocolos DeFi
Categorías principales
| Categoría | Protocolos | Función | TVL (Billion $) |
|---|---|---|---|
| DEX | Uniswap, SushiSwap | Intercambios de tokens | 25+ |
| Lending | Aave, Compound | Préstamos | 20+ |
| Yield | Yearn, Harvest | Optimización de rendimiento | 10+ |
| Derivatives | Synthetix, Perpetual | Derivados | 5+ |
| Oracle | Chainlink, Band | Feeds de precios | 15+ |
Ventajas y desventajas
Ventajas de Web3/DeFi
- Descentralización: No se requiere una autoridad central
- Transparencia: Todas las transacciones son públicamente visibles
- Sin permisos: Cualquiera puede participar sin autorización previa
- Programabilidad: Los Smart Contracts permiten lógica compleja
- Composabilidad: Los protocolos pueden combinarse entre sí
Desventajas
- Escalabilidad: Capacidad limitada de procesamiento de transacciones
- Costos de Gas: Tarifas de transacción elevadas durante congestión de red
- Experiencia de usuario: Integración compleja de wallets
- Riesgos de seguridad: Vulnerabilidades en Smart Contracts
- Incertidumbre regulatoria: Panorama regulatorio aún incierto
Preguntas frecuentes en entrevistas
-
¿Cuál es la diferencia entre Web2 y Web3? Web2 es centralizado con servidores, Web3 es descentralizado con Blockchain y Smart Contracts.
-
Explica qué son los Smart Contracts. Los Smart Contracts son contratos autoejecucables en la Blockchain que ejecutan lógica empresarial sin intermediarios.
-
¿Cuándo se utilizan los protocolos DeFi? Los protocolos DeFi se utilizan para servicios financieros descentralizados como trading, préstamos y yield farming.
-
¿Cuáles son las principales ventajas de Blockchain? Descentralización, transparencia, inmutabilidad y seguridad mediante criptografía.



