paint-brush
Cómo implementar un contrato inteligente en la red Ethereum usando la clave API y el punto final de dRPC por@ileolami
2,915 lecturas
2,915 lecturas

Cómo implementar un contrato inteligente en la red Ethereum usando la clave API y el punto final de dRPC

por Ileolami23m2024/09/11
Read on Terminal Reader

Demasiado Largo; Para Leer

En este artículo, escribirás, compilarás, probarás e implementarás un contrato inteligente de pago de café en la red de pruebas Sepolia de Ethereum utilizando el punto final dRPC y la clave API. Las funciones incluyen: pago de café, revisión del precio del café, recuperación de la cantidad total de café vendido y la cantidad total de dinero ganado.
featured image - Cómo implementar un contrato inteligente en la red Ethereum usando la clave API y el punto final de dRPC
Ileolami HackerNoon profile picture
0-item

Introducción

Al comprender la pila tecnológica para el desarrollo de DApp Web3, debe haber aprendido la pila tecnológica central para el desarrollo de DApp Web3, el rol de RPC en el desarrollo de DApp y cómo usar dRPC para crear una cuenta, generar una clave API, puntos finales, análisis de puntos finales, agregar fondos a su cuenta dRPC y verificar su saldo.


El papel de dRPC en la implementación de contratos inteligentes es simplificar el proceso de configuración de un nodo Ethereum, lo que facilita que los desarrolladores interactúen e implementen con solo una línea de código.


En este artículo, escribirá, compilará, probará e implementará un contrato inteligente de pago de café en Ethereum Sepolia Testnet utilizando el punto final dRPC y la clave API.


Las características incluyen:
  1. Pago del café
  2. Revisando el precio del Café
  3. Recuperar el número total de café vendido y la cantidad total de dinero ganado


Vamos a ensuciarnos las manos.

Prerrequisitos

  1. Tengo una billetera, por ejemplo, Metamask.
  2. Editor de código.
  3. Ya tienes instaladas todas las bibliotecas o frameworks Js de tu elección (por ejemplo, React.js, Next.js, etc.).
  4. Jarra de agua.

Tecnologías y herramientas necesarias

  1. Solidez.
  2. React.js usando Vite.js (Typescript)
  3. Casco duro.
  4. Web3.js.
  5. Dotenv.
  6. Clave API y punto final de dRPC.
  7. La clave privada de su cuenta.
  8. Máscara meta

Cómo escribir un contrato inteligente para el pago del café

  1. Cree una carpeta debajo de su directorio raíz y nómbrela contracts .


  2. Cree un archivo en la carpeta contracts y nómbrelo coffee.sol .

    Se muestra un directorio de archivos con una carpeta llamada "contratos" y un archivo llamado "coffee.sol" dentro de la carpeta.

Usarás Solidity para escribir el contrato inteligente. Los archivos de Solidity se nombran con la extensión .sol porque es la extensión de archivo estándar para el código fuente de Solidity.


  1. Agregue el siguiente código fuente al coffee.sol :

     // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; contract Coffee { uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; // Custom error definitions error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); // Event to log coffee purchases event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); // Function to buy coffee function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } // Fallback function to handle Ether sent directly to the contract receive() external payable { revert DirectEtherTransferNotAllowed(); } // Public view functions to get totals function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; } }

Pragma

  • //SPDX-License-Identifier: MIT : Este identificador de licencia indica que el código está licenciado bajo la Licencia .


  • pragma solidity >=0.8.0 <0.9.0; : Especifica que el código está escrito para versiones de Solidity entre 0.8.0 (inclusive) y 0.9.0 (exclusiva).

Variable de estado

 uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
  • coffeePrice : se establece como un valor constante de 0.0002 ether .
  • totalCoffeesSold : realiza un seguimiento del número de cafés vendidos.
  • totalEtherReceived : rastrea el total de Ether recibido por el contrato.

Errores personalizados

Los errores personalizados en Solidity son mensajes de error que se adaptan a un caso de uso específico, en lugar de los mensajes de error predeterminados que proporciona el lenguaje de programación . Pueden ayudar a mejorar la experiencia del usuario y también pueden ayudar con la depuración y el mantenimiento de contratos inteligentes.


