Skip to main content

使用 Ceramic Self.ID 建立主权用户档案

Ceramic 是一个去中心化的数据网络,它允许建立可组合的 Web3 应用程序。由于 Ceramic 将应用数据库去中心化,应用开发者可以在不同的应用中重用数据,并使其自动具有互通性。

在本文中,我们将深入探讨这些说法的实际含义,以及为什么去中心化的数据首先是重要的。

Web3 和数据

在过去的几年里,我们看到了 Web3 的趋势,如 DeFi,NFT,以及最近的 DAO 的爆炸。像以太坊这样的智能合约平台已经向我们表明,像乐高积木一样的 dApps,可以和其他 dApps 一起组成,建立全新的 dApps,具有很大的价值。这一点在建立在其他代币之上的代币、利用其他 DeFi 协议的 DeFi 协议等方面尤为突出。

陶瓷允许你将这种类型的可组合性带到互联网上的数据。这可以是任何种类的数据。档案、社交关系、博客文章、身份、声誉、游戏资产等等。

这是一个抽象的概念,试图将陶瓷网络定义为一个单一的东西是有点棘手的。与以太坊类似,以太坊本身就很难定义(它甚至意味着是一个智能合约平台?),如果我们看一下 Ceramic 可以实现的具体用例和未来愿景的例子,就更容易理解。当我们看看 DeFi 协议、NFT、DAO 等确切的例子时,Ethereum 就更容易理解了--在试图理解 Ceramic 时也可以应用同样的思维。

Web3 中缺乏数据

现在的 web3 市场主要是由金融应用组成的。与代币和数字资产有关的东西。这在某种程度上是由于设计。智能合约在能存储多少数据方面有其固有的局限性,而且可以建立的数据功能也是有限的。

随着我们的进步和 dApps 的成熟,建立更多数据丰富的应用程序的市场需求正在增加。像去中心化的社交媒体、去中心化的信誉平台、去中心化的博客等趋势正在兴起,人们采取了很多技术手段,但在某一方面有所欠缺。

在 Web2 中,这些平台是作为数据孤岛建立的。你的 Twitter 帖子和社交关系被锁定在 Twitter 平台上--你不能把你在 Twitter 上的社交关系转移到 Facebook 上,你也不能把你的 Twitter 帖子转移到 Facebook 上,等等。它们都是作为孤岛建立的。

这将会改变。Ceramic 将赌注押在可互操作的应用程序和可互操作的生态系统上,使其能够超越这些孤岛式的平台。

那么...Ceramic 是做什么的?

Ceramic 正在建立。

  • 一个通用的数据协议
  • 其中数据只能由所有者修改
  • 大批量的数据处理
  • 具有全球数据的可用性和一致性
  • 支持快速查询
  • 具有跨应用程序的可互操作数据
  • 和社区管理

这是很重要的。这也是为什么 Ceramic 本身的定义会很棘手。毕竟,就像以太坊一样,它是一个通用的协议,尽管是针对数据。

为了实现 Ceramic 的真正规模和愿景,需要实现很多的突破。如果 Ceramic 要成为网络的去中心化数据库,它需要能够大规模扩展--比现在任何中心化数据库都要大,因为它们都没有存储整个互联网的数据。

数据也需要在全球范围内提供,并且需要确保 Ceramic 节点运行者为世界其他地方提供这些数据,而不是劫持这些数据。

此外,不仅仅是存储,Ceramic 还需要快速查询和检索数据。用户读取的数据通常比他们写的数据多得多,所以快速读取和查询对 Ceramic 的规模工作极为重要。

所有这些,同时对数据保持高度的安全和隐私,并确保它不会有一天崩溃。

看看今天 Ceramic 可以实现的具体用例,以及拥有一个可变的、去中心化的、通用的数据协议的好处是有帮助的。

陶瓷的使用案例

去中心化的声誉

声誉与一个人的身份高度相关。在 Twitter 上,它是粉丝和喜欢。在 Instagram 上,它是人心。在 Reddit,它是 Karma,而在 StackOverflow,它是积分。

在今天的 web3 生态系统中,dApps 很难做到比上述例子中的集中式声誉系统更好,每个平台都有它自己的声誉系统。这主要是因为存储大量可能随时间变化的去中心化数据是不可行的。而且,即使 Ethereum Layer 2 的要大量减少存储成本,那么非 Ethereum 链呢?当你切换到 NEAR 或 Flow 或 Solana 时,你的声誉会发生什么?

进入 Ceramic。

