إنتقل إلى المحتوى الرئيسي

تطوير البلوكتشين و Web3 في Cursor: Solidity و Hardhat و ethers.js

تطوير البلوكتشين و Web3 في Cursor

يجمع تطوير البلوكتشين بين برمجة العقود الذكية وتكامل الواجهة الأمامية لإنشاء تطبيقات لامركزية (dApps). Cursor، وهو محرر IDE مدعوم بالذكاء الاصطناعي، يسرّع كل طبقة من طبقات تطوير Web3: كتابة وتدقيق عقود Solidity، وتكوين سلاسل أدوات Hardhat، وتوليد طبقات تفاعل ethers.js، وبناء واجهات أمامية بـ React مدعومة بـ wagmi، وتشغيل مجموعات اختبار شاملة. يغطي هذا الدليل عقود Solidity الذكية، وبيئة تطوير Hardhat، وتكامل ethers.js و wagmi، واستراتيجيات اختبار العقود، وتوليد العقود والتدقيق الأمني بمساعدة الذكاء الاصطناعي.

المتطلبات الأساسية​

قبل البدء، تأكد من أن لديك ما يلي:

  • Cursor (يُوصى بأحدث إصدار) كمحرر IDE الخاص بك
  • Node.js 18 أو أحدث
  • فهم أساسي لـ JavaScript و TypeScript و React
  • MetaMask أو محفظة Web3 أخرى مثبتة في متصفحك
  • ETH تجريبي على شبكة اختبار (Sepolia أو Holesky) لممارسة النشر
تلميح

منحنى تعلم تطوير البلوكتشين انحدار حاد. قبل كتابة عقدك الأول، اطلب من Cursor شرح آلة Ethereum الافتراضية (EVM)، وتكاليف الغاز، ودورة حياة المعاملة. فهم هذه الأساسيات يمنع الأخطاء المكلفة عند النشر على الشبكة الرئيسية.

عقود Solidity الذكية​

Solidity هي اللغة الأكثر استخدامًا لكتابة العقود الذكية على البلوكتشين المتوافق مع EVM. يساعدك Cursor في كتابة ومراجعة وتحسين كود Solidity مع اقتراحات مدعومة بالذكاء الاصطناعي وتحليل أمني.

كتابة عقد رمزية​

اطلب من Cursor توليد عقد رمزية ERC-20:

Create an ERC-20 token contract in Solidity using OpenZeppelin.
The token should be named "TutorialToken" with symbol "TUT",
18 decimals, and a total supply of 1,000,000 tokens minted to
the deployer. Include a public minting function with a per-transaction
cap and an emergency pause mechanism.

يقوم Cursor بتوليد:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

contract TutorialToken is ERC20, AccessControl, Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

// Maximum mint per transaction: 10,000 tokens (with 18 decimals)
uint256 public constant MAX_MINT_PER_TX = 10_000 * 10 ** 18;
// Total supply cap: 1,000,000 tokens
uint256 public constant TOTAL_SUPPLY_CAP = 1_000_000 * 10 ** 18;
uint256 public currentSupply;

event Minted(address indexed to, uint256 amount);
event EmergencyPaused(address indexed by);

constructor() ERC20("TutorialToken", "TUT") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);

uint256 initialSupply = 1_000_000 * 10 ** 18;
_mint(msg.sender, initialSupply);
currentSupply = initialSupply;
}

function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) whenNotPaused {
require(amount <= MAX_MINT_PER_TX, "Exceeds max mint per transaction");
require(currentSupply + amount <= TOTAL_SUPPLY_CAP, "Exceeds total supply cap");

_mint(to, amount);
currentSupply += amount;

emit Minted(to, amount);
}

function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
emit EmergencyPaused(msg.sender);
}

function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}

// Override required by Solidity for multiple inheritance
function _update(address from, address to, uint256 amount) internal override(ERC20, Pausable) {
Pausable._update(from, to, amount);
}

function supportsInterface(bytes4 interfaceId)
public view override(AccessControl) returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
تلميح

استخدم دائمًا مكتبات العقود المدققة من OpenZeppelin بدلاً من كتابة منطق التشفير أو معايير الرمزية من الصفر. اطلب من Cursor استيراد أحدث عقود OpenZeppelin والتحقق من أن الإصدار يتطابق مع إصدار مترجم Solidity الخاص بك. الإصدارات غير المتطابقة تسبب أخطاء الترجمة وقد تُدخل ثغرات أمنية دقيقة.

warning

