# Adding the SDK
If you want to skip the step by step guide, you can refer to one of [the
example apps](/example-apps#react-18) or [the quickstart](/quickstart).
## Installing the SDK
To get started, you need to install the SDK. You can do this by running the following command:
```bash npm
npm install @dynamic-labs/sdk-react-core
```
```bash yarn
yarn add @dynamic-labs/sdk-react-core
```
{" "}
The SDK uses Viem by default. If you need Ethers, please complete this guide
first, and you'll find a link to the Ethers guide at the bottom.
## Adding the context provider
The miminum implementation involves wrapping your app with the [`DynamicContextProvider` component](/adding-dynamic/dynamic-context-provider).
This provider allows your application to use Dynamic functionality.
Make sure to replace `XXXXX` with your environment ID, found
[here](https://app.dynamic.xyz/dashboard/developer/api).
```jsx
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
return (
);
```
If you have any issues while implementing at this stage, check out [the React
troubleshooting section](/troubleshooting/react).
Now you have access to Dynamic inside your app, you can start using the SDK to enable signup/login, once you've enabled a chain!
If you want to skip the step by step guide, you can refer to one of [the
example apps](/example-apps#next-js).
## Installing the SDK
To get started, you need to install the SDK. You can do this by running the following command:
```bash npm
npm install @dynamic-labs/sdk-react-core
```
```bash yarn
yarn add @dynamic-labs/sdk-react-core
```
The SDK uses Viem by default. If you need Ethers, please complete this guide
first, and you'll find a link to the Ethers guide at the bottom.
## Adding the context provider
The miminum implementation involves installing the [`DynamicContextProvider` component](adding-dynamic/dynamic-context-provider) and wrapping your app with it. Because the components will need to be client side rendered, we recommend creating a new file called Dynamic.ts and exporting Dynamic from there:
Make sure to replace `XXXXX` with your environment ID, found
[here](https://app.dynamic.xyz/dashboard/developer/api).
```jsx
// lib/dynamic.ts
"use client";
export * from "@dynamic-labs/sdk-react-core";
```
You can then import Dynamic from this file in your pages:
```jsx
// app/layout.tsx
import { DynamicContextProvider } from "../lib/dynamic";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
If you have any issues while implementing at this stage, check out [the NextJS
troubleshooting section](troubleshooting/next).
Now you have access to Dynamic inside your app, you can start using the SDK to enable signup/login, once you've enabled a chain!
Click here to learn how to enable chains and wallets with the SDK
# DynamicContextProvider props
This is the main provider for the Dynamic SDK. It is used to initialize the SDK and pass in settings. Learn how to install the SDK and add the context provider [here](/adding-dynamic/adding-the-sdk).
Below we provide the complete list of props that can be passed to the `DynamicContextProvider` component. It's a lot! Don't worry though, we reference each prop in the respective tutorials, so you'll learn about them in context as you complete different guides.
## Settings
Passed in using the "settings" prop, available when you first initialize `DynamicContextProvider` in your App.
| Method | Description |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| accessDeniedMessagePrimary?: string; | Custom main error message used when a wallet attempts to authenticate via Dynamic and is rejected because it does not have access. Defaults to “Access denied” |
| accessDeniedMessageSecondary?: string; | Custom secondary error message used when a wallet attempts to authenticate via Dynamic and is rejected because it does not have access. Defaults to “We couldn't find your wallet address on our access list of customers.” |
| accessDeniedButton?: AccessDeniedCustomButton; | Custom secondary error button text and action when a wallet attempts to authenticate via Dynamic and is rejected because it does not have access. Defaults to "Try another method" and allow user to choose another login option. Please see: [AccessDeniedCustomButton](/react-sdk/objects/access-denied-custom-button) |
| coinbaseWalletPreference?: 'all' \| 'smartWalletOnly' \| 'eoaOnly'; | Determines which connection options users will see. Defaults to all. Please see: [https://www.smartwallet.dev/sdk/makeWeb3Provider#options-optional](https://www.smartwallet.dev/sdk/makeWeb3Provider#options-optional) |
| cssOverrides?: string \| JSX.Element; | Allows for custom CSS overrides via ShadowDom. Please see: [Custom CSS](/design-customizations/css/custom-css) |
| debugError?: boolean; | When enabled, errors caught during the authentication step and their stack trace will be set in a state and displayed in the front end. |
| deepLinkPreference?: 'native' \| 'universal'; | Used to control the type of deep link used when connecting a mobile wallet. Defaults to 'native'. This is useful for example if your app is running in a webview of a native mobile app, and you want to be able to link out to any wallet without having to modify your iOS build config. In this case, you can set this to 'universal'. |
| displaySiweStatement?: boolean; | When enabled, this will show a message on terms of service and privacy policy in the signing message on the authentication step. |
| enableVisitTrackingOnConnectOnly?: boolean; | When the Dynamic SDK is being used with auth mode = connect-only, we require this to be set to “true” to track visits of connected wallets in this environment. |
| environmentId: string; | You will need to specify your app’s environment ID, which refers to a unique environment and its settings in Dynamic. To get your environment ID, go to [dashboard’s API tab](https://app.dynamic.xyz/dashboard/api) |
| eventsCallbacks?: DynamicEventsCallbacks; | This prop allows custom event callbacks after important events during the authentication flows for Dynamic's React SDK. For more information, please see [the main SDK reference](/react-sdk) |
| evmNetworks?: \[]EvmNetwork | An array of [EvmNetwork](/react-sdk/objects/evmNetwork), more details in [the custom networks guide](/chains/network-switching). |
| initialAuthenticationMode?: AuthModeType; | Sets the initial SDK authentication mode to either connect-only or connect-and-sign. connect-only does not require users to authenticate to prove ownership of their wallet. connect-and-sign will require an additional step for users to prove ownership of their wallet. Defaults to connect-and-sign. See also the [setAuthMode](/react-sdk/hooks/usedynamiccontext) method, which allows you to toggle this after the app has loaded. |
| logLevel?: keyof typeof LogLevel; | The log level to use for client side logging with our SDK. Defaults to WARN |
| mobileExperience?: 'in-app-browser' \| 'redirect' | Available from SDK V1.1. Controls the mobile user experience of connecting to native app wallets. Defaults to 'in-app-browser', meaning that we will deep link into the native wallet's in-app browser. Set to 'redirect' for an experience where the user accepts prompts in the native wallet and is redirected back to the mobile browser. This is currently applicable to Phantom. |
| networkValidationMode?: 'always' \| 'sign-in' \| 'never' | Note: Supported only in connect-only. Defines how the Dynamic SDK will enforce the wallet network. - always - requires the wallet to be on an enabled network while connecting and while the session is active - sign-in - will only enforce the network on connect - never - completely turn off the network validation. Defaults to `sign-in`. |
| newToWeb3WalletChainMap?: ChainToWalletMap; | When provided, this is used in the Get your first wallet view in the wallet list modal. This can be helpful to steer initial customers who do not have a wallet to download and use a specific chain and wallet. |
| onboardingImageUrl?: string; | When provided, this image will be shown during the customer information capture step after a wallet successfully authenticates with Dynamic and the environment requires additional information from the user. |
| policiesConsentInnerComponent?: ReactNode \| ReactNode\[]; | For environments with the username setting enabled, you will need to pass in a value for this prop to show a custom prompt or label for the policies contest checkboxes displayed during customer information capture after signing. |
| privacyPolicyUrl?: string; | When provided, this will display a privacy policy URL on the signing step. This should be set to a URL of your organization’s privacy policy web page. |
| recommendedWallets: [RecommendedWallet](/wallets/advanced-wallets/recommend-wallets#general-wallet-list)\[]; | Available from V1.2 only. An array of wallet keys that will be recommended to the user. See more in [our section on recommending wallets](/wallets/advanced-wallets/recommend-wallets). |
| shadowDOMEnabled?: boolean; | Shadow DOM allows the SDK to look as intended wherever it is hosted and it plays nicely with your existing styling system. For more information, please see: [Custom CSS](/design-customizations/css/custom-css) |
| siweStatement?: string; | When provided, this custom message will be shown on the message to sign for the wallet signing step. |
| termsOfServiceUrl?: string; | When provided, this will display a terms of service URL on the signing step. This should be set to a URL of your organization’s terms of service web page. |
| walletConnectors?: [walletConnector](/chains/enabling-chains)\[] | When provided, will enable whatever connectors you pass so that your end user can signup/login using those wallets. For the list of available connectors, see the walletConnectors section below. |
| walletConnectorExtensions?: \[]WalletConnectorExtension | Currently only used to enable Ethers instead of Viem which is the default, for example see examples section below. For more info on Ethers instead of viem please [see here](/quickstart#step-4-stick-with-viem-or-switch-to-ethers) |
| walletConnectPreferredChains | Relevant to Wallet Connect only, used to determine which chains to establish a connection with first. The value must be an array containing [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) chain ID's. The format for this is `{namespace-goes-here}:{reference-goes-here}`. Currently we only support Ethereum, so it will always be `eip155:{reference-goes-here}`. For example, Ethereum mainnet being \['eip155:1'] |
| walletsFilter?: (options: WalletOption\[]) => [WalletOption](/react-sdk/objects/wallet-option)\[]; | When specified, this is a custom function that allows clients of Dynamic SDK to filter out wallet options based on a function on the wallet options. For example: walletsFilter: (wallets) => wallets.filter((w) => w\.key !== "walletx") will exclude walletx from showing up on the wallet list. |
| bridgeChains?: [bridgeChains](/building-bridges/dynamic-bridge-widget) | (Only use with bridging) Which chains should be used for bridging. |
| socialProvidersFilter?: (providers: SocialOAuthProvider\[]) => SocialOAuthProvider\[]; | When specified, this is a custom function that allows clients of Dynamic SDK using social auth to filter or modify the order of the social options displayed to the user. For example, we can only show github oauth option: socialProvidersFilter: (providers) => (\['github']). |
| overrides: \{views: SdkView\[]} | Used only for passing in [Views](/react-sdk/objects/views) right now. You can find examples in the view page previously mentioned or a more complete example [here](/design-customizations/tutorials/login-views-guide). |
| enableConnectOnlyFallback?: boolean | When `true`, enables the SDK to fallback to wallet connect-only auth mode if a connection to our servers is not possible. Available in version 1.1 and above. |
# Using Ethers instead of Viem
The SDK and above ships with Viem by default as it is much lighter than Ethers and is generally more performant.
We have also made viem a peerDependency to avoid bundling it multiple times, as other other packages such as wagmi also need it.
For the above reason, viem will be installed as a peerDependency when you install the SDK with npm, even if you want to use Ethers.
If you are using yarn instead of npm, even if you want to use Ethers, you will need to install viem manually:
```bash
yarn i viem
```
Dynamic only supports version v6+ of Ethers. In order to use it, simply import the ethers methods you need from `@dynamic-labs/ethers-v6` directly in the component where you are using them.
```bash
npm i @dynamic-labs/ethers-v6
```
```jsx
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'
import { getWeb3Provider,getSigner, } from '@dynamic-labs/ethers-v6'
const { primaryWallet } = useDynamicContext()
const provider = await getWeb3Provider(primaryWallet)
const signer = await getSigner(primaryWallet)
// do your thing
```
# Wagmi & Dynamic
#### Installing packages
First, you want to install the DynamicWagmiConnector package using `npm` or `yarn`
```shell npm
npm install viem wagmi @tanstack/react-query @dynamic-labs/wagmi-connector @dynamic-labs/sdk-react-core @dynamic-labs/ethereum
```
```shell yarn
yarn add viem wagmi @tanstack/react-query @dynamic-labs/wagmi-connector @dynamic-labs/sdk-react-core @dynamic-labs/ethereum
```
```shell pnpm
pnpm add viem wagmi @tanstack/react-query @dynamic-labs/wagmi-connector @dynamic-labs/sdk-react-core @dynamic-labs/ethereum
```
#### Configure Wagmi and Dynamic
Here's a full code snippet with all the setup code required.
```TypeScript
import {
DynamicContextProvider,
DynamicWidget,
} from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
import { DynamicWagmiConnector } from '@dynamic-labs/wagmi-connector';
import {
createConfig,
WagmiProvider,
useAccount,
} from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http } from 'viem';
import { mainnet } from 'viem/chains';
const config = createConfig({
chains: [mainnet],
multiInjectedProviderDiscovery: false,
transports: {
[mainnet.id]: http(),
},
});
const queryClient = new QueryClient();
export default function App() {
return (
);
}
function AccountInfo() {
const { address, isConnected, chain } = useAccount();
return (
wagmi connected: {isConnected ? 'true' : 'false'}
wagmi address: {address}
wagmi network: {chain?.id}
);
};
```
Two things to note here:
1. `multiInjectedProviderDiscovery` is set to `false` – this is because Dynamic implements the multi injected provider discovery protocol itself. If you'd like to keep this enabled on Wagmi, please do, but you might see some undefined behavior and we might not be able to debug.
2. While this example configures mainnet as the only chain, you can pass in all your supported chains to the `chains` array. For more info, see the "Chain Configuration" section below.
Throughout your app, you can now use Wagmi hooks like `useAccount` and they will automatically sync to the wallet that you logged in with via Dynamic.
Make sure to call `createConfig` at the top-level of your app. If you call it
inside of a component, it will be called during each render, which can lead to
unexpected behavior.
#### Chain Configuration
If you are upgrading from Dynamic + Wagmi v1, then this config will look new to you. Previously, Dynamic was automatically updating the Wagmi config with the chains that you configured in your Dynamic Dashboard.
This behavior has changed in v2 to give you more control over your Wagmi config and for a simpler integration. What this means is that you will need to pass to Wagmi all of the chains you have configured with Dynamic.
For example, if in Dynamic you have enabled Ethereum Mainnet, Optimism and Base, you will need to update your Wagmi config to look like this:
```TypeScript
import {
createConfig,
WagmiProvider,
} from 'wagmi';
import { http } from 'viem';
import { mainnet, optimism, base } from 'viem/chains';
const config = createConfig({
chains: [mainnet, optimism, base],
multiInjectedProviderDiscovery: false,
transports: {
[mainnet.id]: http(),
[optimism.id]: http(),
[base.id]: http(),
},
});
```
### Adding Custom Networks
As you probably already know, you can add a custom EVM network through Dynamic using [the evmNetworks prop](/chains/evmNetwork). If you want to use these custom networks with Wagmi, you will need to declare it in your Wagmi config.
You can use our util function called \[getOrMapViemChain] to convert the EVM network object to a Viem chain object.
Here's an example of adding the Morph network to both Dynamic and to the Wagmi config:
```tsx
import {
createConfig,
WagmiProvider,
} from 'wagmi';
import { http } from 'viem';
import { getOrMapViemChain } from '@dynamic-labs/sdk-react-core';
const customEvmNetworks = [
{
blockExplorerUrls: ["https://explorer-holesky.morphl2.io/"],
chainId: 2810,
name: "Morph",
rpcUrls: ["https://rpc-quicknode-holesky.morphl2.io"],
iconUrls: ["https://avatars.githubusercontent.com/u/132543920?v=4"],
nativeCurrency: {
name: "Ethereum",
symbol: "ETH",
decimals: 18,
},
networkId: 2810,
},
]
export const wagmiConfig = createConfig({
chains: [
mainnet,
...customEvmNetworks.map(getOrMapViemChain),
],
client({ chain }) {
return createClient({
chain,
transport: http(),
});
},
});
```
### Adding Private RPCs
Note that if you are using [a private RPC in Dynamic](/chains/rpc-urls#dashboard-configuration), you will also need to declare that in your Wagmi config. Luckily this is as simple as passing it into your http transport in Wagmi:
```tsx
import {
createConfig,
WagmiProvider,
} from 'wagmi';
export const wagmiConfig = createConfig({
chains: [mainnet],
multiInjectedProviderDiscovery: false,
transports: {
[mainnet.id]: http('your-private-rpc-url'),
},
});
```
### Further Resources
For docs on `createConfig`, see: [https://wagmi.sh/react/api/createConfig](https://wagmi.sh/react/api/createConfig)
For more general information on what you can do with Wagmi, check out their getting started docs: [https://wagmi.sh/react/getting-started](https://wagmi.sh/react/getting-started)
# Create new allowlist for a environment
POST /environments/{environmentId}/allowlists
# Delete an allowlist
DELETE /allowlists/{allowlistId}
# Delete an allowlist entry
DELETE /allowlistEntries/{allowlistEntryId}
# Disable the allow list
PUT /allowlists/{allowlistId}/disable
# Enable the allowlist
PUT /allowlists/{allowlistId}/enable
# Get all allowlists for a environment
GET /environments/{environmentId}/allowlists
# Get allowlist by id
GET /allowlists/{allowlistId}
# Get all entries for an allowlist
GET /allowlists/{allowlistId}/entries
# Create a new entry for an allowlist
POST /allowlists/{allowlistId}/entries
# Update the outcome, scope, or name of an allowlist entry by ID
PUT /allowlists/{allowlistId}
# getVisitAnalytics
GET /environments/{environmentId}/analytics/visits
Fetch visit analytics
# Fetch visit analytics
GET /environments/{environmentId}/analytics/wallets
Fetch wallets breakdown
# Disables the Sanctions API
PUT /environments/{environmentId}/integrations/chainalysis/sanctions/disable
# Enable the Sanctions API
PUT /environments/{environmentId}/integrations/chainalysis/sanctions/enable
# Find the Chainalysis configuration for an environment.
GET /environments/{environmentId}/integrations/chainalysis
# Get tokens for passed chainName.
GET /chains/{chainName}/tokens
# Create a new custom field for an environment
POST /environments/{environmentId}/custom/fields
# Delete a specific custom field by its ID
DELETE /custom/fields/{customFieldId}
# Retrieve a specific custom field by its ID
GET /custom/fields/{customFieldId}
# Get the custom fields for an environment
GET /environments/{environmentId}/custom/fields
# Update an existing custom field by its ID
PUT /custom/fields/{customFieldId}
# Deletes an environment by ID
DELETE /environments/{environmentId}
# Find an environment by ID
GET /environments/{environmentId}
# Get Live and Sandbox environments by projectId
GET /projects/{projectId}/environments
# Get keys for an environment given environmentId
GET /environments/{environmentId}/keys
# Get the unique number of visitors for an environment by environment ID
GET /environments/{environmentId}/statistics/visitors
# Updates the environment settings
PUT /environments/{environmentId}
# Get event types
GET /eventTypes
# Get environment events
GET /environments/{environmentId}/events
# Create a new export request for the project environment
POST /environments/{environmentId}/exports
# Download an export by ID
GET /exports/{exportId}/download
# Get the exports for an environment
GET /environments/{environmentId}/exports
# Get an export using the ID
GET /exports/{exportId}
# Check if the external provided JWT is valid for the specified environment
POST /environments/{environmentId}/externalJwt/check
# Creates a new gate for the project environment
POST /environments/{environmentId}/gates
# Delete a gate
DELETE /gates/{gateId}
# Disable the gate for the environment
PUT /gates/{gateId}/disable
# Enable the gate for the environment
PUT /gates/{gateId}/enable
# Get the gates for an environment
GET /environments/{environmentId}/gates
# Gets a gate
GET /gates/{gateId}
# Updates a gate
PUT /gates/{gateId}
# Creates an invite to the organization
POST /organizations/{organizationId}/invites
# Delete invite for user
DELETE /invites/{inviteId}
# Get all the user invites
GET /invites
# Fetches all the Invites the belong to the organization
GET /organizations/{organizationId}/invites
# Update invite for user (accept/reject)
PUT /invites/{inviteId}
# Delete user membership from being an admin of an organization
DELETE /members/{memberId}
# Get all users that are admins for an organization
GET /organizations/{organizationId}/members
# Get Membership Environment IDs
GET /membershipEnvironmentIds
Retrieve the list of project environments that the user is a member of.
# Update a given members role
PUT /members/{memberId}
# Creates organization
POST /organizations
# Deletes an organization by ID
DELETE /organizations/{organizationId}
# Find the subscription of an organization using its ID
GET /organizations/{organizationId}/billing/subscription
# Find an organization by ID
GET /organizations/{organizationId}
# Fetches all the active organizations that the user has access to
GET /organizations
# Update an organization by ID
PUT /organizations/{organizationId}
# Upgrade organziation to advanced plan
PUT /organizations/{organizationId}/billing/upgrade
# Adds an allowed origin for this project environment
POST /environments/{environmentId}/origins
# Delete a origin by id
DELETE /origins/{originId}
# Get all the allowed origins for a project environment
GET /environments/{environmentId}/origins
# Creates a new project
POST /organizations/{organizationId}/projects
# Deletes a project by ID
DELETE /projects/{projectId}
# Find an project by ID
GET /projects/{projectId}
# Fetches all the active projects the belong to the organization
GET /organizations/{organizationId}/projects
# Update a project
PUT /projects/{projectId}
# Get a sdk view given a type and environment
GET /environments/{environmentId}/sdkViews/{sdkViewType}
# Get the sdk views for an environment
GET /environments/{environmentId}/sdkViews
# Updates the configs for the sdk view and project environment
PUT /environments/{environmentId}/sdkViews/{sdkViewType}
# Revoke a session
PUT /sessions/{sessionId}/revoke
# Creates a new provider for the project environment
POST /environments/{environmentId}/settings/providers
# Delete a provider by providerId
DELETE /settings/providers/{providerId}
# Disable the provider for the environment
PUT /settings/providers/{providerId}/disable
# Enable the provider for the environment
PUT /settings/providers/{providerId}/enable
# Get the URLs for the environment providers
GET /environments/{environmentId}/settings/providers/urls
# Get the providers for an environment
GET /environments/{environmentId}/settings/providers
# Gets a provider
GET /settings/providers/{providerId}
# Updates a provider
PUT /settings/providers/{providerId}
# Delete a token by token id
DELETE /tokens/{tokenId}
# Get all the tokens for a project environment (does not include the raw token)
GET /environments/{environmentId}/tokens
# Create a new API Token
POST /environments/{environmentId}/tokens
# Creates many new users
POST /environments/{environmentId}/users/bulk
# Creates a new user
POST /environments/{environmentId}/users
# Delete user
DELETE /users/{userId}
# Get all users for an environment
GET /environments/{environmentId}/users
# Get a user by Id
GET /users/{userId}
# Get the access token for a user OAuth account
GET /oauthAccounts/{oauthAccountId}/accessToken
# Revoke sessions by user ID
POST /users/{userId}/sessions/revoke
# Update a user
PUT /users/{userId}
# Creates a new embedded wallet for a user given an identifier
POST /environments/{environmentId}/embeddedWallets
Creates a new embedded wallet for a user given an email or userId. If an email is provided and it is not associated with an existing user this call will also create a new user.
# Creates a new embedded wallet. This API is meant to be called from a frame server.
POST /environments/{environmentId}/embeddedWallets/farcaster
Creates a new embedded wallet. This API is meant to be called from a frame server.
# Creates a new wallet for the user
POST /users/{userId}/wallets
Creates a new wallet for the user. Note that this API is not meant for creating embedded wallets. To create embedded wallets use the /embeddedWallets endpoint.
# Delete wallet
DELETE /wallets/{walletId}
# Get a wallet using the ID
GET /wallets/{walletId}
# Get wallets by user
GET /users/{userId}/wallets
# Creates a new Webhooks for the project environment
POST /environments/{environmentId}/webhooks
# Delete the Webhook for an environment
DELETE /environments/{environmentId}/webhooks/{webhookId}
# Get the Webhook for an environment
GET /environments/{environmentId}/webhooks/{webhookId}
# Get the Messages for an webhook
GET /environments/{environmentId}/webhooks/{webhookId}/messages
# Get the Webhooks for an environment
GET /environments/{environmentId}/webhooks
# Redeliver message for an webhook
POST /environments/{environmentId}/webhooks/{webhookId}/messages/{messageId}/redeliver
# Update the Webhook for an environment
PUT /environments/{environmentId}/webhooks/{webhookId}
# Introduction
## Introduction
Dynamic provides a robust API that allows developers to securely access their
dashboard environment's data and programmatically update settings relevant to
their Dynamic-powered application.
## Authentication
All APIs in this section require a bearer token used to authenticate requests
and make sure the caller is authorized to access the resources in the requests.
Please follow these steps:
#### Get an API token from Dashboard
1. Go to the your environment's dashboard
[Developer Tab](https://app.dynamic.xyz/dashboard/developer/api)
2. In the "API Token" section, click on "Create Token"
3. Provide a name for the token. The best practice here would be to name the
token after the system you intend to use this token with. A few examples:
"Mycompany Admin", "Ben's personal token", or "background-job service".
4. **Make sure to copy the API token** before closing the modal. This is the
last time you will have access to the plaintext API token! Dynamic DOES NOT
have access to the plaintext API token anywhere. If you lose this API token,
you will need to create a new one.
5. The token should start with the prefix `dyn_` followed by 56 alphanumeric
characters.
#### Using the API token
Use the token generated through dashboard to programmatically access Dynamic's
API by adding it to the `Authentication` header of your HTTP request.
Example:
```bash curl
curl \
--location 'https://app.dynamic.xyz/api/v0/environments/<>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ADD_YOUR_DYN_TOKEN_HERE'
```
## Standard errors
* `400` - Bad Request. The form of the request is invalid. Please check that
the path parameters, query parameters, or the request's body contains the
correct and expected information defined in our docs.
* `401`- Unauthorized. The endpoint that is being accessed requires an
`Authorization` header.
* `403` - Forbidden. The token authorized for the HTTP call does not have
access to the resource defined by the endpoint (eg. the specific environment,
allowlist, user, etc.)
* `404` - Not found. The path of the requested resource could not be found.
Please check that the URL path is correct or that the ID provided is correct.
## Rate Limits
Dynamic's API endpoints are subject to rate limits. Refer to [Rate Limits](/developer-dashboard/rate-limits) for more information.
# AccessOutcomeEnum
# AddDeeplinkUrlRequest
# Allowlist
# AllowlistEntriesResponse
# AllowlistEntry
# AnalyticsSessionsByDate
# AnalyticsVisitResponse
# AnalyticsWalletsBreakdownResponse
# AuthModeEnum
# AuthSettings
# AuthStorageEnum
# AuthenticatorTransportProtocol
# BadRequest
# BaseUser
# BillingSubscription
# BillingSubscriptionPeriod
# BillingSubscriptionPlanTypeEnum
# BulkUserCreateResponse
# ChainConfiguration
# ChainConfigurations
# ChainEnum
# ChainToken
# ChainTokensResponse
# ChainalysisCheck
# ChainalysisCheckResultEnum
# ChainalysisConfiguration
# CoinbaseMpcWalletProperties
# CompletePasskeyRecoveryRequest
# ConnectRequest
# CountryCode
# CreateEmbeddedWalletParams
# CreateEmbeddedWalletSpecificOpts
# CreateEmbeddedWalletsRequest
# CreateMfaToken
# CreateProjectResponse
# CreateTokenResponse
# CreateTurnkeyEmbeddedWalletSpecificOpts
# CreateUserEmbeddedWalletsFromFarcasterRequest
# CreateUserEmbeddedWalletsRequest
# CreateUserOauthRequest
# CreateWalletRequest
# Currency
# CurrencyType
# CustomField
# CustomFieldRequest
# CustomFieldType
# CustomFieldValidValue
# CustomFieldValidationRules
# CustomFieldsResponse
# CustomHostname
# CustomHostnameCreateRequest
# CustomHostnameStatusEnum
# CustomHostnameVerificationRecord
# CustomHostnameVerificationType
# CustomHostnamesResponse
# DeeplinkUrlResponse
# DeeplinkUrlsResponse
# DnsRecordType
# Duration
# DynamicJwt
# EmailProviderResponse
# EmailVerificationCreateRequest
# EmailVerificationCreateResponse
# EmailVerificationRetryRequest
# EmailVerificationVerifyRequest
# EmbeddedWalletAuthToken
# EmbeddedWalletAuthType
# EmbeddedWalletPasscodeClaimRequest
# EmbeddedWalletProviderEnum
# EmbeddedWalletSecret
# EmbeddedWalletSecretWithUpdatedJwt
# EmbeddedWalletSecurityMethod
# EncodedJwt
# EnvironmentEnum
# EnvironmentVisitorsResponse
# EnvironmentsResponse
# ErrorMessageWithCode
# Event
# EventType
# EventTypesResponse
# EventsResponse
# ExchangeRatesResponse
# Export
# ExportCreateRequest
# ExportCreateRequestFilter
# ExportEmbeddedWalletResponse
# ExportFormatEnum
# ExportModelEnum
# ExportStatusEnum
# ExportsResponse
# ExternalAuth
# ExternalAuthSigninRequest
# ExternalJwtCheckRequest
# ExternalJwtCheckResponse
# FarcasterSignInRequest
# Forbidden
# ForbiddenErrorPayload
# ForbiddenWithErrorAndPayload
# FrameworkSettings
# Gate
# GateCreateRequest
# GateRule
# GateRuleFilter
# GateRuleType
# GateUpdateRequest
# GatesResponse
# GetUserPasskeysResponse
# HCaptchaSettings
# HTTPSUrlOrSNSArn
# HardwareWalletEnum
# HardwareWalletProperties
# HealthcheckResponse
# HealthcheckStatus
# InitEmailAuthRequest
# InitEmailAuthResponse
# InitPasskeyRecoveryRequest
# InitPasskeyRecoveryResponse
# IntegrationSetting
# InternalServerError
# InternalUserFields
# Invite
# InviteConflictResponse
# InviteSendRequest
# InviteStatusEnum
# InviteUpdateRequest
# InvitesResponse
# JwksKey
# JwksResponse
# JwtBlockchainAccount
# JwtPayloadDeprecatedInfo
# JwtVerifiedCredential
# JwtVerifiedCredentialFormatEnum
# Key
# KeyResponse
# KycFieldType
# MFAAuthRecoveryDevicePostRequest
# MFAAuthTotpDevicePostRequest
# MFADevice
# MFADeviceType
# MFAGetDeviceResponse
# MFAGetRecoveryCodesResponse
# MFAListDevicesResponse
# MFARegenRecoveryCodesResponse
# MFARegisterTotpDeviceGetResponse
# MFARegisterTotpDevicePostRequest
# MFARegisterTotpDevicePostResponse
# MFASettings
# MFAUpdateDeviceRequest
# MemberResponse
# MemberRoleField
# MembershipEnvironmentIds
# MergeConflicts
# MergeUser
# MergeUserConflict
# MergeUserConflictResolution
# MergeUserConflictResolutions
# MethodNotAllowed
# MfaBackupCodeAcknowledgement
# MinifiedDynamicJwt
# MobileSettings
# NameService
# NameServiceData
# NativeCurrency
# Network
# NetworkConfiguration
# NetworkConfigurationResponse
# NextJsSettings
# NextViewEnum
# NonEmptyIsoCountryCode
# NonEmptyString
# NonEmptyStringWith255MaxLength
# NonEmptyUrl
# NonEmptyUrlWith255MaxLength
# NonceResponse
# NotFound
# OAuthCode
# OAuthError
# OauthProviderLoginUrl
# OauthProviderRequest
# OauthRedirectRequest
# OauthRedirectRequestIdToken
# OauthRedirectUri
# OauthRequest
# OauthResultRequest
# OauthResultResponse
# OauthResultStatus
# OnrampConfiguration
# OptionalHexString
# OptionalNonEmptyString
# OptionalNonEmptyStringWith255MaxLength
# OptionalNonEmptyUrl
# OptionalNonEmptyUrlWith255MaxLength
# OptionalNullableNonEmptyStringWith255MaxLength
# Organization
# OrganizationFields
# OrganizationMember
# OrganizationMembersResponse
# OrganizationMfaSettings
# OrganizationMfaSettingsResponse
# OrganizationRequest
# OrganizationResponse
# OrganizationsResponse
# OriginResponse
# OriginsResponse
# PasskeyRegistrationCredential
# PasskeyStorage
# PasswordSourceTypeEnum
# PasswordString
# PostAllowlistEntriesRequest
# PostAllowlistsRequest
# PostTokenFields
# PrefetchRequest
# Project
# ProjectEnvironment
# ProjectRequest
# ProjectSettings
# ProjectSettingsChains
# ProjectSettingsDesign
# ProjectSettingsDesignButton
# ProjectSettingsDesignModal
# ProjectSettingsDesignWidget
# ProjectSettingsGeneral
# ProjectSettingsGeneralApps
# ProjectSettingsKyc
# ProjectSettingsPrivacy
# ProjectSettingsSdk
# ProjectSettingsSecurity
# ProjectsResponse
# Provider
# ProviderAgreement
# ProviderCreateRequest
# ProviderEnum
# ProviderUpdateRequest
# ProviderUrl
# ProviderUrlsResponse
# ProvidersResponse
# PublishEvents
# ReactSettings
# RegisterSessionKeyRequest
# RoleEnum
# SdkSettingsRequest
# SdkUser
# SdkView
# SdkViewSection
# SdkViewSectionAlignment
# SdkViewSectionType
# SdkViewType
# SdkViewUpdateRequest
# SdkViewsResponse
# Session
# SignInProviderEnum
# SmsCountryCode
# SmsVerificationCreateRequest
# SmsVerificationCreateResponse
# SmsVerificationRetryRequest
# SmsVerificationVerifyRequest
# SocialSignInProvider
# SocialSignInProviderEnum
# SubscriptionAdvancedScopeEnum
# SubscriptionFreeScopeEnum
# SupportedOnrampsResponse
# SupportedSecurityMethod
# SupportedSecurityMethods
# TimeUnitEnum
# Token
# TokenAddress
# TokenBalance
# TokenCreatedBy
# TokenWithRaw
# TokensResponse
# TooManyRequests
# TurnkeySignedRequest
# TurnkeyStamp
# TurnkeyWalletProperties
# Unauthorized
# UnprocessableEntity
# UnprocessableEntityErrorCode
# UnprocessableEntityErrorPayload
# UpdateProjectRequest
# UpdateProjectResponse
# UpdateRecoveryEmailRequest
# UpdateSelfResponse
# UpdateUserPasskeyRequest
# User
# UserFields
# UserFilterableFieldsEnum
# UserIdentifierTypeEnum
# UserOauthAccessTokenResponse
# UserPasskey
# UserResponse
# UserSearchFilterParams
# UserWalletSelectionRequest
# UserWalletsResponse
# Username
# UsersResponse
# ValidCustomHostname
# ValidStringQueryParam
# VerifyRequest
# VerifyResponse
# VerifyUnlinkRequest
# Visitor
# VisitorFilterableFieldsEnum
# VisitorSearchFilterParams
# VisitorsResponse
# Wallet
# WalletAdditionalAddress
# WalletAddressType
# WalletProperties
# WalletProviderEnum
# WalletPublicKey
# Webhook
# WebhookCreateRequest
# WebhookMessage
# WebhookMessageRedeliveryResponse
# WebhookMessagesResponse
# WebhookUpdateRequest
# WebhooksResponse
# WhenToImplementEnum
# btcWalletString
# caip2
# captchaToken
# ckbWalletString
# deeplinkUrl
# dogeWalletString
# emailOrEmptyString
# kasWalletString
# kdaWalletString
# ltcWalletString
# oAuthAccount
# orderBy
# origin
# phoneNumberOrEmptyString
# sixDigitsVerificationToken
# uuid
# JWTs/Authentication Tokens
## Introduction
When an end user connects their wallet, you, the developer, get a [JSON Web Token (JWT)](https://jwt.io/introduction) that can be used to verify some claims about the end user, notably a proof of ownership over a wallet public address.
We use the JWT information in the SDK to create the user object and their Verified Credentials so that you can simply interact with the user object to get the information you need, so if you don't need to access the JWT directly, you can skip this page and visit the [Users section](/users) instead.
## Storing JWTs
### Localstorage
Once the user has logged in, the JWT gets stored in localstorage under the `dynamic_authentication_token` key. We also store a minified version under the `dynamic_min_authentication_token` key.
### Cookies
If you prefer to store the JWT in a HTTPOnly cookie, you might want to use the [Cookie auth feature](/authentication-methods/cookie-authentication). Bear in mind that with Cookie auth turned on, as long as you're still in Sandbox mode, the JWT will still be stored in localstorage but once you move to live mode it won't be, and you will no longer be able to access it using javascript.
## Accessing JWTs
### Fetch JWT from SDK
The [getAuthToken](/react-sdk/utilities/getauthtoken) utility function will return the JWT token to you from local storage as long as the user is logged in. It will return undefined otherwise.
### Fetch JWT from Local Storage
You can also access the JWT directly from local storage using the following code (note that this will not work if Cookie auth is enabled):
```javascript
const token = localStorage.getItem('dynamic_authentication_token');
if (token) {
// do something with the token
}
```
## Using JWTs
### JWT Vs User Object
Normally, you do not need to use the JWT directly, as all of the information/claims are stored on the user object, which you can learn about in the [Users section](/users).
If however you are doing server-side verification, you can use the JWT to verify the user's identity. See the [Server-side verification](/authentication-methods/how-to-validate-users-on-the-backend) page for more information.
### JWT Payload
##### Standard JWT claims:
See: [https://www.rfc-editor.org/rfc/rfc7519#section-4.1](https://www.rfc-editor.org/rfc/rfc7519#section-4.1)
| Field | Description |
| ----- | --------------------------------------------------------------------------------------------- |
| aud | Audience for the JWT token. This claim shows what domain of the indended audience of the JWT. |
| iss | Issuer of the JWT token. This claim shows app.dynamic.xyz generated and issued the JWT. |
| sub | Subject of the JWT token. userId in the deprecated info claim. |
| iat | Timestamp when the JWT token was issued. |
| exp | Timestamp when the JWT token will expire. |
##### Dynamic-specific claims:
These fields are **optional** and you depends on whether you want to collect this information during onboarding. For more information about collecting this information, see [here](/users/information-capture).
| alias | Alias field from customer information capture. |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| email | Email field from customer information capture. |
| environment\_id | Unique ID of the project environment for the SDK, from [https://app.dynamic.xyz/dashboard/api](https://app.dynamic.xyz/dashboard/api). environmentId in the deprecated info claim. |
| given\_name | First name field from customer information capture. firstName in the deprecated info claim. |
| family\_name | Last name field from customer information capture. lastName in the deprecated info claim. |
| lists | Names of access lists enabled for this user. |
| verified\_credentials | List of all [verified credentials](/react-sdk/objects/verified-credential) connected to this user. |
| verified\_account | If present, this was the most recently signed and verified account. |
#### Example
```JSON{
"alias": "john",
"aud": "https://dashboard.hello.xyz",
"verified_credentials": [
{
"address": "0x000123abc",
"chain": "eip155",
"id": "af615228-99e5-48ee-905d-4575f0a6bfc9",
"wallet_name": "metamask"
}
],
"email": "[[email protected]](/cdn-cgi/l/email-protection)",
"environment_id": "fb6dd9d1-09f5-43c3-8a8c-eab6e44c37f9",
"family_name": "bot",
"given_name": "jon",
"iss": "app.dynamic.xyz/fb6dd9d1-09f5-43c3-8a8c-eab6e44c37f9",
"lists": [ "Community dashboard acess list" ],
"sub": "d261ee91-8ea0-4949-b8bb-b6ab4f712a49",
"verified_account": {
"address": "0x000123abc",
"chain": "eip155",
"id": "af615228-99e5-48ee-905d-4575f0a6bfc9",
"wallet_name": "metamask"
},
"iat": 1660677597,
"exp": 1660684797
}
```
## Using your own JWTs
If you have your own authentication system, you can use your own JWTs to authenticate users in Dynamic. This can be done by exchanging your token for a Dynamic JWT. This will return Dynamic’s standard sign-in artifacts (i.e., a minified JWT and user).
Checkout the [Third-party Authentication](/authentication-methods/third-party-auth) page for more information on how to set up and use this feature.
# Sign Up with Branded Wallets
## Enable Branded Wallet Signup/Login
The first step is to enable the appropriate chains that you'd like to support and add the appropriate connectors to your app. The following [chains and networks configuration guide](/chains/enabling-chains) will show you how to do both.
You can use the default RPC URLs that we provide for each chain/network, but if you'd like to configure your own, please follow [this RPC guide](/chains/rpc-urls).
In [the Log in & User Profile section of the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile), toggle on Wallet Log in under "Branded Wallets" and you're good to go!
## Basic Configuration
In the same section of the dashboard where you enabled Branded Wallets, you can also configure the following:
* Multi-Wallet: This option, when toggled on, allows your end users to have more than one connected wallet to their account and change between them. In this way users don't have to sign out and back in again if they want to use a different wallet, they simply switch between them. You can learn more about Multi-Wallet on [the overview page](/wallets/advanced-wallets/multi-wallet).
* Hide Network: This is used in conjunction with the UI components, specifically [the DynamicUserProfile](/react-sdk/components/dynamicuserprofile) which is available on its own but also bundled as part of [DynamicWidget](/react-sdk/components/dynamicwidget). By default, the user profile shows the network the user is currently connected to and allows them to switch to another. If you want to hide this, simply toggle this option on.
* WalletConnect: Configuration for [https://walletconnect.com/](https://walletconnect.com/)
## Further Configuration
When you enabled Branded Wallets, by default you will be in what's called "connect-and-sign" mode. It's worth reading about the implications of this in [the overview of authentication modes](/wallets/advanced-wallets/connected-vs-authenticated) to decide what's right for your use case.
There's a bunch of further customizations you can do for the Branded Wallet experience including things like [sorting and filtering wallets](/wallets/advanced-wallets/sort-and-filter-wallets), so it's worth reviewing [the advanced wallets section of the docs](/wallets/advanced-wallets) in depth when you're ready.
# Adding Captcha Protection
## Summary
Setting up a captcha can help protect your Dynamic-enabled site from bots. We currently support [hCaptcha](https://www.hcaptcha.com/) as our captcha provider.
Here is an example of how it looks like when enabled in Dynamic's SDK.
## Setting up hCaptcha
To get started, you must first create an hCaptcha account.
1. **Sign up for a new account on [hCaptcha](https://www.hcaptcha.com/)** - We recommend considering upgrading to the "Pro" plan, which allows for "passive" and "friction-free" modes.
2. **Add a new site** - Make sure the hostname corresponds to the domain you are using the Dynamic SDK on. For example, if you plan to set up the Dynamic SDK in `app.myawesomesite.com`, then you should add this value to the Hostname sections in hCaptcha.
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/hcaptcha-general.png)
3. **Get your site key** - This is used by the Dynamic SDK to communicate with hCaptcha and display a captcha challenge before the verification step in the wallet sign-in flow. Copy this value, which we will need to set up in Dynamic.
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/hcaptcha-site-key.png)
4. **Get your secret key** - This is used by the Dynamic server-side backend to verify the captcha result from the SDK, and is never leaked to the SDK. To get your secret key, click on your account profile on the upper right corner and select "Settings". You should see your plaintext secret key, which you should also copy to set up in Dynamic.
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/hcaptcha-settings-button.png)
## Setting up Dynamic captcha
Now that we have hCaptcha set up, we can move on to final configuration within Dynamic.
1. **Log in to Dynamic Dashboard.** On the right navigation bar, click on "Configurations" then select "hCaptcha" under "Onboarding Fields"
2. **Add your site key and secret key from hCaptcha** - See previous steps on how to set up your hCaptcha credentials.
3. **Enable** - When you are ready to enable the captcha, please make sure to switch the "enable" toggle.
# Cookie Based Authentication
#### Introduction
Dynamic can now be configured to set a secure, HttpOnly cookie that can be used for authenticating with Dynamic’s backend. This will contain a minified version of our JWT token.
This feature would also allow your site's end users to sign in on one subdomain and go to another subdomain without that end user needing to log in again using the same Dynamic environment ID.
#### Approach
Dynamic will require the setup of a custom hostname. This is a subdomain that you own, but pointed by DNS CNAME to Dynamic's API. This will allow Dynamic's backend to set secure, HttpOnly cookies on your domain.
For example, if your Dynamic-powered site is [https://app.example.io](https://app.example.io), the custom hostname you could use is [https://auth.example.io](https://auth.example.io).
This would allow your users to sign in with Dynamic on [https://app.example.io](https://app.example.io), receive an HttpOnly secure cookie for `.example.io`. This cookie can then be used on any subdomain ending with `.example.io`, such as [https://marketplace.example.io](https://marketplace.example.io) and [https://shop.example.io](https://shop.example.io).
#### Steps
The following steps would be required to properly set up cookie-based authentication.
1. Go to the dashbaord security page and configure a custom domain.
2. Provide us with a subdomain you intend to use. We need this so we can set a first party cookie on your behalf on our backend. Suggestion here is to prefix with auth. For instance for `example.io`, you can potentially use `auth.example.io`.
3. Follow the instructions to set up DNS. There should be three DNS records: 2 TXT records for site and certificate verification, and 1 CNAME record to proxy the custom subdomain of your choice.
4. Note: In `sandbox`, we will also attempt to set the cookie from the SDK frontend. This will ensure continued support for local development and other preview environments.
5. Once DNS is validated, update the `apiBaseUrl` prop in `DynamicContextProvider` settings. For example:
```TypeScript
...
```
7. Enable the cookies toggle. When this is enabled in `live`, Dynamic **WILL NO LONGER** return a JWT to store in local storage. The auth token will only be set on a cookie.
# Sign Up with Email/Social/SMS
### Introduction
When it comes to onboarding users, having a flexible sign-up flow that can accommodate different preferences is key. Whether users prefer to sign up with their email, phone number, social media accounts, or directly with a wallet, providing the options that fit your user's needs can make the onboarding process smoother, more user-friendly, and increase conversion.
That is why we are offering the flexibility to design the sign up screen to suit your needs. You can now choose whether you want users to sign up with their email, phone number, social accounts, or with wallets. You can configure this as needed.
If you then want to generate a wallet for your users when they sign up using
email, social or sms, please refer to [the embedded wallets
section](/wallets/embedded-wallets/dynamic-embedded-wallets) after configuring email/social/sms below.
#### Sign up with Email
Simply toggle on "Email" in [the Log in & User Profile section of the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile).
#### Sign up with Social
Sign up/sign in with Apple, Discord, Facebook, Farcaster, Github, Google, Telegram, Twitch or Twitter!
Similar to email, you can toggle and configure each social provider in the [social providers section of the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile).
Configuration guides for individual social signup options can be found in [the social providers section of the docs](/social-providers/overview).
Note that when configuring any social provider, you can adjust [the `social` prop](/react-sdk/providers/dynamiccontextprovider#social) in the `DynamicContextProvider` component to customize the user experience i.e. whether you use a redirect or popup.
#### Sign up with SMS
Toggle on "Phone Number" in [the Log in & User Profile section of the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile).
By default, you can use Dynamics credentials to send SMS messages to your users. However, if you want to send beyond US & Canada, you will need to set up your own Twilio account. In order to do this, you toggle off "Use Dynamic's credentials" and a section will open up for you, where you can enter your own credentials.
{/*
## SMS & Embedded Wallets
When you enable SMS sign-up, you can also enable embedded wallets for your users. This means that when a user signs up with their phone number, they will also receive a wallet that they can use to interact with your application.
In order to ensure your end users are adequately protected against attacks like sim swaps, we also require a second factor before the wallet is generated. This can be [a passkey](/wallets/embedded-wallets/passkeys), or [a one time code](/wallets/embedded-wallets/one-time-codes). _We highly recommend using passkeys, which shortens the onboarding flow._
If however, the user loses or deletes their passkey, they will be locked out of their wallet. To prevent this, we recommend prompting users to add an email address to their account. This way, they can recover their wallet if they lose their passkey. When using one time codes, the user will automatically prompted to add their email. */}
# Server-side verification
When an end user connects their wallet, you, the developer, get a [JSON Web Token (JWT)](https://jwt.io/introduction) that can be used to verify some claims about the end user, notably a proof of ownership over a wallet public address.
Upon authentication, we generate a JWT signed with a private key (using RS256 algorithm) that is unique to you. In turn, you can use the associated public key (found in the [API tab](https://app.dynamic.xyz/dashboard/api) of your developer dashboard) to ensure that the token is authentic and hasn't been tampered with. In other words, if a JWT issued by Dynamic can be successfully verified with your public key, the information it contains can be trusted.
You can do this in multiple ways.
#### Option 1: Leverage [NextAuth](https://next-auth.js.org/)
If you are using Next.js, you can easily [integrate the NextAuth library with Dynamic](/guides/frameworks/next-auth) to perform server-side verification and then use a session client-side.
#### Option 2: Leverage [Passport.js](https://www.passportjs.org)
We offer an official [passport-dynamic](https://github.com/dynamic-labs/passport-dynamic) extension.
#### Option 3: Do-It-Yourself Verification
1. Get the JWT through the Dynamic SDK with an [authToken](/react-sdk/utilities/getauthtoken).
2. Send the authToken to the server as a Bearer token
```JavaScript
import { useEffect, useState } from "react";
export const useFetch = (authToken: string | null) => {
const [data, setData] = useState({});
useEffect(() => {
const fetchApi = async () => {
await fetch("http://localhost:9000/api", {
headers: {
Authorization: `Bearer ${authToken}`,
},
}).then(response => response.json()).then(setData);
}
fetchApi()
}, [authToken]);
return { data };
};
```
3. Install the [node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) and [jwks-rsa](https://www.npmjs.com/package/jwks-rsa) packages
4. Validate the JWT on your server by fetching the public key from the [JWKS endpoint](/api-reference/endpoints/sdk/getJwksByEnvironmentId.mdx) API endpoint and verifying the encoded JWT against the public key:
```TypeScript
import jwt, { JwtPayload } from 'jsonwebtoken';
import { JwksClient } from 'jwks-rsa';
// can be found in https://app.dynamic.xyz/dashboard/developer/api
const jwksUrl = `https://app.dynamic.xyz/api/v0/sdk/${YOUR_DYNAMIC_ENV_ID}/.well-known/jwks`
// The clinet should be initalized as
const client = new JwksClient({
jwksUri: jwksUrl,
rateLimit: true,
cache: true,
cacheMaxEntries: 5, // Maximum number of cached keys
cacheMaxAge: 600000 // Cache duration in milliseconds (10 minutes in this case))}
});
const signingKey = await client.getSigningKey();
const publicKey = signingKey.getPublicKey();
const decodedToken: JwtPayload = jwt.verify(encodedJwt, publicKey, {
ignoreExpiration: false,
}) as JwtPayload;
if (decodedToken.scopes.includes('requiresAdditionalAuth')) {
// Either reject or handle the scopes appropriately.
// `requiresAdditionalAuth` is the scope used to indicate that JWT requires additional verification such as MFA.
throw new Error('Additional verification required');
}
console.log(decodedToken) // { iss: 'xxxx', exp: nnnn, ... }
```
This uses the following libraries:
* [jwks-rsa](https://www.npmjs.com/package/jwks-rsa): Provides client to interact and parse JWKS key signing data for a JWT.
* [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken): Provides library to encode/decode and validate a JWT token.
# Multi-Factor Authentication (MFA)
## Introduction
This guide will show you how to enable Dynamic's Account level multi-factor authentication in your app.
Dynamic offers two different types of multi-factor authentication: account level and transaction level. At the account level, users must complete 2FA on login, while at the transaction level, they must complete 2FA when creating a transaction.
You can learn more about transaction-level multi-factor authentication here.
(And yes, we know it might be a bit confusing. Just remember: one MFA is for logging in, the other is for doing stuff after logging in. Think of it like locking your front door and then locking the safe inside!)
## Supported methods
Dynamic currently supports these Account level methods:
* Authenticator app (e.g. Google Authenticator or Authy)
* Passkeys (coming soon)
* SMS (coming soon)
## Setup
1. Make sure you are on the latest Dynamic packages (V3)
2. Go to the [Security page](https://app.dynamic.xyz/dashboard/security) in your developer Dashboard.
3. In the Account MFA section, enable the Authenticator Apps toggle, then click Save Changes.
4. Optionally, you can require users to MFA on signup by clicking on the settings gear to the right of the Authenticar Apps toggle, then toggle on "Require at onboarding"
That's it! Make sure you are using the same environment id from the [SDK & API Keys page](https://app.dynamic.xyz/dashboard/developer/api) in your app. When you sign in to your app, you will be prompted to MFA if you toggled on "Require at onboarding", otherwise you will be able to optionally add MFA from the user profile section of the Dynamic widget.
## Supporting users who lose access to their Authenticator App
Please ensure you only delete MFA devices after confirming the identity of your end users.
In the event that one of your users contacts you that they lost access to their authenticator device, you can delete their device by going to the User Management table.
1. Go to the User Management table.
2. Find the user by searching based on email, username, or other verified credentials.
3. Open the details panel and click the button to delete the authenticator devices.
4. If MFA is required, then on the next login the user will be required to register a new device. Otherwise, the user can optionally add a device after logging in.
# Third-party Authentication
### This is an enterprise-only feature. Please contact us [in slack](https://dynamic.xyz/slack) or via email ([hello@dynamic.xyz](mailto:hello@dynamic.xyz)) to enable.
#### Introduction
If you have built your own authentication system or use other auth providers outside Dynamic, you may want to keep using those services while taking advantage of Dynamic’s product to enable web3-based functionality and interactions.
Third-party authentication allows you to do this by exchanging your token from a JWT issuer to "sign-in" or "link" to Dynamic.
#### Approach
Dynamic provides a new API endpoint and utility hook `useExternalAuth`, to allow sign-in or linking with an external 3rd party JWT by exchanging it for a Dynamic JWT. This will return Dynamic’s standard sign-in artifacts (ie, minified JWT and user).
At a high level, this will do the following steps:
1. Verify the authenticity of the JWT by getting the public key from a JWKS endpoint provided by you, and using this public key to verify the JWT.
2. Require you to provide exact value for either the `iss` (issuer) JWT claim. If the value of this field deviates from that is provided in the project environment’s configuration, we will reject the JWT.
3. Dynamic will also require the `sub` (subject, or user ID) field be provided. These will correspond to the your user ID. These will be mapped to the similar user model in Dynamic.
4. Dynamic will also require `exp` (expires at) JWT claim from external JWT. Dynamic will ensure that this `exp` claim is respected with timeouts on the SDK.
5. Once signed in using this external JWT, we will create an **new verified credential** of type `externalAuth`, letting us know that the user has an external authentication mechanism they used to verify their account. This should have `externalUserId` with the value of the JWT’s `sub`.
6. Optional verified credential data, such as e-mail sign in can be provided as part of the JWT. This would allow Dynamic to create an email verified credential tied to the Dynamic user on our side.
#### Workflow
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/swimlanes-e22f25180d42b5d9494a3d58f2f1cbb5.png)
#### Dashboard Configuration
To access this page, navigate to the [Third-Party Auth](https://app.dynamic.xyz/dashboard/developer/third-party-auth) page in your dashboard.
1. Provide values for the following fields:
* `iss` (required): Standard JWT claim for the "issuer" of the JWT. This should be the entity that issued the token. This is typically a URL, but can be a valid constant string.
* `jwksUrl` (required): This is a publicly-accessible URL that returns the JWT's signer public key in the standard [JWKS format](https://datatracker.ietf.org/doc/html/rfc7517). This is used to verify the signatures of your JWTs.
* `aud` (optional): Standard JWT claim for the "audience" of the JWT. This should be the intended recipient of the token. This is typically a URL, but can be a valid constant string.
* `cookieName` (optional): For clients that use cookie-based authentication for their 3rd party auth and who have no acccess to the raw JWT on the frontend, we provide a way for clients to specify the cookie name to expect the JWT to be stored.
2. When you are ready, enable the feature using the toggle.
3. Additionally, we provide a way for you to check a JWT against your saved settings, and we will return the errors, if any.
#### Frontend Implementation
After properly configuring the settings for this feature, you should be able to "sign-in" with a non-Dynamic JWT token by calling the `signInWithExternalJwt` method from the `useExternalAuth` hook.
```TypeScript
const { signInWithExternalJwt } = useExternalAuth();
try {
// `externalUserId`: User ID in the external auth system
// `externalJwt`: Raw encoded JWT issued by external auth system
const userProfile = await signInWithExternalJwt({
externalUserId,
externalJwt
});
if (userProfile) {
// You should be logged in at this point
}
} catch (e: any) {
console.error('Dynamic login failed:', e);
}
```
Similarly, you can verify the user using the verifyWithExternalJwt method:
```TypeScript
const { verifyWithExternalJwt } = useExternalAuth();
try {
const verifiedProfile = await verifyWithExternalJwt({
externalUserId,
externalJwt
});
if (verifiedProfile) {
// User verification successful
}
} catch (e: any) {
console.error('Dynamic verification failed:', e);
}
```
# Bridge Widget
The `DynamicBridgeWidget` component allows you to handle multi chain bridging
See it in action on [https://starkgate.starknet.io/](https://starkgate.starknet.io/)
## Background
This widget gives you a full UI to handle the Bridge use case (multi chain bridging). It will allow you to connect multiple wallets from different chains and bridge assets between them.
The video above shows our Bridge Widget in action on our demo site. Here you can see how to connect two wallets from different chains and then you can see that you can easily toggle the wallets so different wallets can sign.
For this multi-chain bridge, this flow is custom built for this scenario. It is different from our multiwallet widget, since only 1 wallet of each chain is desired. Additionally, this bridge widget supports the following:
1. It is a Connect-only multiwallet widget where all relevant information about the connected wallets are stored in Local Storage instead of the DB
2. It directs users to add a wallet from 1 chain, and then a second chain
3. It is flexible in that:
* A user can easily add or remove wallets
* It can toggle between the wallets so defining the Depositing wallet vs the Withdrawl wallet is simple.
* It supports network switching.
* It supports unlinking through our widget or via a hook
* The event listeners work on both wallets, so your dapp is always up to date with the proper account and network.
## Usage
To get the bridge component setup, you’ll want to have [your desired chains configured](/chains/enabling-chains.mdx), for example Ethereum and Starknet. Then, in your `index.tsx` (or wherever you render `DynamicContextProvider`, specify the `initialAuthenticationMode ` and [`bridgeChains`](/react-sdk/objects/wallets-by-chain) prop like so:
```typescript
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum ";
import { StarknetWalletConnectors } from "@dynamic-labs/starknet";
```
Then, in `app.tsx`:
```typescript
import { DynamicBridgeWidget } from "@dynamic-labs/sdk-react-core";
export default function App() {
return (
);
}
```
And that's it! Now you're rendering our bridge widget.
## bridgeChains
Primarily used for [bridging](/building-bridges/dynamic-bridge-widget) purposes, this object is passed as a prop to the `settings` in the [`DynamicContextProvider` component](/adding-dynamic/dynamic-context-provider). It creates a situation where the `isFullyConnected` prop on `useDynamicContext` hook is true only if the user has connected at least one wallet per chain, from each chain in the bridgeChains array.
### Description
An Array of objects, in which each object contains the chain name to be connected.
| Field | Description |
| ------------- | ----------- |
| chain: string | Chain name |
### Example
```jsx
[
{
chain: "EVM",
},
{
chain: "STARK",
},
];
```
# Custom Cosmos network
You can enable any Cosmos network that we do not currently support in our dashboard by passing an array of `GenericNetwork`
to the `DynamicContextProvider`'s `overrides.cosmosNetworks` settings.
This can be done in two different ways:
1. By passing an array of `GenericNetwork`, it completely overrides whatever networks were received from your dashboard configurations and uses that array instead.
2. By passing a method with signature `(dashboardNetworks: GenericNetwork[]) => GenericNetwork[]`, you can use this callback to first
receive the array of networks that was sent from your dashboard configurations, and then return the array of networks you want the app
to use.
The second approach is best for making adjustments to the networks you get from our dashboard (like changing rpc urls),
as well as when you want to hide some specific networks.
If you're just trying to merge new networks with the ones from dashboard, we have a helper function that will make that easier:
```TypeScript
import { mergeNetworks } from '@dynamic-labs/sdk-react-core';
const DynamicSettings = {
overrides: {
cosmosNetworks: (networks) => mergeNetworks(myCosmosNetworks, networks),
}
};
```
> Note that the order of the params for `mergeNetworks` matters: the first param takes precedence in case of a conflict.
## Example
The following example sets Sei for the application. Remember to enable the cosmos blockchain in the dashboard
```TypeScript
// we are setting the chainId and networkID to a random ID that will not conflict with other active chains in Dynamic.
const cosmosNetworks= [
{
blockExplorerUrls: ["https://www.seiscan.app/pacific-1"],
chain: 'Sei',
chainId: '404',
iconUrls: ['https://app.dynamic.xyz/assets/networks/sei.svg'],
lcdUrl: 'https://rest.wallet.pacific-1.sei.io',
name: 'pacific-1',
nativeCurrency: {
decimals: 18,
denom: 'usei',
name: 'Sei',
symbol: 'Sei',
},
networkId: '404',
rpcUrls: ['https://rpc.wallet.pacific-1.sei.io'],
shortName: 'Sei',
vanityName: 'Sei',
}
];
const App = () => (
);
export default App;
```
## Type Reference
### Definition
| Attribute | Value | Required/Optional |
| ---------------------- | ---------------- | ----------------- |
| blockExplorerUrls | `string[]` | Required |
| chainId | `number` | Required |
| name | `string` | Required |
| iconUrls | `string[]` | Required |
| nativeCurrency | `NativeCurrency` | Required |
| networkId | `number` | Required |
| privateCustomerRpcUrls | `string[]` | Optional |
| rpcUrls | `string[]` | Required |
| vanityName | `string` | Optional |
#### NativeCurrency
| Attribute | Value | Required/Optional |
| --------- | -------- | ----------------- |
| decimals | `number` | Required |
| name | `string` | Required |
| symbol | `string` | Required |
| denom | `string` | Optional |
# Embedded Wallet Chains
This guide expands on the [integrating chains section](/chains/enabling-chains), and shows you what SDK setup you need to have to enable embedded wallets for your users.
## Prerequisites
You have enabled embedded wallets in the dashboard already, for more information on that, check out the [embedded wallets guide](/wallets/embedded-wallets/dynamic-embedded-wallets).
## Initial setup
You can use embedded wallets with Ethereum, Solana or both! Therefore, whichever one you choose, you need to make sure you have the correct walletConnectors are installed and added to the walletConnectors array in the SDK settings.
For Ethereum, you need to install the `@dynamic-labs/ethereum` package:
```bash npm
npm i @dynamic-labs/ethereum
```
```bash yarn
npm i @dynamic-labs/ethereum
```
Then, you need to add the `EthereumWalletConnectors` to the `walletConnectors` array in the SDK settings:
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
const App = () => (
);
```
For Solana, you need to install the `@dynamic-labs/solana` package:
```bash npm
npm i @dynamic-labs/solana
```
```bash yarn
npm i @dynamic-labs/solana
```
Then, you need to add the `SolanaWalletConnectors` to the `walletConnectors` array in the SDK settings:
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { SolanaWalletConnectors } from '@dynamic-labs/solana';
const App = () => (
);
```
That's it! You've set up your chains correctly in order to create embedded wallets for your users.
Click here to learn how to enable chains for Smart Wallets!
# Enabling Chains
## Supported Chains/Networks
Below is a list of the supported chains/networks, but you can also view them more visually in the [dashboard](https://app.dynamic.xyz/dashboard/chains-and-networks):
If you don't see the chain/network you need and it's not EVM compatible, just [let us know](https://dynamic.xyz/slack)!
1. EVM:
We support all EVM networks. You can currently turn on the following via the
dashboard (simply go to the [EVM
tab](https://app.dynamic.xyz/dashboard/chains-and-networks#evm) in your
dashboard and flip the toggle). For any not mentioned below, you can easily
add them manually using [our evmNetworks prop](/chains/network-switching).
* Arbitrum
* Aurora
* Avalanche C chain
* BNB Smart Chain
* Base
* Berachain
* Celo
* Chiliz
* Cronos
* EON
* Ethereum
* Fantom
* Gnosis
* Moonbeam
* Optimism
* Palm
* Polygon
* Scroll
* Zora
* zkSync
2. Solana
3. Eclipse
4. Bitcoin
5. Flow
6. StarkNet
7. Cosmos
8. Algorand
## Enabling a Chain/Network
To integrate a specific chain/network, you should first [enable it in the dashboard](https://app.dynamic.xyz/dashboard/chains-and-networks). You'll also see that you can add custom RPC URLs for each network. Each provider will use the RPC configured in the Dashboard if present, otherwise they fall back to public RPCs urls. By default, all EVM and Solana networks have public default providers as shown in this table:
| Network | Public RPC Url |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Ethereum | [https://cloudflare-eth.com](https://cloudflare-eth.com) |
| Optimism | [https://mainnet.optimism.io](https://mainnet.optimism.io) |
| Gnosis Chain | [https://rpc.gnosischain.com](https://rpc.gnosischain.com) |
| Aurora | [https://mainnet.aurora.dev](https://mainnet.aurora.dev) |
| Polygon | [https://polygon-rpc.com](https://polygon-rpc.com) |
| Palm | [https://palm-mainnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b](https://palm-mainnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b) |
| BNB Smart Chain | [https://arb1.arbitrum.io/rpc](https://arb1.arbitrum.io/rpc) |
| Solana | [https://api.mainnet-beta.solana.com](https://api.mainnet-beta.solana.com) |
| Eclipse | [https://mainnetbeta-rpc.eclipse.xyz](https://mainnetbeta-rpc.eclipse.xyz) |
Once your chains are enabled and you're happy with your RPC providers, you're going to want to add the appropriate
Wallet Connector to the Dynamic Context Provider that you integrated when [getting started](/adding-dynamic/adding-the-sdk).
Each chain has it's own wallet connector, and there are also some add on wallet connectors used to enable
things like smart wallets. Follow the steps below and you'll be up and running in no time!
Below is a list of all the available wallet connectors and their corresponding packages.
| Package Name | Chain | WalletConnector to include |
| :--------------------- | :------ | ---------------------------------------------------------------- |
| @dynamic-labs/ethereum | EVM | `EthereumWalletConnectors` |
| @dynamic-labs/algorand | ALGO | `AlgorandWalletConnectors` |
| @dynamic-labs/solana | SOL | `SolanaWalletConnectors` or `SolanaWalletConnectorsWithConfig` |
| @dynamic-labs/eclipse | ECLIPSE | `EclipseWalletConnectors` or `EclipseWalletConnectorsWithConfig` |
| @dynamic-labs/flow | FLOW | `FlowWalletConnectors` |
| @dynamic-labs/starknet | STARK | `StarknetWalletConnectors` |
| @dynamic-labs/cosmos | COSMOS | `CosmosWalletConnectors` |
| @dynamic-labs/bitcoin | BTC | `BitcoinWalletConnectors` |
##### EVM Addon Wallets
| Package Name | Which Wallets | WalletConnector to include |
| :------------------------ | :------------ | :----------------------------- |
| @dynamic-labs/magic | *magic* | `MagicWalletConnectors` |
| @dynamic-labs/blocto-evm | *blocto* | `BloctoEvmWalletConnectors` |
| @dynamic-labs/starknet | STARK | `StarknetWalletConnectors` |
| @dynamic-labs/ethereum-aa | ZeroDev | `ZeroDevSmartWalletConnectors` |
EthereumWalletConnectors (@dynamic-labs/ethereum) also includes all EVM
compatible chains including layer 2's i.e. Base as well as [Dynamic Embedded
Wallets](/wallets/embedded-wallets/dynamic-embedded-wallets).
Install 1 or more wallet connectors from the packages listed above. Here is an example for Ethereum and Solana:
```bash npm
npm i @dynamic-labs/ethereum @dynamic-labs/solana
```
```bash yarn
yarn add @dynamic-labs/ethereum @dynamic-labs/solana
```
Add to an array in your settings under `walletConnectors`. Here is an example for Ethereum and Solana:
```jsx
import { DynamicContextProvider, DynamicWidget} from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
import { SolanaWalletConnectors } from '@dynamic-labs/solana';
const App = () => (
);
export default App;
```
## Configurable Wallet Connectors
Some wallet connectors come with a configurable alternative.
This is useful if you want to customize the wallet connector to your needs.
> For example, you can pass a `ConnectionConfig` to set the commitement level or define HTTP request headers for Solana and Eclipse.
See an example of how to use the configurable wallet connector for Solana:
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { SolanaWalletConnectorsWithConfig } from '@dynamic-labs/solana';
{...}
```
Here are our available configurable wallet connectors:
| Chain | Wallet Connector with Config | Type of Wallet Connector with Config |
| :------ | :---------------------------------- | :-------------------------------------------------------- |
| Solana | `SolanaWalletConnectorsWithConfig` | `(connectionConfig: ConnectionConfig) => WalletConnector` |
| Eclipse | `EclipseWalletConnectorsWithConfig` | `(connectionConfig: ConnectionConfig) => WalletConnector` |
> `ConnectionConfig` is the object type that can be passed as second argument to` @solana/web3.js`'s `Connection` constructor.
> You can find more information about it [here](https://solana.com/docs/clients/javascript-reference#connection).
Click here to learn how to enable chains for embedded wallets!
# Custom EVM networks
For adding a custom EVM networks with Wagmi see: [WAGMI - adding custom networks](https://docs.dynamic.xyz/adding-dynamic/using-wagmi#adding-custom-networks)
You can enable any EVM network that we do not currently support out of the box by passing an array of `EvmNetwork`
to the `DynamicContextProvider`'s `overrides.evmNetworks` settings.
This can be done in two different ways:
1. By passing an array of `EvmNetwork`, it completely overrides whatever networks were received from your dashboard configurations and uses that array instead.
2. By passing a method with signature `(dashboardNetworks: EvmNetwork[]) => EvmNetwork[]`, you can use this callback to first
receive the array of networks that was sent from your dashboard configurations, and then return the array of networks you want the app
to use.
The second approach is best for making adjustments to the networks you get from our dashboard (like changing rpc urls),
as well as when you want to hide some specific networks.
If you're just trying to merge new networks with the ones from dashboard, we have a helper function that will make that easier:
```TypeScript
import { mergeNetworks } from '@dynamic-labs/sdk-react-core';
const DynamicSettings = {
overrides: {
evmNetworks: (networks) => mergeNetworks(myEvmNetworks, networks),
}
};
```
> Note that the order of the params for `mergeNetworks` matters: the first param takes precedence in case of a conflict.
## Example
The following example sets the Ethereum mainnet and Polygon as supported networks for the application.
A comprehensive list of networks can be found at [chainlist.org](https://chainlist.org)
```TypeScript
// Setting up list of evmNetworks
const evmNetworks = [
{
blockExplorerUrls: ['https://etherscan.io/'],
chainId: 1,
chainName: 'Ethereum Mainnet',
iconUrls: ['https://app.dynamic.xyz/assets/networks/eth.svg'],
name: 'Ethereum',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
iconUrl: 'https://app.dynamic.xyz/assets/networks/eth.svg',
},
networkId: 1,
rpcUrls: ['https://mainnet.infura.io/v3/'],
vanityName: 'ETH Mainnet',
},
{
blockExplorerUrls: ['https://etherscan.io/'],
chainId: 5,
chainName: 'Ethereum Goerli',
iconUrls: ['https://app.dynamic.xyz/assets/networks/eth.svg'],
name: 'Ethereum',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
iconUrl: 'https://app.dynamic.xyz/assets/networks/eth.svg',
},
networkId: 5,
rpcUrls: ['https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161'],
vanityName: 'Goerli',
},
{
blockExplorerUrls: ['https://polygonscan.com/'],
chainId: 137,
chainName: 'Matic Mainnet',
iconUrls: ["https://app.dynamic.xyz/assets/networks/polygon.svg"],
name: 'Polygon',
nativeCurrency: {
decimals: 18,
name: 'MATIC',
symbol: 'MATIC',
iconUrl: 'https://app.dynamic.xyz/assets/networks/polygon.svg',
},
networkId: 137,
rpcUrls: ['https://polygon-rpc.com'],
vanityName: 'Polygon',
},
];
const App = () => (
);
export default App;
```
## Type Reference
### Definition
| Attribute | Value | Required/Optional |
| ---------------------- | ---------------- | ----------------- |
| blockExplorerUrls | `string[]` | Required |
| chainId | `number` | Required |
| name | `string` | Required |
| iconUrls | `string[]` | Required |
| nativeCurrency | `NativeCurrency` | Required |
| networkId | `number` | Required |
| privateCustomerRpcUrls | `string[]` | Optional |
| rpcUrls | `string[]` | Required |
| vanityName | `string` | Optional |
#### NativeCurrency
| Attribute | Value | Required/Optional |
| --------- | -------- | ----------------- |
| decimals | `number` | Required |
| iconUrl | `string` | Optional |
| name | `string` | Required |
| symbol | `string` | Required |
| denom | `string` | Optional |
# Switching Networks
### Usage
Using the primaryWallet provided by [useDynamicContext](/react-sdk/hooks/usedynamiccontext),
you have two useful methods for network switching:
Available on the connector object for the wallet.Whether the connector supports network switching.
Available directly on the wallet object. Switch to another network by providing either the network name or chain id.
When calling `switchNetwork` with a connector supporting network switching, the SDK will either request the user to confirm the network switch or add the network if it was not previously set.
### Example
```TypeScript
const { primaryWallet } = useDynamicContext();
if (primaryWallet?.connector.supportsNetworkSwitching()) {
await primaryWallet.switchNetwork(137);
console.log("Success! Network switched");
}
```
![1440](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/network-switching.png "Metamask network switching prompt")
# Using RPC Providers
## Summary
We provide the [`useRpcProviders` hook](/react-sdk/hooks/userpcproviders)
that allows direct access to RPC providers for EVM & Solana.
Rpc providers can be used to make RPC calls to the blockchain while also providing convenience
methods without going through a wallet.
Each provider will use the RPC configured in the Dashboard if present,
otherwise they fall back to public RPCs urls. By default, all EVM and Solana
networks have public default providers as shown in this table:
| Network | Public RPC Url |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Ethereum | [https://cloudflare-eth.com](https://cloudflare-eth.com) |
| Solana | [https://api.mainnet-beta.solana.com](https://api.mainnet-beta.solana.com) |
| Optimism | [https://mainnet.optimism.io](https://mainnet.optimism.io) |
| Gnosis Chain | [https://rpc.gnosischain.com](https://rpc.gnosischain.com) |
| Aurora | [https://mainnet.aurora.dev](https://mainnet.aurora.dev) |
| Polygon | [https://polygon-rpc.com](https://polygon-rpc.com) |
| Palm | [https://palm-mainnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b](https://palm-mainnet.infura.io/v3/3a961d6501e54add9a41aa53f15de99b) |
| BNB Smart Chain | [https://arb1.arbitrum.io/rpc](https://arb1.arbitrum.io/rpc) |
## Dashboard Configuration
To enter your provider url for a given network:
1. Go to the [Chains & Networks](https://app.dynamic.xyz/dashboard/chains-and-networks) page in your Dashboard.
2. Click on the chain to open the details tab
3. Click the down down arrow to expand a network
4. Enter your Provider Url
5. Click the test button to check url
## Usage
You are able to use the [`useRpcProviders` hook](/react-sdk/hooks/userpcproviders)
to obtain an object with rpc providers, and this hook expects a selector parameter
that you must use to select either EVM or Solana rpc providers.
```typescript
import { useRpcProviders } from '@dynamic-labs/sdk-react-core'
import { evmProvidersSelector } from '@dynamic-labs/ethereum-core'
import { solanaProvidersSelector } from '@dynamic-labs/solana-core'
const App = () => {
const evmProviders = useRpcProviders(evmProvidersSelector)
const solanaProviders = useRpcProviders(solanaProvidersSelector)
}
```
The hook returns either [EvmRpcProviderMethods](/react-sdk/objects/EvmRpcProviderMethods)
or [SolanaRpcProviderMethods](/react-sdk/objects/SolanaRpcProviderMethods), with the following fields:
The provider for EVM or Solana Mainnet, if mainnet is enabled
A full list of all EVM or Solana providers that have been configured
A convenience method that lets you retrieve a provider for a specific Chain ID
Check out the reference for
[EvmRpcProvider](/react-sdk/objects/EvmRpcProvider) and
[SolanaRpcProvider](/react-sdk/objects/SolanaRpcProvider)
### Example
Below is a simple example using EVM providers to fetch an arbitrary ENS mapping:
```TypeScript
import { useRpcProviders } from '@dynamic-labs/sdk-react-core'
import { evmProvidersSelector } from '@dynamic-labs/ethereum-core'
const useLogEnsMapping = () => {
const { defaultProvider } = useRpcProviders(evmProvidersSelector)
const mainnetProvider = defaultProvider?.provider;
const ensAddress = mainnetProvider.resolveName('myname.eth');
console.log('address for myname.eth', ensAddress);
}
```
# Smart Wallet Chains
If you are using our Zerodev integration to enable Smart Wallets, you'll need to already have the Ethereum chain enabled. If you don't, please refer to [this guide](/chains/enabling-chains).
You will also need to install a new package, even if you already have `@dynamic-labs/ethereum` installed, it's called `ethereum-aa`.
```bash npm
npm i @dynamic-labs/ethereum-aa
```
```bash yarn
npm i @dynamic-labs/ethereum-aa
```
Then, you need to add the `ZeroDevSmartWalletConnectors` to the `walletConnectors` array in the SDK settings:
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
import { ZeroDevSmartWalletConnectors } from '@dynamic-labs/ethereum-aa';
const App = () => (
);
```
# Custom Information Capture
## Overview
In order to capture information immediately after a user logs in that is not already available via our standard info capture (such as their shoe size, etc.), you can create custom fields to be prompted during onboarding and stored in the `metadata` object of the `user`. There are three different field types:
### Input
* **Unique**: Can force uniqueness in the environment, ex: only one user can have the nickname `Bob`.
* **Regex**: Can follow a regex, ex: text must be an Ethereum address: `^0x`.
### Dropdown
* A list of options that a user can select in a dropdown, ex: `Pizza`, `Ice Cream`.
### Checkbox
* Provide the text that goes along with a checkbox, ex: `Confirm that you are using this app at your own risk`.
All of these fields can be configured to be optional, meaning they can be skipped during onboarding. Additionally, all fields can be edited by the user in the Dynamic Widget.
## Configuration
In the Dynamic Dashboard, go to [the Login and User Profile Settings page](https://app.dynamic.xyz/dashboard/log-in-user-profile). Here, under "Additional User Information" you will see a list of your current available fields and at the bottom you will see a button to "Create New Field".
# CSS Variables
The Dynamic SDK allows you to customize the theme via [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). We recommend setting these CSS variables in your host site by targetting the `dynamic-shadow-dom` class.
Here is an example of a custom theme done via CSS variables and the code used below:
```css
.dynamic-shadow-dom {
--dynamic-font-family-primary: "Poppins", sans-serif;
--dynamic-font-family-numbers: "Roboto Mono", monospace, sans-serif;
--dynamic-base-1: #fff;
--dynamic-base-2: #fff;
--dynamic-base-3: #fff;
--dynamic-base-4: #fff;
--dynamic-base-5: #fff;
--dynamic-brand-hover-color: linear-gradient(
0deg,
rgba(0, 0, 0, 0.08),
rgba(0, 0, 0, 0.08)
), #4779ff;
--dynamic-brand-primary-color: #4779ff;
--dynamic-brand-secondary-color: rgba(71, 121, 255, 0.15);
--dynamic-connection-green: #41cc99;
--dynamic-border-radius: 12px;
--dynamic-hover: rgba(22, 37, 77, 0.03);
--dynamic-error-1: #ff4646;
--dynamic-error-2: rgba(255, 70, 70, 0.1);
--dynamic-footer-background-color: #fff;
--dynamic-footer-text-color: #000;
--dynamic-footer-icon-color: #4779ff;
--dynamic-loading-animation-gradient: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.5) 60%,
rgba(255, 255, 255, 0)
);
--dynamic-overlay: #fff;
--dynamic-shadow-down-1: 0px 3px 0px #fff;
--dynamic-shadow-down-2: 0px 3px 0px #000000;
--dynamic-shadow-down-3: 0px 6px 0px #000000;
--dynamic-shadow-up-1: 0px -8px 48px -8px rgba(109, 121, 165, 0.16);
--dynamic-text-link: #0047ff;
--dynamic-text-primary: #000;
--dynamic-text-secondary: black;
--dynamic-text-size-body-mini: 11px;
--dynamic-text-size-body-normal: 15px;
--dynamic-text-size-body-small: 12px;
--dynamic-text-size-button-primary: 14px;
--dynamic-text-size-button-secondary: 12px;
--dynamic-text-size-numbers-big: 14px;
--dynamic-text-size-numbers-medium: 12px;
--dynamic-text-size-numbers-small: 10px;
--dynamic-text-size-title: 18px;
--dynamic-text-tertiary: rgba(0, 0, 0, 0.65);
--dynamic-badge-background: #000;
--dynamic-badge-color: #fff;
--dynamic-badge-dot-background: #fff;
--dynamic-badge-primary-background: #4779FF;
--dynamic-badge-primary-color: #fff;
--dynamic-search-bar-background: ;
--dynamic-search-bar-background-hover: ;
--dynamic-search-bar-background-focus: #fff;
--dynamic-search-bar-border: 2px solid #000;
--dynamic-search-bar-border-hover: 2px solid #000;
--dynamic-search-bar-border-focus: 2px solid #000;
--dynamic-modal-border: 2px solid #000;
--dynamic-modal-width: 22.5rem;
--dynamic-modal-padding: 1.5rem;
--dynamic-footer-padding: 1.25rem 1.25rem 1.25rem 1.5rem;
--dynamic-footer-border: 2px solid #000;
--dynamic-wallet-list-tile-padding: 0.75rem;
--dynamic-wallet-list-tile-gap: 0.375rem;
--dynamic-wallet-list-max-height: 16.25rem;
--dynamic-wallet-list-tile-background: #fff;
--dynamic-wallet-list-tile-border: 2px solid #000000;
--dynamic-wallet-list-tile-shadow: 0px 3px 0px #fff;
--dynamic-wallet-list-tile-background-hover: #fff;
--dynamic-wallet-list-tile-border-hover: 2px solid #000000;
--dynamic-wallet-list-tile-shadow-hover: 0px 3px 0px #000000;
--dynamic-connect-button-background: #fff;
--dynamic-connect-button-color: #000;
--dynamic-connect-button-border: 2px solid #000;
--dynamic-connect-button-shadow: 0px 3px 0px #fff;
--dynamic-connect-button-background-hover: rgba(22, 37, 77, 0.03);
--dynamic-connect-button-color-hover: #000;
--dynamic-connect-button-border-hover: 2px solid #000;
--dynamic-connect-button-shadow-hover: 0px 3px 0px #000000;
--dynamic-tooltip-color: #000;
--dynamic-tooltip-text-color: #fff;
}
```
### Theme variables
| Variable | Description |
| -------------------------------------- | ---------------------------- |
| `--dynamic-base-1` | Base 1 color |
| `--dynamic-base-2` | Base 2 color |
| `--dynamic-base-3` | Base 3 color |
| `--dynamic-base-4` | Base 4 color |
| `--dynamic-base-5` | Base 5 color |
| `--dynamic-brand-primary-color` | Brand primary color |
| `--dynamic-border-radius` | Border radius (default 24px) |
| `--dynamic-connection-green` | Connected status dot |
| `--dynamic-hover` | Hover background color |
| `--dynamic-error-1` | Error text color |
| `--dynamic-error-2` | Error background color |
| `--dynamic-loading-animation-gradient` | Loading animation gradient |
| `--dynamic-shadow-down-1` | Shadow down 1 |
| `--dynamic-shadow-down-2` | Shadow down 2 |
| `--dynamic-shadow-down-3` | Shadow down 3 |
| `--dynamic-shadow-up-1` | Shadow up 1 |
| `--dynamic-success-1` | Success text color |
| `--dynamic-success-2` | Success background color |
### Text variables
| Variable | Description |
| -------------------------------------- | ----------------------------- |
| `--dynamic-font-family-primary` | Font used for general text |
| `--dynamic-font-family-numbers` | Font used for number elements |
| `--dynamic-text-primary` | Primary text color |
| `--dynamic-text-secondary` | Secondary text color |
| `--dynamic-text-tertiary` | Tertiary text color |
| `--dynamic-text-link` | Link text color |
| `--dynamic-text-size-body-mini` | Text size body mini |
| `--dynamic-text-size-body-small` | Text size body small |
| `--dynamic-text-size-body-normal` | Text size body normal |
| `--dynamic-text-size-button-primary` | Text size primary button |
| `--dynamic-text-size-button-secondary` | Text size secondary button |
| `--dynamic-text-size-numbers-small` | Text size small numbers |
| `--dynamic-text-size-numbers-medium` | Text size medium numbers |
| `--dynamic-text-size-numbers-big` | Text size big numbers |
| `--dynamic-text-size-title` | Text size title |
### Modal variables
| Variable | Description |
| ------------------------------------- | ------------------------- |
| `--dynamic-modal-border` | Modal border |
| `--dynamic-modal-width` | Modal width |
| `--dynamic-modal-padding` | Modal padding |
| `--dynamic-modal-backdrop-background` | Modal backdrop background |
| `--dynamic-modal-backdrop-filter` | Modal backdrop filter |
### Footer variables
| Variable | Description |
| ----------------------------------- | --------------------- |
| `--dynamic-footer-background-color` | Background color |
| `--dynamic-footer-text-color` | Text color |
| `--dynamic-footer-icon-color` | Icon color |
| `--dynamic-footer-arrow-color` | Arrow color |
| `--dynamic-footer-border` | Top and bottom border |
| `--dynamic-footer-padding` | Footer padding |
### Wallet list variables
| Variable | Description |
| ----------------------------------------------- | -------------------------------------------------------------- |
| `--dynamic-wallet-list-max-height` | Maximum wallet list height |
| `--dynamic-wallet-list-tile-gap` | Gap between wallet tiles |
| `--dynamic-wallet-list-tile-padding` | Wallet tile padding |
| `--dynamic-wallet-list-tile-background` | Wallet tile background |
| `--dynamic-wallet-list-tile-border` | Wallet tile border |
| `--dynamic-wallet-list-tile-shadow` | Wallet tile shadow |
| `--dynamic-wallet-list-tile-background-hover` | Wallet tile background on hover |
| `--dynamic-wallet-list-tile-border-hover` | Wallet tile border on hover |
| `--dynamic-wallet-list-tile-shadow-hover` | Wallet tile shadow on hover |
| `--dynamic-wallet-list-tile-animation-duration` | Wallet time animation duration between regular and hover state |
| `--dynamic-badge-background` | Wallet tile badge background |
| `--dynamic-badge-color` | Wallet tile badge text color |
| `--dynamic-badge-dot-background` | Wallet tile badge dot background |
| `--dynamic-recommended-badge-background` | Wallet tile recommended badge background |
| `--dynamic-recommended-badge-color` | Wallet tile recommended badge text color |
| `--dynamic-search-bar-background` | Wallet search bar background |
| `--dynamic-search-bar-border` | Wallet search bar border |
| `--dynamic-search-bar-background-hover` | Wallet search bar background on hover |
| `--dynamic-search-bar-border-hover` | Wallet search bar border on hover |
| `--dynamic-search-bar-background-focus` | Wallet search bar background on focus |
| `--dynamic-search-bar-border-focus` | Wallet search bar border on focus |
### Connect button variables
| Variable | Description |
| ------------------------------------------- | -------------------------- |
| `--dynamic-connect-button-background` | Button background |
| `--dynamic-connect-button-color` | Button text color |
| `--dynamic-connect-button-border` | Button border |
| `--dynamic-connect-button-shadow` | Button shadow |
| `--dynamic-connect-button-background-hover` | Button background on hover |
| `--dynamic-connect-button-color-hover` | Button text color on hover |
| `--dynamic-connect-button-border-hover` | Button border on hover |
| `--dynamic-connect-button-shadow-hover` | Button shadow on hover |
| `--dynamic-connect-button-radius` | Button border radius |
### Other variables
| Variable | Description |
| ------------------------------ | ------------------------------- |
| `--dynamic-overlay` | Widget connection modal overlay |
| `--dynamic-tooltip-color` | Tooltip background color |
| `--dynamic-tooltip-text-color` | Tooltip text color |
If you wish to customize the UI even more, we recommend using [CSS overrides](/design-customizations/css/custom-css).
# Custom CSS
### Summary
The Dynamic SDK uses [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web%5FComponents/Using%5Fshadow%5FDOM). Shadow DOM allows the SDK to look as intended wherever it is hosted and it plays nicely with your existing styling system. We completely rewrote our styling system to support encapsulation while still allowing for intentional styling overrides.
### How to overrides SDK styles
We introduced a new cssOverrides prop in the settings allowing you to pass either a string or JSX element for styling overrides. These overrides will be placed inside the shadow DOM and will only affect SDK elements. Overrides should be used when you want to completely customize the style and behaviour of the SDK. General theme customization can be done via CSS variables instead.
#### Using a string
You can pass string overrides directly through settings. The content will be wrapped by a style tag inside the shadow DOM. Here's an example that adds a one time rotating animation to the wallet logo in the wallet list.
```TypeScript
const cssOverrides = `
.wallet-list-item__tile:hover > img {
animation: rotate 1s forwards;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`
```
\`\`
#### Using JSX element
You can link a stylesheets to be added the shadow DOM. Since the stylesheet is loaded, there might be a small delay before the styles get applied. If this is a problem, we recommend using the string method.
```TypeScript
, // pass JSX element to be included in the shadow DOM
// ...
}}
>
```
#### Disabling shadow DOM
Shadow DOM is enabled by default on the SDK version v0.15.0 rc and higher. If you wish to disable it, you can use the shadowDOMEnabled in the dynamic context settings.
```TypeScript
```
# Themes & Settings
Dynamic offers beautiful themes in both light, dark, or auto themes with the ability to customize the colors and styles to your liking, all through your developer dashboard.
Simply go to the [design page](https://app.dynamic.xyz/dashboard/design) in your dashboard and toggle from the current list of settings (more customizations will be coming soon).
You can toggle between:
1. Themes
2. Light and dark theme
3. Your brand color
4. Subtle/Bold
## Light & Dark Modes
You can manage light and dark mode in three different ways depending on your use case:
### Using the dashboard
You can set your theme in our [Dashboard](https://app.dynamic.xyz/dashboard/design), under the "Style" heading, where you see the toggle for "Light" or "Dark".
### Using DynamicContextProvider
While you can set your theme in our [Dashboard](https://app.dynamic.xyz/dashboard/design), a lot of site support users switching between light and dark theme and it's important that our SDK updates to match the theme of the site.
You can update whether you want to show the Light or Dark theme to customers by passing a prop to DynamicContextProvider with `theme`:
```TypeScript
>",
appName: "<>",
appLogoUrl: "...",
}}>
```
### Using CSS directly
The root element of your application (perhaps `body`) will have a data-dynamic-theme variable set. You can override this at any time to adjust the theme to light or dark mode. There is also an "auto" value which will adapt to whatever is set in \[]`prefers-color-scheme`]\([https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)).
# Adapt Copy With Translations
The SDK allows you to customize almost all of the text it displays, as well as add extra translations. You do so using the "locale" prop on the DynamicContextProvider.
In this short guide we will update some text in the default language (English), add an Italian translation, learn how to let the user change their language and along the way, learn all about the locale object you'll be working with!
Let's imagine we want to change the "Select your wallet" text in the image below to "Find your favorite" and then do the same with an Italian translation.
![Select your wallet](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/select-your-wallet.png)
We simply need to inspect that element in the browser, and find the key that is being used to display that text by checking the `copykey` attribute on that element. In this case, you'll see that it is "dyn\_login.title.all\_wallet\_list".
Now we'll create our locale object, and add a new key with that value. It always starts with a key of the language you're editing, in this case "en" for English. Then we add the key that we found in the browser, and then the value we want to replace it with.
```tsx
const locale = {
en: {
dyn_login: {
title: {
all_wallet_list: "Find your favorite",
},
},
},
it: {
dyn_login: {
title: {
all_wallet_list: "Trova il tuo preferito",
},
},
},
};
```
You probably noticed the translation object has a very specific shape. You can actually check it yourself by looking in "@dynamic-labs/sdk-react-core/src/lib/locale/en/translation.js" which you can reach by going to the definition of LocaleResource in your IDE once imported (`import { LocaleResource } from '@dynamic-labs/sdk-react-core'`)
Here is an example excerpt from that file:
```jsx
dyn_account_exists: {
connect: 'Connect with {{socialOauth}}',
description: 'It looks like an account already exists using',
title: 'Account already exists',
trail_message_email: '. Please log in with your email.',
trail_message_social: 'through {{socialOauth}}',
},
```
We're now ready to pass our locale object to the DynamicContextProvider like so:
```tsx
```
The result should then immediately show up in the SDK UI like so:
Next we'll need to let the user change between languages, and it's as simple as the following:
```tsx
const {locale} = useDynamicContext();
const handleOnClick = (localeProvided) => {
if (localeProvided === 'it') {
locale.changeLanguage('it');
} else {
locale.changeLanguage('en');
}
};
return (
);
```
That's it! You now know how to customize the text in the SDK to your heart's content.
## Further Reading
Make sure to check out the full reference for the locale object [here](/react-sdk/objects/locale).
# UI Customization Overview
All the guides in this section pertain to UI customizations for [the Dynamic UI components](/react-sdk/components) of the SDK. If you are implementing Dynamic headlessly i.e. without any of our UI components, check out the [headless section](/headless) instead.
There are multiple ways to customize the UI of the Dynamic SDK. You can customize the views, translations, and CSS design among other things. We outline the main methods below and link to respective guides.
## Views
Views are used to customize the kind of UI that shows up at any point in time in your application, specifically giving you complete control over the signup/login options you show your user. [Learn more about views.](/design-customizations/views)
## Translations
Every piece of text displayed in a Dynamic UI component has a corresponding key in our translation file. You can override these keys to customize the text displayed in the UI. [Learn more about translations.](/design-customizations/customizing-copy-translations)
## CSS Design
You have three primary options for completely adapting the look and feel of the UI components on a CSS level. These are:
You can use a theme to override the default styles of components cohesively. [Learn more.](/design-customizations/css/themes)
You can use CSS variables to override individual styles of the UI components.
[Learn more.](/design-customizations/css/css-variables)
You can add custom CSS in order to ignore all Dynamic styles and create your own.
[Learn more.](/design-customizations/css/custom-css)
You'll get to interact with all three methods if you take the [Design Tutorial](/design-customizations/tutorials/design-tutorial)!
# Customizing Signup/Login UI
As you can see [on the demo](https://demo.dynamic.xyz/), you can configure the login page in many different ways. This page walks you through where to find those configuration options.
For the most part you will find them in [the "Log in & User Profile" section of the Dynamic Dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile).
## Choose login methods to display and in what order
As far as the login methods to display, this is determined firstly by whatever you have enabled at the time, but you can override it using Login Views and also handle custom ordering there - learn more in [the tutorial](/design-customizations/tutorials/login-views-guide).
## Embedded Widget or Button
By default, if you use our DynamicWidget, it will show up as a button which opens the login screen in a modal. You can change this to an embedded widget so that the whole signup/login modal shows up directly on the page.
You can see both these options in action on [the demo](https://demo.dynamic.xyz/) in the "Widget integration" section under "Design". Learn more about the standard widget [here](/react-sdk/components/dynamicwidget) and the embedded widget [here](/react-sdk/components/dynamicembeddedwidget).
## Email Continue button
By default, we split email login into two components, an input and a continue button directly below it. You can merge them into one single component using the "Display email submit button inside the input field" button in "Design Settings".
## Expanded vs Collapsed wallet list
The login screen normally lists wallets one by one, which can clutter the screen if branded wallet login is not the most important thing to your users. You can collapse this list down into a single "Continue with a wallet" button (which will in turn open this list) but using the "Collapse wallet list" button in "Design Settings".
## Splitting Social & Email
The social and email sections of the login screen are bundled together by default i.e. without an OR seperator like with branded wallets, but you can change this using the "Split email and social into separate sections" button in "Design Settings".
## Social above email
The default UI shows email login above social if both are enabled. You can switch the default ordering using the "Place social above email section" button in "Design Settings".
## Changing the text
You can change any part of the text which is shown in the login UI using our translations feature - learn more about that in [the tutorial](/design-customizations/customizing-copy-translations).
## Adjusting Signup/Login button
If you want to change the text/css, please refer to the Changing CSS section below. To change the text itself, we will use the innerButtonComponent prop on the DynamicWidget. This prop allows us to pass in a JSX element that will be rendered as the button.
```jsx
My Custom Connect Wallet Button}
>
{/* ... rest of your app ... */}
```
If you want to use your own custom button we can use `setShowAuthFlow` available though [the useDynamicContext hook](/react-sdk/hooks/usedynamiccontext).
We access it like this:
```jsx
const { setShowAuthFlow } = useDynamicContext();
```
We can then use this function to trigger the Dynamic Connect Wallet flow when our custom button is clicked.
```jsx
```
## Adding your own sections
You can inject your own custom content into the login UI using [our Custom Widget Content tutorial](/design-customizations/tutorials/custom-widget-content).
## Changing the CSS
We offer multiple ways of changing/overriding the CSS in the login view as well as every other view:
You can use a theme to override the default styles of components cohesively. [Learn more.](/design-customizations/css/themes)
You can use CSS variables to override individual styles of the UI components.
[Learn more.](/design-customizations/css/css-variables)
You can add custom CSS in order to ignore all Dynamic styles and create your own.
[Learn more.](/design-customizations/css/custom-css)
# Inject Extra Content in Widget
Should you want to add content to the widget (or indeed any of our UI components), since we use [a Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) to ensure encapsulation of styling, you should first access the `shadowRoot` property of the host element - from there you can access the elements and manipulate them or append new ones.
For example, if you want to target the Dynamic Widget, you'd look for an element with `data-testid="dynamic-modal-shadow"`:
```javascript
const shadowHosts = document.querySelectorAll(
'[data-testid="dynamic-modal-shadow"]'
);
```
From there you'll need to pick the element that is the shadowRoot:
```javascript
shadowHosts.forEach((shadowHost) => {
if (shadowHost && shadowHost.shadowRoot) {
// Logic here
}
})
```
As a best practice, you can encapsulate your manipulation logic in a function, then call that function immediately but also add it to a mutation observer.
The observer is important for multiple reasons:
1. The widget content may change dynamically (e.g., different views or states).
2. The widget might be destroyed and recreated in the DOM.
3. Your custom content might be overwritten by internal widget updates.
```javascript
const injectButton = () => {
// Logic to inject a new button into the widget
}
injectButton();
const observer = new MutationObserver((mutations) => {
injectButton();
});
observer.observe(shadowHost.shadowRoot, {
childList: true,
subtree: true,
});
```
Now you have access to the modal, you can target whatever element you want to adjust, or append new elements at will.
# Design Techniques Tutorial
Let's become familiar with the design customization techniques for your Dynamic implementation.
We achieve these three use cases along the way:
1. Redesign and implement a Connect Wallet button.
2. Change the whole design theme.
3. Customize the logged in widget to suit your own style needs.
4. Implement a custom welcome screen for our users when they click Connect Wallet.
During the guide we will utilize all three of the main design techniques, which are as follows:
* [Configurations available via the Dashboard](/design-customizations/css/themes)
* [CSS Variable configurations](/design-customizations/css/css-variables)
* [CSS Overrides](/design-customizations/css/custom-css)
Let's go!
## Pre-requisites
Make sure you have the SDK set up in a simple application, you can find the guide to do that [here](/quickstart).
## Implement a custom Connect Wallet button
We want to achieve three things here:
1. Change the default button colors
2. Change the default button text
3. Use our own custom button
Before we start you should know that the Connect button is a part of the [DynamicWidget](/react-sdk/components/dynamicwidget), but can also be used seperately through the [Dynamic Connect Button](/react-sdk/components/dynamicconnectbutton) component. It doesn't matter which one you use here, the techniques will affect both!
### Button colors
The easiest way to achieve this is to utilize the default CSS variables.
We'll focus on the background color, main color and the text color here but there are more variables available such as the border, shadow etc. You can find the full reference [here](/design-customizations/css/css-variables#connect-button-variables).
First we'll need to make sure in our CSS we use the following class name:
```css
.dynamic-shadow-dom {
}
```
Now we add add the following variables to that class:
```css
.dynamic-shadow-dom {
--dynamic-connect-button-background: #000;
--dynamic-connect-button-color: #fff;
--dynamic-text-primary: #fff;
}
```
Run your application and you'll see the color changes. You can also use the inspector to see exactly which elements are being affected.
### Button text & font
We can also change the text of the button and the font used. For the font, the approach is exactly the same as for the colors, we just need to use the correct variables.
```css
.dynamic-shadow-dom {
--dynamic-text-size-button-primary: 16px;
--dynamic-font-family-primary: "Roboto", sans-serif;
}
```
For the text itself, we will use the innerButtonComponent prop on the DynamicWidget. This prop allows us to pass in a JSX element that will be rendered as the button.
```jsx
My Custom Connect Wallet Button}
>
{/* ... rest of your app ... */}
```
### Use our own button
For this we'll want to utilize one of the handy functions available though the useDynamicContext hook: "setShowAuthFlow".
We access it like this:
```jsx
const { setShowAuthFlow } = useDynamicContext();
```
We can then use this function to trigger the Dynamic Connect Wallet flow when our custom button is clicked.
```jsx
```
## Change the whole design theme
This is as simple as a visit to [the design section of your Dynamic dashboard](https://app.dynamic.xyz/dashboard/design).
Under the "Themes" section, choose any of the available themes, save when prompted, and you'll be live immediately with your new design!
## Customising the logged in widget
The logged in widget is the widget is the part of the DynamicWidget that renders the [Dynamic User Profile component](/react-sdk/components/dynamicuserprofile). As with the Connect button, you can use the Widget or the Widget Content component, this guide will work for both.
Let's say in this example we want to do two things:
1. Remove the network picker from the logged in widget.
2. Rotate the wallet icon using animations in the logged in widget.
### Remove the network picker
This is an option found under the "Branded Wallets" heading in [the Log in & User Profile section of the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile),
Toggle on "Hide Network", save, and you're done!
### Change the border radius
There is no CSS variable to do this, so it means we will use a CSS override.
Once you have found the class names you want to override, and know what css you wish to apply instead, we need to declare the css as a string literal, ideally stored under a variable called cssOverrides in whichever component you're initialising DynamicContextProvider.
```jsx
const cssOverrides = `
.dynamic-widget-inline-controls__account-control > img {
animation: rotation 2s infinite linear
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
};
`;
```
Then, you can use this variable as a setting for DynamicContextProvider:
```jsx
{/* ... rest of your app ... */}
```
Note that you can also pass a JSX element rather than a string, but more on that [here](/design-customizations/css/custom-css)
That's it! Run your app. Once logged in, you should no longer see your network picker and the wallet icon should be happily spinning!
Note that is you need to add extra content to the widget, not just style the
existing content differently, we recommend following the [custom widget
content guide](/design-customizations/tutorials/custom-widget-content).
## Implement a custom welcome screen
Here we see how to reach beyond the confines of the components themselves, and extending the flow into your own application using callbacks and functions.
You cannot implement a welcome screen inside the Dynamic modal, but you can of course create your own in your application. This raises the question about how to trigger it at the right time.
The answer is to use the [onAuthSuccess](/react-sdk/events/onauthsuccess) callback. This callback is triggered when the user has successfully authenticated with Dynamic.
```jsx
{
console.log("onAuthSuccess was called", args);
},
},
}}
>
{/* ... rest of your app ... */}
```
The second part of the puzzle is to check if this is a new user or not, since we might want a different experience for new users. We can do this by checking the "newUser" property on the object supplied by the callback above.
This is because the callback supplies you with the `user` object, and this is a representation of the [UserProfile](/react-sdk/objects/userprofile) which contains the `newUser` boolean.
```jsx
onAuthSuccess: (args) => {
if(args.user.newUser) {
console.log("This is a new user")
}
},
```
If this is indeed a new user, you can replace the console log above with your own logic to call your welcome screen component, and voila!
## Conclusion
You just learned how to customize the design of your Dynamic implementation using the three main techniques:
1. [Configurations available via the Dashboard](/design-customizations/css/themes)
2. [CSS Variable configurations](/design-customizations/css/css-variables)
3. [CSS Overrides](/design-customizations/css/custom-css)
For more information on the design customization options available, check out the [design customization section](/design-customizations).
If you have any questions at all about these techniques or how to achieve a certain design use case, don't hesitate to ask [in the community slack](https://www.dynamic.xyz/slack).
# Login Views Tutorial
In this guide we will set up a simple example of programmatically changing views based on where the user is coming from.
We're going to be referring a lot to the views type, found [here](/design-customizations/views).
### Determine where the user is coming from
In our example, we will show a different signup/login experience to users coming from a specific URL. We will use the `useLocation` hook from `react-router-dom` to get the current URL.
```jsx
import { useLocation } from "react-router-dom";
const location = useLocation();
```
If the user is coming from /landing-page/facebook, we will show only a Facebook button which gives you an embedded wallet. If you are coming from /landing-page/web3, we will show the default signup/login experience.
In the below code we are using the `useState` hook to create a state variable called `source` and a setter function called `setSource`. We are then using the `useEffect` hook to set the source to the current location.
```jsx
const [source, setSource] = useState("");
const location = useLocation();
const isFacebookCampaign = location.pathname === "/landing-page/facebook";
const isWeb3Campaign = location.pathname === "/landing-page/web3";
const views = isFacebook ? "facebook" : isWeb3Campaign ? "web3" : "";
useEffect(() => {
setSource(views);
}, [views]);
```
### Declare the views
Views are passed in via the overrides key on the settings prop of [DynamicContextProvider](/react-sdk/providers/dynamiccontextprovider)
The view object itself is of a specific type and you can review the type on [the reference](/react-sdk/objects/views).
In our case, we want two different views, one for Facebook and one for Web3. We will use the `facebook` and `web3` views.
```jsx
const FACEBOOK_VIEW = {
type: SdkViewType.Login,
sections: [
{
type: SdkViewSectionType.Social,
defaultItem: "facebook",
},
],
};
```
```jsx
const WEB3_VIEW = {
type: SdkViewType.Login,
sections: [
{ type: SdkViewSectionType.Wallet },
{
type: SdkViewSectionType.Separator,
label: "Or",
},
{
type: SdkViewSectionType.Email,
},
{
type: SdkViewSectionType.Separator,
label: "Or",
},
{
type: SdkViewSectionType.Social,
defaultItem: "twitter",
},
],
};
```
### Setting types to state
We want somewhere to store our view overrides. We will use the `useState` hook to create a state variable called `viewOverrides` and a setter function called `setViewOverrides`.
```jsx
const [viewOverrides, setViewOverrides] = useState([]);
```
### Setting the view overrides
We will use the `useEffect` hook to set the view overrides to the correct view based on the source.
```jsx
useEffect(() => {
if (source === "facebook") {
setViewOverrides([FACEBOOK_VIEW]);
} else if (source === "web3") {
setViewOverrides([WEB3_VIEW]);
} else {
setViewOverrides([]);
}
}, [source]);
```
### Adding to the DynamicContextProvider
We can now pass the viewOverrides to the `DynamicContextProvider`.
### Putting it all together
```jsx
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import {
DynamicContextProvider,
DynamicWidget,
} from "@dynamic-labs/sdk-react-core";
import { SdkViewSectionType, SdkViewType } from "@dynamic-labs/sdk-api";
const FACEBOOK_VIEW = {
type: SdkViewType.Login,
sections: [
{
type: SdkViewSectionType.Social,
defaultItem: "facebook",
},
],
};
const WEB3_VIEW = {
type: SdkViewType.Login,
sections: [
{ type: SdkViewSectionType.Wallet },
{
type: SdkViewSectionType.Separator,
label: "Or",
},
{
type: SdkViewSectionType.Email,
},
{
type: SdkViewSectionType.Separator,
label: "Or",
},
{
type: SdkViewSectionType.Social,
defaultItem: "twitter",
},
],
};
const App = () => {
const [source, setSource] = useState("");
const [viewOverrides, setViewOverrides] = useState([]);
const location = useLocation();
const isFacebookCampaign = location.pathname === "/landing-page/facebook";
const isWeb3Campaign = location.pathname === "/landing-page/web3";
useEffect(() => {
setSource(isFacebookCampaign ? "facebook" : isWeb3Campaign ? "web3" : "");
}, [isFacebookCampaign, isWeb3Campaign]);
useEffect(() => {
if (source === "facebook") {
setViewOverrides([FACEBOOK_VIEW]);
} else if (source === "web3") {
setViewOverrides([WEB3_VIEW]);
} else {
setViewOverrides([]);
}
}, [source]);
return (
);
};
export default App;
```
### Adding embedded wallets
That's it! To make sure our facebook group also get an embedded wallet,follow [this guide](/wallets/embedded-wallets/dynamic-embedded-wallets)
# Wallet List Views Tutorial
Below is an example showcasing the setup for tabs that categorize wallets by blockchain network, utilizing both custom and predefined filter functions:
```tsx
import {
DynamicContextProvider,
FilterChain,
} from "@dynamic-labs/sdk-react-core";
import {
BitcoinIcon,
EthereumIcon,
FlowIcon,
SolanaIcon,
} from "@dynamic-labs/iconic";
const App = () => {
return (
},
walletsFilter: FilterChain("EVM"),
recommendedWallets: [
{
walletKey: "phantomevm",
},
],
},
{
label: { icon: },
walletsFilter: FilterChain("SOL"),
},
{
label: { icon: },
walletsFilter: FilterChain("BTC"),
},
{
label: { icon: },
walletsFilter: FilterChain("FLOW"),
},
],
},
},
],
},
}}
>
);
};
```
This is the wallet list view with the tabs
| All chains tab selected | Ethereum selected |
| ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| | |
Let's create a button that, when clicked, will automatically open the Ethereum tab.
```tsx
import React from "react";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
/**
* Component for a button that opens the Ethereum tab by default.
*/
const ConnectWithEthereum: React.FC = () => {
const { setShowAuthFlow, setSelectedTabIndex } = useDynamicContext();
/**
* Handles the button click event by setting the default tab to Ethereum and showing the authentication flow.
*/
const onClickHandler = (): void => {
setSelectedTabIndex(1); // Set the selected tab index to 1, which corresponds to the Ethereum tab
setShowAuthFlow(true);
};
return ;
};
export default ConnectWithEthereum;
```
# Adapt UI With Views
## What is it?
Views are used to customize the kind of UI that shows up at any point in time in your application, specifically giving you complete control over the signup/login options you show your user.
## How does it work?
Views are used primarily in the overrides prop of the [DynamicContextProvider](/react-sdk/providers/dynamiccontextprovider). You pass in an array of configurations for each view you want to customize, each view has its own set of options.
## Types of Views
### Authentication Method Options (Login)
The `SdkViewType.Login` is used to adjust the login/signup UI options programmatically.
When using the login view, you add an object to the views array. This object should have `type: SdkViewType.Login` and `sections` which is an array of SdkViewSection objects.
### SdkViewSection structure
An identifier of the kind of view section to be provided, possible values outlined in section below.
The text alignment used for Text sections.
The option to be displayed as the main one.
The default item will be displayed in a more prominent way than the rest of the items in the section.
* For `Social` section, represents the social provider to be displayed by default.
* For `EmailAndPhone` section, represents whether email or phone options are displayed by default (`"email"` vs `"phone"`).
The label for the section.
This will be displayed above the section or as part of the separator component if it is a `Separator` section.
The default number of items to display in the section.
User has to click a button to view more options if any are left out.
* For `Wallet` section, represents the number of wallet items to be displayed by default.
* For `Social` section, represents the number of social providers to be displayed by default.
### SdkViewSectionType
The possible values are:
* `SdkViewSectionType.Email`
* `SdkViewSectionType.EmailAndPhone`
* `SdkViewSectionType.Phone`
* `SdkViewSectionType.Social`
* `SdkViewSectionType.Separator`
* `SdkViewSectionType.Text`
* `SdkViewSectionType.Wallet`
The `EmailAndPhone` section is what you get by default when you enable both
email and phone in our dashboard without using login view overrides. It's a
section where the user has the option to toggle between email and phone number
input. Read about `defaultItem` [here](/design-customizations/views#sdkviewsection-structure)
to learn how to set whether to show email or phone by default.
### SdkViewSectionAlignment
The possible values are:
* `SdkViewSectionAlignment.Center`
* `SdkViewSectionAlignment.Left`
* `SdkViewSectionAlignment.Right`
#### Example snippets
Here's an example where you are overriding the login view to show only the email login option:
```jsx
import { SdkViewSectionType, SdkViewType } from "@dynamic-labs/sdk-api";
;
```
Here is an example where you are overriding the login view to show email or social login options:
```jsx
import { SdkViewSectionType, SdkViewType } from "@dynamic-labs/sdk-api";
;
```
#### Complete example
There is a more complete example also found [here](/design-customizations/tutorials/login-views-guide).
### Branded Wallet Signup/Login (Wallet List)
The `wallet-list` configuration enables you to define tabs with predetermined labels, icons, filters, and recommended wallets, enhancing your application's wallet selection interface. This feature is particularly useful for grouping wallets.
#### Configuring Wallet List Tabs
In the `DynamicContextProvider` setup, the `overrides` field is used to configure each tab in the wallet list. The configuration options available for each tab allow for detailed customization:
* **Label and Icon**: Customize the tab's appearance with a `label` for text and an `icon` for visual representation. The `icon` can be one of the following options:
* A icon from the dynamic iconic package
```tsx
import { BitcoinIcon } from '@dynamic-labs/iconic';
}
]
}
}
]
}
}}
/>
```
* A image URL
```tsx
```
* Or you can bring your own React icon
```tsx
}
]
}
}
]
}
}}
/>
```
* **Wallets Filter**: This option enables to dynamic display of wallets based on the selected tab. Clients have the flexibility to write custom filter functions or utilize predefined ones, for more information read the [sort and filter wallets](/wallets/advanced-wallets/sort-and-filter-wallets) doc
* **Recommended Wallets**: Specify recommended wallets for each tab by providing [wallet option](/react-sdk/objects/wallet-option) keys and optional labels. This feature is designed to highlight preferred wallets, steering users towards secure and suitable options for their specific needs.
* **Style**: An optional field that determines how the tabs are displayed within the wallet list. Currently, the only supported style is `"grid"`.
#### Complete example
There is a more complete example also found [here](/design-customizations/tutorials/wallet-list-views-guide).
# Best Practices
Dynamic is SOC 2 Type II compliant and regularly completes penetration testing and external security audits from Cure53. Dynamic also has an ongoing bug bounty program with HackerOne. All data with Dynamic is transmitted with encryption using HTTPS and similar protocols. Furthermore, all data is securely stored with encryption-at-rest using AES-256 or higher standards.
Dynamic-powered embedded wallets are non-custodial, meaning they are always end-user owned and controlled. Only the end-user has ownership and access to their wallet private keys. For a more detailed description of Dynamic-powered embedded wallets, you can review the architecture and security handling [here](/wallets/embedded-wallets/architecture-security).
### Protect Developer Credentials
* Limit and manage access to the Dynamic Dashboard and API tokens.
* Use Dynamic’s role-based permissions to restrict employee actions. Learn more [here](/developer-dashboard/invite-members).
* Require employees to use a time-based one-time password (e.g., Google Authenticator) for accessing the Dynamic dashboard and features. Contact us for access to this feature.
### Secure Storage of Dynamic API keys
* Store Dynamic’s API tokens securely and minimize access to these credentials.
* They should only be used on the backend and never shared on the client side.
* Institute an internal policy within your organization to rotate Dynamic API keys regularly.
### JWT Length and Storage
* When the JWT expires a user’s session ends (user is logged out) so they will have to re-authenticate once it expires. The JWT token has a maximum lifetime of 30 days. Configure this to the shortest acceptable time to balance security and user experience. More details [here](/developer-dashboard/security).
* Never save or log user JWTs.
Note: When using Dynamic-powered embedded wallets without transactional MFA, it’s important to limit the shelf life of the JWT since the wallet is primarily gated by the JWT and the method used by the user to log in.
## Implement a Code Review Process
* Ensure another employee approves new code before deployment.
* Establish policies to control who can push code to production.
## Mitigate XSS Attacks
* Use content security policies on the frontend.
* Implement TLS and HTTPS for all requests.
* Limit permissible JavaScript, set context headers properly, and avoid open redirects.
* If you enable the frame-src CSP, then you need to perform this whitelisting (learn how [here](https://docs.dynamic.xyz/wallets/embedded-wallets/create-wallets/overview#content-security-policy-csp)).
* Enforce a strict Content Security Policy (CSP). Refer to [this guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) for more information.
## Enhance CORS Security
* Add specific origins for CORS to protect your environment from unauthorized websites using your public environment key.
* Avoid using wildcards in favor of explicit domains.
* Be especially strict with your live environment (e.g don’t have localhost etc)
## Ease of Seed Phrase Export (using Dynamic-Powered embedded wallets)
* Make it easy and readily available for users to export their seed phrase and consider Including it as a step in your sign-up flow.
* Educate Users on security and how you intend to communicate with them
* Clearly communicate to users that neither Dynamic nor any associated parties will ask for private keys or encourage sharing this information.
* Specify the forms of communication and types of interactions users can expect.
## Addressing Potential Risks - internal employee account accessed
If an employee account is compromised and best practices are not followed, there are several risks:
* Malicious code could be deployed to your application that could attempt to drain user wallets on their next login.
* Unauthorized activities could be conducted using acquired JWT and session key.
## Rate Limits
Familiarize yourself with Dynamic’s rate limits for IP addresses, project environments, and endpoints you are using. Refer to [Rate Limits](/developer-dashboard/rate-limits) for more information.
# Analytics
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/analytics-overview.png)
#### Summary
The developer dashboard provides access to daily user count and wallet
distribution data for both connected users and visitors. We'll be adding
additional insights in the coming months.
### 📘 Have charts you wish to see here?
Let us know if you have specific insights through any of our
[channels](/introduction/welcome#support-and-feedback).
#### Usage
1. Log in to the developer dashboard.
2. Navigate to the [Analytics](https://app.dynamic.xyz/dashboard/analytics) tab.
3. View the charts for daily user counts and wallet distribution.
4. The user counts and wallet distribution data is separated by connected users
and visitors.
5. Use the data to gain insights on the usage and financial performance of your
product or service.
# Users
#### Summary
In your developer dashboard, you have full access to view your users, those that have connected and signed.
From here, you can see which wallets connected and when, session information, any information you've collected during onboarding, and more.
To get here, simply navigate to the [Users](https://app.dynamic.xyz/dashboard/users) tab in your dashboard and you'll see your list of authenticated users.
#### User Management Table
We've enabled a simply search box for you. Here you can type in any data point, whether it be the wallet address, user id, email, alias, etc. By default, it will search across **All** columns. If you want to search by a specific column, simply click the dropdown on the right of the search bar and select the column that you'll want to search by.
You can also sort by any column by simply click on the column header, or you filter by chains if you want to narrow your search criteria.
Lastly, if you've collected any custom information during onboarding, you can click on the Columns button in the upper right and select which columns you would like to view.
#### User Details
If you click on a user, then a slide out of the user details will appear. Here you have up to 3 tabs (for now): Account, Sessions, Chainalysis.
The Account tab will show any collected information about your user.
The sessions tab will show you all the sessions associated with that user, chronologically ordered from top to bottom. If you've collected IP addresses, then that information will appear here.
The Chainalysis tab will appear if you selected to turn it on in our [integrations](https://app.dynamic.xyz/dashboard/integrations) page. Here, we will show the results of every Chainlaysis check for a given wallet, including details if a user did not pass.
# Webhook Event types
This is the list of all the types of events we send as part of webhook payloads. We may add more at any time, so when developing and maintaining your code, you should not assume that only these types exist. The current list of event types can be fetched from the [event types endpoint](/api-reference/endpoints/events/getEventTypes).
The events follow the pattern: `resource.event`. Our goal is to design a consistent system that makes it easy to anticipate and understand.
Events that occur on subresources like user.session will include the parent id
but do not trigger the parent's update event.
## User
Occurs whenever a user is created. Is a [user.](/api-reference/schemas/User)
Occurs whenever a user is updated. Is a [user.](/api-reference/schemas/User)
Occurs whenever a user is deleted. Is a [user.](/api-reference/schemas/User)
Occurs whenever a user started the passkey recovery process. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a user completed the passkey recovery process. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a user session is created. Is a
[session.](/api-reference/schemas/Session)
Occurs whenever a user session is revoked. Is a
[session.](/api-reference/schemas/Session)
Occurs whenever a user links a social account. Is a
[provider.](/api-reference/schemas/SocialSignInProvider)
Occurs whenever a user unlinks a social account. Is a
[provider.](/api-reference/schemas/SocialSignInProvider)
## Wallet
Occurs whenever a Dynamic-powered embedded wallet is created. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a wallet is linked to a user. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a wallet is unlinked from a user. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a Dynamic-powered embedded wallet is exported. Is a
[wallet.](/api-reference/schemas/Wallet)
Occurs whenever a wallet is transferred between user accounts. Is a
[wallet.](/api-reference/schemas/Wallet)
## Visit
Occurs whenever a visit is created. Is a
[visit.](/api-reference/schemas/Visitor)
# General Settings
One of the first things you'll want to do as you create a new project is update some of the basic metadata associated with the SDK such as your app name, logo url, and support url.
To update this information, go to the
[General page under Settings](https://app.dynamic.xyz/dashboard/settings/general)
in your developer dashboard. The available options for you are:
| Setting | Description |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Display Name | This is the name that will appear in the SDK whenever we reference your site. By default, the Display Name will be pre-filled with the Project name. |
| App Logo Url | Whenever we display an app logo url, then we'll use this url. |
| Image when user is not found in an Access list | If you use an access list, and want a friendly image in the SDK (think a success image), then you can add an image url that we'll display |
| Image when user is on an Access list | If you use an access list, and want a friendly image in the SDK (think a friendly image to show that a user doesn't have access), then you can add an image url that we'll display |
| Support URL | Whenever a user may be stuck, we'll show your support url to customers so they can reach out. |
# Invite Members
## Inviting a user to your organization
You can invite members of your company to your Dynamic organization by navigating to the Admin screen and clicking on the Members tab.
You can then add a wallet address/email address and a name which will add this wallet/email based user to your
organization. You will also need to assign a role to each new user you invite. The four possible roles are:
| Role | Description |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Owner | Owners have unrestricted visibility and control over the entire dashboard, including sensitive sections such as billing and user management. |
| Admin | Admins have unrestricted visibility and control over the entire dashboard, including sensitive sections such as billing and user management. The only thing an admin cannot do is remove an Owner from an Organization. |
| Developer | Users assigned the Developer role have comprehensive access to all areas of the system, with the exception of the admin-specific functionalities. This exclusion includes areas like billing and member invitations. |
| Member | This is a read-only role. Users are restricted from making any changes to settings or configurations. Their access is limited to viewing user management and analytics data, ensuring they can monitor and review information without modifying it. |
## Accepting an invite to an organization
If the invited user already has a Dynamic account associated with the invited wallet/email then the next time they log in, they will see a notification that they have been invited to join your organization. They can click on the notification to accept the invitation, or choose to ignore it.
If the invited user does not have a Dynamic account associated with the invited wallet/email, they will need to create an account and then accept the invitation. They can do this by following these steps:
1. Connect their wallet/email
2. Complete the onboarding flow
3. Enter their Alias and Email
After this point, they will be asked if they want to join your organization as long as the wallet/email matches that which was invited. They can then accept, and then they will be logged into the organization's dashboard.
# Privacy Settings
#### Configure IP collection
Keeping privacy in mind is central to how we design and build our systems. We realize that some companies prefer to collect as little data as possible about their users while others would prefer to collect a little bit more.
That's why, in our effort to minimize information collected about users where possible have the option to collect or not to collect IP addresses. By default, IP logging is off for logged in users. If you need to enable IP collection for compliance or other reasons, you can toggle this option on in the [Privacy page](https://app.dynamic.xyz/dashboard/settings/privacy) of your dashboard.
As we build out more features, additional configurations will be placed here so you have control about what is or is not collected.
# Projects vs Organizations
If you visit your dashboard, on the top of the page, you will see two tabs: **Projects** and **Organizations**. There are related but different concepts, and this short section will help you understand when to utalize each.
## Projects
You can think of a project as a scoped configuration for a single implementation. Each project comes with both a sandbox and live mode, which each have their own environment ID and configuration.
Importantly, if a user is invited as a member of an organization, they will have access to all projects within that organization.
### Creating a new project
In the navigation bar of the [dashboard](https://app.dynamic.xyz), click on the Project dropdown menu (second from the left):
1. Click "Create new project"
2. Enter Project Name.
## Organizations
Organizations are a way to group projects together. Organizations are billed distinctly, and each organization can have multiple projects.
### Creating a new organization
In the navigation bar, click on the Organization dropdown menu (first on the left):
1. Click "Create organization" at the bottom of the dropdown
2. Enter Organization Name and Website URL
And you're done. You now have a new organization that you can use. You will need
to invite members of your team to this organization if you would like to work
with them there.
# Rate Limits
Dynamic rate limit policies
Dynamic enforces rate limits based upon IP address, project environment, and certain endpoints to protect against attacks and to prevent abuse of Dynamic's platform. These limits are subject to change.
## Errors
Requests returning a `429` status code have been rate limited.
```json
{
"message": "Rate limit exceeded",
"code": 429
}
```
## Rate limit categories
* IP-based rate limits
* Project environment-based rate limits
* Endpoint-based rate limits
## SDK rate limits
All endpoints used by the SDK prefixed by `/sdk` are subject to the following limits.
* 100 requests per minute per IP
* 10000 requests per minute per project environment
### Endpoints
| Endpoint | Paths | Limited by | Rate Limit |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------ |
| Nonce | [/:environmentId/nonce](/api-reference/endpoints/sdk/getNonce) | IP Address | 10 requests per 1 minute |
| Signin | [/:environmentId/verify](/api-reference/endpoints/sdk/verify) [/:environmentId/emailVerifications/signin](/api-reference/endpoints/sdk/signInWithEmailVerification) /:environmentId/smsVerifications/signin [/:environmentId/providers/:providerType/signin](/api-reference/endpoints/sdk/oauthSignIn) /:environmentId/telegram/signin [/:environmentId/farcaster/signin](/api-reference/endpoints/sdk/farcasterSignIn) [/:environmentId/externalAuth/signin](/api-reference/endpoints/sdk/externalAuthSignin) | IP Address | 10 requests per 1 minute |
| OTP | [/:environmentId/emailVerifications/create](/api-reference/endpoints/sdk/createEmailVerification) [/:environmentId/emailVerifications/retry](/api-reference/endpoints/sdk/retryEmailVerification) [/:environmentId/smsVerifications/create](/api-reference/endpoints/sdk/createSmsVerification) [/:environmentId/smsVerifications/retry](/api-reference/endpoints/sdk/retrySmsVerification) | IP Address | 10 requests per 1 minute |
| MFA | [/:environmentId/users/mfa/register/totp](/api-reference/endpoints/sdk/registerTotpMfaDevice) [/:environmentId/users/mfa/auth/totp](/api-reference/endpoints/sdk/authMfaTotpDevice) [/:environmentId/users/mfa/register/passkey](/api-reference/endpoints/sdk/) [/:environmentId/users/mfa/auth/passkey](/api-reference/endpoints/sdk/) [/:environmentId/users/mfa/recovery](/api-reference/endpoints/sdk/createNewRecoveryCodes) [/:environmentId/users/mfa/auth/recovery](/api-reference/endpoints/sdk/authMfaRecovery) | IP Address | 5 requests per 1 minute |
| Token balances | [/:environmentId/chains/:chainName/balances](/api-reference/endpoints/sdk/getAccountBalances) | IP Address | 20 requests per 1 minute |
| Embedded wallets | [/:environmentId/users/embeddedWallets](/api-reference/endpoints/sdk/createEmbeddedWallets) [/:environmentId/users/embeddedWallets/walletAccounts](/api-reference/endpoints/sdk/createEmbeddedWallets) [/:environmentId/users/embeddedWallets/sessionKey](/api-reference/endpoints/sdk/registerSessionKey) | IP Address | 20 requests per 1 minute |
| Update user | [/:environmentId/users](/api-reference/endpoints/sdk/updateSelf) | IP Address | 5 requests per 1 minute |
## Developer rate limits
All endpoints used by Developers are subject to the following rate limits.
* 1500 requests per minute per IP
* 3000 requests per minute per project environment
### Endpoints
| Endpoint | Path | Limited by | Rate Limit |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------- |
| Bulk create user | [/environments/:environmentId/users/bulk](/api-reference/endpoints/users/bulkCreateUser) | IP Address | 150 requests per 1 minute |
| Create embedded wallet | [/environments/:environmentId/users/embeddedWallets](/api-reference/endpoints/wallets/createEmbeddedWallet) [/environments/:environmentId/users/embeddedWallets/farcaster](/api-reference/endpoints/wallets/createEmbeddedWalletFromFarcaster) | IP Address | 300 requests per 1 minute |
| Organization invites | [/organizations/:organizationId/invites](/api-reference/endpoints/invites/createInvite) | IP Address | 20 requests per 1 minute |
## Webhooks
See [webhooks limits](/developer-dashboard/webhooks#limits) for more information.
# Sandbox vs Live
Every project you create in Dynamic comes with a Sandbox and a Live environment. Both these environments behave similarly but we've customized the Sandbox environment to make it ideal for testing and exploring features.
### Two Key Differences
1. **Everything is free**: all Dynamic features on Sandbox are free. Whether you
choose to upgrade to our Advanced tier or not, all the features that we offer
will be available for you to test and explore.
2. **Sandbox is limited to 100 users**: your Sandbox environment is intended to
help you test configurations and features and is not intended to be live on
your site. We therefore have limited Sandbox to 100 users. After 100 users,
you will not be able to authenticate new wallets unless you delete users.
# Security Settings
We take security seriously at Dynamic and most of the work we do is behind the scenes so you don't have to worry about it. We will surface some security features that can be configured.
Today, we have 2 features that you can configure
1. CORS origin urls
2. JWT expiration time
### Allowed CORS Origin
Adding origins for
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to your project
environment (sandbox and live) protects your environment from unauthorized
websites using your public environment key.
Any origins added to an environment will allow only those domains to make API
requests via our SDK.
If you don't add an origin, all domains will be allowed to make API requests
#### Adding an Origin
To add an origin, navigate to [the Security Settings in the dashboard](https://app.dynamic.xyz/dashboard/settings/security).
Click **Create Origin** and add your origin. (You can add multiple origins to
any environment)
Be sure to format your origin according to the RFC 6545 format (exception of the
wildcard `*`). An origin is a URL without the path.
#### Using wildcards
One or more `*` wildcard characters in your origin will represent 0 or more
characters (a-z, 0-9, -, .) when matching origins.
#### Acceptable Example Values
* [http://domain.com](http://domain.com)
* [https://domain.com](https://domain.com)
* [https://sub.domain.com](https://sub.domain.com)
* [http://127.0.0.1:4200](http://127.0.0.1:4200)
* [http://localhost:3200](http://localhost:3200)
* [https://my-app-\*.vercel.app](https://my-app-%2A.vercel.app)
#### Unacceptable Example Values
* domain.com
* //domain.com
* [https://domain.com/home](https://domain.com/home)
### JWT Expiration Time
In the security settings page, you can update the expiration date of the JWT
token. The expiration time is the amount of time before one of your customers
will need to sign to log in.
To update this expiration time, navigate to **Settings > Security** in the
dashboard. Enter the amount of time in Day, Weeks, Months for the expiration
time.
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/jwt-expiration.png "jwt-expiration.png")
The default value that we have set is 2 hours. We recommend that you verify with
a security expert or your security team before updating this value.
# Test Accounts
Test Accounts allow you to log in with a static OTP
Test Accounts are only available in the
[Sandbox](/developer-dashboard/sandbox-vs-live) environment
While testing projects in a Sandbox environment, you can use Test Accounts
to log in with a static verification code.
[Test Accounts](https://app.dynamic.xyz/dashboard/developer/test-accounts)
are enabled through the Developer Dashboard and allow you to use the
provided OTP code with:
1. Any email including `+dynamic_test` right before the "@" sign, e.g. `example+dynamic_test@YOUR_DOMAIN`
2. Any US phone number with an area code of `(555)`, e.g. `+1 (555)-123-4242`
# SDK and API Keys
This page hosts the keys that you'll need to setup the SDK, validate the JWT, and generate tokens for API usage.
To access this page, navigate to the
[developer](https://app.dynamic.xyz/dashboard/developer/api) page in your
dashboard.
#### Environment ID
The environment ID is the key used for our platform to identify your SDK and
associate the users to your project environment. Each project has 2 keys, one
for Sandbox and one for Live. Whenever you want to setup a new instance of the
SDK, you simply need to copy the Environment ID and copy it into the settings
prop in the SDK.
```ts TypeScript
import {
DynamicContextProvider,
DynamicWidget,
} from "@dynamic-labs/sdk-react-core";
const App = () => (
);
export default App;
```
#### Public Key
The public key is what you can use to validate the JWT is authentic on your
backend. We recommend that you follow
[this guide](/authentication-methods/how-to-validate-users-on-the-backend) to properly validate your
users and ensure that users are using authenticated JWT's.
#### API Tokens
To use our API's, all you'll need to do is create a token and use it in your API
requests as a bearer Token. You can test out our API's
[here](/api-reference/overview).
# Visitors
### 📘 Visitors are users that have connected a wallet through the connect-only path
For more information on authenticated users compared to visitors, see [here](/wallets/advanced-wallets/connected-vs-authenticated).
In your developer dashboard, you have full access to view all your visitors, ie, those users who have connected through the **connect-only path** and therefore didn't need to sign to login.
This is a simplified table from the Users table, those are users **who authenticated**, since minimal information is collected about visitors. One of the main points to highlight here is that no User ID is created and no information is associated to this visitor.
#### Visitors Table
Here you can view a list of your visitors and search by wallet address and filter. You can also sort by any column by simply click on the column header, or you filter by chains if you want to narrow your search criteria.
Note that since we don't associate a wallet to a user ID, the same user may have many visits if they choose to connect with different wallets.
# General
## Why webhooks?
Webhooks allow you to listen to events happening in your Dynamic environment and integrate applications to automatically receive information about those events.
To enable webhook events, you need to register webhook endpoints. After you register them, Dynamic can push real-time event data to your application’s webhook endpoint when events happen within your Dynamic environment.
Dynamic sends webhook events to your app as a JSON payload that includes an Event object.
## Events
Events are our way of letting you know when something happens in your Dynamic environment. When an event occurs, we create a new Event object. For example, when a user links a wallet, we create a `wallet.linked` event. Certain API requests might create multiple events. For example, when a user first signs in to your environment, we create a `user.created` event, and when a user successfully authenticates, we create a `user.session.created` event.
### Event object
**eventId**\
The unique id of the event that triggered the webhook message. A single event in a project can trigger one or more webhook messages if there is more than one webhook configured.
**messageId**\
The unique id of the message sent. This key should be used as the idempotency key inorder to handle redeliveries.
**webhookId**\
The unique id of the configured webhook that sent the message.
**userId**\
The unique id of the user who triggered the event. This can be different than `userId`'s in the event payload. For instance, when creating users via developer APIs the triggerer will be the developer's userId and the event payload will contain the id of the created user. This parameter will be `undefined` if the event is triggered using an API key.
**eventName**\
The name of the event that triggered the message. Events always conform to the following convention `{resourceType}.{action}`. For example with the event name of `user.created`, `user` is the resource type and `created` is the action performed on the resouce. The full list of event names can be retrieved by using the `/eventTypes` endpoint in the API [here](/api-reference/endpoints/events/getEventTypes).
**environmentId**\
The unique id of the environment from which the event originated.
**environmentName**\
The name of the environment from which the event originated. This can be used to have the same systems handle both `live` and `sandbox` events.
**timestamp**\
Timestamp when the event occurred.
**redelivery**\
If the message is a redelivery, this will be set to true.
**data**\
The data object contains the event payload. The structure of the data object will depend on the event that triggered the message. The full list of event payloads can be found [here](/developer-dashboard/eventTypes).
#### Example Event Object
Below is an example of a `user.created` event
```json
{
"eventId": "2a92c161-3167-44ad-8fce-4c6cdaed8129",
"messageId": "5a2a5360-bb7e-4ea6-9bd3-0146bf2f734f",
"webhookId": "a86acea4-e050-4846-8e4f-0ae039f6e37c",
"eventName": "user.created",
"environmentId": "123e4567-e89b-12d3-a456-426614174000",
"environmentName": "sandbox",
"timestamp": "2023-10-26T14:30:59.210Z",
"data": {
"chain": "EVM",
"origin": null,
"ipAddress": "::1",
"verifiedCredentialId": "b13f337d-a8dc-41a7-96f5-5e5d76ad864a",
"userId": "a5914498-7a8b-4c58-b04c-9624fef2897c",
"expiresAt": null,
"walletPublicKey": "0x3FcE1F4F28DbA209344072867134A3a7F547C7f1",
"createdAt": "2023-10-26T14:30:02.909Z",
"projectEnvironmentId": "7181a853-fb76-4dc2-9af8-6aeb6d2b818b",
"provider": "browserExtension",
"walletName": "metamask",
"id": "484e49ba-3026-4e2c-9bf0-ed98ae224833",
"revokedAt": "2023-10-26T14:30:59.204Z",
"verifiedCredentialType": "wallet"
}
}
```
### What generates events?
| Source | Trigger |
| :------------------ | :------------------------------------------------------------------------------------ |
| Developer Dashboard | When you call an API by modifying Dynamic resources in the Developer Dashboard. |
| SDK | When a user action in your app or website integrating the SDK results in an API call. |
| API | When you call an API directly. |
## Setting up Webhooks
Identify what events you would like to monitor [here](/developer-dashboard/eventTypes).
Develop a webhook endpoint to receive event data POST requests, making sure it uses HTTPS.
Register your endpoint with Dynamic using the [Webhooks Developer Dashboard](https://app.dynamic.xyz/dashboard/developer/webhooks) or the API.
#### Signature validation
Dynamic follows general best practice when it comes to signature validation. As such, each payload includes a `x-dynamic-signature-256` header which has a hash signature value, generated from your secret token.
Each webhook has a unique secret token that is used to generate the message signature from the event object. This secret can be found on the webhook detail page in the Developer Dashboard.
![webhook secret](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/webhook-secret.png)
To perform the validation, you can follow the guide [here](/guides/webhooks-signature-validation) for a javascript implementation. There are plenty of other template examples out there to help with different implementations. We recommend [the Github guide](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) as it follows exactly the same format of signature as we do.
## Event delivery behavior
### Test payload
When you register a webhook, Dynamic sends a test payload to the webhook’s configured URL to verify that the endpoint exists and can receive payloads. The test payload is a `ping` event.
### Retry behavior
Webhook messages have built-in retry logic. All non-200 response codes will result in a retry.
Events are delivered at least once, but can be retried up to 5 times for live environments, make sure to handle idempotency using the `messageId` attribute.
Below are the request retry intervals for live environments
* 15 seconds
* 1 minute
* 10 minutes
* 1 hour
* 1 day
### Event ordering
Dynamic makes no guarantee of message ordering. Events can be sent in an order not congruent with the order in which they happened.
## Best Practices for implementing webhooks
### Idempotency
Configured endpoints may receive the same event multiple times. This can happen for a number of reasons, including network timeouts, duplicate events, or as a result of retry logic. As such, it is important to design your endpoint to be idempotent so that it can safely handle duplicate events. Using the `messageId` attribute in the event object is a good way to handle this.
### Events
Only select the events you are interested in. If you are only interested in `user` events, only select those event types. This will reduce the number of messages you receive and the number of messages you need to handle.
### Verify messages
Make sure to validate the `x-dynamic-signature-256` header to ensure the message is coming from Dynamic. This is a hash signature value, generated from your secret token.
### Security
Make sure your endpoint is using HTTPS. Dynamic will not send messages to endpoints that use HTTP.
## Limits
### Webhooks
10 per environment
### Webhook messages
After repeated failed delivery of messages within a 30 day period to a configured endpoint that responds with a non `2XX` HTTP status code the webhook will be automatically disabled. For live environments this is 6 failed attempts per message up to a maximum of 1500 failed messages, and for sandbox environments this is 3 failed attempts per message up to a maximum of 250 failed messages. Note that if any of the message retries are successful it is not counted against the failure limit. The webhook will remain disabled until it is re-enabled manually.
Messages in live environments will be available for 90 days and 30 days for sandbox environments.
### Endpoint response time
Configured payload urls must respond promptly, if the endpoint does not respond within 15 seconds, the message timeout and will be retried.
# Overview
Traditionally, embedded wallets were limited to single applications, hindering cross-app interoperability for end-users and developers. This restriction made it challenging to create seamless experiences across multiple web3 applications. Additionally, developers aiming to build ecosystems around their brand or chain faced limited tools for enabling universal wallet access from most wallet providers.
**Dynamic is designed to avoid both pitfalls and offer you solutions to improve end-user interoperability across web3 and solutions to let you build around your app or third-party ecosystem.**
This document outlines the different features and tools to enable cross-app connectivity for your embedded wallets and ecosystem growth.
## I'm building an ecosystem or chain
Designed for developers who want to foster unified login experiences across partner apps or associated third-party application developers. Ideal if you are building a chain, you are already a large brand that is extending reach to smaller partners, or you are a small company with a deep relationship with a single large entity in the ecosystem.
### 1: [Display your wallet within other apps](/ecosystem-wallets/web3-libraries)
We provide simple guides you can offer to your partners to add your wallet to their apps.
### 2: Create a full ecosystem around your brand
We are building multiple powerful features to let you build a full ecosystem around your brand. [Reach out to us to learn more](https://dynamic.xyz/slack).
## I'm building a single app
If you're building an app, Dynamic offers two features to let you enable your end-users to connect their Dynamic-powered wallets to other apps.
### 1: [Enable your end-users to connect to any app with QR code scanning (and URI codes)](/ecosystem-wallets/qr-scanning)
You can allow your end-users to connect to any app that uses WalletConnect. This provides end-users with the broadest web3 app coverage but is mostly suited for end-users who have some familiarity with WalletConnect and other apps across web3.
**How it works:**
1. Mobile - Upon clicking "Connect," your end-users are prompted to go to their preferred app and scan a WalletConnect QR code or copy a URI code. Once scanned, they will confirm the connection, and their assets and wallet will now be connected to the app.
2. Desktop - Your end-users will copy and paste WalletConnect's URI code to associate their embedded wallet with an external site.
### 2: Inject your end-users' wallets into an iframe
Coming soon.
# QR Scanning (Global Connectivity)
## Overview
Global connectivity allows end-users to use your app and their embedded wallet on any web3 app that integrates WalletConnect.
Transactions from the app will be forwarded to the end-user on the app that initiated the connection.
This feature is currently restricted to EVM embedded wallets.
To ensure all transactions from a connected app are approved first by the
end-user, confirmation prompts will be forced for messages that come from a
different app.
## Getting started
In order to use Global Connectivity, we have provided an additional optional package:
```bash
npm i @dynamic-labs/global-wallet
```
```jsx
import { GlobalWalletExtension } from "@dynamic-labs/global-wallet";
return (
);
```
After passing in the extension, you should see a "Connect" button on the main page of the dynamic widget.
Follow the instructions on the widget to start connecting to other apps.
## Security
When users connect to third-party websites and initiate transactions, Dynamic automatically interacts with the Blockaid API ([www.blockaid.io](http://www.blockaid.io)) to run transaction simulations and verify that the associated URLs are safe and not malicious.
Please be aware that this feature has usage limits, and additional charges may apply for higher-volume usage. For details on scaling and pricing, please contact our support team.
## FAQ
### Headless and hooks support
Currently, global connectivity is not supported in headless mode or with hooks.
### Safety practices
For best safety practices, please refer to our security section above. In general:
* Educate users to check which web3 apps they connect to.
* Dynamic will be rolling out new integrations to warn users if they attempt to connect to malicious dApps in the near future.
### Using Account Abstraction with global wallets
Account Abstraction is compatible with global wallets. If using gas sponsorship, you should whitelist your own dApps/contracts. For more information, see our [Smart Wallets documentation](/smart-wallets/add-smart-wallets).
### What is a URI code?
A URI (Uniform Resource Identifier) code in the context of WalletConnect is a string that contains all the necessary information for a wallet to establish a connection with a dApp.
### Compatibility with other chains
Currently, global connectivity only work on EVM chains. Solana support may come in the future as more dApps support WalletConnect with Solana.
# Web3 Wallet Libraries
## RainbowKit
You can guide your partners to add your wallet as a recommended wallet in their app. To do so, they will create a custom connector, as described below, which will should your wallet. When clicked, the wallet will render a QR code, which will work with Dynamic's [QR scanning](/ecosystem-wallets/qr-scanning) feature.
### Step 1: Import `connectorsForWallets` and `getWalletConnectConnector`
```javascript
import { RainbowKitProvider, connectorsForWallets, Wallet, getWalletConnectConnector, darkTheme } from '@rainbow-me/rainbowkit';
```
### Step 2: Create a custom connector
```javascript
export interface MyWalletOptions {
projectId: string;
}
export const legionkey = ({ projectId }: MyWalletOptions): Wallet => ({
id: 'legion-key',
name: 'LegionKey',
iconUrl: './images/legion-key.png',
iconBackground: '#985d3c',
mobile: {
getUri: (uri: string) => uri,
},
qrCode: {
getUri: (uri: string) => uri,
instructions: {
learnMoreUrl: 'https://my-wallet/learn-more',
steps: [
{
description:
'We recommend putting My Wallet on your home screen for faster access to your wallet.',
step: 'install',
title: 'Open the My Wallet app',
},
{
description:
'After you scan, a connection prompt will appear for you to connect your wallet.',
step: 'scan',
title: 'Tap the scan button',
},
],
},
},
extension: {
instructions: {
learnMoreUrl: 'https://my-wallet/learn-more',
steps: [
{
description:
'We recommend pinning My Wallet to your taskbar for quicker access to your wallet.',
step: 'install',
title: 'Install the My Wallet extension',
},
{
description:
'Be sure to back up your wallet using a secure method. Never share your secret phrase with anyone.',
step: 'create',
title: 'Create or Import a Wallet',
},
{
description:
'Once you set up your wallet, click below to refresh the browser and load up the extension.',
step: 'refresh',
title: 'Refresh your browser',
},
],
},
},
createConnector: getWalletConnectConnector({ projectId }),
});
```
### Step 3: Config the connectors
```javascript
const connectors = connectorsForWallets(
[
{
groupName: 'Recommended',
wallets: [legionkey],
},
{
groupName: 'Common',
wallets: [rainbowWallet, walletConnectWallet],
},
],
{
appName: 'My RainbowKit App',
projectId: 'YOUR_PROJECT_ID',
}
);
```
### Step 4: Add to the Rainbowkit Configuration
```javascript
const config = createConfig({
connectors,
...
});
```
## ReOwn AppKit (formally WalletConnect)
To integrate ReOwn AppKit (formally WalletConnect), follow these steps to submit your wallet as an official wallet into the ReOwn ecosystem:
### Step 1: Create an account on reown.com
* Visit [reown.com](https://reown.com) and sign up for a new account if you don't already have one.
* Verify your email address and complete the account setup process.
### Step 2: Create a new walletKit project
* Log in to your ReOwn account.
* Navigate to the dashboard and look for the option to create a new walletKit project.
* Fill in the required information about your wallet, including its name, description, and supported networks.
### Step 3: Submit your wallet for review
* Once your walletKit project is set up, you'll need to submit your wallet for review.
* Provide all necessary documentation, including your wallet's features, security measures, and any other relevant information.
* Ensure that your wallet meets all of ReOwn's guidelines and requirements for official wallets.
### Step 4: Once approved, it will show up in any AppKit list
* After submission, the ReOwn team will review your wallet.
* When approved, your wallet will be added to the official ReOwn ecosystem.
* It will then appear in AppKit lists, making it easily discoverable for users across various dApps that implement ReOwn AppKit.
* Edit the WalletKit project ID in the Dynamic Dashboard [here](https://app.dynamic.xyz/dashboard/log-in-user-profile/#walletconnect).
By following these steps, you'll integrate your wallet with ReOwn AppKit, increasing its visibility and accessibility within the ReOwn ecosystem.
# Example apps
## Create Dynamic App
Whether you need React, React Native or Next.js, whether you want to use Ethers or Viem, whether you want to use Wagmi or not, we've got you covered. Simply run `npx create-dynamic-app@latest` and follow the prompts!
```
npx create-dynamic-app@latest
```
# Banxa
## Summary
At Dynamic, we make it incredibly simple to add a Fiat onramp solution to your SDK integration. We are currently working with [Banxa](https://banxa.com/) and other great onramp solutions will be made available soon.
### Usage
To enable an onramp solution:
1. Go to your developer dashboard and find the [Onramp Providers](https://app.dynamic.xyz/dashboard/configurations#onramp) tab.
2. **Review and agree** to Banxa's Terms and Conditions.
3. Once confirmed, enable the Banxa onramp provider.
1. Note: if you enable it on Sandbox, then you'll be using Test Order. Reference Banxa's test information so you can easily [test in sandbox.](https://docs.banxa.com/docs/order-flow)
![Banxa](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/banxa.png)
You're done. In the Dynamic widget, users will now see a "Buy" button that will open up a fiat onramper iFrame.
# Bring Your Own Onramp
## Introduction
If you want to embed any onramp provider in the widget, we've got your back! In this example we'll integrate [the Coinbase onramp](https://www.coinbase.com/en-gb/developer-platform/products/onramp) but the principle is exactly the same for any other.
## Pre-requisites
We assume you already have Dynamic integrated into a React based app, and are using the Dynamic Widget. The plan is to override the existing Buy button in the user profile page of the widget, so make sure you have [onramps enabled in your Dynamic dashboard]() for that to show up.
## Full Demo
You can find the full example app of this implementation code [here](https://github.com/dynamic-labs/custom-onramp-example/), and the deployment at [https://custom-onramp-example.vercel.app/](https://custom-onramp-example.vercel.app/).
## Implementation
### Install dependancies
The only other library we will need is the Coinbase Pay javascript SDK:
```bash
npm i @coinbase/cbpay-js
```
### Scaffold our override file
Create a new file in your codebase called `customOnramp.js`. In it let's add the imports and declarations needed to get started:
```javascript
// Method to initialize the Coinbase Pay instance.
import { initOnRamp } from "@coinbase/cbpay-js";
// We want to only run things once, and this variable will help.
let isSetupInitiated = false;
// Empty function that we will fill out in the next section.
export const setupCoinbaseButtonOverride = (options) => {
}
```
For the following sections, unless otherwise told, place the code inside the now empty `setupCoinbaseButtonOverride` function.
### Setup the pay instance
Inside the setupCoinbaseButtonOverride, let's set up a few things, including the CB pay instance:
```javascript
// Stop if it already ran
if (isSetupInitiated) {
return;
}
// Set our flag to say the function has initiated
isSetupInitiated = true;
// Destructure the options needed for the onramp
const {
appId,
addresses,
assets,
debug = false
} = options;
// Variable to hold the instance
let onrampInstance = null;
// Initialize the onramp
initOnRamp({
appId,
widgetParameters: {
addresses,
assets,
},
// Transaction callback
onSuccess: () => {
if (debug) console.log('Coinbase transaction successful');
},
// Widget close callback
onExit: () => {
if (debug) console.log('Coinbase widget closed');
},
// General event callback
onEvent: (event) => {
if (debug) console.log('Coinbase event:', event);
},
experienceLoggedIn: 'popup',
experienceLoggedOut: 'popup',
closeOnExit: true,
closeOnSuccess: true,
}, (_, instance) => {
// Set assign the instance to our variable
onrampInstance = instance;
if (debug) console.log('Coinbase instance initialized');
});
```
### Find the current Buy button
Add a new function (still inside setupCoinbaseButtonOverride) called `findButtonInShadowDOM` which is responsible for detecting the button in the shadow dom:
```javascript
const findButtonInShadowDOM = (root) => {
const shadows = root.querySelectorAll('*');
for (const element of shadows) {
if (element.shadowRoot) {
const button = element.shadowRoot.querySelector('[data-testid="buy-crypto-button"]');
if (button) {
return button;
}
const deepButton = findButtonInShadowDOM(element.shadowRoot);
if (deepButton) {
return deepButton;
}
}
}
return null;
};
```
### Override the current button
Add another new function (still inside setupCoinbaseButtonOverride) called `setupOverride` which is responsible replacing the existing button functionality with our own:
```javascript
const setupOverride = () => {
// Call our previously declared function
const button = findButtonInShadowDOM(document);
// We're ready to override the button
if (button && onrampInstance) {
if (debug) console.log('Found button and Coinbase instance ready');
// Remove disabled state
button.classList.remove('disabled');
button.removeAttribute('disabled');
// Remove all existing click handlers
const newButton = button.cloneNode(true);
button.parentNode?.replaceChild(newButton, button);
// Add our new click handler
newButton.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if (debug) console.log('Opening Coinbase widget');
onrampInstance?.open();
});
return true;
}
return false;
};
```
### Poll for the button and onramp
Since we need both the button to be present and the onramp instance to exist for us to complete the override, we must poll for that state:
```javascript
// Set the current time
const startTime = Date.now();
// Declare an interval of .5 seconds
const checkInterval = setInterval(() => {
// Run the override setup and if it succeeds or 30 seconds have passed...
if (setupOverride() || Date.now() - startTime > 30000) {
clearInterval(checkInterval);
if (Date.now() - startTime > 30000) {
if (debug) console.warn('Timeout reached while setting up Coinbase button override');
}
}
}, 500);
```
### Return a cleanup function
We need to remember to tear things down again when we're finished:
```javascript
return () => {
clearInterval(checkInterval);
onrampInstance?.destroy();
isSetupInitiated = false;
};
```
### Use setupCoinbaseButtonOverride
You've now finished with the setupCoinbaseButtonOverride method, so let's add it to one of our components. It doesn't matter to much which one as long as it's rendered at the same time as the Widget is. Note that it cannot be the same component you declare your DynamicContextProvider in, it must be inside the component tree.
#### Adding Dynamic & other imports
Here we'll do it in the same component (Main.js as we have our DynamicWidget). Let's do the relevant imports first, we're going to need a couple of hooks from Dynamic, as well as our setupCoinbaseButtonOverride:
```jsx
// Main.js
import { DynamicWidget, useDynamicContext, useIsLoggedIn } from "@dynamic-labs/sdk-react-core";
import { isEthereumWallet } from '@dynamic-labs/ethereum';
import { setupCoinbaseButtonOverride } from './customOnramp.js';
```
#### UseEffect, Dynamic hooks & widget
Next we'll scaffold the Main component itself and create an empty useEffect which depends on the relevant Dynamic hooks:
```jsx
export default const Main = () => {
// We need to know if user is logged in
const isLoggedIn = useIsLoggedIn();
// We need to know that the user has a wallet
const { primaryWallet } = useDynamicContext();
// We will fill this in next
useEffect(() => {
}, [isLoggedIn, primaryWallet])
return (
)
};
```
#### Conditionals and calling our init
Everything from now on will be added inside the useEffect we just declared.
```jsx
let cleanup;
const initOnramp = async () => {
// We only want to support Eth here, you can do more
if (!isEthereumWallet(primaryWallet)) {
console.log('not an Eth Wallet');
return;
}
if (cleanup) {
cleanup();
}
// Fetch the currently supported EVM networks for that wallet
const networks = primaryWallet.connector.evmNetworks.map(network =>
network.name.toLowerCase()
);
cleanup = setupCoinbaseButtonOverride({
appId: '12109858-450c-4be4-86b9-13867f0015a1',
addresses: {
[primaryWallet.address]: networks
},
assets: ['ETH', 'USDC'],
debug: true
});
};
// Initialize our custom onramp
if (isLoggedIn && primaryWallet) {
initOnramp();
}
return () => {
if (cleanup) {
cleanup();
}
}
```
That's it! Your Buy button now opens the coinbase onramp widget, while passing in all the relevant parameters it needs!
# Dynamic Package
Please be aware that our Flutter SDK is in open alpha at the moment!
Available now:
* email login (headless)
* sms login (headless)
* auth flow UI
* profile UI
* EVM embedded wallets
* web3dart integration
* social login
Coming next:
* solana embedded wallets
## Installation
Simply run the following in your terminal:
```
flutter pub add dynamic_sdk
```
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):
```
dependencies:
dynamic_sdk: ^0.0.1-alpha.2
```
## Set up
Getting started with `DynamicSDK` takes only three steps:
### 1. Initialize your client
First you have to start the client singleton with your data in `ClientProps`;
```dart
import 'package:dynamic_sdk/dynamic_sdk.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
DynamicSDK.init(
props: ClientProps(
environmentId: 'your-environment-id',
appLogoUrl: 'your-logo-url',
appName: 'your-app-name',
),
);
runApp(const MyApp());
}
```
### 2. Wait for the SDK to load
Add the `DynamicSDK.instance.dynamicWidget` and wait for the SDK to loaded using the `DynamicSDK.instance.sdk.readyChanges` stream;
```dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: Stack(
children: [
// Make sure the SDK is ready before using it
StreamBuilder(
stream: DynamicSDK.instance.sdk.readyChanges,
builder: (context, snapshot) {
final sdkReady = snapshot.data ?? false;
return sdkReady
? const MyHomePage(title: 'Flutter Demo Home Page')
: const SizedBox.shrink();
},
),
// DynamicSDK widget must be available all the time
DynamicSDK.instance.dynamicWidget,
],
),
);
}
}
```
### 3. Do your stuff
That's it! Now you are good to go! See below how to authenticate using our UI:
```dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
// Listen to auth token changes
StreamBuilder(
stream: DynamicSDK.instance.auth.tokenChanges,
builder: (context, snapshot) {
final authToken = snapshot.data;
// Show the auth token when logged in
return authToken != null
? Column(
children: [
const LogoutButton(),
const SizedBox(height: 24),
Text('AUTH TOKEN: $authToken'),
],
)
// Show Dynamic UI for sign in
: const LoginButton();
},
),
],
),
],
),
),
),
);
}
}
// Show Dynamic UI for sign in
class LoginButton extends StatelessWidget {
const LoginButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => DynamicSDK.instance.ui.showAuth(),
child: const Text('Dynamic Login'),
);
}
}
// Headless logout function
class LogoutButton extends StatelessWidget {
const LogoutButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => DynamicSDK.instance.auth.logout(),
child: const Text('Logout'),
);
}
}
```
## Available modules
Let's walk you through the available modules and how to use most of the features.
### SDK Module
1. Get to know when the SDK is ready to be used by listening to `readyChanges` stream.
```dart
StreamBuilder(
stream: DynamicSDK.instance.sdk.readyChanges,
builder: (context, snapshot) {
final sdkReady = snapshot.data ?? false;
return sdkReady
? const MyHomePage(title: 'Flutter Demo Home Page')
: const SizedBox.shrink();
},
),
```
### User Interface Module
1. Use our interface to sign in
```dart
class LoginButton extends StatelessWidget {
const LoginButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => DynamicSDK.instance.ui.showAuth(),
child: const Text('Dynamic Login'),
);
}
}
```
2. Use our interface to see the user's profile
```dart
class UserProfileButton extends StatelessWidget {
const UserProfileButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => DynamicSDK.instance.ui.showUserProfile(),
child: const Text('Show Profile'),
);
}
}
```
### Auth Module (headless authentication)
1. Headless e-mail sign in
```dart
class EmailLoginButton extends StatelessWidget {
const EmailLoginButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async => await DynamicSDK.instance.auth.email.sendOTP(
'user@email.com'
),
child: const Text('Email Login'),
);
}
}
```
2. Headless SMS sign in
```dart
class SMSLoginButton extends StatelessWidget {
const SMSLoginButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async => await DynamicSDK.instance.auth.sms.sendOTP(
PhoneData(
phone: phone, // User's phone number
iso2: 'US',
dialCode: '+1',
),
),
child: const Text('SMS Login'),
);
}
}
```
3. Verity OTP
```dart
class VerifyOTPButton extends StatelessWidget {
const VerifyOTPButton({
super.key,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
// For email use await DynamicSDK.instance.auth.email.verifyOTP(code),
onPressed: () async => await DynamicSDK.instance.auth.sms.verifyOTP(code), // Verification code
child: const Text('Verify Code'),
);
}
}
```
### Wallets Module
1. Get network
```dart
Future getNetworkInfo({required BaseWallet wallet}) async {
final network = await DynamicSDK.instance.wallets.getNetwork(wallet: wallet);
final name = getNetworkName(network.value);
return name;
}
String getNetworkName(networkId) {
final evm = DynamicSDK.instance.networks.evm;
bool isEvm = evm.any((network) => network.networkId == networkId);
if (isEvm) {
final network = evm.firstWhere(
(network) {
return network.networkId == networkId;
},
);
return network.name;
} else {
return networkId;
}
}
```
2. Switch networks
```dart
void switchNetwork({
required BaseWallet wallet,
required int chainId,
}) async {
await DynamicSDK.instance.wallets.switchNetwork(
wallet: wallet,
network: Network(chainId),
);
}
```
3. Sign message
```dart
Future signMessage({
required String message,
required BaseWallet wallet,
}) async {
try {
final signedMessage = await DynamicSDK.instance.wallets.signMessage(
message: message,
wallet: wallet,
);
return signedMessage;
} catch (e) {
print(e);
rethrow;
}
}
```
Other methods like `getBalance` and `setPrimary` are as straight forward as the ones above.
You can read more about our client package [here](/flutter/package-references/client).
# Deeplink URLs
In order to use most of our Flutter features (such as social connections), you will first
have to set up the list of mobile deeplink URLs Dynamic may use to redirect back to your app.
For security reasons, when deeplinking back to your app, we must verify whether the app's deeplink URL
is included in this list. If it isn't, Dynamic will not perform the deeplink.
## How to set up your app for deeplinking
### Configuring your app's custom scheme
First, you should follow Flutter's guide on enabling deeplinking for your app
[here](https://docs.flutter.dev/ui/navigation/deep-linking).
### Registering your app's deeplink URL with Dynamic
Afterwards, head to your Dynamic dashboard's
[Security page](https://app.dynamic.xyz/dashboard/security) and enable "Whitelist Mobile Deeplink".
Click "Save changes".
Next, click the cog in this same section to open up the
[Mobile Deeplink URL page](https://app.dynamic.xyz/dashboard/security#mobile-deeplink-urls), and there
you must add your app's deeplink URL with the same custom scheme you configured in the previous step.
It might look something like this: `myappcustomscheme://`.
With this, you're done! Dynamic is ready to deeplink back to your app.
# Client Reference
The base package that provides access to the Dynamic Client, which can be extended with extension
packages.
## Functions
### `DynamicSDK.init`
```
DynamicSDK init({
required ClientProps props,
})
```
Initiates a singleton of a client object, which provides an interface to read state, trigger actions and
listen to events of our SDK.
## Objects
### `ClientProps`
The parameters that are acceptable by the [DynamicSDK.init](#DynamicSDK.init) method.
| Param | Type | Description |
| --------------- | -------------- | --------------------------------------------------------------------------- |
| `environmentId` | `string` | The ID of the environment of your dynamic application. |
| `apiBaseUrl` | `string?` | Allows you to override the URL to which the SDK will make its API requests. |
| `cssOverrides` | `string?` | Allows you to inject CSS into our UI modules (currently out of effect) |
| `appName` | `string?` | How you'd like your app to be named in our copies. |
| `appLogoUrl` | `string?` | A URL of the logo of your app. |
| `logLevel` | `LoggerLevel?` | Allows you to set the level of the logs to the console |
### `DynamicSDK.instance`
The base object of a client.
Since clients can be extended (and thus have their objects composed with those of the extensions), this is considered the most basic kind of client:
the one that is returned from the [DynamicSDK.init](#DynamicSDK.init) method.
It is composed of properties which we call modules. Note that all modules have state properties and **implement one or more streams**.
For every property that is a state, there will always be a stream named with
the same name plus `"Changes"`, which will be triggered when the property
changes value. We will omit these from the docs below as they are implicit.
#### `auth` module
Provides access to authentication related properties and methods of the SDK.
| Property | Type | Description |
| ------------------- | ----------------------- | ------------------------------------------------------------------------------- |
| `token` | `string \| null` | The JWT of the currently logged in user. |
| `authenticatedUser` | `UserProfile \| null` | The [UserProfile](/react-sdk/objects/userprofile) object of the logged in user. |
| `logout` | `Future logout()` | Allows you to log the current user out. |
#### `auth.email` submodule
Provides methods to send, re-send and verify OTPs to email.
| Property | Type | Description |
| ----------- | -------------------------------------- | ------------------------------------------------------------------------ |
| `sendOTP` | `Future sendOTP(String email)` | Sends an OTP token to the target email. |
| `resendOTP` | `Future resendOTP()` | Sends another OTP token to the same email as the last call to `sendOTP`. |
| `verifyOTP` | `Future verifyOTP(String token)` | Receives an OTP token and logs the user in if it is valid. |
#### `auth.sms` submodule
Provides methods to send, re-send and verify OTPs to phone numbers.
| Property | Type | Description |
| ----------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
| `sendOTP` | `Future sendOTP(PhoneData phoneData)` | Sends an OTP token to the target [PhoneData](/react-sdk/objects/phone-data). |
| `resendOTP` | `Future resendOTP()` | Sends another OTP token to the same phone number as the last call to `sendOTP`. |
| `verifyOTP` | `Future verifyOTP(String token)` | Receives an OTP token and logs the user in if it is valid. |
#### `auth.social` submodule
Provides a method to connect social accounts.
| Property | Type | Description |
| --------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `connect` | `Future connect({required SocialProvider provider, String? redirectPathname})` | Requests social connection to the provided [SocialProvider](/react-sdk/objects/social-provider). |
#### `sdk` module
Gives insight over the state of the SDK.
| Property | Type | Description |
| -------- | --------- | ----------------------------------------------------------- |
| `loaded` | `boolean` | Whether the SDK has loaded and is ready to handle requests. |
#### `ui` module
Provides access to Dynamic's UI.
| Property | Type | Description |
| ----------------- | ------ | ---------------------------------------------------------------------------------------------------------- |
| `showAuth` | `void` | Opens up Dynamic's authentication flow modal for your user to sign in. Automatically closes when finished. |
| `showUserProfile` | `void` | Opens up Dynamic's user profile modal, allowing your user to manage their profile and wallets. |
#### `wallets` module
Provides access to the user's wallets.
| Property | Type | Description |
| --------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `userWallets` | `BaseWallet[]` | The array of all the user's [wallets](/react-sdk/objects/wallet). |
| `primary` | `BaseWallet \| null` | The primary [wallet](/react-sdk/objects/wallet) of the user. |
| `setPrimary` | `Future setPrimary({required String walletId})` | Sets primary \[wallet]/sdks(/react-sdk/objects/wallet) of the user. |
| `getBalance` | `Future getBalance({required BaseWallet wallet})` | Returns the balance of a wallet. |
| `getNetwork` | `Future getNetwork({required BaseWallet wallet})` | Returns the network the wallet is connected to. |
| `signMessage` | `Future signMessage({required BaseWallet wallet, required String message})` | Signs a message with this wallet. |
| `switchNetwork` | `Future switchNetwork({required BaseWallet wallet, required Network network})` | Switches the wallet's network. |
#### `wallets.embedded` submodule
Allows interacting with and creating an embedded wallet for the current user.
| Property | Type | Description |
| -------------- | -------------------- | ------------------------------------------------------------------------------- |
| `hasWallet` | `boolean` | Whether the logged in user has an embedded wallet. |
| `getWallet` | `BaseWallet \| null` | Retrieves the embedded wallet of the current user, or null if it doesn't exist. |
| `createWallet` | `BaseWallet ` | Creates an embedded wallet for the current user. Throws if one already exists. |
# Web3dart Reference
The package allows integrating [web3dart](https://pub.dev/packages/web3dart) to our [client](/react-native/client).
## Extensions
### `class DynamicRpcService extends RpcService`
```
DynamicRpcService({required this.chainId, required this.requestChannel})
```
Provides a method to make an `ethRequest` through the DynamicSDK.
| Property | Type | Description |
| -------- | ----------------------------------------------------------- | -------------------------------------------- |
| `call` | `Future call(String function, [List? params])` | Make an `ethRequest` through the DynamicSDK. |
#### `class DynamicCredential extends CredentialsWithKnownAddress implements CustomTransactionSender`
```
DynamicCredential({required this.requestChannel, required String address})
```
Provides methods to sign methods and send transactions for web3 wallets.
| Property | Type | Description |
| ----------------- | ---------------------------------------------------------- | ------------------------------------------------------ |
| `signMessage` | `Future signMessage({required Uint8List payload})` | Signs a message with `EthRequestWithAddressParams` |
| `sendTransaction` | `Future sendTransaction(Transaction transaction)` | Sends a transaction with `EthRequestWithAddressParams` |
# Social Authentication
This feature requires you set up the deeplink URLs whitelist for your Dynamic
app. See [here](/flutter/deeplink-urls).
The dynamic client authentication module enables social authentication.
## Connecting to your users' social accounts
Notice that all social options you use in code **must be respectively
enabled** in your environment's dashboard settings first!
You can prompt a user to connect their social accounts with the `connect` method in our social module.
It returns a promise that resolves with no params on success, and rejects on failure.
Here's how you can connect a user's farcaster account, for example:
```dart
OutlinedButton(
onPressed: () async =>
await DynamicSDK.instance.auth.social.connect(
provider: SocialProvider.farcaster,
redirectPathname: '/login_screen',
),
child: const Text(
'Continue with Farcaster',
),
)
```
See [here](/react-sdk/objects/social-provider) for a list of all our supported social providers —
it will be the same list you see in your dashboard.
Not only can this method be used to sign a user in, but it is also able to
link a social account to an already signed in user.
## Adjusting app name in social connection dialogues
Social connection dialogues will derive the name of your app from the `appName` prop you used to initialize your client — read [here](/flutter/package-references/client#clientprops).
So in order for your app's name to be displayed correctly to your user when they are connecting their social accounts,
you need to provide this prop.
You can read more about the social module [here](/flutter/package-references/client#auth-social-submodule).
# Web3Dart Package
The Dynamic SDK Web3Dart is a dart package that provides a few methods for interacting with the Ethereum blockchain. It connects to an Ethereum through [web3dart](https://pub.dev/packages/web3dart) node to send transactions, interact with smart contracts and much more!
## Installation
Simply run the following in your terminal:
```
flutter pub add dynamic_sdk_web3dart
```
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):
```
dependencies:
dynamic_sdk_web3dart: ^0.0.1-alpha.2
```
## Main Features
### 1. Sign Message
```dart
Future signMessage({
required BaseWallet wallet,
required String message,
}) async {
final requestChannel = RequestChannel(
DynamicSDK.instance.messageTransport,
);
final signedMessage = await DynamicCredential.fromWallet(
requestChannel: requestChannel,
wallet: wallet,
).signMessage(
payload: Uint8List.fromList(
message.codeUnits,
),
);
return signedMessage;
}
```
### 2. Send Transaction
```dart
Future web3dartSendTransaction({
required BaseWallet wallet,
required String recipientAddress,
required String amount,
}) async {
final recipient = EthereumAddress.fromHex(
recipientAddress,
);
final network = await DynamicSDK.instance.wallets.getNetwork(
wallet: wallet,
);
final service = DynamicRpcService(
requestChannel: RequestChannel(
DynamicSDK.instance.messageTransport,
),
chainId: network.intValue() ?? 0,
);
final client = Web3Client.custom(service);
final gasPrice = await client.getGasPrice();
final maxFeePerGas =
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2);
final maxPriorityFeePerGas = gasPrice.getValueInUnitBI(EtherUnit.wei);
final amountInWei =
(double.parse(amount) * BigInt.from(10).pow(18).toDouble()).toInt();
final transaction = Transaction(
to: recipient,
maxGas: 21000,
gasPrice: gasPrice,
maxFeePerGas: EtherAmount.inWei(
maxFeePerGas,
),
maxPriorityFeePerGas: EtherAmount.inWei(
maxPriorityFeePerGas,
),
value: EtherAmount.inWei(
BigInt.from(amountInWei),
),
);
final requestChannel = RequestChannel(
DynamicSDK.instance.messageTransport,
);
final dynamicCredential = DynamicCredential.fromWallet(
requestChannel: requestChannel,
wallet: wallet,
);
final signedTransaction = await dynamicCredential.sendTransaction(
transaction,
);
return signedTransaction;
}
```
### 3. Sign Contract
```dart
Future writeContract({
required BaseWallet wallet,
required String message,
}) async {
final requestChannel = RequestChannel(
DynamicSDK.instance.messageTransport,
);
final dynamicCredential = DynamicCredential.fromWallet(
requestChannel: requestChannel,
wallet: wallet,
);
final network = await DynamicSDK.instance.wallets.getNetwork(
wallet: wallet,
);
final service = DynamicRpcService(
requestChannel: RequestChannel(
DynamicSDK.instance.messageTransport,
),
chainId: network.intValue() ?? 0,
);
final client = Web3Client.custom(service);
final gasPrice = await client.getGasPrice();
final maxFeePerGas =
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2);
final maxPriorityFeePerGas =
gasPrice.getValueInUnitBI(EtherUnit.wei) * BigInt.from(2);
final contract = DeployedContract(
ContractAbi.fromJson(TestContract.contractAbi, ''),
TestContract.deployedAddress,
);
final updateFunction = contract.function('update');
final transaction = Transaction.callContract(
contract: contract,
maxFeePerGas: EtherAmount.inWei(
maxFeePerGas,
),
maxPriorityFeePerGas: EtherAmount.inWei(
maxPriorityFeePerGas,
),
function: updateFunction,
parameters: [
message,
],
);
final signedContract = await dynamicCredential.sendTransaction(
transaction,
);
return signedContract;
}
```
You can read more about our client package [here](/flutter/package-references/client).
# Access Lists
## Overview
If you're launching a new site or want to restrict parts of your site to a predefined list of users, then access lists or waitlists are probably a high priority for you.
Leveraging gating with access lists through the Dynamic dashboard is a simple no-code option that gives you flexibility to define various and manage various lists easily. Through your dashboard, you can:
1. Restrict site access based on a list of emails, wallet addresses, or other unique identifiers.
2. Return a scope in the JWT of emails, wallet addresses, or other unique identifiers.
In our dashboard, uou can design precise gating rules by combining multiple elements such as various access access list with [NFT and token gates.](/gating/nft-token-gating)
## Usage
Find the [Access Control tab via the Configurations page](https://app.dynamic.xyz/dashboard/configurations#accesscontrol) of your developer dashboard.
Here you can create access lists based on emails or wallet addresses.
1. Click "Create new gate"
2. Name your gate
3. Select the gating method:
1. Allow Site Access - this option will block users from access your site (we won't generate a JWT) unless the criteria is met
2. Return scope - this option will not block users, but instead will return the JWT with a predefined scope if the user has met the defined criteria
4. Choose the type of identifier (email, wallet address, or other).
5. Enter the identifier.
1. You can also add an alias to more easily keep track of these users.
6. Click the "Add +" button to save the user to the list. Keep adding users as needed.
7. Save and enable the toggle when you're ready.
8. You're done!
You can create multiple lists to help keep track of different groups of users (ie, VIPs, beta users, internal users, etc). Note: a user only needs to be in one of the lists to pass.
## Working with scopes
To simplify working with scopes, we created a custom hook named useDynamicScopes. It allows you to easily checking the user scopes and to check for a specific scope on a user. To learn more about how to use this hook, see our docs [here](/react-sdk/hooks/usedynamicscopes).
## Customize the copy and button
You can customize the copy through props in our SDK by updating the `accessDeniedMessagePrimary` and `accessDeniedMessageSecondary`.
```tsx
```
You can also return a completely custom button by passing a JSX/TSX element to the `accessDeniedButton` prop. Here's an example to link to a contact page:
```tsx
window.open(`https://www.dynamic.xyz/talk-to-us`,'_blank'),
title: `Book a demo`
}
}}
>
```
The button should adhere to the following type:
```tsx
type AccessDeniedCustomButton = {
action?: () => void;
title: string;
};
```
# NFT/Token Gating
NFT/Token Gating is a feature of access control, just like access lists. It is a way of restricting access to those who have certain NFTs or tokens. For example, you can:
* restrict page access based on users NFTs or Tokens
* give user list of scopes based on users NFTs or Tokens
The feature works by defining what are called "gates". You can have multiple gates based on different contract addresses. Inside of a gate you can add multiple criteria which all needs to be fulfilled for gate to be applied. Remember that between different gates we are adding an `OR` statement. That allows you to create a complex logic based on `AND` and `OR` gates, as you'll see in the example section on this page!
### How to create a new gate?
Visit [the Access Control tab of the Dynamic dashboard](https://app.dynamic.xyz/dashboard/configurations#accesscontrol).
1. Click "Create new gate"
2. Set a name for your gate
3. Choose from two options:
1. Allow Site Access - users who fulfils the criteria won't be blocked from entering the site.
2. Return scope - users who fulfils the criteria will have a scope added to their `jwt` token.
4. Select chain on which you want to apply the gate. Right now we only allow adding gates for `Ethereum`, `Polygon`, `Optimism`, `Arbitrum`. If you need another, let us know!
5. Select the type of criteria for the gate:
6. Token - for token criteria you need to select a token you want for users to have and amount.
7. NFT - for NFT criteria you need to enter contract address of NFT you want for users to have.
8. Save and enable the toggle when you're ready.
9. Boom. You're done!
### Examples
**Block site for users without specific amount of tokens.**
Gate setup:
* User needs to have at least 1 SHIB to enter the site.
* User is blocked in Dynamic SDK:
**Add scope for users `jwt` when having specific NFT**
Gate setup:
* User needs to have specific NFT to have `admin` scope
* User has an `admin` scope added to the `jwt`
```Text json
{
...
"scope": "admin",
...
}
```
### Working with scopes
To simplify working with scopes, we created a custom hook named `useDynamicScopes`. It allows checking for users scopes and checking if one or many of them are in users `jwt` token. Check the docs [here](/react-sdk/hooks/usedynamicscopes).
# Go Live Checklist
* Review the [general settings](/developer-dashboard/general) and apply the correct ones for your project.
* Review the [security settings](/developer-dashboard/security) and make sure your CORS is set up correctly for your live deployment.
If you haven't done so already, before going to production we highly recommend turning on MFA for your account. You can do this in the [security settings](https://app.dynamic.xyz/dashboard/security).
Multi Wallet allows users to connect and handle multiple wallets at once, and
is a great UX boost for end users. Consider turning it on after reviewing the
[multi-wallet support section](/wallets/advanced-wallets/multi-wallet).
Make sure you're capturing all the user information you need via [Information
capture](/users/information-capture).
Make sure to choose the right Dynamic
[plan](https://app.dynamic.xyz/dashboard/admin#subscription) for your needs. -
All features are free on sandbox, but if you're using [any advanced
features](https://www.dynamic.xyz/pricing) you'll need to upgrade to a paid
plan.
There are two different modes to operate Dynamic in: Sandbox and Live. You can
learn more about them [here](/developer-dashboard/sandbox-vs-live). To switch
to live mode, you'll need to do so in the Dynamic dashboard. Remember that the
environment ID differs between sanbox and live, so make sure to also update
that in your own code.
If you haven't done so already, before going to production we highly recommend turning on MFA for your account. You can do this in the [security settings](https://app.dynamic.xyz/dashboard/security).
We test all the time, but it's also worth you running through your implementation from a few different angles, here are some common testing cases that might be useful to double check:
* Cross device
* Embedded wallets (if you are using)
* Transactions
* New vs existing user flow
Let us know how it's going! Drop a line to [hello@dynamic.xyz](mailto:hello@dynamic.xyz) or join
[Slack](https://dynamiccustomers.slack.com/) - if you like, we can give your
implementation a test and offer advice. We'll also check that your
implementation is ready for launch.
# Phantom Redirect
## Introduction
This guide will help you use the Phantom redirect-based connect feature on mobile. Instead of deeplinking into the Phantom in-app browser, your users can enjoy a redirect-based connection, where they are automatically redirected between the app and their wallet to faciliate the connect, sign and additional method calls.
## Tutorial
### `mobileExperience` prop
Make sure you set `mobileExperience` to `redirect` on your DynamicContextProvider:
```JSX
```
With just this code, your users will connect with the redirect-based approach. If you want to learn how to make method calls (like `signAndSendTransaction`) and access the result, read on
### Signer methods
The redirect-based approach makes it a little trickier to access the result, and soon we will offer an easy-to-use hook to do so. For now, you will need this code in your app:
```JSX
import { useState, useEffect } from 'react';
import {
VersionedTransaction,
Transaction,
} from '@solana/web3.js';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { ISolana } from '@dynamic-labs/solana-core';
import {
isPhantomRedirectConnector,
SignAndSendTransactionListener,
} from '@dynamic-labs/wallet-connector-core';
const useSignAndSendTransaction = () => {
const { primaryWallet } = useDynamicContext();
const [signature, setSignature] = useState(undefined);
const [errorCode, setErrorCode] = useState(undefined);
const [errorMessage, setErrorMessage] = useState(
undefined,
);
useEffect(() => {
if (!isPhantomRedirectConnector(primaryWallet?.connector)) return;
const handler: SignAndSendTransactionListener = (response) => {
if (response.signature) {
setSignature(response.signature);
} else {
setErrorCode(response.errorCode);
setErrorMessage(response.errorMessage);
}
};
primaryWallet.connector.on('signAndSendTransaction', handler);
return () => {
if (!isPhantomRedirectConnector(primaryWallet?.connector)) return;
primaryWallet.connector.off('signAndSendTransaction', handler);
};
}, [primaryWallet?.connector]);
const execute = async (transaction: Transaction | VersionedTransaction) => {
if (!primaryWallet) return;
const signer = await primaryWallet.getSigner();
await signer.signAndSendTransaction(transaction);
};
return { errorCode, errorMessage, execute, signature };
};
```
You can render this hook in your app, and call `execute` on a button you render. This will trigger a redirect to the user's wallet for signing, and if they accept the transaction, they will be redirected back to your app. Upon your app re-loading, this hook should be rendered again, and it's state values will be populated – in this case the `signature` will contain the signature of the transcation.
# NextAuth & Dynamic
## Pre-requisites
* Cloned [the nextAuth example repo](https://github.com/nextauthjs/next-auth-example) and installed dependencies (You can also see the end result after adding Dynamic [here](https://github.com/dynamic-labs/nextAuth-example)).
## Steps
### Add the right env variables
You'll need to define two environment variables in your `.env.local` file:
```bash
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=
NEXT_DYNAMIC_BEARER_TOKEN=
```
You'll be able to find both in [the SDK & API Keys page of the Dynamic dashboard](https://app.dynamic.xyz/dashboard/developer/api). The first will already be generated for you but for the API key, you'll need to generate your own via the UI on that page.
Make sure you add the values of each variable to the `.env.local` file, and you're good to go.
### Add the Dynamic Provider
We are going to use the Dynamic UI on the client, so the first thing we'll want to do is add an initialize the DynamicContextProvider and then we can use the DynamicWidget UI.
First make sure you have the appropriate dependencies installed:
```bash
npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum
```
Next we will add the provider. Normally you would add the provider to one of your high level component files directly, wrapping everything else but in Next, this will likely cause some client - server issues especially because we're going to be utalizing callbacks, so we'll handle this in two ways:
Firstly, we'll create a file to export what we need from Dynamic and then we'll import that file in the client side code:
```js
// app/lib/dynamic.ts
"use client";
export * from "@dynamic-labs/sdk-react-core";
export * from "@dynamic-labs/ethereum";
```
Secondly, we'll create a wrapper for the provider itself:
```js
// app/components/dynamic-wrapper.ts
"use client";
import { DynamicContextProvider } from "../lib/dynamic";
import { EthereumWalletConnectors } from "../lib/dynamic";
export default function ProviderWrapper({ children }: React.PropsWithChildren) {
return (
{children}
);
}
```
Note that we are using only EthereumWalletConnectors, but you can add any of the other connectors you want to use.
Now we can add the wrapper to our `layout.tsx` file:
```js
// app/layout.tsx
import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Footer from "@/components/footer";
import Header from "@/components/header";
import ProviderWrapper from "@/components/dynamic-wrapper";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "NextAuth.js Example",
description:
"This is an example site to demonstrate how to use NextAuth.js for authentication",
};
export default function RootLayout({ children }: React.PropsWithChildren) {
return (
{children}
);
}
```
### Add the DynamicWidget
You don't need to add the widget at the same level as the provider, you can place this anywhere. For this example we'll add it to the header:
```js
// app/components/header.tsx
import { MainNav } from "./main-nav";
import UserButton from "./user-button";
import { DynamicWidget } from "../lib/dynamic";
export default function Header() {
return (
);
}
```
Now we're almost done with the client side. We'll need to somehow send the JWT that Dynamic returns on login to our server functions so that we can validate it and create a session. To do this we'll use the `events` which Dynamic provides but let's come back to that and first add the server side code.
### Define the JWT decoding
NextAuth needs to know how to decode and validate the JWT which Dynamic sends back. To do this we'll create a custom JWT decoder inside a new helper file:
```js
// app/lib/authHelpers.ts
import jwt, { JwtPayload, Secret, VerifyErrors } from "jsonwebtoken";
export const validateJWT = async (
token: string
): Promise => {
try {
const decodedToken =
((await new Promise()) < JwtPayload) |
(null >
((resolve, reject) => {
jwt.verify(
token,
getKey,
{ algorithms: ["RS256"] },
(
err: VerifyErrors | null,
decoded: string | JwtPayload | undefined
) => {
console.log("decoded the jwt");
if (err) {
reject(err);
} else {
// Ensure that the decoded token is of type JwtPayload
if (typeof decoded === "object" && decoded !== null) {
resolve(decoded);
} else {
reject(new Error("Invalid token"));
}
}
}
);
}));
return decodedToken;
} catch (error) {
console.error("Invalid token:", error);
return null;
}
};
```
You'll see that the above function depends on a few things, one of which is the external jsonwebtoken library. We'll need to install this:
```bash
npm install jsonwebtoken
```
Next we'll need to define the `getKey` function which is used to fetch the public key which you can use to decode the JWT. This function will make an API call to Dynamic. We'll add this to the same file:
```js
// app/lib/authHelpers.ts
export const getKey = (
headers,
callback: (err: Error | null, key?: Secret) => void
): void => {
console.log("calling getKey");
// Define the options for the fetch request
const options = {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.NEXT_DYNAMIC_BEARER_TOKEN}`,
},
};
// Perform the fetch request asynchronously
fetch(
`https://app.dynamicauth.com/api/v0/environments/${process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID}/keys`,
options
)
.then((response) => {
return response.json();
})
.then((json) => {
const publicKey = json.key.publicKey;
const pemPublicKey = Buffer.from(publicKey, "base64").toString("ascii");
callback(null, pemPublicKey); // Pass the public key to the callback
})
.catch((err) => {
console.error(err);
callback(err); // Pass the error to the callback
});
};
```
With this in place, there are just two steps left. Firstly we'll need to adapt the NextAuth configuration to use [the CredentialsProvider](https://next-auth.js.org/configuration/providers/credentials) so the JWT works, and then we'll need to trigger everything correct when a user logs in.
### Update the NextAuth configuration
You can copy the below code and paste it over the full existing auth.ts file in the demo repo:
```js
// ./auth.ts
import NextAuth from "next-auth";
import type { NextAuthConfig } from "next-auth";
import Credentials from "@auth/core/providers/credentials";
import { validateJWT } from "./authHelpers";
type User = {
id: string;
name: string;
email: string;
// Add other fields as needed
};
export const config = {
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
},
providers: [
Credentials({
name: "Credentials",
credentials: {
token: { label: "Token", type: "text" },
},
async authorize(
credentials: Partial>,
request: Request
): Promise {
const token = credentials.token as string; // Safely cast to string; ensure to handle undefined case
if (typeof token !== "string" || !token) {
throw new Error("Token is required");
}
const jwtPayload = await validateJWT(token);
if (jwtPayload) {
// Transform the JWT payload into your user object
const user: User = {
id: jwtPayload.sub, // Assuming 'sub' is the user ID
name: jwtPayload.name || "", // Replace with actual field from JWT payload
email: jwtPayload.email || "", // Replace with actual field from JWT payload
// Map other fields as needed
};
return user;
} else {
return null;
}
},
}),
],
callbacks: {
authorized({ request, auth }) {
const { pathname } = request.nextUrl;
if (pathname === "/middleware-example") return !!auth;
return true;
},
},
} satisfies NextAuthConfig;
export const { handlers, auth, signIn, signOut } = NextAuth(config);
```
### Trigger the JWT validation on login
We'll need to access the JWT which Dynamic sends back after a user has logged in so that we can co-ordinate with NextAuth. To do this we'll use one of the events which Dynamic provides. Back in the `dynamic-wrapper.ts` file we'll adjust the settings object passed to DynamicContextProvider as a prop to the following:
```js
// app/components/dynamic-wrapper.ts
...
{
const authToken = getAuthToken();
const csrfToken = await getCsrfToken();
fetch("/api/auth/callback/credentials", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `csrfToken=${encodeURIComponent(
csrfToken
)}&token=${encodeURIComponent(authToken)}`,
})
.then((res) => {
if (res.ok) {
console.log('LOGGED IN', res);
// Handle success - maybe redirect to the home page or user dashboard
} else {
// Handle any errors - maybe show an error message to the user
console.error("Failed to log in");
}
})
.catch((error) => {
// Handle any exceptions
console.error("Error logging in", error);
});
},
},
}}
>
...
```
Note that we're using the `getCsrfToken` function which NextAuth provides. This is important because NextAuth uses CSRF tokens to prevent CSRF attacks.
### Token Scopes
A JWT token supports the concept of scopes, which are used to define the permissions that the token has. Dynamic uses various scopes to identify limitations for a token.
The most common and important scope value is `requiresAdditionalAuth` which signifies that the token requires MFA to be completed before the token is considered valid and the user is fully authenticated.
Our SDK handles this for the frontend, but for the backend you will need to check this scope and handle it accordingly.
To do this, you can add a check for the `requiresAdditionalAuth` scope in the `authorize` function in the `auth.ts` file:
```js
// ./auth.ts
...
async authorize(
credentials: Partial>,
request: Request
): Promise {
const token = credentials.token as string; // Safely cast to string; ensure to handle undefined case
if (typeof token !== "string" || !token) {
throw new Error("Token is required");
}
const jwtPayload = await validateJWT(token);
if (jwtPayload) {
// Transform the JWT payload into your user object
const user: User = {
id: jwtPayload.sub, // Assuming 'sub' is the user ID
name: jwtPayload.name || "", // Replace with actual field from JWT payload
email: jwtPayload.email || "", // Replace with actual field from JWT payload
scopes: jwtPayload.scopes || [], // Add the scopes to the user object
// Map other fields as needed
};
return user;
} else {
return null;
}
}
...
```
Alternatively, you can return `null` if you do not want to handle the token with the `requiresAdditionalAuth` scope.
### Run the example
```bash
npm run dev
```
You should now see a "Connect your wallet" button in the header, which you can use to log in with a wallet etc. Once you've logged in you should see "LOGGED IN" in the browser console.
### Going further
You'll see in auth.js that we are assigning certain JWT fields to a user object. You can add any fields you want to this object, and then access them in your pages via the `useSession` hook.
# Dynamic in PWA
In this guide we are going to setup a PWA using Dynamic and Vite, React & Typescript.
### Full code example
You can find the full code from this example [here](https://github.com/dynamic-labs/react-pwa).
## Pre-requisites
* Node installed
* Boilerplate project created with Vite
```
npm create vite@latest dynamic-pwa -- --template react-ts
```
## Dynamic Setup
With a basic project in place, we can now setup the Dynamic React SDK
### Install Dynamic Packages
```
npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum
```
### Add the DynamicContextProvider
In the `src/main.tsx` file we can add the DynamicContextProvider with an environment ID
```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
// Dynamic SDK imports
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
ReactDOM.createRoot(document.getElementById("root")!).render(
);
```
### Add the DynamicWidget
Over in the App file, we are going to use the DynamicWidget for users to be able to authenticate using the Dynamic UI
Now, in the `src/App.tsx` file, we can add the DynamicWidget component
```tsx
import { DynamicWidget } from "@dynamic-labs/sdk-react-core";
function App() {
return (
);
}
export default App;
```
Great, now Dynamic is fully added and we can get start with the PWA setup!
## PWA Setup
### Install Dependencies
```
npm install -D vite-plugin-pwa
```
### Setup PWA
For this app to became a PWA we will need to generate a manifest file with some extra icons that the OS will use.
First, we can update our `vite.config.ts` file with vite-plugin-pwa:
```
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({
publicDir: "public",
plugins: [
react(),
VitePWA({
registerType: "autoUpdate",
devOptions: {
enabled: true,
},
includeAssets: [
"logo.png",
"apple-touch-icon.png",
"mask-icon.svg",
"icon.svg",
],
manifest: {
name: "My Awesome DApp",
short_name: "Dynamic PWA",
description: "My Awesome DApp description",
theme_color: "#ffffff",
icons: [
{
src: "logo.png",
sizes: "192x192",
type: "image/png",
},
{
purpose: "any maskable",
sizes: "260x260",
src: "/apple-touch-icon.png",
type: "image/png",
},
],
},
}),
],
});
```
We also need to add some images like `logo.png` and `apple-touch-icon.png` to our `public` to serve as the PWA icons.
Now we have to specify in the `index.html` file some basic information about the PWA:
So, add the following to the `head` tag
```
Dynamic PWA
```
## Add an Install Button
Let's add an install button so users can install our PWA.
We can add a hook to `src/useInstallPWA.ts` that allows us to trigger the PWA installation:
```ts
import { useCallback } from "react";
let deferredPrompt: Event | null = null;
const handleBeforeInstallPrompt = (event: Event) => {
event.preventDefault();
deferredPrompt = event;
};
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
export const useInstallPWA = () =>
useCallback(() => {
if (deferredPrompt) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(deferredPrompt as any).prompt();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(deferredPrompt as any).userChoice.then(() => {
deferredPrompt = null;
});
}
}, []);
```
Then we can use useInstallPWA in the `src/main.tsx` file to prompt the user to install the PWA:
```tsx
import { DynamicWidget } from "@dynamic-labs/sdk-react-core";
import { useInstallPWA } from "./useInstallPWA";
function App() {
const installPWA = useInstallPWA();
return (
<>
>
);
}
export default App;
```
Perfect, now you can serve your DApp under `https` and install the PWA on your computer or phone, then authenticate with Dynamic!
# Create-web3-dapp & Dynamic
[Create Web3 Dapp](https://github.com/alchemyplatform/create-web3-dapp) is an excellent tool created by our friends over at Alchemy. It is described as "an npx tool that allows developers to create web3 applications in \~4 minutes."
We've created a version of a create-web3-dapp setup which has Dynamic integrated so you can start building apps with beautiful auth in no time!
Instructions all in the README: [https://github.com/dynamic-labs/create-web3-dapp-dynamic](https://github.com/dynamic-labs/create-web3-dapp-dynamic)
# Enhanced analytics with Bello
## Introduction
[Bello](https://bello.lol/) offers you analytics to help you understand your users and their behavior. You can use this data to make informed decisions about your app and improve your user experience.
To get set up, all you need to do it run an export of wallet addresses from Dynamic and upload it to Bello. Upon upload, Bello filters the data, retaining only EVM-compatible addresses and discarding any non-relevant fields such as emails, ensuring that only blockchain-relevant information is processed.
Upon launching a search, you as a free user gain access to features such as:
* "Assets in Common," illuminating shared community memberships and fostering an understanding of collective affiliations.
* "NFT Interests," which employs AI to tag users' collection habits, offering insights into their NFT collecting patterns.
* A "Recently Purchased" segment, highlighting the most recent community NFTs acquired by a user.
* "Web3 Social" insights, providing a comprehensive look at a user's interactions across platforms like Lens, Farcaster, and Warpcast.
To delve into more advanced insights and obtain a higher level of search functionality, users are encouraged to explore a 7-day free trial of premium services. This trial unlocks over 50 curated insights and tools, including:
* Advanced segmentation tools for sifting through user data, revealing key information such as percentages of users with specific Twitter handles, wallet ages, net worth, and holdings in NFTs, ERC20s, and POAPs, to name a few.
* Wallet messaging capabilities powered by XMTP, facilitating direct engagement with holders.
* Detailed dashboards that categorize AI-labeled interests, POAP holders, and more.
* The capacity to search across more than 20,000 addresses and over three cohorts, providing a broad view of purchasing behaviors, on-chain activity times and days, and recent NFT purchases, among others.
These features offer an in-depth exploration of user engagement and preferences, enhancing the ability to tailor strategies and interactions within the Web3 ecosystem.
Use the keyword DYNAMIC for a 20% discount on premium Bello plans.
OK, let's get started! This is a simple two step process, as outlined above:
## Export wallet addresses from Dynamic
We offer both a drag and drop method (UI only, no code) as well as a programmatic method
Using [the `getEnvironmentUsers` endpoint](/api-reference/endpoints/users/getEnvironmentUsers), you can fetch all users for your environmentment like so:
```javascript
const fetchUsers = async (bearer, environmentId) => {
const options = {
method: "POST",
headers: {
Authorization: `Bearer ${bearer}`,
"Content-Type": "application/json",
},
};
try {
const response = await fetch(
`https://app.dynamicauth.com/api/v0/environments/${environmentId}/users`,
options
);
return await response.json();
} catch (err) {
console.error(err);
}
};
```
You can then loop through the users and extract the wallet addresses:
```javascript
const extractWallets = (users) => {
const addresses = [];
users.forEach((user) => {
if (user.wallets && user.wallets.length > 0) {
user.wallets.forEach((wallet) => {
if (wallet.publicKey) {
addresses.push(wallet.publicKey);
}
});
}
if (user.verifiedCredentials && user.verifiedCredentials.length > 0) {
user.verifiedCredentials.forEach((credential) => {
if (credential.address) {
addresses.push(credential.address);
}
});
}
});
return addresses;
};
```
## Save wallets as CSV
If you're using Node, you can use the `fs` module to write the wallet addresses to a CSV file:
```javascript
const fs = require("fs").promises;
const path = require("path");
const writeToCSV = async (data, name) => {
try {
const csvContent = data.join("\n");
const filePath = path.join(__dirname, name + ".csv");
await fs.writeFile(filePath, csvContent, "utf8");
console.log("CSV file has been written successfully");
} catch (err) {
console.log("Error writing CSV file", err);
}
};
```
## Put it all together
Now we can chain our functions together to export the wallet addresses and save them as a CSV:
```javascript
const exportWalletList = async (bearer, environmentId) => {
const { users } = await fetchUsers(bearer, environmentId);
const wallets = await extractWallets(users);
return await writeToCSV(wallets, "wallets");
};
exportWalletList("BEARER_TOKEN", "ENVIRONMENT_ID");
```
## Upload wallet addresses to Bello
Once you have your CSV file, you can upload it to Bello through the dedicated Portal. You can access the portal by clicking the button below:
## Beyond the basics
Once you have your dashboard set up, you can start to explore the data and gain insights into your users' behavior. Here are some ideas to get you started:
* [Metrics & Insights](https://help.bello.lol/en/collections/7887705-analytics-dashboards)
* [Build segments](https://help.bello.lol/en/collections/7887701-segment)
* [Broadcast messages](https://help.bello.lol/en/collections/7887702-broadcast-messages)
# Generate an multi-chain embedded wallet from a Farcaster frame
## Introduction
This guide shows you how to generate an embedded wallet in both Ethereum and Solana from a Farcaster frame based on an email address input.
We will use a frame building library in this example, specifically Frog.fm however you can use any frame builder you like.
You can find a full demo of this implementation
[here](https://warpcast.com/~/developers/frames?url=https%3A%2F%2Ffrog-frames-three.vercel.app%2Fapi)
and the source code for the demo
[here](https://github.com/dynamic-labs/frog-frames/blob/main/dynamic-embedded/api/index.tsx).
## Implementation
### Step 1: Configure your Dynamic app
### Step 2: Bootstrap your frame
First, you'll need to create a new frame template Frog.fm, they provide instructions on how to do this [here](https://frog.fm/getting-started). Specifically, they allow you to create a new frame and deploy to vercel with a single command. [See here for more info](https://frog.fm/getting-started#bootstrap-via-vercel).
### Step 3: Check out the Dynamic API endpoint
You'll be utilizing our new API endpoint built especially for frames. This endpoint is called `POST https://app.dynamicauth.com/api/v0/environments/{environmentId}/embeddedWallets/farcaster` and it takes in a JSON object with the following properties:
The full reference for the endpoint can be found [here](/api-reference/endpoints/wallets/createEmbeddedWalletFromFarcaster)
```
{
"chains": [
"EVM"
],
"email": "jsmith@example.com",
"fid": 123
}
```
The `chains` property is an array of the chains you want to support with the wallet. Currently, the supported chains are `EVM` and `SOL`.
The `email` property is the email address of the user you want to generate the wallet for, it must be a valid email.
The `fid` property is the Farcaster user ID for whom you want to use to generate the wallet. You can find the FID for a user from within the Warpcast profile tab by clicking on the three dots and choosing "about".
### Step 4: Define our creation function a frame with an email input
In your frame code, you'll need to define a function that will be called when the user submits their email address. This function will call the Dynamic API endpoint to generate the wallet and then display the wallet to the user.
```tsx
import { configDotenv } from "dotenv";
import { ChainEnum } from "@dynamic-labs/sdk-api/models/ChainEnum";
import { UserResponse } from "@dynamic-labs/sdk-api/models/UserResponse";
configDotenv();
const key = process.env.KEY;
const environmentId = process.env.ENVIRONMENT_ID;
let newWallets: string[];
const createEmbeddedWallet = async (
email: string,
fid: number,
chains: ChainEnum[]
) => {
console.log("Creating embedded wallets for", email, fid, chains);
const options = {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
fid,
chains,
}),
};
const response = await fetch(
`https://app.dynamic-preprod.xyz/api/v0/environments/${environmentId}/embeddedWallets/farcaster`,
options
).then((r) => r.json());
console.log(response);
newWallets = (response as UserResponse).user.wallets.map(
(wallet: any) => wallet.publicKey
);
return newWallets;
};
```
Note that the `key` should be a Dynamic.xyz API key. You can get one from the
following page: [https://app.dynamic.xyz/dashboard/developer/api](https://app.dynamic.xyz/dashboard/developer/api) (you can find
your ENVIRONMENT\_ID on the same page)
### Step 5: Call the creation function when the user submits their email
For this we will need to define our frame UI and call the `createEmbeddedWallet` function on submit. it will look like this:
```tsx
import { Button, Frog, TextInput } from "frog";
import { handle } from "frog/vercel";
app.frame("/", async (c) => {
const { frameData, inputText, status, buttonValue } = c;
const isValidEmail = inputText
? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputText)
: false;
const fid = frameData?.fid;
let error = status != "initial" && (!isValidEmail || !fid);
if (
!error &&
status != "initial" &&
isValidEmail &&
inputText &&
fid &&
buttonValue === "submit"
) {
try {
newWallets = await createEmbeddedWallet(inputText, fid, [
ChainEnum.Sol,
ChainEnum.Evm,
]);
// Add any additional logic specific to your frame integration here. e.g. airdrop an NFT to the embedded wallet, etc.
} catch (e) {
error = true;
}
}
return c.res({
image: (
),
intents: [
,
,
],
});
});
export const GET = handle(app);
export const POST = handle(app);
```
### Step 6: Test the frame
You can now test the frame by running it locally and entering an email address. You should see the wallet address displayed on the screen after you submit the email address.
### Step 7: Deploy the frame
With Frog.fm you can deploy quickly using Vercel. They have a great guide for it [here](https://frog.fm/platforms/vercel).
### Step 8: Connect to your dApp
Now that you've created embedded wallets for your users, which are linked to their email and Farcaster ID so you can set up Dynamic with your dApp, and the user can log into your dApp with either their email or Farcaster account to access their embedded wallets!
# Farcaster Overview
## Introduction to Farcaster
[Farcaster](https://www.farcaster.xyz/) is a protocol for building sufficiently decentralized social networks. Clients (such as [Warpcast](https://warpcast.com/)) build on top of Farcaster to provide the user interface and user experience for interacting with the decentralized network.
This is a beautiful relationship because the underlying social graph is shared by all the clients. This means that users can switch between clients without losing their social graph. It also means that clients can be built by different teams, each focusing on different aspects of the user experience.
Farcaster is not a blockchain. It is a protocol that stores the social graph on a blockchain, currently Optimism. If you're wondering what exactly it means when Farcaster defines itself as "sufficiently decentralized", Varun Srinivasan, one of the co-founders of Farcaster [describes it as](https://www.varunsrinivasan.com/2022/01/11/sufficient-decentralization-for-social-networks):
"A social network achieves sufficient decentralization if two users can find each other and communicate, even if the rest of the network wants to prevent it. This implies that users can always reach their audience, which can only be true if developers can build many clients on the network. If only one client existed, it could stop users from communicating. Achieving this only requires three decentralized features: the ability to claim a unique username, post messages under that name, and read messages from any valid name."
Due to this decentralized and permissionless nature, Farcaster has been able to introduce a number of features that are not possible in traditional social networks, one being Frames which you can think of as "mini-apps". For example, you can purchase items directly from your social media feed or tip creators directly from your feed.
## Farcaster + Dynamic
Our mission is to create incredible onboarding experiences for anyone interacting with Web3. We believe that Farcaster is a key piece of the Web3 puzzle, and we are excited to be working with the Farcaster team to bring the best possible onboarding experience to users of Farcaster. You can do this in a number of different ways:
### Sign in with Farcaster
If you want to allow your users to sign into a dapp using their Farcaster identity, similar to how you would with Google or other, this is as easy as a toggle in the dashboard. You can learn about that [here](/guides/integrations/farcaster/sign-in-with-farcaster).
### Enable Write Access
You may want to create certain actions in Farcaster on behalf of your users. For example, you may want to post a message on their behalf. For this you will need your user to go through a write access flow, and you can learn about how to do that [here](/guides/integrations/farcaster/farcaster-write-access).
### Wallets in Frames
If you are building a Frame and want to allow users to interact in a way that requires a wallet, you can generate a wallet from just an email address on multiple chains. You can learn about that [here](/guides/integrations/farcaster/embedded-wallet-frame).
# Enable Write Access to Farcaster
## Introduction
This tutorial will help you enable Sign in with Farcaster in your application, as well as enabling write access to Farcaster on behalf of that user.
We've partnered with the wonderful team at Neynar on this use case who have implemented Dynamic as part of Sign in with Neynar. This means three things for you:
* it's completely plug and play, drop in the code snippet and ship
* warps are already sponsored by Neynar
* you get the best of Dynamic and Neynar in one go
## Video walkthrough
## Live demo
You can see this flow in action on the Neynar demo: [https://demo.neynar.com/](https://demo.neynar.com/). You can find the code for this demo [here](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar).
## Step By Step Walkthrough
We recommend referring to [the Neynar guide](https://docs.neynar.com/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) for the most up-to-date information.
### Configure Neynar
Go to [the Neynar Developer Portal settings tab](https://dev.neynar.com/app) and update the following:
Name - Displayed to the user in Step 3.
Logo URL - Displayed to the user in Step 3. Use a PNG or SVG format.
Authorized origins - Authorized origins are the HTTP origins that host your web application. e.g. [https://demo.neynar.com](https://demo.neynar.com) This is required to pass user credentials back to your application securely. This cannot contain wildcards or IP addresses.
### Drop in the code snippet
Add the following code snippet to your html, replacing `YOUR_NEYNAR_CLIENT_ID` with your Neynar client ID. You can find this in [the Neynar Developer Portal App tab](https://dev.neynar.com/app):
```html
```
### Handle callback
Once the user is authenticated and a signer has been authorized by the user, the signer\_uuid and fid will be passed in via the data object in the callback function.
signer\_uuid is unique to your app and is used to write to Farcaster on behalf of the user (same uuid format)
fid: This is the unique Farcaster identifier for the user e.g. 6131
Store the signer\_uuid securely on your backend or the browser’s local storage, it's not meant to be exposed to the user or anyone other than you. Switch the app to the logged-in state for that Farcaster user.
### Ship it!
You’re all set! The user is now logged in and you should use the fid for any [read APIs](https://docs.neynar.com/docs/what-does-vitalikeths-farcaster-feed-look-like) and the signer\_uuid to do any [write actions](https://docs.neynar.com/docs/liking-and-recasting-with-neynar-sdk) on behalf of the user in your App.
# Sign in with Farcaster
## Introduction
This tutorial will guide you through the process of enabling read-only access to Farcaster using the Dynamic SDK (if you need write access, see [this guide](/guides/integrations/farcaster/farcaster-write-access)).
If you enable only social signup, and only Farcaster as a provider, the user will see the QR code immediately. If there are other providers enabled/other login methods, Farcaster will appear as a button, which will then open the QR code. Both UIs are shown below:
## Video walkthrough
## Step By Step Walkthrough
### Enable Farcaster Signup
Visit [Log in & User Profile page in the dashboard](https://app.dynamic.xyz/dashboard/log-in-user-profile) and under "Social", enable Farcaster.
### Configure your Dynamic app
You don't need to do anything extra in your Dynamic app to enable Farcaster sign-in. The Dynamic SDK will automatically handle the sign-in process for you.
The easiest way of seeing the UI is to use the DynamicWidget UI component:
```tsx
import { DynamicContextProvider, DynamicWidget } from "@dynamic-labs/sdk-react";
export default function App() {
return (
);
}
```
You're done! Your users can now sign in with Farcaster.
## Further resources
* [Enable write access to Farcaster](/guides/integrations/farcaster/farcaster-write-access)
* [Create embedded wallet from Farcaster Frame](/guides/integrations/farcaster/embedded-wallet-frame)
# How to Use Dynamic in a Safe App
This is an enterprise feature and access is gated. Please let us know [in Slack](https://dynamic.xyz/slack) if you'd like access.
## Implementation
In this guide, you'll learn how to integrate Dynamic with the Safe App environment. By using Dynamic’s Safe EVM wallet connector, you can enable seamless wallet integration for Safe users. Whether your app is set to connect-only mode or requires a Sign-In with Ethereum (SIWE) signature, Dynamic’s SDK allows you to quickly and securely connect users. Follow the steps below to get started and deploy your app on Safe.
### Step 1: Install Safe EVM Wallet Connector
Ensure you're using SDK version 3.4.3 or higher.
Install the Dynamic Safe EVM wallet connector with the following command:
```bash
npm install @dynamic-labs-connectors/safe-evm@3.0.0-alpha.0
```
### Step 2: Import the Safe Connector
In your project, import the `SafeEvmWalletConnectors` from the Dynamic Labs Safe EVM package:
```javascript
import { SafeEvmWalletConnectors } from "@dynamic-labs-connectors/safe-evm";
```
### Step 3: Add the Connector to Dynamic
Add the Safe EVM wallet connector into your `DynamicContextProvider` as a `walletConnectors` option:
```javascript
{ /* Your app code here */ }
```
### Step 4: Update `manifest.json`
Update the `manifest.json` file in your project (usually located in `public/manifest.json`) to include an app name, description, and icon.
Example:
```json
{
"short_name": "Safe App Test",
"name": "Safe App Test",
"description": "This is a safe app test",
"iconPath": "https://demo.dynamic.xyz/logo.png",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
```
Note: you can see the full guide [here](https://help.safe.global/en/articles/145503-how-to-create-a-safe-app-with-safe-apps-sdk-and-list-it).
### Step 5: Testing Your Safe App
To test your Safe app, follow these steps:
1. Navigate to [Safe Apps](https://app.safe.global/apps).
2. Add your app as a custom app by pasting the app’s URL.
3. Your app will automatically log you in if you're using connect-only mode, or it will ask for a Sign-In with Ethereum (SIWE) signature if you're using connect-and-sign mode.
That's it! Your Dynamic-powered Safe App should now be ready to use.
## Troubleshooting
### Safe not auto-connecting/asking for signature
This can be because of two things:
1. You have not been granted access to the feature on the environment ID you're using, please let us know [in Slack](https://dynamic.xyz/slack).
2. You have mismatched Dynamic packages installed - if you have a V4 of the safe-evm connector lib, your other packages must also be on V4.
# Shield3 & Dynamic Integration
Public Beta
## Introduction
Shield3 provides automated security & compliance tools for developers to protect user transactions through a customizable policy engine delivered as a custom RPC.
Dynamic provides onboarding and login flows with a high level of customization and a focus on developer tooling.
With a couple of simple steps, customers of Dynamic can add custom rules to all transactions that are facilitated by their application through Shield3.
[Here's a short video walkthrough of the process and the result!](https://www.loom.com/share/8c344d27546747cf977d2a1bab583b51?sid=80bc7b14-1982-4357-a6e5-3198c177c629).
## Tutorial
### Step 1 - Get an API Key
Option 1. (Recommended)
Sign up at [https://www.shield3.com/auth/sign-in](https://www.shield3.com/auth/sign-in) to get a free API key & RPC URL
Get your API Key & Configure Policies
Option 2.
Use this public shared key to test it out: [https://rpc.shield3.com/v3/UlihwLJtYY9bWgkuLA6KS6liL6iTpXKqhWvPBl80/0x01/rpc](https://rpc.shield3.com/v3/UlihwLJtYY9bWgkuLA6KS6liL6iTpXKqhWvPBl80/0x01/rpc)
### Step 2 - Change the RPC URL
On the Dynamic.xyz dashboard go to [the EVM Configuration tab](https://app.dynamic.xyz/dashboard/configurations#evm).
Toggle on Ethereum Mainnet, then click the down arrow on the far right.
The section will expand to show you the current RPC URL used and you can paste in the RPC URL you received in step 1 here.
to test our Goerli change the 0x01 in the URL to 0x05, or select Goerli on the
Shield3 Dashboard
### Step 3 - Test it out!
You can now run your Dynamic Implementation normally and send a transaction while connected to Ethereum.
The easiest way to get up and running if you haven’t already integrated
Dynamic already is via [the Quickstart](/quickstart) or [the Sample
Apps](/example-apps). You should just make sure you have [the
EthereumWalletConnector
implemented](/react-sdk/wallet-connectors#implementation).
The transaction should succeed if there are no violations and will show up on your Shield3 Dashboard. If any issues cause the transaction to be blocked or flagged you will receive an error message which you can learn more about [here](/guides/integrations/shield3#custom-error-codes).
Tell us how it went! Survey Link Telegram or email [info@shield3.com](mailto:info@shield3.com)
## All about policies
1 - Unverified Contracts (Block | Flag | Alert)
If enabled, interactions with unverified contracts that cannot be decoded are forbidden. User can configure this policy to result in block, flag for MFA, or permit with an alert.
2 - OFAC SDN Block Native and ERC20 Transactions
Block native transfers, ERC20 transfers, and ERC20 approvals to OFAC addresses. As part of its enforcement efforts, OFAC publishes a list of individuals and companies owned or controlled by, or acting for or on behalf of, targeted countries. It also lists individuals, groups, and entities, such as terrorists and narcotics traffickers designated under programs that are not country-specific. Collectively, such individuals and companies are called “Specially Designated Nationals” or “SDNs.” Their assets are blocked and U.S. persons are generally prohibited from dealing with them.
3 - Native Transfers Spending Limits (Block | MFA)
If enabled, transactions with native values (ex. ETH) over a customizable threshold are forbidden. Users can configure this policy to result in a block, or flag for MFA.
4 - ERC20 Stablecoin Transfers Spending Limits (Block | MFA)
If enabled, transfers and approvals with verified stablecoins (ex. USDC, DAI, USDT) over a customizable threshold are forbidden. Users can configure this policy to result in a block, or flag for MFA.
5 - DEX Token Swap Protection
If enabled, token swaps are permitted with verified token pairs. Users can customize which pairs are allowed, and set a slippage limit to prevent trades that might result in MEV exploitation.
If you have additional requests for policies or custom use cases reach out to Shield3 at [info@shield3.com](mailto:info@shield3.com), join the Support Telegram or fill out the Survey
## Custom Error Codes
When Shield3 blocks or flags a transaction the RPC url returns a custom error message. The format is as follows:
```json
{
"jsonrpc": "2.0",
"error": {
"code": "-XXXXXX",
"message": "ERROR MESSAGE HERE"
},
"id": XX
}
```
These include:
**Transaction Blocked \[code -9999982]**
* Raised when policy engine results in block.
**Transaction Flagged for MFA \[code -9999981]**
* Raised when MFA result from policy engine.
* Webhook is sent to user’s configured webhook.
**RPC Call Failed \[code -9999980]**
* Raised when proxy call to user’s RPC fails.
**Invalid API Key \[code -9999979]**
* Raised when API key does not match any account.
**Invalid Node \[code -9999977]**
* Raised when network in RPC url does not match a valid network (0x01, 0x05, etc).
**Uncaught Error in Shield3 \[code -9999976]**
* Something went wrong, raises alert at Shield3 internally as well.
# Telegram Auto-Wallets
Create a wallet in a single-click from your telegram mini-app.
## Introduction
**Attention Required: Telegram Bot Secret Security**
To use automatic wallet creation for Telegram, developers must create a Telegram bot configured with a bot secret, which serves as both the control mechanism for the bot and as a symmetric key for authentication. This key technically grants the developer an ability to sign authentication data and actions with a wallet. Any compromise of the bot secret internally or externally can expose users and their accounts to significant risk.
Securing this symmetric key is critical to ensuring the integrity of all Telegram login sessions in your application.
Telegram Auto-Wallets is currently in closed beta. Please [reach out to us](https://www.dynamic.xyz/talk-to-us) for early access if you are interested in testing this feature.
No transactional MFA must also be enabled to use this feature. This feature will also be enabled when requesting access to Telegram Auto-Wallets.
[Seamless Telegram Login](https://core.telegram.org/api/url-authorization) with Telegram Web Apps improves user experience by enabling quick access directly within Telegram Messenger. Users can launch a mini app and get automatically logged in.
Live example of a Dynamic Seamless Telegram Login *@DynamicMiniAppBot* on
Telegram App.
## Live Demo
🤖 [Open Dynamic Mini App Bot in Telegram](tg://resolve?domain=DynamicMiniAppBot)
## Integration guide
To begin, you can clone the provided [Mini App example and repository](https://github.com/dynamic-labs/telegram-miniapp-dynamic) as a foundation for your project.
### Step-by-Step Instructions
1. **Create an Account on Dynamic**
* If you haven’t done so already, [sign up for an account on Dynamic](https://app.dynamic.xyz/) and take note of your Environment ID.
2. **Clone the Repository and Deploy**
* Clone [Mini App example and repository](https://github.com/dynamic-labs/telegram-miniapp-dynamic) to your local machine.
* Follow steps in the [README](https://github.com/dynamic-labs/telegram-miniapp-dynamic?tab=readme-ov-file#telegram-mini-app--dynamic-connect)
* Deploy the application online. With Next.js, you can deploy a web app within minutes. Follow the deployment instructions [here](https://vercel.com/docs/deployments/git#deploying-a-git-repository).
3. **Create a Telegram App and Bot**
* Use [BotFather](https://core.telegram.org/bots#botfather) to create a new app and bot. For detailed steps, refer to our comprehensive guide [here](https://docs.dynamic.xyz/social-providers/telegram#creating-a-new-app).
4. **Configure Settings in Dynamic Dashboard**
* In your Dynamic dashboard, complete the following steps:
* Add your web app URL as authorized [CORS origin](https://app.dynamic.xyz/dashboard/security).
* [Enable Telegram Social Login](https://app.dynamic.xyz/dashboard/log-in-user-profile) and configure it with your Bot Name and Secret Token.
5. **Update Bot Configuration**
* Use the Bot `TOKEN` from Telegram and set your web app URL as the `LOGIN_URL` in `scripts/bot.ts` or add them as environment variables.
6. **Run the Telegram Bot**
* Run the Telegram bot using the following command:
```bash
npx ts-node scripts/bot.ts
```
7. **Start the Bot in Telegram**
* Open Telegram, go to your newly created bot, and type `/start`.
### Success!
You should now have Dynamic’s Seamless Telegram Login fully functional in your Mini App. 🎉
If you have any questions or need help with the integration, feel free to reach out to us in [Slack](https://dynamic.xyz/slack).
## Telegram Authentication Check for Existing Users
This feature avoids accidental duplicate account creation by **ensuring that users with existing accounts don't create unnecessary new ones** if their account is not yet linked to Telegram.
The `isAuthWithTelegram()` function verifies whether the user is authenticated through Telegram.
If the user is linked with Telegram, they are automatically logged in.
If not, a splash modal prompts the user to confirm if they have an account. Based on the user's response, they are either are prompted to log in or a new account is automatically created and logged in.
This ensures app owners can maintain a single user identity, avoiding duplicate accounts.
Hook is available from SDK version 3.1.3
Full working example in our [Telegram Mini App repository here](https://github.com/dynamic-labs/telegram-miniapp-dynamic/pull/18)
1. Import the necessary Telegram authentication functions:
```javascript
import { useTelegramLogin } from "@dynamic-labs/sdk-react-core";
const { telegramSignIn, isAuthWithTelegram } = useTelegramLogin();
```
2. Check if the user is authenticated via Telegram:
Use isAuthWithTelegram() to determine if the user is linked with Telegram.
```javascript
const checkTelegramConnection = async () => {
const isLinkedWithTelegram = await isAuthWithTelegram();
if (isLinkedWithTelegram) {
// Auto login if Telegram is connected
await telegramSignIn();
} else {
// Show modal splash page
}
};
```
3. Handle the modal splash response:
If the user has an account: Prompt developer's login into the app without creating a new account.
If the user doesn't have an account: Automatically create a new account and log the user in.
```javascript
const handleModalResponse = async (hasAccount: boolean) => {
if (hasAccount) {
// Prompt developer's login
console.log("User already has an account on XYZ");
} else {
// Call signIn with autoCreate: true
console.log(
"User does not have an account on XYZ => Auto Create + Auto Login"
);
await telegramSignIn({ forceCreateUser: true });
}
};
```
You can clone the provided full working example in our [Telegram Mini App repository here](https://github.com/dynamic-labs/telegram-miniapp-dynamic/pull/18)
## Providing the Telegram auth token to Dynamic
The Telegram Auto-Wallets feature allows you to authenticate your end user with Dynamic using a Telegram authentication token directly. There are currently two
ways to provide this token to our SDK.
1. Pass the auth token as a query parameter when the mini app is launched.
In this approach, your [Telegram Bot script](https://github.com/dynamic-labs/telegram-miniapp-dynamic/blob/main/scripts/bot.ts#L59) generates the Telegram auth token and appends it to your mini app URL.
When `telegramSignIn` is called within your mini app, the token is grabbed from the URL query parameters and used to authenticate the user
2. Pass the auth token directly into the `telegramSignIn` hook
In this approach, you directly pass the Telegram auth token into the `telegramSignIn` hook. This is useful if you are generating the Telegram auth token somewhere outside of Telegram
such as your backend.
```javascript
await telegramSignIn({ forceCreateUser: true, authToken: 'telegram-auth-token-goes-here' });
```
## FAQ
### What do I need to do to ensure that Telegram Auto-Wallets is working correctly?
1. **CORS Origin Setup**:
Ensure that your CORS Origin is correctly configured to include your domain(s).
Go to Dashboard > Security > CORS and verify that your domain(s) are listed.
2. **Telegram Social Provider**:
Verify that the Telegram Social Provider is enabled and configured with the correct Bot details.
Navigate to Dashboard > Log In & User Profile > Telegram and confirm that it is turned on, with the BotName and Bot Token entered correctly.
3. **Session keys**:
Please reach out to us in [Slack](https://dynamic.xyz/slack) to request access.
4. **Email Profile Information Requirement**:
Make sure that the option to require profile information for email login is turned off.
Go to Dashboard > Log In & User Profile > Email ⚙️ and confirm that Profile Information required is off.
5. **Bot Code & Telegram Mini Web App**:
Clone the provided [Mini App example and repository](https://github.com/dynamic-labs/telegram-miniapp-dynamic) as a foundation for your project.
Double-check that the Telegram bot code and the Telegram Mini Web App are functioning without any errors.
Check the environment variable are loaded correctly `LOGIN_URL` and `TOKEN`.
Ensure that the logic for seamless login and user verification is working as expected, both server-side and on the front end.
6. **SDK and Package Versions**:
Verify that the SDK and any packages you’re using are up to date. Hook is available from SDK version 3.1.3
### How do I fix MetaMask wallet connection issues?
MetaMask wallet connection does not work on Telegram mobile devices. To resolve this issue, use the following workaround:
Open your app within the mobile version of the MetaMask app. This will allow the wallet connection to function correctly on mobile.
If you encounter further issues, ensure that MetaMask is up to date.
# Telegram Mini App
## Introduction
Telegram Mini Apps (or TMAs) are web applications that run inside the Telegram messenger.
Dynamic works out of the box with TMAs, all you need to do is get your app scaffolded, and add Dynamic by following the quickstart guide.
You can see a live example of a Dynamic integration in a TMA when using [Flooz](https://t.me/flooz_xyz).
## Intregration guide
To get started, you can use one of the TMA templates outlined [here](https://docs.telegram-mini-apps.com/packages/telegram-apps-create-mini-app).
The basic command to scaffold a new TMA is:
```bash
npx @telegram-apps/create-mini-app@latest
```
Cd into the `src/components/` directory in `Root.tsx` file and add the Dynamic Context Provider in the returned TSX:
```tsx
{routes.map((route) => )}
}/>
```
```tsx
{routes.map((route) => )}
}/>
```
Make sure that you've also added the correct imports and the variable for your Dynamic environment ID:
```tsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from '@dynamic-labs/ethereum';
const dynEnv = import.meta.env.VITE_DYNAMIC_ENV_ID || process.env.DYNAMIC_ENV_ID;
```
That's it! You should now be able to use Dynamic in your Telegram Mini App..
You can customize the integration further by following the [documentation](/) in the generated project README file.
If you have any questions or need help with the integration, feel free to reach out to us in [Slack](https://dynamic.xyz/slack).
# XMTP & Dynamic Integration
![](https://mintlify.s3-us-west-1.amazonaws.com/dynamic-docs/images/xmtp.png)
XMTP is a messaging protocol which helps you build apps that communicate with every wallet in the world, and we're huge fans here at Dynamic!
In this guide you'll learn how to set up an example XMTP application using Dynamic as the authentication layer, and learn how it all works.
The running version of what you're about to build can be found here: [https://xmtp-quickstart-reactjs.matthew18091.repl.co/](https://xmtp-quickstart-reactjs.matthew18091.repl.co/)
## Run the quickstart
All you'll need to get going is a code editor and a terminal! When you're ready, clone the official XMTP React quickstart, which ships with Dynamic:
```bash
git clone https://github.com/fabriguespe/xmtp-quickstart-reactjs/
```
Navigate to the xmtp-quickstart-reactjs directory, and run `npm i && npm run dev` to install the dependancies and start the app.
Open it up on whichever ports it displays for you, and you should see a simple app with a button that says "Connect Wallet".
Connect an Etherium supported wallet i.e. Metamask, and you should see a new button called "Connect XMTP" appear. Click it, and you'll be prompted to sign a message in your wallet.
Those two steps could be combined into one for ease of use, but we've kept them separate here to show you how it works under the hood.
That's it! You're authenticated and connected. You can now send messages to wallet addresses, and they'll be able to see them in real time.
## How it works
### The Dynamic integration
Take a look in App.tsx and you'll see that things get wrapped in DynamicContextProvider. This makes sure that Dynamic is available to your components.
If you check the import for this provider at the top of the file, you'll see it's imported from `sdk-react-core`. This is important.
The `-core` signifies that we are using the modular version of the SDK, where you pick and choose which chain/wallet providers you want to import and thus keep the bundle size as small as possible.
This is why you'll also see an `EthereumWalletConnectors` import, which is the Ethereum wallet connector from Dynamic. It's passed in through the settings prop on the DynamicContextProvider, like this:
```jsx
```
As you can see, The other setting passed in is the environment ID for the Dynamic environment you want to use. You can find this in the developer section of the Dynamic dashboard but we've given you a default demo ID to get started with.
Next take a look at the `Home` component, where you'll find a lot of different XMTP functionality If you want to learn more, the docs detail the entire SDK and methods available: [https://xmtp.org/docs/build/get-started](https://xmtp.org/docs/build/get-started)
### What next?
Discover some of the advanced use cases XMTP and Dynamic can provide! You can find use case examples for XMTP [here](https://xmtp.org/docs/use-cases/messaging) and Dynamic examples in the left hand nav [here](https://docs.dynamic.xyz/docs/getting-started).
# Add new users to your CRM
This guide will teach you how to automatically update a Web3 based CRM (in this case, Holder.xyz) once you have a new signup to your dapp.
Holder is a CRM and marketing automation platform built uniquely to handle web3 and wallet data. With Holder + Dynamic, you can:
* Have a single view of your customers, unifying Dynamic user information with your CRM and marketing data.
* Enable powerful on-chain automations and marketing campaigns (like XMTP messaging) based on Dynamic webhook events.
## Prerequisites
This guide builds on [the serverless guide](/guides/webhooks-serverless), so you should have already completed that guide and have a working serverless function. It also assumes you already have a [Holder](https://holder.xyz) account.
## Step 1: Get your Authentication token
For Holder.xyz, you'll need to follow a couple of steps in order to get the token you'll need to authenticate with the API. The fastest way is to follow this guide: [https://docs.holder.xyz/holder-api/authentication](https://docs.holder.xyz/holder-api/authentication). Come back here once you have your token!
You'll be passing this in the header of API requests like so:
```javascript
{
"headers": {
"Authorization": "Bearer Your_Token_Here"
}
}
```
## Step 2: Define your API request
The choice is yours as to which library you use, we will use Fetch here.
First we need to know our base URL, which is `https://api.holder.xyz/`. We will be using the `POST` method to create a new user, and we will be hitting the `/contacts` endpoint. The full reference for that endpoint is [here](https://docs.holder.xyz/holder-api/~/revisions/gDfIZFl5wGxV0PcmHORf/api-reference/contacts).
We need to pass a primaryIdentifier which in our case will be email, and a value, which is the email itself. So putting it together it will look something like this:
```javascript
// holder.js
const token = XXXX;
const createUserInCrm = async (userEmail) => {
const options = {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: `{"primaryIdentifier":"email", "value": ${userEmail}}`,
};
try {
return await fetch("https://api.holder.xyz/contacts", options);
} catch (error) {
console.error(error);
}
};
module.exports = createUserInCrm;
```
## Step 3: Plug in your serverless function
Going back to our Serverless guide at [Step 3](/guides/webhooks-serverless#step-3-adapt-the-function-to-receive-a-dynamic-webhook-event), all we have to do now is add in our Holder function call:
```javascript
// index.js
const createUserInCrm = require("./holder");
module.exports.handler = async (event) => {
const email = event.data.email;
await createUserInCrm(email);
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Go Serverless v3.0! Your function executed successfully!",
input: event,
},
null,
2
),
};
};
```
You can then test and redeploy your function as in [Step 4 of the serverless guide](/guides/webhooks-serverless#step-4-test-and-deploy-your-function), and you're done!
You should see a new contact in [the Holder app](https://app.holder.xyz/contacts) like so:
The next step you might want to take is to update the contact if the user adds more details to their Dynamic profile i.e. wallet addresses etc. We will cover this in a further guide, so stay tuned!
# Webhooks using a serverless function
This guide will teach you how to catch events from Dynamic webhooks using a serverless function.
## Prerequisites
* A [Dynamic](https://dynamic.xyz) account
* An [AWS](https://aws.amazon.com/) account
## Step 1: Scaffold a serverless function
You can do this directly through AWS, but we prefer to use [Serverless](https://www.serverless.com/) which creates a helpful layer of abstraction on top of Lambda.
First install their framework:
```bash
npm install -g serverless
```
Then run their setup command:
```bash
serverless
```
You will need to plug in your AWS credentials during setup, which you can find in your AWS console.
When asked which template you want to use, select `AWS - Node.js - HTTP API` as this tutorial will be using Node.js.
If you cd into the directory that was created, you should see an `index.js` file which is where we will be writing our code.
It will look something like this:
```javascript
module.exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Go Serverless v3.0! Your function executed successfully!",
input: event,
},
null,
2
),
};
};
```
## Step 2: Create our webhook inside Dynamic
We need to create a webhook inside Dynamic that will send a POST request to our serverless function whenever a new user signs up.
To do this we can visit [the webhooks section of the dashboard](https://app.dynamic.xyz/dashboard/developer/webhooks) and click "Create Webhook". It will ask you for a URL and to mark the events you want to listen to.
For now we can use a fake URL, which we can generate from [webhook.site](https://webhook.site/). This will give us a URL that we can use to test our webhook, and will show us the data that is being sent to it.
For the events, you should make sure user.created is selected, and that's it! Save away and a test event will be sent to your URL.
## Step 3: Adapt the function to receive a Dynamic webhook event
Let's say in this instance We are most interested in [the user object](/api-reference/schemas/User), as this is returned by the user.created event. You can find the full list of events [here](/developer-dashboard/eventTypes).
As you'll see from the event reference, the only required field on the object is the ID, so if you want to collect more information you'll need to make sure you're doing [Info Capture](/users/information-capture) on signup.
In this tutorial we will assume we are using email signup with embedded
wallets, so we'll always have email available on the user object at time of
signup
In the test event, when you inspect it using webhook.site, you'll see the payload comes with a `data` field, and it's inside this field that the user object will be present. Here's how we can use the email inside the function:
```javascript
module.exports.handler = async (event) => {
const email = event.data.email;
console.log(email);
...
};
```
Note that we are not adding error handling in the end response, you should do so by adapting the returned status code and message etc.
## Step 4: Test & deploy your function
Serverless hooks into the AWS local testing functionality using [invoke local](https://www.serverless.com/framework/docs/providers/aws/cli-reference/invoke-local).
You can find the function name in the serverless.yml file, and then run the following command to test it:
```bash
serverless invoke local --function FUNCTION_NAME_HERE --data '{data: {"email":"test@mail.com"}}'
```
If you get a log of the user email, you're ready to deploy your function so you can run:
```bash
serverless deploy
```
As long as this is successful, you should get a URL for your function and you can use that to update your webhook in Dynamic from the webhook.site URL.
Now you're ready to run it! Feel free to use one of [the example apps](https://docs.dynamic.xyz/example-apps) to test out creating a new user via the end UI!
## Step 5: Going further
You can use this approach to send notifications for any event, and you can use any service you like to handle the processing of the event.
Don't hesitate to check out [Zapier Webhooks](https://zapier.com/apps/webhook/integrations) to hook into many other services, CRMs and so on.
# Verifying webhook message signatures
This guide will teach you how to verify the webhook message signatures sent in the header of webhook message requests. This enables you to verify that the message originated from dynamic and the message payload has not been tampered with since only Dynamic and you have access to the secret used to sign messages.
Below is a simple typescript function that takes in the webhook secret, signature, and payload; and returns a boolean value indicating if the signature is valid or not.
```typescript
import * as crypto from "crypto";
export const verifySignature = ({
secret,
signature,
payload,
}: {
secret: string;
signature: string;
payload: any;
}) => {
const payloadSignature = crypto
.createHmac("sha256", secret)
.update(JSON.stringify(payload))
.digest("hex");
const trusted = Buffer.from(`sha256=${payloadSignature}`, "ascii");
const untrusted = Buffer.from(signature, "ascii");
return crypto.timingSafeEqual(trusted, untrusted);
};
```
The following is a practical example of how to use the verifySignature function to verify a webhook message signature for a given payload.
The structure of the payload object matters; if the payload object is not structured in the manner in which the message was sent the signature verification will fail.
```typescript
// webhook secret, unique per webhook
const secret = 'dyn_5LqRXtZsXit7Cjt9KktC8EGywoSkGSbGqtTud[S7AYd{LUTMcS38QRMT';
// signature sent in the header (x-dynamic-signature) of the webhook message
const signature = 'sha256=9c1eade367c30f371990918b8d79ecdd86ea2c8042849a1c22c94ad50b7d98fe';
// message payload sent in the body of the webhook POST request
const payload = {
messageId: 'd487290b-9bbd-4b50-a5f5-6620634c8dd9',
eventId: 'bfb51cec-345f-4539-b004-e3415223b86b',
eventName: 'user.deleted',
timestamp: '2024-05-31T17:21:14.890Z',
webhookId: 'c51e4b22-193e-4989-9ce2-90851d57d861',
userId: '001a4205-3d67-4956-a132-5ef41a06c78e',
environmentId: 'ba1f6471-626b-45a1-ab63-c6bf4793a1c5',
environmentName: 'sandbox',
redelivery: true,
data: {
onboardingCompletedAt: '2024-01-22T20:38:29.049Z',
metadata: {},
createdAt: '2024-01-22T20:38:27.181Z',
projectEnvironmentId: 'ba1f6471-626b-45a1-ab63-c6bf4793a1c5',
id: '001a4205-3d67-4956-a132-5ef41a06c78e',
firstVisit: '2024-01-22T20:38:27.169Z',
email: 'dynamicuser@gmail.com',
updatedAt: '2024-05-31T17:21:14.870Z',
lastVisit: '2024-01-22T20:55:10.126Z',
deletedAt: '2024-05-31T17:21:14.867Z',
},
};
const isValid = verifySignature({ secret, signature, payload });
console.log('isValid', isValid);
```
# Slack notifications of new signups
Post in a slack channel when you have a new signup
This guide will teach you how to automatically post a message into a slack channel of your choice with details of a new signup to your dapp.
## Prerequisites
* Completed [the serverless webhooks guide](/guides/webhooks-serverless)
* A [Slack](https://slack.com/) account
* A [Dynamic](https://dynamic.xyz) account
## Post a message to Slack
As long as you have completed the serverless webhook guide, you'll know how to access the email of a user within your serverless function. Now we have the email, we can post a message to Slack. To do this we'll need to create a [Slack app](https://api.slack.com/apps) and set it up to post messages using incoming webhooks.
[This guide](https://api.slack.com/messaging/webhooks) will get you set up, and once you've completed it you will end up with a webhook URL, that's all you need.
It's time to write the code to send our Slack message. We can do this with anything like Axios or Request, but here we will use [the Slack/webhook library](https://slack.dev/node-slack-sdk/webhook) to make things even easier.
```javascript
// Slack.js
const { IncomingWebhook } = require("@slack/webhook");
// Read a url from the environment variables
const url = YOUR_SLACK_WEBHOOK_URL;
// Initialize
const webhook = new IncomingWebhook(url);
const sendMessage = async (message) => {
try {
return await webhook.send({
text: message,
});
} catch (error) {
console.error(error);
}
};
module.exports = sendMessage;
```
```javascript
// index.js
const webhook = require("./slack");
module.exports.handler = async (event) => {
const email = event.data.email;
const message = `New signup: ${email}`;
await webhook(message);
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Go Serverless v3.0! Your function executed successfully!",
input: event,
},
null,
2
),
};
};
```
## Test it out
As per the serverless webhook guide, we can test this out using:
```bash
serverless invoke local --function FUNCTION_NAME_HERE --data '{data: {"email":"test@mail.com"}}'
```
We can then deploy using `serverless deploy`, and you're good to go!
# Send new users an XMTP message
XMTP is a communication protocol for Web3, allowing you to build apps that communicate with
every wallet in the world. In this guide you will learn to programmatically send XMTP messages to users when they sign up using a wallet.
## Prerequisites
* A [Dynamic](https://dynamic.xyz) account
* Completed [the serverless webhooks guide](/guides/webhooks-serverless)
## Step 1: Access the wallet address
As long as you have completed the serverless webhook guide, you'll know how to access the email of a user within your serverless function. However in this instance, we need a wallet address.
A new user will have a wallet in two instances:
1. They signup using an EOA
2. They receive an embedded wallet
You can learn more about the different kinds of signup you can enable in the following section of the docs: [https://docs.dynamic.xyz/sign-in-sign-up/overview](https://docs.dynamic.xyz/sign-in-sign-up/overview).
Let's assume for now that you have both embedded wallets enabled, and EOA signups enabled so that the user object is guaranteed to have a wallet address.
When a user signs up, you can access their wallet address through the Verified Credentials array ([reference here](/react-sdk/objects/verified-credential)):
```js
const verifiedCredentials = event.data?.verifiedCredentials;
if (!verifiedCredentials) {
return {
statusCode: 400,
body: JSON.stringify({
error: "No verified credentials found",
}),
};
}
const blockchainVC = verifiedCredentials.find((vc) => vc.type === "blockchain");
if (!blockchainVC) {
return {
statusCode: 400,
body: JSON.stringify({
error: "No blockchain VC found",
}),
};
}
const address = blockchainVC.address;
```
## Step 2: Send the XMTP message
We will need to install the XMTP library for NodeJS first:
```bash
npm i @xmtp/xmtp-js
```
Then we can import the library and send a message:
```javascript
// xmtp.js
const { Client } = require("@xmtp/xmtp-js");
const sendMessage = (address) => {
try {
const conv = await xmtp.conversations.newConversation(address);
return await conv.send("gm");
} catch(e) {
console.log(e)
}
}
export default sendMessage;
```
## Step 3: Call the function
Going back to our Serverless guide at [Step 3](/guides/webhooks-serverless#step-3-adapt-the-function-to-receive-a-dynamic-webhook-event), all we have to do now is add in our XMTP function call:
```javascript
// index.js
const sendMessage = require("./xmtp.js");
module.exports.handler = async (event) => {
const verifiedCredentials = event.data?.verifiedCredentials;
if (!verifiedCredentials) {
return {
statusCode: 400,
body: JSON.stringify({
error: "No verified credentials found",
}),
};
}
const blockchainVC = verifiedCredentials.find(
(vc) => vc.type === "blockchain"
);
if (!blockchainVC) {
return {
statusCode: 400,
body: JSON.stringify({
error: "No blockchain VC found",
}),
};
}
const address = blockchainVC.address;
await sendMessage(address);
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Go Serverless v3.0! Your function executed successfully!",
input: event,
},
null,
2
),
};
};
```
You can then test and redeploy your function as in [Step 4 of the serverless guide](/guides/webhooks-serverless#step-4-test-and-deploy-your-function), and you're done!
# ETHGlobal Singapore
## Prizes
## Ideas
* [Flutter](/flutter/client) or [React Native](/react-native/introduction) mobile apps
* [Flutter Example App](https://github.com/dynamic-labs/flutter-example)
* [React Native Example App (with Expo)](https://github.com/dynamic-labs/react-native-expo)
* [Telegram Mini Apps](/guides/integrations/telegram/telegram-mini-app) or [Telegram Auto Wallets](/guides/integrations/telegram/telegram-auto-wallets)
* [TG Mini App with Auto Wallets Example Repo](https://github.com/dynamic-labs/telegram-miniapp-dynamic)
## Past Winners
* [London 2024 Winners](https://x.com/dynamic_xyz/status/1769710456015647079)
* [Brussels 2024 Winners](https://x.com/dynamic_xyz/status/1812532549601857693)
# Headless Email Signup
## Introduction
This guide will help you let a user signup/login with email, without any of our UI components. We've split the guide into two tabs as you'll see below. The first uses our SDK hooks (but none of the UI components) - we recommend this choice if you can. The second tab shows you how to handle email login without any SDK interaction at all i.e. API only.
## Tutorial
### Configuring the SDK
Let's start by installing our packages. Follow our [Quickstart guide](/quickstart) for a detailed walkthrough.
When you're done, we'll update our App.js to include our environment ID in the setup and import the `DynamicContextProvider` and `EthereumWalletConnectors`.
You app.js file should look like this (note that we are only importing the EthereumWalletConnectors for now):
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
// Placeholder for our SMS signup/login form component
import ConnectWithEmailView from './ConnectWithEmailView';
function App() {
return (
);
}
export default App;
```
### useConnectWithOtp
All we will need for this use case is the [useConnectWithOtp hook](/react-sdk/hooks/useconnectwithotp). This exposes multiple methods, and we are interested primarily in the following:
* connectWithEmail
* verifyOneTimePassword
Once you have those available in your component, the rest is as simple as building your form!
### Code Example
```tsx
import { FC, FormEventHandler } from 'react';
import { useConnectWithOtp, useDynamicContext } from '@dynamic-labs/sdk-react-core';
const ConnectWithEmailView: FC = () => {
const { user } = useDynamicContext()
const { connectWithEmail, verifyOneTimePassword } = useConnectWithOtp();
const onSubmitEmailHandler: FormEventHandler = async (
event,
) => {
event.preventDefault();
const email = event.currentTarget.email.value;
await connectWithEmail(email);
};
const onSubmitOtpHandler: FormEventHandler = async (
event,
) => {
event.preventDefault();
const otp = event.currentTarget.otp.value;
await verifyOneTimePassword(otp);
};
return (
{!!user && (
Authenticated user:
{JSON.stringify(user, null, 2)}
)}
)
}
```
Here we follow three simple steps, creating the email verification, verifying the OTP, and getting the JWT.
### Basic UI
First let's create a basic React component to help us handle the UI:
```JSX
import React, { useState } from 'react';
const EmailSignup = () => {
const DYNAMIC_ENVIRONMENT_ID = "YOUR_ENVIRONMENT_ID";
const [email, setEmail] = useState("");
const [verifying, setVerifying] = useState(false);
const [OTP, setOTP] = useState("");
const [UUID, setUUID] = useState("");
const [JWT, setJWT] = useState("");
const sendEmailVerification = async () => {};
const verify = async () => {};
return (
Signup with Email
setEmail(e.target.value)}
placeholder="Enter your email"
value={email}
/>
{verifying && (
setOTP(e.target.value)}
placeholder="Enter your OTP"
value={OTP}
/>
)}
{JWT &&
Your JWT is: {JWT}
}
);
}
```
We're creating a few state variables to help us keep track of the user's email, the OTP, the verificationUUID, and the JWT. We're also creating a couple of (currently empty) functions to handle the email verification and the OTP verification.
You might be wondering what the UUID variable is for. When you create an email verification, we return a verificationUUID that you must pass into the API along with the OTP itself so that it's correctly verified.
Lastly but most importantly, there's a variable called DYNAMIC\_ENVIRONMENT\_ID. We will need this to populate the URLs for the API calls. You can find your environment ID in the [Dynamic dashboard](https://app.dynamic.xyz/dashboard/developer/api).
OK, let's fill in the empty functions now, going step by step:
### Create an email verification
You will be interacting with [the emailVerification endpoint](/api-reference/endpoints/sdk/createEmailVerification) for this. This will send an email to the user with a One Time Password so they can verify their email address.
You can find the full reference for the endpoint in the link above, but the main thing you need to pass is the email address of the user. We'll edit our `sendEmailVerification` function like so:
```javascript
const sendEmailVerification = async () => {
setVerifying(true);
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email: email }),
};
fetch(
`https://app.dynamicauth.com/api/v0/sdk/${DYNAMIC_ENVIRONMENT_ID}/emailVerifications/create`,
options
)
.then((response) => response.json())
.then((response) => {
setUUID(response.verificationUUID);
})
.catch((err) => console.error(err));
};
```
### Verify the OTP
With the above step complete, as long as the email address is valid, the user will receive an email with an OTP. They will enter the OTP in the UI, and we will verify it once they hit the "Verify" button.
To verify the OTP, the next call will be to signIn endpoint, which you can find [here](/api-reference/endpoints/sdk/signInWithEmailVerification). You will need to pass the OTP from the email and the verificationUUID from the previous response.
Let's fill in our `verify` function with that in mind:
```javascript
const verify = async () => {
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
verificationToken: OTP,
verificationUUID: UUID,
}),
};
fetch(
`https://app.dynamicauth.com/api/v0/sdk/${DYNAMIC_ENVIRONMENT_ID}/emailVerifications/signIn`,
options
)
.then((response) => response.json())
.then((response) => {
setVerifying(false);
setJWT(response.jwt);
})
.catch((err) => console.error(err));
};
```
### Get the JWT
At this point, you'll have a JWT returned from the verify call and saved in state as the jwt variable. This JWT will be tied to a session in Dynamic, and we return details of the user from that JWT.
Learn more about the JWT object [here](/react-sdk/objects/user-payload).
## Summary
That's it! You now have a completely headless sign-in with email, using Dynamic OTP.
# Headless Embedded Wallets With OTP
## Summary
This guide will show you how to create an embedded wallet for a user, then help them secure it once they are ready to make their first transaction using One Time Codes, and optionally adding a passkey.
When using one time codes, we'll also create a session for that user so they can transact for a further period of time without any extra steps. for more information on the differences between passkeys and one codes, please see [this guide](/wallets/embedded-wallets/dynamic-embedded-wallets#signing-and-security-methods).
We will assume that the user has already signed up. If you need to handle the signup part headlessly, we recommend reading the [headless email guide](/headless/headless-email).
Here are the steps we need to follow:
1. Check for active session
2. Create the embedded wallet
3. Secure the wallet pre-transaction
4. Create a session for the user
For all of these steps we will need just one hook - [`useEmbeddedWallet`](/react-sdk/hooks/useembeddedwallet).
## Pre-requisites
* You have already set up the Dynamic SDK and wrapped your app with the `DynamicContextProvider`.
* You have enabled Dynamic-powered embedded wallets and toggled "Manual Mode" on in the configuration.
* You do not have the DynamicWidget component in your application code.
## Session Notes
For security reasons, sessions are held in an iframe which gets deleted after page refresh and user log out.
To overcome sending users one-time codes each time they don't have an active session, we're able to restore it and developers should always try to do it before starting the one-time codes flow.
You'll see this in the code examples below, where we check for an active session before sending the one-time code i.e.
```jsx
useEffect(() => {
const startSession = () => {
try {
if (isSessionActive) return;
createOrRestoreSession();
} catch (err) {
return;
}
};
startSession();
}, []);
```
## Full code examples
You must add the following to your root CSS file to hide the iframe
```css
iframe[id*="dyn-secure-enclave-element-id"] {
display: none;
}
```
```javascript
import { useEmbeddedWallet, useDynamicContext } from “@dynamic-labs/sdk-react-core”
// component declaration and all other logic you might need
const { createEmbeddedWallet,
createOrRestoreSession,
isSessionActive,
sendOneTimeCode,
userHasEmbeddedWallet } = useEmbeddedWallet();
const oneTimeCodeSent = useRef(false);
useEffect(() => {
const startSession = () => {
try {
if (isSessionActive) return;
createOrRestoreSession();
} catch (err) {
return;
}
};
startSession();
}, []);
const onSendOneTimeCodeHandler = async () => {
// You do not need to call createEmbeddedWallet
// if the Create on Sign up setting is enabled in the
// Dynamic dashboard as the embedded wallets will be generated
// automatically on user sign up.
if(!userHasEmbeddedWallet()) await createEmbeddedWallet();
if(!isSessionActive) {
try {
await sendOneTimeCode();
oneTimeCodeSent.current = true;
return;
} catch(e) {
console.error(e)
}
} else return;
}
const onCreateSessionHandler: FormEventHandler = async (event) => {
try {
event.stopPropagation();
event.preventDefault();
if (!primaryWallet || !userHasEmbeddedWallet()) return;
const otc = event.currentTarget.otc.value;
await createOrRestoreSession({oneTimeCode: otc})
.then((result) => setResult(result))
.catch((error) => setResult(JSON.stringify(error, null, 2)));
} catch (err) {
logger.error(err);
}
};
return (
<>
{!isSessionActive && (
)}
>
)
```
```javascript
import { useEmbeddedWallet, useDynamicContext } from “@dynamic-labs/sdk-react-core”
// component declaration and all other logic you might need
const { createEmbeddedWallet,
createPasskey,
getPasskeys,
userHasEmbeddedWallet } = useEmbeddedWallet();
const onCreatePasskeyHandler: FormEventHandler = async (event) => {
try {
event.stopPropagation();
event.preventDefault();
if (!primaryWallet || !userHasEmbeddedWallet()) return;
const otc = event.currentTarget.otc.value;
return await createPasskey(otc)
} catch (err) {
console.error(err);
}
};
return (
<>
{!oneTimeCodeSent.current && }
{oneTimeCodeSent.current && (
)}
>
)
```
# Headless embedded wallets using Passkeys
## Summary
This guide will show you how to create a secured embedded wallet with a passkey, completely headlessly. We will use the createPasskey and createEmbeddedWallet methods, available from [the useEmbeddedWallet hook](/react-sdk/hooks/useembeddedwallet).
Here are the steps we need to follow:
1. Ensure the user is logged in
2. Create a passkey for the user
3. Create the embedded wallet with the passkey provided from the previous step
## Pre-requisites
* You have already set up the Dynamic SDK and wrapped your app with the `DynamicContextProvider`.
* You have enabled Dynamic-powered embedded wallets and toggled "Manual Mode" on in the configuration.
* You do not have the DynamicWidget component in your application code.
## Step by step
### Ensure the user is logged in
You can do this by implementing signup/login i.e. via [email](/headless/headless-email), or via [SMS](/headless/headless-sms).
### Create a passkey for the user
To create a passkey for the user, we will use the createPasskey method from the useEmbeddedWallet hook. This method will request the user create a passkey through their device UI and return a WebAuthnAttestation object to be used next while creating the embedded wallet.
Method signature:
```typescript
createPasskey(): Promise
```
Implementation:
```tsx
const { createPasskey } = useEmbeddedWallet();
const createPasskeyHandler = async () => {
const webAuthnAttestation = await createPasskey({});
console.log(webAuthnAttestation);
};
```
### Create the embedded wallet
To create the embedded wallet, we will use the `createEmbeddedWallet` method from the `useEmbeddedWallet` hook. This method will create the wallet with the passkey we generated in the previous step.
Method signature:
```typescript
createEmbeddedWallet(chains?: ChainEnum[], options?: { webAuthnAttestation: WebAuthnAttestation }): Promise
```
Implementation:
```tsx
const { createEmbeddedWallet } = useEmbeddedWallet();
const createWalletHandler = async () => {
const wallet = await createEmbeddedWallet(['EVM'], { webAuthnAttestation });
console.log(wallet);
};
```
## Full code example
```tsx
export const CreateEmbeddedWalletWithPasskey: FC = () => {
const { createEmbeddedWallet, createPasskey } = useEmbeddedWallet();
const [result, setResult] = useState(undefined);
const onSubmitHandler = async (event: React.FormEvent) => {
event.preventDefault();
event.stopPropagation();
const webAuthnAttestation = await createPasskey({});
createEmbeddedWallet([ChainEnum.EVM, ChainEnum.Sol], { webAuthnAttestation })
.then((result) => setResult(JSON.stringify(result, null, 2)))
.catch((error) => setResult(JSON.stringify(error, null, 2)));
};
return (
);
};
```
# Headless Embedded Wallet Export
## Introduction
This functionality is only available on V2.1.0-alpha.11 of the SDK and above.
You can use the SDK to headlessly reveal the private key or recovery phrase of an embedded wallet.
We will use the `revealWalletKey` method from the `useEmbeddedWallet` hook, which has the following signature:
```typescript
revealWalletKey: (options?: {
type: "recoveryPhrase" | "privateKey";
htmlContainerId?: string | undefined;
} | undefined) => Promise;
```
As you can see, the method takes an object with two properties:
* `type`: The type of key you want to reveal. It can be either `recoveryPhrase` or `privateKey`.
* `htmlContainerId`: The id of the HTML element where the key will be displayed. You must have a div present in your component with this id.
## Full code example
```tsx
import { FC, useState } from 'react';
import { useEmbeddedWallet, useIsLoggedIn } from '@dynamic-labs/sdk-react-core';
export const Reveal: FC = () => {
const isLoggedIn = useIsLoggedIn();
const { revealWalletKey } = useEmbeddedWallet();
const [result, setResult] = useState(undefined);
const onSubmitHandler = async (
selectedKey: 'recoveryPhrase' | 'privateKey',
) => {
try {
await revealWalletKey({
htmlContainerId: 'reveal-example-container-id',
type: selectedKey,
});
} catch (error) {
setResult(JSON.stringify(error, null, 2));
}
};
return (
{isLoggedIn ?
Choose which type of key you want to reveal from the embedded wallet
{result}
:
Please log in to continue.
}
);
};
```
# Headless Overview
## Defining "headless"
While you can use the API to entirely decouple a frontend from Dynamic and still enable onboarding experiences, headless in this case refers to the ability to use the Dynamic SDK without our UI components.
This allows you to build your own UI components and still use the Dynamic SDK to manage the data and logic of your onboarding experience.
## Why go headless?
The Dynamic UI components handle a ton of edge cases, are actively maintained, and are designed to be easy to use while being completely flexible.
However, if for any reason these components are not fitting your use case, you might want to go headless.
## Getting started
You should still follow [the getting started guide](/adding-dynamic/adding-the-sdk) to add the SDK to your project.
Once your app is wrapped with the DynamicContextProvider, you can now start to use our hooks and context to build your own UI components.
This section outlines guides for each part of the onboarding experience, and how to build them headlessly. Specifically, they mimic building the same components as the Dynamic UI components, but with your own UI.
## Guides
* [Headless Email Signup/Login](/headless/headless-email)
* [Headless SMS Signup/Login](/headless/headless-sms)
* [Headless Social Signup/Login](/headless/headless-social)
* [Headless Embedded Wallets With OTP](/headless/headless-embedded-otp)
* [Headless Embedded Wallets With Passkey](/headless/headless-embedded-passkey)
* [Headless Export for Embedded Wallets](/headless/headless-embedded-reveal)
# Headless SMS signup
### Overview
Leveraging Dynamic's React package, this guide will help you create a sign in / sign up flow based on an sms login form.
We use [the `useConnectWithOtp` hook](/react-sdk/hooks/useconnectwithotp) to expose two methods: `connectWithSms` and `verifyOneTimePassword`.
The first method will send an SMS to the user's phone number, and the second will verify the OTP sent to the user's phone.
Once that's done, the user will be authenticated and you can access their information through the `useDynamicContext` hook!
### Dashboard Setup
#### SMS Signup/Login
The first thing you'll want to do is create a new Dynamic organization and setup Dashboard.
1. Sign up at app.dynamic.xyz
2. Fill in the modal to create your organization and project.
3. Under the Developer menu, go to SDK & APIs and fetch the Environment ID. Keep this as we'll need this key in a little bit.
Next we'll want to enable SMS signup/login:
For more information on how to configure SMS signup/login, check out our [SMS Signup/Login guide](/authentication-methods/email-social-sms).
### Configuring the SDK
Let's start by installing our packages. Follow our [Quickstart guide](/quickstart) for a detailed walkthrough.
When you're done, we'll update our App.js to include our environment ID in the setup and import the `DynamicContextProvider` and `EthereumWalletConnectors`.
You app.js file should look like this (note that we are only importing the EthereumWalletConnectors for now):
```jsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
// Placeholder for our SMS signup/login form component
import ConnectWithSmsView from './ConnectWithSmsView';
function App() {
return (
);
}
export default App;
```
### SMS Signup/Login UI
Now that we have our environment, we can create our SMS signup/login form. We use the [`useConnectWithOtp` hook](/react-sdk/hooks/useconnectwithotp) to handle the SMS setup.
```tsx
import { FC, FormEventHandler } from 'react';
import { useConnectWithOtp, useDynamicContext } from '@dynamic-labs/sdk-react-core';
const ConnectWithSmsView: FC = () => {
const { user } = useDynamicContext()
const { connectWithSms, verifyOneTimePassword } = useConnectWithOtp();
const onSubmitSmsHandler: FormEventHandler = async (
event,
) => {
event.preventDefault();
const phone = {
dialCode: event.currentTarget.dialCode.value,
iso2: event.currentTarget.iso2.value,
phone: event.currentTarget.phone.value,
};
await connectWithSms(phone);
};
const onSubmitOtpHandler: FormEventHandler = async (
event,
) => {
event.preventDefault();
const otp = event.currentTarget.otp.value;
await verifyOneTimePassword(otp);
};
return (
{!!user && (
Authenticated user:
{JSON.stringify(user, null, 2)}
)}
)
}
export default ConnectWithSmsView
```
# Headless Social Login
This section shows you how to enable and implement social signup/login headlessly, using the Dynamic React SDK but without any of the UI components.
## Prerequisites
You must have at least one social provider enabled in the dashboard. For instructions on how to do this, check out [the configuring social providers section](/social-providers/overview).
## Implementation
You'll use [the `useSocialAccounts` hook](/react-sdk/hooks/usesocialaccounts) available from the `sdk-react-core` package:
```npm
npm i @dynamic-labs/sdk-react-core
```
```yarn
yarn add @dynamic-labs/sdk-react-core
```
This hook comes with a method called `signInWithSocialAccount` that you can utilize:
```tsx
import { FC } from 'react';
import {
DynamicWidget,
useDynamicContext,
useSocialAccounts,
} from '@dynamic-labs/sdk-react-core';
import { ProviderEnum } from '@dynamic-labs/types';
import { FarcasterIcon, GoogleIcon, TwitterIcon } from '@dynamic-labs/iconic';
const SocialSignIn = () => {
const { error, isProcessing, signInWithSocialAccount } = useSocialAccounts();
return (
>
);
};
export const HeadlessSocialSignInView: FC = () => {
const { user } = useDynamicContext();
return (
{user ? : }
);
};
```
# Headless User Profile
## Introduction
This guide will help you to create a completely headless user profile including everything you see in the regular [Dynamic Widget UI component](/react-sdk/components/dynamicwidget)/[Dynamic Embedded Widget UI component](/react-sdk/components/dynamicembeddedwidget). If you'd like to see the Dynamic Widget/Embedded Widget in action, head over to [the live demo](https://demo.dynamic.xyz/).
## Prerequisites
Like with all this series of headless guides, "headless" is defined a way to use the Dynamic SDK without the need for Dynamic UI components (i.e. DynamicWidget, DynamicUserProfile).
You still need to add the SDK and set up the Dynamic Context Provider (complete [the quickstart](/quickstart) if you haven't done so already, or refer to [an example app](/example-apps))
### Setup
#### Show the user's profile based on whether they are logged in or not
How: Check if the user is logged in or not
Hook/Component: [useIsLoggedIn](/react-sdk/hooks/useisloggedin)
Notes: We start here assuming you have signup/login implemented already
```
import { useIsLoggedIn } from "@dynamic-labs/sdk-react-core";
const isLoggedIn = useIsLoggedIn();
return <>{isLoggedIn ? : }>
```
### Profile Info
#### Show user profile information
How: Fetch info from user object on useDynamicContext
Hook/Component: [useDynamicContext](/react-sdk/hooks/usedynamiccontext)
Notes: The format of the user can be found here: [userProfile](/react-sdk/objects/userprofile)
```jsx
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
const { user } = useDynamicContext();
return (
{user?.firstName &&
First name: {user.firstName}
}
{user?.email &&
E-Mail: {user.email}
}
{user?.alias &&
Alias: {user.alias}
}
{user?.lastName &&
Last name: {user.lastName}
}
{user?.jobTitle &&
Job: {user.jobTitle}
}
{user?.phoneNumber &&
Phone: {user.phoneNumber}
}
{user?.tShirtSize &&
Tshirt size: {user.tShirtSize}
}
{user?.team &&
Team: {user.team}
}
{user?.country &&
Country: {user.country}
}
{user?.username &&
Username: {user.username}
}
);
```
#### Allow user to update their profile information
How: useUserUpdateRequest hook
Hook/Component: [useUserUpdateRequest](/react-sdk/hooks/useuserupdaterequest)
Notes: We include the validation for email updates here
```
import { useUserUpdateRequest, useOtpVerificationRequest } from "@dynamic-labs/sdk-react-core";
const { verifyOtp } = useOtpVerificationRequest();
const { updateUser } = useUserUpdateRequest();
const [showUpdateForm, setShowUpdateForm] = useState(false);
const [showVerifyForm, setShowVerifyEmailForm] = useState(false);
const updateUserInfoFormSubmit = async (e) => {
e.preventDefault();
try {
setLoading(true);
// Call the updateUser function with the new values entered by the user
const { isEmailVerificationRequired } = await updateUser({
firstName: e.target[0].value,
email: e.target[1].value,
});
// If email verification is required, show the email verification form
if (isEmailVerificationRequired) {
setShowVerifyEmailForm(true);
}
} catch (e) {
console.log("Error", e);
} finally {
setLoading(false);
setShowUpdateForm(false);
}
};
// Handler for the email verification form submission
const onVerifyEmailFormSubmit = async (e) => {
e.preventDefault();
try {
setLoading(true);
const verificationToken = e.target[0].value;
// Call the verifyEmail function with the entered verification token
await verifyOtp(verificationToken);
} catch (e) {
console.log("Error", e);
} finally {
setLoading(false);
// Hide the email verification form after the process is completed
setShowVerifyEmailForm(false);
}
return false;
};
return (
{/* Render the profile update form */}
{showUpdateForm && (
)}
{/* Render the email verification form if needed */}
{showVerifyEmailForm && (
)}
)
```
### Socials
#### Show users linked social accounts, allow linking/unlinking
How: useSocialAccounts hook from sdk-react-core
Hook/Component: [useSocialAccounts](/react-sdk/hooks/usesocialaccounts)
Notes: None
```jsx
import { useSocialAccounts } from "@dynamic-labs/sdk-react-core";
import { SocialIcon } from '@dynamic-labs/iconic';
const Avatar = ({ avatarUrl }) => {
return (
{isProviderLinked ? (
) : (
)}
);
};
const Socials = () => {
const providers = [
"discord",
"facebook",
"github",
"google",
"instagram",
"twitch",
"twitter",
];
return (
{providers.map((provider) => (
))}
);
};
```
# Headless Wallet Management
This guide covers how to implement the wallet aspect of the Dynamic Widget UI Component but using only our SDK hooks and your own UI implementation.
## Prerequisites
Like with all this series of headless guides, "headless" is defined a way to use the Dynamic SDK without the need for Dynamic UI components (i.e. DynamicWidget, DynamicUserProfile).
You still need to add the SDK and set up the Dynamic Context Provider (complete [the quickstart](/quickstart) if you haven't done so already, or refer to [an example app](/example-apps))
## Signup/Login
### Fetch available wallets
You can find the list of available wallets in the `walletOptions` prop returned by the [useWalletOptions hook](/react-sdk/hooks/usewalletoptions).
When browsing wallets in the Dynamic Widget, you might see labels beside them like "Last Used", "Multichain" or "Recommended".
Last used comes from the "dynamic\_last\_used\_wallet" value in localstorage.
"Multichain" comes from the `chainGroup` node in each wallet (Remember to also add [the WalletConnectors](/chains/enabling-chains#enabling-a-chain-network) for each chain).
"Recommendeded" from [the Recommended Wallets feature](/wallets/advanced-wallets/recommend-wallets).
### Display a wallet icon
Use the `@dynamic-labs/wallet-book` library to display a wallet icon using the exported `WalletIcon` component. This component takes a `walletKey` prop, which is the key of the wallet you want to display.
```tsx
import { WalletIcon } from '@dynamic-labs/wallet-book';
const WalletIconComponent = () => {
return ;
};
```
### Connect to a wallet
[useWalletOptions](react-sdk/hooks/usewalletoptions) allows you to prompt the user to connect using a specific wallet (by passing in the key).
```
import { useWalletOptions } from "@dynamic-labs/sdk-react-core";
// component setup etc.
const { selectWalletOption } = useWalletOptions();
const connectWithWallet = async (walletKey) => {
return await selectWalletOption(walletKey)
}
```
### Onramp
#### Show onramp button and open flow
How: Regular button attached to useFunding hook
Hook/Component: [useFunding](/react-sdk/hooks/usefunding)
Notes: Ensure Banxa is enabled in the Dynamic dashboard
```jsx
import { useFunding } from "@dynamic-labs/sdk-react-core";
const Onramp = () => {
const { enabled, openFunding } = useFunding();
return (
{enabled && (
)}
);
};
```
### Networks
#### Display current and available networks
How: Use evmNetworks and getNetwork to fetch, use supportsNetworkSwitching and switchNetwork to switch
Hook/Component: [useDynamicContext](/react-sdk/hooks/usedynamiccontext) & [getNetwork](/react-sdk/utilities/getnetwork)
Notes: This example is for Evm networks only
```jsx
import { useDynamicContext, getNetwork } from "@dynamic-labs/sdk-react-core";
const CustomNetworkPicker = () => {
const [currentNetwork, setCurrentNetwork] = useState(null);
const { primaryWallet } = useDynamicContext();
const handleNetworkChange = async (event) => {
const chainId = parseInt(event.target.value);
if (primaryWallet?.connector?.supportsNetworkSwitching()) {
try {
return await primaryWallet?.connector?.switchNetwork({
networkChainId: chainId,
});
} catch (error) {
console.error("Error switching network", error);
}
}
};
useEffect(() => {
if (!currentNetwork)
getNetwork(primaryWallet?.connector).then((network) => {
setCurrentNetwork(network);
});
}, [primaryWallet]);
return (
<>
{currentNetwork && (
)}
>
);
};
```
### Primary Wallet
Primary wallet is the wallet that is defined as the active one of all your connected wallets. All interactions, such as signing or creating transactions, will be made with this wallet. In addition, when leveraging our multiasset functionality, this is the wallet that will be display all available tokens.
#### Display primary wallet address
How: Fetch the primary wallet info from context
Hook/Component: [useDynamicContext](/react-sdk/hooks/usedynamiccontext)
Notes: There can be a moment after logging in when it’s not populated yet
```jsx
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
const { primaryWallet } = useDynamicContext();
const copyWalletAddress = () => {
navigator.clipboard.writeText(primaryWallet?.address);
};
return (
);
```
#### Display primary wallet icon
How: Utilize the Walletbook library
Hook/Component: WalletIcon (not publicly documented yet)
Notes: Requires the primaryWallet.connector object.
```jsx
import { primaryWallet } from "@dynamic-labs/sdk-react-core";
import { WalletIcon } from "@dynamic-labs/wallet-book";
const WalletIconWrapped = ({ connector }) => {
return (
;
```
#### Show all tokens and balances
How: Mapping return from usetokenbalances hook
Hook/Component: [https://docs.dynamic.xyz/react-sdk/hooks/usetokenbalances](https://docs.dynamic.xyz/react-sdk/hooks/usetokenbalances)
Notes: Multi-asset is only supported on the following networks - Ethereum, Optimism, Polygon, Arbitrum, and Base.
```jsx
import { useTokenBalances } from "@dynamic-labs/sdk-react-core";
const TokenBalances = () => {
const tokenBalances = useTokenBalances();
return (
{tokenBalances.map((token) => (
{token.symbol}
{token.balance}
))}
);
};
```
#### Detect Wallet Locking
The wallet object has a field called `connected` which indicates whether the wallet is locked or not, and you can use this to trigger your own workflow to warn the user.
### Multi-Wallet
#### Show all linked wallets and allow unlinking
How: Mapping return from useUserWallets hook and handleUnlinkWallet from useDynamicContext
Hook/Component: [https://docs.dynamic.xyz/react-sdk/hooks/useuserwallets](https://docs.dynamic.xyz/react-sdk/hooks/useuserwallets) & [https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext](https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext)
Notes: Doesn’t yet show if a wallet is the primary wallet. Unlinking is also handled here.
```jsx
import {
useDynamicContext,
useUserWallets,
} from "@dynamic-labs/sdk-react-core";
import { Tooltip } from "react-tooltip";
const LinkedWallets = () => {
const { handleUnlinkWallet } = useDynamicContext();
const userWallets = useUserWallets();
return (
<>
{userWallets.length > 0 && (
Connected Wallets
{userWallets.length < 2 && }
{userWallets.map((wallet) => (
{wallet.address}
))}
)}
>
);
};
```
#### Detect which wallet is primary
How: You can grab it directly from useDynamicContext
Hook/Component: [useDynamicContext](/react-sdk/hooks/usedynamiccontext)
```jsx
import { useDynamicContext, useUserWallets } from "@dynamic-labs/sdk-react-core";
const { primaryWallet } = useDynamicContext();
console.log(primaryWallet);
```
#### Allow user to link a new wallet
How: Pop up our linking modal using a hook (setShowLinkNewWalletModal)
Hook/Component: [useDynamicModals](/react-sdk/hooks/usedynamicmodals)
Notes:
* You will need to also use the DynamicMultiWalletPromptsWidget component
```jsx
import {
useDynamicModals,
DynamicMultiWalletPromptsWidget,
} from "@dynamic-labs/sdk-react-core";
const LinkWallet = ({ text }) => {
const { setShowLinkNewWalletModal } = useDynamicModals();
return (
<>
>
);
};
```
#### Switch the primary wallet to another
How: useSwitchWallets hooks
Hook/Component: [https://docs.dynamic.xyz/react-sdk/hooks/useswitchwallet#useswitchwallet](https://docs.dynamic.xyz/react-sdk/hooks/useswitchwallet#useswitchwallet)
Notes: None
```jsx
import React from "react";
import { useSwitchWallet, useUserWallets } from "@dynamic-labs/sdk-react-core";
const WalletSwitcher = () => {
const switchWallet = useSwitchWallet();
const userWallets = useUserWallets();
return (
{userWallets.map((wallet) => (
))}
);
};
```
## Wallet Interactions
To learn how to send balance, sign messages etc., please check out [the Wallet Interaction guides](/wallets/using-wallets/interacting-with-wallets) where we split into Read actions and Write actions, and we provide per chain examples for common functionality.
# Welcome
Welcome to Dynamic! We're so excited that you're here!
### Versioning
This documentation is for V3 of the SDK. Therefore we assume you have [the latest version of V3](https://www.npmjs.com/package/@dynamic-labs/sdk-react-core?activeTab=versions) installed. If you are using V2, please refer to the [V2 documentation](https://v2.docs.dynamic.xyz).
Please also make sure you have all @dynamic/labs packages on exactly the same version to avoid any issues, and use [Dynamic Doctor](/troubleshooting/dynamic-doctor) for easy debugging. Happy building!
### What is Dynamic?
Dynamic offers smart and beautiful login flows for crypto-native users, simple
onboarding flows for everyone else, and powerful developer tools that go beyond
authentication.
Watch a product walkthrough
Play around with the SDK
Get up and running in minutes
Be part of the Dynamic Slack group
#### Common Docs
Some of the common next steps after completing the quickstart etc. above are:
* [Configuring the Signup page](/design-customizations/signup-login)
* [Start interacting with wallets](/wallets/using-wallets/interacting-with-wallets)
* [Customizing the look and feel of the UI](/design-customizations)
* [Adding custom EVM Networks](/chains/evmNetwork)
* [Collecting custom information](/custom-fields/overview)
* [Adding smart wallet support](/smart-wallets/add-smart-wallets)
#### Support & Feedback
You can reach out to us at any time for questions, issues, concerns, product
ideas, or random thoughts. Here are some ways to do so:
Join our Slack community
Send us a tweet or DM us any time.
Email us at [hello@dynamic.xyz](mailto:hello@dynamic.xyz)
Bug our founders at any point at [founders@dynamic.xyz](mailto:founders@dynamic.xyz) (tell us anything that's on your mind - the good and the bad).
# Migrating to Dynamic
Migration from one auth and embedded wallet provider to another can seem daunting. You have users, they might have wallets, and a subset of them will have assets in their wallets, making it a fun adventure of a migration. But have no fear! We can help. Below, we put together a guide on migration strategies you can think through depending on your use case. At the end of the guide you'll also find some helpful tips and techniques to ensure the migration all goes smoothly.
When in doubt, talk to us. Migrations can get complex, and we'd love to help
guide you through them. You can [set up a call with us
here](https://www.dynamic.xyz/talk-to-us)
If you don't want to migrate yet, but want to take advantage of Dynamic on top of your existing provider, you can also use [our Third Party Authentication feature](/authentication-methods/third-party-auth).
We put together a simple flow chart to help you identify the right migration strategy:
## Migration Path A: You only have a front-end "connect wallet" flow
### Common scenarios
You built your own front end wallet connector or use Rainbowkit/ConnectKit. There is no SIWE, no issuing JWTs, and mostly just a front end.
### Recommended approach
You pretty much just swap out the modals. If you use wagmi, add our [wagmi connector](/adding-dynamic/using-wagmi#-install-wagmi) and follow our [rainbowkit guide](/migrations/tutorials/rainbowkit), but in general, no migration needed. After the initial update to the Dynamic modal, you can stay on our [connect-only mode](/wallets/advanced-wallets/connect) and not store any user info with Dynamic, or alternatively start adding more sophisticated features.
## Migration Path B: You store wallet info somewhere and a wallet is a user
### Common scenarios
You built your own front end wallet connector or use Rainbowkit/ConnectKit, and in addition implemented a SIWE on your own, authenticating users and creating sessions for them.
### Recommended approach
For front end changes, you can follow path A above. In addition, you can use our [create wallet](/api-reference/endpoints/wallets/createWallet) endpoint to generate users and add wallet records to them. When they first log in, they will verify wallet ownership via SIWE (we provide that and return a JWT - you don't need to do this), and you're done.
## Migration Path C: You have users, but no embedded wallets
### Common scenarios
You use Auth0, Firebase or another web2 user management system, or alternatively created something on your own. You store your users' emails and basic info.
### Recommended approach
You'll need to import your users into Dynamic
1. [Bulk upsert your users into Dynamic](/api-reference/endpoints/users/bulkCreateUser), including their info capture/kyc information and their data
2. For any profile information that Dynamic does not yet explicitly support, you can add these in the user.metadata, which is a key-value object
3. When they log in, we verify their emails
And that's it. You're done. When your users log in next time around, we will verify their email addresses/social login information independently as part of the auth flow, and issue a JWT once you have successfully logged in.
## Migration Path D: You have users, and they have embedded EOA wallets
### Common scenarios
You use an embedded wallets provider, or have built a simple KMS-based key management system on your own
### Recommended approach
**There are really two options here:**
1. Keep current users in their existing embedded wallets and generate Dynamic wallets for new users
2. Migrate existing wallet users to Dynamic
**Keep current users:**
1. Switch to Dynamic auth
2. When user logs in with email, check if they have an existing account:
* If so, redirect them to old login flow
* If they don't, create a Dynamic-powered embedded wallet for them
```typescript
import { useEmbeddedWallet } from "@dynamic-labs/sdk-react-core";
// component declaration and all other logic you might need
const { createEmbeddedWallet } = useEmbeddedWallet();
const onCreateWalletHandler = async () => {
try {
await createEmbeddedWallet();
} catch (e) {
console.error(e);
}
};
```
**Migrate users:**
1. You'll need to import your users into Dynamic (the previous section)
2. When a user logs in, check if they have a wallet in the previous system
3. Check if wallet was ever used/is empty
4. If it isn't, prompt user to transfer funds to new Dynamic wallet (this can feel like an account upgrade flow for the users)
5. Next time that the user logs in, they go to their Dynamic wallet
## Migration Path E: You have users, and they have AA wallets
### Considerations
1. Your users have an EOA embedded wallet, but it only serves as a signer for an AA layer (safe, zerodev etc).
2. Hence, you'll want to add the Dynamic-powered embedded wallet as a second signer to the AA layer, or alternatively swap signers from the old embedded wallet one to the Dynamic one
**Main activities**
You'll want to have the end user add their new Dynamic wallet as a second signer to the smart contract wallet, or alternatively have them replace the current signer with a second signer
**Steps**
This is non-trivial, and we recommend that we chat before starting this
process. [You can book time here](https://www.dynamic.xyz/talk-to-us)
## Extra Migration Techniques/Tips
### Asset Transfers
If your users have assets in their existing embedded wallets, then you'll want to ensure they move those over to their new Dynamic-powered embedded wallets. To do this, you'll want to build a migration flow for your users either using your own UI to prompt asset transfer (recommended), or by sending them to a tool such as [BulkSender](https://bulksender.app/).
### Webhooks
As you import your users into Dynamic, you may want to notify other systems and take further action - webhooks allow you to do exactly this. We have events such as user.created, user.updated, wallet.created and many more (find the full list [here](/developer-dashboard/eventTypes)).
Setting them up is as simple as providing a URL to post to [in the Dynamic Dashboard](https://app.dynamic.xyz/dashboard/developer/webhooks) and selecting which events you'd like to subscribe to (there's a full guide [here](/developer-dashboard/webhooks)).
# Migrating Users via CSV
Dynamic.xyz offers a versatile platform for managing users and their associated data. If you're transitioning from another system, dynamic.xyz's API simplifies the process of migrating these users. The API is flexible and does not require any specific field to create a user, you only need at least one field. This guide will demonstrate how to migrate users from a data source, such as a CSV file, into dynamic.xyz's user management system using TypeScript.
### Step 1: Get an API Token
You'll first need to obtain an API token from the dynamic.xyz dashboard. Follow the instructions provided in the [API reference](/api-reference/overview#get-an-api-token-from-dashboard) to get your token.
### Step 2: Prepare Your Data Source
Prepare a data source, such as a CSV file, with your users' data. In this guide, we'll use a CSV file as an example, but remember that you can use any data source that you can read programmatically. The file needs to contain at least one field for each user.
### Step 3: Write a Script to Send Requests
You can use a script to read from your data source and send POST requests to the dynamic.xyz create user API endpoint. Here's an example script in TypeScript using `axios` and `csv-parser`:
```typescript
import axios from "axios";
import fs from "fs";
import csv from "csv-parser";
// Replace with your actual API token and environment ID
const API_TOKEN = "YOUR_API_TOKEN";
const ENVIRONMENT_ID = "<>";
// URL for the API endpoint
const url = `https://app.dynamic.xyz/api/v0/environments/${ENVIRONMENT_ID}/users`;
// Set up headers for the request
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${API_TOKEN}`,
};
// Open the CSV file and read the data
fs.createReadStream("users.csv")
.pipe(csv())
.on("data", async (row) => {
// Prepare the user data for the request
const user_data = {
field: row["field"], // replace 'field' with the actual field name from your CSV
};
// Send the POST request to the API
try {
const response = await axios.post(url, user_data, { headers });
console.log(
`User created successfully: ${JSON.stringify(response.data)}`
);
} catch (error) {
console.error(`Failed to create user. Error: ${error}`);
}
});
```
In the above TypeScript code, replace `'users.csv'` with the path to your actual data source, and replace `'YOUR_API_TOKEN'` and `'YOUR_ENVIRONMENT_ID'` with your actual API token and environment ID. Also, replace `'field'` with the name of the actual field you're using from your data source.
Please note that this is a basic script and does not handle errors robustly. In a production environment, you'd want to add error handling code to deal with potential issues such as network errors, API rate limits, and incorrect data in your data source.
Remember to install the necessary libraries if you haven't already done so:
```shell
npm install axios csv-parser
```
For more information on the user creation API endpoint, refer to the [API reference](/api-reference/endpoints/users/createUser).
# Migration from RainbowKit
Rainbowkit is a popular wallet adapter library built on Wagmi. That said, it has several limitations in terms of flexibility, customizability etc. As your company scales, it makes sense to explore transitioning to Dynamic for wallet auth. In this guide, we'll cover how you can quickly swap out Rainbowkit for Dynamic.
### A few notes
1. Rainbowkit uses wagmi for all functions. Dynamic can leverage wagmi as well, requiring a quick connector to coordinate between the libraries.
2. Rainbowkit requires that you manage your own authentication. Dynamic, on the other hand, comes with auth built in and will return a JWT if you select the connect and auth option.
### Overview
To start, we will assume your current setup looks similar to this:
```jsx
const App = () => {
return (
);
};
```
In this setup, you have 3 dependencies:
1. Ethers.js
2. Wagmi
3. Rainbowkit
### Step 1: Set up the Dynamic SDK
```bash Shell
npm install @dynamic-labs/sdk-react-core
```
> 📘 Note: Dynamic comes with ethers.js as part of its package, meaning you can remove any independent ethers.js installation.
You can read the full Dynamic set up guide [here](/quickstart).
### Step 2: Set up the Dynamic Wagmi connector
```bash Shell
npm install @dynamic-labs/wagmi-connector
```
You can read the full wagmi guide [here](/adding-dynamic/using-wagmi).
### Step 3: Configure Dynamic instead of Rainbowkit
#### Replace `RainbowKitProvider` with `DynamicContextProvider`
> 📘 Note: you'll need to grab a Dynamic environment ID from your [developer dashboard](https://app.dynamic.xyz)
First, we will swap out the Rainbowkit provider with the Dynamic one.
```jsx
const App = () => {
return (
);
};
```
#### Replace `WagmiConfig` with `DynamicWagmiConnector`
As mentioned in the full wagmi guide above, the `DynamicWagmiConnector` replaces `wagmiConfig` in the Dynamic setup. Note that the Dynamic provider wraps the Wagmi connector, vs wagmi wrapping the Rainbowkit adapter.
```jsx
const App = () => {
return (
);
};
```
#### Replace `ConnectButton` with `DynamicWidget`
Dynamic uses the Dynamic widget to manage wallets, connect, user profile etc. We will replace the Rainbowkit ConnectButton with it:
```jsx
export const App = () => {
return (
);
};
```
**That's it! you should now have Dynamic working instead of Rainbowkit.**
### Things to know
#### Authenticating with Dynamic vs Rainbowkit
As mentioned above, Dynamic comes built in with authentication. That means that as part of a Dynamic setup, you receive optional access to user management functions, as well as a JWT that contains verified credentials for the user.
Optionally, you can chose to keep the only connect-only experience of Rainbowkit. To do so, you can add to the settings \`initialAuthenticationMode: 'connect-only'. You can read more about connect-only vs authentication[here.](/wallets/advanced-wallets/connected-vs-authenticated)
#### Chains and customization
Rainbowkit configures chains within code using wagmi. Dynamic, on the other hand, pulls its configuration from your [developer dashboard](https://app.dynamic.xyz/dashboard/configurations).
# Quickstart
Let's get you up and running with a React based signup/login UI! Just [sign up](https://app.dynamic.xyz) and follow the guide below!
You can also refer to one of [our sample apps](/example-apps) if you want to
clone a full application rather than embed into an existing one!
Dynamic uses Viem v2, with optional support for Wagmi v2. To use older
versions of Viem and Wagmi, follow [this
guide](https://v1.docs.dynamic.xyz/quickstart).
## Select your SDK
## Install the SDK
EthereumWalletConnectors also includes all EVM compatible chains including
layer 2's i.e. Base as well as [Dynamic Embedded
Wallets](/wallets/embedded-wallets/dynamic-embedded-wallets). Learn more about WalletConnectors [here](/react-sdk/providers/dynamiccontextprovider#walletconnectors).
## Initialize the SDK
If you are using Wagmi, make sure to read our [full Wagmi
guide](/adding-dynamic/using-wagmi) for more setup instructions.
If you're using Vite, you'll need to check out [the polyfills guide](/troubleshooting/react/vitejs-polyfills-necessary-for-dynamic-sdk).
If you see any other errors, it's worth checking out [Dynamic Doctor](/troubleshooting/dynamic-doctor) for quick debugging tips!
To quickly test the login flow, you can enable [Test
Accounts](/developer-dashboard/test-accounts) in Sandbox mode.
## Celebrate & Learn More!
That's it! You should now have an initial working version of the Dynamic login flow. You can now configure different kinds of signup flows including [Embedded wallets](/wallets/embedded-wallets/dynamic-embedded-wallets) and [Email, Social & SMS](/authentication-methods/email-social-sms).
You can also [join our Slack Community](https://www.dynamic.xyz/slack) to ask us
anything on your mind.
# Account Abstraction with ZeroDev
When using account abstraction in your project to sponsor gas, batch transactions, or any other account abstraction features, you will likely want to get a ZeroDev kernel client to perform these operations.
You can use the `@dynamic-labs/zerodev-extension` package to create a ZeroDev kernel client for a wallet.
Here is how you can set up and create a kernel client.
The `@dynamic-labs/zerodev-extension` depends on the Viem Extension, so before going through this setup, make sure to have the Viem Extension set up and working. [Viem Extension Setup](/react-native/viem)
# Setup
## Install ZeroDevExtension
Install the @dynamic-labs/zerodev-extension package:
```bash Shell
npx expo install @dynamic-labs/zerodev-extension@alpha
```
```bash Shell
npm install @dynamic-labs/zerodev-extension@alpha
```
```bash Shell
yarn add @dynamic-labs/zerodev-extension@alpha
```
## Resolve File Resolution Error (Optional)
When running the ZeroDevExtension in your React Native application, you might encounter an error where Metro cannot resolve the `paymasterClient.js` file. This issue occurs because Metro tries to load `paymasterClient.js`, but the actual file is named `paymasterClient.ts`.
To fix this, you need to customize Metro’s resolver in your `metro.config.js` file to handle TypeScript file extensions properly.
### Generate metro.config.js for Expo Projects
If you don’t have a `metro.config.js` file in your project, you can generate one using the following command:
```bash
npx expo customize metro.config.js
```
### Customize Metro Resolver
Add the following code to your `metro.config.js` file to instruct Metro to resolve `.ts` files when it cannot find the corresponding `.js` files:
```js
// metro.config.js
/**
* Custom resolver to handle ZeroDev imports
*/
config.resolver.resolveRequest = (context, moduleName, platform) => {
try {
return context.resolveRequest(context, moduleName, platform);
} catch (error) {
if (moduleName.endsWith(".js")) {
const tsModuleName = moduleName.replace(/\.js$/, ".ts");
return context.resolveRequest(context, tsModuleName, platform);
}
throw error;
}
};
```
This modification allows Metro to attempt to resolve `.ts` files when it fails to find `.js` files, which fixes the resolution error for the `paymasterClient` file.
## Events Polyfill
ZeroDevExtension requires a polyfill for the Node.js events module. You can install the [events](https://www.npmjs.com/package/events) polyfill using one of the following commands:
```bash Shell
npx expo install events
```
```bash Shell
npm install events
```
```bash Shell
yarn add events
```
## Integrate with your Dynamic client
To include the ZeroDev module in your Dynamic client, you need to extend the client with the ZeroDev extension:
```typescript
import { createClient } from "@dynamic-labs/client";
import { ViemExtension } from "@dynamic-labs/viem-extension";
import { ZeroDevExtension } from "@dynamic-labs/zerodev-extension";
const dynamicClient = createClient({
environmentId: "",
})
.extend(ViemExtension())
.extend(ZeroDevExtension());
```
Now your setup is complete, and you have the ZeroDev module available in your Dynamic client.
# Usage
Now that you have the ZeroDev module in your Dynamic client, you can get the ZeroDev kernel client by using the `dynamicClient.zeroDev.createKernelClient` method. Here is an example:
```typescript
// dynamicClient.ts
const dynamicClient = createClient({
environmentId: "",
})
.extend(ViemExtension())
.extend(ZeroDevExtension());
// getPrimaryWalletKernelClient.ts
const getPrimaryWalletKernelClient = async () => {
const primaryWallet = dynamicClient.wallets.primaryWallet;
if (!primaryWallet) {
throw new Error('No primary wallet');
}
const kernelClient = await dynamicClient.zeroDev.createKernelClient({
wallet: primaryWallet
});
return kernelClient;
}
```
# Examples
## Batch transaction example
This example demonstrates how you can use a ZeroDev kernel client to perform a batched transaction:
```typescript
import { encodeFunctionData } from 'viem';
const contractAddress = '0x123';
const contractABI = [
{
inputs: [{ internalType: 'address', name: '_to', type: 'address' }],
name: 'mint',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
const sendBatchedTransactions = async () => {
const primaryWallet = dynamicClient.wallets.primaryWallet;
if (!primaryWallet) {
throw new Error('No primary wallet');
}
const kernelClient = await client.zeroDev.createKernelClient({
wallet: primaryWallet,
});
const { account } = kernelClient;
const hash = await kernelClient.sendUserOperation({
account,
userOperation: {
callData: await account.encodeCallData([
{
data: encodeFunctionData({
abi: contractABI,
args: [primaryWallet.address],
functionName: 'mint',
}),
to: contractAddress,
value: BigInt(0),
},
{
data: encodeFunctionData({
abi: contractABI,
args: [primaryWallet.address],
functionName: 'mint',
}),
to: contractAddress,
value: BigInt(0),
},
]),
},
});
return hash;
}
```
# Setting up the Dynamic Client
The Dynamic Client is a vanilla javascript package that allows users to quickly set up all the configurations
needed to get an interface that allows them to interact with our features.
It provides a very simple, bare bones client object through the call to `createClient`.
This client can then be extended with many other features, like React Native support and viem integration.
## Installation
Simply run the following in your terminal:
```bash Shell
npm install @dynamic-labs/client@alpha
```
```bash Shell
npx expo install @dynamic-labs/client@alpha
```
```bash Shell
yarn install @dynamic-labs/client@alpha
```
## Set up
Now create your client with the following code:
```typescript
import { createClient } from '@dynamic-labs/client'
export const dynamicClient = createClient({
environmentId: 'YOUR-ENVIRONMENT-ID',
// Optional:
appLogoUrl: 'https://demo.dynamic.xyz/favicon-32x32.png',
appName: 'Dynamic Demo',
})
```
You can read more about our client package [here](/react-native/package-references/client).
# Deeplink URLs
In order to use most of our react native features (such as social connections), you will first
have to set up the list of mobile deeplink URLs dynamic may use to redirect back to your app.
For security reasons, when deeplinking back to your app, we must verify whether the app's deeplink URL
is included in this list. If it isn't, Dynamic will not perform the deeplink.
## How to set up your app for deeplinking
### Configuring your app's custom scheme
First, you should follow Expo's guide on enabling deeplinking for your app
[here](https://docs.expo.dev/guides/linking/#linking-to-your-app).
### Registering your app's deeplink URL with Dynamic
Afterwards, head to your Dynamic dashboard's
[Security page](https://app.dynamic.xyz/dashboard/security) and enable "Whitelist Mobile Deeplink".
Click "Save changes".
Next, click the cog in this same section to open up the
[Mobile Deeplink URL page](https://app.dynamic.xyz/dashboard/security#mobile-deeplink-urls), and there
you must add your app's deeplink URL with the same custom scheme you configured in the previous step.
It might look something like this: `myappcustomscheme://`.
With this, you're done! Dynamic is ready to deeplink back to your app.
# Using Dynamic's UI
Besides all of our headless methods, we also provide a compreehensive set of UI modals that can be easily leveraged
to achieve the same functionalities.
## Using Dynamic's modals
You can leverage our authentication flow modal to sign your users in — this will bring our sign in modal on top of
your app, and once the user has successfully signed in, it will automatically close.
You can show the authentication modal by calling:
```typescript
dynamicClient.ui.auth.show()
```
To hide the authentication modal programmatically, you can use:
```typescript
dynamicClient.ui.auth.hide()
```
After the user has signed in successfully, you can also bring up our user profile interface, which allows your user
to view their wallet's address and balance, as well as editing their fields like email and phone number.
To show the user profile modal, call:
```typescript
dynamicClient.ui.userProfile.show()
```
To hide the user profile modal programmatically, use:
```typescript
dynamicClient.ui.userProfile.hide()
```
You can read more about our UI modules [here](/react-native/package-references/client#ui-module).
# Email and SMS verification
The dynamic client authentication module enables user authentication via email or SMS.
Notice that email and SMS login methods **must be respectively enabled** in
your environment's dashboard settings first!
## Sending and verifying OTPs
The methods below can be used to send/resend and verify one time passwords
```typescript
// Send, retry and then verify OTP to an email
await dynamicClient.auth.email.sendOTP('someone@gmail.com')
await dynamicClient.auth.email.resendOTP()
await dynamicClient.auth.email.verifyOTP('received-token')
// Send, retry and then verify OTP to a phone number
await dynamicClient.auth.sms.sendOTP({
dialCode: '1',
iso2: 'us',
phone: '2793334444',
})
await dynamicClient.auth.sms.resendOTP()
await dynamicClient.auth.sms.verifyOTP('received-token')
```
### Example
The example below demonstrates a React component that can send, resend and verify OTPs through email.
You can follow the same pattern for phone numbers but collect phone number, iso2 and dial code instead
of email address.
```typescript
import { dynamicClient } from '';
const EmailSignIn: FC = () => {
const [email, setEmail] = useState('')
const [otp, setOtp] = useState('')
const [otpSent, setOtpSent] = useState(false)
const handleSendOTP = async () => {
await dynamicClient.auth.email.sendOTP(email)
setOtpSent(true)
}
const handleResendOTP = () => {
dynamicClient.auth.email.resendOTP()
}
const handleVerifyOTP = () => {
dynamicClient.auth.email.verifyOTP(otp)
}
return (
{!otpSent ? (
) : (
)}
)
}
```
You can read more about these modules [here](/react-native/package-references/client#auth-email-submodule).
# Embedded wallets
The dynamic client provides an interface to easily interact with and create embedded wallets for your users.
Notice that embedded wallets **must be enabled** in your environment's
dashboard settings first!
## Creating and Getting the Wallet
This interface allows you to create new embedded wallets, check if any already exist and fetch the current wallet.
See the following example which renders a prompt to create a wallet or renders its address if it already exists:
```typescript
import { dynamicClient } from '';
import { useReactiveClient } from '@dynamic-labs/react-hooks';
const EmbeddedWallet: FC = () => {
const { wallets } = useReactiveClient(dynamicClient)
const wallet = wallets.userWallets[0]
return (
{wallet && wallets.embedded.hasWallet ? (
Your wallet address: {wallet.address}
) : (
)}
)
}
```
## Creating a Wallet for a Specific Chain
The `createWallet` method also allows you to create an embedded wallet for a specific chain
See the example below:
```typescript
import { dynamicClient } from '';
import { useReactiveClient } from '@dynamic-labs/react-hooks';
const AddEvmWalletButton: FC = () => {
return (