各位观众,欢迎来到今天的 "Substrate/Polkadot Wasm Smart Contracts 前端交互" 讲座!今天咱们不整那些虚头巴脑的,直接上干货,让大家伙儿能听懂、能上手、能回家就能开撸代码。
开场白:为啥要搞前端交互?
咱们先聊聊为啥要搞前端交互。想象一下,你辛辛苦苦用 Rust 写了个牛逼哄哄的 Wasm 合约,能发 token、能搞 NFT、甚至能玩 DeFi,但是呢,用户只能通过命令行或者 Polkadot JS Apps 这种工具才能用你的合约,是不是感觉有点…憋屈?
这就好比你做了个香气扑鼻的红烧肉,但是别人只能用筷子尖儿戳一下闻闻味儿,不能大快朵颐,是不是很可惜?
所以,前端交互就是要把你这个“红烧肉”端到用户面前,让他们能用鼠标点点、手指划划,就能轻松调用你的合约,体验你的 DApp 的魅力。
第一部分:准备工作,磨刀不误砍柴工
在开始撸代码之前,咱们得先准备好家伙什儿。这就像做饭之前要先洗菜、切菜一样,不能省略。
-
环境搭建:
-
Node.js 和 npm/yarn: 这是前端开发的基石,没有它啥也玩不转。确保你的电脑上安装了 Node.js 和 npm (或者 yarn)。
-
Polkadot JS API: 这是咱们跟 Substrate/Polkadot 链交互的桥梁。用 npm 或者 yarn 安装:
npm install @polkadot/api @polkadot/extension-dapp # 或者 yarn add @polkadot/api @polkadot/extension-dapp
-
一个 Wasm 合约: 这个嘛… 你得自己写一个,或者找个现成的。 咱们假设你已经有一个编译好的
.wasm
文件 和.contract
文件 (包含了 ABI)。
-
-
Polkadot{.js} Extension (插件):
这个插件是用户授权你的 DApp 访问他们的 Polkadot 账户的关键。 确保用户安装并配置好这个插件。
第二部分:核心代码,手把手教你撸
好了,家伙什儿都齐了,咱们开始撸代码! 这里我们使用 React 作为一个例子,因为现在 React 用的人多,比较主流。
import React, { useState, useEffect } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { ContractPromise } from '@polkadot/api-contract';
import { web3Accounts, web3Enable, web3FromSource } from '@polkadot/extension-dapp';
const CONTRACT_ADDRESS = '你的合约地址'; // 替换成你的合约地址
const CONTRACT_ABI = require('./你的合约ABI.json'); // 替换成你的合约ABI文件
function App() {
const [api, setApi] = useState(null);
const [accounts, setAccounts] = useState([]);
const [contract, setContract] = useState(null);
const [greeting, setGreeting] = useState('');
const [status, setStatus] = useState('');
const [currentAccount, setCurrentAccount] = useState(null);
useEffect(() => {
const init = async () => {
setStatus('Connecting to the blockchain...');
// 连接到 Substrate/Polkadot 节点
const wsProvider = new WsProvider('ws://127.0.0.1:9944'); // 替换成你的节点地址
const api = await ApiPromise.create({ provider: wsProvider });
setApi(api);
// 获取用户账户
const extensions = await web3Enable('你的 DApp 名称'); // 替换成你的 DApp 名称
if (extensions.length === 0) {
setStatus('No extension installed, or you did not allow access');
return;
}
const accounts = await web3Accounts();
setAccounts(accounts);
if (accounts.length > 0) {
setCurrentAccount(accounts[0].address);
}
// 创建合约实例
const contract = new ContractPromise(api, CONTRACT_ABI, CONTRACT_ADDRESS);
setContract(contract);
setStatus('Ready');
};
init();
}, []);
const getGreeting = async () => {
if (!contract || !currentAccount) {
setStatus('Contract not initialized or no account selected.');
return;
}
setStatus('Calling get_greeting...');
try {
const { output } = await contract.query.getGreeting(currentAccount, { gasLimit: 10000000000 }, currentAccount); // 替换成你的合约方法名
setGreeting(output.toString());
setStatus('');
} catch (error) {
console.error('Error calling get_greeting:', error);
setStatus(`Error: ${error.message}`);
}
};
const setGreetingHandler = async (newGreeting) => {
if (!contract || !currentAccount) {
setStatus('Contract not initialized or no account selected.');
return;
}
setStatus('Setting new greeting...');
try {
const injector = await web3FromSource(accounts.find(a => a.address === currentAccount).meta.source);
const gasLimit = 10000000000;
const tx = contract.tx.setGreeting( { gasLimit, value: 0 }, newGreeting); // 替换成你的合约方法名
tx.signAndSend(currentAccount, { signer: injector.signer }, ({ status }) => {
if (status.isInBlock) {
setStatus(`Completed at block hash #${status.asInBlock.toString()}`);
} else if (status.isFinalized) {
setStatus(`Finalized block hash #${status.asFinalized.toString()}`);
} else {
setStatus(`Current Status: ${status.type}`);
}
}).catch(error => {
console.error('Error setting greeting:', error);
setStatus(`Error: ${error.message}`);
});
} catch (error) {
console.error('Error setting greeting:', error);
setStatus(`Error: ${error.message}`);
}
};
const handleAccountChange = (event) => {
setCurrentAccount(event.target.value);
};
return (
<div>
<h1>Substrate/Polkadot Wasm Contract Demo</h1>
<p>Status: {status}</p>
{accounts.length > 0 ? (
<div>
<label>Select Account:</label>
<select value={currentAccount} onChange={handleAccountChange}>
{accounts.map((account) => (
<option key={account.address} value={account.address}>
{account.meta.name} ({account.address})
</option>
))}
</select>
</div>
) : (
<p>No accounts found. Please install and configure the Polkadot{.js} extension.</p>
)}
<button onClick={getGreeting} disabled={!contract || !currentAccount}>Get Greeting</button>
<p>Greeting: {greeting}</p>
<button onClick={() => {
const newGreeting = prompt('Enter new greeting:');
if (newGreeting) {
setGreetingHandler(newGreeting);
}
}} disabled={!contract || !currentAccount}>Set Greeting</button>
</div>
);
}
export default App;
代码解释:
- 引入依赖: 引入必要的库,包括
@polkadot/api
,@polkadot/extension-dapp
, 和@polkadot/api-contract
。 - 状态管理: 使用
useState
管理组件的状态,包括 API 连接、账户信息、合约实例、greeting 信息和状态信息。 - useEffect 钩子: 在组件加载时初始化 API 连接、获取账户信息、创建合约实例。
- 连接到 Substrate 节点: 使用
ApiPromise.create
连接到指定的 Substrate 节点。 记住替换ws://127.0.0.1:9944
为你自己的节点地址。 - 获取用户账户: 使用
web3Enable
启用 Polkadot{.js} 扩展,然后使用web3Accounts
获取用户账户。 替换'你的 DApp 名称'
为你自己的 DApp 名称。 - 创建合约实例: 使用
ContractPromise
创建合约实例,需要传入 API 对象、合约 ABI 和合约地址。 记住替换你的合约地址
和你的合约ABI.json
为你自己的合约地址和 ABI 文件。 - 调用合约方法 (query): 使用
contract.query.getGreeting
调用合约的get_greeting
方法。 这是一个只读方法,不需要签名。 - 调用合约方法 (tx): 使用
contract.tx.setGreeting
调用合约的set_greeting
方法。 这是一个需要签名的方法。- 首先,你需要使用
web3FromSource
获取一个injector
对象,用于签名交易。 - 然后,你需要使用
tx.signAndSend
签名并发送交易。 signAndSend
接收两个参数: 账户地址 和 一个包含signer
的对象。signAndSend
还会返回一个unsubscribe
函数,用于取消订阅交易状态。
- 首先,你需要使用
- UI 渲染: 使用 JSX 渲染 UI,包括状态显示、账户选择、调用合约方法的按钮和 greeting 信息。
第三部分:部署和运行,让你的 DApp 飞起来
- 编译前端代码: 使用你喜欢的构建工具 (比如 webpack, parcel, vite) 编译前端代码。
- 部署前端代码: 将编译好的前端代码部署到你喜欢的服务器 (比如 Netlify, Vercel, GitHub Pages)。
- 配置 CORS: 确保你的 Substrate 节点配置了正确的 CORS 策略,允许你的前端应用访问。
- 运行 DApp: 在浏览器中打开你的 DApp,连接到你的 Substrate 节点,选择你的账户,然后就可以开始体验你的 Wasm 合约了!
第四部分:常见问题和注意事项,避坑指南
- CORS 问题: 这是前端开发中最常见的问题之一。 确保你的 Substrate 节点配置了正确的 CORS 策略,允许你的前端应用访问。
- Gas Limit: 调用合约方法时,需要设置合适的 Gas Limit。 如果 Gas Limit 设置得太小,交易会失败。 如果 Gas Limit 设置得太大,你会浪费 Gas。
- 错误处理: 在调用合约方法时,一定要进行错误处理。 如果交易失败,你需要向用户显示错误信息。
- 账户权限: 用户需要授权你的 DApp 访问他们的 Polkadot 账户。 如果用户没有授权,你的 DApp 将无法调用合约方法。
- ABI 文件: ABI 文件描述了合约的接口。 确保你使用的是正确的 ABI 文件。
第五部分:进阶技巧,更上一层楼
- 使用 Redux 或者 Zustand 管理状态: 如果你的 DApp 比较复杂,可以使用 Redux 或者 Zustand 等状态管理库来管理状态。
- 使用 Typescript: 使用 Typescript 可以提高代码的可读性和可维护性。
- 使用 GraphQL: 使用 GraphQL 可以更高效地查询链上的数据。
- 使用 Substrate API Sidecar: Substrate API Sidecar 是一个 REST API 服务,可以简化与 Substrate 链的交互。
总结:
今天我们一起学习了如何使用前端技术与 Substrate/Polkadot Wasm 智能合约进行交互。 希望大家能够掌握这些知识,开发出更多有趣、有用的 DApp。 记住,实践是检验真理的唯一标准。 赶紧动手撸代码吧!
代码表格:
代码片段 | 描述 |
---|---|
npm install @polkadot/api @polkadot/extension-dapp |
安装 Polkadot JS API 和 Polkadot{.js} 扩展 DApp 接口。 |
const wsProvider = new WsProvider('ws://127.0.0.1:9944'); |
创建一个 WebSocket Provider 连接到 Substrate 节点。 |
const api = await ApiPromise.create({ provider: wsProvider }); |
使用 WebSocket Provider 创建一个 ApiPromise 实例,用于与 Substrate 链交互。 |
const extensions = await web3Enable('你的 DApp 名称'); |
启用 Polkadot{.js} 扩展,并请求用户授权你的 DApp 访问他们的账户。 |
const accounts = await web3Accounts(); |
获取用户授权的 Polkadot 账户列表。 |
const contract = new ContractPromise(api, CONTRACT_ABI, CONTRACT_ADDRESS); |
创建一个 ContractPromise 实例,用于与 Wasm 合约交互。 |
contract.query.getGreeting(currentAccount, { gasLimit: 10000000000 }, currentAccount) |
调用合约的只读方法 (query),获取 greeting 信息。 |
contract.tx.setGreeting( { gasLimit, value: 0 }, newGreeting) |
调用合约的交易方法 (tx),设置 greeting 信息。 |
tx.signAndSend(currentAccount, { signer: injector.signer }, ({ status }) => { ... }); |
签名并发送交易,并监听交易状态。 |
友情提示:
- 代码仅供参考,请根据你的实际情况进行修改。
- 在生产环境中使用时,请务必进行安全审计。
- 遇到问题不要慌,Google 一下,Stack Overflow 一下,实在不行,问问我!
感谢大家的观看! 下次再见!