تغيرت حماية تجاوز الأعداد الصحيحة في Solidity في الإصدار 0.8.0. في الإصدارات قبل 0.8.0، كانت العمليات الحسابية يمكن أن تتجاوز الحد بصمت، مما يؤدي إلى ثغرات كارثية. إذا كنت تراجع عقودًا قديمة، اطلب من Cursor التحقق من استخدام SafeMath وتمييز أي حساب يعتمد على سلوك ما قبل 0.8.0.

بيئة تطوير Hardhat​

Hardhat هي بيئة التطوير الأكثر شيوعًا لـ Ethereum. توفر محاكاة بلوكتشين محلية، وتجميع العقود، والاختبار، ونصوص النشر، وقابلية توسيع الإضافات.

إعداد المشروع​

اطلب من Cursor إنشاء هيكل مشروع Hardhat:

Create a Hardhat TypeScript project with the following configuration:
- Solidity compiler version 0.8.24
- Hardhat toolbox plugin for testing and deployment
- A deployment script for the TutorialToken contract
- Environment variables for RPC URL and private key
- TypeChain for TypeScript contract bindings

يقوم Cursor بتوليد هيكل المشروع:

my-dapp/
contracts/
TutorialToken.sol
scripts/
deploy.ts
test/
TutorialToken.test.ts
hardhat.config.ts
package.json
.env.example

تكوين Hardhat​

// hardhat.config.ts
import { HardhatUserConfig } from 'hardhat/config';
import '@nomicfoundation/hardhat-toolbox';
import * as dotenv from 'dotenv';

dotenv.config();

