热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
本提示词专为区块链开发者设计,提供智能合约编程、Web3功能集成和加密货币操作管理的全方位技术指导。通过任务分步法和链式思维分析,将复杂的区块链开发任务拆解为可执行的步骤序列,涵盖从需求分析到代码实现的完整流程。提示词具备多场景适配能力,支持DeFi应用、NFT平台、去中心化交易所等主流区块链业务场景,提供结构化的技术方案和最佳实践建议,帮助开发者规避常见技术陷阱,提升开发效率和质量。
以下方案基于给定技术偏好(Solidity+Hardhat,React+Next.js,NestJS,PostgreSQL,IPFS,监控与日志)与目标平台(以太坊主网 + Layer2),面向“去中心化票务DApp”从0到1的全流程设计与实现指导。所附智能合约代码仅作示例参考,正式上线前务必经过专业审计与严格测试。
一、需求分析总结
用户注册/钱包绑定
门票NFT铸造
二级市场转售
订单与支付
活动方管理
风控与防刷
性能目标落地
合规与多链
二、技术架构设计
合约层
后端(NestJS)
前端(Next.js + React)
存储与内容分发
监控与日志
多链适配
三、核心代码示例(简化版,需审计后方可生产) A. TicketERC1155(签名铸造 + 转移管控 + 版税) 说明:使用OpenZeppelin库;关键路径避免重入;示例省略部分检查与事件细节。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
contract TicketERC1155 is ERC1155, AccessControl, ERC2981, EIP712, Pausable, ReentrancyGuard {
using ECDSA for bytes32;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant TRANSFER_GATE_ROLE = keccak256("TRANSFER_GATE_ROLE");
struct MintVoucher {
address buyer;
uint256 eventId;
uint256 ticketId; // 票种ID
uint256 amount;
uint256 priceWei; // 单价,0表示免费或已线下付
uint256 nonce;
uint256 startTime;
uint256 endTime;
uint256 maxPerWallet;
}
bytes32 public constant MINT_VOUCHER_TYPEHASH =
keccak256("MintVoucher(address buyer,uint256 eventId,uint256 ticketId,uint256 amount,uint256 priceWei,uint256 nonce,uint256 startTime,uint256 endTime,uint256 maxPerWallet)");
address public signer; // 后端签名者(热签名仅可签发凭证,不可挪用资金)
mapping(bytes32 => bool) public usedVouchers;
mapping(address => mapping(uint256 => uint256)) public purchased; // buyer => eventId => count
// 转移限制
uint256 public transferUnlockTime; // 解锁时间
mapping(address => bool) public allowedOperators; // 指定白名单市场在锁定期可转
// 事件与库存
mapping(uint256 => uint256) public maxSupply; // ticketId => cap
mapping(uint256 => uint256) public minted; // ticketId => minted
constructor(
string memory uri_,
address admin,
address signer_,
address royaltyReceiver,
uint96 royaltyBps
) ERC1155(uri_) EIP712("Ticket1155", "1") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_setDefaultRoyalty(royaltyReceiver, royaltyBps);
signer = signer_;
}
function setTransferUnlockTime(uint256 ts) external onlyRole(DEFAULT_ADMIN_ROLE) {
transferUnlockTime = ts;
}
function setAllowedOperator(address op, bool allowed) external onlyRole(TRANSFER_GATE_ROLE) {
allowedOperators[op] = allowed;
}
function setMaxSupply(uint256 ticketId, uint256 cap) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(minted[ticketId] <= cap, "cap < minted");
maxSupply[ticketId] = cap;
}
function hashVoucher(MintVoucher memory v) public view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
MINT_VOUCHER_TYPEHASH,
v.buyer, v.eventId, v.ticketId, v.amount, v.priceWei, v.nonce, v.startTime, v.endTime, v.maxPerWallet
)));
}
function verifyVoucher(MintVoucher memory v, bytes calldata sig) public view returns (bool) {
bytes32 digest = hashVoucher(v);
address recovered = ECDSA.recover(digest, sig);
return recovered == signer;
}
function mintWithVoucher(MintVoucher calldata v, bytes calldata sig)
external
payable
nonReentrant
whenNotPaused
{
require(block.timestamp >= v.startTime && block.timestamp <= v.endTime, "sale window");
require(v.buyer == msg.sender, "not buyer");
require(verifyVoucher(v, sig), "bad sig");
bytes32 key = keccak256(abi.encode(v.buyer, v.eventId, v.ticketId, v.nonce));
require(!usedVouchers[key], "voucher used");
usedVouchers[key] = true;
require(maxSupply[v.ticketId] == 0 || minted[v.ticketId] + v.amount <= maxSupply[v.ticketId], "sold out");
require(purchased[msg.sender][v.eventId] + v.amount <= v.maxPerWallet, "limit");
if (v.priceWei > 0) {
require(msg.value == v.priceWei * v.amount, "price");
// 收款:留在合约待结算,避免立即外流。由管理员提现到多签。
}
purchased[msg.sender][v.eventId] += v.amount;
minted[v.ticketId] += v.amount;
_mint(msg.sender, v.ticketId, v.amount, "");
}
// 转移限制:锁定期仅白名单运营者可转;解锁后自由
function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
internal override
{
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
if (from != address(0) && to != address(0)) { // 非铸造/销毁
if (block.timestamp < transferUnlockTime) {
require(allowedOperators[operator], "transfer locked");
}
}
}
// Admin withdraw
function withdraw(address payable to, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
(bool ok, ) = to.call{value: amount}("");
require(ok, "withdraw failed");
}
function supportsInterface(bytes4 iid) public view override(ERC1155, AccessControl, ERC2981) returns (bool) {
return super.supportsInterface(iid);
}
}
B. 固定价Marketplace(签名订单撮合、价格上限、版税与平台费) 说明:示例仅供结构参考,务必补齐安全检查(非cehcked:fee上限、代币白名单、重入位置等)与审计。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract FixedPriceMarket is EIP712, ReentrancyGuard, Ownable {
using ECDSA for bytes32;
using SafeERC20 for IERC20;
struct Order {
address maker;
address nft;
bool is1155;
uint256 tokenId;
uint256 amount; // ERC721固定1
address currency; // address(0)=ETH
uint256 price; // 总价
uint256 deadline;
uint256 nonce;
uint256 maxResaleBps; // 例如 11000 表示 <=1.1x
address eventPolicy; // 可选策略合约,返回面值与上限判断
}
bytes32 public constant ORDER_TYPEHASH =
keccak256("Order(address maker,address nft,bool is1155,uint256 tokenId,uint256 amount,address currency,uint256 price,uint256 deadline,uint256 nonce,uint256 maxResaleBps,address eventPolicy)");
mapping(address => mapping(uint256 => bool)) public cancelled; // maker => nonce => cancelled
mapping(address => mapping(uint256 => bool)) public filled; // maker => nonce => filled
uint96 public platformFeeBps; // 平台费
address public feeReceiver;
constructor(uint96 feeBps, address feeRecv) EIP712("TicketMarket", "1") {
platformFeeBps = feeBps;
feeReceiver = feeRecv;
}
function setPlatformFee(uint96 bps, address recv) external onlyOwner {
require(bps <= 1000, "fee too high"); // <=10%
platformFeeBps = bps;
feeReceiver = recv;
}
function hashOrder(Order memory o) public view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
ORDER_TYPEHASH,
o.maker, o.nft, o.is1155, o.tokenId, o.amount, o.currency, o.price, o.deadline, o.nonce, o.maxResaleBps, o.eventPolicy
)));
}
function cancel(uint256 nonce) external {
cancelled[msg.sender][nonce] = true;
}
function matchAsk(Order calldata ask, bytes calldata sig) external payable nonReentrant {
require(block.timestamp <= ask.deadline, "expired");
require(!cancelled[ask.maker][ask.nonce] && !filled[ask.maker][ask.nonce], "used");
require(_verify(ask, sig), "bad sig");
// 价格上限策略(示意):调用eventPolicy查询原始面值
if (ask.eventPolicy != address(0)) {
(bool ok, bytes memory data) = ask.eventPolicy.staticcall(
abi.encodeWithSignature("validateResale(address,uint256,uint256)", ask.nft, ask.tokenId, ask.price)
);
require(ok && abi.decode(data, (bool)), "resale limit");
}
// 收款处理
uint256 remaining = ask.price;
// 版税收取(若实现了ERC-2981)
(bool supports, bytes memory rdata) = ask.nft.staticcall(abi.encodeWithSelector(IERC2981.royaltyInfo.selector, ask.tokenId, ask.price));
if (supports && rdata.length >= 64) {
(address receiver, uint256 royalty) = abi.decode(rdata, (address, uint256));
if (royalty > 0) {
_payout(ask.currency, receiver, royalty);
remaining -= royalty;
}
}
// 平台费
uint256 fee = (ask.price * platformFeeBps) / 10000;
if (fee > 0) {
_payout(ask.currency, feeReceiver, fee);
remaining -= fee;
}
// 给卖家结算
_payout(ask.currency, ask.maker, remaining);
// 资产转移:要求卖家提前对本市场合约授权
if (ask.is1155) {
IERC1155(ask.nft).safeTransferFrom(ask.maker, msg.sender, ask.tokenId, ask.amount, "");
} else {
IERC721(ask.nft).safeTransferFrom(ask.maker, msg.sender, ask.tokenId);
}
filled[ask.maker][ask.nonce] = true;
}
function _payout(address currency, address to, uint256 amount) internal {
if (currency == address(0)) {
require(msg.value == amount || msg.value == 0, "eth mismatch");
(bool ok, ) = to.call{value: amount}("");
require(ok, "eth pay fail");
} else {
IERC20(currency).safeTransferFrom(msg.sender, to, amount); // 买家->收款方,需Permit2或预授权
}
}
function _verify(Order calldata o, bytes calldata sig) internal view returns (bool) {
address recovered = ECDSA.recover(hashOrder(o), sig);
return recovered == o.maker;
}
receive() external payable {}
}
C. Hardhat网络与部署脚本(多链)
// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "hardhat-deploy";
const config: HardhatUserConfig = {
solidity: "0.8.20",
namedAccounts: { deployer: { default: 0 } },
networks: {
hardhat: {},
base: { url: process.env.RPC_BASE!, accounts: [process.env.PRIVATE_KEY!] },
op: { url: process.env.RPC_OP!, accounts: [process.env.PRIVATE_KEY!] },
arb: { url: process.env.RPC_ARB!, accounts: [process.env.PRIVATE_KEY!] },
mainnet:{ url: process.env.RPC_MAIN!, accounts: [process.env.PRIVATE_KEY!] },
}
};
export default config;
// deploy/001_deploy_tickets.ts
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async ({ deployments, getNamedAccounts }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
const signer = process.env.MINTER_SIGNER!;
const royaltyRecv = process.env.ROYALTY_RECV!;
const royaltyBps = 500; // 5%
await deploy("TicketERC1155", {
from: deployer,
args: ["ipfs://{cid}/{id}.json", deployer, signer, royaltyRecv, royaltyBps],
log: true,
});
await deploy("FixedPriceMarket", {
from: deployer,
args: [250, process.env.FEE_RECV!], // 2.5%
log: true,
});
};
export default func;
D. NestJS:签名铸造凭证与Permit2示例
// mint.service.ts
import { Injectable } from "@nestjs/common";
import { ethers } from "ethers";
import { TypedDataDomain, TypedDataField } from "@ethersproject/abstract-signer";
@Injectable()
export class MintService {
private wallet = new ethers.Wallet(process.env.MINTER_SIGNER_PK!, new ethers.providers.JsonRpcProvider(process.env.RPC!));
domain: TypedDataDomain = {
name: "Ticket1155",
version: "1",
chainId: Number(process.env.CHAIN_ID),
verifyingContract: process.env.TICKET1155!,
};
types: Record<string, TypedDataField[]> = {
MintVoucher: [
{ name: "buyer", type: "address" },
{ name: "eventId", type: "uint256" },
{ name: "ticketId", type: "uint256" },
{ name: "amount", type: "uint256" },
{ name: "priceWei", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "startTime", type: "uint256" },
{ name: "endTime", type: "uint256" },
{ name: "maxPerWallet", type: "uint256" },
],
};
async createVoucher(dto: { buyer: string; eventId: number; ticketId: number; amount: number; priceWei: string; maxPerWallet: number; startTime: number; endTime: number }) {
const nonce = Date.now(); // 生产建议使用DB自增 + 唯一约束
const message = { ...dto, nonce };
const sig = await this.wallet._signTypedData(this.domain, this.types, message);
return { ...message, sig };
}
}
E. Next.js前端:创建订单并签名(wagmi/viem)
// useCreateOrder.ts
import { useAccount, useSignTypedData } from "wagmi";
export function useCreateOrder() {
const { address } = useAccount();
const { signTypedDataAsync } = useSignTypedData();
return async (order: any, domain: any, types: any) => {
if (!address) throw new Error("Connect wallet");
const signature = await signTypedDataAsync({ domain, types, primaryType: "Order", message: order });
return { order, signature };
};
}
F. IPFS元数据模板
{
"name": "Event X - Section A Ticket",
"description": "Admission ticket for Event X at Section A.",
"image": "ipfs://<poster-cid>",
"attributes": [
{ "trait_type": "EventId", "value": "123" },
{ "trait_type": "Section", "value": "A" },
{ "trait_type": "Seat", "value": "GA" }
],
"external_url": "https://yourdapp.xyz/event/123",
"valid_from": 1710000000,
"valid_to": 1710086400
}
四、安全注意事项
合约安全
后端安全
前端与用户安全
合规
五、部署运维指南
环境与节点
CI/CD
指标与告警
性能优化
运行手册
六、里程碑与实施路线
M0(第1-2周):需求与合规评估
M1(第3-5周):合约PoC与测试网
M2(第6-8周):后端与前端Alpha
M3(第9-10周):风控与支付增强
M4(第11-12周):安全审计与渗透测试
M5(第13周):试运营与灰度发布(L2)
M6(第14-16周):主网上线与规模化
七、最佳实践补充
备注与免责声明:以上代码仅为演示用,未经过审计;请在生产前完成全面审计与攻防测试,确保符合当地监管要求,并采用多签与时间锁保护关键资金与权限。
说明:以下为参考实现骨架,基于 OpenZeppelin Upgradeable 库,适用于测试网与内部验证。务必在投产前进行第三方审计与严格测试。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
// Chainlink Aggregator interface (simplified)
interface IAggregatorV3 {
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
contract StakingRewardsV1 is
Initializable,
UUPSUpgradeable,
AccessControlUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
// Roles
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
bytes32 public constant REWARD_DISTRIBUTOR_ROLE = keccak256("REWARD_DISTRIBUTOR_ROLE");
// Tokens
IERC20Upgradeable public stakingToken;
IERC20Upgradeable public rewardsToken;
// Global accounting
uint256 public totalStaked;
uint256 public rewardRate; // rewards per second
uint256 public rewardPerTokenStored; // accumulator scaled by 1e18
uint64 public lastUpdateTime; // packed small ints
uint64 public periodFinish;
// Governance parameters
uint64 public lockPeriod; // seconds
uint128 public minStakeAmount; // minimal stake per tx
// Oracle config
IAggregatorV3 public priceFeed;
uint64 public staleSeconds; // oracle freshness guard
int256 public oracleMinBound; // inclusive bound
int256 public oracleMaxBound; // inclusive bound
struct UserInfo {
uint128 balance; // staked amount
uint128 rewards; // accrued but not claimed
uint256 userRewardPerTokenPaid; // snapshot
uint64 lastStakeTime; // for lock
}
mapping(address => UserInfo) private users;
// Events
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardAdded(uint256 reward, uint256 newRate, uint256 periodFinish);
event ParametersUpdated(
uint64 lockPeriod,
uint128 minStakeAmount,
address priceFeed,
uint64 staleSeconds,
int256 minBound,
int256 maxBound
);
// Initializer (no constructor for upgradeable contracts)
function initialize(
address _stakingToken,
address _rewardsToken,
address admin,
address governor,
address pauser,
address upgrader
) public initializer {
require(_stakingToken != address(0) && _rewardsToken != address(0), "Zero token");
__AccessControl_init();
__Pausable_init();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
stakingToken = IERC20Upgradeable(_stakingToken);
rewardsToken = IERC20Upgradeable(_rewardsToken);
// Roles
_setupRole(DEFAULT_ADMIN_ROLE, admin);
_setupRole(GOVERNOR_ROLE, governor);
_setupRole(PAUSER_ROLE, pauser);
_setupRole(UPGRADER_ROLE, upgrader);
_setupRole(REWARD_DISTRIBUTOR_ROLE, governor);
lastUpdateTime = uint64(block.timestamp);
lockPeriod = 0;
minStakeAmount = 1;
}
// --- Modifiers/helpers ---
modifier updateReward(address account) {
(uint256 rpt, uint64 lastUpdate) = _rewardPerToken();
rewardPerTokenStored = rpt;
lastUpdateTime = lastUpdate;
if (account != address(0)) {
UserInfo storage u = users[account];
uint256 earnedAmt = _earned(account, rpt);
// cap to uint128 if reasonable; else store as uint256 in a second slot (here we keep uint128 for gas)
if (earnedAmt > type(uint128).max) {
// fallback: clamp; in production consider upgrading storage type to uint256
u.rewards = type(uint128).max;
} else {
u.rewards = uint128(earnedAmt);
}
u.userRewardPerTokenPaid = rpt;
}
_;
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
// --- Views ---
function rewardPerToken() external view returns (uint256) {
(uint256 rpt,) = _rewardPerToken();
return rpt;
}
function _rewardPerToken() internal view returns (uint256 rpt, uint64 lastUpdate) {
lastUpdate = uint64(block.timestamp);
uint64 applicableTime = lastUpdate;
if (periodFinish != 0 && applicableTime > periodFinish) {
applicableTime = periodFinish;
}
if (totalStaked == 0) {
return (rewardPerTokenStored, uint64(applicableTime));
}
uint256 delta = uint256(applicableTime) - uint256(lastUpdateTime);
// rpt accumulator scaled by 1e18
rpt = rewardPerTokenStored + (delta * rewardRate * 1e18) / totalStaked;
lastUpdate = uint64(applicableTime);
}
function earned(address account) external view returns (uint256) {
(uint256 rpt,) = _rewardPerToken();
return _earned(account, rpt);
}
function _earned(address account, uint256 rpt) internal view returns (uint256) {
UserInfo storage u = users[account];
uint256 paid = u.userRewardPerTokenPaid;
uint256 accrued = (uint256(u.balance) * (rpt - paid)) / 1e18;
return accrued + u.rewards;
}
// --- Core actions ---
function stake(uint256 amount)
external
nonReentrant
whenNotPaused
updateReward(msg.sender)
{
require(amount >= minStakeAmount, "Insufficient stake");
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
UserInfo storage u = users[msg.sender];
unchecked {
u.balance += uint128(amount);
totalStaked += amount;
}
u.lastStakeTime = uint64(block.timestamp);
emit Staked(msg.sender, amount);
}
function stakeFor(address beneficiary, uint256 amount)
external
nonReentrant
whenNotPaused
updateReward(beneficiary)
{
require(amount >= minStakeAmount, "Insufficient stake");
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
UserInfo storage u = users[beneficiary];
unchecked {
u.balance += uint128(amount);
totalStaked += amount;
}
u.lastStakeTime = uint64(block.timestamp);
emit Staked(beneficiary, amount);
}
function stakeWithPermit(
uint256 amount,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
)
external
nonReentrant
whenNotPaused
updateReward(msg.sender)
{
require(amount >= minStakeAmount, "Insufficient stake");
IERC20PermitUpgradeable(address(stakingToken)).permit(msg.sender, address(this), amount, deadline, v, r, s);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
UserInfo storage u = users[msg.sender];
unchecked {
u.balance += uint128(amount);
totalStaked += amount;
}
u.lastStakeTime = uint64(block.timestamp);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount)
external
nonReentrant
updateReward(msg.sender)
{
UserInfo storage u = users[msg.sender];
require(amount > 0 && amount <= u.balance, "Bad amount");
require(lockPeriod == 0 || block.timestamp >= (uint256(u.lastStakeTime) + uint256(lockPeriod)), "Locked");
unchecked {
u.balance -= uint128(amount);
totalStaked -= amount;
}
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function getReward() public nonReentrant updateReward(msg.sender) {
UserInfo storage u = users[msg.sender];
uint256 reward = u.rewards;
if (reward > 0) {
u.rewards = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(users[msg.sender].balance);
getReward();
}
// Batch ops (use cautiously; operator or service use)
function batchGetReward(address[] calldata accounts)
external
nonReentrant
whenNotPaused
onlyRole(GOVERNOR_ROLE)
{
uint256 len = accounts.length;
for (uint256 i; i < len; ) {
address acc = accounts[i];
// Pull-to-user pattern
(uint256 rpt,) = _rewardPerToken();
UserInfo storage u = users[acc];
uint256 reward = _earned(acc, rpt);
if (reward > 0) {
u.rewards = 0;
u.userRewardPerTokenPaid = rpt;
rewardsToken.safeTransfer(acc, reward);
emit RewardPaid(acc, reward);
}
unchecked { ++i; }
}
// update global lastUpdateTime once; safe since _rewardPerToken reads it
(uint256 rpt2, uint64 last2) = _rewardPerToken();
rewardPerTokenStored = rpt2;
lastUpdateTime = last2;
}
// Multicall to save gas on combining actions
function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i; i < data.length; ) {
bytes memory d = data[i];
// delegatecall keeps msg.sender; careful with reentrancy
(bool ok, bytes memory ret) = address(this).delegatecall(d);
require(ok, "Multicall failed");
results[i] = ret;
unchecked { ++i; }
}
}
// --- Reward distribution ---
// Notify new reward amount over a duration, distributor must fund contract with rewards beforehand
function notifyRewardAmount(uint256 amount, uint256 duration)
external
nonReentrant
onlyRole(REWARD_DISTRIBUTOR_ROLE)
updateReward(address(0))
{
require(duration > 0, "Bad duration");
require(block.timestamp >= periodFinish, "Ongoing period"); // simple model
// Ensure enough reward balance to pay out
uint256 bal = rewardsToken.balanceOf(address(this));
require(bal >= amount, "Insufficient rewards funded");
rewardRate = amount / duration;
require(rewardRate > 0, "Zero rate");
periodFinish = uint64(block.timestamp + duration);
emit RewardAdded(amount, rewardRate, periodFinish);
}
// Governance: parameters
function setParameters(
uint64 _lockPeriod,
uint128 _minStakeAmount,
address _priceFeed,
uint64 _staleSeconds,
int256 _minBound,
int256 _maxBound
) external onlyRole(GOVERNOR_ROLE) {
lockPeriod = _lockPeriod;
minStakeAmount = _minStakeAmount;
priceFeed = IAggregatorV3(_priceFeed);
staleSeconds = _staleSeconds;
oracleMinBound = _minBound;
oracleMaxBound = _maxBound;
emit ParametersUpdated(_lockPeriod, _minStakeAmount, _priceFeed, _staleSeconds, _minBound, _maxBound);
}
// Pause controls
function pause() external onlyRole(PAUSER_ROLE) { _pause(); }
function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); }
// Oracle-driven reward rate refresh (bounded, fresh)
function refreshRewardRateFromOracle(uint256 duration) external onlyRole(GOVERNOR_ROLE) updateReward(address(0)) {
require(address(priceFeed) != address(0), "No oracle");
require(duration > 0, "Bad duration");
(, int256 answer,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(answer >= oracleMinBound && answer <= oracleMaxBound, "Oracle out-of-bounds");
require(block.timestamp - updatedAt <= staleSeconds, "Oracle stale");
// Example mapping: linear map price to amount (customize per protocol)
// IMPORTANT: Keep conservative mapping; here we just scale to a capped reward amount.
uint256 amount = _mapPriceToReward(uint256(answer));
uint256 bal = rewardsToken.balanceOf(address(this));
if (amount > bal) amount = bal;
rewardRate = amount / duration;
require(rewardRate > 0, "Zero rate");
periodFinish = uint64(block.timestamp + duration);
emit RewardAdded(amount, rewardRate, periodFinish);
}
function _mapPriceToReward(uint256 price) internal view returns (uint256) {
// Example: simple clamp and proportional mapping; replace with safe logic
// Avoid extreme emissions; governance should set reasonable bounds.
uint256 base = 1e18; // normalizer
if (price > uint256(oracleMaxBound)) price = uint256(oracleMaxBound);
if (price < uint256(oracleMinBound)) price = uint256(oracleMinBound);
// naive mapping: amount = price / 100 (for demo). In production use a safe schedule.
return price / 100;
}
// Storage gap for future upgrades
uint256[45] private __gap;
}
Foundry 部署脚本(UUPS + ERC1967Proxy)示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "forge-std/Script.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "../src/StakingRewardsV1.sol";
contract DeployStakingRewards is Script {
// Edit with your addresses (testnet)
address stakingToken = 0x...; // ERC-20 stake token
address rewardsToken = 0x...; // ERC-20 rewards token
address admin = 0x...;
address governor= 0x...;
address pauser = 0x...;
address upgrader= 0x...;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(pk);
StakingRewardsV1 impl = new StakingRewardsV1();
bytes memory initData = abi.encodeWithSelector(
StakingRewardsV1.initialize.selector,
stakingToken,
rewardsToken,
admin,
governor,
pauser,
upgrader
);
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData);
StakingRewardsV1 staking = StakingRewardsV1(address(proxy));
// Optional: set parameters
// staking.setParameters(0, 1, address(0), 0, 0, type(int256).max);
console2.log("StakingRewards proxy:", address(staking));
console2.log("Implementation:", address(impl));
vm.stopBroadcast();
}
}
Foundry 测试样例(片段):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "forge-std/Test.sol";
import "../src/StakingRewardsV1.sol";
import "./mocks/MockERC20.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract StakingRewardsTest is Test {
MockERC20 stakeToken;
MockERC20 rewardToken;
StakingRewardsV1 staking;
address admin = address(0xA1);
address gov = address(0xA2);
address usr = address(0xB1);
function setUp() public {
stakeToken = new MockERC20("Stake", "STK", 18);
rewardToken = new MockERC20("Reward", "RWD", 18);
StakingRewardsV1 impl = new StakingRewardsV1();
bytes memory initData = abi.encodeWithSelector(
StakingRewardsV1.initialize.selector,
address(stakeToken),
address(rewardToken),
admin, gov, admin, admin
);
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData);
staking = StakingRewardsV1(address(proxy));
// fund user and rewards
stakeToken.mint(usr, 1000e18);
rewardToken.mint(address(staking), 1000e18);
vm.prank(gov);
staking.notifyRewardAmount(100e18, 1000); // 0.1 RWD/s
}
function testStakeAndEarn() public {
vm.startPrank(usr);
stakeToken.approve(address(staking), type(uint256).max);
staking.stake(100e18);
vm.warp(block.timestamp + 100); // advance time
uint256 earned = staking.earned(usr);
assertGt(earned, 0);
staking.getReward();
vm.stopPrank();
}
function testWithdrawLock() public {
vm.startPrank(gov);
staking.setParameters(3600, 1, address(0), 0, 0, 0); // lock 1h
vm.stopPrank();
vm.startPrank(usr);
stakeToken.approve(address(staking), type(uint256).max);
staking.stake(10e18);
vm.expectRevert(); // locked
staking.withdraw(1e18);
vm.warp(block.timestamp + 3600);
staking.withdraw(1e18);
vm.stopPrank();
}
}
MockERC20(简单可测试):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory n, string memory s, uint8 decimals_) ERC20(n, s) {
_mint(msg.sender, 0);
_decimals = decimals_;
}
uint8 private _decimals;
function decimals() public view override returns (uint8) { return _decimals; }
function mint(address to, uint256 amt) external { _mint(to, amt); }
}
说明与建议:
核心功能
技术难点与对策
目标网络
前端(TypeScript + React + Vite)
后端(Node.js + Express)
数据流与订阅设计
协作规范
目录结构建议
注意:示例为指导用途,需根据实际业务调整,并确保使用可信 RPC 与安全配置。
安装依赖
npm i ethers @walletconnect/ethereum-provider
WalletManager(React Hook)
// apps/web/src/wallet/useWallet.ts
import { useEffect, useState, useCallback } from 'react';
import { ethers } from 'ethers';
import EthereumProvider from '@walletconnect/ethereum-provider';
type Connector = 'browser' | 'walletconnect';
export type WalletState = {
connector?: Connector;
provider?: ethers.BrowserProvider;
signer?: ethers.Signer;
address?: string;
chainId?: number;
connected: boolean;
};
const WC_PROJECT_ID = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID;
const DEFAULT_CHAINS = (import.meta.env.VITE_SUPPORTED_CHAINS ?? '11155111').split(',').map(Number);
const RPC_MAP = JSON.parse(import.meta.env.VITE_RPC_MAP || '{}'); // { "11155111": "wss://..." }
export function useWallet() {
const [state, setState] = useState<WalletState>({ connected: false });
const connectBrowser = useCallback(async () => {
if (!window.ethereum) throw new Error('No browser wallet found');
const provider = new ethers.BrowserProvider(window.ethereum, { name: 'browser', chainId: undefined });
await window.ethereum.request({ method: 'eth_requestAccounts' });
const signer = await provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
setState({ connector: 'browser', provider, signer, address, chainId: Number(network.chainId), connected: true });
// handle events
window.ethereum.on('accountsChanged', async () => {
const signer = await provider.getSigner();
const address = await signer.getAddress();
setState(s => ({ ...s, signer, address }));
});
window.ethereum.on('chainChanged', async (_chainIdHex: string) => {
const network = await provider.getNetwork();
setState(s => ({ ...s, chainId: Number(network.chainId) }));
});
}, []);
const connectWalletConnect = useCallback(async () => {
const wc = await EthereumProvider.init({
projectId: WC_PROJECT_ID,
chains: DEFAULT_CHAINS,
showQrModal: true,
rpcMap: RPC_MAP,
methods: ['eth_sendTransaction', 'eth_signTransaction', 'personal_sign', 'eth_signTypedData', 'eth_signTypedData_v4'],
events: ['chainChanged', 'accountsChanged'],
metadata: {
name: 'My DApp',
description: 'DApp with WalletConnect',
url: window.location.origin,
icons: ['https://yourcdn/icon.png']
}
});
await wc.enable();
const provider = new ethers.BrowserProvider(wc as unknown as any);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
setState({ connector: 'walletconnect', provider, signer, address, chainId: Number(network.chainId), connected: true });
wc.on('accountsChanged', async () => {
const signer = await provider.getSigner();
const address = await signer.getAddress();
setState(s => ({ ...s, signer, address }));
});
wc.on('chainChanged', async () => {
const network = await provider.getNetwork();
setState(s => ({ ...s, chainId: Number(network.chainId) }));
});
}, []);
const disconnect = useCallback(async () => {
if (state.connector === 'walletconnect') {
try { await (state.provider?.provider as any)?.disconnect?.(); } catch {}
}
setState({ connected: false });
}, [state]);
const switchChain = useCallback(async (targetChainId: number) => {
if (!state.provider) throw new Error('No provider');
const hexId = '0x' + targetChainId.toString(16);
try {
await (state.provider.provider as any).request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: hexId }]
});
} catch (err: any) {
if (err?.code === 4902) {
const rpcUrl = RPC_MAP[String(targetChainId)];
await (state.provider.provider as any).request({
method: 'wallet_addEthereumChain',
params: [{
chainId: hexId,
chainName: `Chain ${targetChainId}`,
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: [rpcUrl].filter(Boolean),
blockExplorerUrls: []
}]
});
} else {
throw err;
}
}
const network = await state.provider.getNetwork();
setState(s => ({ ...s, chainId: Number(network.chainId) }));
}, [state.provider]);
return { ...state, connectBrowser, connectWalletConnect, disconnect, switchChain };
}
发送交易与状态订阅(SSE)
// apps/web/src/services/tx.ts
import { ethers } from 'ethers';
export async function sendNativeTx(signer: ethers.Signer, to: string, valueEth: string) {
const tx = await signer.sendTransaction({
to,
value: ethers.parseEther(valueEth)
});
return tx.hash;
}
// SSE 订阅后端推送的交易状态
export function subscribeTxStatus(hash: string, network: string, onMessage: (ev: MessageEvent) => void) {
const url = `/tx/${hash}/stream?network=${encodeURIComponent(network)}`;
const es = new EventSource(url, { withCredentials: true });
es.onmessage = onMessage;
es.onerror = (e) => { console.error('SSE error', e); es.close(); };
return () => es.close();
}
EIP-712 签名示例
// apps/web/src/services/sign.ts
import { ethers } from 'ethers';
export async function signTypedData(signer: ethers.Signer, domain: any, types: any, value: any) {
// ethers v6: signTypedData from Signer
// Some wallets require stripping the "EIP712Domain" from types
const sig = await (signer as any).signTypedData(domain, types, value);
return sig;
}
npm i siwe
// apps/web/src/services/siwe.ts
import { SiweMessage } from 'siwe';
export async function createSiweMessage(address: string, chainId: number) {
const nonceResp = await fetch('/auth/nonce', { credentials: 'include' });
const { nonce } = await nonceResp.json();
const msg = new SiweMessage({
domain: window.location.host,
address,
statement: 'Sign in to the DApp',
uri: window.location.origin,
version: '1',
chainId,
nonce
});
return msg.prepareMessage();
}
export async function verifySiwe(message: string, signature: string) {
const resp = await fetch('/auth/verify', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature })
});
return resp.json(); // { ok: boolean, address: string }
}
安装依赖
npm i express cors cookie-parser siwe ethers ioredis bullmq pino
环境变量(示例)
PORT=3000
REDIS_URL=redis://localhost:6379
RPC_HTTP_11155111=https://eth-sepolia.g.alchemy.com/v2/xxxx
RPC_WS_11155111=wss://eth-sepolia.g.alchemy.com/v2/xxxx
SESSION_SECRET=replace_me
Provider 工厂
// apps/api/src/lib/provider.ts
import { ethers } from 'ethers';
const httpMap: Record<string, string | undefined> = {
'11155111': process.env.RPC_HTTP_11155111,
};
const wsMap: Record<string, string | undefined> = {
'11155111': process.env.RPC_WS_11155111,
};
export function getProvider(network: string, preferWs = true) {
const ws = wsMap[network];
if (preferWs && ws) {
return new ethers.WebSocketProvider(ws);
}
const http = httpMap[network];
if (!http) throw new Error(`No RPC for network ${network}`);
return new ethers.JsonRpcProvider(http);
}
启动 Express 与基础中间件
// apps/api/src/server.ts
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import pino from 'pino';
import { authRouter } from './routes/auth';
import { chainRouter } from './routes/chain';
import { txRouter } from './routes/tx';
const logger = pino();
const app = express();
app.use(cors({ origin: [/^https?:\/\/localhost:\d+$/, /your\.domain$/], credentials: true }));
app.use(express.json());
app.use(cookieParser(process.env.SESSION_SECRET));
app.use('/auth', authRouter);
app.use('/chain', chainRouter);
app.use('/tx', txRouter);
const port = process.env.PORT || 3000;
app.listen(port, () => logger.info(`API listening on ${port}`));
Redis、BullMQ 初始化
// apps/api/src/lib/redis.ts
import { Redis } from 'ioredis';
export const redis = new Redis(process.env.REDIS_URL!);
// apps/api/src/lib/queue.ts
import { Queue, Worker } from 'bullmq';
import { redis } from './redis';
export const txQueue = new Queue('tx-status', { connection: redis.options });
export function makeTxWorker(processor: (data: any) => Promise<void>) {
return new Worker('tx-status', async job => {
await processor(job.data);
}, { connection: redis.options });
}
SIWE Auth 路由
// apps/api/src/routes/auth.ts
import { Router } from 'express';
import { randomBytes } from 'crypto';
import { redis } from '../lib/redis';
import { SiweMessage } from 'siwe';
export const authRouter = Router();
authRouter.post('/nonce', async (req, res) => {
const nonce = randomBytes(16).toString('hex');
// 简单会话:nonce 绑定到一个临时 session key(cookie)
const sid = req.signedCookies.sid || randomBytes(16).toString('hex');
res.cookie('sid', sid, { httpOnly: true, signed: true, sameSite: 'lax' });
await redis.setex(`siwe:nonce:${sid}`, 300, nonce);
res.json({ nonce });
});
authRouter.post('/verify', async (req, res) => {
const sid = req.signedCookies.sid;
if (!sid) return res.status(401).json({ ok: false, error: 'No session' });
const nonce = await redis.get(`siwe:nonce:${sid}`);
if (!nonce) return res.status(401).json({ ok: false, error: 'Nonce expired' });
try {
const msg = new SiweMessage(req.body.message);
const { data: result } = await msg.verify({
signature: req.body.signature,
domain: req.headers.host!,
nonce,
});
if (!result?.success) throw new Error('Invalid SIWE');
await redis.setex(`siwe:session:${sid}`, 3600, result.address);
await redis.del(`siwe:nonce:${sid}`);
res.json({ ok: true, address: result.address });
} catch (e: any) {
res.status(401).json({ ok: false, error: e.message });
}
});
链上只读接口(余额查询 + 缓存)
// apps/api/src/routes/chain.ts
import { Router } from 'express';
import { getProvider } from '../lib/provider';
import { redis } from '../lib/redis';
import { ethers } from 'ethers';
export const chainRouter = Router();
chainRouter.get('/balance', async (req, res) => {
const { address, network = '11155111' } = req.query as any;
if (!ethers.isAddress(address)) return res.status(400).json({ error: 'Invalid address' });
const cacheKey = `balance:${network}:${address}`;
const cached = await redis.get(cacheKey);
if (cached) return res.json({ network, address, balance: cached, cached: true });
const provider = getProvider(network);
const bal = await provider.getBalance(address);
const balanceEth = ethers.formatEther(bal);
await redis.setex(cacheKey, 30, balanceEth); // TTL 30s
res.json({ network, address, balance: balanceEth, cached: false });
});
交易状态队列与 SSE 推送
// apps/api/src/routes/tx.ts
import { Router } from 'express';
import { txQueue } from '../lib/queue';
import { redis } from '../lib/redis';
export const txRouter = Router();
// 请求后端监控某交易
txRouter.post('/watch', async (req, res) => {
const { hash, network = '11155111' } = req.body;
if (!/^0x([0-9a-fA-F]{64})$/.test(hash)) return res.status(400).json({ error: 'Invalid tx hash' });
await txQueue.add('watch', { hash, network }, { removeOnComplete: true, removeOnFail: true });
res.json({ ok: true });
});
// SSE 通道:推送该交易状态
txRouter.get('/:hash/stream', async (req, res) => {
const { hash } = req.params;
const { network = '11155111' } = req.query as any;
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const channel = `tx-status:${network}:${hash}`;
const sub = new (require('ioredis')).Redis(process.env.REDIS_URL!);
await sub.subscribe(channel);
sub.on('message', (_chan: string, msg: string) => {
res.write(`data: ${msg}\n\n`);
});
req.on('close', async () => {
await sub.unsubscribe(channel);
sub.disconnect();
res.end();
});
// 立即发送一个 hello 包,避免前端等待
res.write(`data: ${JSON.stringify({ status: 'subscribed', hash, network })}\n\n`);
});
交易状态 Worker(waitForTransaction + 推送)
// apps/api/src/workers/txWorker.ts
import { makeTxWorker } from '../lib/queue';
import { getProvider } from '../lib/provider';
import { redis } from '../lib/redis';
function toMessage(obj: any) { return JSON.stringify(obj); }
// confirmations 可配置,减少重组影响
const CONFIRMATIONS = Number(process.env.CONFIRMATIONS ?? 1);
export const worker = makeTxWorker(async ({ hash, network }: { hash: string; network: string }) => {
const provider = getProvider(network, true);
const channel = `tx-status:${network}:${hash}`;
// 初始状态
await redis.publish(channel, toMessage({ status: 'pending', hash }));
try {
const receipt = await provider.waitForTransaction(hash, CONFIRMATIONS);
if (!receipt) {
await redis.publish(channel, toMessage({ status: 'timeout', hash }));
return;
}
const msg = {
status: receipt.status === 1 ? 'confirmed' : 'failed',
hash,
blockNumber: receipt.blockNumber,
confirmations: CONFIRMATIONS,
gasUsed: receipt.gasUsed?.toString(),
effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
to: receipt.to,
from: receipt.from,
contractAddress: receipt.contractAddress,
transactionIndex: receipt.transactionIndex,
};
await redis.publish(channel, toMessage(msg));
} catch (e: any) {
// 可能是替换或 RPC 错误
await redis.publish(channel, toMessage({ status: 'error', hash, error: e.message }));
}
});
Worker 启动
// apps/api/src/workers/index.ts
import './txWorker';
// 可在独立进程启动:node dist/workers/index.js
OpenAPI 契约(示例片段)
// apps/api/src/types/api.d.ts
export type BalanceResponse = { network: string; address: string; balance: string; cached: boolean };
export type TxWatchRequest = { hash: string; network?: string };
export type TxStatusEvent =
| { status: 'subscribed'; hash: string; network: string }
| { status: 'pending'; hash: string }
| { status: 'confirmed' | 'failed' | 'timeout' | 'error' | 'replaced'; hash: string; [k: string]: any };
环境与配置
部署方案
version: '3.8'
services:
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
ports: ["6379:6379"]
api:
build: ./apps/api
env_file: ./apps/api/.env
depends_on: [redis]
ports: ["3000:3000"]
web:
build: ./apps/web
env_file: ./apps/web/.env
ports: ["5173:5173"]
监控与日志
性能优化
测试与发布
运营与支持
以上方案以 TypeScript、React、Vite、Express、ethers.js 与 Redis 为核心组件,覆盖从连接器实现、交易签名与状态跟踪,到后端编排与协作规范的完整流程。实际落地时,请根据目标侧链/测试网的 RPC 能力与延迟特性微调确认数与订阅策略,并在生产环境启用严格的安全加固与监控。
把AI化身为你的区块链开发总监,面向DeFi、NFT、去中心化交易等高价值场景,提供从需求拆解、架构设计、核心实现到安全加固与上线运维的全流程指导;以清晰、可执行的步骤帮助团队快速做对决定、避坑提效、稳健交付;根据你的技术偏好与目标平台智能适配方案,生成行动清单、关键示例与上线攻略,驱动项目更快落地、提升用户体验,并在试用期即可验证价值、促成付费转化。
从零到一梳理需求与模块,选择适配链与工具,生成实施路线与里程碑,降低试错与预算浪费。
借助分步指导与模板快速产出合约草案,优化性能与费用,完成安全自检与部署流程,提升交付速度。
快速集成钱包与链上交互,获取交互方案与数据流设计,统一协作规范,减少返工与沟通成本。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
免费获取高级提示词-优惠即将到期