Para definir un error personalizado en Solidity, puede utilizar la siguiente sintaxis:
  • error : Esta palabra clave se utiliza para definir un error personalizado
  • Nombre único: El error debe tener un nombre único
  • Parámetros: si desea incluir detalles o parámetros específicos en el mensaje de error, puede agregarlos entre paréntesis después del nombre del error.
 error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
  • QuantityMustBeGreaterThanZero() : garantiza que la cantidad sea mayor que cero.
  • InsufficientEtherSent(uint256 required, uint256 sent) : garantiza que el Ether enviado sea suficiente.
  • DirectEtherTransferNotAllowed() : evita transferencias directas de Ether al contrato.

Eventos

Un evento es una parte del contrato que almacena los argumentos que se pasan en los registros de transacciones cuando se emite. Los eventos se utilizan generalmente para informar a la aplicación que realiza la llamada sobre el estado actual del contrato mediante la función de registro de EVM. Notifican a las aplicaciones sobre los cambios realizados en los contratos, que luego se pueden utilizar para ejecutar la lógica relacionada.
 event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
  • CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost) : Registra las compras de café.

Funciones

Las funciones son módulos de código autónomos que realizan una tarea específica. Eliminan la redundancia de tener que reescribir el mismo fragmento de código. En cambio, los desarrolladores pueden llamar a una función en el programa cuando sea necesario.

 function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } receive() external payable { revert DirectEtherTransferNotAllowed(); } function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; }
  • buyCoffee(uint256 quantity) external payable : Gestiona las compras de café y realiza las siguientes operaciones:
    • Verifique si la cantidad es válida.
    • Calcula el costo total.
    • Garantiza que se envíe suficiente Ether.
    • Actualiza las variables de estado.
    • Emite el evento de compra.
    • Reembolsa el exceso de Ether.
  • receive() external payable : revierte las transferencias directas de Ether en caso de que alguien envíe fondos directamente a la dirección del contrato.
  • getTotalCoffeesSold() external view returns (uint256) : Devuelve el total de cafés vendidos.
  • getTotalEtherReceived() external view returns (uint256) : Devuelve el Ether total recibido.

Compilación del contrato inteligente de pago de café

Aquí utilizará Hardhat para compilar el contrato inteligente.


  1. Instale Hardhat utilizando el siguiente símbolo del sistema.

     npm install --save-dev hardhat


    Recibirá la respuesta a continuación después de una instalación exitosa.

    Una terminal que muestra el resultado de la instalación de hardhat.


  2. En el mismo directorio donde inicializas hardhat usando este símbolo del sistema:

     npx hardhat init


  3. Seleccione Create a Javascript project utilizando el botón de flecha hacia abajo y presione Enter.

  4. Presione enter para instalar en la carpeta raíz

  5. Acepte todas las indicaciones utilizando la y en su teclado, incluidas las dependencias @nomicfoundation/hardhat-toolbox

  6. A continuación, verá esta respuesta que muestra que ha inicializado correctamente

Notarás que se agregaron algunas carpetas y archivos nuevos a tu proyecto, por ejemplo, Lock.sol , iginition/modules , test/Lock.js y hardhat.config.cjs . No te preocupes por ellos.


Los únicos que son útiles son iginition/modules y hardhat.config.cjs . Más adelante sabrás para qué se usan. Puedes eliminar Lock.sol de la carpeta contracts y Lock.js de la carpeta iginition/modules .


  1. Compile el contrato utilizando el siguiente símbolo del sistema:

     npx hardhat compile 

  1. Verás carpetas y archivos adicionales como éste.

  1. Dentro del archivo Coffee.json está el código ABI en formato JSON que llamarás cuando interactúes con el contrato inteligente.
 { "_format": "hh-sol-artifact-1", "contractName": "Coffee", "sourceName": "contracts/coffee.sol", "abi": [ { "inputs": [], "name": "DirectEtherTransferNotAllowed", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "required", "type": "uint256" }, { "internalType": "uint256", "name": "sent", "type": "uint256" } ], "name": "InsufficientEtherSent", "type": "error" }, { "inputs": [], "name": "QuantityMustBeGreaterThanZero", "type": "error" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "buyer", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "quantity", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "totalCost", "type": "uint256" } ], "name": "CoffeePurchased", "type": "event" }, { "inputs": [ { "internalType": "uint256", "name": "quantity", "type": "uint256" } ], "name": "buyCoffee", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "coffeePrice", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ], "bytecode": "", "deployedBytecode": "", "linkReferences": {}, "deployedLinkReferences": {} }