dApps 可以使用 Ceramic 的数据协议来建立标准化的多链信誉系统。一个用户可以将多个钱包连接到他们的去中心化身份,属于不同的区块链,数据可以被写入和更新自用户在 Ceramic 上的去中心化数据存储。

因此,无论用户使用什么链和 DApp,他们都可以随身携带他们的信誉系统。

社交图谱

与声誉类似,你的社交图谱在当今世界也是严重集中化的。你的 Twitter 粉丝,Facebook 朋友,LinkedIn 连接,和 Snapchat 好友都是不同的孤岛。

随着中心化的社交媒体的审查制度随着时间的推移而增加,而且由于社交媒体公司是世界上最大的公司,其中一些公司有足够的力量操纵整个国家的选举,因此对去中心化的社交媒体的需求从未如此强烈。

然而,如果我们要建立去中心化的社交媒体,让我们试着比旧系统做得更好。与其将用户锁定在一个平台上,我们实际上可以允许互操作性和选择性。

dApps 可以遵循标准化的数据模型来存储帖子和社交图。这些社交图由用户携带到他们想使用的任何 dApp。这意味着产品的竞争在于谁能提供最好的体验,而不是谁拥有最多的供应商锁定。

这也允许拥有更好产品的小型企业和初创企业更容易实现市场渗透。他们可以利用已经存在的底层数据,当用户在他们的平台上注册时,他们的所有数据都会随之而来。对于较大的参与者,可以为该数据模型提供大量的数据而给予经济奖励。

多钱包和多币种身份

你可以推断去中心化的信誉用例,为用户实现普遍的多钱包和多链身份。与其将数据绑在智能合约或基于钱包地址的链外,不如将数据绑在用户的去中心化身份上,这可以由多个钱包在多个链上控制。

这样一来,多链的 dApps 可以为用户的数据提供一个去中心化的,但又是单一的真实来源。

陶瓷的多链架构

在最底层,有一个去中心化的身份。在 Ceramic 上,最常见的去中心化身份的方法是一种叫做 3ID(Three ID)的东西--以 Ceramic Network 背后的团队 3Box Labs 命名。

一个用户可以将多个链上的多个钱包链接到一个 3ID 上。目前,Ceramic 支持 10 多个区块链,并不断增加对更多区块链的支持。

3ID 可以拥有 Ceramic 网络上的数据。Ceramic 上的数据被称为 "流"。因此,每个流有一个所有者(或多个所有者)。

数据流有唯一的 StreamIDs,在数据流的生命周期内保持不变。3IDs 可以修改和更新他们拥有所有权的流的内容。

流有一个创世状态,它是流创建时的初始数据。在创世状态之后,用户可以在流上创建提交,这代表对数据的修改。一个流的最新状态可以通过从创世状态开始并逐一应用所有的提交来计算。最新状态也被称为流的顶端。

使用 Ceramic

Ceramic 提供了一套高级和低级的库和 SDK,可以根据不同的使用情况进行使用。

对于普通的用例,开发者可以使用高层次的 SDK--Self.ID--它抽象了大部分与 3ID 和流相关的复杂工作。

对于复杂或定制的用例,开发人员可以使用较低级别的 Ceramic HTTP 客户端,它连接到他们可以在自己的(或公共节点)上运行的 Ceramic 节点,并手动管理 3IDs 和流数据。

在本教程中,我们将坚持使用 Self.ID 高级别 SDK,否则本教程将变得非常庞大。如果你有兴趣深入研究,请查看下面的推荐资源部分。

Self.ID

Self.ID 是一个单一的、高水平的库,它封装了 3ID 账户、创建和设置 3ID、对 Ceramic 节点的底层调用,所有这些都在一个单一的软件包中进行了优化,以便在浏览器中工作。

该 SDK 还包括流行的用例,如内置的多链用户配置文件,这使得开发人员非常容易检索和存储与 3ID 相关的多链数据。

BUIDL

我们将创建一个简单的 Next.js 应用程序,它使用 Self.ID。它将允许用户使用他们选择的钱包登录网站,该钱包将与他们的 3ID 链接。然后,我们将允许用户将一些数据写入他们的去中心化配置文件,并能够从陶瓷网络中检索。

为了验证这一水平,我们将要求你在最后输入你的配置文件的 StreamID。

让我们从创建一个新的下一个应用程序开始。运行以下命令,在名为 ceramic-tutorial 的文件夹内创建一个新的 Next.js 应用程序

npx create-next-app@latest ceramic-tutorial
cd ceramic-tutorial
npm install @self.id/react @self.id/web key-did-provider-ed25519
npm install ethers web3modal

在你选择的文本编辑器中打开 ceramic-tutorial 文件夹,让我们开始编写代码。

