Implementing a Sui wallet
If you’re new to the world of Sui and web3, the sheer volume of information and concepts can be daunting. This guide is designed to help demystify the topic of wallets and related concepts, making them easily understandable for developers at any level.
What is a wallet?
A wallet in the context of blockchain and cryptocurrency refers to a piece of software that allows an individual to securely store, manage, and send digital assets. The two main functions of the wallet is to keep your private key safe and allow you to interact securely with the blockchain.
At the heart of all blockchain technology lies the concept of public-key cryptography, which allows for the generation of a key-pair consisting of a private key (to be kept secret) and a public key that can be shared with anyone. In the context of blockchains, specific addresses are generated based on this key-pair, acting as public keys that enable the receipt of coins and tokens. Only the holder of the corresponding private key has access to these digital assets and can send them to a marketplace or to another address. Here is an example of what a key-pair and Sui address looks like:
Private key (base58 encoding)
4y9tCUuXv9gZKJf1cBL4qtWMCLrYzYeYWze5ARrCYUVru7op3EyYPBHTSkwfr7bvg3s4HQotTpWDHL8DjZEqTAtL
Public key (base58 encoding)
5LFt9j5DFwsVWBdLEMa7bxAte3Zco8UhuQn9iK6XScAx
Sui Address
0x505c1becba4055d0c0be153479064df05cd078f3
The security of private keys is a critical issue for users, as the responsibility for their protection falls solely on the individual. Unlike traditional online accounts, there is no “Forgot password” option for private keys, and the only way to recover a lost key is through a recovery phrase, which must be written down and stored in a secure location beforehand. Furthermore, if a private key is shared, anyone who obtains it will have control over the associated digital assets, with no means of reversing unauthorized transactions. Given the severity of these risks, it is crucial to have secure wallet software to store private keys and handle blockchain transactions without the risk of exposing keys. Wallets come in a few different forms:
- Software wallet: Pure software wallet where your private keys are stored encrypted on a computer. This is the easiest to set up, but comes with risk as you have to trust the wallet provider to keep your secrets secure. Certain web3 marketplaces and wallets are described as non-custodial, meaning that you don’t have access to the private key and instead rely on conventional email/password login to access your funds. In these cases, you don’t have full control over your assets, and if the company were to go bankrupt or shut down, you could potentially lose everything. Many wallets do give you access to the private key and allow exporting your assets to another address.
- Hardware wallet: A security-hardened physical device that generates and stores your private key. When you create your key-pair, a recovery phrase is generated that you physically write down and store in case you lose your physical device. After that, your private key never leaves this device for any reason. These wallets also come with software and the main distinction with pure software wallets is that the private key is completely under your control.
- Paper wallet: A paper wallet is just a piece of paper with a key-pair printed on it — there is no actual wallet functionality here. It’s a piece of paper…
How does a wallet interact with the blockchain without exposing your private key? By utilizing the concept of transactions. When a user connects their wallet to a website, transactions can be prepared and sent to the wallet for review and approval. No digital assets can be transferred out of the wallet without the user’s explicit authorization. If the user chooses to proceed with the transaction, the private key is used to sign the transaction, and it is then submitted to the network for execution. There is no way for the malicious actors to reverse the user’s signature on the transaction and deduce their private key. Additionally, without access to the user’s private key, it is impossible for anyone to impersonate their signature.
These days, most wallets are available as browser extensions. The reason for this is because it’s the easiest way to bridge the web2 world of websites with the web3 world of blockchains. Browser extensions can alter any part of any website and help to make the experience of interacting with web3 much nicer, but that comes at the cost of having all these extensions being able to read and alter every website you access. It becomes very important to keep track of what extensions you are installing and whether they can be trusted. There will undoubtedly be better solutions to integrating web2 and web3 in the future, but for now this is the best option we have to cross that bridge.
Wallet software patterns
If you’ve been exploring blockchains like Sui and Solana, you may have come across terms like “wallet adapter”, “wallet standard”, “wallet adapter wallet”, “mobile wallet adapter” and so on. Understanding these concepts can be challenging, so let’s clarify the reasons behind these terms.
First off, these concepts are extremely young and are in constant development. Developers are trying to find the simplest way for applications to interact with wallets and establish a standard interface. Let’s say we’re working on a website that needs to interact with a wallet. Initially, there was no standard way to do this and every wallet would simply have it’s own API — meaning you would have to implement code for every wallet provider you want to interact with. If you decided to stick with a single wallet provider, many users would not be able to use your application.
This is where the wallet adapter pattern comes in. The wallet adapter serves as a single package that allows support for many different wallets. It exposes a standard interface and wallet developers implement this interface to internally interact with their own API. Afterwards, they make a pull request on Github to get their final adapter code into the package. This pattern makes it easier for application developers to interact with wallets, but maintaining the single adapter package is cumbersome for both the maintainers and the wallet developers. There is also an issue with code bloat as application developers need to package the code for all these wallets alongside their own code. This shouldn’t really be necessary, and that’s why the idea of the wallet standard was recently developed.
The wallet standard provides a standard interface for wallet developers to implement, but they don’t have to go through the process of submitting their code to a central package. Instead, wallet-standard provides a method that allows any wallet to register in the browser window. Website developers can now interact directly with the wallet-standard package and get a list of registered wallets to interact with. This approach avoids the code bloat and maintenance associated with the adapter pattern. Instead, the wallet extension provides the wallet code exposed via the standard interface.
Implementing the wallet
The wallet-standard library can be found here. This provides the standard interface that can be used as a base for any blockchain wallet. Sui provides their own wallet-standard package here. This is a very light package that provides some Sui-specific types and constants to ease development of Sui-compatible wallets. This package also imports the core features from the original wallet-standard, so it won’t be necessary for us to import both packages in our wallet code.
Implementing the standard is simple enough: We create a class and implement a series of required functions. I’ll include a copy of the entire class at the bottom of this post. Keep in mind that this post is purely about implementing the wallet standard, there are many further security considerations to make when building a production-ready wallet extension. Starting out with the basic functions the scaffolding of our class looks like this:
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 {};
}
}
You can read the comments to see what the functions do - most of them are pretty straightforward. The notable function here is features
which is a part of the wallet-standard interface that gives developers an easy way to check which features are supported, and then call those functions. The standard provides two required features "standard:connect"
and "standard:events"
. There are also optional features available, for example "standard:disconnect"
which allows us to run code on disconnect. The features are intended to be extendable by any blockchain, so Sui requires the inclusion of a third feature "sui:signAndExecuteTransaction".
This is how our features
function will look:
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,
}
};
}
For each feature that we want to support, we implement a corresponding function and reference that function here. The events
function is used for setting up event handling. The signAndExecuteTransaction
function is basically all we need for a simple wallet. The function receives a transaction for approval, which the wallet then signs and sends to the Sui network to execute. We have four features defined here, so we’ll need to implement the four functions referenced above. Before that, we need to add a constructor and some internal state to our class:
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] : [];
}
// ...
We add a listeners
variable for events handling and a single account
which will store the connected account details. For this simple example, we will pass an existing keypair to the wallet on creation. Production-ready wallets have password protection and utilize a vault / encryption to reduce risk, which is out of scope for this blog post. We also define a provider
and signer
. These are types provided by Sui. The signer
, as you might have guessed, is used to sign and execute the transaction. The provider
handles JSON RPC calls for us. If you don’t what that is, RPC stands for “remote procedure call” and is a simple communication protocol used by many blockchains. We don’t need to worry about the details of RPC, the most important thing to note about the provider is that it will connect to a network of our choosing and send transactions to that network.
Note that we can also implement the accounts
getter since we now have an #account
variable to reference. Now that we have our constructor, let’s implement the event handling feature since it will be referenced by other features. These functions have been copied from the wallet-standard example here. This is boilerplate event handling code, feel free to substitute whatever events library or approach you prefer.
#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);
}
After we have events set up, we can now implement the connect
method:
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 };
}
As you can see, the connect
method doesn’t really do much of anything. We setup a ReadonlyWalletAccount
based on the keypair and network, and then emit an event. There’s not much to do here since connecting just means sharing our address with the application, we aren’t really taking any action.
Next up, let’s implement the disconnect method — which simply clears the account and emits an event:
async disconnect() {
this.#account = null;
this.#emit("change", { accounts: this.accounts });
}
Finally, we can implement the workhorse of our wallet - the signAndExecuteTransaction
method:
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 takes care of the heavy lifting for us via the RawSigner
object and it’s signAndExecuteTransaction
function. The return type of our function is SuiSignAndExecuteTransactionOutput
which extends the SuiTransactionResponse
type. So essentially we’re returning an object that conforms to this type:
type SuiTransactionResponse = {
certificate: CertifiedTransaction;
effects: TransactionEffects;
timestamp_ms: number | null;
parsed_data: SuiParsedTransactionResponse | null;
};
That’s it! We now support the 3 required features needed for the wallet implementation. We support events handling, and have implemented the basic functions needed by the interface. However, there are still some optional functions that we can add for common functionality and testing:
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;
}
The requestFromFaucet
function makes it easier to acquire coins when testing on devnet and testnet, and can also be used during unit tests. The transferObject
and executeMoveCall
functions represent basic Sui actions that users might want to take and are useful for all sorts of testing.
While we’ve talked about implementing this wallet, we’ve glossed over how to actually use it. As a final step, inside the code of our application, we have to create a wallet instance and register it on the window:
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);
Once the wallet has been registered, it will be available to interact with by websites when they click the “Connect” button.
For reference, here is the completed class in it’s entirety:
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;
}
}