Probando el contrato inteligente

Escribir un script de prueba automatizado mientras crea su contrato inteligente es crucial y muy recomendable. Actúa como una autenticación de dos factores (2FA), lo que garantiza que su contrato inteligente funcione como se espera antes de implementarlo en la red en vivo.


En la carpeta test , cree un nuevo archivo y nómbrelo Coffee. . Dentro del archivo, pegue el código que aparece a continuación:

 const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers.js"); const { expect } = require("chai"); const pkg = require("hardhat"); const ABI = require('../artifacts/contracts/coffee.sol/Coffee.json'); const { web3 } = pkg; describe("Coffee Contract", function () { // Fixture to deploy the Coffee contract async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); // Estimate gas for the deployment const estimateGas = await rawContract.estimateGas({ from: deployer }); // Deploy the contract const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; } describe("Deployment", function () { // Test to check initial values after deployment it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); }); describe("Buying Coffee", function () { // Test to check coffee purchase and event emission it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); // Buyer purchases coffee const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); // Check event const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); // Test to check revert when quantity is zero it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); // Test to check if totalCoffeesSold and totalEtherReceived are updated correctly it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); }); describe("Fallback function", function () { // Test to check revert when ether is sent directly to the contract it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); }); });
Este código prueba la funcionalidad del contrato inteligente Coffee. Incluye pruebas de implementación, compra de café y manejo de transferencias directas de Ether al contrato.


A continuación se muestra un desglose:

Función de fijación: deployCoffeeFixture

 async function deployCoffeeFixture() {  const coffeeContract = new web3.eth.Contract(ABI.abi);  coffeeContract.handleRevert = true;  const [deployer, buyer] = await web3.eth.getAccounts();  const rawContract = coffeeContract.deploy({    data: ABI.bytecode,  });  const estimateGas = await rawContract.estimateGas({ from: deployer });  const coffee = await rawContract.send({    from: deployer,    gas: estimateGas.toString(),  gasPrice: "",  });  console.log("Coffee contract deployed to: ", coffee.options.address);  return { coffee, deployer, buyer, rawContract }; }
  • Implementa el contrato de Coffee : crea una nueva instancia de contrato y la implementa utilizando la cuenta del implementador.
  • Estimación de gas : estima el gas necesario para la implementación.
  • Devoluciones : la instancia del contrato implementada, el implementador y las cuentas del comprador.

Pruebas de implementación

 describe("Deployment", function () {  it("Should set the initial values correctly", async function () {    const { coffee } = await loadFixture(deployCoffeeFixture);    const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();    const totalEtherReceived = await coffee.methods.totalEtherReceived().call();    expect(totalCoffeesSold).to.equal("0");    expect(totalEtherReceived).to.equal("0");  }); });
  • Verifica los valores iniciales : garantiza que totalCoffeesSold y totalEtherReceived estén configurados en cero después de la implementación.