我们需要做的第一件事是在应用程序中添加 Self.ID 的提供者。SDK 提供了一个 Provider 组件,需要添加到你的 Web 应用程序的根部。这将启动 Self.ID 实例,连接到 Ceramic 网络,并使 Ceramic 相关功能在你的应用程序中可用。

要做到这一点,请注意在你的页面文件夹 Next.js 自动创建了一个名为_app.js 的文件。这是你的 web-app 的根,你创建的所有其他页面都是根据这个文件中的配置呈现的。默认情况下,它不做任何特别的事情,只是直接渲染你的页面。在我们的例子中,我们想用 Self.ID 提供者来包装我们的每个组件。

首先,让我们从 Self.ID 导入提供者。在 _app.js 的顶部添加以下一行

import { Provider } from "@self.id/react";
function MyApp({ Component, pageProps }) {
return (
<Provider client={{ ceramic: "testnet-clay" }}>
<Component {...pageProps} />;
</Provider>
);
}

我们还为提供者指定了一个配置选项。具体来说,我们说我们希望 Provider 连接到 Ceramic 的 Clay Test Network。

让我们确保到目前为止一切正常。在你的终端运行以下程序

npm run dev

你的网站应该已经启动并运行在 http://localhost:3000

修改样式

最后,在我们开始为实际应用编写代码之前,将 style/Home.module.css 中的 CSS 替换为以下内容。

.main {
min-height: 100vh;
}

.navbar {
height: 10%;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-top: 1%;
padding-bottom: 1%;
padding-left: 2%;
padding-right: 2%;
background-color: orange;
}

.content {
height: 80%;
width: 100%;
padding-left: 5%;
padding-right: 5%;
display: flex;
flex-direction: column;
align-items: center;
}

.flexCol {
display: flex;
flex-direction: column;
align-items: center;
}

.connection {
margin-top: 2%;
}

.mt2 {
margin-top: 2%;
}

.subtitle {
font-size: 20px;
font-weight: 400;
}

.title {
font-size: 24px;
font-weight: 500;
}

.button {
background-color: azure;
padding: 0.5%;
border-radius: 10%;
border: 0px;
font-size: 16px;
cursor: pointer;
}

.button:hover {
background-color: beige;
}

修改页面代码

pages/index.js

import { Web3Provider } from "@ethersproject/providers";
import { useEffect, useRef, useState } from "react";
import Web3Modal from "web3modal";

const web3ModalRef = useRef();

const getProvider = async () => {
const provider = await web3ModalRef.current.connect();
const wrappedProvider = new Web3Provider(provider);
return wrappedProvider;
};

这个函数将提示用户连接他们的 Ethereum 钱包,如果尚未连接,然后返回一个 Web3Provider。然而,如果你现在尝试运行它,它将失败,因为我们还没有初始化 web3Modal。

在我们初始化 Web3Modal 之前,我们将设置一个由 Self.ID SDK 提供的 React Hook。Self.ID 提供了一个名为 useViewerConnection 的钩子,它为我们提供了一个简单的方法来连接和断开与陶瓷网络的连接。添加以下导入

import { useViewerConnection } from "@self.id/react";
// 初始化 hook
const [connection, connect, disconnect] = useViewerConnection();

useEffect(() => {
if (connection.status !== "connected") {
web3ModalRef.current = new Web3Modal({
network: "goerli",
providerOptions: {},
disableInjectedProvider: false,
});
}
}, [connection.status]);

这段代码我们以前已经看过很多次了。唯一不同的是这里的条件。我们正在检查,如果用户还没有连接到 Ceramic,我们将初始化 web3Modal。

我们需要连接到 Ceramic 网络的最后一件事是一个叫做 EthereumAuthProvider 的东西。它是由 Self.ID SDK 导出的一个类,它接受一个 Ethereum 提供者和一个地址作为参数,并使用它来连接你的 Ethereum 钱包和你的 3ID。

为了设置这个,让我们首先导入它

import { EthereumAuthProvider } from "@self.id/web";

const connectToSelfID = async () => {
const ethereumAuthProvider = await getEthereumAuthProvider();
connect(ethereumAuthProvider);
};

const getEthereumAuthProvider = async () => {
const wrappedProvider = await getProvider();
const signer = wrappedProvider.getSigner();
const address = await signer.getAddress();
return new EthereumAuthProvider(wrappedProvider.provider, address);
};

getEthereumAuthProvider 创建一个 EthereumAuthProvider 实例。

