Skip to content

Commit

Permalink
support aptos new wallet standard
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmaayan committed Feb 28, 2024
1 parent 71cf0c9 commit f51c7aa
Show file tree
Hide file tree
Showing 51 changed files with 5,173 additions and 7,216 deletions.
2 changes: 0 additions & 2 deletions apps/nextjs-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"private": true,
"license": "Apache-2.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start",
"lint": "next lint"
Expand Down
4 changes: 4 additions & 0 deletions apps/nextjs-standard-example/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["@aptos-labs/eslint-config-adapter"],
};
Empty file.
4 changes: 4 additions & 0 deletions apps/nextjs-standard-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Wallet Adapter Demo app

a demo nextjs app.
[https://aptos-labs.github.io/aptos-wallet-adapter](https://aptos-labs.github.io/aptos-wallet-adapter)
56 changes: 56 additions & 0 deletions apps/nextjs-standard-example/components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Dispatch, ReactNode, SetStateAction } from "react";

type Alert = {
text: ReactNode;
setText: Dispatch<SetStateAction<ReactNode | null>>;
};

export function SuccessAlert({ text, setText }: Alert) {
return (
<div
className="bg-teal-100 border border-teal-400 text-teal-900 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline break-all right-3">{text}</span>
<span
className="absolute top-0 bottom-0 right-0 px-4 py-3"
onClick={() => setText(null)}
>
<svg
className="fill-current h-6 w-6 text-teal-500"
role="button"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<title>Close</title>
<path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" />
</svg>
</span>
</div>
);
}

export function ErrorAlert({ text, setText }: Alert) {
return (
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline break-all">{text}</span>
<span
className="absolute top-0 bottom-0 right-0 px-4 py-3"
onClick={() => setText(null)}
>
<svg
className="fill-current h-6 w-6 text-red-500"
role="button"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<title>Close</title>
<path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" />
</svg>
</span>
</div>
);
}
74 changes: 74 additions & 0 deletions apps/nextjs-standard-example/components/AlertProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
createContext,
Dispatch,
FC,
ReactNode,
SetStateAction,
useCallback,
useContext,
useState,
} from "react";
import { ErrorAlert, SuccessAlert } from "./Alert";

interface AlertContextState {
setSuccessAlertHash: (hash: string, networkName?: string) => void;
setSuccessAlertMessage: Dispatch<SetStateAction<ReactNode | null>>;
setErrorAlertMessage: Dispatch<SetStateAction<ReactNode | null>>;
}

export const AlertContext = createContext<AlertContextState | undefined>(
undefined
);

export function useAlert(): AlertContextState {
const context = useContext(AlertContext);
if (!context)
throw new Error("useAlert must be used within an AlertProvider");
return context;
}

export const AlertProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [successAlertMessage, setSuccessAlertMessage] =
useState<ReactNode | null>(null);
const [errorAlertMessage, setErrorAlertMessage] = useState<ReactNode | null>(
null
);

const setSuccessAlertHash = useCallback(
(hash: string, networkName?: string) => {
const explorerLink = `https://explorer.aptoslabs.com/txn/${hash}${
networkName ? `?network=${networkName.toLowerCase()}` : ""
}`;
setSuccessAlertMessage(
<>
View on Explorer:{" "}
<a className="underline" target="_blank" href={explorerLink} rel={"noreferrer"}>
{explorerLink}
</a>
</>
);
},
[]
);

return (
<AlertContext.Provider
value={{
setSuccessAlertHash,
setSuccessAlertMessage,
setErrorAlertMessage,
}}
>
{successAlertMessage && (
<SuccessAlert
text={successAlertMessage}
setText={setSuccessAlertMessage}
/>
)}
{errorAlertMessage && (
<ErrorAlert text={errorAlertMessage} setText={setErrorAlertMessage} />
)}
{children}
</AlertContext.Provider>
);
};
31 changes: 31 additions & 0 deletions apps/nextjs-standard-example/components/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react";
import { AutoConnectProvider, useAutoConnect } from "./AutoConnectProvider";
import { FC, ReactNode } from "react";
import { AlertProvider, useAlert } from "./AlertProvider";

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { autoConnect } = useAutoConnect();
const { setErrorAlertMessage } = useAlert();

return (
<AptosWalletAdapterProvider
autoConnect={autoConnect}
onError={(error) => {
console.log("Custom error handling", error);
setErrorAlertMessage(JSON.stringify(error));
}}
>
{children}
</AptosWalletAdapterProvider>
);
};

export const AppContext: FC<{ children: ReactNode }> = ({ children }) => {
return (
<AutoConnectProvider>
<AlertProvider>
<WalletContextProvider>{children}</WalletContextProvider>
</AlertProvider>
</AutoConnectProvider>
);
};
57 changes: 57 additions & 0 deletions apps/nextjs-standard-example/components/AutoConnectProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { FC, ReactNode, useEffect, useState } from "react";
import React, { createContext, useContext } from "react";

const AUTO_CONNECT_LOCAL_STORAGE_KEY = "AptosWalletAutoConnect";

export interface AutoConnectContextState {
autoConnect: boolean;
setAutoConnect(autoConnect: boolean): void;
}

export const AutoConnectContext = createContext<AutoConnectContextState>(
{} as AutoConnectContextState
);

