开发第一个Sui 区块链应用
区块链本身可能和以往一样复杂,但围绕它们的工具已经大大成熟。让我向你展示在更新更快的区块链之一 Sui上构建你的第一个区块链应用程序是多么容易。
一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK
啊,区块链。这个词让许多开发人员心生恐惧。
说实话,我们几乎没人真正知道它们到底是怎么工作的。加密这个,节点那个,哈希我的地址。验证器到底是什么?验证 deez 疯子!
好吧,你我都很幸运,现在已经不是 2017 年了。区块链本身可能和以往一样复杂,但围绕它们的工具已经大大成熟。让我向你展示在更新更快的区块链之一 Sui(“sweet”,t 不发音)上构建你的第一个区块链应用程序是多么容易。
我们的最终应用程序将显示任何给定钱包的硬币余额,如下所示:
1、环境搭建
计划很简单:使用 HTMX 和 Typescript 构建一个网站。没有 React。没有 Move。没有 Vite。呃,不寒而栗。不,不。我们喜欢简单的东西。而且可爱。
这就是为什么我们将使用 bunjs 作为我们的包管理器。它将帮助你安装其他东西。为什么是 bun?看看它!天哪,我只想捏捏它!
1.1 安装 bun(或者我称之为 bun-bun)
如果你使用的是 Windows 计算机,那么首先,对不起,其次,你可以安装 Node。只需将bun create
替换为node create
,将bun add
替换为node i
。
打开你的终端并运行此命令(当然不要输入 $ 符号):
$ exec bash
以确保我们使用的是 bash shell(当然是所有 shell 中最好的),然后运行此命令:
$ curl -fsSL https://bun.sh/install | bash
安装完成后,我们可以使用 bun create
命令来启动项目,但该命令缺乏……你说的是什么,“创造力”?是的!我非常同意。
在终端中运行此命令,这样我们就可以使用更多有趣的命令:
$ echo -e '\nalias bake="bun create"\nalias prep="bun init"\nalias bun-bun="bun add"' >> ~/.bashrc
这让我们可以输入 bake
而不是 bun create
。因为 buns.. bake。哈哈
好的,输入此命令以重新加载配置文件,以便你的终端知道新的别名:
$ source ~/.bashrc
2、创建项目
将终端指向将保存项目的父文件夹。如果你已经有一个,请使用 cd
命令导航到那里,否则通过依次运行这两个命令创建一个:
$ mkdir Sui
$ cd Sui
现在你已经创建了 Sui 目录并导航到它。终端窗口底部的行应该类似于此(除了你将使用 Sui 而不是 sui):
呃,“Fuck-Putin”只是我的计算机的名称。因此: [ComputerName]:[ActiveFolder] [username]$
是你应该看到的。
现在让我们告诉 bun-bun 使用名为 elysia
的模板为我们创建项目。Elysia 是我们将要使用的 Web 服务器,它有自己的同名模板。从模板创建完全是可选的;它只是为我们节省了一些步骤。
依次运行这 3 个命令(等待上一个命令完成):
$ bake elysia sui-first-bun
$ cd sui-first-bun
$ bun-bun @elysiajs/html @mysten/sui.js
如果您觉得无聊,请将 bake
替换为 bun create
,将 bun-bun
替换为 bun add
。
哇哦!Bun 为我们烘焙了项目脚手架,并添加了所需的两个依赖项:Elysia 的 html 扩展和 Sui 的 JS 库。我们准备好享用 err 代码了!
3、基础
打开 Visual Studio Code(或你选择的编辑器),然后选择文件 -> 打开文件夹...,选择主用户目录中的 Sui(或您选择的任何名称)文件夹(在 Mac 上,它位于 Macintosh HD/Users/[用户名]),然后选择 sui-first-bun,然后单击打开。您应该看到以下内容:
让我们确保它运行。返回终端,确保你位于项目的文件夹(sui-first-bun)中,然后运行命令 bun dev
。你应该看到以下输出:
现在在浏览器中导航到 localhost:3000,应该会看到“Hello Elysia” - 这是你现在在机器上运行的 Web 服务器的输出!
如果你想知道 bun dev
的作用,请返回 VS Code 并在项目的根文件夹中打开 package.json
(CMD+B 或 CTRL+B 显示/隐藏主侧边栏,然后显示带有两个文件作为图标的选项卡)。你将在 scripts
下看到一行 "dev": "bun run --watch src/index.ts"
。如果愿意,你可以添加自己的行,例如 "debug": "bun --inspect --watch src/index.tsx"
。这非常适合你想要逐行使用调试器逐步执行 TypeScript 文件的情况,并且如果你在项目的根文件夹中在终端中输入 bun debug
,它将运行。但我离题了……
3.1 配置
最后几件事使我们能够混合 HTML 和 TypeScript 代码,而不会让编译器给我们带来问题。
将 index.ts 文件重命名为 index.tsx(在 VS Code 中,当焦点位于文件名上时,只需按 Enter)。在 package.json 文件的第 6 行也更新名称。
打开 tsconfig.json 并在 compilerOptions
下添加以下3行,如下所示:
"compilerOptions": {
"jsx": "react",
"jsxFactory": "Html.createElement",
"jsxFragmentFactory": "Html.Fragment",
回到index.tsx,在顶部添加以下2行:
import { html } from "@elysiajs/html";
import * as Sui from "@mysten/sui.js/client";
最后,添加 .use(html())
到 Elysia 服务器声明,并可能将其格式化为:
const app = new Elysia()
.use(html())
.get("/", () => "Hello Elysia")
.listen(3000);
如果你保存了所有文件,应该会在终端底部看到“🦊 Elysia 正在运行...”消息,否则请再次尝试 bun dev
命令。
3.2 向用户提供主页
是时候用一些真正的 HTML 替换“Hello Elysia”了。即,一个文本框,用户可以在其中输入钱包地址和一个启动按钮。
单击主侧边栏中的 index.tsx(CMD+B),然后单击侧边栏顶部的“新建文件...”图标,将此文件命名为 BaseHTML.tsx。在里面粘贴以下代码:
export default function BaseHTML() {
return (
<html lang="en">
<head>
<script
src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<script src="https://cdn.tailwindcss.com"></script>
<title>My First Sui-t Bun</title>
</head>
<body>
<div class="flex justify-center p-4">
<form hx-post="/coins" hx-target="#results">
<input name="address" type="text" class="border pl-1 pr-1"></input>
<button type="submit" class="ml-4 border pl-2 pr-2">Check if Chose Rich</button>
</form>
</div>
<div id="results" class="flex justify-center"></div>
</body>
</html>
)
}
它有什么用?第一个 <script>
标签导入 HTMX javascript 库,第二个 - Tailwind CSS(我个人选择用于设置页面样式)。然后我们有一个简单的表单,其中包含一个文本字段和一个按钮。该按钮将文本字段的内容发送到服务器,然后获取服务器返回的任何 html 并将其插入到结果 div 中。当然,最重要的一行是 <title>
标签之间的文本,因为,让我们面对现实吧,这是一个非常棒的双关语😊。
如果你以前没有使用过 HTMX,那么这里披着斗篷的英雄是两个关键字 hx-post 和 hx-target。第一个将表单发送到 Web 服务器上的 /coins
地址,后者告诉 htmx 将从服务器返回的任何内容粘贴到 id 为 results
的 div 中。非常简单,简单就是好。
为了向用户展示这个精彩的新 html 代码,在 index.tsx 中将 .get("/", () => "Hello Elysia")
行替换为:
.get("/", () => BaseHTML())
同时在顶部添加此导入行,以便 index.tsx 知道在哪里查找 BaseHTML 函数:
import BaseHTML from "./BaseHTML";
保存文件后,刷新浏览器页面,你应该会看到:
当然,如果你粘贴钱包地址并单击按钮,则不会发生任何事情,因为 Web 服务器尚未设置为在 /coins
路由接收任何内容,更不用说返回任何有用的东西了。但是,如果你检查开发人员工具 -> 控制台,会看到记录的错误,其中客户端在服务器上调用 /coins
并返回 404 错误(未找到),这是应该的。
现在给自己倒一杯,因为你已经完成了一半以上,我们终于准备好使用 Sui javascript 包装器从区块链中读取数据了,这次完全不是开玩笑。是时候了。
4、要点
首先,我们必须告诉 Elysia 当有人尝试访问 /coins
时她必须做什么。在 .get
和 .listen
行之间插入以下行:
.post("/coins", ({ body }) => balances(body))
现在我们的代码应如下所示:
const app = new Elysia()
.use(html())
.get("/", () => BaseHTML())
.post("/coins", ({ body }) => balances(body))
.listen(3000);
body
是 Elysia 用来表示从客户端收到的数据的特殊关键字。我们告诉 Web 服务器“请将 POST 的主体(内容)传递给我们的函数 balances。谢谢。” )
你会注意到 balances
带有下划线,因为我们尚未定义该函数。让我们这样做。在 index.tsx
的底部,添加:
function balances(body: any) {
const addy = body.address;
if (!addy || addy === "") {
return <div>Plz enter wallet addy above</div>;
}
return (
<div class="grid grid-cols-[50px_60px_150px] text-middle">
<div class="col-span-3">{addy}</div>
</div>
);
}
首先,我们期望 body
对象上有一个地址字段。如果没有这样的字段,我们将返回 HTML,告诉用户输入地址。否则,我们现在返回仅显示输入地址的 HTML,作为我们收到该地址的确认。
好的,现在开始真正的讨论:是时候实际使用 Sui JS 包装器来访问区块链了。
在 function balances(...)
行的正上方,插入以下行:
const client = new Sui.SuiClient({ url: Sui.getFullnodeUrl("mainnet") });
然后回到 balances()
函数内部,在检查是否提供了地址之后,让我们读取区块链!
const coins = await client.getAllBalances({ owner: addy });
你会看到 await
现在带有下划线:
这是因为我们的 balances()
函数未标记为异步,并且所有 Sui 函数都定义为 Promises
,即异步操作。 这意味着我们的闭包函数也必须是这样的。
通过在函数 balances
前面添加 async 来解决这个问题:
async function balances(body: any) {
现在让我们处理钱包没有硬币的情况。在 const coins = ...
之后添加:
if (!coins || coins.length === 0) {
return <div>No coins in this wallet. They didn't choose rich.</div>;
}
如果我们确实取回了币余额,我们应该将它们包含在返回给客户端的 HTML 中。将 <div class="col-span-3">{addy}</div>
替换为:
{coins.map(c => <><div class="col-span-2">{c.coinType}</div><div>{c.totalBalance}</div></>)}
请注意空的 <>
和 </>
标签。这是我们使用的库的一个工件,它只允许将返回的 html 包装在一个父标签中。当我们不想使用其他标签(例如 div)时,会使用空标签。它允许代码工作,但实际上它只是被忽略,并且不会影响 Web 服务器返回给客户端的最终 html。
如果你正确遵循并刷新页面并提供钱包地址,可能会看到一些非常丑陋的东西:
这是因为我们被调用的 getAllBalances()
函数“弄得”很“粗糙”。它不会返回每个币的元数据,例如符号 ($SUI 等) 或币使用的小数位数。因此,对于 SUI,我们实际上看到的是 MIST 余额,而不是 SUI 余额。即,我的钱包(见此处)有 4.35 SUI,而不是 40 亿 SUI。不幸的是😅。 ::fud::FUD
前面的那个长数字?它基本上是部署币的合约的地址。
FUD 币的余额隐藏在地址后面,因为,听着,我不是 CSS 专家,好吧,我只是一个想写博客的邻家男孩。
丑陋与否,关键在于你刚刚从区块链中读取了数据!!!哇哦!喝上一两杯龙舌兰酒,既是为了庆祝,也是为了应对令人沮丧的认识:事实上,我确实在整整 15 分钟的事情上对你撒了谎,我可能不是你以为的那个朋友。
5、最终代码
由于 getAllBalances()
对我们很不利,我们需要对返回的每个硬币进行另一次调用,以获取该硬币的元数据 - 它的符号、图标/图像的 url(如果有)等。
有很多方法可以剥这只狐狸的皮;在这里,我选择了一种快速而肮脏的方法。
让我们首先定义一个封装我们想要发送回客户端的信息的类型。将此代码插入到函数 balances()
的主体之外,例如 const client = ...
行的正上方:
type coin_balance = {
symbol: string,
url?: string | null,
balance: string
}
现在回到 balances()
函数内部,在最后的 return
语句上方,添加以下代码:
let coin_balances: coin_balance[] = []
for (let coin of coins) {
const meta = await client.getCoinMetadata({ coinType: coin.coinType });
if (!meta) {
coin_balances.push({ symbol: coin.coinType, url: undefined, balance: coin.totalBalance });
} else {
const balance = Number(coin.totalBalance) / Math.pow(10, meta.decimals);
coin_balances.push({ symbol: meta.symbol, url: meta.iconUrl, balance: String(balance)});
}
}
对于 getAllBalances()
函数返回的每种硬币,我们尝试获取其元数据。如果我们没有得到它(即 !meta),那么我们将创建一个 coin_balance
对象,并用我们已有的丑陋数据填充。否则,我们将用从 getCoinMetadata()
函数获得的漂亮数据填充它。通过将 totalBalance 除以从元数据中获得的 10 的幂,我们得到了真实的硬币数量(据我所知)。也就是说,我的 40 亿 SUI 余额变成了更现实的 4.35 SUI。
现在我们需要显示这些数据。将 return 语句中的 {coins.map...}
行替换为:
{coin_balances.map(coin_row)}
最后,在 balances()
函数下方添加 coin_row()
函数:
function coin_row(cb: coin_balance) {
const img = cb.url ? <img src={cb.url} class="ml-2 w-6 h-6"></img> : <></>;
return <><div>{img}</div><div>{cb.symbol}:</div> <div>{cb.balance}</div></>;
}
最后一个函数只是采用 coin_balance
视图模型(此类对象的术语)并为包含 3 列的行创建 HTML:图像、符号和余额。如果提供的数据中没有 url,它会返回空标签代替图像,即我们之前讨论过的标签。
保存,返回浏览器并刷新,输入钱包地址,然后 vuala:
我今天在那只狗币上赚了 5%!也就是......支票纸币...... 0.08 美元。太棒了!😭
就这样结束了!你可以尝试前 100 名列表中的一些 Sui 钱包,看看它们包含哪些硬币。
原文链接:Build Your First Blockchain App in 15 min with Sui & Typescript
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。