const config: HardhatUserConfig = {
solidity: {
version: '0.8.24',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
chainId: 31337,
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL || '',
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
mainnet: {
url: process.env.MAINNET_RPC_URL || '',
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
typechain: {
target: 'ethers-v6',
outDir: 'typechain-types',
},
gasReporter: {
enabled: true,
currency: 'USD',
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
token: 'ETH',
},
};

export default config;

نص النشر​

// scripts/deploy.ts
import { ethers } from 'hardhat';

async function main() {
const [deployer] = await ethers.getSigners();
console.log('Deploying contracts with account:', deployer.address);
console.log('Account balance:', ethers.formatEther(await ethers.provider.getBalance(deployer.address)), 'ETH');

const TutorialToken = await ethers.getContractFactory('TutorialToken');
const token = await TutorialToken.deploy();
await token.waitForDeployment();

const tokenAddress = await token.getAddress();
console.log('TutorialToken deployed to:', tokenAddress);

// Verify the deployment
const name = await token.name();
const symbol = await token.symbol();
const totalSupply = await token.totalSupply();
console.log('Name:', name);
console.log('Symbol:', symbol);
console.log('Total Supply:', ethers.formatEther(totalSupply));
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
تلميح

اطلب من Cursor توليد نصوص نشر لشبكات متعددة مع التحقق التلقائي من العقود على Etherscan. أمر مثل "Create a deploy script that detects the network, deploys the contract, and calls verify on Etherscan if not on localhost" يوفر ساعات من عمل التحقق اليدوي.

اختبار العقود​

اختبار العقود الذكية أمر بالغ الأهمية لأن العقود المنشورة غير قابلة للتغيير. يقوم Cursor بتوليد مجموعات اختبار شاملة تغطي المسارات الصحيحة وحالات الحدود والسيناريوهات الأمنية.

كتابة الاختبارات باستخدام Hardhat و Chai​

// test/TutorialToken.test.ts
import { expect } from 'chai';
import { ethers, network } from 'hardhat';
import { TutorialToken } from '../typechain-types';
import { SignerWithAddress } from '@nomicfoundation/hardhat-network-signers';

describe('TutorialToken', () => {
let token: TutorialToken;
let owner: SignerWithAddress;
let minter: SignerWithAddress;
let alice: SignerWithAddress;
let bob: SignerWithAddress;

const MINTER_ROLE = ethers.keccak256(ethers.toUtf8Bytes('MINTER_ROLE'));
const MAX_MINT = ethers.parseEther('10000');
const TOTAL_CAP = ethers.parseEther('1000000');

beforeEach(async () => {
[owner, minter, alice, bob] = await ethers.getSigners();

const TutorialToken = await ethers.getContractFactory('TutorialToken');
token = await TutorialToken.deploy();
await token.waitForDeployment();
});

describe('Deployment', () => {
it('sets the correct name and symbol', async () => {
expect(await token.name()).to.equal('TutorialToken');
expect(await token.symbol()).to.equal('TUT');
});

it('mints the total supply to the deployer', async () => {
const balance = await token.balanceOf(owner.address);
expect(balance).to.equal(TOTAL_CAP);
});

it('grants DEFAULT_ADMIN_ROLE and MINTER_ROLE to deployer', async () => {
expect(await token.hasRole(ethers.ZeroHash, owner.address)).to.be.true;
expect(await token.hasRole(MINTER_ROLE, owner.address)).to.be.true;
});
});

describe('Minting', () => {
beforeEach(async () => {
await token.grantRole(MINTER_ROLE, minter.address);
});

it('allows minter to mint within per-transaction cap', async () => {
const mintAmount = ethers.parseEther('5000');
await token.connect(minter).mint(alice.address, mintAmount);
expect(await token.balanceOf(alice.address)).to.equal(mintAmount);
});

it('reverts when minting exceeds per-transaction cap', async () => {
const mintAmount = ethers.parseEther('10001');
await expect(
token.connect(minter).mint(alice.address, mintAmount)
).to.be.revertedWith('Exceeds max mint per transaction');
});

it('reverts when non-minter tries to mint', async () => {
await expect(
token.connect(alice).mint(bob.address, ethers.parseEther('100'))
).to.be.revertedWithCustomError(token, 'AccessControlUnauthorizedAccount');
});

it('reverts when minting exceeds total supply cap', async () => {
// Already minted 1,000,000 on deployment, so any additional mint should fail
await expect(
token.connect(minter).mint(alice.address, ethers.parseEther('1'))
).to.be.revertedWith('Exceeds total supply cap');
});
});

describe('Pause Mechanism', () => {
it('allows admin to pause and unpause', async () => {
await token.pause();
expect(await token.paused()).to.be.true;

await token.unpause();
expect(await token.paused()).to.be.false;
});

it('reverts minting when paused', async () => {
await token.grantRole(MINTER_ROLE, minter.address);
await token.pause();

await expect(
token.connect(minter).mint(alice.address, ethers.parseEther('100'))
).to.be.revertedWithCustomError(token, 'EnforcedPause');
});

it('reverts when non-admin tries to pause', async () => {
await expect(token.connect(alice).pause())
.to.be.revertedWithCustomError(token, 'AccessControlUnauthorizedAccount');
});
});

describe('Transfers', () => {
it('allows token transfers and reverts when paused', async () => {
const amount = ethers.parseEther('100');
await token.transfer(alice.address, amount);
expect(await token.balanceOf(alice.address)).to.equal(amount);

// Now pause and verify transfer is blocked
await token.pause();
await expect(token.transfer(alice.address, amount))
.to.be.revertedWithCustomError(token, 'EnforcedPause');
});
});
});
تلميح

اطلب من Cursor توليد اختبارات الضبابية (fuzzing) التي تقوم بعشوائية المدخلات. أمر مثل "Create a fuzz test that tries minting with random amounts between 0 and 100,000 to find edge cases in the per-transaction cap and total supply cap logic" يمكن أن يكشف أخطاء تجاوز الحد وشروط الحدود التي تفوتها الاختبارات ذات القيم الثابتة.

warning

لا تنشر عقدًا أبداً بدون تغطية اختبار كاملة. العقود غير القابلة للتغيير لا يمكن إصلاحها بعد النشر. حالة حد واحدة تفوتة يمكن أن تؤدي إلى فقدان دائم للأموال. شغّل مجموعة الاختبار الكاملة واطلب أيضًا من Cursor تدقيق العقد لأنماط الثغرات الشائعة قبل أي نشر على الشبكة الرئيسية.

تكامل ethers.js​

ethers.js هي مكتبة JavaScript الأكثر شيوعًا للتفاعل مع Ethereum. يقوم Cursor بتوليد طبقات تفاعل آمنة من حيث النوع تغلف ABI العقود.

طبقة تفاعل العقد​

// src/lib/token.ts
import { ethers, Contract, formatEther, parseEther } from 'ethers';
import type { TutorialToken } from '../typechain-types';

export interface TokenInfo {
name: string;
symbol: string;
decimals: number;
totalSupply: string;
}

export class TokenService {
private contract: TutorialToken;
private provider: ethers.Provider;
private signer?: ethers.Signer;

constructor(
address: string,
abi: ethers.InterfaceAbi,
provider: ethers.Provider,
signer?: ethers.Signer
) {
this.provider = provider;
this.signer = signer;
this.contract = (signer
? new Contract(address, abi, signer)
: new Contract(address, abi, provider)) as unknown as TutorialToken;
}

async getInfo(): Promise<TokenInfo> {
const [name, symbol, decimals, totalSupply] = await Promise.all([
this.contract.name(),
this.contract.symbol(),
this.contract.decimals(),
this.contract.totalSupply(),
]);

return {
name,
symbol,
decimals: Number(decimals),
totalSupply: formatEther(totalSupply),
};
}

async balanceOf(address: string): Promise<string> {
const balance = await this.contract.balanceOf(address);
return formatEther(balance);
}

async mint(to: string, amount: string): Promise<string> {
if (!this.signer) throw new Error('Signer required for minting');
const tx = await this.contract.mint(to, parseEther(amount));
await tx.wait();
return tx.hash;
}

async transfer(to: string, amount: string): Promise<string> {
if (!this.signer) throw new Error('Signer required for transfers');
const tx = await this.contract.transfer(to, parseEther(amount));
await tx.wait();
return tx.hash;
}

async isPaused(): Promise<boolean> {
return this.contract.paused();
}

async hasMinterRole(address: string): Promise<boolean> {
const MINTER_ROLE = ethers.keccak256(ethers.toUtf8Bytes('MINTER_ROLE'));
return this.contract.hasRole(MINTER_ROLE, address);
}
}

استخدام ethers.js مع محفظة المتصفح​

// src/lib/wallet.ts
import { BrowserProvider } from 'ethers';

let provider: BrowserProvider | null = null;

export async function getProvider(): Promise<BrowserProvider> {
if (!provider) {
if (!window.ethereum) {
throw new Error('No Web3 wallet found. Please install MetaMask.');
}
provider = new BrowserProvider(window.ethereum);
}
return provider;
}

export async function connectWallet(): Promise<string> {
const provider = await getProvider();
const accounts = await provider.send('eth_requestAccounts', []);
return accounts[0];
}

export async function getSigner() {
const provider = await getProvider();
return provider.getSigner();
}

export function onAccountsChanged(callback: (accounts: string[]) => void): void {
if (window.ethereum) {
window.ethereum.on('accountsChanged', callback);
}
}

export function onChainChanged(callback: (chainId: string) => void): void {
if (window.ethereum) {
window.ethereum.on('chainChanged', callback);
}
}
warning

تحقق دائمًا من أن المستخدم متصل بالشبكة المتوقعة قبل إرسال المعاملات. إرسال معاملة على الشبكة الرئيسية أثناء الاتصال بشبكة اختبار، أو العكس، يسبب أخطاء مربكة. تحقق من معرف السلسلة (chain ID) واطلب من المستخدم تبديل الشبكات إذا لم تتطابق.

بناء واجهة أمامية بـ wagmi​

wagmi هي مجموعة من React Hooks لـ Ethereum تبسط اتصال المحفظة وقراءات العقود وكتابات العقود. يقوم Cursor بتوليد مكونات React مدعومة بـ wagmi مع أمان أنواع كامل.

تكوين wagmi​

// src/lib/wagmi.ts
import { createConfig, http } from 'wagmi';
import { sepolia, mainnet } from 'wagmi/chains';
import { injected } from 'wagmi/connectors';

export const config = createConfig({
chains: [sepolia, mainnet],
connectors: [injected()],
transports: {
[sepolia.id]: http('https://rpc.sepolia.org'),
[mainnet.id]: http(),
},
});

// Contract address and ABI
export const TOKEN_ADDRESS = '0x...'; // Replace with your deployed address
export const TOKEN_ABI = [
// ABI generated by TypeChain or hardhat
] as const;

مكون لوحة الرمزية​

// src/components/TokenDashboard.tsx
import { useAccount, useReadContract, useWriteContract, useWatchContractEvent } from 'wagmi';
import { parseEther, formatEther } from 'viem';
import { TOKEN_ADDRESS, TOKEN_ABI } from '../lib/wagmi';
import { useState } from 'react';

export function TokenDashboard() {
const { address, isConnected } = useAccount();
const { writeContractAsync } = useWriteContract();
const [mintAmount, setMintAmount] = useState('100');
const [transferTo, setTransferTo] = useState('');
const [transferAmount, setTransferAmount] = useState('10');

// Batch-read contract state
const { data: name } = useReadContract({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'name' });
const { data: symbol } = useReadContract({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'symbol' });
const { data: balance } = useReadContract({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'balanceOf', args: [address] });
const { data: isPaused } = useReadContract({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'paused' });

// Listen for mint events
useWatchContractEvent({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, eventName: 'Minted', onLogs: (logs) => console.log('Mint:', logs) });

const handleMint = () => writeContractAsync({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'mint', args: [address, parseEther(mintAmount)] });
const handleTransfer = () => writeContractAsync({ address: TOKEN_ADDRESS, abi: TOKEN_ABI, functionName: 'transfer', args: [transferTo, parseEther(transferAmount)] });

if (!isConnected) {
return <div className="p-8 text-center text-gray-500">Connect your wallet to view your token balance.</div>;
}

return (
<div className="space-y-6 p-4">
<div className="rounded-lg bg-gray-100 p-4">
<h2 className="text-xl font-bold">{name} ({symbol})</h2>
<p className="mt-1 text-sm">Balance: {balance ? formatEther(balance as bigint) : '0'} {symbol}</p>
<p className="text-sm">Status: {isPaused ? 'Paused' : 'Active'}</p>
</div>

<div className="rounded-lg border p-4">
<h3 className="mb-2 font-semibold">Mint Tokens</h3>
<input type="number" value={mintAmount} onChange={(e) => setMintAmount(e.target.value)} className="mb-2 w-full rounded border px-3 py-2" disabled={isPaused} />
<button onClick={handleMint} disabled={isPaused} className="w-full rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50">Mint</button>
</div>

<div className="rounded-lg border p-4">
<h3 className="mb-2 font-semibold">Transfer Tokens</h3>
<input type="text" placeholder="Recipient address (0x...)" value={transferTo} onChange={(e) => setTransferTo(e.target.value)} className="mb-2 w-full rounded border px-3 py-2" />
<input type="number" placeholder="Amount" value={transferAmount} onChange={(e) => setTransferAmount(e.target.value)} className="mb-2 w-full rounded border px-3 py-2" disabled={isPaused} />
<button onClick={handleTransfer} disabled={isPaused} className="w-full rounded bg-green-600 px-4 py-2 text-white disabled:opacity-50">Transfer</button>
</div>
</div>
);
}
تلميح

اطلب من Cursor توليد wagmi Read hook لكل دالة عرض في عقده و Write hook لكل دالة تغيير الحالة. أمر مثل "Create custom React hooks wrapping wagmi useReadContract and useWriteContract for every function in my TutorialToken ABI" يمنحك طبقة تفاعل نظيفة وقابلة لإعادة الاستخدام.

توليد العقود والتدقيق الأمني بمساعدة الذكاء الاصطناعي​

يمكن لذكاء Cursor الاصطناعي توليد أنظمة عقود كاملة من وصف وتدقيق العقود الموجودة للبحث عن أنماط الثغرات الشائعة.

توليد نظام عقود كامل​

Create a decentralized marketplace contract system with:
1. An ERC-721 NFT contract for listing items
2. A marketplace contract that accepts ETH and the NFT
3. Royalty enforcement using ERC-2981
4. A 2.5% platform fee sent to a treasury address
5. Reentrancy protection on all withdrawal functions
6. Events for every state change
Use OpenZeppelin libraries and include full NatSpec comments.

التدقيق للبحث عن الثغرات​

شغّل أمر التدقيق هذا ضد عقدك:

Audit my Solidity contract for the following vulnerability classes:
1. Reentrancy attacks (check for CEI pattern and ReentrancyGuard)
2. Integer overflow/underflow
3. Access control bypass
4. Front-running susceptibility
5. Denial of service via gas limit
6. Timestamp dependence
7. Delegate call injection
8. Unchecked external calls
Rate the severity of each finding and provide a fix.
warning

تدقيقات الأمان بمساعدة الذكاء الاصطناعي هي خط الدفاع الأول، وليست بديلاً عن التدقيقات المهنية. قبل نشر أي عقد يتعامل مع قيمة حقيقية، اطلب تدقيقًا رسميًا من شركة ذات سمعة طيبة. يمكن للذكاء الاصطناعي أن يفوّت أنماط ثغرات دقيقة يلتقطها المدققون ذوو الخبرة.

الخلاصة​

يجمع تطوير البلوكتشين و Web3 في Cursor بين قوة توليد الكود بمساعدة الذكاء الاصطناعي وصرامة هندسة العقود الذكية. باتباع أنماط هذا الدليل، عقود مبنية على OpenZeppelin، ومجموعات اختبار Hardhat شاملة، وطبقات تفاعل ethers.js آمنة من حيث النوع، وواجهات أمامية بـ React مدعومة بـ wagmi، وتدقيق العقود بمحرك الذكاء الاصطناعي، يمكنك بناء وإطلاق تطبيقات لامركزية بثقة. اختبر دائمًا بشكل شامل، ودقّق عن الثغرات قبل النشر على الشبكة الرئيسية، ولا تنشر أبدًا عقودًا لا تفهمها بالكامل.