export function useAutoConnect(): AutoConnectContextState {
return useContext(AutoConnectContext);
}

export const AutoConnectProvider: FC<{ children: ReactNode }> = ({
children,
}) => {
const [autoConnect, setAutoConnect] = useState<boolean>(() => {
try {
const isAutoConnect = localStorage.getItem(
AUTO_CONNECT_LOCAL_STORAGE_KEY
);
if (isAutoConnect) return JSON.parse(isAutoConnect);
} catch (e: any) {
if (typeof window !== "undefined") {
console.error(e);
}
}
});

useEffect(() => {
try {
if (!autoConnect) {
localStorage.removeItem(AUTO_CONNECT_LOCAL_STORAGE_KEY);
} else {
localStorage.setItem(
AUTO_CONNECT_LOCAL_STORAGE_KEY,
JSON.stringify(autoConnect)
);
}
} catch (error: any) {
if (typeof window !== "undefined") {
console.error(error);
}
}
}, [autoConnect]);

return (
<AutoConnectContext.Provider value={{ autoConnect, setAutoConnect }}>
{children}
</AutoConnectContext.Provider>
);
};
19 changes: 19 additions & 0 deletions apps/nextjs-standard-example/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function Button(props: {
color: string | undefined;
onClick: () => void;
disabled: boolean;
message: string;
}) {
const { color, onClick, disabled, message } = props;
return (
<button
className={`bg-${color}-500 text-white font-bold py-2 px-4 rounded mr-4 ${
disabled ? "opacity-50 cursor-not-allowed" : `hover:bg-${color}-700`
}`}
onClick={onClick}
disabled={disabled}
>
{message}
</button>
);
}
15 changes: 15 additions & 0 deletions apps/nextjs-standard-example/components/Col.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function Col(props: {
title?: boolean;
border?: boolean;
children?: React.ReactNode;
}) {
return (
<td
className={`px-8 py-4 ${props.border ? "border-t" : ""} w-${
props.title ? "1/4" : "3/4"
}`}
>
{props.children}
</td>
);
}
3 changes: 3 additions & 0 deletions apps/nextjs-standard-example/components/Row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Row(props: { children?: React.ReactNode }) {
return <tr>{props.children}</tr>;
}
89 changes: 89 additions & 0 deletions apps/nextjs-standard-example/components/WalletButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
useWallet,
WalletReadyState,
isRedirectable,
} from "@aptos-labs/wallet-adapter-react";
import { useAlert } from "./AlertProvider";
import { IAptosWallet } from "@aptos-labs/wallet-adapter-core";

const WalletButtons = () => {
const { wallets } = useWallet();
const { connect } = useWallet();
const { setErrorAlertMessage } = useAlert();

return (
<>
{wallets &&
wallets.map((wallet: IAptosWallet) => {
return WalletView(wallet, connect, setErrorAlertMessage);
})}
</>
);
};

const WalletView = (
wallet: IAptosWallet,
connect: (walletName: string) => void,
setErrorAlertMessage: React.Dispatch<React.SetStateAction<React.ReactNode>>
) => {
const isWalletReady =
wallet.readyState === WalletReadyState.Installed ||
wallet.readyState === WalletReadyState.Loadable;
const mobileSupport = Boolean(wallet.features["aptos:openInMobileApp"]);

const onWalletConnectRequest = async (walletName: string) => {
try {
await connect(walletName);
} catch (error: any) {
setErrorAlertMessage(error);
}
};

/**
* If we are on a mobile browser, adapter checks whether a wallet has a `deeplinkProvider` property
* a. If it does, on connect it should redirect the user to the app by using the wallet's deeplink url
* b. If it does not, up to the dapp to choose on the UI, but can simply disable the button
* c. If we are already in a in-app browser, we dont want to redirect anywhere, so connect should work as expected in the mobile app.
*
* !isWalletReady - ignore installed/sdk wallets that dont rely on window injection
* isRedirectable() - are we on mobile AND not in an in-app browser
* mobileSupport - does wallet have deeplinkProvider property? i.e does it support a mobile app
*/
if (!isWalletReady && isRedirectable()) {
// wallet has mobile app
if (mobileSupport) {
return (
<button
className={`bg-blue-500 text-white font-bold py-2 px-4 rounded mr-4 hover:bg-blue-700`}
disabled={false}
key={wallet.name}
onClick={() => onWalletConnectRequest(wallet.name)}
>
<>{wallet.name}</>
</button>
);
}
// wallet does not have mobile app
return (
<button
className={`bg-blue-500 text-white font-bold py-2 px-4 rounded mr-4 opacity-50 cursor-not-allowed`}
disabled={true}
key={wallet.name}
>
<>{wallet.name} - Desktop Only</>
</button>
);
} else {
// we are on desktop view
return (
<button
className={`bg-blue-500 text-white font-bold py-2 px-4 rounded mr-4 hover:bg-blue-700`}
key={wallet.name}
onClick={() => onWalletConnectRequest(wallet.name)}
>
<>{wallet.name}</>
</button>
);
}
};
export default WalletButtons;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WalletSelector } from "@aptos-labs/wallet-adapter-ant-design";

const WalletSelectorAntDesign = () => {
return <WalletSelector />;
};

export default WalletSelectorAntDesign;
Loading

0 comments on commit f51c7aa

Please sign in to comment.