Sui钱包开发简明教程
本指南旨在帮助你揭开Sui钱包和相关概念的神秘面纱,让各个级别的开发人员都能轻松理解。
一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK
如果你是 Sui 和 web3 领域的新手,那么大量的信息和概念可能会让你望而生畏。本指南旨在帮助你揭开钱包和相关概念的神秘面纱,让各个级别的开发人员都能轻松理解。
1、什么是钱包?
在区块链和加密货币的背景下,钱包是指允许个人安全存储、管理和发送数字资产的软件。钱包的两个主要功能是保护你的私钥安全并允许你安全地与区块链交互。
所有区块链技术的核心是公钥加密的概念,它允许生成由私钥(保密)和可与任何人共享的公钥组成的密钥对。在区块链的背景下,基于此密钥对生成特定地址,充当允许接收硬币和代币的公钥。只有相应私钥的持有者才能访问这些数字资产,并可以将它们发送到市场或其他地址。以下是密钥对和 Sui 地址的示例:
Private key (base58 encoding)
4y9tCUuXv9gZKJf1cBL4qtWMCLrYzYeYWze5ARrCYUVru7op3EyYPBHTSkwfr7bvg3s4HQotTpWDHL8DjZEqTAtL
Public key (base58 encoding)
5LFt9j5DFwsVWBdLEMa7bxAte3Zco8UhuQn9iK6XScAx
Sui Address
0x505c1becba4055d0c0be153479064df05cd078f3
私钥的安全性对于用户来说是一个至关重要的问题,因为保护私钥的责任完全落在个人身上。与传统的在线账户不同,私钥没有“忘记密码”选项,恢复丢失的密钥的唯一方法是通过恢复短语,必须事先将其写下来并存储在安全的位置。此外,如果私钥被共享,任何获得它的人都可以控制相关的数字资产,而无法逆转未经授权的交易。鉴于这些风险的严重性,拥有安全的钱包软件来存储私钥并处理区块链交易而没有泄露密钥的风险至关重要。钱包有几种不同的形式:
- 软件钱包:纯软件钱包,你的私钥以加密形式存储在计算机上。这是最容易设置的,但也存在风险,因为你必须信任钱包提供商来保证你的秘密安全。某些 web3 市场和钱包被描述为非托管的,这意味着你无法访问私钥,而是依靠传统的电子邮件/密码登录来访问你的资金。在这些情况下,你无法完全控制您的资产,如果公司破产或倒闭,你可能会失去一切。许多钱包确实允许你访问私钥,并允许将你的资产导出到另一个地址。
- 硬件钱包:一种安全强化的物理设备,用于生成和存储你的私钥。当你创建密钥对时,会生成一个恢复短语,你可以将其写下来并存储起来,以防你丢失物理设备。此后,你的私钥永远不会因任何原因离开此设备。这些钱包还附带软件,与纯软件钱包的主要区别在于私钥完全由你控制。
- 纸钱包:纸钱包只是一张纸,上面印有密钥对——这里没有实际的钱包功能。这是一张纸……
钱包如何在不暴露私钥的情况下与区块链交互?通过利用交易的概念。当用户将他们的钱包连接到网站时,可以准备交易并将其发送到钱包进行审核和批准。未经用户明确授权,任何数字资产都不能从钱包中转出。如果用户选择继续交易,则使用私钥对交易进行签名,然后将其提交给网络执行。恶意行为者无法逆转用户在交易上的签名并推断出他们的私钥。此外,如果无法访问用户的私钥,任何人都不可能冒充他们的签名。
如今,大多数钱包都以浏览器扩展的形式提供。原因是这是将网站的 web2 世界与区块链的 web3 世界连接起来的最简单方法。浏览器扩展可以改变任何网站的任何部分,并有助于使与 web3 交互的体验更加愉快,但这是以所有这些扩展都能够读取和更改你访问的每个网站为代价的。跟踪你正在安装的扩展以及它们是否可以信任变得非常重要。毫无疑问,未来会有更好的解决方案来整合 web2 和 web3,但就目前而言,这是我们跨越这座桥梁的最佳选择。
2、钱包软件模式
如果你一直在探索 Sui 和 Solana 等区块链,可能遇到过“钱包适配器”、“钱包标准”、“钱包适配器钱包”、“移动钱包适配器”等术语。理解这些概念可能具有挑战性,所以让我们澄清这些术语背后的原因。
首先,这些概念非常年轻,并且正在不断发展。开发人员正在尝试找到应用程序与钱包交互的最简单方法并建立标准界面。假设我们正在开发一个需要与钱包交互的网站。最初,没有标准的方法来做到这一点,每个钱包都只是有自己的 API——这意味着你必须为每个想要交互的钱包提供商实现代码。如果你决定坚持使用单个钱包提供商,许多用户将无法使用你的应用程序。
这就是钱包适配器模式的用武之地。钱包适配器作为一个单独的包,允许支持许多不同的钱包。它公开一个标准接口,钱包开发人员实现此接口以在内部与他们自己的 API 进行交互。之后,他们在 Github 上发出拉取请求,将最终的适配器代码放入包中。这种模式使应用程序开发人员更容易与钱包交互,但维护单个适配器包对维护者和钱包开发人员来说都很麻烦。还有一个代码膨胀的问题,因为应用程序开发人员需要将所有这些钱包的代码与他们自己的代码一起打包。这实际上不应该是必要的,这就是为什么最近开发了钱包标准的想法。
钱包标准为钱包开发人员提供了一个标准接口来实现,但他们不必经历将代码提交给中央包的过程。相反,wallet-standard 提供了一种允许任何钱包在浏览器窗口中注册的方法。网站开发人员现在可以直接与 wallet-standard 包交互,并获取要交互的注册钱包列表。这种方法避免了与适配器模式相关的代码膨胀和维护。相反,钱包扩展提供了通过标准接口公开的钱包代码。
3、实现钱包
wallet-standard 库可以在这里找到。这提供了可用作任何区块链钱包基础的标准接口。Sui 在此处提供了自己的钱包标准包。这是一个非常轻量的包,它提供了一些 Sui 特定的类型和常量,以简化与 Sui 兼容的钱包的开发。此包还从原始钱包标准中导入了核心功能,因此我们无需在钱包代码中导入这两个包。
实现标准非常简单:我们创建一个类并实现一系列所需的函数。我将在本文底部附上整个类的副本。请记住,本文纯粹是关于实现钱包标准的,在构建可用于生产的钱包扩展时,还有许多进一步的安全考虑因素。从基本功能开始,我们类的脚手架如下所示:
import { Wallet, SUI_CHAINS } from "@mysten/wallet-standard";
export class MySuiWallet implements Wallet {
// Return the version of the Wallet Standard this implements.
get version() {
return "1.0.0" as const;
}
// Return the name of this wallet.
get name() {
return "My Sui Wallet";
}
// Return the icon of this wallet (the one below is the Sui icon).
get icon() {
return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzIiIGhlaWdodD0iNzIiIHZpZXdCb3g9IjAgMCA3MiA3MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjcyIiBoZWlnaHQ9IjcyIiByeD0iMTYiIGZpbGw9IiM2RkJDRjAiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMC40MjEzIDUyLjc4MzhDMjMuNjQ5NiA1OC4zNzYgMjkuNDMyMSA2MS43MTQyIDM1Ljg4ODggNjEuNzE0MkM0Mi4zNDU1IDYxLjcxNDIgNDguMTI3IDU4LjM3NiA1MS4zNTY0IDUyLjc4MzhDNTQuNTg0OCA0Ny4xOTI2IDU0LjU4NDggNDAuNTE2MyA1MS4zNTY0IDM0LjkyNEwzNy43NTI0IDExLjM2MTVDMzYuOTI0MSA5LjkyNzAxIDM0Ljg1MzUgOS45MjcwMSAzNC4wMjUzIDExLjM2MTVMMjAuNDIxMyAzNC45MjRDMTcuMTkyOSA0MC41MTUyIDE3LjE5MjkgNDcuMTkxNSAyMC40MjEzIDUyLjc4MzhaTTMyLjA1NjYgMjIuNTcxM0wzNC45NTcxIDE3LjU0NzRDMzUuMzcxMiAxNi44MzAxIDM2LjQwNjUgMTYuODMwMSAzNi44MjA2IDE3LjU0NzRMNDcuOTc5MSAzNi44NzQ4QzUwLjAyOTEgNDAuNDI1NCA1MC40MTM5IDQ0LjUzNSA0OS4xMzM1IDQ4LjI5NTRDNDkuMDAwMiA0Ny42ODE5IDQ4LjgxMzggNDcuMDU0MiA0OC41NjI2IDQ2LjQyMDFDNDcuMDIxMyA0Mi41MzA0IDQzLjUzNjMgMzkuNTI4OSAzOC4yMDIzIDM3LjQ5ODJDMzQuNTM1MSAzNi4xMDcxIDMyLjE5NDMgMzQuMDYxMyAzMS4yNDMxIDMxLjQxNzFDMzAuMDE4IDI4LjAwODkgMzEuMjk3NiAyNC4yOTI0IDMyLjA1NjYgMjIuNTcxM1pNMjcuMTEwNyAzMS4xMzc5TDIzLjc5ODYgMzYuODc0OEMyMS4yNzQ4IDQxLjI0NTkgMjEuMjc0OCA0Ni40NjQxIDIzLjc5ODYgNTAuODM1M0MyNi4zMjIzIDU1LjIwNjQgMzAuODQxMyA1Ny44MTUgMzUuODg4OCA1Ny44MTVDMzkuMjQxMyA1Ny44MTUgNDIuMzYxNSA1Ni42NjMzIDQ0LjgxODQgNTQuNjA4OEM0NS4xMzg4IDUzLjgwMjEgNDYuMTMxIDUwLjg0OTIgNDQuOTA1MiA0Ny44MDU4QzQzLjc3MyA0NC45OTU0IDQxLjA0ODIgNDIuNzUxOSAzNi44MDYxIDQxLjEzNkMzMi4wMTEgMzkuMzE3MSAyOC44OTU4IDM2LjQ3NzQgMjcuNTQ4NiAzMi42OTg0QzI3LjM2MzEgMzIuMTc4MSAyNy4yMTg5IDMxLjY1NjggMjcuMTEwNyAzMS4xMzc5WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+" as const;
}
// Return the list of chains this wallet supports (devnet, localnet, etc).
get chains() {
return SUI_CHAINS;
}
// Return connected accounts adhering to the `WalletAccount` interface.
get accounts() {
return [];
}
// Return the features this wallet supports.
get features() {
return {};
}
}
你可以阅读注释以了解这些函数的作用 - 大多数函数都非常简单。这里值得注意的函数是 features
,它是钱包标准接口的一部分,它为开发人员提供了一种简单的方法来检查支持哪些功能,然后调用这些函数。
该标准提供了两个必需的功能 standard:connect
和 standard:events
。还有可选的功能,例如 standard:disconnect
,它允许我们在断开连接时运行代码。这些功能旨在由任何区块链扩展,因此 Sui 需要包含第三个功能 sui:signAndExecuteTransaction
。这就是我们的 features
函数的样子:
get features(): ConnectFeature & DisconnectFeature & EventsFeature & SuiSignAndExecuteTransactionFeature {
return {
"standard:connect": {
version: "1.0.0",
connect: this.connect,
},
"standard:disconnect": {
version: "1.0.0",
disconnect: this.disconnect,
},
"standard:events": {
version: "1.0.0",
on: this.#on,
},
"sui:signAndExecuteTransaction": {
version: "1.0.0",
signAndExecuteTransaction: this.signAndExecuteTransaction,
}
};
}
对于我们想要支持的每个功能,我们都会实现相应的函数并在此处引用该函数。 events
函数用于设置事件处理。 signAndExecuteTransaction
函数基本上就是我们对简单钱包所需的全部功能。该函数接收待批准的交易,然后钱包对其进行签名并发送到 Sui 网络执行。我们在这里定义了四个功能,因此我们需要实现上面提到的四个函数。在此之前,我们需要向我们的类添加一个构造函数和一些内部状态:
export class MySuiWallet implements Wallet {
#listeners: { [E in EventsNames]?: EventsListeners[E][] } = {};
#account: ReadonlyWalletAccount | null;
#keypair: Ed25519Keypair;
#provider: JsonRpcProvider;
#signer: RawSigner;
constructor(keypair: Ed25519Keypair, network: string | Network = Network.LOCAL) {
this.#keypair = keypair;
this.#account = null;
this.#provider = new JsonRpcProvider(network);
this.#signer = new RawSigner(
this.#keypair,
this.#provider,
);
}
// ...
// Return connected accounts adhering to the `WalletAccount` interface.
get accounts() {
return this.#account ? [this.#account] : [];
}
// ...
我们添加了一个用于事件处理的监听器变量和一个用于存储已连接帐户详细信息的单个帐户。对于这个简单示例,我们将在创建时将现有密钥对传递给钱包。可用于生产的钱包具有密码保护功能,并使用保险库/加密来降低风险,这超出了本博客文章的范围。我们还定义了提供者和签名者。这些是 Sui 提供的类型。您可能已经猜到了,签名者用于签署和执行交易。提供者为我们处理 JSON RPC 调用。如果您不知道那是什么,RPC 代表“远程过程调用”,是许多区块链使用的简单通信协议。我们不需要担心 RPC 的细节,关于提供者,最重要的是要注意它将连接到我们选择的网络并向该网络发送交易。
请注意,我们还可以实现帐户获取器,因为我们现在有一个 #account
变量可以引用。现在我们有了构造函数,让我们实现事件处理功能,因为它将被其他功能引用。这些函数是从此处的钱包标准示例中复制而来的。这是样板事件处理代码,您可以随意替换你喜欢的任何事件库或方法。
#on: EventsOnMethod = (event, listener) => {
this.#listeners[event]?.push(listener) || (this.#listeners[event] = [listener]);
return (): void => this.#off(event, listener);
}
#emit<E extends EventsNames>(event: E, ...args: Parameters<EventsListeners[E]>): void {
// eslint-disable-next-line prefer-spread
this.#listeners[event]?.forEach((listener) => listener.apply(null, args));
}
#off<E extends EventsNames>(event: E, listener: EventsListeners[E]): void {
this.#listeners[event] = this.#listeners[event]?.filter((existingListener) => listener !== existingListener);
}
设置事件后,我们现在可以实现连接方法:
async connect() {
this.#account = new ReadonlyWalletAccount({
address: this.#keypair.getPublicKey().toSuiAddress(),
publicKey: this.#keypair.getPublicKey().toBytes(),
chains: this.chains,
// The features that this account supports. This can be a subset of the wallet's supported features.
features: ["sui:signAndExecuteTransaction"],
});
this.#emit("change", { accounts: this.accounts });
return { accounts: this.accounts };
}
如你所见, connect
方法实际上并没有做太多事情。我们根据密钥对和网络设置一个 ReadonlyWalletAccount
,然后发出一个事件。这里没什么可做的,因为连接只是意味着与应用程序共享我们的地址,我们实际上并没有采取任何行动。
接下来,让我们实现 disconnect
方法——它只是清除帐户并发出一个事件:
async disconnect() {
this.#account = null;
this.#emit("change", { accounts: this.accounts });
}
最后,我们可以实现钱包的主力—— signAndExecuteTransaction
方法:
async signAndExecuteTransaction(input: SuiSignAndExecuteTransactionInput): Promise<SuiSignAndExecuteTransactionOutput> {
if (!this.#account) {
throw new Error("Not connected");
}
const response = await this.#signer.signAndExecuteTransaction(input.transaction);
// return object adhering to SuiTransactionResponse type
return {
certificate: getCertifiedTransaction(response)!,
effects: getTransactionEffects(response)!,
timestamp_ms: null,
parsed_data: null,
};
}
Sui 通过 RawSigner
对象及其 signAndExecuteTransaction
函数为我们处理了繁重的工作。我们函数的返回类型是 SuiSignAndExecuteTransactionOutput
,它扩展了 SuiTransactionResponse
类型。因此,本质上我们返回的是符合此类型的对象:
type SuiTransactionResponse = {
certificate: CertifiedTransaction;
effects: TransactionEffects;
timestamp_ms: number | null;
parsed_data: SuiParsedTransactionResponse | null;
};
就这样!我们现在支持钱包实现所需的 3 个必需功能。我们支持事件处理,并实现了接口所需的基本功能。但是,我们仍然可以添加一些可选功能以实现常用功能和测试:
DEFAULT_GAS_BUDGET = 10_000;
async requestFromFaucet() {
return this.#provider.requestSuiFromFaucet(
this.#keypair.getPublicKey().toSuiAddress()
);
}
async getObjects() {
return await this.#provider.getObjectsOwnedByAddress(
this.#keypair.getPublicKey().toSuiAddress()
);
}
async transferObject(
objectId: string,
recipientAddress: string,
gasBudget: number = DEFAULT_GAS_BUDGET
) {
const transaction = await this.#signer.transferObject({
objectId: objectId,
gasBudget: gasBudget,
recipient: recipientAddress,
});
return transaction;
}
async executeMoveCall(
packageId: string,
moduleName: string,
functionName: string,
functionArguments: Array<string>,
gasBudget: number = DEFAULT_GAS_BUDGET
) {
const transaction = await this.#signer.executeMoveCall({
packageObjectId: packageId,
module: moduleName,
function: functionName,
typeArguments: [],
arguments: functionArguments,
gasBudget: gasBudget,
});
return transaction;
}
requestFromFaucet
函数使在 devnet 和 testnet 上进行测试时更容易获取代币,也可以在单元测试期间使用。 transferObject
和 executeMoveCall
函数代表用户可能想要采取的基本 Sui 操作,可用于各种测试。
虽然我们已经讨论了如何实现这个钱包,但我们忽略了如何实际使用它。 作为最后一步,在我们的应用程序代码中,我们必须创建一个钱包实例并将其注册到窗口中:
import { Ed25519Keypair, Network } from "@mysten/sui.js";
import { registerWallet } from '@mysten/wallet-standard';
// Fetch or generate keypair
const keypair = Ed25519Keypair.generate();
const wallet = new MySuiWallet(keypair, Network.LOCAL);
registerWallet(wallet);
一旦钱包注册完毕,网站点击“连接”按钮即可与其交互。
作为参考,以下是完整的类:
import {
Base64DataBuffer,
Ed25519Keypair,
getCertifiedTransaction,
getTransactionEffects,
JsonRpcProvider,
Network,
RawSigner,
} from "@mysten/sui.js";
import {
SUI_CHAINS,
ConnectFeature,
DisconnectFeature,
EventsFeature,
EventsListeners,
EventsNames,
EventsOnMethod,
ReadonlyWalletAccount,
SuiSignAndExecuteTransactionFeature,
SuiSignAndExecuteTransactionInput,
SuiSignAndExecuteTransactionOutput,
Wallet,
} from "@mysten/wallet-standard";
export class MySuiWallet implements Wallet {
#listeners: { [E in EventsNames]?: EventsListeners[E][] } = {};
#account: ReadonlyWalletAccount | null;
#keypair: Ed25519Keypair;
#provider: JsonRpcProvider;
#signer: RawSigner;
constructor(keypair: Ed25519Keypair, network: string | Network = Network.LOCAL) {
this.#keypair = keypair;
this.#account = null;
this.#provider = new JsonRpcProvider(network);
this.#signer = new RawSigner(
this.#keypair,
this.#provider,
);
}
// Return the version of the Wallet Standard this implements.
get version() {
return "1.0.0" as const;
}
// Return the name of this wallet.
get name() {
return "My Sui Wallet";
}
// Return the icon of this wallet (the one below is the Sui icon).
get icon() {
return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzIiIGhlaWdodD0iNzIiIHZpZXdCb3g9IjAgMCA3MiA3MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjcyIiBoZWlnaHQ9IjcyIiByeD0iMTYiIGZpbGw9IiM2RkJDRjAiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMC40MjEzIDUyLjc4MzhDMjMuNjQ5NiA1OC4zNzYgMjkuNDMyMSA2MS43MTQyIDM1Ljg4ODggNjEuNzE0MkM0Mi4zNDU1IDYxLjcxNDIgNDguMTI3IDU4LjM3NiA1MS4zNTY0IDUyLjc4MzhDNTQuNTg0OCA0Ny4xOTI2IDU0LjU4NDggNDAuNTE2MyA1MS4zNTY0IDM0LjkyNEwzNy43NTI0IDExLjM2MTVDMzYuOTI0MSA5LjkyNzAxIDM0Ljg1MzUgOS45MjcwMSAzNC4wMjUzIDExLjM2MTVMMjAuNDIxMyAzNC45MjRDMTcuMTkyOSA0MC41MTUyIDE3LjE5MjkgNDcuMTkxNSAyMC40MjEzIDUyLjc4MzhaTTMyLjA1NjYgMjIuNTcxM0wzNC45NTcxIDE3LjU0NzRDMzUuMzcxMiAxNi44MzAxIDM2LjQwNjUgMTYuODMwMSAzNi44MjA2IDE3LjU0NzRMNDcuOTc5MSAzNi44NzQ4QzUwLjAyOTEgNDAuNDI1NCA1MC40MTM5IDQ0LjUzNSA0OS4xMzM1IDQ4LjI5NTRDNDkuMDAwMiA0Ny42ODE5IDQ4LjgxMzggNDcuMDU0MiA0OC41NjI2IDQ2LjQyMDFDNDcuMDIxMyA0Mi41MzA0IDQzLjUzNjMgMzkuNTI4OSAzOC4yMDIzIDM3LjQ5ODJDMzQuNTM1MSAzNi4xMDcxIDMyLjE5NDMgMzQuMDYxMyAzMS4yNDMxIDMxLjQxNzFDMzAuMDE4IDI4LjAwODkgMzEuMjk3NiAyNC4yOTI0IDMyLjA1NjYgMjIuNTcxM1pNMjcuMTEwNyAzMS4xMzc5TDIzLjc5ODYgMzYuODc0OEMyMS4yNzQ4IDQxLjI0NTkgMjEuMjc0OCA0Ni40NjQxIDIzLjc5ODYgNTAuODM1M0MyNi4zMjIzIDU1LjIwNjQgMzAuODQxMyA1Ny44MTUgMzUuODg4OCA1Ny44MTVDMzkuMjQxMyA1Ny44MTUgNDIuMzYxNSA1Ni42NjMzIDQ0LjgxODQgNTQuNjA4OEM0NS4xMzg4IDUzLjgwMjEgNDYuMTMxIDUwLjg0OTIgNDQuOTA1MiA0Ny44MDU4QzQzLjc3MyA0NC45OTU0IDQxLjA0ODIgNDIuNzUxOSAzNi44MDYxIDQxLjEzNkMzMi4wMTEgMzkuMzE3MSAyOC44OTU4IDM2LjQ3NzQgMjcuNTQ4NiAzMi42OTg0QzI3LjM2MzEgMzIuMTc4MSAyNy4yMTg5IDMxLjY1NjggMjcuMTEwNyAzMS4xMzc5WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+" as const;
}
// Return the list of chains this wallet supports.
get chains() {
return SUI_CHAINS;
}
// Return connected accounts adhering to the `WalletAccount` interface.
get accounts() {
return this.#account ? [this.#account] : [];
}
// Return the features this wallet supports.
get features(): ConnectFeature & DisconnectFeature & EventsFeature & SuiSignAndExecuteTransactionFeature {
return {
"standard:connect": {
version: "1.0.0",
connect: this.connect,
},
"standard:disconnect": {
version: "1.0.0",
disconnect: this.disconnect,
},
"standard:events": {
version: "1.0.0",
on: this.#on,
},
"sui:signAndExecuteTransaction": {
version: "1.0.0",
signAndExecuteTransaction: this.signAndExecuteTransaction,
},
};
}
// ---------------
// Events
// ---------------
#on: EventsOnMethod = (event, listener) => {
this.#listeners[event]?.push(listener) || (this.#listeners[event] = [listener]);
return (): void => this.#off(event, listener);
}
#emit<E extends EventsNames>(event: E, ...args: Parameters<EventsListeners[E]>): void {
// eslint-disable-next-line prefer-spread
this.#listeners[event]?.forEach((listener) => listener.apply(null, args));
}
#off<E extends EventsNames>(event: E, listener: EventsListeners[E]): void {
this.#listeners[event] = this.#listeners[event]?.filter((existingListener) => listener !== existingListener);
}
// ---------------
// Features
// ---------------
async connect() {
this.#account = new ReadonlyWalletAccount({
address: this.#keypair.getPublicKey().toSuiAddress(),
publicKey: this.#keypair.getPublicKey().toBytes(),
chains: this.chains,
// The features that this account supports. This can be a subset of the wallet's supported features.
// These features must exist on the wallet as well.
features: ["sui:signAndExecuteTransaction"],
});
this.#emit("change", { accounts: this.accounts });
return { accounts: this.accounts };
}
async disconnect() {
this.#account = null;
this.#emit("change", { accounts: this.accounts });
}
async signAndExecuteTransaction(input: SuiSignAndExecuteTransactionInput): Promise<SuiSignAndExecuteTransactionOutput> {
if (!this.#account) {
throw new Error("Not connected");
}
const response = await this.#signer.signAndExecuteTransaction(input.transaction);
// return SuiTransactionResponse object
return {
certificate: getCertifiedTransaction(response)!,
effects: getTransactionEffects(response)!,
timestamp_ms: null,
parsed_data: null,
};
}
// ---------------
// Utils & Extras
// ---------------
async requestFromFaucet() {
return this.#provider.requestSuiFromFaucet(this.#keypair.getPublicKey().toSuiAddress());
}
async getObjects() {
return await this.#provider.getObjectsOwnedByAddress(
this.#keypair.getPublicKey().toSuiAddress()
);
}
async transferObject(objectId: string, recipientAddress: string, gasBudget: number) {
const transaction = await this.#signer.transferObject({
objectId: objectId,
gasBudget: gasBudget,
recipient: recipientAddress,
});
return transaction;
}
async executeMoveCall(packageId: string, moduleName: string, functionName: string, functionArguments: Array<string>, gasBudget: number) {
const transaction = await this.#signer.executeMoveCall({
packageObjectId: packageId,
module: moduleName,
function: functionName,
typeArguments: [],
arguments: functionArguments,
gasBudget: gasBudget,
});
return transaction;
}
async signMessage(message: string | Uint8Array | Base64DataBuffer) {
if (!this.#account) {
throw new Error("Not connected");
}
let data: Base64DataBuffer;
if (message instanceof Base64DataBuffer) {
data = message;
}
else {
data = new Base64DataBuffer(message);
}
const response = await this.#signer.signData(data);
return response;
}
}
原文链接:Implementing a Sui wallet
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。