你可能想知道为什么我们要把 wrappedProvider.provider 传递给它而不是直接传递给 wrappedProvider。这是因为 ethers 用辅助函数抽象了低级别的提供者调用,所以它更容易被开发人员使用,但由于不是每个人都使用 ethers.js,Self.ID 维护了一个实际提供者规范的通用接口,而不是 ethers 包装的版本。我们可以通过 wrappedProvider 上的 provider 属性来访问实际的提供者实例。 connectToSelfID 使用这个 Ethereum Auth Provider,并调用我们从 useViewerConnection 钩子得到的 connect 函数,它为我们处理了其他一切。

现在,让我们来看看前端的情况。

在你的首页函数的末尾添加以下代码

return (
<div className={styles.main}>
<div className={styles.navbar}>
<span className={styles.title}>Ceramic Demo</span>
{connection.status === "connected" ? (
<span className={styles.subtitle}>Connected</span>
) : (
<button
onClick={connectToSelfID}
className={styles.button}
disabled={connection.status === "connecting"}
>
Connect
</button>
)}
</div>

<div className={styles.content}>
<div className={styles.connection}>
{connection.status === "connected" ? (
<div>
<span className={styles.subtitle}>
Your 3ID is {connection.selfID.id}
</span>
<RecordSetter />
</div>
) : (
<span className={styles.subtitle}>
Connect with your wallet to access your 3ID
</span>
)}
</div>
</div>
</div>
);

为了解释,这里是发生了什么。在页面的右上方,我们有条件地呈现一个连接按钮,将你连接到 SelfID,或者如果你已经连接了,它只是说连接了。然后,在页面的主体中,如果你已经连接了,我们显示你的 3ID,并渲染一个我们还没有创建的名为 RecordSetter 的组件(我们很快就会创建)。如果你没有连接,我们会渲染一些文字,要求你先连接你的钱包。

到目前为止,我们可以通过 Self.ID 连接到 Ceramic 网络,但是在 Ceramic 上实际存储和检索数据呢?

我们将考虑在 Ceramic 上建立一个去中心化的配置文件的用例。值得庆幸的是,这是一个很常见的用例,Self.ID 为创建和编辑你的个人资料提供了内置的支持。在本教程中,我们将只为你的 3ID 设置一个名字并进行更新,但你可以扩展它以包括各种其他属性,如头像图片、你的社交媒体链接、描述、你的年龄、性别等等。为了便于阅读,我们将把它划分为第二个 React 组件。这就是我们上面使用的 RecordSetter 组件。

首先,在同一文件中创建一个新的组件。在你的主页函数之外,创建一个新的函数,像这样

function RecordSetter() {}

我们将使用 Self.ID 提供给我们的另一个钩子,叫做 useViewerRecord,它允许在 Ceramic Network 上存储和检索个人资料信息。让我们首先导入它

import { useViewerRecord } from "@self.id/react";

现在,让我们在 RecordSetter 组件中使用这个钩子。

const record = useViewerRecord("basicProfile");

const updateRecordName = async (name) => {
await record.merge({
name: name,
});
};

我们还创建了一个辅助函数来更新存储在我们记录中的名字(Ceramic 上的数据)。它接受一个参数 name 并更新记录。

最后,让我们也为 name 创建一个状态变量,你可以输入它来更新你的记录

const [name, setName] = useState("");

对于前端部分,在你的 RecordSetter 函数的末尾添加以下内容

return (
<div className={styles.content}>
<div className={styles.mt2}>
{record.content ? (
<div className={styles.flexCol}>
<span className={styles.subtitle}>Hello {record.content.name}!</span>

<span>
The above name was loaded from Ceramic Network. Try updating it
below.
</span>
</div>
) : (
<span>
You do not have a profile record attached to your 3ID. Create a basic
profile by setting a name below.
</span>
)}
</div>

<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
className={styles.mt2}
/>
<button onClick={() => updateRecordName(name)}>Update</button>
</div>
);

这段代码主要渲染 Hi ${name}。如果你在 Ceramic 中设置了用户信息则会显示,如果没有设置,会显示一个提示让你设置,可以在输入框里填写并点击更新按钮。

下面就可以运行了

npm run dev

或者刷新你的网页,如果你已经运行了它,你应该能够通过你的 Metamask 钱包连接到 Ceramic Network,并在你的分散的配置文件上设置一个名字,同时也可以更新它!

要看我们所建立的最终代码,你可以访问 Github。

希望这篇文章可以让你偷窥到如何使用 Ceramic,并帮助你了解 Ceramic 的重要性的原因。

为了验证这一水平,请从网页上复制你的整个 did:3id:...字符串,并将其粘贴到下面的验证框中。

在 Discord 的展示频道中展示你的网站,并像往常一样,随时可以提出任何问题