| 1 | require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') }); |
| 2 | |
| 3 | const hre = require("hardhat"); |
| 4 | const { execSync } = require("child_process"); |
| 5 | const { Pool } = require("pg"); |
| 6 | |
| 7 | |
| 8 | const SAMPLE_PROPERTIES = [ |
| 9 | { |
| 10 | title: "Luxury Downtown Apartment", |
| 11 | propertyFor: "sell", |
| 12 | description: "Stunning modern apartment in the heart of downtown with panoramic city views. Features high-end finishes, smart home technology, and access to premium amenities.", |
| 13 | locality: "Financial District", |
| 14 | address: "123 Main Street, Apt 45", |
| 15 | price: 850000, |
| 16 | priceEth: 425, |
| 17 | roiPercentage: 7.2, |
| 18 | totalTokens: 85000, |
| 19 | availableTokens: 9350, |
| 20 | minInvestment: 10, |
| 21 | images: ["https://images.unsplash.com/photo-1613490493576-7fde63acd811?w=800&q=80"], |
| 22 | }, |
| 23 | { |
| 24 | title: "Modern Tech District Complex", |
| 25 | propertyFor: "sell", |
| 26 | description: "Prime commercial space in the booming tech corridor. Perfect for startups and established companies alike. High foot traffic and excellent public transit access.", |
| 27 | locality: "Tech Hub", |
| 28 | address: "456 Innovation Boulevard", |
| 29 | price: 1200000, |
| 30 | priceEth: 600, |
| 31 | roiPercentage: 6.8, |
| 32 | totalTokens: 120000, |
| 33 | availableTokens: 6000, |
| 34 | minInvestment: 10, |
| 35 | images: ["https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800&q=80"], |
| 36 | }, |
| 37 | { |
| 38 | title: "Waterfront Commercial Space", |
| 39 | propertyFor: "sell", |
| 40 | description: "Exceptional waterfront property with direct water access. Ideal for hospitality or retail ventures. Breathtaking sunset views and ample parking.", |
| 41 | locality: "Harbor District", |
| 42 | address: "789 Waterfront Drive", |
| 43 | price: 2100000, |
| 44 | priceEth: 1050, |
| 45 | roiPercentage: 7.5, |
| 46 | totalTokens: 210000, |
| 47 | availableTokens: 115500, |
| 48 | minInvestment: 10, |
| 49 | images: ["https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800&q=80"], |
| 50 | }, |
| 51 | { |
| 52 | title: "Historic Victorian Estate", |
| 53 | propertyFor: "sell", |
| 54 | description: "Beautifully restored Victorian mansion with original architectural details. Features 6 bedrooms, 4 bathrooms, and a sprawling garden. Perfect blend of historic charm and modern comfort.", |
| 55 | locality: "Heritage Hills", |
| 56 | address: "321 Oak Avenue", |
| 57 | price: 1750000, |
| 58 | priceEth: 875, |
| 59 | roiPercentage: 5.9, |
| 60 | totalTokens: 175000, |
| 61 | availableTokens: 52500, |
| 62 | minInvestment: 10, |
| 63 | images: ["https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&q=80"], |
| 64 | }, |
| 65 | { |
| 66 | title: "Beachfront Vacation Rental", |
| 67 | propertyFor: "rent", |
| 68 | description: "Stunning beachfront property generating consistent rental income. Fully furnished with luxury amenities. Strong booking history and established vacation rental business.", |
| 69 | locality: "Coastal Paradise", |
| 70 | address: "555 Ocean View Lane", |
| 71 | price: 950000, |
| 72 | priceEth: 475, |
| 73 | roiPercentage: 8.4, |
| 74 | totalTokens: 95000, |
| 75 | availableTokens: 28500, |
| 76 | minInvestment: 10, |
| 77 | images: ["https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?w=800&q=80"], |
| 78 | }, |
| 79 | ]; |
| 80 | |
| 81 | async function resetDatabase() { |
| 82 | console.log("=== RESETTING DATABASE ===\n"); |
| 83 | |
| 84 | try { |
| 85 | console.log("Dropping database..."); |
| 86 | execSync("dbmate drop", { stdio: "inherit" }); |
| 87 | } catch (e) { |
| 88 | |
| 89 | console.log("(Database didn't exist, continuing...)"); |
| 90 | } |
| 91 | |
| 92 | console.log("Running migrations..."); |
| 93 | execSync("dbmate up", { stdio: "inherit" }); |
| 94 | |
| 95 | console.log("Database reset complete.\n"); |
| 96 | } |
| 97 | |
| 98 | async function seedDatabase(wallets) { |
| 99 | console.log("=== SEEDING DATABASE ===\n"); |
| 100 | |
| 101 | const pool = new Pool({ connectionString: process.env.DATABASE_URL }); |
| 102 | |
| 103 | try { |
| 104 | |
| 105 | const stateResult = await pool.query( |
| 106 | "INSERT INTO states (name) VALUES ('California'), ('Texas'), ('Washington') RETURNING id, name" |
| 107 | ); |
| 108 | const states = stateResult.rows; |
| 109 | console.log(`Created ${states.length} states`); |
| 110 | |
| 111 | |
| 112 | const cityResult = await pool.query(` |
| 113 | INSERT INTO cities (name, state_id) VALUES |
| 114 | ('Los Angeles', $1), ('San Francisco', $1), ('Beverly Hills', $1), |
| 115 | ('Austin', $2), ('Houston', $2), |
| 116 | ('Seattle', $3) |
| 117 | RETURNING id, name, state_id |
| 118 | `, [states[0].id, states[1].id, states[2].id]); |
| 119 | const cities = cityResult.rows; |
| 120 | console.log(`Created ${cities.length} cities`); |
| 121 | |
| 122 | |
| 123 | const typeResult = await pool.query(` |
| 124 | INSERT INTO property_types (title, type) VALUES |
| 125 | ('Apartment', 'residential'), |
| 126 | ('House', 'residential'), |
| 127 | ('Commercial Building', 'commercial'), |
| 128 | ('Office Space', 'commercial'), |
| 129 | ('Farm', 'agricultural') |
| 130 | RETURNING id, title |
| 131 | `); |
| 132 | const propertyTypes = typeResult.rows; |
| 133 | console.log(`Created ${propertyTypes.length} property types`); |
| 134 | |
| 135 | |
| 136 | const [deployer, realtor, seller, buyer] = wallets; |
| 137 | |
| 138 | const userResult = await pool.query(` |
| 139 | INSERT INTO users (fname, lname, email, phone_no, password, wallet_address, is_admin) VALUES |
| 140 | ('Admin', 'User', 'admin@goldenprop.com', '555-0001', '$2b$10$test', $1, true), |
| 141 | ('Rachel', 'Realtor', 'realtor@goldenprop.com', '555-0002', '$2b$10$test', $2, false), |
| 142 | ('Sam', 'Seller', 'seller@goldenprop.com', '555-0003', '$2b$10$test', $3, false), |
| 143 | ('Bob', 'Buyer', 'buyer@goldenprop.com', '555-0004', '$2b$10$test', $4, false) |
| 144 | RETURNING id, fname, lname, wallet_address |
| 145 | `, [ |
| 146 | deployer.address.toLowerCase(), |
| 147 | realtor.address.toLowerCase(), |
| 148 | seller.address.toLowerCase(), |
| 149 | buyer.address.toLowerCase() |
| 150 | ]); |
| 151 | const users = userResult.rows; |
| 152 | console.log(`Created ${users.length} users`); |
| 153 | |
| 154 | console.table(users.map(u => ({ |
| 155 | name: `${u.fname} ${u.lname}`, |
| 156 | wallet: u.wallet_address |
| 157 | }))); |
| 158 | |
| 159 | |
| 160 | const propertyIds = []; |
| 161 | for (let i = 0; i < SAMPLE_PROPERTIES.length; i++) { |
| 162 | const p = SAMPLE_PROPERTIES[i]; |
| 163 | const city = cities[i % cities.length]; |
| 164 | const cityId = city.id; |
| 165 | const stateId = city.state_id; |
| 166 | const typeId = propertyTypes[i % propertyTypes.length].id; |
| 167 | const slug = p.title.toLowerCase().replace(/\s+/g, '-'); |
| 168 | |
| 169 | const result = await pool.query(` |
| 170 | INSERT INTO properties ( |
| 171 | title, property_for, description, type_id, state_id, city_id, |
| 172 | locality, length, breadth, address, email, price, price_eth, |
| 173 | phone_no, pincode, user_id, slug, images, roi_percentage, |
| 174 | total_tokens, available_tokens, min_investment, status |
| 175 | ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, 'available') |
| 176 | RETURNING id, title |
| 177 | `, [ |
| 178 | p.title, p.propertyFor, p.description, typeId, stateId, cityId, |
| 179 | p.locality, 100, 100, p.address, 'contact@goldenprop.com', p.price, p.priceEth, |
| 180 | '555-0000', '90210', users[1].id, slug, p.images, p.roiPercentage, |
| 181 | p.totalTokens, p.availableTokens, p.minInvestment |
| 182 | ]); |
| 183 | |
| 184 | propertyIds.push(result.rows[0].id); |
| 185 | console.log(`Created property: ${result.rows[0].title}`); |
| 186 | } |
| 187 | |
| 188 | await pool.end(); |
| 189 | return { users, propertyIds }; |
| 190 | |
| 191 | } catch (error) { |
| 192 | await pool.end(); |
| 193 | throw error; |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | async function deployContracts(wallets, propertyIds) { |
| 198 | console.log("\n=== DEPLOYING SMART CONTRACTS ===\n"); |
| 199 | |
| 200 | const [deployer, realtor, seller, buyer] = wallets; |
| 201 | |
| 202 | console.log("Deploying with account:", deployer.address); |
| 203 | console.log("Account balance:", hre.ethers.utils.formatEther(await deployer.getBalance()), "ETH"); |
| 204 | |
| 205 | |
| 206 | const Factory = await hre.ethers.getContractFactory("Factory", deployer); |
| 207 | const factory = await Factory.deploy(); |
| 208 | await factory.deployed(); |
| 209 | console.log("\nFactory deployed to:", factory.address); |
| 210 | |
| 211 | |
| 212 | const pool = new Pool({ connectionString: process.env.DATABASE_URL }); |
| 213 | const contractAddresses = []; |
| 214 | |
| 215 | for (let i = 0; i < SAMPLE_PROPERTIES.length; i++) { |
| 216 | const p = SAMPLE_PROPERTIES[i]; |
| 217 | const price = hre.ethers.utils.parseEther(p.priceEth.toString()); |
| 218 | const realtorFee = price.mul(5).div(100); |
| 219 | |
| 220 | const tx = await factory.connect(realtor).create( |
| 221 | p.address, |
| 222 | "90210", |
| 223 | p.locality, |
| 224 | realtorFee, |
| 225 | price, |
| 226 | seller.address, |
| 227 | buyer.address |
| 228 | ); |
| 229 | await tx.wait(); |
| 230 | |
| 231 | const contractAddress = await factory.getInstance(i); |
| 232 | contractAddresses.push(contractAddress); |
| 233 | |
| 234 | |
| 235 | await pool.query( |
| 236 | "UPDATE properties SET contract_address = $1 WHERE id = $2", |
| 237 | [contractAddress.toLowerCase(), propertyIds[i]] |
| 238 | ); |
| 239 | |
| 240 | |
| 241 | await pool.query(` |
| 242 | INSERT INTO blockchain_transactions |
| 243 | (property_id, contract_address, tx_type, from_address, status) |
| 244 | VALUES ($1, $2, 'deploy', $3, 'confirmed') |
| 245 | `, [propertyIds[i], contractAddress.toLowerCase(), realtor.address.toLowerCase()]); |
| 246 | |
| 247 | console.log(`Property ${i + 1}: ${p.title}`); |
| 248 | console.log(` Contract: ${contractAddress}`); |
| 249 | } |
| 250 | |
| 251 | await pool.end(); |
| 252 | return { factory, contractAddresses }; |
| 253 | } |
| 254 | |
| 255 | async function main() { |
| 256 | const mnemonic = "test test test test test test test test test test test junk"; |
| 257 | |
| 258 | |
| 259 | const roles = ["Deployer", "Realtor", "Seller", "Buyer"]; |
| 260 | const wallets = roles.map((_, i) => { |
| 261 | const path = `m/44'/60'/0'/0/${i}`; |
| 262 | return hre.ethers.Wallet.fromMnemonic(mnemonic, path).connect(hre.ethers.provider); |
| 263 | }); |
| 264 | |
| 265 | console.log("=== KEY DERIVATION ===\n"); |
| 266 | console.log(`Mnemonic: "${mnemonic}"\n`); |
| 267 | |
| 268 | console.table(wallets.map((w, i) => ({ |
| 269 | role: roles[i], |
| 270 | path: `m/44'/60'/0'/0/${i}`, |
| 271 | address: w.address, |
| 272 | privateKey: w.privateKey |
| 273 | }))); |
| 274 | |
| 275 | |
| 276 | await resetDatabase(); |
| 277 | const { users, propertyIds } = await seedDatabase(wallets); |
| 278 | |
| 279 | |
| 280 | const { factory, contractAddresses } = await deployContracts(wallets, propertyIds); |
| 281 | |
| 282 | console.log("\n=== DEPLOYMENT COMPLETE ===\n"); |
| 283 | console.log("Factory:", factory.address); |
| 284 | console.log("Properties with contracts:", contractAddresses.length); |
| 285 | |
| 286 | console.log("\n=== ENVIRONMENT VARIABLES ===\n"); |
| 287 | console.log(`REACT_APP_FACTORY_ADDRESS=${factory.address}`); |
| 288 | |
| 289 | console.log("\n=== METAMASK SETUP ===\n"); |
| 290 | console.log("Network Name: Hardhat Local"); |
| 291 | console.log("RPC URL: http://127.0.0.1:8545"); |
| 292 | console.log("Chain ID: 31337"); |
| 293 | console.log("Currency: ETH"); |
| 294 | console.log("\nImport accounts using private keys from table above"); |
| 295 | } |
| 296 | |
| 297 | main() |
| 298 | .then(() => process.exit(0)) |
| 299 | .catch((error) => { |
| 300 | console.error(error); |
| 301 | process.exit(1); |
| 302 | }); |