├── .gitattributes ├── dapp-ui ├── assets │ └── default.png ├── static │ └── favicon.ico ├── README.md ├── plugins │ ├── walletConnect.js │ ├── utils.js │ └── airbnbABI.json ├── package.json ├── components │ ├── card.vue │ ├── navbar.vue │ ├── detailsModal.vue │ └── propertyForm.vue ├── nuxt.config.js ├── layouts │ └── default.vue └── pages │ └── index.vue ├── migrations ├── 2_deploy_contracts.js └── 1_initial_migration.js ├── package.json ├── contracts ├── Migrations.sol └── Airbnb.sol ├── README.md ├── .gitignore ├── test └── AirbnbTest.js ├── setup.md ├── instructions.md ├── truffle-config.js └── dapps.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /dapp-ui/assets/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maticnetwork/ethindia-workshop/HEAD/dapp-ui/assets/default.png -------------------------------------------------------------------------------- /dapp-ui/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maticnetwork/ethindia-workshop/HEAD/dapp-ui/static/favicon.ico -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Airbnb = artifacts.require("Airbnb"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Airbnb); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /dapp-ui/README.md: -------------------------------------------------------------------------------- 1 | # dapp-ui 2 | 3 | > My wondrous Nuxt.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm run install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm run start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethindia-workshop", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test:ethereum": "ganache-cli -m 'layer post chase message demise fresh resource beyond bulb rose garden kite'", 8 | "compile": "truffle compile", 9 | "test": "truffle test" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "truffle": "^5.0.29" 15 | }, 16 | "dependencies": { 17 | "ganache-cli": "^6.5.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dapp-ui/plugins/walletConnect.js: -------------------------------------------------------------------------------- 1 | import WalletConnect from '@walletconnect/browser' 2 | import WalletConnectQRCodeModal from '@walletconnect/qrcode-modal' 3 | 4 | let walletConnector = null 5 | let accountAddress = null 6 | 7 | export const initWalletConnect = () => { 8 | // Create a walletConnector 9 | 10 | 11 | // Check if connection is already established 12 | 13 | // Subscribe to Events 14 | 15 | } 16 | 17 | 18 | export async function sampleTx() { 19 | // Draft transaction 20 | 21 | // Send transaction 22 | 23 | } 24 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dapp-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapp-ui", 3 | "version": "1.0.0", 4 | "description": "My wondrous Nuxt.js project", 5 | "author": "ptsayli@gmail.com", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@walletconnect/browser": "^1.0.0-beta.31", 15 | "@walletconnect/qrcode-modal": "^1.0.0-beta.31", 16 | "bootstrap": "^4.1.3", 17 | "bootstrap-vue": "^2.0.0-rc.11", 18 | "node-sass": "^4.12.0", 19 | "nuxt": "^2.0.0", 20 | "vuejs-datepicker": "^1.6.2", 21 | "web3": "^1.2.0" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^1.18.9", 25 | "sass-loader": "^7.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decentralized Airbnb 2 | Setup 3 | 4 | 1. Install dependencies 5 | ``` 6 | npm install 7 | ``` 8 | 9 | 2. Follow along the instructions to complete the code. 10 | - Follow [instructions](./Instructions.md) to complete the smart contract code. 11 | - Follow [setup](./setup.md) to setup all requirements. 12 | - Follow [dapps](./dapps.md) to complete the DApp code. 13 | 14 | 3. Run ethereum blockchain locally 15 | ``` 16 | npm run test:ethereum 17 | ``` 18 | 19 | 4. Run DApp UI 20 | ``` 21 | cd dapp-ui 22 | npm install 23 | npm run build 24 | npm run start 25 | ``` 26 | 27 | 5. Navigate to http://localhost:3000/ to see the app running. 28 | 29 | ## Full Solution 30 | 31 | [Complete Decentralized Airbnb](https://github.com/maticnetwork/ethindia-workshop/tree/complete-dapp) | or `git checkout complete-dapp` 32 | -------------------------------------------------------------------------------- /dapp-ui/components/card.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /dapp-ui/components/navbar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 45 | -------------------------------------------------------------------------------- /dapp-ui/plugins/utils.js: -------------------------------------------------------------------------------- 1 | import AirbnbABI from './airbnbABI' 2 | const Web3 = require('web3') 3 | 4 | let metamaskWeb3 = new Web3('http://localhost:8545') 5 | let account = null 6 | let airbnbContract 7 | let airbnbContractAddress = '' // Paste Contract address here 8 | 9 | export function web3() { 10 | return metamaskWeb3 11 | } 12 | 13 | export const accountAddress = () => { 14 | return account 15 | } 16 | 17 | export async function setProvider() { 18 | // TODO: get injected Metamask Object and create Web3 instance 19 | 20 | } 21 | 22 | 23 | function getAirbnbContract() { 24 | // TODO: create and return contract Object 25 | 26 | } 27 | 28 | 29 | export async function postProperty(name, description, price) { 30 | // TODO: call Airbnb.rentOutproperty 31 | 32 | alert('Property Posted Successfully') 33 | } 34 | 35 | export async function bookProperty(spaceId, checkInDate, checkOutDate, totalPrice) { 36 | // TODO: call Airbnb.rentSpace 37 | 38 | alert('Property Booked Successfully') 39 | } 40 | 41 | export async function fetchAllProperties() { 42 | // TODO: call Airbnb.propertyId 43 | // iterate till property Id 44 | // push each object to properties array 45 | } 46 | -------------------------------------------------------------------------------- /dapp-ui/nuxt.config.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | mode: 'spa', 4 | /* 5 | ** Headers of the page 6 | */ 7 | head: { 8 | title: process.env.npm_package_name || '', 9 | meta: [ 10 | { charset: 'utf-8' }, 11 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 12 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 13 | ], 14 | link: [ 15 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 16 | ] 17 | }, 18 | /* 19 | ** Customize the progress-bar color 20 | */ 21 | loading: { color: '#fff' }, 22 | /* 23 | ** Global CSS 24 | */ 25 | css: [ 26 | ], 27 | /* 28 | ** Plugins to load before mounting the App 29 | */ 30 | plugins: [ 31 | ], 32 | /* 33 | ** Nuxt.js dev-modules 34 | */ 35 | devModules: [ 36 | ], 37 | /* 38 | ** Nuxt.js modules 39 | */ 40 | modules: [ 41 | // Doc: https://bootstrap-vue.js.org/docs/ 42 | 'bootstrap-vue/nuxt', 43 | ], 44 | /* 45 | ** Build configuration 46 | */ 47 | build: { 48 | /* 49 | ** You can extend webpack config here 50 | */ 51 | extend(config, ctx) { 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dapp-ui/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | build 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | 66 | # next.js build output 67 | .next 68 | 69 | # nuxt.js build output 70 | .nuxt 71 | 72 | # Nuxt generate 73 | dist 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless 80 | 81 | # IDE / Editor 82 | .idea 83 | .editorconfig 84 | 85 | # Service worker 86 | sw.* 87 | 88 | # Mac OSX 89 | .DS_Store 90 | -------------------------------------------------------------------------------- /test/AirbnbTest.js: -------------------------------------------------------------------------------- 1 | const Airbnb = artifacts.require("Airbnb"); 2 | const SCALING_FACTOR = web3.utils.toBN(10).pow(web3.utils.toBN(18)) 3 | 4 | contract("Airbnb", async function(accounts) { 5 | let airbnb 6 | 7 | beforeEach(async function() { 8 | airbnb = await Airbnb.new() 9 | }) 10 | 11 | it("rentOutSpace", async function() { 12 | const price = web3.utils.toBN('25').mul(SCALING_FACTOR).div(web3.utils.toBN(100)) // 0.25 ether 13 | const advertiseProperty = await airbnb.rentOutproperty( 14 | "Casa Koko", 15 | "Contemporary craft architecture on the beach", 16 | price 17 | ) 18 | const propertyId = advertiseProperty.receipt.logs[0].args.propertyId 19 | 20 | let rentProperty = await airbnb.rentProperty( 21 | propertyId, 22 | 5, 23 | 7, 24 | { value: web3.utils.toWei('0.5', 'ether'), from: accounts[1] } 25 | ) 26 | assert.ok(rentProperty.receipt.logs[0].args.propertyId.eq(propertyId)) 27 | assert.ok(rentProperty.receipt.logs[0].args.bookingId.eq(web3.utils.toBN(0))) 28 | 29 | try { 30 | await airbnb.rentProperty( 31 | propertyId, 32 | 6, 33 | 8, 34 | { value: web3.utils.toWei('0.5', 'ether'), from: accounts[1] } 35 | ) 36 | assert.fail('Should have failed') 37 | } catch(e) { 38 | assert.ok(e.reason.includes('property is not available for the selected dates')) 39 | } 40 | 41 | rentProperty = await airbnb.rentProperty( 42 | propertyId, 43 | 113, 44 | 116, 45 | { value: web3.utils.toWei('0.75', 'ether'), from: accounts[2] } 46 | ) 47 | assert.ok(rentProperty.receipt.logs[0].args.propertyId.eq(propertyId)) 48 | assert.ok(rentProperty.receipt.logs[0].args.bookingId.eq(web3.utils.toBN(1))) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /dapp-ui/pages/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 43 | 44 | 73 | -------------------------------------------------------------------------------- /setup.md: -------------------------------------------------------------------------------- 1 | ## Creating a Decentralized Airbnb DApp 2 | ### Intro 3 | In this workshop, we'll explore how to create decentralized Airbnb. We will use the pre-written Ethereum smart contract and deploy it locally on ganache using remix, we can deploy contract on any Testnet or Mainnet . 4 | 5 | This dApp uses [Nuxt](https://nuxtjs.org/), but is out of scope for this workshop, so don't worry, we will only be focusing on the pieces that helps us build. In practice, any JS framework can be used. 6 | 7 | 8 | # Installation 9 | ## Check your environment 10 | Prior to all, you should have the following prerequisites installed on your machine: 11 | #### NodeJS 8.10+ 12 | ``` 13 | node version 14 | > 8.10+ 15 | ``` 16 | If you need to update Node, please [install `nvm`](https://github.com/creationix/nvm#installation) and install/use the LTS version. macOS/Linux commands provided for you below for convenience: 17 | ``` 18 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 19 | nvm install --lts 20 | nvm use lts 21 | ``` 22 | #### MetaMask 23 | 24 | [Download MetaMask chrome extension](https://metamask.io/) 25 | 26 | 27 | #### Install ganache-cli 28 | ``` 29 | npm install -g ganache-cli 30 | ``` 31 | 32 | # Template clone and explore 33 | Now we have Setup installed properly, let’s grab the dApp template that we will use as the skeleton of our dApp. This template is a website built using Nuxt.js (don't worry if you don't know Nuxt.js, we are not focusing on this part). 34 | 35 | ## We will... 36 | We will use existing solidity contracts developed in previous session, and then use these functions inside the website. 37 | [Airbnb.sol](./contracts/Airbnb.sol) 38 | 39 | ## Let's go 40 | 41 | Clone the template into your folder: 42 | ``` 43 | git clone https://github.com/maticnetwork/ethindia-workshop.git 44 | 45 | cd ethindia-workshop 46 | 47 | npm i 48 | ``` 49 | 50 | ## Start TestRPC for development using [ganache-cli](https://github.com/trufflesuite/ganache-cli) 51 | 52 | ``` 53 | npm run test:ethereum 54 | ``` 55 | 56 | ## Deploy contract using Remix Etherium IDE 57 | 58 | - Open [Remix IDE](https://remix.ethereum.org) 59 | 60 | - Enable `Solidity Compiler` and `Deploy & Run Transactions` on remix IDE 61 | 62 | - Create new file [Airbnb.sol](./contracts/Airbnb.sol) from plus icon on left panel 63 | 64 | - Select appropriate network on metamask, as we are going to deploy contract using ganache-cli we will select `Locahost 8545` 65 | 66 | - Select appropriate compiler on remix IDE, in this example we are going to use `0.5.7` above compiler 67 | 68 | - Go to DEPLOY & RUN TRANSACTIONS and click deploy. 69 | 70 | - On sucessfull deploy copy `contract address` 71 | 72 | 73 | [NEXT - Complete DApps UI ](./dapps.md) 74 | -------------------------------------------------------------------------------- /contracts/Airbnb.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | contract Airbnb { 4 | // INSERT struct Property 5 | 6 | // Unique and sequential propertyId for every new property 7 | uint256 public propertyId; 8 | 9 | // mapping of propertyId to Property object 10 | mapping(uint256 => Property) public properties; 11 | 12 | // INSERT struct Booking 13 | 14 | uint256 public bookingId; 15 | 16 | // mapping of bookingId to Booking object 17 | mapping(uint256 => Booking) public bookings; 18 | 19 | // This event is emitted when a new property is put up for sale 20 | event NewProperty ( 21 | uint256 indexed propertyId 22 | ); 23 | 24 | // This event is emitted when a NewBooking is made 25 | event NewBooking ( 26 | uint256 indexed propertyId, 27 | uint256 indexed bookingId 28 | ); 29 | 30 | /** 31 | * @dev Put up your funky space on the market 32 | * @param name Name of the property 33 | * @param description Short description of your property 34 | * @param price Price per day in wei (1 ether = 10^18 wei) 35 | */ 36 | function rentOutproperty(string memory name, string memory description, uint256 price) public { 37 | // create a property object 38 | 39 | // Persist `property` object to the "permanent" storage 40 | 41 | // emit an event to notify the clients 42 | } 43 | 44 | /** 45 | * @dev Make an Airbnb booking 46 | * @param _propertyId id of the property to rent out 47 | * @param checkInDate Check-in date 48 | * @param checkoutDate Check-out date 49 | */ 50 | function rentProperty(uint256 _propertyId, uint256 checkInDate, uint256 checkoutDate) public payable { 51 | // Retrieve `property` object from the storage 52 | 53 | // Assert that property is active 54 | 55 | // Assert that property is available for the dates 56 | 57 | // Check the customer has sent an amount equal to (pricePerDay * numberOfDays) 58 | 59 | // send funds to the owner of the property 60 | _sendFunds(property.owner, msg.value); 61 | 62 | // conditions for a booking are satisfied, so make the booking 63 | _createBooking(_propertyId, checkInDate, checkoutDate); 64 | } 65 | 66 | function _sendFunds (address beneficiary, uint256 value) internal { 67 | // address(uint160()) is a weird solidity quirk 68 | // Read more here: https://solidity.readthedocs.io/en/v0.5.10/050-breaking-changes.html?highlight=address%20payable#explicitness-requirements 69 | address(uint160(beneficiary)).transfer(value); 70 | } 71 | 72 | function _createBooking(uint256 _propertyId, uint256 checkInDate, uint256 checkoutDate) internal { 73 | // Create a new booking object 74 | 75 | // persist to storage 76 | 77 | // Retrieve `property` object from the storage 78 | 79 | // Mark the property booked on the requested dates 80 | 81 | // Emit an event to notify clients 82 | } 83 | 84 | /** 85 | * @dev Take down the property from the market 86 | * @param _propertyId Property ID 87 | */ 88 | function markPropertyAsInactive(uint256 _propertyId) public { 89 | require( 90 | properties[_propertyId].owner == msg.sender, 91 | "THIS IS NOT YOUR PROPERTY" 92 | ); 93 | properties[_propertyId].isActive = false; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /instructions.md: -------------------------------------------------------------------------------- 1 | ### Let's begin by writing our data structures 2 | 1. `struct Property` represents a property that you may want to rent out 3 | ``` 4 | struct Property { 5 | string name; 6 | string description; 7 | bool isActive; // is property active 8 | uint256 price; // per day price in wei (1 ether = 10^18 wei) 9 | address owner; // Owner of the property 10 | 11 | // Is the property booked on a particular day, 12 | // For the sake of simplicity, we assign 0 to Jan 1, 1 to Jan 2 and so on 13 | // so isBooked[31] will denote whether the property is booked for Feb 1 14 | bool[] isBooked; 15 | } 16 | ``` 17 | 18 | 2. `struct Booking` represents the details of a particular booking 19 | ``` 20 | struct Booking { 21 | uint256 propertyId; 22 | uint256 checkInDate; 23 | uint256 checkoutDate; 24 | address user; 25 | } 26 | ``` 27 | 28 | ### Functions 29 | 1. Put up your funky space on the market 30 | ``` 31 | function rentOutproperty(string memory name, string memory description, uint256 price) public { 32 | Property memory property = Property({ 33 | name: name, 34 | description: description, 35 | isActive: true, 36 | price: price, 37 | owner: msg.sender, 38 | isBooked: new bool[](365) 39 | }); 40 | 41 | // Persist `property` object to the "permanent" storage 42 | properties[propertyId] = property; 43 | 44 | // emit an event to notify the clients 45 | emit NewProperty(propertyId++); 46 | } 47 | ``` 48 | 49 | 2. Rent/Book a property for your vacation! 50 | ``` 51 | function rentProperty(uint256 _propertyId, uint256 checkInDate, uint256 checkoutDate) public payable { 52 | // Retrieve `property` object from the storage 53 | Property storage property = properties[_propertyId]; 54 | 55 | // Assert that property is active 56 | require( 57 | property.isActive == true, 58 | "property with this ID is not active" 59 | ); 60 | 61 | // Assert that property is available for the dates 62 | for (uint256 i = checkInDate; i < checkoutDate; i++) { 63 | if (property.isBooked[i] == true) { 64 | // if property is booked on a day, revert the transaction 65 | revert("property is not available for the selected dates"); 66 | } 67 | } 68 | 69 | // Check the customer has sent an amount equal to (pricePerDay * numberOfDays) 70 | require( 71 | msg.value == property.price * (checkoutDate - checkInDate), 72 | "Sent insufficient funds" 73 | ); 74 | } 75 | ``` 76 | 77 | 3. Helper function to create a booking 78 | ``` 79 | function _createBooking(uint256 _propertyId, uint256 checkInDate, uint256 checkoutDate) internal { 80 | // Create a new booking object 81 | Booking memory booking = Booking({ 82 | propertyId: _propertyId, 83 | checkInDate: checkInDate, 84 | checkoutDate: checkoutDate, 85 | user: msg.sender 86 | }); 87 | 88 | // persist to storage 89 | bookings[bookingId] = booking; 90 | 91 | // Retrieve `property` object from the storage 92 | Property storage property = properties[_propertyId]; 93 | 94 | // Mark the property booked on the requested dates 95 | for (uint256 i = checkInDate; i < checkoutDate; i++) { 96 | property.isBooked[i] = true; 97 | } 98 | 99 | // Emit an event to notify clients 100 | emit NewBooking(_propertyId, bookingId++); 101 | } 102 | ``` 103 | 104 | -------------------------------------------------------------------------------- /dapp-ui/components/detailsModal.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 68 | 69 | 134 | -------------------------------------------------------------------------------- /dapp-ui/components/propertyForm.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 71 | 72 | 137 | -------------------------------------------------------------------------------- /dapp-ui/plugins/airbnbABI.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "constant": false, 5 | "inputs": [ 6 | { 7 | "name": "_propertyId", 8 | "type": "uint256" 9 | } 10 | ], 11 | "name": "markPropertyAsInactive", 12 | "outputs": [], 13 | "payable": false, 14 | "stateMutability": "nonpayable", 15 | "type": "function" 16 | }, 17 | { 18 | "constant": false, 19 | "inputs": [ 20 | { 21 | "name": "name", 22 | "type": "string" 23 | }, 24 | { 25 | "name": "description", 26 | "type": "string" 27 | }, 28 | { 29 | "name": "price", 30 | "type": "uint256" 31 | } 32 | ], 33 | "name": "rentOutproperty", 34 | "outputs": [], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": false, 41 | "inputs": [ 42 | { 43 | "name": "_propertyId", 44 | "type": "uint256" 45 | }, 46 | { 47 | "name": "checkInDate", 48 | "type": "uint256" 49 | }, 50 | { 51 | "name": "checkoutDate", 52 | "type": "uint256" 53 | } 54 | ], 55 | "name": "rentProperty", 56 | "outputs": [], 57 | "payable": true, 58 | "stateMutability": "payable", 59 | "type": "function" 60 | }, 61 | { 62 | "anonymous": false, 63 | "inputs": [ 64 | { 65 | "indexed": true, 66 | "name": "propertyId", 67 | "type": "uint256" 68 | } 69 | ], 70 | "name": "NewProperty", 71 | "type": "event" 72 | }, 73 | { 74 | "anonymous": false, 75 | "inputs": [ 76 | { 77 | "indexed": true, 78 | "name": "propertyId", 79 | "type": "uint256" 80 | }, 81 | { 82 | "indexed": true, 83 | "name": "bookingId", 84 | "type": "uint256" 85 | } 86 | ], 87 | "name": "NewBooking", 88 | "type": "event" 89 | }, 90 | { 91 | "constant": true, 92 | "inputs": [ 93 | { 94 | "name": "", 95 | "type": "uint256" 96 | } 97 | ], 98 | "name": "bookings", 99 | "outputs": [ 100 | { 101 | "name": "propertyId", 102 | "type": "uint256" 103 | }, 104 | { 105 | "name": "checkInDate", 106 | "type": "uint256" 107 | }, 108 | { 109 | "name": "checkoutDate", 110 | "type": "uint256" 111 | }, 112 | { 113 | "name": "user", 114 | "type": "address" 115 | } 116 | ], 117 | "payable": false, 118 | "stateMutability": "view", 119 | "type": "function" 120 | }, 121 | { 122 | "constant": true, 123 | "inputs": [ 124 | { 125 | "name": "", 126 | "type": "uint256" 127 | } 128 | ], 129 | "name": "properties", 130 | "outputs": [ 131 | { 132 | "name": "name", 133 | "type": "string" 134 | }, 135 | { 136 | "name": "description", 137 | "type": "string" 138 | }, 139 | { 140 | "name": "isActive", 141 | "type": "bool" 142 | }, 143 | { 144 | "name": "price", 145 | "type": "uint256" 146 | }, 147 | { 148 | "name": "owner", 149 | "type": "address" 150 | } 151 | ], 152 | "payable": false, 153 | "stateMutability": "view", 154 | "type": "function" 155 | } 156 | ] 157 | } -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | development: { 46 | host: "127.0.0.1", // Localhost (default: none) 47 | port: 8545, // Standard Ethereum port (default: none) 48 | network_id: "*", // Any network (default: none) 49 | }, 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // timeout: 100000 83 | }, 84 | 85 | // Configure your compilers 86 | compilers: { 87 | solc: { 88 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 89 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 90 | // settings: { // See the solidity docs for advice about optimization and evmVersion 91 | // optimizer: { 92 | // enabled: false, 93 | // runs: 200 94 | // }, 95 | // evmVersion: "byzantium" 96 | // } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /dapps.md: -------------------------------------------------------------------------------- 1 | ## Creating a Decentralized Airbnb DApp 2 | 3 | ### setting up DApp 4 | 5 | 1. paste your contract address to variable `airbnbContractAddress` on line 7 in [utils.js](./dapp-ui/plugins/utils.js). 6 | 7 | -> insert this statements inside `mounted()` method in `index.vue` 8 | 9 | ```js 10 | await setProvider() 11 | 12 | const properties = await fetchAllProperties() 13 | this.posts = properties 14 | ``` 15 | 16 | -> copy this to `setProvider` method in `utils.js` 17 | 18 | ```js 19 | if (window.ethereum) { 20 | metamaskWeb3 = new Web3(ethereum) 21 | try { 22 | // Request account access if needed 23 | await ethereum.enable() 24 | } catch (error) { 25 | // User denied account access... 26 | } 27 | } else if (window.web3) { 28 | metamaskWeb3 = new Web3(web3.currentProvider) 29 | } 30 | account = await metamaskWeb3.eth.getAccounts() 31 | ``` 32 | 33 | 4. create and return contract Object 34 | 35 | ```js 36 | airbnbContract = 37 | airbnbContract || 38 | new metamaskWeb3.eth.Contract(AirbnbABI.abi, airbnbContractAddress) 39 | return airbnbContract 40 | ``` 41 | 42 | 5. `postProperty` - copy below code inside `postAd` in `propertyForm.vue` 43 | 44 | ```js 45 | // convert price from ETH to Wei 46 | const weiValue = web3().utils.toWei(this.price, 'ether') 47 | 48 | // call metamask.postProperty 49 | postProperty(this.title, this.description, weiValue) 50 | ``` 51 | 52 | -> copy below code inside `postProperty` in `utils.js` 53 | 54 | ```js 55 | const prop = await getAirbnbContract() 56 | .methods.rentOutproperty(name, description, price) 57 | .send({ 58 | from: account[0], 59 | }) 60 | ``` 61 | 62 | 6. `bookProperty` - copy below code inside `book` in `detailsModal.vue` 63 | 64 | ```js 65 | const startDay = this.getDayOfYear(this.startDate) 66 | const endDay = this.getDayOfYear(this.endDate) 67 | const totalPrice = 68 | web3().utils.toWei(this.propData.price, 'ether') * (endDay - startDay) 69 | bookProperty(this.propData.id, startDay, endDay, totalPrice) 70 | ``` 71 | 72 | -> copy below code inside `bookProperty` in `utils.js` 73 | 74 | ```js 75 | const prop = await getAirbnbContract() 76 | .methods.rentProperty(spaceId, checkInDate, checkOutDate) 77 | .send({ 78 | from: account[0], 79 | value: totalPrice, 80 | }) 81 | ``` 82 | 83 | 7. fetch all properties 84 | 85 | -> insert inside `fetchAllProperties()` method in `utils.js` 86 | 87 | ```js 88 | const propertyId = await getAirbnbContract() 89 | .methods.propertyId() 90 | .call() 91 | 92 | const properties = [] 93 | for (let i = 0; i < propertyId; i++) { 94 | const p = await airbnbContract.methods.properties(i).call() 95 | properties.push({ 96 | id: i, 97 | name: p.name, 98 | description: p.description, 99 | price: metamaskWeb3.utils.fromWei(p.price), 100 | }) 101 | } 102 | return properties 103 | ``` 104 | 105 | VOILA 😀 106 | 107 | ---------- 108 | 109 | ### [WalletConnect Demo](https://example.walletconnect.org/) | [Docs](https://docs.walletconnect.org/) 110 | 111 | _Note: this is an altenative to Metamask, you can either use metamask or WalletConnect protocol to sign the transaction._ 112 | 113 | -> add connect button to menubar 114 | 115 | ```html 116 | 121 | ``` 122 | 123 | -> init walletConnect instance 124 | 125 | ```js 126 | // Create a walletConnector 127 | walletConnector = new WalletConnect({ 128 | bridge: 'https://bridge.walletconnect.org', // Required 129 | }) 130 | 131 | // Check if connection is already established 132 | if (!walletConnector.connected) { 133 | // create new session 134 | walletConnector.createSession().then(() => { 135 | // get uri for QR Code modal 136 | const uri = walletConnector.uri 137 | // display QR Code modal 138 | WalletConnectQRCodeModal.open(uri, () => { 139 | console.log('QR Code Modal closed') 140 | }) 141 | }) 142 | } 143 | // Subscribe to Events 144 | 145 | walletConnector.on('connect', (error, payload) => { 146 | if (error) { 147 | throw error 148 | } 149 | 150 | // Close QR Code Modal 151 | WalletConnectQRCodeModal.close() 152 | 153 | // Get provided accounts and chainId 154 | const { accounts, chainId } = payload.params[0] 155 | accountAddress = accounts[0] 156 | }) 157 | ``` 158 | 159 | -> add send Transaction button on homeScreen 160 | 161 | ```html 162 | 163 | Send Transaction 164 | 165 | ``` 166 | 167 | -> use draft transaction 168 | 169 | ```js 170 | const tx = { 171 | from: accountAddress, // Required 172 | to: '0x89D24A7b4cCB1b6fAA2625Fe562bDd9A23260359', // Required (for non contract deployments) 173 | data: '0x', // Required 174 | gasPrice: '0x02540be400', // Optional 175 | gasLimit: '0x9c40', // Optional 176 | value: '0x00', // Optional 177 | nonce: '0x0114', // Optional 178 | } 179 | ``` 180 | 181 | -> Send transaction 182 | 183 | ```js 184 | walletConnector 185 | .signTransaction(tx) 186 | .then(result => { 187 | // Returns transaction id (hash) 188 | console.log(result) 189 | alert(`Signed Data: ${result}`) 190 | }) 191 | .catch(error => { 192 | // Error returned when rejected 193 | console.error(error) 194 | }) 195 | ``` 196 | --------------------------------------------------------------------------------