Skip to content

Commit

Permalink
Merge pull request #297 from suiet/feat/stashed-wallet
Browse files Browse the repository at this point in the history
Feat/stashed wallet
  • Loading branch information
bruceeewong authored Jun 17, 2024
2 parents fc25170 + 623f3b2 commit 24d020a
Show file tree
Hide file tree
Showing 27 changed files with 8,543 additions and 10,141 deletions.
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@suiet/wallet-kit",
"version": "0.2.25",
"version": "0.3.0",
"type": "module",
"files": [
"dist"
Expand Down
180 changes: 143 additions & 37 deletions packages/kit/src/components/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import React, {
} from "react";
import { WalletContext } from "../hooks";
import type {
SignedTransaction,
StandardConnectInput,
SuiReportTransactionEffectsInput,
SuiSignAndExecuteTransactionBlockInput,
SuiSignAndExecuteTransactionInput,
SuiSignMessageInput,
SuiSignPersonalMessageInput,
SuiSignTransactionBlockInput,
SuiSignTransactionInput,
WalletAccount,
} from "@mysten/wallet-standard";
import { Extendable } from "../types/utils";
import { isNonEmptyArray } from "../utils";
import { useAvailableWallets } from "../hooks/useAvaibleWallets";
import { useAvailableWallets } from "../hooks/useAvailableWallets";
import { useAutoConnect } from "../hooks/useAutoConnect";
import { Storage } from "../utils/storage";
import { StorageKey } from "../constants/storage";
Expand All @@ -37,6 +41,14 @@ import {
WalletEvent,
WalletEventListeners,
} from "@suiet/wallet-sdk";
import { SuiReportTransactionEffectsMethod } from "@mysten/wallet-standard/dist/esm";
import {
ExecuteTransactionOptions,
ExecuteTransactionResult,
} from "../types/params";
import { SuiClient } from "@mysten/sui/client";
import { toB64 } from "@mysten/sui/utils";
import { SuiClientContext } from "../contexts/SuiClientContext";

export type WalletProviderProps = Extendable & {
defaultWallets?: IDefaultWallet[];
Expand Down Expand Up @@ -65,6 +77,12 @@ export const WalletProvider = (props: WalletProviderProps) => {
if (isNonEmptyArray(chains)) return chains[0]; // first one as default chain
return UnknownChain;
});
// suiClient will automatically be updated when chain changes
const suiClient = useMemo(
() => new SuiClient({ url: chain.rpcUrl }),
[chain]
);

const walletOffListeners = useRef<(() => void)[]>([]);

const isCallable = (
Expand All @@ -88,6 +106,18 @@ export const WalletProvider = (props: WalletProviderProps) => {
}
};

const safelyGetWalletAndAccount = useCallback((): [
IWalletAdapter,
WalletAccount
] => {
ensureCallable(walletAdapter, status);
if (!account) {
throw new KitError("no active account");
}
const _wallet = walletAdapter as IWalletAdapter;
return [_wallet, account];
}, [walletAdapter, account, status]);

const connect = useCallback(
async (adapter: IWalletAdapter, opts?: StandardConnectInput) => {
if (!adapter) throw new KitError("param adapter is missing");
Expand Down Expand Up @@ -186,8 +216,7 @@ export const WalletProvider = (props: WalletProviderProps) => {

const on = useCallback(
(event: WalletEvent, listener: WalletEventListeners[WalletEvent]) => {
ensureCallable(walletAdapter, status);
const _wallet = walletAdapter as IWalletAdapter;
const [_wallet] = safelyGetWalletAndAccount();

// filter event and params to decide when to emit
const off = _wallet.on("change", (params) => {
Expand Down Expand Up @@ -215,79 +244,151 @@ export const WalletProvider = (props: WalletProviderProps) => {
walletOffListeners.current.push(off); // should help user manage off cleaners
return off;
},
[walletAdapter, status]
[safelyGetWalletAndAccount]
);

const getAccounts = useCallback(() => {
ensureCallable(walletAdapter, status);
const _wallet = walletAdapter as IWalletAdapter;
const [_wallet] = safelyGetWalletAndAccount();
return _wallet.accounts;
}, [walletAdapter, status]);
}, [safelyGetWalletAndAccount]);

const signAndExecuteTransactionBlock = useCallback(
async (
input: Omit<SuiSignAndExecuteTransactionBlockInput, "account" | "chain">
) => {
ensureCallable(walletAdapter, status);
if (!account) {
throw new KitError("no active account");
}
const _wallet = walletAdapter as IWalletAdapter;
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.signAndExecuteTransactionBlock({
account,
chain: chain.id as IdentifierString,
...input,
});
},
[walletAdapter, status, chain, account]
[safelyGetWalletAndAccount, chain]
);

const signTransaction = useCallback(
async (input: Omit<SuiSignTransactionInput, "account" | "chain">) => {
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.signTransaction({
account,
chain: chain.id as IdentifierString,
...input,
});
},
[safelyGetWalletAndAccount, chain]
);

const signAndExecuteTransaction = useCallback(
async (
input: Omit<SuiSignAndExecuteTransactionInput, "account" | "chain">,
options?: ExecuteTransactionOptions
) => {
const [_wallet, account] = safelyGetWalletAndAccount();

const executeTransaction = async (
signedTransaction: SignedTransaction
): Promise<ExecuteTransactionResult> => {
if (typeof options?.execute === "function") {
return await options.execute(signedTransaction);
}
const { digest, rawEffects } = await suiClient.executeTransactionBlock({
transactionBlock: signedTransaction.bytes,
signature: signedTransaction.signature,
options: {
showRawEffects: true,
},
});
return {
digest,
rawEffects,
};
};

const signedTransaction = await _wallet.signTransaction({
transaction: input.transaction,
account,
chain: chain.id as IdentifierString,
});
const execResult = await executeTransaction(signedTransaction);

let effects: string;

if ("effects" in execResult && execResult.effects?.bcs) {
effects = execResult.effects.bcs;
} else if ("rawEffects" in execResult) {
effects = toB64(new Uint8Array(execResult.rawEffects!));
} else {
throw new Error(
"effects or rawEffects not found in the execution result"
);
}

try {
await _wallet.reportTransactionEffects({
effects,
account,
chain: chain.id as IdentifierString,
});
} catch (error) {
console.warn("Failed to report transaction effects:", error);
}

return {
bytes: signedTransaction.bytes,
signature: signedTransaction.signature,
digest: execResult.digest,
effects,
};
},
[safelyGetWalletAndAccount, chain, suiClient]
);

const reportTransactionEffects = useCallback(
async (
input: Omit<SuiReportTransactionEffectsInput, "account" | "chain">
) => {
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.reportTransactionEffects({
account,
chain: chain.id as IdentifierString,
...input,
});
},
[safelyGetWalletAndAccount, chain]
);

const signTransactionBlock = useCallback(
async (input: Omit<SuiSignTransactionBlockInput, "account" | "chain">) => {
ensureCallable(walletAdapter, status);
if (!account) {
throw new KitError("no active account");
}
const _wallet = walletAdapter as IWalletAdapter;
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.signTransactionBlock({
account,
chain: chain.id as IdentifierString,
...input,
});
},
[walletAdapter, status, chain, account]
[safelyGetWalletAndAccount, chain]
);

const signMessage = useCallback(
async (input: Omit<SuiSignMessageInput, "account">) => {
ensureCallable(walletAdapter, status);
if (!account) {
throw new KitError("no active account");
}

const adapter = walletAdapter as IWalletAdapter;
return await adapter.signMessage({
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.signMessage({
account,
message: input.message,
});
},
[walletAdapter, account, status]
[safelyGetWalletAndAccount]
);

const signPersonalMessage = useCallback(
async (input: Omit<SuiSignPersonalMessageInput, "account">) => {
ensureCallable(walletAdapter, status);
if (!account) {
throw new KitError("no active account");
}

const adapter = walletAdapter as IWalletAdapter;
return await adapter.signPersonalMessage({
const [_wallet, account] = safelyGetWalletAndAccount();
return await _wallet.signPersonalMessage({
account,
message: input.message,
});
},
[walletAdapter, account, status]
[safelyGetWalletAndAccount]
);

useAutoConnect(select, status, allAvailableWallets, autoConnect);
Expand Down Expand Up @@ -327,16 +428,21 @@ export const WalletProvider = (props: WalletProviderProps) => {
on,
getAccounts,
account,
signAndExecuteTransactionBlock,
signPersonalMessage,
signTransaction,
signAndExecuteTransaction,
reportTransactionEffects,
signMessage,
signTransactionBlock,
signAndExecuteTransactionBlock,
verifySignedMessage,
address: account?.address,
}}
>
<QueryClientProvider client={new QueryClient()}>
{children}
<SuiClientContext.Provider value={suiClient}>
{children}
</SuiClientContext.Provider>
</QueryClientProvider>
</WalletContext.Provider>
);
Expand Down
9 changes: 9 additions & 0 deletions packages/kit/src/contexts/SuiClientContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";
import { UnknownChain } from "@suiet/wallet-sdk";
import { SuiClient } from "@mysten/sui/client";

export const SuiClientContext = createContext<SuiClient>(
new SuiClient({
url: UnknownChain.rpcUrl,
})
);
11 changes: 6 additions & 5 deletions packages/kit/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './useWallet';
export * from './useAccountBalance';
export * from './useCoinBalance';
export * from './useChain';
export * from './useSuiProvider';
export * from "./useWallet";
export * from "./useAccountBalance";
export * from "./useCoinBalance";
export * from "./useChain";
export * from "./useSuiProvider";
export * from "./useSuiClient";
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { useMemo } from "react";
import { isNonEmptyArray } from "../utils";
import { useWalletAdapterDetection } from "./useWalletDetection";
import { IDefaultWallet, IWallet } from "@suiet/wallet-sdk";
import { useInstallWebWalletAdapters } from "./useInstallWebWalletAdapters";

export const useAvailableWallets = (defaultWallets: IDefaultWallet[]) => {
useInstallWebWalletAdapters(defaultWallets);
const { data: availableWalletAdapters } = useWalletAdapterDetection();

// configured wallets
const configuredWallets: IWallet[] = useMemo(() => {
if (!isNonEmptyArray(defaultWallets)) return [];
Expand Down
28 changes: 28 additions & 0 deletions packages/kit/src/hooks/useInstallWebWalletAdapters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useLayoutEffect } from "react";
import { IDefaultWallet, WalletType } from "@suiet/wallet-sdk";
import { isNonEmptyArray } from "../utils";

export function useInstallWebWalletAdapters(defaultWallets: IDefaultWallet[]) {
useLayoutEffect(() => {
const unregisterFunctions: (() => void)[] = [];

if (isNonEmptyArray(defaultWallets)) {
defaultWallets.forEach((item) => {
if (
item.type === WalletType.WEB &&
item.downloadUrl?.registerWebWallet
) {
const unregister = item.downloadUrl.registerWebWallet();
unregisterFunctions.push(unregister);
}
});
}

return () => {
if (!isNonEmptyArray(unregisterFunctions)) return;
unregisterFunctions.forEach((unregister) => {
unregister();
});
};
}, [defaultWallets]);
}
9 changes: 9 additions & 0 deletions packages/kit/src/hooks/useSuiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useContext } from "react";
import { SuiClientContext } from "../contexts/SuiClientContext";

/**
* Return a SuiClient instance with the current chain's rpcUrl
*/
export const useSuiClient = () => {
return useContext(SuiClientContext);
};
4 changes: 4 additions & 0 deletions packages/kit/src/hooks/useSuiProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { useMemo } from "react";

export type SuiProvider = SuiClient;

/**
* Return a SuiClient instance.
* @deprecated Use `useSuiClient` instead.
*/
export function useSuiProvider(endpoint: string): SuiProvider {
return useMemo<SuiClient>(() => new SuiClient({ url: endpoint }), [endpoint]);
}
Loading

0 comments on commit 24d020a

Please sign in to comment.