Skip to content

Commit

Permalink
Merge branch 'NoshonNetworks:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
jedstroke authored Dec 2, 2024
2 parents 04467a9 + d584872 commit 5758f96
Show file tree
Hide file tree
Showing 15 changed files with 2,104 additions and 2 deletions.
52 changes: 52 additions & 0 deletions app/server/src/db/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-- Create a dedicated schema for the land registry
CREATE SCHEMA IF NOT EXISTS land_registry;

-- Set the search path
SET search_path TO land_registry, public;

-- Land Events table
CREATE TABLE IF NOT EXISTS land_events (
id SERIAL PRIMARY KEY,
event_type VARCHAR(50) NOT NULL,
land_id VARCHAR(255) NOT NULL,
block_number BIGINT NOT NULL,
transaction_hash VARCHAR(66) NOT NULL,
timestamp TIMESTAMP NOT NULL,
data JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

-- Indexes for better query performance
CONSTRAINT unique_tx_event UNIQUE(transaction_hash, event_type, land_id)
);
CREATE INDEX IF NOT EXISTS idx_land_events_land_id ON land_events(land_id);
CREATE INDEX IF NOT EXISTS idx_land_events_timestamp ON land_events(timestamp);

-- Lands table for current state
CREATE TABLE IF NOT EXISTS lands (
land_id VARCHAR(255) PRIMARY KEY,
owner_address VARCHAR(42) NOT NULL,
location TEXT NOT NULL,
area NUMERIC NOT NULL,
land_use VARCHAR(100) NOT NULL,
document_hash VARCHAR(255) NOT NULL,
is_verified BOOLEAN DEFAULT FALSE,
last_transaction_timestamp TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_lands_owner ON lands(owner_address);

-- Function to update timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';

-- Trigger to automatically update timestamp
CREATE TRIGGER update_lands_updated_at
BEFORE UPDATE ON lands
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
11 changes: 9 additions & 2 deletions app/server/src/services/blockchainService.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ dotenv.config();

const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_RPC_URL);
const privateKey = process.env.PRIVATE_KEY;
const signer = new ethers.Wallet(privateKey, provider);
const contractAddress = process.env.LAND_REGISTRY_CONTRACT_ADDRESS;
if (!privateKey || !privateKey.startsWith('0x')) {
throw new Error('Invalid private key format in .env. Must start with 0x');
}

const wallet = new ethers.Wallet(privateKey);
const signer = wallet.connect(provider);
const contractAddress = process.env.LAND_REGISTRY_CONTRACT_ADDRESS;
if (!contractAddress || !contractAddress.startsWith('0x')) {
throw new Error('Invalid or missing LAND_REGISTRY_CONTRACT_ADDRESS in .env');
}
const contract = new ethers.Contract(contractAddress, LandRegistryABI, signer);

function generateLandId() {
Expand Down
143 changes: 143 additions & 0 deletions app/server/src/services/eventTrackerService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
const { Indexer } = require('@apibara/protocol');
const { Pool } = require('pg');
const path = require('path');
const fs = require('fs');

const pool = new Pool({
user: process.env.POSTGRES_USER,
host: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DB,
password: process.env.POSTGRES_PASSWORD,
port: process.env.POSTGRES_PORT,
});

async function setupDatabase() {
const client = await pool.connect();
try {
// Read and execute schema.sql
const schemaPath = path.join(__dirname, '../db/schema.sql');
const schema = fs.readFileSync(schemaPath, 'utf8');
await client.query(schema);
} finally {
client.release();
}
}

async function processEvent(client, event, blockNumber, timestamp) {
const { name: eventType, args, transactionHash } = event;

// Insert into land_events
await client.query(
`INSERT INTO land_registry.land_events (
event_type, land_id, block_number, transaction_hash,
timestamp, data
) VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT ON CONSTRAINT unique_tx_event DO NOTHING`,
[
eventType,
args[0], // landId
blockNumber,
transactionHash,
new Date(timestamp * 1000),
JSON.stringify(args)
]
);

// Update lands table based on event type
switch (eventType) {
case 'LandRegistered':
const [landId, owner, location, area, landUse, documentHash] = args;
await client.query(
`INSERT INTO land_registry.lands (
land_id, owner_address, location, area,
land_use, document_hash, last_transaction_timestamp
) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[landId, owner, location, area.toString(), landUse, documentHash, new Date(timestamp * 1000)]
);
break;

case 'LandVerified':
const [verifiedLandId] = args;
await client.query(
`UPDATE land_registry.lands
SET is_verified = true,
last_transaction_timestamp = $2
WHERE land_id = $1`,
[verifiedLandId, new Date(timestamp * 1000)]
);
break;

case 'LandTransferred':
const [transferredLandId, , newOwner] = args;
await client.query(
`UPDATE land_registry.lands
SET owner_address = $2,
last_transaction_timestamp = $3
WHERE land_id = $1`,
[transferredLandId, newOwner, new Date(timestamp * 1000)]
);
break;
}
}

async function startEventTracking(contractAddress, startBlock) {
const indexer = new Indexer({
stream_url: process.env.APIBARA_URL,
network: process.env.ETHEREUM_NETWORK,
starting_block: startBlock
});

indexer.addContract({
address: contractAddress,
events: [
'LandRegistered(string,address,string,uint256,string,string)',
'LandVerified(string)',
'LandTransferred(string,address,address)'
]
});

indexer.on('data', async (block) => {
const client = await pool.connect();
try {
await client.query('BEGIN');

for (const event of block.events) {
await processEvent(client, event, block.number, block.timestamp);
}

await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
console.error('Error processing events:', error);
} finally {
client.release();
}
});

indexer.start();
console.log('Event tracking started');
}

// Helper functions for querying the database
async function getLandCurrentState(landId) {
const result = await pool.query(
'SELECT * FROM land_registry.lands WHERE land_id = $1',
[landId]
);
return result.rows[0];
}

async function getLandHistory(landId) {
const result = await pool.query(
'SELECT * FROM land_registry.land_events WHERE land_id = $1 ORDER BY block_number ASC',
[landId]
);
return result.rows;
}

module.exports = {
setupDatabase,
startEventTracking,
getLandCurrentState,
getLandHistory
};
Loading

0 comments on commit 5758f96

Please sign in to comment.