This guide explains how to integrate PropChain smart contracts with frontend applications and other blockchain services.
- Node.js 16+ for frontend development
- Polkadot.js API for blockchain interaction
- Basic understanding of Web3 concepts
npm install @polkadot/api @polkadot/api-contract @polkadot/extension-dappimport { ApiPromise, WsProvider } from '@polkadot/api';
const connect = async () => {
const wsProvider = new WsProvider('ws://localhost:9944');
const api = await ApiPromise.create({ provider: wsProvider });
return api;
};import { ContractPromise } from '@polkadot/api-contract';
const loadContract = async (api, contractAddress, abi) => {
const contract = new ContractPromise(api, abi, contractAddress);
return contract;
};const registerProperty = async (contract, metadata) => {
const { gasRequired } = await contract.query.registerProperty(
account.address,
{ gasLimit: -1 },
metadata
);
const tx = contract.tx.registerProperty(
{ gasLimit: gasRequired },
metadata
);
await tx.signAndSend(account);
};const getProperty = async (contract, propertyId) => {
const { result, output } = await contract.query.get_property(
account.address,
{ gasLimit: -1 },
propertyId
);
if (result.isOk) {
return output.toHuman();
}
return null;
};import React, { useState, useEffect } from 'react';
import { useSubstrate } from './substrate-lib';
const PropertyRegistry = () => {
const { api, account } = useSubstrate();
const [contract, setContract] = useState(null);
const [properties, setProperties] = useState([]);
useEffect(() => {
if (api && account) {
loadContract();
}
}, [api, account]);
const loadContract = async () => {
// Load contract ABI and address
const contract = await loadContract(api, contractAddress, abi);
setContract(contract);
};
const handleRegisterProperty = async (metadata) => {
await registerProperty(contract, metadata);
// Refresh properties list
loadProperties();
};
return (
<div>
<h2>Property Registry</h2>
<PropertyForm onSubmit={handleRegisterProperty} />
<PropertyList properties={properties} />
</div>
);
};const handleContractError = (error) => {
if (error.message.includes('PropertyNotFound')) {
return 'Property not found';
}
if (error.message.includes('Unauthorized')) {
return 'Not authorized to perform this action';
}
if (error.message.includes('InsufficientBalance')) {
return 'Insufficient balance for this transaction';
}
return 'An unexpected error occurred';
};const sendTransaction = async (tx) => {
try {
const hash = await tx.signAndSend(account, ({ status }) => {
if (status.isInBlock) {
console.log(`Transaction included in block: ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Transaction finalized: ${status.asFinalized}`);
}
});
return hash;
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
};import { render, screen, fireEvent } from '@testing-library/react';
import { PropertyRegistry } from './PropertyRegistry';
describe('PropertyRegistry', () => {
test('registers property successfully', async () => {
const mockContract = {
tx: {
registerProperty: jest.fn().mockReturnValue({
signAndSend: jest.fn().mockResolvedValue('hash')
})
}
};
render(<PropertyRegistry contract={mockContract} />);
fireEvent.click(screen.getByText('Register Property'));
expect(mockContract.tx.registerProperty).toHaveBeenCalled();
});
});describe('Contract Integration', () => {
let api, contract;
beforeAll(async () => {
api = await connect();
contract = await loadContract(api, testContractAddress, abi);
});
test('property registration flow', async () => {
const metadata = {
location: 'Test Location',
size: 1000,
valuation: 100000
};
const result = await registerProperty(contract, metadata);
expect(result).toBeDefined();
});
});const batchQueryProperties = async (contract, propertyIds) => {
const queries = propertyIds.map(id =>
contract.query.get_property(account.address, { gasLimit: -1 }, id)
);
const results = await Promise.all(queries);
return results.map(result => result.output.toHuman());
};const useContractCache = (contract, method, ...args) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const cacheKey = `${method}-${JSON.stringify(args)}`;
const cached = localStorage.getItem(cacheKey);
if (cached) {
setData(JSON.parse(cached));
setLoading(false);
} else {
contract.query[method](account.address, { gasLimit: -1 }, ...args)
.then(({ output }) => {
const result = output.toHuman();
localStorage.setItem(cacheKey, JSON.stringify(result));
setData(result);
})
.finally(() => setLoading(false));
}
}, [contract, method, args]);
return { data, loading };
};- Validate Inputs: Always validate user inputs before sending to contract
- Secure Storage: Use secure storage for private keys and sensitive data
- Rate Limiting: Implement rate limiting for contract interactions
- Error Handling: Never expose sensitive error information to users
- Transaction Confirmation: Always confirm important transactions with users
- Connection Failed: Check if the blockchain node is running
- Contract Not Found: Verify contract address and ABI
- Gas Limit Exceeded: Increase gas limit for complex operations
- Account Locked: Ensure account is unlocked in Polkadot.js extension
// Enable debug logging
import { logger } from '@polkadot/util';
logger.setLevel('debug');
// Monitor contract events
contract.query.getEvents(account.address, { gasLimit: -1 })
.then(({ output }) => {
console.log('Contract events:', output.toHuman());
});