Compare commits

..
Author SHA1 Message Date
Jonas DanielsGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
8516fc11f9 Version Packages (#2817)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-19 14:36:14 -07:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
b4e7c74ce5 chore(chains): update chains (#2812)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-19 11:11:44 -07:00
Jonas DanielsandGitHub d6646475e2 refactor(viem): explicitly type walletClient return type (#2816) 2024-04-19 17:30:13 +00:00
Jonas DanielsandGitHub 4dbcbd13db feat(react): initial auth for <ConnectButton /> (#2793)
Co-authored-by: Manan Tank <[email protected]>
2024-04-19 10:36:11 -07:00
43 changed files with 1052 additions and 28 deletions
+7
View File
@@ -1,5 +1,12 @@
# @thirdweb-dev/auth
## 4.1.64
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@2.5.6
## 4.1.63
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "4.1.63",
"version": "4.1.64",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/chains
## 0.1.97
### Patch Changes
- [#2812](https://github.com/thirdweb-dev/js/pull/2812) [`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
## 0.1.96
### Patch Changes
+31
View File
@@ -0,0 +1,31 @@
import type { Chain } from "../src/types";
export default {
"chain": "IOTA EVM",
"chainId": 1075,
"explorers": [],
"faucets": [
"https://evm-toolkit.evm.testnet.iotaledger.net"
],
"icon": {
"url": "ipfs://bafkreibky2sy6qhi6arktayvologkrgu5kudpgdxfkx4uosbvmstz7v4di",
"width": 720,
"height": 720,
"format": "png"
},
"infoURL": "https://www.iota.org",
"name": "IOTA EVM Testnet",
"nativeCurrency": {
"name": "IOTA",
"symbol": "IOTA",
"decimals": 18
},
"networkId": 1075,
"rpc": [
"https://1075.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://json-rpc.evm.testnet.iotaledger.net"
],
"shortName": "iotaevm-testnet",
"slug": "iota-evm-testnet",
"testnet": true,
"title": "IOTA EVM Testnet"
} as const satisfies Chain;
+40
View File
@@ -0,0 +1,40 @@
import type { Chain } from "../src/types";
export default {
"chain": "MOS",
"chainId": 1188,
"explorers": [
{
"name": "mosscan",
"url": "https://www.mosscan.com",
"standard": "none",
"icon": {
"url": "ipfs://QmczQf61mZmg3NmmGnwseDxzH1AqapA252KCTT85GuNG96",
"width": 51,
"height": 50,
"format": "png"
}
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmczQf61mZmg3NmmGnwseDxzH1AqapA252KCTT85GuNG96",
"width": 51,
"height": 50,
"format": "png"
},
"infoURL": "https://www.mosscan.com",
"name": "ClubMos Mainnet",
"nativeCurrency": {
"name": "ClubMos",
"symbol": "MOS",
"decimals": 18
},
"networkId": 1188,
"rpc": [
"https://1188.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://mainnet.mosscan.com"
],
"shortName": "MOS",
"slug": "clubmos",
"testnet": false
} as const satisfies Chain;
+28
View File
@@ -0,0 +1,28 @@
import type { Chain } from "../src/types";
export default {
"chain": "ETH",
"chainId": 1221,
"explorers": [],
"faucets": [],
"icon": {
"url": "ipfs://QmeC4mKGbBpwmLfFxUSsareWkSWJomevUmex8ajjxo1zHx",
"width": 500,
"height": 500,
"format": "png"
},
"infoURL": "https://www.cyclenetwork.io/",
"name": "Cycle Network Testnet",
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"networkId": 1221,
"rpc": [
"https://1221.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc-testnet.cyclenetwork.io"
],
"shortName": "Cycle",
"slug": "cycle-network-testnet",
"testnet": true
} as const satisfies Chain;
+46
View File
@@ -0,0 +1,46 @@
import type { Chain } from "../src/types";
export default {
"chain": "MagApe",
"chainId": 141319,
"explorers": [
{
"name": "etherscan",
"url": "http://testnet-api.magape.io:81",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmPsxWMvnxnZ9dZcRJJMaE24o34dQsnsAMCJh3kVDrE1aB",
"width": 1000,
"height": 1000,
"format": "png"
}
}
],
"faucets": [],
"features": [
{
"name": "EIP155"
}
],
"icon": {
"url": "ipfs://QmPsxWMvnxnZ9dZcRJJMaE24o34dQsnsAMCJh3kVDrE1aB",
"width": 1000,
"height": 1000,
"format": "png"
},
"infoURL": "https://magape.io",
"name": "MagApe Testnet",
"nativeCurrency": {
"name": "MagApe",
"symbol": "MAG",
"decimals": 18
},
"networkId": 141319,
"rpc": [
"https://141319.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet-api.magape.io/chain/"
],
"shortName": "mag",
"slug": "magape-testnet",
"testnet": true,
"title": "MagApeChain"
} as const satisfies Chain;
+30
View File
@@ -0,0 +1,30 @@
import type { Chain } from "../src/types";
export default {
"chain": "ETH",
"chainId": 167000,
"explorers": [],
"faucets": [],
"icon": {
"url": "ipfs://QmcHdmVr5VRUJq13jnM6tgah5Ge7hn3Dm14eY6vwivJ5ui",
"width": 288,
"height": 258,
"format": "png"
},
"infoURL": "https://taiko.xyz",
"name": "Taiko Mainnet",
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"networkId": 167000,
"rpc": [
"https://167000.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.taiko.xyz",
"wss://ws.taiko.xyz"
],
"shortName": "tko-mainnet",
"slug": "taiko",
"status": "incubating",
"testnet": false
} as const satisfies Chain;
+1 -1
View File
@@ -29,7 +29,7 @@ export default {
"rpc": [
"https://167008.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.katla.taiko.xyz",
"wss://rpc.katla.taiko.xyz",
"wss://ws.katla.taiko.xyz",
"https://taiko-katla.drpc.org",
"wss://taiko-katla.drpc.org"
],
+2 -2
View File
@@ -16,8 +16,8 @@ export default {
},
{
"name": "ankrscan-neura",
"url": "https://testnet.neuraprotocol.io/explorer",
"standard": "none",
"url": "https://testnet.explorer.neuraprotocol.io",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmWdpK5WFKrosaCSpJRCvU7uXeKk2qVeCckTxh6Zw2JrK8",
"width": 600,
+30
View File
@@ -0,0 +1,30 @@
import type { Chain } from "../src/types";
export default {
"chain": "Avalanche",
"chainId": 58316,
"explorers": [],
"faucets": [],
"features": [],
"icon": {
"url": "https://images.ctfassets.net/9bazykntljf6/62CceHSYsRS4D9fgDSkLRB/877cb8f26954e1743ff535fd7fdaf78f/avacloud-placeholder.svg",
"width": 256,
"height": 256,
"format": "svg"
},
"infoURL": "https://avacloud.io",
"name": "QaUser1943 Testnet",
"nativeCurrency": {
"name": "QaUser1943 Testnet Token",
"symbol": "JAI",
"decimals": 18
},
"networkId": 58316,
"redFlags": [],
"rpc": [
"https://58316.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc"
],
"shortName": "QaUser1943 Testnet",
"slug": "qauser1943-testnet",
"testnet": true
} as const satisfies Chain;
+28
View File
@@ -0,0 +1,28 @@
import type { Chain } from "../src/types";
export default {
"chain": "Planq",
"chainId": 7077,
"explorers": [],
"faucets": [],
"icon": {
"url": "ipfs://QmWEy9xK5BoqxPuVs7T48WM4exJrxzkEFt45iHcxWqUy8D",
"width": 256,
"height": 256,
"format": "png"
},
"infoURL": "https://planq.network",
"name": "Planq Atlas Testnet",
"nativeCurrency": {
"name": "Planq",
"symbol": "tPLQ",
"decimals": 18
},
"networkId": 7077,
"rpc": [
"https://7077.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://evm-rpc-atlas.planq.network"
],
"shortName": "planq-atlas-testnet",
"slug": "planq-atlas-testnet",
"testnet": true
} as const satisfies Chain;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/chains",
"version": "0.1.96",
"version": "0.1.97",
"main": "dist/thirdweb-dev-chains.cjs.js",
"module": "dist/thirdweb-dev-chains.esm.js",
"exports": {
File diff suppressed because one or more lines are too long
+10
View File
@@ -1,5 +1,15 @@
# thirdweb
## 0.13.72
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
- @thirdweb-dev/wallets@2.5.6
- @thirdweb-dev/auth@4.1.64
## 0.13.71
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/cli",
"main": "dist/cli/index.js",
"version": "0.13.71",
"version": "0.13.72",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/react-core
## 4.6.5
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
- @thirdweb-dev/wallets@2.5.6
- @thirdweb-dev/auth@4.1.64
## 4.6.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "4.6.4",
"version": "4.6.5",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -1,5 +1,7 @@
# @thirdweb-dev/react-native-compat
## 0.7.37
## 0.7.36
## 0.7.35
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/react-native-compat",
"description": "Shims for Thirdweb in React Native Projects",
"version": "0.7.36",
"version": "0.7.37",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-native-compat",
"license": "Apache-2.0",
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/react-native
## 0.7.37
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
- @thirdweb-dev/react-core@4.6.5
- @thirdweb-dev/wallets@2.5.6
## 0.7.36
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.7.36",
"version": "0.7.37",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/react
## 4.6.5
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
- @thirdweb-dev/react-core@4.6.5
- @thirdweb-dev/wallets@2.5.6
## 4.6.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "4.6.4",
"version": "4.6.5",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+8
View File
@@ -1,5 +1,13 @@
# @thirdweb-dev/sdk
## 4.0.66
### Patch Changes
- Updated dependencies [[`4dbcbd1`](https://github.com/thirdweb-dev/js/commit/4dbcbd13dbd388454993aee7e67b43bb9d9249aa), [`d664647`](https://github.com/thirdweb-dev/js/commit/d6646475e2715c666316174e1c035a035a075ab1), [`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- thirdweb@5.9.0
- @thirdweb-dev/chains@0.1.97
## 4.0.65
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "4.0.65",
"version": "4.0.66",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/sdk",
"license": "Apache-2.0",
@@ -1,5 +1,15 @@
# @thirdweb-dev/unity-js-bridge
## 0.6.15
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
- @thirdweb-dev/wallets@2.5.6
- @thirdweb-dev/auth@4.1.64
## 0.6.14
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/unity-js-bridge",
"version": "0.6.14",
"version": "0.6.15",
"main": "dist/thirdweb-unity-bridge.js",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
"license": "Apache-2.0",
+8
View File
@@ -1,5 +1,13 @@
# @thirdweb-dev/wallets
## 2.5.6
### Patch Changes
- Updated dependencies [[`b4e7c74`](https://github.com/thirdweb-dev/js/commit/b4e7c74ce5ddc88a1aa3169ea89ff139d185a648)]:
- @thirdweb-dev/chains@0.1.97
- @thirdweb-dev/sdk@4.0.66
## 2.5.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/wallets",
"version": "2.5.5",
"version": "2.5.6",
"main": "dist/thirdweb-dev-wallets.cjs.js",
"module": "dist/thirdweb-dev-wallets.esm.js",
"browser": {
+42
View File
@@ -1,5 +1,47 @@
# thirdweb
## 5.9.0
### Minor Changes
- [#2793](https://github.com/thirdweb-dev/js/pull/2793) [`4dbcbd1`](https://github.com/thirdweb-dev/js/commit/4dbcbd13dbd388454993aee7e67b43bb9d9249aa) Thanks [@jnsdls](https://github.com/jnsdls)! - **thirdweb/react**
Add a new optional `auth` prop to `<ConnectButton />` to allow specifying a SIWE auth flow after users connect their wallets.
```jsx
<ConnectButton
client={client}
auth={{
isLoggedIn: async (address) => {
// check if the user is logged in by calling your server, etc.
// then return a boolean value
return true || false;
},
getLoginPayload: async ({ address, chainId }) => {
// send the address (and optional chainId) to your server to generate the login payload for the user to sign
// you can use the `generatePayload` function from `thirdweb/auth` to generate the payload
// once you have retrieved the payload return it from this function
return; // <the login payload here>
},
doLogin: async (loginParams) => {
// send the login params to your server where you can validate them using the `verifyPayload` function
// from `thirdweb/auth`
// you can then set a cookie or return a token to save in local storage, etc
// `isLoggedIn` will automatically get called again after this function resolves
},
doLogout: async () => {
// do anything you need to do such as clearing cookies, etc when the user should be logged out
// `isLoggedIn` will automatically get called again after this function resolves
},
}}
/>
```
### Patch Changes
- [#2816](https://github.com/thirdweb-dev/js/pull/2816) [`d664647`](https://github.com/thirdweb-dev/js/commit/d6646475e2715c666316174e1c035a035a075ab1) Thanks [@jnsdls](https://github.com/jnsdls)! - adapters(viem): explicitly type walletClient return type
## 5.8.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "thirdweb",
"version": "5.8.0",
"version": "5.9.0",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+1 -1
View File
@@ -170,7 +170,7 @@ type ToViemWalletClientOptions = {
chain: Chain;
};
function toViemWalletClient(options: ToViemWalletClientOptions) {
function toViemWalletClient(options: ToViemWalletClientOptions): WalletClient {
const { account, chain, client } = options;
if (!account) {
throw new Error("Wallet not connected.");
+3
View File
@@ -96,3 +96,6 @@ export {
AutoConnect,
type AutoConnectProps,
} from "../react/core/hooks/connection/useAutoConnect.js";
// auth
export { type SiweAuthOptions } from "../react/core/hooks/auth/useSiweAuth.js";
@@ -35,7 +35,6 @@ export function executeSale(
contract: options.contract,
auctionId: options.auctionId,
});
console.log("*** winning bid", winningBid);
if (!winningBid) {
throw new Error("Auction is still active");
}
@@ -0,0 +1,139 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { LoginPayload } from "../../../../auth/core/types.js";
import type { VerifyLoginPayloadParams } from "../../../../auth/core/verify-login-payload.js";
import { useActiveWallet } from "../../../core/hooks/wallets/wallet-hooks.js";
/**
* Options for Setting up SIWE (Sign in with Ethereum) Authentication
* @auth
*/
export type SiweAuthOptions = {
// we pass address and chainId and retrieve a login payload (we do not care how)
/**
* Method to get the login payload for given address and chainId
* @param params - The parameters to get the login payload for.
*/
getLoginPayload: (params: {
address: string;
chainId: number;
}) => Promise<LoginPayload>;
// we pass the login payload and signature and the developer passes this to the auth server however they want
/**
* Method to login with the signed login payload
* @param params
*/
doLogin: (params: VerifyLoginPayloadParams) => Promise<void>;
// we call this internally when a user explicitly disconnects their wallet
/**
* Method to logout the user
*/
doLogout: () => Promise<void>;
// the developer specifies how to check if the user is logged in, this is called internally by the component
/**
* Method to check if the user is logged in or not
* @param address
*/
isLoggedIn: (address: string) => Promise<boolean>;
};
/**
* @internal
*/
export function useSiweAuth(authOptions?: SiweAuthOptions) {
const activeWallet = useActiveWallet();
const activeAccount = activeWallet?.getAccount();
const requiresAuth = !!authOptions;
const queryClient = useQueryClient();
const isLoggedInQuery = useQuery({
queryKey: ["siwe_auth", "isLoggedIn", activeAccount?.address],
enabled: requiresAuth && !!activeAccount?.address,
queryFn: () => {
// these cases should never be hit but just in case...
if (!authOptions || !activeAccount?.address) {
return false;
}
return authOptions.isLoggedIn(activeAccount.address);
},
});
const loginMutation = useMutation({
mutationFn: async () => {
if (!authOptions) {
throw new Error("No auth options provided");
}
if (!activeWallet) {
throw new Error("No active wallet");
}
const chain = activeWallet.getChain();
if (!chain) {
throw new Error("No active chain");
}
if (!activeAccount) {
throw new Error("No active account");
}
const [payload, { signLoginPayload }] = await Promise.all([
authOptions.getLoginPayload({
address: activeAccount.address,
chainId: chain.id,
}),
// we lazy-load this because it's only needed when logging in
import("../../../../auth/core/sign-login-payload.js"),
]);
const signedPayload = await signLoginPayload({
payload,
account: activeAccount,
});
return await authOptions.doLogin(signedPayload);
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["siwe_auth", "isLoggedIn"],
});
},
});
const logoutMutation = useMutation({
mutationFn: async () => {
if (!authOptions) {
throw new Error("No auth options provided");
}
return await authOptions.doLogout();
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["siwe_auth", "isLoggedIn"],
});
},
});
return {
// is auth even enabled
requiresAuth,
// login
doLogin: loginMutation.mutateAsync,
isLoggingIn: loginMutation.isPending,
// logout
doLogout: logoutMutation.mutateAsync,
isLoggingOut: logoutMutation.isPending,
// checking if logged in
isLoggedIn: isLoggedInQuery.data,
isLoading: isLoggedInQuery.isLoading,
};
}
@@ -268,7 +268,7 @@ export function AutoConnect(props: AutoConnectProps) {
(async () => {
isAutoConnecting.setValue(true);
startAutoConnect();
await startAutoConnect();
isAutoConnecting.setValue(false);
})();
});
@@ -7,6 +7,7 @@ import type { AppMetadata } from "../../../wallets/types.js";
import type { ConnectButton_connectModalOptions } from "../../web/ui/ConnectWallet/ConnectWalletProps.js";
import type { ConnectLocale } from "../../web/ui/ConnectWallet/locale/types.js";
import type { LocaleId } from "../../web/ui/types.js";
import type { SiweAuthOptions } from "../hooks/auth/useSiweAuth.js";
export const ConnectUIContext = /* @__PURE__ */ createContext<{
wallets: Wallet[];
@@ -27,4 +28,5 @@ export const ConnectUIContext = /* @__PURE__ */ createContext<{
connectModal: Omit<ConnectButton_connectModalOptions, "size"> & {
size: "compact" | "wide";
};
auth?: SiweAuthOptions;
} | null>(null);
@@ -1,5 +1,6 @@
import styled from "@emotion/styled";
import { useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSiweAuth } from "../../../core/hooks/auth/useSiweAuth.js";
import { AutoConnect } from "../../../core/hooks/connection/useAutoConnect.js";
import {
useActiveAccount,
@@ -12,15 +13,20 @@ import {
} from "../../providers/wallet-ui-states-provider.js";
import { canFitWideModal } from "../../utils/canFitWideModal.js";
import { getDefaultWallets } from "../../wallets/defaultWallets.js";
import { Modal } from "../components/Modal.js";
import { Spinner } from "../components/Spinner.js";
import { Container } from "../components/basic.js";
import { Button } from "../components/buttons.js";
import { fadeInAnimation } from "../design-system/animations.js";
import { iconSize } from "../design-system/index.js";
import type { ConnectButtonProps } from "./ConnectWalletProps.js";
import { ConnectedWalletDetails } from "./Details.js";
import ConnectModal from "./Modal/ConnectModal.js";
import { defaultTokens } from "./defaultTokens.js";
import { LockIcon } from "./icons/LockIcon.js";
import { useConnectLocale } from "./locale/getConnectLocale.js";
import type { ConnectLocale } from "./locale/types.js";
import { SignatureScreen } from "./screens/SignatureScreen.js";
const TW_CONNECT_WALLET = "tw-connect-wallet";
@@ -40,7 +46,10 @@ const TW_CONNECT_WALLET = "tw-connect-wallet";
* @component
*/
export function ConnectButton(props: ConnectButtonProps) {
const wallets = props.wallets || getDefaultWallets();
const wallets = useMemo(
() => props.wallets || getDefaultWallets(),
[props.wallets],
);
const localeQuery = useConnectLocale(props.locale || "en_US");
const autoConnectComp = props.autoConnect !== false && (
@@ -100,6 +109,7 @@ export function ConnectButton(props: ConnectButtonProps) {
: props.connectModal?.size || "wide",
},
onConnect: props.onConnect,
auth: props.auth,
}}
>
<WalletUIStatesProvider theme={props.theme}>
@@ -117,6 +127,16 @@ function ConnectButtonInner(
},
) {
const activeAccount = useActiveAccount();
const siweAuth = useSiweAuth(props.auth);
const [showSignatureModal, setShowSignatureModal] = useState(false);
// if wallet gets disconnected suddently, close the signature modal if it's open
useEffect(() => {
if (!activeAccount) {
setShowSignatureModal(false);
}
}, [activeAccount]);
const theme = props.theme || "dark";
const connectionStatus = useActiveWalletConnectionStatus();
const locale = props.connectLocale;
@@ -180,6 +200,73 @@ function ConnectButtonInner(
);
}
if (siweAuth.requiresAuth) {
// loading state if loading
// TODO: figure out a way to consolidate the loading states with the ones from locale loading
if (siweAuth.isLoading) {
return (
<AnimatedButton
disabled={true}
className={`${
props.connectButton?.className || ""
} ${TW_CONNECT_WALLET}`}
variant="primary"
type="button"
style={{
minWidth: "140px",
...props.connectButton?.style,
}}
>
<Spinner size="sm" color="primaryButtonText" />
</AnimatedButton>
);
}
// sign in button + modal if *not* loading and *not* logged in
if (!siweAuth.isLoggedIn) {
return (
<>
<Button
variant="primary"
type="button"
onClick={() => {
setShowSignatureModal(true);
}}
className={props.signInButton?.className}
style={{
minWidth: "140px",
...props.signInButton?.style,
}}
>
{siweAuth.isLoggingIn ? (
<Spinner size="sm" color="primaryButtonText" />
) : (
<Container flex="row" center="y" gap="sm">
<LockIcon size={iconSize.sm} />
<span> {props.signInButton?.label || locale.signIn} </span>
</Container>
)}
</Button>
<Modal
size="compact"
open={showSignatureModal}
setOpen={setShowSignatureModal}
>
<SignatureScreen
client={props.client}
connectLocale={locale}
modalSize="compact"
termsOfServiceUrl={props.connectModal?.termsOfServiceUrl}
privacyPolicyUrl={props.connectModal?.privacyPolicyUrl}
onDone={() => setShowSignatureModal(false)}
auth={props.auth}
/>
</Modal>
</>
);
}
// otherwise, show the details button
}
return (
<ConnectedWalletDetails
theme={theme}
@@ -187,7 +274,10 @@ function ConnectButtonInner(
detailsModal={props.detailsModal}
supportedTokens={supportedTokens}
onDisconnect={() => {
// no op
// logout on explicit disconnect!
if (siweAuth.requiresAuth) {
siweAuth.doLogout();
}
}}
chains={props?.chains || []}
chain={props.chain}
@@ -3,6 +3,7 @@ import type { ThirdwebClient } from "../../../../client/client.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../wallets/types.js";
import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js";
import type { Theme } from "../design-system/index.js";
import type { LocaleId } from "../types.js";
import type { NetworkSelectorProps } from "./NetworkSelector.js";
@@ -495,6 +496,23 @@ export type ConnectButtonProps = {
className?: string;
};
signInButton?: {
/**
* Set a custom label for the sign-in button
*/
label?: string;
/**
* CSS styles to apply to the sign-in button element
*/
style?: React.CSSProperties;
/**
* CSS class to apply to the sign-in button element
*/
className?: string;
};
/**
* Configurations for the `ConnectButton`'s Modal that is shown for connecting a wallet
* Refer to the [`ConnectButton_connectModalOptions`](https://portal.thirdweb.com/references/typescript/v5/ConnectButton_connectModalOptions) type for more details
@@ -622,4 +640,12 @@ export type ConnectButtonProps = {
* You can disable this button by setting `showAllWallets` prop to `false`
*/
showAllWallets?: boolean;
/**
* Enable SIWE (Sign in with Ethererum) by passing an object of type `SiweAuthOptions` to
* enforce the users to sign a message after connecting their wallet to authenticate themselves.
*
* Refer to the [`SiweAuthOptions`](https://portal.thirdweb.com/references/typescript/v5/SiweAuthOptions) for more details
*/
auth?: SiweAuthOptions;
};
@@ -1,8 +1,11 @@
import { useEffect } from "react";
import type { Chain } from "../../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../../client/client.js";
import type { SiweAuthOptions } from "../../../../../exports/react.js";
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
import type { SmartWalletOptions } from "../../../../../wallets/smart/types.js";
import type { AppMetadata } from "../../../../../wallets/types.js";
import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import { AutoConnect } from "../../../../core/hooks/connection/useAutoConnect.js";
import {
useActiveAccount,
@@ -21,6 +24,7 @@ import type { LocaleId } from "../../types.js";
import {
modalMaxWidthCompact,
modalMaxWidthWide,
reservedScreens,
wideModalMaxHeight,
} from "../constants.js";
import { useConnectLocale } from "../locale/getConnectLocale.js";
@@ -304,6 +308,14 @@ export type ConnectEmbedProps = {
* Note that if the screen width can not fit the wide modal, the `compact` version will be shown regardless of this `modalSize` options provided
*/
modalSize?: "compact" | "wide";
/**
* Enable SIWE (Sign in with Ethererum) by passing an object of type `SiweAuthOptions` to
* enforce the users to sign a message after connecting their wallet to authenticate themselves.
*
* Refer to the [`SiweAuthOptions`](https://portal.thirdweb.com/references/typescript/v5/SiweAuthOptions) for more details
*/
auth?: SiweAuthOptions;
};
/**
@@ -330,7 +342,9 @@ export type ConnectEmbedProps = {
*/
export function ConnectEmbed(props: ConnectEmbedProps) {
const activeAccount = useActiveAccount();
const show = !activeAccount;
const siweAuth = useSiweAuth(props.auth);
const show =
!activeAccount || (siweAuth.requiresAuth && !siweAuth.isLoggedIn);
const wallets = props.wallets || getDefaultWallets();
const localeId = props.locale || "en_US";
@@ -346,6 +360,7 @@ export function ConnectEmbed(props: ConnectEmbedProps) {
appMetadata={props.appMetadata}
client={props.client}
wallets={wallets}
accountAbstraction={props.accountAbstraction}
timeout={
typeof props.autoConnect === "boolean"
? undefined
@@ -388,6 +403,7 @@ export function ConnectEmbed(props: ConnectEmbedProps) {
termsOfServiceUrl: props.termsOfServiceUrl,
},
onConnect: props.onConnect,
auth: props.auth,
}}
>
<WalletUIStatesProvider theme={props.theme}>
@@ -412,11 +428,19 @@ const ConnectEmbedContent = (
// const requiresSignIn = false;
const screenSetup = useSetupScreen();
const { setScreen, initialScreen } = screenSetup;
const siweAuth = useSiweAuth(props.auth);
const activeAccount = useActiveAccount();
const isAutoConnecting = useIsAutoConnecting();
let content = null;
useEffect(() => {
if (siweAuth.requiresAuth && !siweAuth.isLoggedIn && activeAccount) {
setScreen(reservedScreens.signIn);
}
}, [siweAuth, setScreen, activeAccount]);
const modalSize = !canFitWideModal()
? "compact"
: props.modalSize || ("compact" as const);
@@ -1,10 +1,12 @@
import { Suspense, lazy, useCallback } from "react";
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import { useConnectUI } from "../../../../core/hooks/others/useWalletConnectionCtx.js";
import { useConnect } from "../../../../core/hooks/wallets/wallet-hooks.js";
import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
import { WalletSelector } from "../WalletSelector.js";
import { onModalUnmount, reservedScreens } from "../constants.js";
import { SignatureScreen } from "../screens/SignatureScreen.js";
import { StartScreen } from "../screens/StartScreen.js";
import { AnyWalletConnectUI } from "./AnyWalletConnectUI.js";
import {
@@ -27,10 +29,20 @@ export const ConnectModalContent = (props: {
}) => {
const { setModalVisibility, onClose } = props;
const { screen, setScreen, initialScreen } = props.screenSetup;
const { wallets, accountAbstraction } = useConnectUI();
const { onConnect, connectModal, connectLocale } = useConnectUI();
const {
wallets,
accountAbstraction,
auth,
onConnect,
connectModal,
connectLocale,
client,
} = useConnectUI();
const { connect } = useConnect();
const siweAuth = useSiweAuth(auth);
const showSignatureScreen = siweAuth.requiresAuth && !siweAuth.isLoggedIn;
const handleConnected = useCallback(
(wallet: Wallet) => {
connect(wallet);
@@ -43,9 +55,21 @@ export const ConnectModalContent = (props: {
setModalVisibility(true);
});
onClose();
// show sign in screen if required
if (showSignatureScreen) {
setScreen(reservedScreens.signIn);
} else {
onClose();
}
},
[setModalVisibility, onClose, onConnect, connect],
[
setModalVisibility,
onClose,
onConnect,
connect,
showSignatureScreen,
setScreen,
],
);
const handleBack = useCallback(() => {
@@ -107,6 +131,18 @@ export const ConnectModalContent = (props: {
);
};
const signatureScreen = (
<SignatureScreen
onDone={onClose}
modalSize={connectModal.size}
termsOfServiceUrl={connectModal.termsOfServiceUrl}
privacyPolicyUrl={connectModal.privacyPolicyUrl}
auth={auth}
client={client}
connectLocale={connectLocale}
/>
);
return (
<ScreenSetupContext.Provider value={props.screenSetup}>
{connectModal.size === "wide" ? (
@@ -114,7 +150,7 @@ export const ConnectModalContent = (props: {
left={walletList}
right={
<>
{/* {screen === reservedScreens.signIn && signatureScreen} */}
{screen === reservedScreens.signIn && signatureScreen}
{screen === reservedScreens.main && getStarted}
{screen === reservedScreens.getStarted && getStarted}
{screen === reservedScreens.showAll && showAll}
@@ -124,7 +160,7 @@ export const ConnectModalContent = (props: {
/>
) : (
<ConnectModalCompactLayout>
{/* {screen === reservedScreens.signIn && signatureScreen} */}
{screen === reservedScreens.signIn && signatureScreen}
{screen === reservedScreens.main && walletList}
{screen === reservedScreens.getStarted && getStarted}
{screen === reservedScreens.showAll && showAll}
@@ -0,0 +1,321 @@
import { keyframes } from "@emotion/react";
import { ReloadIcon } from "@radix-ui/react-icons";
import { useCallback, useEffect, useRef, useState } from "react";
import type { ThirdwebClient } from "../../../../../client/client.js";
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import {
useActiveWallet,
useDisconnect,
} from "../../../../core/hooks/wallets/wallet-hooks.js";
import { wait } from "../../../../core/utils/wait.js";
import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
import { Spacer } from "../../components/Spacer.js";
import { Spinner } from "../../components/Spinner.js";
import { WalletImage } from "../../components/WalletImage.js";
import { Container, ModalHeader } from "../../components/basic.js";
import { Button } from "../../components/buttons.js";
import { Text } from "../../components/text.js";
import { useCustomTheme } from "../../design-system/CustomThemeProvider.js";
import { StyledDiv } from "../../design-system/elements.js";
import { iconSize, radius, spacing } from "../../design-system/index.js";
import type { ConnectButtonProps } from "../ConnectWalletProps.js";
import { TOS } from "../Modal/TOS.js";
import type { ConnectLocale } from "../locale/types.js";
import { WalletLogoSpinner } from "./WalletLogoSpinner.js";
type Status = "signing" | "failed" | "idle";
export const SignatureScreen: React.FC<{
onDone: () => void;
modalSize: "compact" | "wide";
termsOfServiceUrl?: string;
privacyPolicyUrl?: string;
connectLocale: ConnectLocale;
client: ThirdwebClient;
auth: ConnectButtonProps["auth"];
}> = (props) => {
const {
onDone,
modalSize,
termsOfServiceUrl,
privacyPolicyUrl,
connectLocale,
} = props;
const siweAuth = useSiweAuth(props.auth);
const [status, setStatus] = useState<Status>("idle");
const { disconnect } = useDisconnect();
const wallet = useActiveWallet();
const locale = connectLocale.signatureScreen;
const signIn = useCallback(async () => {
try {
setStatus("signing");
await siweAuth.doLogin();
onDone();
} catch (err) {
await wait(1000);
setStatus("failed");
console.error("failed to log in", err);
}
}, [onDone, siweAuth]);
// this should not happen
if (!wallet) {
return <LoadingScreen />;
}
if (wallet.id === "inApp" || wallet.id === "embedded") {
return (
<HeadlessSignIn
signIn={signIn}
status={status}
connectLocale={connectLocale}
wallet={wallet}
/>
);
}
const handleRetry = () => {
signIn();
};
return (
<Container animate="fadein" fullHeight flex="column">
<Container
p="lg"
style={{
paddingBottom: 0,
}}
>
<ModalHeader title={locale.instructionScreen.title} />
</Container>
<Container
style={{
paddingTop: 0,
paddingBottom: spacing.xl,
}}
flex="column"
px={modalSize === "compact" ? "lg" : "xxl"}
center="y"
expand
>
{status === "idle" ? (
<>
<Container flex="row" center="x" animate="fadein" py="3xl">
<PulsatingContainer>
<WalletImage id={wallet.id} client={props.client} size={"80"} />
</PulsatingContainer>
</Container>
<Text center multiline balance>
{locale.instructionScreen.instruction}
</Text>
<Spacer y="lg" />
<Button
fullWidth
variant="accent"
onClick={signIn}
style={{
alignItems: "center",
padding: spacing.md,
}}
>
{connectLocale.signatureScreen.instructionScreen.signInButton}
</Button>
<Spacer y="sm" />
<Button
fullWidth
variant="secondary"
onClick={() => {
disconnect(wallet);
}}
style={{
alignItems: "center",
padding: spacing.md,
}}
>
{connectLocale.signatureScreen.instructionScreen.disconnectWallet}
</Button>
</>
) : (
<>
<Container py="3xl">
<WalletLogoSpinner error={status === "failed"} id={wallet.id} />
</Container>
<Container flex="column" gap="md" animate="fadein" key={status}>
<Text size="lg" center color="primaryText">
{status === "failed"
? locale.signingScreen.failedToSignIn
: locale.signingScreen.inProgress}
</Text>
{status === "signing" && (
<Text center multiline balance>
{connectLocale.signatureScreen.signingScreen.prompt}
</Text>
)}
{status === "failed" && (
<Container>
<Spacer y="sm" />
<Button
fullWidth
variant="accent"
onClick={handleRetry}
style={{
gap: spacing.xs,
alignItems: "center",
padding: spacing.md,
}}
>
<ReloadIcon width={iconSize.sm} height={iconSize.sm} />
{locale.signingScreen.tryAgain}
</Button>
<Spacer y="sm" />
<Button
fullWidth
variant="secondary"
onClick={() => {
disconnect(wallet);
}}
style={{
alignItems: "center",
padding: spacing.md,
}}
>
{locale.instructionScreen.disconnectWallet}
</Button>
</Container>
)}
</Container>
</>
)}
</Container>
{(termsOfServiceUrl || privacyPolicyUrl) && (
<Container p="md" animate="fadein">
<TOS
termsOfServiceUrl={termsOfServiceUrl}
privacyPolicyUrl={privacyPolicyUrl}
/>
</Container>
)}
</Container>
);
};
function HeadlessSignIn({
signIn,
status,
connectLocale,
wallet,
}: {
signIn: () => void;
status: Status;
connectLocale: ConnectLocale;
wallet: Wallet;
}) {
const locale = connectLocale.signatureScreen;
const mounted = useRef(false);
const { disconnect } = useDisconnect();
useEffect(() => {
if (mounted.current) {
return;
}
mounted.current = true;
signIn();
}, [signIn]);
return (
<Container p="lg" fullHeight flex="column" animate="fadein">
<ModalHeader title={locale.signingScreen.title} />
<Container
expand
flex="row"
center="both"
style={{
minHeight: "250px",
}}
>
{status === "signing" && <Spinner size="xl" color="accentText" />}
{status === "failed" && (
<>
<Container>
<Spacer y="lg" />
<Text size="lg" center color="danger">
{locale.signingScreen.failedToSignIn}
</Text>
<Spacer y="lg" />
<Button
fullWidth
variant="accent"
onClick={() => {
signIn();
}}
style={{
gap: spacing.xs,
alignItems: "center",
padding: spacing.md,
}}
>
<ReloadIcon width={iconSize.sm} height={iconSize.sm} />
{locale.signingScreen.tryAgain}
</Button>
<Spacer y="sm" />
<Button
fullWidth
variant="secondary"
onClick={() => {
disconnect(wallet);
}}
style={{
alignItems: "center",
padding: spacing.md,
}}
>
{locale.instructionScreen.disconnectWallet}
</Button>
</Container>
</>
)}
</Container>
</Container>
);
}
const pulseAnimation = keyframes`
0% {
transform: scale(0.9);
}
100% {
opacity: 0;
transform: scale(1.4);
}
`;
const PulsatingContainer = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
position: "relative",
"&::before": {
content: '""',
display: "block",
position: "absolute",
left: 0,
top: 0,
bottom: 0,
right: 0,
background: theme.colors.accentText,
animation: `${pulseAnimation} 2s cubic-bezier(0.175, 0.885, 0.32, 1.1) infinite`,
zIndex: -1,
borderRadius: radius.xl,
},
};
});