Bài 7: Bắt tay xây dựng DApp đầu tiên trên Solana (Phần 2)
Bài viết này sẽ tổng hợp các kiến thức cần có và hướng dẫn để bạn bắt đầu xây dựng DApp trên Solana. Tham khảo ngay!

Trong Workshop 1, chúng ta đã hoàn thành Solana program cho hệ thống bỏ phiếu có trọng số. Trong bài này, chúng ta sẽ cùng nhau phát triển giao diện đơn giản cũng như kết nối giao diện đó với Solana program để thực hiện việc giao tiếp cho hệ thống.
Building the React App
Trong thư mục gốc của dự án Anchor, tạo một React app mới để ghi đè lên thư mục app hiện có:
npx create-react-app app --template typescript
cd app
Tham khảo bài số 2 và bài số 3 để cài đặt ứng dụng cũng như các dependencies.
Kết nối đến ví
Ở các bài trước, chúng ta đã tìm hiểu về Goki, cách kết nối ví và lưu thông tin ví vào Redux để có thể sử dụng, quản lý một cách hiệu quả. Tương tự như vậy, chúng ta ứng dụng lại vào chương trình và tạo kết nối đến ví người dùng.
// view/app/index.tsx
import { useCallback, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { useWalletKit, useSolana, useConnectedWallet } from '@gokiprotocol/walletkit'
import { Button, Col, Layout, Row, Typography } from 'antd'
import ListCandidates from 'view/listCandidates'
import CandidateCreate from 'view/candidateCreate'
import { setWalletInfo, WalletState } from 'store/wallet.reducer'
import { AppDispatch } from 'store'
const { Header, Content } = Layout
function App() {
const dispatch = useDispatch<AppDispatch>()
const wallet = useConnectedWallet()
const { connect } = useWalletKit()
const { providerMut } = useSolana()
const fetchWalletInfo = useCallback(async () => {
if (!wallet || !providerMut) return
// TODO: fetch SOL balance
const lamports = await providerMut.connection.getBalance(wallet.publicKey)
let walletInfo: WalletState = {
walletAddress: wallet.publicKey.toBase58(),
balance: lamports,
}
dispatch(setWalletInfo(walletInfo))
}, [providerMut, wallet])
useEffect(() => {
fetchWalletInfo()
}, [fetchWalletInfo])
return (
<Layout style={{ height: '100vh' }}>
<Header>
<Col span={24}>
{wallet ? (
<Col span={24} style={{ color: 'white' }}>
{wallet.publicKey.toBase58()}
</Col>
) : (
<Button type="primary" style={{ borderRadius: 40 }} onClick={connect}>
Connect Wallet
</Button>
)}
</Col>
</Header>
<Content style={{ padding: 40 }}>
<Row gutter={[24, 24]}>
<Col span={24}>
<Row gutter={[24, 24]}>
<Col flex="auto">
<Typography.Title>List Candidates</Typography.Title>
</Col>
<Col>
<CandidateCreate />
</Col>
</Row>
</Col>
<Col span={24}>
<ListCandidates />
</Col>
</Row>
</Content>
</Layout>
)
}
export default App
Giao diện bỏ phiếu
Danh sách Candidate
Giao diện List candidates thể hiện cách thông tin: candidate, start_date, end_date, …
Hàm khởi tạo ứng viên (New candidate)
Giao diện khởi tạo candidate cần các thông tin cơ bản như địa chỉ token để bỏ phiếu, ngày bắt đầu, ngày kết thúc bỏ phiếu.
Hàm bỏ phiếu (Vote)
Kết nối đến Solana Program (Smart Contract)
Sao chép tệp target/types/l6.ts vào thư mục app/config và đặt tên idl.ts. Tệp này giúp định nghĩa các giao diện (interface) của Solana program. Nó giúp các chương trình off-chain, có thể là frontend hoặc backend với các ngôn ngữ khác nhau, biết các để giao tiếp với chương trình chạy on-chain. Nếu các bạn đã quen thuộc với Ethereum thì tệp IDL này có ý nghĩa tương đương với ABI.
Tiếp đến chúng ta phải tạo file config để định nghĩa kết nối. Cụ thể là mạng devnet.
// app/src/config/index.ts
import * as anchor from '@project-serum/anchor'
import { clusterApiUrl } from '@solana/web3.js'
import { IDL } from './idl'
export const DEFAULT_COMMITMENT = 'confirmed'
export const DEFAULT_CLUSTER = 'devnet'
export const PROGRAM_ADDRESS = new anchor.web3.PublicKey(
'HCoUastFpW7wB9Ue4o4YHy27VTuiJEo7h9hKmhnXDQhD',
)
export const NODE_URL = clusterApiUrl(DEFAULT_CLUSTER)
export type CandidateData = {
address: string
mint: string
amount: number
startTime: number
endTime: number
}
// Function support
export const getProvider = (wallet: any) => {
const connection = new anchor.web3.Connection(NODE_URL, DEFAULT_COMMITMENT)
return new anchor.Provider(connection, wallet, {
preflightCommitment: DEFAULT_COMMITMENT,
})
}
export const getProgram = (wallet: any) => {
const provider = getProvider(wallet)
return new anchor.Program(IDL, PROGRAM_ADDRESS, provider)
}
Viết hàm gọi và truyền vào các tham số định nghĩa ở IDL để tạo ứng viên
await program.rpc.initializeCandidate(new anchor.BN(startTime), new anchor.BN(endTime), {
accounts: {
authority: wallet.publicKey,
candidate: candidate.publicKey,
treasurer,
mint: new anchor.web3.PublicKey(mintAddress),
candidateTokenAccount,
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [candidate],
})
Tạo hàm bỏ phiếu cho ứng viên
await program.rpc.vote(new anchor.BN(amount), {
accounts: {
authority: wallet.publicKey,
candidate: candidatePublicKey,
treasurer,
mint: candidateData.mint,
candidateTokenAccount,
ballot,
voterTokenAccount: walletTokenAccount,
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [],
})
Tạo hàm close vote
await program.rpc.close({
accounts: {
authority: wallet.publicKey,
candidate: candidatePublicKey,
treasurer,
mint: candidateData.mint,
candidateTokenAccount,
ballot,
voterTokenAccount: walletTokenAccount,
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [],
})
Tham khảo
- Example repository: https://github.com/tuphan-dn/evoting-system
- Blockchain là gì?: https://academy.sentre.io/#/blogs/what-is-blockchain?category=dev
- Thiết kế giao diện DApp: https://academy.sentre.io/#/blogs/design-dapp-ui?category=dev
- Quản lý State của Dapp: https://academy.sentre.io/#/blogs/manage-dapp-state?category=dev
- Viết phần mềm Solana đầu tiên: https://academy.sentre.io/#/blogs/first-solana-program?category=dev
- PDA và chuẩn SPLT trong lập trình Solana: https://academy.sentre.io/#/blogs/pda-and-splt-on-solana?category=dev
- Xây dựng DApp đầu tiên trên Solana (phần 1): https://academy.sentre.io/#/blogs/dapp-on-solana-1?category=dev
Về Sentre Protocol
Sentre Protocol là nền tảng mở All-in-One hoạt động trên mạng lưới Solana, cung cấp DApp Store và giao thức mở giúp tích lũy thanh khoản. Với Sentre:
- Người dùng có thể cài đặt các DApp yêu thích của mình và trải nghiệm thế giới DeFi trên một nền tảng duy nhất;
- Lập trình viên và đối tác có thể phát hành DApp thông qua Sen Store, tận dụng tài nguyên có sẵn và tự do đóng góp cho nền tảng.