@pontem/aptos-wallet-adapter
Version:
Wallet adapter with supporting Vue and React
312 lines (279 loc) • 9.77 kB
text/typescript
import { Types } from 'aptos';
import { defineStore } from 'pinia';
import { ref, watch } from 'vue';
import {
AccountKeys,
NetworkInfo,
SignMessagePayload,
WalletAdapter,
WalletName,
WalletReadyState
} from '../WalletAdapters';
import {
Wallet,
WalletError,
WalletNotConnectedError,
WalletNotReadyError,
WalletNotSelectedError
} from '../WalletProviders';
import { isMobile, isRedirectable } from '../utilities/helpers';
interface IUseVueWalletProvider {
wallets: WalletAdapter[];
onError?: (error: WalletError) => void;
localStorageKey?: string;
autoConnect: boolean;
}
const getWalletNameFromLocalStorage = (key: string) => {
try {
const value = localStorage.getItem(key);
if (value) return JSON.parse(value);
} catch (e: any) {
if (typeof window !== 'undefined') {
console.error(e);
}
}
return null;
};
export const useWalletProviderStore = defineStore('walletProviderStore', () => {
const adapters = ref<WalletAdapter[]>([]);
const localStorageKey = ref<string>('walletName');
const autoConnect = ref<boolean>(false);
const onError = ref<((error: WalletError) => void) | undefined>(undefined);
function init({
wallets = [],
onError: onHandleError,
localStorageKey: lsKey,
autoConnect: autoConnection
}: IUseVueWalletProvider) {
adapters.value = wallets;
if (lsKey) localStorageKey.value = lsKey;
if (autoConnection !== undefined) autoConnect.value = autoConnection;
if (onError.value) onError.value = onHandleError;
}
const walletName = ref<WalletName | null>(null);
const wallet = ref<Wallet | null>(null);
const adapter = ref<WalletAdapter | null>(null);
const account = ref<AccountKeys | null>(null);
const connected = ref<boolean>(false);
const connecting = ref<boolean>(false);
const disconnecting = ref<boolean>(false);
const readyState = ref<WalletReadyState>(WalletReadyState.Unsupported);
const walletNetwork = ref<NetworkInfo | null>(null);
const wallets = ref<Wallet[]>([]);
// Setting default state to yours
function setDefaultState() {
wallet.value = null;
adapter.value = null;
account.value = null;
connected.value = false;
walletNetwork.value = null;
}
// When the wallets change, start listen for changes to their `readyState`
watch(adapters, (_value, _oldValue, onCleanup) => {
function handleReadyStateChange(this: WalletAdapter, isReadyState: WalletReadyState) {
const index = wallets.value.findIndex((prevWallet) => prevWallet.adapter.name === this.name);
if (index === -1) return wallets.value;
const currentWallet = wallets.value[index];
wallets.value = [
...wallets.value.slice(0, index),
{ adapter: currentWallet.adapter, readyState: isReadyState },
...wallets.value.slice(index + 1)
];
}
wallets.value = adapters.value.map((adpt) => ({
adapter: adpt,
readyState: adpt.readyState
}));
for (const wAdapter of adapters.value) {
wAdapter.on('readyStateChange', handleReadyStateChange, wAdapter);
}
// When adapters dependency changed - cleanUp function runs before body of watcher;
onCleanup(() => {
for (const wAdapter of adapters.value) {
wAdapter.off('readyStateChange', handleReadyStateChange, wAdapter);
}
});
});
function handleAddressChange() {
if (!adapter.value) return;
account.value = adapter.value.publicAccount;
}
function handleNetworkChange() {
if (!adapter.value) return;
walletNetwork.value = adapter.value.network;
}
// set or reset current wallet from localstorage
function setWalletName(name: WalletName | null) {
try {
if (name === null) {
localStorage.removeItem(localStorageKey.value);
walletName.value = null;
} else {
localStorage.setItem(localStorageKey.value, JSON.stringify(name));
walletName.value = name;
}
} catch (e: any) {
if (typeof window !== 'undefined') {
console.error(e);
}
}
}
//Handle the adapter's connect event - add network and account listeners.
function handleAfterConnect() {
if (!adapter.value) return;
adapter.value.on('accountChange', handleAddressChange);
adapter.value.on('networkChange', handleNetworkChange);
adapter.value.on('disconnect', handleDisconnect);
adapter.value.on('error', handleError);
adapter.value.onAccountChange();
adapter.value.onNetworkChange();
}
// Handle the adapter's disconnect event
function handleDisconnect() {
setWalletName(null);
if (!adapter.value) return;
adapter.value.off('accountChange', handleAddressChange);
adapter.value.off('networkChange', handleNetworkChange);
adapter.value.off('disconnect', handleDisconnect);
adapter.value.off('error', handleError);
setDefaultState();
}
// Handle the adapter's error event, and local errors
function handleError(error: WalletError) {
if (onError.value)
(onError.value || console.log)(error);
return error;
}
// function to connect adapter
async function connect() {
if (connecting.value || disconnecting.value || connected.value) return;
const selectedWallet = wallets.value.find(
(wAdapter) => wAdapter.adapter.name === walletName.value
);
if (!selectedWallet?.adapter) throw handleError(new WalletNotSelectedError());
// check if we are in a redirectable view (i.e on mobile AND not in an in-app browser) and
// since wallet readyState can be NotDetected, we check it before the next check
if (isRedirectable()) {
// use wallet deep link
if (selectedWallet.adapter.deeplinkProvider) {
const url = encodeURIComponent(window.location.href);
const location = selectedWallet.adapter.deeplinkProvider({ url });
window.location.href = location;
}
}
if (
!(
selectedWallet.adapter.readyState === WalletReadyState.Installed ||
selectedWallet.adapter.readyState === WalletReadyState.Loadable
)
) {
// Clear the selected wallet
setWalletName(null);
if (typeof window !== 'undefined' && selectedWallet.adapter.url && !isMobile()) {
window.open(selectedWallet.adapter.url, '_blank');
}
throw handleError(new WalletNotReadyError());
}
connecting.value = true;
try {
await selectedWallet.adapter.connect();
wallet.value = selectedWallet;
adapter.value = selectedWallet.adapter;
connected.value = selectedWallet.adapter.connected;
account.value = selectedWallet.adapter.publicAccount;
walletNetwork.value = selectedWallet.adapter.network;
handleAfterConnect();
} catch (error: any) {
// Clear the selected wallet
setWalletName(null);
// Rethrow the error, and handleError will also be called
throw error;
} finally {
connecting.value = false;
}
}
// function to disconnect adapter and clear localstorage
async function disconnect() {
if (disconnecting.value) return;
if (!adapter.value) {
setWalletName(null);
return;
}
disconnecting.value = true;
try {
await adapter.value?.disconnect();
} catch (error: any) {
// Clear the selected wallet
setWalletName(null);
// Rethrow the error, and handleError will also be called
throw error;
} finally {
disconnecting.value = false;
handleDisconnect();
}
}
watch([walletName, wallets, readyState], () => {
wallets.value.forEach((item) => {
if (walletName.value === item.adapter.name) {
readyState.value = item.adapter.readyState;
}
});
});
// autoConnect adapter if localStorage not empty
watch([autoConnect, localStorageKey, walletName], () => {
walletName.value = getWalletNameFromLocalStorage(localStorageKey.value);
});
// If autoConnect is enabled, try to connect when the adapter changes and is ready
watch([connecting, connected, readyState, autoConnect], () => {
if (
connecting.value ||
connected.value ||
!autoConnect.value ||
readyState.value === WalletReadyState.Unsupported
) {
return;
}
(async function () {
try {
await connect();
} catch (error: any) {
handleError(error);
}
})();
});
async function signAndSubmitTransaction(transaction: Types.TransactionPayload, option?: any) {
if (!adapter.value) throw handleError(new WalletNotSelectedError());
if (!connected.value) throw handleError(new WalletNotConnectedError());
const response = await adapter.value.signAndSubmitTransaction(transaction, option);
return response;
}
async function signTransaction(transaction: Types.TransactionPayload, option?: any) {
if (!adapter.value) throw handleError(new WalletNotSelectedError());
if (!connected.value) throw handleError(new WalletNotConnectedError());
const response = await adapter.value.signTransaction(transaction, option);
return response;
}
async function signMessage(msgPayload: string | SignMessagePayload | Uint8Array) {
if (!adapter.value) throw handleError(new WalletNotSelectedError());
if (!connected.value) throw handleError(new WalletNotConnectedError());
const response = await adapter.value.signMessage(msgPayload);
return response;
}
return {
init,
wallets,
wallet,
account,
connected,
connecting,
disconnecting,
autoConnect,
network: walletNetwork,
select: setWalletName,
connect,
disconnect,
signAndSubmitTransaction,
signTransaction,
signMessage
};
});