从 Stellar 开始:从头开始构建全栈 dApp 所需的唯一教程。
我们就长话短说吧。
你想利用区块链技术构建一些很酷的东西吗?你来对地方了。
本教程,以及我本人,将指导你,并向你讲解构建你自己的去中心化应用程序所需的一切知识,无需了解 Rust,就能使用 Stellar 基金会提供的这项不可思议的技术。
我怎样才能自信地说出上面这句话?
因为在开始开发这款应用之前,我对 Stellar 一无所知。我只是想开发一个能改善社会的东西,一路走来,我学到了很多东西。所有这些时间都浓缩成了这篇教程,它真的能满足你所有的需求,让你能够去开发一个有潜力让世界变得更美好的产品,或者仅仅是让你觉得好玩而已。就像我常说的,如果我能做到,那么任何人都可以。
我们今天要建造什么?
(鼓声)坦白说,我是通过 Stellar 举办的挑战活动才知道他们的。但我当时很感兴趣,于是浏览了 Stellar 的网站,想真正了解他们到底在做什么。
而且,我非常欣赏他们的愿景,并且渴望创造一些他们为世界所做的善事,哪怕只是其中很小的一部分。
所以今天,我们正在构建FlareWay,这是一款去中心化的应用程序,允许用户向世界各地有需要的学生捐赠加密货币。所以,系好安全带,要么在跑步机上阅读这篇文章,要么拿出你的机器,跟着我一起学习。
重要提示:虽然本教程非常简单易懂,而且我已经用一种你可以轻松理解的方式解释了所有内容,但它的长度与这句话一样长,因为它可以帮助你开发一个全栈 DApp,所以不要害怕按照自己的节奏学习,并在学习过程中休息一下。让我们开始吧😼。
我们今天将使用什么技术?
- Next.js:我们将使用 Next.js 构建整个前端。如果您对 Next 或 React 有一定的了解,也可以直接开始。其他所有事情我都会帮您处理。
- TailwindCSS:我们将使用 TailwindCSS 来设计前端样式。我们不会深入探讨它,因为我希望你能够创建自己的风格,让你的应用展现你的个性。
- MongoDB:我们将使用 MongoDB 来存储注册我们应用程序的学生信息。简而言之,MongoDB 是一个用于存储信息的 NoSQL 数据库。
- Prisma:Prisma 是一个 ORM,我们将用它来与数据库通信。如果你对 Prisma 没有任何实际操作经验,完全没问题,你可以随时学习。
- Stellar-SDK:这款工具可以帮助您从 Web2 开发者转型为 Web3 开发者。Stellar 的 SDK 将帮助我们实现在 Stellar 区块链上执行交易的功能。
这是我们正在构建的应用程序的简单演示
让我们看一下工作流程。
下图将帮助您了解用户在 DApp 中的旅程,并帮助您为心中的绝妙想法创建自己的工作流程。我们不会直接深入代码,因为工作流程可以帮助您了解应用正常运行所需的不同功能。俗话说,
三思而后行,一次编码。
作为一项脑力练习,您可以思考一下这个应用程序的一些必备功能。
以下是本教程的详细章节。
构建前端
- 初始化项目并安装依赖项。
- 添加 UI,并创建登录页面的基本框架。
- 为学生创建一个注册表格。
- 设置 MongoDB。
- 初始化 Prisma。
- 设置用于创建学生的 API 路由。
- 设置 API 路由以获取学生列表。
- 向潜在捐赠者展示学生名单。
使用 Stellar SDK
在开始前端之前,我意识到逐个讲解代码会比较繁琐。因此,我会解释所有必要的细节,如果您想查看代码,可以在这里的代码库中查看。
1.初始化项目
好了,现在打开你最喜欢的 IDE(我使用的是 VSCode),并把它放在一个空文件夹里。我们将在这里创建我们的应用。打开一个新的终端,输入以下命令初始化 NextJs 应用:
npx create-next-app@latest
系统将提示您一些选项,您可以保留它们,就像我在这里保留的一样(如下图所示)。
(我在我的项目中使用了 Typescript,现在已经成为一种习惯,但对你来说,我们将在这里保留旧的 Javascript。)
这将使用最新版本的 NextJs 创建一个新应用程序。
现在,为了真正拥有一个空白画布,我们将做三件事。1
.转到app/page.jsx
并删除主组件内的所有内容。只需添加一个Hello World (I add Bonsoir, Elliot here.)
。2
.转到app/globals.css
并用以下代码片段替换所有内容(我们刚刚删除了默认样式):
@tailwind base;
@tailwind components;
@tailwind utilities;
3.我们来快速安装一些需要的依赖项。在终端中输入
npm i react-file-base64 react-icons react-clock stellar-sdk
(我的 UI 需要 react-icons 和 react-clock,但您的 UI 可能不需要它们)。
瞧,我们现在有了一块空白画布,可以随意涂鸦了,那就开始吧。
在终端中,运行
npm run dev
如果你已经完成了到目前为止的所有操作,你会看到类似这样的内容
2. 添加 UI,并创建落地页的基本框架
好了,这是本教程中唯一需要你认真对待的部分。不过别担心,在开发 DApp 的过程中,你不会遇到任何困难。我在这里做的是创建一个简单的落地页,其中我会:
- 创建了一个导航栏,显示应用程序的名称。
- 创建了一个侧边栏,它只显示一些社交链接(用于
react-icons
徽标),下面给出了其中一个的代码。 - 创建了功能部分。
- 使用该包创建了一个时钟
react-clock
。 - 创建了包含两个链接的部分,分别是“我有资源”和“我需要资源”。
(PS:如果您感觉不太有创意,这里是所有这些组件的代码。)
这里需要注意的是这两个按钮。
我们现在要做的(正如我们在工作流程中看到的)是判断用户是学生还是捐赠者。
- 如果他们是学生,我们会将他们带到一个带有表格的页面来提交信息。
- 如果他们是捐赠者,我们会将他们带到一个包含学生详细信息的页面。
NextJs 中如何设置路由?
我们需要根据用户点击的按钮将用户路由到两个不同的位置。
这里有一个关于 NextJs 路由的速成课程。NextJs 的路由比 React 好得多,后者遵循基于文件的路由结构。这意味着什么?嗯,你在应用文件
夹 内的文件夹中创建的任何新文件,都会为该文件创建一个新的路由。
让我们通过一个例子来理解:
您正在本地开发,因此您的主页的 URL 是http://localhost:3000/
现在在您的文件夹中,假设您在文件夹内添加了一个名为的文件夹students
,并在students
文件夹中创建一个page.jsx
文件,然后添加一个简单的 React 功能组件,如下所示:
然后,这将导致形成一条新路线,其 URL 为http://localhost:3000/students
作为练习,创建一个/new
路由。在这里我们将创建一个表单,供学生上传他们的信息。
该use client;
指令。
完整解释这一点需要一些时间(如果您需要专门的 Next.js 教程,请告诉我),但您会use client;
在这个代码库的许多文件顶部看到这些内容。您需要知道的是,每当您需要与客户端交互(例如,react-hooks)时,都需要use client
在页面的最顶部编写,如下所示:
3. 创建学生注册表格
到目前为止,你表现得很好。现在我们开始深入探讨,所以如果你需要吃点零食或喝杯咖啡,尽管来吧。
好了,接下来app/new/page.jsx
我们要创建表单了。
这是一个简单的表单,用于询问学生一些详细信息。
最终结果如下:
表单的代码在这里。
表单字段会根据你的想法而变化。不过,我会解释一下这里发生的两件重要的事情。
这只是一个简单的表单,所以我们来关注一下真正重要的部分。1
. 使用 base-64 编码:base-64 编码能帮我们做两件事。首先,我们无需为上传图片/文件创建单独的输入类型。其次,更重要的是,它能帮助我们将这些文件和图片转换成字符串,这样我们就可以轻松地将它们存储在数据库中,无需再费心。
以下是如何为图片字段编写输入代码:
<label htmlFor="image" className="flex flex-col font-semibold gap-1" >
Upload your image
<FileBase64 type='file' multiple={false} onDone={({ base64 }) => {
setpost({ ...post, profilePicture: base64 }) }} />
</label>
(在这里,我们所做的只是更新profilePicture
我们为创建的状态部分post
,您可以在下面看到。
const [post, setpost] = useState({
name: "",
address: "",
currentGrade: "",
fieldOfStudy: "",
academicAchievements: "",
householdIncome: "",
needFinancialAid: "",
additionalInfo: "",
profilePicture: "",
transcripts: "",
})
2. 如何以及将表单数据发送到哪里?:我们创建了一个表单,并且一位学生已输入其详细信息。现在,我们需要处理这些信息。为此,我们需要将这些详细信息发送到后端,我们将在那里处理所有逻辑和存储。
因此,让我们在下面的 fetch 函数中命名一个 API 端点,并将数据发送到那里。(我们接下来将创建这个端点。)
handlePostSubmit
可以在submit
表单上的按钮上调用下面的函数。
好的,这个函数首先尝试是否可以向我们指定的端点(即 )发送POSTapi/posts/create
请求,如果可以,它会将 POST 的数据发送到该端点。
但是,如果失败,它会在控制台中记录发生的错误(目前,它会记录无法找到 API 端点)。
最后,顾名思义,它会将用户重定向到主页。useRouter()
是 Simple React Trivia 的一个导入next/navigation
,我们e.preventDefault()
在此处添加它是为了确保页面在提交表单时不会刷新。
const router = useRouter();
const handlePostSubmit = async (e: any) => {
try {
console.log("The post we are sending is: ", post);
e.preventDefault();
const response = await fetch("api/posts/create", {
method: "POST",
body: JSON.stringify({
name: post.name,
address: post.address,
currentGrade: post.currentGrade,
fieldOfStudy: post.fieldOfStudy,
academicAchievements: post.academicAchievements,
householdIncome: post.householdIncome,
needFinancialAid: post.needFinancialAid,
additionalInfo: post.additionalInfo,
profilePicture: post.profilePicture,
transcripts: post.transcripts,
})
})
} catch (error) {
console.log("Unable to add post. Error in submitting.");
console.log(error);
}
finally {
router.push("/");
}
}
4.设置MongoDB
好的,到目前为止,我们已经将数据发送到 API 端点。我们要如何处理这些数据?当然,我们需要存储它并显示它,对吧?
因此,我们需要一个可以存储数据的地方,瞧,我们进入了下一节——数据库。在本项目中,我们将使用 MongoDB,它是一个 NoSQL 非关系型数据库,非常适合入门项目。
我不会在本节深入讲解 MongoDB 的所有内容,因为那样会使本教程过于冗长。但是,我也不会让你一头雾水。
以下是获取MongoDB 上的免费数据库所需要做的所有事情。
1.前往此页面进行注册。2
.注册后,登录您的 MongoDB Atlas 帐户。3
.您现在需要单击“新建项目”。为您的项目指定一个相关名称(或者如果您喜欢随意命名,也可以使用一个完全随机的名称),然后单击“下一步”。4
.好了,现在您可以将所有内容保留为默认设置,只需确保您已选择共享集群(M0 Santbox)。确定选择了共享集群后,单击“创建集群”。
瞧,您刚刚创建了第一个集群,做得好。现在,我知道您面前的 MongoDB 屏幕看起来很有挑战性,但别担心,我们会轻松完成。
5.现在,前往左侧“安全”选项卡中的“数据库访问”。在那里,您需要点击Add a new database user
,并输入密码和用户名(记住这些)。其他任何内容无需更改。
6.现在,我们需要确保该数据库基本上可以从互联网上的任何地方访问。
为此,我们转到网络访问选项卡,添加一个 IP 地址,然后添加0.0.0.0/0
(这样做是为了确保您可以在任何地址访问此数据库。)
通常我们不这样做,并指定特定的 IP 地址,但对于初学者项目来说,这很好。
好的,现在剩下的就是连接数据库。
7.前往“集群”选项卡,然后单击“连接”。在那里,您将选择“连接您的应用程序”选项卡,然后复制以 开头的字符串mongodb+srv://
。
现在,您已经完成了 MongoDB 的操作。现在只需保存此连接字符串,我们将在安装 Prisma 后立即使用它,这将带我们进入下一步。
5.初始化Prisma
首先,我们到底为什么要用 Prisma?
如果你有这个疑问,那很好。引入新技术和技术栈,让应用程序变得比它应有的复杂度高绝对不是个好主意。但是使用 Prisma(特别是搭配 NextJS 使用)有很多好处,其中最主要的就是不需要connectToDB
一直调用某个函数。
困惑了吗?让我来解释一下。NextJs
遵循所谓的无服务器架构,你需要了解的是,Next.js 中的 API 路由被视为函数。正如你所知,函数一旦完成其用途,就会从调用堆栈中移除。现在这没问题,但是我们的 API 路由会与 MongoDB 数据库通信。因此,每次调用 API 路由时,它都必须再次建立与数据库的连接(这正是我们在connectToDB
函数中写入的内容)。Prisma
帮我们省去了这个麻烦。
我们将在几分钟内快速设置 Prisma。让我们开始吧:
1.在 IDE 中,在项目文件夹中打开一个新终端,然后输入以下命令:npx prisma
2.这只是调用 Prisma CLI,我们现在将设置 Prisma ORM。所以继续
npx prisma init
在此之后,您将看到两样东西,一个.env
文件和一个名为的新目录prisma
。
3.首先,我们来查看.env
文件。在那里你会看到一个名为 的字符串DATABASE_URL
。这是 Prisma 提供的,包含指向某个数据库的虚拟 URL。请将你的MongoDB 连接字符串复制到<username>
,然后粘贴到这里。另外,请确保将和字段替换<password>
为你的用户名和密码。(还记得我们在网络访问步骤中做过这些吗?)
(如果您想知道一位 80 岁的老人在编写本教程,那么这些不是我的资历。)
到目前为止,你做得非常好。现在让我们来探索一下 Prisma 的功能。前往prisma
文件夹,然后打开文件prisma.schema
。
我们将在这里定义数据库的模式。如果您之前使用过 MongoDB,可以将其视为与使用 Mongoose 设置模式相同的操作。
如果觉得这太难了,那就深吸一口气,继续阅读吧。读完之后,一切都会变得清晰易懂。好消息是,一旦你理解了这一点,就可以将 Prisma 添加到你的技能库中,因为 Prisma 的大部分用途都在这里!
好的,在里面prisma.schema
,找到datasource db
并输入你的provider
as mongodb
。
你应该得到类似这样的内容:
好了,现在剩下要做的就是定义数据库的模式。我们在这里要做的是定义数据库的行,也就是要存储每个学生的哪些信息。根据你正在创建的应用,这可能会有所不同,但如果你遵循这个思路,那么模式将如下所示:
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
address String
currentGrade String
fieldOfStudy String
academicAchievements String
householdIncome String
needFinancialAid String
additionalInfo String
profilePicture String
transcripts String
}
(PS:这里我们给这些帖子命名,它们是包含学生数据的卡片)。
这里的格式是 类型ColumnName - ColumnType
。您可以复制粘贴我为 编写的内容id
,但这意味着我们希望自动分配 id,并将其映射到id
MongoDB 默认提供给每一行的 。
现在有两件事。
首先,运行
npm install @prisma/client
然后运行
npx prisma generate
现在,这些命令会发生的情况是,Prisma 将读取您的模式,为该模式创建一个表,并知道当输入数据时,它将存储在那里。
大功告成。在另一个终端中,你可以运行
npx prisma studio
这本质上只是数据库中数据的可视化界面,因此您可以定期检查一切是否正常运行。
大功告成了!如果你能坚持到这里,那就太棒了!这可不是什么小成就。
(我知道,我写到这里都哭了。所以,我们俩都要为自己鼓掌,你会继续学习,我会继续写。)
6. 设置创建学生的 API 路由
好了,还记得我们是如何将数据从表单发送到 API 端点的吗api/posts/create
?现在,我们需要捕获这些数据并将其保存到数据库中。因此,在你的app
文件夹中,创建一个名为 的新文件夹api
,并在其中创建另一个名为 的文件夹posts
,然后在 中posts
创建一个名为 的文件route.js
。
您的文件夹结构应如下所示:
还记得 Next.js 是如何基于文件的路由结构的吗?这只是意味着我们创建了一个路由https://localhost:3000/api/posts/create
。
现在,我们将简单地获取数据,将其保存在数据库中,然后将用户发送回主页。
下面是代码,然后我将解释它的不同部分。
import { PrismaClient } from "@prisma/client";
export const POST = async (req, res) => {
// getting the data in this object. The data is the one we submitted on the form.
const receivedFromBody = await req.json();
console.log(receivedFromBody);
const prisma = new PrismaClient();
try {
const result = await prisma.post.create({
data: {
name: receivedFromBody.name,
address: receivedFromBody.address,
currentGrade: receivedFromBody.currentGrade,
fieldOfStudy: receivedFromBody.fieldOfStudy,
academicAchievements: receivedFromBody.academicAchievements,
householdIncome: receivedFromBody.householdIncome,
needFinancialAid: receivedFromBody.needFinancialAid,
additionalInfo: receivedFromBody.additionalInfo,
profilePicture: receivedFromBody.profilePicture,
transcripts: receivedFromBody.transcripts,
}
});
return new Response(JSON.stringify("StudentDataCreated."), { status: 201 });
} catch (error) {
console.log("Not able to create a student post.");
console.log(error);
return new Response("Failed to store student info.", { status: 500 });
}
}
首先,我们创建一个 Prisma 客户端实例。假设这就是我们connectToDB
之前讨论过的功能。现在,Prisma 将处理与数据库操作相关的所有事务。
剩下的就是相当简单的 API 路由,如果您之前创建过 REST API,那么您不会遇到任何问题,但即使您没有,我们也可以这样做:
- 此请求为 POST 请求,即我们从前端发送一些数据。我们接收的所有数据都在
req
(或 request) 中,而我们发送的数据则在res
(或 respond) 中。 - 我们打开一个 try-catch 代码块(这始终是一个好习惯)。如果出现错误,该代码块会“捕获”它,显示错误,并向用户
catch
发送错误代码(即服务器错误)。500
- 在 try 块内部,我们首先生成一个实例
Prisma Client
。然后,我们将从表单接收到的数据存储在对象中receivedFromBody
并记录下来。 - 然后,我们要求 Prisma 创建一个新用户,其中每个字段都将填充来自的相关数据,并且一旦 Prisma 将这些新数据保存在数据库中,我们就会向用户
receivedFromBody
发送状态代码,即一切正常。201
恭喜!现在,如果您从表单提交您的详细信息,您的详细信息将被保存到数据库中!
但我们也需要查看这些详细信息,对吧?因此,我们在这里也设置一下获取所有学生信息的路由。
7. 设置 API 路由以获取学生列表
这一步其实相当简单。你只需要知道如何使用 Prisma 从数据库中获取所有记录即可,除此之外,你完全可以自行完成。
1.首先,我们来设置一个路由。
在你的app/api/posts
文件夹中,创建一个route.js
。最终的结构应该如下所示:
app/
├── api/
│ ├── posts/
│ └── create/
│ └── route.js
│ │ └── route.js
现在,我们创建的路线是http://localhost:3000/api/posts
2.太好了,现在我们需要做的就是获取已输入的学生列表。为了更好地理解,我在数据库中输入了一些随机的学生数据。(这些数据来自表单,所以你也可以尝试一下,看看你的全栈应用程序是否正常运行!)
在新创建的route.js
文件中,我们编写以下代码,与上一个代码非常相似,只是在这里,我们要求 Prisma 获取我们数据库中的所有数据。
import { PrismaClient } from "@prisma/client";
export const GET = async (req, res) => {
const prisma = new PrismaClient();
try {
const students = await prisma.post.findMany();
return new Response(JSON.stringify(students), { status: 201 });
} catch (error) {
console.log("We had some error fetching posts.");
return new Response("Error fetching students", { status: 500 });
}
}
其他一切都保持不变。
有趣的是:http://localhost:3000/api/posts
如果您在数据库中输入了任何数据,它将以 JSON 格式显示在这里,就像您调用任何 API 端点时一样。
8. 向潜在捐赠者展示学生名单。
好了,这是我们 CSS/JSX 工作的最后一步了。(你是欢呼雀跃了,还是感到难过?告诉我吧。)
我们在这里要做的就是以一种视觉上吸引人的方式显示这份学生名单。
我们要把它显示在哪里?
记得我们的主页上有两个不同的按钮,现在我们要处理I have resources
按钮。所以,请直接转到app/students/page.jsx
你在步骤 2 中创建的文件。
我们接下来要做什么?
流程相当简单:从 API 获取数据,创建学生卡,并显示所有学生的信息。我们还donate
为每个学生创建一个按钮,之后会让它工作。
由于您的应用程序的前端可能会有所不同,我将在此解释您需要的最重要的部分,并链接整个代码以供您自行判断。
- 获取数据
const [students, setstudents] = useState([])
useEffect(() => {
const getStudents = async () => {
const response = await fetch("api/posts");
const studentsData = await response.json();
console.log(studentsData);
setstudents(studentsData);
}
getStudents();
}, [])
让我们看看。
这里,我们有一个名为 的学生列表状态students
。
我们有一个函数,它getStudents
被包装在一个UseEffect
钩子中,并以[]
为依赖项。这意味着我们希望这个函数在组件渲染时运行,并且只运行一次。这也是合理的,因为我们希望在页面加载时就获取学生列表。
现在,在getStudents
函数内部,我们有一个 fetch 函数,它返回一个Promise
解析为 的response
。然后我们将其转换response
为 JSON 格式,得到 ,studentsData
我们用它来更新 的状态students
。
- 映射学生列表,并为每个学生创建卡片
我们现在要做的是每个完整堆栈的核心,也就是显示数据。
所以我们要做的是将students
我们创建的状态映射到学生的状态上,并将每个学生的信息传递给 StudentCard 组件。
映射代码:
<div>
{students.length > 0 ?
<div className='flex flex-col justify-start items-center mt-10 gap-6 '>
{students.map((student) => {
return (
<div className='w-full'>
<StudentCard student={student} />
</div>
)
})}
</div>
:
"Fetching students..."}
</div>
在这里,我们还声明,如果students.length
不大于零(即列表为空,那么我们将渲染一个非常简单的加载器,内容如下Fetching students...
(我没有在这里写这些代码块的原因是为了避免让本教程对于您已经知道如何编码的组件显得冗长。)
如果您要以自己的方式显示列表(您应该这样做),只需记住为每个学生添加一个按钮,按钮上只显示Donate
,并附加一个空函数即可。
(我们稍后会编写相关的逻辑。)
完成后,你会看到根据你的设想绘制的学生名单。
我的名单如下:
使用 Stellar SDK
好啦好啦!我们终于来了。你得费劲钻研整个前端(除非你热爱构建酷炫的前端),才能最终到达这里。在这里,我们将把你的应用从 Web2 提升到 Web3。
再喝杯咖啡休息一下,散散步。等你感觉精神焕发的时候再回来。
焕然一新?太棒了!我们开始吧。
1. 熟悉 Stellar 的 SDK
Stellar 的 SDK 到底是什么?我就不多说了,直接告诉你 Stellar 的 SDK 到底对你来说意味着什么。
如果您像我一样不懂 Rust(这是一种编写大量智能合约的语言。智能合约有点像 DApp 的后端逻辑),但仍想构建 DApp,那么这个 SDK 非常适合您。它将简化您在 Stellar 网络上的服务构建,并允许您轻松创建交易。
我第一次听说这个挑战时,心想我肯定参加不了,因为我不懂 Rust。不过你看我的参赛作品,还有个 DApp,可见这个 SDK 有多强大。
您会听到以下几个术语:
1. 测试网:测试网本质上就像你的开发分支。它是区块链的一个版本,仅用于测试和开发。2
. 流明币:流明币(XLM)是恒星网络的数字货币。你可以把它们想象成你在游戏机上玩游戏时使用的代币。只不过,在恒星网络上,你只能使用它们。
2.获取测试币代币账户。
由于我们正在测试此应用,我们需要同时充当捐赠者和接收者。因此,让我们为捐赠者和接收者创建两个测试帐户。
让我们先研究一下接收器。
点击此处的链接。
这是 Stellar 的实验室。在这里,您可以看到您位于测试网络上。我们需要生成一个密钥对。(您可以将其视为您账户的用户名和密码。)
您将获得一个Public Key
和一个Secret key
。获取您的公钥,并在我们使用此密钥创建的表单中添加“创建学生”作为字段的输入address
。
还要将这些保存在某个地方,以便我们以后可以访问该帐户。
查看 的正下方Keypair generator
,你会发现一个FriendBot
。这会给你一些测试代币,即在现实生活中没有价值的代币,但可以用来测试交易。
粘贴你的公钥,然后点击“获取测试网络流明”。
同时,保存你的公钥和私钥。
全部完成后,这将为您提供测试网络上的 10K Lumens,供您尝试交易并查看一切是否正常。
(有趣的轶事:我最初没有给接收者任何测试积分,交易也没有成功。我花了一个小时才解决这个问题:'))。
让我们为捐赠者创建一个帐户。
捐赠者的账户流程将是相同的,我们还需要一些代币。
因此,重复上述过程,存储密钥,您就准备好所需的一切了。
3. 创建表格,供捐赠者提供相关详细信息。
让我们快速完成前端的最后几个部分。
假设我们的捐赠者已经决定了捐赠对象。我们需要三样东西:
- 捐赠金额。
- 捐赠者的账户详细信息。
- 学生的 Stellar 账户。
我们已经有1和3,让我们创建一个表单来输入它们。
(我在这里创建了一个弹出模式。但如果这看起来太复杂,您也可以在其中创建一个新表单StudentCard.jsx
。)
如果您想让表单弹出,则步骤如下:
- 为 创建一个状态
isModalVisible
,并将其设置为 false。 - 当用户点击
Donate
按钮时,将状态设置为 true。 - 在你的 JSX 中,按如下方式呈现模式:
{isModalVisible && <StudentPaymentModal receiverAddress={student.address} handleCloseSite={handleCloseSite} />}
(我在这里发送学生地址和关闭模态框的函数作为道具。)
捐款表格
模态框如下所示:
这里有两个字段,amount
和privateKey
(某种程度上是捐赠者的账户密码)。
让我们关注一下提交按钮。这很重要。
我们需要获取这些详细信息并将其发送到后端,以便进行交易。
因此,转到您的api
文件夹,并创建一个名为的新目录transactions
,并在其中创建一个名为的文件route.js
。
单击提交按钮时,将运行以下函数:
const handleDonation = async () => {
try {
const response = await fetch("api/transactions", {
method: "POST",
body: JSON.stringify({
receiverAddress: receiverAddress,
amount: amount,
privateKey: privateKey
})
})
} catch (error) {
console.error("We had some troubles making a donation", error);
}
}
这里,我们只是向该 API 路由发出了一个 POST 请求。我们还发送了amount
、receiverAddress
以及捐赠者的privateKey
。所以,让我们来编写该路由的逻辑。
PS如果您需要查看其 CSS,这里是模式的代码。
4.创建处理捐赠的 API 路由。
这就是我们与恒星网络互动的关键所在。
我将分部分解释代码的每个部分,API 路由的完整代码可以在这里找到。
1.获取从前端发送过来的值:
const { receiverAddress, amount, privateKey } = await req.json();
console.log("Received values:", receiverAddress, amount, privateKey);
这只是我们从表单中获取发送的值并记录它们。
2.初始化Stellar服务器:
const server = new StellarSDK.Horizon.Server('https://horizon-testnet.stellar.org');
在这里,我们创建 SDK 服务器的实例(如果您熟悉 OOP,则为 Server 类)。通过它,我们可以与区块链上的测试网络服务器进行通信。
3.获取捐赠者详细信息:
const donorKeypair = StellarSDK.Keypair.fromSecret(privateKey);
// console.log("Donor account:", donorKeypair);
// Fetch the donor account from the network
const donorAccount = await server.loadAccount(donorKeypair.publicKey());
console.log("Donor account:", donorAccount);
好吧,你还记得我们是怎么向捐赠者索要私钥的吗?我们是通过私钥检索他们的账户信息,以便在发送交易时使用。
现在,一切就绪,我们可以发送交易了。
4.创建交易:
const transaction = new StellarSDK.TransactionBuilder(donorAccount, {
fee: StellarSDK.BASE_FEE,
networkPassphrase: StellarSDK.Networks.TESTNET,
})
.addOperation(
StellarSDK.Operation.payment({
destination: receiverAddress,
asset: StellarSDK.Asset.native(),
amount: amount,
})
)
.setTimeout(30)
.build();
// Sign the transaction
transaction.sign(donorKeypair);
让我们来分析一下。
- 我们将它
fee
作为进行交易所需花费的费用。 - 我们正在
TESTNET
网络上创建此交易。 - 这笔付款将支付给
receiverAddress
。 - 这个金额是
amount
我们从前端收到的,也就是捐赠者想要捐赠的金额。 setTimeout
用于确保进程在超过指定时间时停止。- 最后,我们
build
或者创建这个。
5.发送交易:
const transactionResult = await server.submitTransaction(transaction);
console.log("Transaction successful:", transactionResult);
// Return success response
return new Response(JSON.stringify("Transaction Successful."), { status: 201 });
在这里,我们提交此交易,如果成功,我们将发送一条回复“一切顺利。”。
6.跟踪错误:
catch (error) {
console.error("Transaction failed:", error);
// Return error response
return new Response(JSON.stringify("Failed to transact"), { status: 500 });
}
虽然上面的代码在try
块中,但该catch
块是一个简单的块,它会通知我们错误,并将结果发送回前端。
综合起来,完整的代码如下所示。(如果你懒得打开 GitHub 链接的话,你懂的:p)
import * as StellarSDK from "stellar-sdk";
export const POST = async (req: any, res: any) => {
// Extract data from the request
const { receiverAddress, amount, privateKey } = await req.json();
console.log("Received values:", receiverAddress, amount, privateKey);
// Initialize the Stellar server
const server = new StellarSDK.Horizon.Server('https://horizon-testnet.stellar.org');
const donorKeypair = StellarSDK.Keypair.fromSecret(privateKey);
// console.log("Donor account:", donorKeypair);
// Fetch the donor account from the network
const donorAccount = await server.loadAccount(donorKeypair.publicKey());
console.log("Donor account:", donorAccount);
try {
// Create a Keypair from the private key
// Build the transaction
const transaction = new StellarSDK.TransactionBuilder(donorAccount, {
fee: StellarSDK.BASE_FEE,
networkPassphrase: StellarSDK.Networks.TESTNET,
})
.addOperation(
StellarSDK.Operation.payment({
destination: receiverAddress,
asset: StellarSDK.Asset.native(),
amount: amount,
})
)
.setTimeout(30)
.build();
// Sign the transaction
transaction.sign(donorKeypair);
// Send the transaction
const transactionResult = await server.submitTransaction(transaction);
console.log("Transaction successful:", transactionResult);
// Return success response
return new Response(JSON.stringify("Transaction Successful."), { status: 201 });
} catch (error) {
console.error("Transaction failed:", error);
// Return error response
return new Response(JSON.stringify("Failed to transact"), { status: 500 });
}
};
让我们测试一下!
如果您正确遵循了所有步骤,那么您现在就可以将流明从捐赠者帐户发送到学生帐户。
您可以按照以下方式进行测试:
1.PublicKey
将下方链接中的替换为您的捐赠者公钥,然后前往balances
查看当前余额。请记下当前余额。https://horizon-testnet.stellar.org/accounts/PublicKey
2.返回应用程序。打开模式,并将一些 XLM 发送到接收方账户。
3.重复步骤 1,但这次检查您的余额。它会显示您之前的 XLM -(发送金额 + 交易费)。
(这是我发送 20 流明后学生账户余额的图片。)
我们刚才成功了吗?是的!这就是你的 DApp,它允许你通过 Stellar 测试网络将 XLM 从一个账户发送到另一个账户。
5. 结论
经过无数次的滚动,无数次的按键,以及我希望简单易懂的教程,我们终于到达了终点。
又或者,是终点的开始?
因为我们现在已经解锁了无限的可能性。仔细想想,大多数 Web2 应用只是 CRUD(创建-读取-更新-删除)应用。同样,大多数 Web3 应用的核心也是交易。
现在,您已经创建了一个 DApp,甚至不需要编写智能合约。
下一步是什么?
- 您现在可以用这款应用做很多事情。例如,为学生创建一个仪表盘,显示交易的特殊弹出窗口,确保用户只有当其余额大于其要发送的 XLM 时才能发送交易。
- 您还可以开始学习 Rust,并开始探索 Soroban 来创建成熟的智能合约。
我会时不时地潜伏在评论中,所以如果您的代码因错误而困扰您,请随时询问,以便我和比我更有经验的人查看并帮助您。
我希望你喜欢,不,是爱上这个教程。请告诉我你喜欢哪些地方,以及我可以改进的地方。我真心希望你能以此为起点,创造出一些非常非常酷的东西。当你成功的时候,别忘了给我看看。
你可以在这里找到我。
这是我,Kartik,正在结束 :)