sequenceDiagram
用户 ->> 前端: 点击创建投票/投票/查看结果
前端 ->> AnchorProvider: 请求签名交易
AnchorProvider ->> 钱包: 弹出签名确认
钱包 -->> 用户: 用户确认
钱包 -->> AnchorProvider: 签名完成
AnchorProvider ->> 程序: 发送指令至 Solana devnet
程序 -->> 程序: 更新合约状态/投票数据
程序 -->> AnchorProvider: 返回确认交易ID
前端 ->> 区块浏览器: 轮询 / 订阅确认状态
前端 ->> 前端: 更新 UI
17.4.1 Anchor Provider 配置
typescript
// hooks/useVoteProgram.ts
import { useEffect, useState } from 'react';
import { Connection, PublicKey, clusterApiUrl } from '@solana/web3.js';
import { Program, AnchorProvider, web3 } from '@coral-xyz/anchor';
import { Vote, IDL } from '../types/vote'; // 自动生成的 IDL
const PROGRAM_ID = new PublicKey("Fg6...your_program_id...");
const connection = new Connection(clusterApiUrl('devnet'));
export const useVoteProgram = (wallet: any) => {
const [program, setProgram] = useState<Program<Vote>>();
useEffect(() => {
if (!wallet) return;
const provider = new AnchorProvider(
connection,
wallet, // 需适配以 Phantom 签名器
{ commitment: 'confirmed' }
);
const program = new Program<Vote>(IDL, PROGRAM_ID, provider);
setProgram(program);
}, [wallet]);
return program;
};
// 适配 Phantom 为 Anchor 钱包适配器
const getPhantomWallet = () => {
const phantom = (window as any).phantom?.solana;
return {
publicKey: phantom?.publicKey ? new PublicKey(phantom.publicKey.toString()) : null,
signTransaction: async (tx: any) => {
const signed = await phantom.signTransaction(tx);
return signed;
},
signAllTransactions: async (txs: any[]) => {
return await phantom.signAllTransactions(txs);
},
};
};17.4.2 调用 create_poll
typescript
// components/CreatePollForm.tsx
import { useState } from 'react';
import { PublicKey, Keypair } from '@solana/web3.js';
export const CreatePollForm = ({ program, wallet }: { program: any; wallet: any }) => {
const [question, setQuestion] = useState('');
const [options, setOptions] = useState(['Option A', 'Option B']);
const [txId, setTxId] = useState('');
const createPoll = async () => {
// 生成新投票账户
const voteAccount = Keypair.generate();
const tx = await program.methods
.createPoll(
question,
options,
Math.floor(Date.now() / 1000) + 86400 // 24h 过期
)
.accounts({
voteAccount: voteAccount.publicKey,
creator: wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.signers([voteAccount])
.rpc(); // 自动发送并确认
setTxId(tx);
console.log("投票已创建:", tx);
};
return (
<form onSubmit={(e) => { e.preventDefault(); createPoll(); }}>
<h3>创建新投票</h3>
<input value={question} onChange={e => setQuestion(e.target.value)} placeholder="问题" />
{options.map((o, i) => (
<input key={i} value={o} onChange={e => {
const newOpts = [...options]; newOpts[i] = e.target.value; setOptions(newOpts);
}} />
))}
<button type="submit">创建投票</button>
{txId && <div>交易: <a href={`https://explorer.solana.com/tx/${txId}?cluster=devnet`} target="_blank">{txId.slice(0, 16)}...</a></div>}
</form>
);
};17.4.3 调用 cast_vote
typescript
// components/VoteCard.tsx
export const VoteCard = ({ program, wallet, voteAccount }: any) => {
const [voteData, setVoteData] = useState<any>(null);
// 加载投票数据
const loadPoll = async () => {
const data = await program.account.voteAccount.fetch(voteAccount);
setVoteData(data);
};
const castVote = async (optionIndex: number) => {
const tx = await program.methods
.castVote(optionIndex)
.accounts({
voteAccount,
voter: wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.rpc();
await loadPoll(); // 刷新数据
};
if (!voteData) { loadPoll(); return <div>加载中...</div>; }
return (
<div className="vote-card">
<h4>{voteData.question}</h4>
<p>状态: {voteData.isActive ? "进行中" : "已结束"}</p>
{voteData.options.map((opt: string, i: number) => (
<div key={i}>
<span>{opt}: {voteData.votes[i]} 票</span>
{voteData.isActive && !voteData.hasVoted.includes(wallet?.publicKey?.toString()) && (
<button onClick={() => castVote(i)}>投票</button>
)}
</div>
))}
</div>
);
};, 前往 → 17.5 完整 UI |*
评论
0评论加载中…