Pruebas de compra de café

 describe("Buying Coffee", function () {  it("Should purchase coffee and emit an event", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    const quantity = 3;    const totalCost = web3.utils.toWei("0.0006", "ether");    const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });    const event = receipt.events.CoffeePurchased;    expect(event).to.exist;    expect(event.returnValues.buyer).to.equal(buyer);    expect(event.returnValues.quantity).to.equal(String(quantity));    expect(event.returnValues.totalCost).to.equal(totalCost);  });  it("Should revert if the quantity is zero", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    expect(      coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") })    ).to.be.revertedWith("QuantityMustBeGreaterThanZero");  });  it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    const quantity = 5;    const totalCost = web3.utils.toWei("0.001", "ether");    await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });    const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();    const totalEtherReceived = await coffee.methods.totalEtherReceived().call();    expect(totalCoffeesSold).to.equal(String(quantity));    expect(totalEtherReceived).to.equal(totalCost);  }); });
  • Comprar café y emitir un evento : prueba que comprar café actualiza el estado y emite el evento CoffeePurchased .
  • Revertir a cantidad cero : garantiza que la transacción se revierta si la cantidad es cero.
  • Actualización del estado correctamente : verifica que totalCoffeesSold y totalEtherReceived se actualicen correctamente después de una compra.

Prueba de función de respaldo

 describe("Fallback function", function () {  it("Should revert if ether is sent directly to the contract", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    expect(      web3.eth.sendTransaction({        from: buyer,        to: coffee.options.address,        value: web3.utils.toWei("0.001", "ether"),      })    ).to.be.revertedWith("DirectEtherTransferNotAllowed");  }); });
  • Revertir la transferencia directa de Ether : garantiza que enviar Ether directamente al contrato (sin llamar a una función) revierta la transacción.

Probando el contrato inteligente

Después de haber escrito el script de prueba, deberá
  1. Al ejecutar sus contratos y pruebas en Hardhat Network, puede imprimir mensajes de registro y variables de contrato llamando a console.log() desde su código de Solidity. Para usarlo, debe importar hardhat/console.sol en su código de contrato de la siguiente manera:
 //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
  1. Para probar el contrato, ejecute el siguiente comando en su terminal:

     npx hardhat test


  2. Deberías tener un resultado como el que se muestra a continuación:

Esto demuestra que su contrato inteligente funciona como se espera.

Si ejecuta npx hardhat test compilará y probará automáticamente el contrato inteligente. Puede probarlo y decírmelo en la sección de comentarios.

Implementación del contrato inteligente

Aquí, implementará su contrato inteligente en Testnet le permite probar su contrato inteligente en un entorno que imita la red principal de Ethereum sin incurrir en costos significativos. Si está familiarizado con la función de la aplicación descentralizada, puede volver a implementarla en la red principal de Ethereum.


  1. Instale el paquete dotenv y estas dependencias.

     npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
    Esto agregará Web3.Js y Dotenv a su proyecto al incluirlo en la carpeta 'node_modules'.


  2. Importarlos a su archivo hardhat.config.cjs

     require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } };
  3. Crea un archivo .env en tu carpeta raíz.


  4. Obtén la clave privada de tu cuenta desde tu billetera MetaMask y la clave API dRPC.


  5. Guárdelos en su archivo .env .

     DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key


  6. Actualice el archivo hardhat.config.cjs para incluir la configuración de Sepolia Testnet:

     require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); const dRPC_API_KEY = process.env.VITE_dRPC_API_KEY; const PRIVATE_KEY = process.env.VITE_PRIVATE_KEY; module.exports = { solidity: "0.8.24", networks: { sepolia: { url: `//lb.drpc.org/ogrpc?network=sepolia&dkey=${dRPC_API_KEY}`, accounts: [`0x${PRIVATE_KEY}`], } } };
  7. Cree un nuevo archivo de secuencia de comandos en la carpeta ignition/module y nómbrelo deploy.cjs . Agregue el siguiente código para implementar su contrato inteligente:

     const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
  8. Implemente el contrato inteligente ejecutando el siguiente comando en su terminal:

     npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia


    Después de ejecutar el símbolo del sistema, se le solicitará que Confirm deploy to network sepolia (11155111)? (y/n) , escriba y . Debería ver la dirección de su contrato inteligente implementado en la terminal luego de una implementación exitosa.

    También puede acceder a la dirección del contrato en el archivo deployed_addresses.json .

    Captura de pantalla de un explorador de archivos de Vite-Project y un archivo deployment_addresses.json abierto. Felicitaciones, has implementado exitosamente tu contrato inteligente en Sepolia Testnet. 🎉

Conclusión

Este artículo le ha enseñado cómo escribir contratos inteligentes de pago, probarlos, compilarlos e implementarlos utilizando la CLI de Hardhat.


En el siguiente artículo, aprenderá a crear la interfaz de usuario para esta aplicación descentralizada. Esta interfaz de usuario constará de lo siguiente:

  1. Campo de entrada para el número de cafés comprados.
  2. Un botón que activa una transacción de pago y la deduce de su cuenta.
  3. Muestra el total de café comprado y la cantidad recibida en ether y USD
  4. El precio del café en sí, tanto en ether como en USD.

Referencia


← Artículo anterior Artículo siguiente →

바카라사이트 바카라사이트 온라인바카라