# Authentication
Source: https://docs.streambird.io/api-reference/authentication
Learn how to authenticate your API requests to MoonKey
## Overview
The MoonKey Auth API uses **Bearer token authentication**. All Users, Phone Numbers, Emails, and OTPs are associated with an `App` as the container.
Most endpoints require your App's **Secret API Key**, while certain client-side endpoints (used via SDK) accept a **Public Token** instead.
## API Keys
MoonKey provides two types of API keys:
* **Secret API Keys** (`sk_test_...` or `sk_live_...`) - Used for server-side API calls. Keep these secure and never expose them in client-side code.
* **Public Tokens** - Used for client-side SDK integration. Safe to use in frontend applications.
Never share your secret API keys in publicly accessible areas such as GitHub, client-side code, or browser inspector.
## Making Authenticated Requests
Include your API key in the `Authorization` header of your requests using the Bearer scheme:
### Header Format
```bash theme={null}
Authorization: Bearer {api_key}
```
### Example Request
```bash theme={null}
curl -X GET https://api.moonkey.fun/v1/auth/users/user_24wFP9pDa9YiMJLun94iKykoZs2 \
-H "Authorization: Bearer sk_test_pRqweh3wvWmJAAVYv7Z0T5iPLzFM4ql0muoyQcjOxGeN3p1r"
```
### Example with JavaScript
```javascript theme={null}
const response = await fetch('https://api.moonkey.fun/v1/auth/users/user_24wFP9pDa9YiMJLun94iKykoZs2', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_test_pRqweh3wvWmJAAVYv7Z0T5iPLzFM4ql0muoyQcjOxGeN3p1r',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
### Example with Python
```python theme={null}
import requests
url = "https://api.moonkey.fun/v1/auth/users/user_24wFP9pDa9YiMJLun94iKykoZs2"
headers = {
"Authorization": "Bearer sk_test_pRqweh3wvWmJAAVYv7Z0T5iPLzFM4ql0muoyQcjOxGeN3p1r",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
data = response.json()
```
## Test vs Live Keys
* **Test Keys** (`sk_test_...`) - Use these keys for development and testing. They won't affect production data.
* **Live Keys** (`sk_live_...`) - Use these keys for production. All operations will affect real users and data.
## Getting Your API Keys
You can find your API keys in the MoonKey Dashboard:
1. Log in to your [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Navigate to **Settings** → **API Keys**
3. Copy your test or live API key
4. Use it in your API requests as shown above
Rotate your API keys regularly and immediately revoke any keys that may have been compromised.
# Create Embeddable Magic Link
Source: https://docs.streambird.io/api-reference/magic-links/create-embeddable-magic-link
post /v1/auth/magic_links/create
Create an embeddable token for Magic Link for a specific user. The token can be used with a custom email or any other methods of delivery to your user.
## Returns
A successful response returns an object with `user_id`, `token` properties.
**Deprecated:** Magic Links are deprecated and will be removed in a future version. Please use [One-time Passcodes (OTP)](/methods/otp/overview) or [OAuth](/methods/oauth/overview) for passwordless authentication instead.
# Invite User by Magic Link
Source: https://docs.streambird.io/api-reference/magic-links/invite-user-by-magic-link
post /v1/auth/magic_links/email/invite
Create an email Magic Link to the provided email for invite verification. If no user account exists, a new user will be created and Magic Link sent.
## Returns
A successful response returns an object with `user_id` and `email_id` properties.
**Deprecated:** Magic Links are deprecated and will be removed in a future version. Please use [One-time Passcodes (OTP)](/methods/otp/overview) or [OAuth](/methods/oauth/overview) for passwordless authentication instead.
# Login or Create User by Magic Link
Source: https://docs.streambird.io/api-reference/magic-links/login-or-create-user-by-magic-link
post /v1/auth/magic_links/email/login_or_create
Create an email Magic Link to the provided email for login verification. If no user account exists, a new user will be created and Magic Link sent.
## Returns
A successful response returns an object with `user_id`, `user_created` indicating if the user has been newly created, and `email_id` properties.
**Deprecated:** Magic Links are deprecated and will be removed in a future version. Please use [One-time Passcodes (OTP)](/methods/otp/overview) or [OAuth](/methods/oauth/overview) for passwordless authentication instead.
# Verify Token
Source: https://docs.streambird.io/api-reference/magic-links/verify-token
post /v1/auth/magic_links/verify
Verify the magic token of a magic link to authenticate the user. This endpoints verifies that the magic token sent in is valid and returns the method ID and verified user ID.
## Returns
A successful response returns an object with `method_id`, `method_type`, and verified `user_id` properties. If session parameters are sent in, `session_token`, `session_jwt` and `session` will be included in the response.
**Deprecated:** Magic Links are deprecated and will be removed in a future version. Please use [One-time Passcodes (OTP)](/methods/otp/overview) or [OAuth](/methods/oauth/overview) for passwordless authentication instead.
# Create Wallet
Source: https://docs.streambird.io/api-reference/managed-wallets/create-wallet
post /v1/wallets/create
Create a wallet for a given user. If an existing wallet of the given wallet type has been created for that user, it will be returned.
## Returns
A successful response returns an `Wallet` object.
# Ethereum Sign EIP-7702 Authorization
Source: https://docs.streambird.io/api-reference/managed-wallets/ethereum/sign-7702-authorization
POST https://api.moonkey.fun/v1/wallets/{wallet_id}/rpc
Sign an EIP-7702 authorization using a managed wallet. An EIP-7702 authorization allows an externally owned account (EOA) to authorize a delegation / contract address without signing or submitting a transaction.
The private key **never leaves the vault** and is never exposed to the client.
## Path Parameters
Unique ID of the wallet used to sign the authorization.
## Request Body
The request uses an **Ethereum JSON-RPC–compatible payload**.
Must be `eth_sign7702Authorization`.
Parameters used to construct the authorization.
Delegated contract address being authorized (hex, `0x`-prefixed).
Chain ID the authorization is valid for.
Authorization nonce.
All fields are signed exactly as provided after normalization. Changing any value invalidates the signature.
## Response
RPC method that was executed (`eth_sign7702Authorization`).
Signed authorization payload.
The signed authorization object.
Chain ID the authorization is valid for, returned as a string representation of the number.
Authorized contract address.
Authorization nonce, returned as a string representation of the number.
ECDSA recovery identifier.
ECDSA signature `r` value (hex).
ECDSA signature `s` value (hex).
Signature y-parity (`0` or `1`).
## Using the Signed Authorization
The returned signature can be included in an EIP-7702 transaction as part of an `authorizationList`, sent to a relay or bundler, or stored and replayed until the nonce is consumed.
To submit on-chain, recombine the signature parts into a standard Ethereum signature:
```
signature = r || s || yParity
```
## Notes
* This method does not broadcast a transaction.
* The authorization is off-chain until included in a transaction.
* Private keys are never exposed.
* The same authorization can be reused until its nonce is consumed.
* Input values for `chainId` and `nonce` may be provided as hex strings or decimal strings/numbers.
* Response values for `chainId` and `nonce` are **always returned as strings**.
* This endpoint signs EIP-712 typed data under the EIP-7702 domain.
```bash cURL theme={null}
curl -X POST https://api.moonkey.fun/v1/wallets/wallet_36kLvUkIIvyQPbdVr7Ackvh726L/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "eth_sign7702Authorization",
"params": {
"contractAddress": "0x1234567890abcdef1234567890abcdef12345678",
"chainId": "1",
"nonce": "0"
}
}'
```
```json 200 theme={null}
{
"method": "eth_sign7702Authorization",
"data": {
"authorization": {
"chainId": "1",
"contractAddress": "0x1234567890abcdef1234567890abcdef12345678",
"nonce": "0",
"v": "1",
"r": "0x2183aed038b571552e7692ebcc2b27578354c95dc6ea7e2ed1cb0fc46e5f37f9",
"s": "0x2b11a93b69e8abfd3db613ed617520ea6826806c856199bdd818563d4c01d202",
"yParity": 1
}
}
}
```
# Ethereum Sign Message
Source: https://docs.streambird.io/api-reference/managed-wallets/ethereum/sign-message
POST https://api.moonkey.fun/v1/wallets/{wallet_id}/rpc
Sign a personal message using a managed wallet. Managed wallet means the private key is never exposed to the client and always stored encrypted in our vault.
## Path Parameters
Unique wallet ID of the wallet used to sign the message.
## Request Body
Must be `eth_personalSign`.
Parameters for the RPC call.
Message to be signed.
Encoding of the input message. Supported values: `utf-8`, `hex`. Defaults to `utf-8`.
## Response
RPC method that was executed (`eth_personalSign`).
Signed message data.
Always `hex`.
Hex-encoded EIP-191 personal message signature.
```bash cURL theme={null}
curl -X POST https://api.moonkey.fun/v1/wallets/wallet_36kLvUkIIvyQPbdVr7Ackvh726L/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "eth_personalSign",
"params": {
"message": "test"
}
}'
```
```json 200 theme={null}
{
"method": "eth_personalSign",
"data": {
"signature": "0x1bc639199849d4afd5d3a32de9c5dffa236529245166baf00b1df871b55693b71c0d5c609c01714bcc0042ccca287b2e4ca1b79f5e89d6a9f8496726a5fddb961c",
"encoding": "hex"
}
}
```
# Ethereum Sign Transaction
Source: https://docs.streambird.io/api-reference/managed-wallets/ethereum/sign-transaction
POST https://api.moonkey.fun/v1/wallets/{wallet_id}/rpc
Sign an Ethereum transaction using a managed wallet. A managed wallet means the private key is never exposed to the client and is always stored encrypted in the vault.
## Path Parameters
Unique ID of the wallet used to sign the transaction.
## Request Body
The request uses an Ethereum JSON-RPC–compatible payload.
Must be `eth_signTransaction`.
Parameters for the RPC call.
The transaction object is compatible with the Ethereum `eth_signTransaction` / MetaMask transaction format.
Recipient address (hex, 0x-prefixed).
Amount in wei (hex).
Transaction nonce (hex).
Gas price in wei (hex).
Gas limit (hex).
Ethereum chain ID (hex).
## Response
RPC method that was executed (`eth_signTransaction`).
Signed transaction data.
Encoding format of the signed transaction (`rlp`).
Fully signed, serialized raw transaction (hex). Can be broadcast using `eth_sendRawTransaction`.
Private keys are never exposed or returned.
```bash cURL theme={null}
curl -X POST https://api.moonkey.fun/v1/wallets/wallet_36kLvUkIIvyQPbdVr7Ackvh726L/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "eth_signTransaction",
"params": {
"transaction": {
"to": "0xE9534C9fAF3cDBc636c25E1981A80EfaE03f187a",
"value": "0x38d7ea4c68000",
"nonce": "0x05",
"gasPrice": "0xdf8475800",
"gas": "0x5208",
"chainId": "0x5"
}
}
}'
```
```json 200 theme={null}
{
"method": "eth_signTransaction",
"data": {
"encoding": "rlp",
"signed_transaction": "0xf86b05850df847580082520894e9534c9faf3cdbc636c25e1981a80efae03f187a87038d7ea4c68000802da086b0ec61705e3b7c583c09704317733a17dcb8a8101dd26c393a553214bede0da039e23209b1af42aba9e82d5f3601d0f1ba6155cb46004e968205cce54c6a241b"
}
}
```
# Get Wallet
Source: https://docs.streambird.io/api-reference/managed-wallets/get-wallet
get /v1/wallets/{wallet_id}
Get wallet using wallet ID.
## Returns
A successful response returns a `Wallet` object.
# Get Wallet Balance
Source: https://docs.streambird.io/api-reference/managed-wallets/get-wallet-balance
post /v1/wallets/balance
Get wallet balance of a specific token/asset for a specific wallet.
## Returns
A successful response returns a `balance` property and a `Wallet` object.
# List Wallet
Source: https://docs.streambird.io/api-reference/managed-wallets/list-wallet
get /v1/wallets/list
List wallets within the app.
## Returns
A successful response returns list of `Wallet` objects.
# Solana Sign Message
Source: https://docs.streambird.io/api-reference/managed-wallets/solana/sign-message
POST https://api.moonkey.fun/v1/wallets/{wallet_id}/rpc
Sign a message using a managed wallet. Managed wallet means the private key is never exposed to the client and always stored encrypted in our vault.
## Path Parameters
Unique wallet ID of the wallet used to sign the message.
## Request Body
Must be `sol_signMessage`.
Parameters for the RPC call.
Message to be signed.
Encoding of the input message. Possible values: `utf-8`, `base58`, `base64`. If not provided, the message is treated as plaintext (`utf-8`).
## Response
RPC method that was executed (`sol_signMessage`).
Signed message data.
Signature of the message.
Encoding of the returned signature. If request `encoding` is not provided, the response encoding is `base58`.
* If `encoding` is omitted, the message is interpreted as plaintext (`utf-8`).
* If `encoding` is omitted, the response encoding defaults to `base58`.
* If `encoding` is provided, the response uses the same encoding as the request but will be `base64` or `base58`.
* No private key material is ever exposed or returned.
```bash cURL theme={null}
curl -X POST https://api.moonkey.fun/v1/wallets/wallet_35ptpWOqO2SnacamII91ecIzmKT/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "sol_signMessage",
"params": {
"message": "test"
}
}'
```
```json 200 theme={null}
{
"method": "sol_signMessage",
"data": {
"signature": "53qEX5SmUJkmvLjTMvi45M4otThvZfd4QJiXa998eRJBqxGuvg9tHTqYCaBtvfLd4VRbpes5ukPNsrdC1Yz7ZMeU",
"encoding": "base58"
}
}
```
# Solana Sign Transaction
Source: https://docs.streambird.io/api-reference/managed-wallets/solana/sign-transaction
POST https://api.moonkey.fun/v1/wallets/{wallet_id}/rpc
Sign transaction using a managed wallet. Managed wallet means the private key is never exposed to the client and always stored encrypted in our vault.
## Path Parameters
Unique wallet ID of the wallet used to sign the transaction.
## Request Body
Must be `sol_signTransaction`.
Parameters for the RPC call.
Solana transaction encoded string.
Transaction encoding used. Possible values: `base58`, `base64`.
## Response
RPC method that was executed (`sol_signTransaction`).
Signed transaction data.
Encoding of the signed transaction.
Fully signed Solana transaction, encoded using the specified encoding. Can be submitted directly to Solana-compatible RPC nodes using the `sendTransaction` method.
```bash cURL theme={null}
curl -X POST https://api.moonkey.fun/v1/wallets/wallet_35ptpWOqO2SnacamII91ecIzmKT/rpc \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "sol_signTransaction",
"params": {
"encoding": "base64",
"transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAGDA08vWamfyIoyDhsI9yXdCvPecZ9v0v5euJrLB0K6fjbMxFRGHto/5mWD8A8pcP0XHqpGj8K0Vm5O245V+KqDgps+5nkIR4PjeMZwsR2+Qiefp0oEjWHZp3gMlVOPI7f0o2B49tF+KpAT2ief/W17eIyjcJ+S+7/nCvt5uidaWZUx6cy3d6gYOG8RvmFb/9ot878LyGTQ+hvRoA/69odNeDUycpxa+n4mgf3tLMujqWHDrG/9WPbc6RBUhzDToQWAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTj3l4txRFPNPoeusfwJvwZ/ls0tTWm1cTlaqb8Yav+do/jJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+Fm0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6IxiJRccY+n9NPLdZvDyol2uSO+Doy45hKJFKMtAVz2GBAcABQLAXBUABwAJA9dxAwAAAAAACgYABAAJBhEBAQgYEQADBAgJCAsIEwASDgMEDw0QEQEMBQIII+UXy5d6460qAQAAABpkAAFAQg8AAAAAAP1pLwAAAAAAMgAAAf1558D6beNj/gjdJig+JU7n5ciKJQoVvFO+Z0PkSEBBBfL2LzIuAwb1MQ=="
}
}'
```
```json 200 theme={null}
{
"method": "sol_signTransaction",
"data": {
"encoding": "base64",
"signed_transaction": "ASsuR7sDXtEMREFI4Vq2m4D7zH5aXEXkffVKB7b/A8JX+x0mRxI9UE8NkWuCUSIcf1DjKl06EY37RcOU5xVDugWAAQAGDA08vWamfyIoyDhsI9yXdCvPecZ9v0v5euJrLB0K6fjbMxFRGHto/5mWD8A8pcP0XHqpGj8K0Vm5O245V+KqDgps+5nkIR4PjeMZwsR2+Qiefp0oEjWHZp3gMlVOPI7f0o2B49tF+KpAT2ief/W17eIyjcJ+S+7/nCvt5uidaWZUx6cy3d6gYOG8RvmFb/9ot878LyGTQ+hvRoA/69odNeDUycpxa+n4mgf3tLMujqWHDrG/9WPbc6RBUhzDToQWAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTj3l4txRFPNPoeusfwJvwZ/ls0tTWm1cTlaqb8Yav+do/jJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+Fm0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6IxiJRccY+n9NPLdZvDyol2uSO+Doy45hKJFKMtAVz2GBAcABQLAXBUABwAJA9dxAwAAAAAACgYABAAJBhEBAQgYEQADBAgJCAsIEwASDgMEDw0QEQEMBQIII+UXy5d6460qAQAAABpkAAFAQg8AAAAAAP1pLwAAAAAAMgAAAf1558D6beNj/gjdJig+JU7n5ciKJQoVvFO+Z0PkSEBBBfL2LzIuAwb1MQ=="
}
}
```
# Apple
Source: https://docs.streambird.io/api-reference/oauth/apple
get /v1/auth/oauth/apple/begin
Client side **public endpoint** to generate a `redirect_url` for OAuth provider that will direct the user to sign in via Apple. User will first sign in to their Apple account, Apple will then call the MoonKey callback URL set during the setup process for Apple provider. Once MoonKey completes the OAuth flow with Apple, we will redirect back to the login/registration redirect URLs set for your App with an `internal token` for this session. You can then use this token to verify with our `VerifyOAuthToken` endpoint to retrieve the authenticated user and optionally the original `access_token` and `refresh_token` from Apple.
## Query String Example
``` json
/v1/auth/oauth/apple/begin?public_token=pk_live_bGcnsYLoObxCSvUcCNBEWgWkOFIBD6JQhx1bMTakf1R6QWrR&redirect=true&login_redirect_url=http://localhost:8080/register
```
## Returns
A successful response returns an object with a `redirect_url` property. If `redirect` query parameter is set to true, a response with status code 302 will be returned, which allows the browser to automatically redirect to the returned `redirect_url` without custom client side redirect logic.
# Discord
Source: https://docs.streambird.io/api-reference/oauth/discord
get /v1/auth/oauth/discord/begin
Client side **public endpoint** to generate a `redirect_url` for OAuth provider that will direct the user to sign in via Discord. User will first sign in to their Discord account, Discord will then call the MoonKey callback URL set during the setup process for Discord provider. Once MoonKey completes the OAuth flow with Discord, we will redirect back to the login/registration redirect URLs set for your App with an `internal token` for this session. You can then use this token to verify with our `VerifyOAuthToken` endpoint to retrieve the authenticated user and optionally the original `access_token` and `refresh_token` from Discord.
## Query String Example
``` json
/v1/auth/oauth/discord/begin?public_token=pk_live_bGcnsYLoObxCSvUcCNBEWgWkOFIBD6JQhx1bMTakf1R6QWrR&redirect=true&login_redirect_url=http://localhost:8080/register
```
## Returns
A successful response returns an object with a `redirect_url` property. If `redirect` query parameter is set to true, a response with status code 302 will be returned, which allows the browser to automatically redirect to the returned `redirect_url` without custom client side redirect logic.
# Github
Source: https://docs.streambird.io/api-reference/oauth/github
get /v1/auth/oauth/github/begin
Client side **public endpoint** to generate a `redirect_url` for OAuth provider that will direct the user to sign in via Github. User will first sign in to their Github account, Github will then call the MoonKey callback URL set during the setup process for Github provider. Once MoonKey completes the OAuth flow with Github, we will redirect back to the login/signup redirect URLs set for your App with an `internal token` for this session. You can then use this token to verify with our `VerifyOAuthToken` endpoint to retrieve the authenticated user and optionally the original `access_token` and `refresh_token` from Github.
## Query String Example
``` json
/v1/auth/oauth/github/begin?public_token=pk_live_bGcnsYLoObxCSvUcCNBEWgWkOFIBD6JQhx1bMTakf1R6QWrR&redirect=true&login_redirect_url=http://localhost:8080/register
```
## Returns
A successful response returns an object with a `redirect_url` property. If `redirect` query parameter is set to true, a response with status code 302 will be returned, which allows the browser to automatically redirect to the returned `redirect_url` without custom client side redirect logic.
# Google
Source: https://docs.streambird.io/api-reference/oauth/google
get /v1/auth/oauth/google/begin
Client side **public endpoint** to generate a `redirect_url` for OAuth provider that will direct the user to sign in via Google. User will first sign in to their Google account, Google will then call the MoonKey callback URL set during the setup process for Google provider. Once MoonKey completes the OAuth flow with Google, we will redirect back to the login/registration redirect URLs set for your App with an `internal token` for this session. You can then use this token to verify with our `VerifyOAuthToken` endpoint to retrieve the authenticated user and optionally the original `access_token` and `refresh_token` from Google.
## Query String Example
``` json
/v1/auth/oauth/google/begin?public_token=pk_live_bGcnsYLoObxCSvUcCNBEWgWkOFIBD6JQhx1bMTakf1R6QWrR&redirect=true&login_redirect_url=http://localhost:8080/register
```
## Returns
A successful response returns an object with a `redirect_url` property. If `redirect` query parameter is set to true, a response with status code 302 will be returned, which allows the browser to automatically redirect to the returned `redirect_url` without custom client side redirect logic.
# Microsoft
Source: https://docs.streambird.io/api-reference/oauth/microsoft
get /v1/auth/oauth/microsoft/begin
Client side **public endpoint** to generate a `redirect_url` for OAuth provider that will direct the user to sign in via Microsoft. User will first sign in to their Microsoft account, Microsoft will then call the MoonKey callback URL set during the setup process for Microsoft provider. Once MoonKey completes the OAuth flow with Microsoft, we will redirect back to the login/registration redirect URLs set for your App with an `internal token` for this session. You can then use this token to verify with our `VerifyOAuthToken` endpoint to retrieve the authenticated user and optionally the original `access_token` and `refresh_token` from Microsoft.
## Query String Example
``` json
/v1/auth/oauth/microsoft/begin?public_token=pk_live_bGcnsYLoObxCSvUcCNBEWgWkOFIBD6JQhx1bMTakf1R6QWrR&redirect=true&login_redirect_url=http://localhost:8080/register
```
## Returns
A successful response returns an object with a `redirect_url` property. If `redirect` query parameter is set to true, a response with status code 302 will be returned, which allows the browser to automatically redirect to the returned `redirect_url` without custom client side redirect logic.
# Verify Token
Source: https://docs.streambird.io/api-reference/oauth/verify-token
post /v1/auth/oauth/verify
Verify an internal OAuth token redirected to your application upon the completion of the OAuth flow. This token is generated by MoonKey and can be used to exchange for the authenticated user information and/or the original `access_token` and `refresh_token` of the idp providers that can be used directly with the external OAuth providers (e.g., Google, Apple, Microsoft, etc).
## Returns
A successful response returns `user_id`, `idp_session` property with data from the OAuth provider by default. `Session` object in `session` property if any of the `session_token`, `session_jwt`, or `session_expires_in` is valid.
# Create Email OTP
Source: https://docs.streambird.io/api-reference/otps/create-email-otp
post /v1/auth/otps/email/send
Send an OTP (one-time passcode) to the provided email for login verification.
## Returns
A successful response returns an object with `user_id` and `email_id` properties.
# Create SMS OTP
Source: https://docs.streambird.io/api-reference/otps/create-sms-otp
post /v1/auth/otps/sms/send
Send an SMS OTP (one-time passcode) to the provided phone number for verification.
## Returns
A successful response returns an object with `user_id` and `phone_number_id` properties.
# Login or Create User by Email OTP
Source: https://docs.streambird.io/api-reference/otps/login-or-create-user-by-email-otp
post /v1/auth/otps/email/login_or_create
Create an OTP (one-time passcode) to the provided email for login verification. If no user account exists for the provided email, a new user will be created and email OTP sent.
## Returns
A successful response returns an object with `user_id`, `user_created` status indicating if the user has been newly created, and `email_id` properties.
# Login or Create User by SMS
Source: https://docs.streambird.io/api-reference/otps/login-or-create-user-by-sms
post /v1/auth/otps/sms/login_or_create
Create an SMS OTP (one-time passcode) to the provided phone number for login verification. If no user account exists for the provided phone number, a new user will be created and OTP sent by SMS.
## Returns
A successful response returns an object with `user_id`, `user_created` indicating if the user has been newly created, and `phone_number_id` properties.
# Verify OTP (One-time passcode)
Source: https://docs.streambird.io/api-reference/otps/verify-otp-one-time-passcode
post /v1/auth/otps/verify
Verify an OTP (one-time passcode) against a method ID (email, phone number) to authenticate the user. This endpoints verifies that the OTP sent in is valid for the given method ID.
## Returns
A successful response returns an object with `method_id`, `method_type`, and verified `user_id` properties.
# PasswordStrengthCheck (Enterprise)
Source: https://docs.streambird.io/api-reference/passwords/passwordstrengthcheck-enterprise
post /v1/auth/passwords/strength_check
Perform a password strength check.
## Returns
A successful response returns an object with password strength properties such as `score`, `crack_time` and `crack_time_display` for UI. Higher `score` demonstrates higher security and the `crack_time` is calculated assuming unlimited programmatic attempts using standard compute power.
# Update Password by Session (Enterprise)
Source: https://docs.streambird.io/api-reference/passwords/update-password-by-session-enterprise
post /v1/auth/passwords/session/update
Update user password using an active session. If the session token does not have an active factor from OTP, magic link, or password, it will return an error.
## Request Body
The following table lists the properties of an HTTP request that this action supports.
## Returns
A successful response returns `user_id` property and `session` object associated with the `session_token` sent in.
# Verify Password (Enterprise)
Source: https://docs.streambird.io/api-reference/passwords/verify-password-enterprise
post /v1/auth/passwords/verify
Verify user password and generate a session. When a new factor is added, a new `session_token` will be generated but inheriting the same session ID and history (e.g. OTP, magic link factors).
## Returns
A successful response returns an object with verified `user_id` and `session` properties.
# Delete Session
Source: https://docs.streambird.io/api-reference/sessions/delete-session
delete /v1/auth/sessions/delete
Delete an identity session in an App. You can only send in one of the following: `session_token`, `session_jwt`, `session_id`. If more than one parameter is sent in, a validation error will be returned.
## Returns
A successful response returns an object with `message` property.
# GetJWKs
Source: https://docs.streambird.io/api-reference/sessions/getjwks
get /v1/auth/jwks/default
Get JWK Set using a publishable `PublicToken` or secret `ApiKey`. It serves as a wrapper on top of `GetJWKsByApp` and infers the `app_id` from the api key used.
## Returns
A successful response returns a `JWK` Set object.
# GetJWKsByApp
Source: https://docs.streambird.io/api-reference/sessions/getjwksbyapp
get /v1/auth/jwks/{app_id}
Get JWK Set for the App.
## Request String Example
``` json
/v1/auth/jwks/app_24ydphdixx2ydhF0E5WUFUKWNqi
```
## Returns
A successful response returns a `JWK` Set object.
# List Sessions
Source: https://docs.streambird.io/api-reference/sessions/list-sessions
get /v1/auth/sessions/list
List identity sessions in an App by user ID.
## Query String Example
```
/v1/auth/sessions/list?user_id=user_24wFP9pDa9YiMJLun94iKykoZs2
```
## Returns
A successful response returns list of `SessionListItem` objects with associated authentication factors that include unique identifiers and delivery methods in the `sessions` property.
# Verify Session
Source: https://docs.streambird.io/api-reference/sessions/verify-session
post /v1/auth/sessions/verify
Verify session in an application by session token and/or optionally extend the expiration time of the session by N minutes from now if the `session_expires_in` property is present.
## Returns
A successful response returns a `Session` object with associated authentication factors that include unique identifiers and delivery methods.
# Create TOTP (beta)
Source: https://docs.streambird.io/api-reference/totp-time-based-one-time-passcode/create-totp-beta
post /v1/auth/totps/create
Initiates a TOTP authenticator for the specified user.
## Returns
A successful response returns an `TOTP` object.
# Get TOTP Recovery Codes (beta)
Source: https://docs.streambird.io/api-reference/totp-time-based-one-time-passcode/get-totp-recovery-codes-beta
post /v1/auth/totps/recovery_codes
Get TOTP (time-based one-time passcode) recovery codes of the given user ID.
## Returns
A successful response returns an object with `user_id` and `totps` properties that contain the recovery codes.
# TOTP Recovery (beta)
Source: https://docs.streambird.io/api-reference/totp-time-based-one-time-passcode/totp-recovery-beta
post /v1/auth/totps/recovery
Verify an TOTP (time-based one-time passcode) recovery code against a user ID to authenticate the user.
## Returns
A successful response returns an object with `totp_id` and verified `user_id` properties.
# Verify TOTP (beta)
Source: https://docs.streambird.io/api-reference/totp-time-based-one-time-passcode/verify-totp-beta
post /v1/auth/totps/verify
Verify an TOTP (time-based one-time passcode) against a user ID to authenticate the user. This endpoints verifies that the TOTP sent in is valid for the given user ID.
## Returns
A successful response returns an object with `totp_id` and verified `user_id` properties.
# Confirm Transfer (beta)
Source: https://docs.streambird.io/api-reference/transfers/confirm-transfer-beta
post /v1/transfers/{transfer_id}/confirm
Confirm a transfer to withdraw from wallet. Each transfer expires within 2 minutes to avoid provide the best estimate in the network charge of the specified blockchain.
## Returns
A successful response returns a `Transfer` object with updated `status` and `blockchain_tx`. We recommend that you check to make sure status is `completed`. If `status` is `pending`, perform polling on `GetTransfer` until status is completed or failed.
# Create Transfer (beta)
Source: https://docs.streambird.io/api-reference/transfers/create-transfer-beta
post /v1/transfers/create
Create transfer to withdraw from wallet.
## Returns
A successful response returns an unconfirmed `Transfer` object with `source_amount`, `dest_amount` (the total the recipient will get), and `total_fees` (estimated maximum blockchain network fees charged by the blockchain for performing the transfer).
# Get Transfer (beta)
Source: https://docs.streambird.io/api-reference/transfers/get-transfer-beta
get /v1/transfers/{transfer_id}
Get transfer object by ID.
## Returns
A successful response returns a `Transfer` object.
# List Transfer (beta)
Source: https://docs.streambird.io/api-reference/transfers/list-transfer-beta
get /v1/transfers/list
List transfers using available filters and pagination. All transfers are returned in `created_at` descending order.
## Query String Example
```
/v1/transfers/list?user_id=user_26l6ha8syVN8oqmaHaFShTxZ5RC&source=wallet_2CxAACd2h0dwzSlsqqyFLsJXCxW
```
## Returns
A successful response returns a list of transfer objects that had been executed (failed and successful). All unconfirmed and expired transfers will be considered abandoned and will not be returned in the result.
# Create User
Source: https://docs.streambird.io/api-reference/users/create-user
post /v1/auth/users/create
Creates a user in an App that requires authentication. Each user will have a default wallet created for the wallet_type sent in.
## Returns
A successful response returns a `User` object with `email_id` and `phone_number_id` properties.
# Delete User
Source: https://docs.streambird.io/api-reference/users/delete-user
delete /v1/auth/users/{user_id}/delete
Delete a user by ID.
## Returns
A successful response returns an object with `message` and `user_id` properties.
# Delete User Email
Source: https://docs.streambird.io/api-reference/users/delete-user-email
delete /v1/auth/users/emails/{email_id}/delete
Delete an email from the associated user.
## Returns
A successful response returns an object with `message` and `user_id` properties.
# Delete User Phone Number
Source: https://docs.streambird.io/api-reference/users/delete-user-phone-number
delete /v1/auth/users/phone_number/{phone_number_id}/delete
Delete a phone number from the associated user.
## Returns
A successful response returns an object with `message` and `user_id` properties.
# Delete User TOTP
Source: https://docs.streambird.io/api-reference/users/delete-user-totp
delete /v1/auth/users/totps/{totp_id}/delete
Delete a TOTP attached to a user.
## Returns
A successful response returns an object with `message` and `user_id` properties.
# Delete User Wallet
Source: https://docs.streambird.io/api-reference/users/delete-user-wallet
delete /v1/auth/users/wallets/{wallet_id}/delete
Delete a read only imported wallet attached to a user.
## Returns
A successful response returns an object with `user_id` property.
# Delete User WebAuthn Credential
Source: https://docs.streambird.io/api-reference/users/delete-user-webauthn-credential
delete /v1/auth/users/webauthn_credentials/{webauthn_credential_id}/delete
Delete a WebAuthn credential from the associated user.
## Returns
A successful response returns an object with `user_id` property.
# Get User
Source: https://docs.streambird.io/api-reference/users/get-user
get /v1/auth/users/{user_id}
Get a user with their various properties like `emails`, `phone_numbers`, and other attached identifiers.
## Returns
A successful response returns a `User` object with linked identifiers such as emails, phone numbers in their corresponding properties.
# Search Users (beta)
Source: https://docs.streambird.io/api-reference/users/search-users-beta
post /v1/auth/users/search
Search users within an App.
## Returns
A successful response returns a list of `User` objects in `users` property and also pagination related properties.
# Update User
Source: https://docs.streambird.io/api-reference/users/update-user
put /v1/auth/users/{user_id}/update
Update a user by ID.
## Returns
A successful response returns a `User` object in `user` owith linked emails and phone numbers in `emails` and `phone_numbers` properties.
# Begin Wallet Import
Source: https://docs.streambird.io/api-reference/wallet-import/begin-wallet-import
post /v1/wallets/import/begin
Initiates the wallet-import process and returns the HPKE encryption public key required for encrypting wallet entropy.
This endpoint does not import or store entropy; it only returns the key needed for secure encryption before the final submission step.
## Entropy Encryption Model
- The server generates an ephemeral HPKE keypair.
- The public key is returned to the client.
- The client encrypts either:
- HD entropy for the specified `index`, or
- A private key if `entropy_type = "private_key"`
- Encrypted entropy is submitted to the wallet-import completion endpoint.
- Raw entropy never touches the server in plaintext.
## Returns
A successful response returns an `encryption_type` and `encryption_public_key` for encrypting the wallet entropy.
# Import Wallet
Source: https://docs.streambird.io/api-reference/wallet-import/import-wallet
post /v1/wallets/import
Completes the wallet import process by submitting the HPKE-encrypted wallet entropy.
This endpoint receives the encrypted entropy (either HD seed or private key) that was encrypted using the public key obtained from the `/v1/wallets/import/begin` endpoint.
## Security Model
- The wallet entropy is encrypted client-side using HPKE with the server's public key.
- The ciphertext and encapsulated key are submitted to this endpoint.
- Raw entropy never touches the server in plaintext.
- If an existing wallet of the given wallet type has been created for that user, it will be returned.
## Returns
A successful response returns a `Wallet` object with `is_imported` set to `true`.
# Begin Wallet Registration
Source: https://docs.streambird.io/api-reference/wallets-login/begin-wallet-registration
post /v1/auth/wallets/registrations/begin
Initiates a wallet registration request for the specified user. This endpoint will return a challenge that must be signed by the private key of the wallet address you are registering against the user. Once verified, we will attach the wallet to the user specified.
## Returns
A successful response returns a `WalletRegistration` object.
# Get Wallet Registration Nonce
Source: https://docs.streambird.io/api-reference/wallets-login/get-wallet-registration-nonce
post /v1/auth/wallets/registrations/nonce
Initiates a wallet registration request for the specified user. This endpoint will return a `nonce` that must be signed by the private key of the wallet address you are registering against the user using the [SIWE EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) (Sign in with Ethereum) spec. We will apply this to both Ethereum and Solana. In the case of Solana, instead of `${domain} wants you to sign in with your Ethereum account:`, we will expect `${domain} wants you to sign in with your Solana account:` and also Chain ID will not be expected either for Solana. Once verified, we will attach the wallet to the user specified.
## Returns
A successful response returns an object with `nonce` and \`user_id\` properties.
# Verify Siwe Wallet Registration
Source: https://docs.streambird.io/api-reference/wallets-login/verify-siwe-wallet-registration
post /v1/auth/wallets/siwe/verify
Verifies the signature created by the wallet's private key signing a SIWE challenge using the [SIWE EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) (Sign in with Ethereum) spec using the `nonce` returned by `GetWalletRegistrationNonce` and the wallet address used during registration. If successful, a wallet will be created and associated with the user. Wallet imported will be marked as `is_imported` and `is_read_only` true. If it is an existing associated wallet for the specified user, we allow you to create a MoonKey managed session.
## Returns
A successful response returns a `Wallet` object.
# Verify Wallet (beta)
Source: https://docs.streambird.io/api-reference/wallets-login/verify-wallet-beta
post /v1/auth/wallets/verify
Verifies a wallet signature. This endpoint will verify the signature signed by the wallet private key using the challenge presented by the `BeginWalletRegistration` endpoint for the wallet address specified. If signature is valid, we will return the wallet object with its associated user ID. If the wallet is not previously attached the the user initiating the wallet registration, the wallet will be attached to the user.
## Returns
A successful response returns a `Wallet` object.
# Create WebAuthn Registration (beta)
Source: https://docs.streambird.io/api-reference/webauthn/create-webauthn-registration-beta
post /v1/auth/webauthn/registrations/create
Verify a signed WebAuthn registration request for the specified user ID and complete and create the WebAuthn registration. This is usually returned by [navigator.credentials.create(options)](https://w3c.github.io/webappsec-credential-management/#algorithm-create-cred) or [webauthn-json](https://github.com/github/webauthn-json) library. If you are using the `webauthn-json` library, pass in the response from `const response = await create(options);` in `public_key_credential`.
## Returns
A successful response returns an `user_id` property and `webauthn_credential_id` property.
# Initiate WebAuthn Authentication (beta)
Source: https://docs.streambird.io/api-reference/webauthn/initiate-webauthn-authentication-beta
post /v1/auth/webauthn/authentication/begin
Initiate a WebAuthn authentication request. This endpoint requires that a user has at least one WebAuthn credential attached for the provided `domain` from a successful `CreateWebAuthnRegistration`. Pass the JSON object in `public_key_credential_request_options` property into `await get(options)` as `options` if you are using [webauthn-json](https://github.com/github/webauthn-json) or to [navigator.credentials.get(options)](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get).
## Returns
A successful response returns a `public_key_credential_request_options` property and `user_id` property.
# Initiate WebAuthn Registration (beta)
Source: https://docs.streambird.io/api-reference/webauthn/initiate-webauthn-registration-beta
post /v1/auth/webauthn/registrations/begin
Initiate a WebAuthn Registration Request. This endpoint requires that a user already exists on MoonKey. Pass the returned JSON object in `public_key_credential_creation_options` property into `await create(options)` as `options` if you are using [webauthn-json](https://github.com/github/webauthn-json) or to [navigator.credentials.create(options)](https://w3c.github.io/webappsec-credential-management/#algorithm-create-cred).
## Returns
A successful response returns a `public_key_credential_creation_options` property and `user_id` property.
# Verify WebAuthn Authentication (beta)
Source: https://docs.streambird.io/api-reference/webauthn/verify-webauthn-authentication-beta
post /v1/auth/webauthn/verify
Verify and complete a WebAuthn credential authentication request. This endpoint requires that a user already exists and the `BeginWebAuthnAuthentication` has been called. Pass in the response from [navigator.credentials.get(options)](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get) into `public_key_credential` as a JSON object. If you are using the [webauthn-json](https://github.com/github/webauthn-json) library, pass in the response from `let response = await get(options)`.
## Returns
A successful response returns a `webauthn_credential_id` property and `user_id` property.
# Overview
Source: https://docs.streambird.io/authentication/overview
Learn about MoonKey's authentication methods and how to integrate them into your application
# Authentication with MoonKey
MoonKey provides a comprehensive authentication system that supports multiple login methods, giving your users flexibility while maintaining security. Whether you're building for web3-native users or traditional audiences, MoonKey handles the complexity of authentication so you can focus on building your product.
## Supported authentication methods
MoonKey offers a variety of authentication methods:
### Passwordless authentication
* **[Email OTP](/methods/otp/email)**: One-time passcode sent to a user's email address for passwordless login
### Social login (OAuth)
* **[OAuth](/methods/oauth/overview)**: Social login with popular providers
* [Google](/methods/oauth/google)
* Apple
* Additional providers coming soon
### Web3 authentication
* **[Wallet login](/methods/web3/ethereum)**: External wallet authentication using:
* Sign-In With Ethereum (SIWE) for Ethereum and EVM-compatible chains
* Sign-In With Solana (SIWS) for Solana
## Flexible authentication flows
MoonKey allows you to configure authentication methods in two ways:
1. **Upfront login methods**: Users can authenticate directly with these methods during initial login
2. **Linked accounts**: Users can connect additional authentication methods to their profile after initial login
This flexibility lets users authenticate with their preferred method while maintaining a single unified identity across all connected accounts.
## The unified user object
All of MoonKey's authentication methods create a common user object. Regardless of how a user authenticates—whether through email, OAuth, or a wallet—they're represented by the same user structure.
The user object contains:
* A unique user ID
* All linked accounts (emails, wallets, OAuth providers)
* Authentication factors and verification status
* User metadata and profile information
**A user is a user**, whether they've authenticated with an email, Google account, or Ethereum wallet. This unified approach simplifies user management and enables seamless multi-method authentication.
## Getting started
### Using the React SDK
The easiest way to integrate MoonKey authentication is with the React SDK, which provides pre-built UI components and hooks:
```typescript theme={null}
import { MoonKeyProvider, useLoginWithEmail } from '@moon-key/react-auth';
function LoginButton() {
const { loginWithEmail } = useLoginWithEmail();
return (
);
}
function App() {
return (
);
}
```
[Get started with the React SDK →](/get-started/frontend-sdks/react/installation)
### Using the REST API
For more control or custom integrations, use the REST API directly:
```bash theme={null}
# Send email OTP
curl -X POST "https://api.moonkey.fun/v1/auth/otps/email/send" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
# Verify OTP and create session
curl -X POST "https://api.moonkey.fun/v1/auth/otps/verify" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"code": "123456",
"session_expires_in": 10080
}'
```
[Get started with the REST API →](/get-started/rest-api/setup)
## Configuration and setup
### Dashboard configuration
Configure your authentication methods in the [MoonKey Dashboard](https://dashboard.moonkey.fun):
1. **[Configure login methods](/get-started/dashboard/login-methods)** - Enable email, OAuth, and wallet authentication
2. **[Set up OAuth providers](/get-started/dashboard/login-methods#social-providers-oauth)** - Configure Google, Apple, and other social providers
3. **[Manage redirect URLs](/get-started/redirect-urls)** - Set up secure redirect destinations for OAuth flows
### SDK integration
Pre-built components and hooks for React and Next.js
Direct API access for custom integrations
Server-side authentication and user management
## Best practices
### Security
* **Use HTTPS only** - Always transmit authentication credentials over secure connections
* **Implement rate limiting** - Protect against brute force attacks on OTP verification
* **Validate sessions server-side** - Never trust client-side authentication alone
### User experience
* **Offer multiple methods** - Let users choose their preferred authentication method
* **Streamline the flow** - Minimize steps between login initiation and success
* **Handle errors gracefully** - Provide clear error messages and recovery options
### Implementation
* **Test thoroughly** - Verify all authentication flows work correctly
* **Handle edge cases** - Account for expired sessions, network failures, and invalid codes
* **Keep credentials secure** - Store session tokens in IndexedDB
# Access Tokens
Source: https://docs.streambird.io/authentication/user-authentication/access-tokens
Retrieve session credentials to authenticate requests to your backend.
When a user logs in to your app and becomes **authenticated**, MoonKey issues the user session credentials. These credentials allow your backend to verify that the requesting user is truly authenticated.
## Session Credentials
MoonKey provides two types of session credentials:
### Session Token
An opaque token that can be verified through the MoonKey API:
```
session_vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue
```
* Must be verified via `/sessions/verify` API endpoint
* Can be instantly revoked
* No user information exposed in the token itself
### Session JWT
A JSON Web Token (JWT) signed with RS256 that can be verified independently:
```
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```
* Can be verified using public keys without API call
* Contains session information in claims
* Expires after 5 minutes but can be refreshed
Learn more about the differences in the [Session Token vs JWT](/get-started/sessions/session-token-vs-jwt) guide.
## Getting Session Credentials
To retrieve the current user's session credentials, use the `getSessionTokens` method from the `useMoonKey` hook:
```typescript theme={null}
getSessionTokens: () => Promise<{
sessionToken: string;
sessionJwt: string
} | null>
```
### Import
```typescript theme={null}
import { useMoonKey } from '@moon-key/react-auth';
```
### Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function MyComponent() {
const { getSessionTokens } = useMoonKey();
const fetchProtectedData = async () => {
// Get session credentials
const credentials = await getSessionTokens();
if (!credentials) {
console.error('User is not authenticated');
return;
}
const { sessionToken, sessionJwt } = credentials;
// Use credentials to authenticate backend request
// (see examples below)
};
return (
);
}
```
### Return Value
The `getSessionTokens` method returns:
* An object with `sessionToken` and `sessionJwt` if the user is authenticated
* `null` if the user is not authenticated
```typescript theme={null}
// When authenticated
{
sessionToken: "session_abc123...",
sessionJwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
// When not authenticated
null
```
## Sending Session Credentials
When making requests to your backend, include the session credentials in the request headers.
### With fetch
Using the session token (recommended for most use cases):
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function DataFetcher() {
const { getSessionTokens } = useMoonKey();
const fetchData = async () => {
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await fetch('https://your-api.com/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`,
'Content-Type': 'application/json'
}
});
return response.json();
};
return ;
}
```
Using the session JWT (for offline verification):
```tsx theme={null}
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await fetch('https://your-api.com/protected', {
method: 'POST',
headers: {
'Authorization': `Bearer ${credentials.sessionJwt}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
```
### With axios
```tsx theme={null}
import axios from 'axios';
import { useMoonKey } from '@moon-key/react-auth';
export default function DataManager() {
const { getSessionTokens } = useMoonKey();
const postData = async (data: any) => {
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await axios({
method: 'post',
url: 'https://your-api.com/protected',
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`
},
data: data
});
return response.data;
};
return (
);
}
```
### With custom hooks
Create a reusable hook for authenticated requests:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useCallback } from 'react';
export function useAuthenticatedFetch() {
const { getSessionTokens, isAuthenticated } = useMoonKey();
const authenticatedFetch = useCallback(
async (url: string, options: RequestInit = {}) => {
if (!isAuthenticated) {
throw new Error('User is not authenticated');
}
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Failed to get session credentials');
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${credentials.sessionToken}`,
'Content-Type': 'application/json'
}
});
if (response.status === 401) {
throw new Error('Session expired');
}
return response;
},
[getSessionTokens, isAuthenticated]
);
return authenticatedFetch;
}
// Usage
function MyComponent() {
const authenticatedFetch = useAuthenticatedFetch();
const loadData = async () => {
try {
const response = await authenticatedFetch('https://your-api.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to fetch:', error);
}
};
return ;
}
```
## Common Patterns
### API Client Wrapper
Create an API client that automatically includes session credentials:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
class APIClient {
constructor(private getSessionTokens: () => Promise) {}
async get(endpoint: string) {
return this.request('GET', endpoint);
}
async post(endpoint: string, data: any) {
return this.request('POST', endpoint, data);
}
async request(method: string, endpoint: string, data?: any) {
const credentials = await this.getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await fetch(`https://your-api.com${endpoint}`, {
method,
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`,
'Content-Type': 'application/json'
},
body: data ? JSON.stringify(data) : undefined
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
}
// Usage in component
export default function DataComponent() {
const { getSessionTokens } = useMoonKey();
const api = new APIClient(getSessionTokens);
const fetchUser = async () => {
const user = await api.get('/user');
console.log(user);
};
const updateUser = async (userData: any) => {
const result = await api.post('/user/update', userData);
console.log(result);
};
return (
);
}
```
### With React Query
Integrate with React Query for data fetching:
```tsx theme={null}
import { useQuery, useMutation } from '@tanstack/react-query';
import { useMoonKey } from '@moon-key/react-auth';
function useAuthenticatedQuery() {
const { getSessionTokens } = useMoonKey();
return useQuery({
queryKey: ['userData'],
queryFn: async () => {
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await fetch('https://your-api.com/user', {
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return response.json();
}
});
}
// Usage
export default function UserProfile() {
const { data, isLoading, error } = useAuthenticatedQuery();
if (isLoading) return
);
}
```
### With Next.js API Routes
Make authenticated requests to Next.js API routes:
```tsx theme={null}
// Client component
import { useMoonKey } from '@moon-key/react-auth';
export default function ClientComponent() {
const { getSessionTokens } = useMoonKey();
const callAPI = async () => {
const credentials = await getSessionTokens();
if (!credentials) {
console.error('Not authenticated');
return;
}
const response = await fetch('/api/protected', {
method: 'POST',
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: 'example' })
});
const result = await response.json();
console.log(result);
};
return ;
}
```
```typescript theme={null}
// app/api/protected/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{ error: 'Missing authorization header' },
{ status: 401 }
);
}
const sessionToken = authHeader.substring(7);
// Verify session with MoonKey API
const verifyResponse = await fetch(
'https://api.moonkey.fun/v1/auth/sessions/verify',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_token: sessionToken })
}
);
if (!verifyResponse.ok) {
return NextResponse.json(
{ error: 'Invalid session' },
{ status: 401 }
);
}
const { user } = await verifyResponse.json();
// Process request with authenticated user
return NextResponse.json({
message: 'Success',
userId: user.id
});
}
```
## Best Practices
### Always Check for Null
The `getSessionTokens` method returns `null` when the user is not authenticated:
```tsx theme={null}
const credentials = await getSessionTokens();
if (!credentials) {
// Handle unauthenticated state
console.error('User is not authenticated');
return;
}
// Safe to use credentials here
const { sessionToken, sessionJwt } = credentials;
```
### Handle Expired Sessions
Session tokens can expire or become invalid:
```tsx theme={null}
const fetchData = async () => {
const credentials = await getSessionTokens();
if (!credentials) {
throw new Error('Not authenticated');
}
const response = await fetch('https://your-api.com/data', {
headers: {
'Authorization': `Bearer ${credentials.sessionToken}`
}
});
if (response.status === 401) {
// Session expired - redirect to login or refresh
console.error('Session expired');
// Optionally trigger re-authentication
start();
return;
}
return response.json();
};
```
### Choose the Right Credential Type
**Use Session Token when:**
* You need real-time revocation
* Security is the top priority
* You're making requests to your own backend
**Use Session JWT when:**
* You need offline verification
* Performance is critical (no API call needed)
* You're integrating with external JWT-based systems
### Secure Storage
The MoonKey SDK automatically handles secure storage of session credentials in IndexedDB. You don't need to manually store these values.
# Email OTP
Source: https://docs.streambird.io/authentication/user-authentication/authentication-methods/email
Authenticate users with one-time passcodes sent to their email address
# Email Authentication
MoonKey enables users to login to your application with email-based one-time passcodes (OTP). With email authentication, your application can verify ownership of a user's email address without requiring passwords.
Enable email authentication in the [MoonKey Dashboard](https://dashboard.moonkey.fun) under **Login Methods** before implementing this feature.
For the complete and latest API reference, including all configuration options, check the [@moon-key/react-auth](https://www.npmjs.com/package/@moon-key/react-auth) npm package.
## Using the React SDK
To authenticate your users, use the `useMoonKey` hook and call the `start()` method to initiate the authentication flow.
### Import
```typescript theme={null}
import { useMoonKey } from '@moon-key/react-auth';
```
## Configuration
### Email OTP Configuration
You can customize the email OTP flow by passing an `EmailOtpConfig` object to the `MoonKeyProvider`:
Skip the success screen after successful verification
OTP expiration time in seconds (default: 600 = 10 minutes)
How long to display the success message in milliseconds
Custom title for the email verification screen
Custom text for the resend code button
Time in seconds before resend is allowed (default: 60)
Custom message shown to new users after verification
Custom message shown to existing users after verification
Custom success message after verification
### Example Configuration
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
## Using the start() method
### Start authentication flow
Initiate the authentication flow with email or other login methods:
```typescript theme={null}
start: (params?: {
loginMethods?: ('email' | 'google' | 'apple' | 'wallet')[];
prefill?: { type: 'email' | 'phone'; value: string };
disableSignup?: boolean;
walletChainType?: 'ethereum' | 'solana' | 'ethereum-or-solana';
}) => void
```
#### Parameters
Array of login methods to show. Options: `'email'`, `'google'`, `'apple'`, `'wallet'`. If not provided, uses login methods configured in the provider.
Pre-fill the authentication form with an email or phone number.
* `type`: Either `'email'` or `'phone'`
* `value`: The email address or phone number to prefill
If true, prevents new user signups and only allows existing users to login.
Specify which wallet type(s) to support. Options: `'ethereum'`, `'solana'`, or `'ethereum-or-solana'`.
#### Returns
Opens the authentication modal/flow. No return value.
### Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function LoginButton() {
const { start, isAuthenticated, user } = useMoonKey();
if (isAuthenticated) {
return
Welcome, {user.email}!
;
}
return (
);
}
```
### Email-only login
To show only email authentication:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function EmailOnlyLogin() {
const { start, isAuthenticated, user } = useMoonKey();
if (isAuthenticated) {
return (
Welcome, {user.email}!
You're successfully logged in.
);
}
return (
);
}
```
### Customizing Messages and Behavior
Here's an example with custom configuration for a better user experience:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
### Prefill email address
Prefill the email input when you know the user's email:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function LoginWithPrefill({ userEmail }: { userEmail: string }) {
const { start } = useMoonKey();
return (
);
}
```
### Login-only (no signup)
Prevent new user signups and only allow existing users to login:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function LoginOnly() {
const { start } = useMoonKey();
return (
);
}
```
### Multiple login methods
Show multiple authentication options:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function MultiMethodLogin() {
const { start } = useMoonKey();
return (
);
}
```
## Using the REST API
For custom implementations or backend integration, use the MoonKey REST API directly.
### Send Email OTP
Send a one-time passcode to the user's email:
```bash theme={null}
curl -X POST "https://api.moonkey.fun/v1/auth/otps/email/send" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'
```
#### Response
```json theme={null}
{
"success": true,
"message": "OTP sent successfully"
}
```
### Verify Email OTP
Verify the OTP code and authenticate the user:
```bash theme={null}
curl -X POST "https://api.moonkey.fun/v1/auth/otps/verify" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"code": "123456",
"session_expires_in": 10080
}'
```
#### Parameters
| Parameter | Type | Required | Description |
| -------------------- | ------ | -------- | ----------------------------------------------------- |
| `email` | string | Yes | The user's email address |
| `code` | string | Yes | The 6-digit OTP code |
| `session_expires_in` | number | No | Session duration in minutes (default: 10080 = 7 days) |
#### Response
```json theme={null}
{
"session_token": "session_abc123...",
"session_jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_xyz789",
"email": "user@example.com",
"created_at": 1704067200,
"updated_at": 1704067200
},
"session": {
"id": "session_abc123",
"user_id": "user_xyz789",
"expires_at": 1704672000,
"created_at": 1704067200
}
}
```
### Backend Implementation Example
```javascript theme={null}
// Express.js backend example
import express from 'express';
const app = express();
app.use(express.json());
// Send OTP endpoint
app.post('/api/auth/send-otp', async (req, res) => {
const { email } = req.body;
try {
const response = await fetch('https://api.moonkey.fun/v1/auth/otps/email/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
if (!response.ok) {
throw new Error('Failed to send OTP');
}
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Failed to send OTP' });
}
});
// Verify OTP endpoint
app.post('/api/auth/verify-otp', async (req, res) => {
const { email, code } = req.body;
try {
const response = await fetch('https://api.moonkey.fun/v1/auth/otps/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
code,
session_expires_in: 10080 // 7 days
})
});
if (!response.ok) {
return res.status(401).json({ error: 'Invalid OTP code' });
}
const data = await response.json();
// Return session credentials to client
res.json({
session_token: data.session_token,
session_jwt: data.session_jwt,
user: data.user
});
} catch (error) {
res.status(500).json({ error: 'Verification failed' });
}
});
```
## Configuration
### Enable email authentication
1. Log in to the [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Navigate to **Login Methods**
3. Enable **Email OTP**
4. Save your changes
### Customize email templates
You can customize the OTP email template in the dashboard:
1. Navigate to **Email Templates**
2. Select **OTP Email**
3. Customize the subject, content, and styling
4. Preview and save
## Best Practices
### Security
* **Rate limiting**: Implement rate limits on OTP sending to prevent abuse
* **Code expiration**: OTP codes expire after 10 minutes
* **Maximum attempts**: Limit verification attempts to prevent brute force
* **Secure transmission**: Always use HTTPS for API calls
### User Experience
* **Clear instructions**: Tell users where to find the code
* **Resend option**: Allow users to request a new code if needed
* **Loading states**: Show loading indicators during API calls
* **Error handling**: Provide clear error messages
### Implementation
The MoonKey SDK handles the entire email OTP flow automatically, including:
* Sending the OTP code
* Displaying the code input form
* Verifying the code
* Managing resend functionality with countdown timer
* Error handling and validation
You can customize this behavior using the `emailOtp` configuration in the provider (see Configuration section above).
## Error Handling
The MoonKey SDK automatically handles common errors in the authentication flow:
* **Invalid or expired codes** - Shows an error message and allows the user to request a new code
* **Rate limiting** - Prevents too many code requests in a short time period
* **Invalid email format** - Validates email addresses before sending codes
* **Network errors** - Displays appropriate error messages for connectivity issues
You can customize error messages using the `emailOtp` configuration options like `verifiedNewUserMessage`, `verifiedExistingUserMessage`, and `verifiedSuccessMessage`.
## Testing
### Test with different email providers
Test your email authentication flow with various email providers:
* Gmail
* Outlook/Hotmail
* Yahoo
* Corporate email domains
* Custom domains
### Test edge cases
* Invalid email format
* Non-existent email addresses
* Expired codes
* Already used codes
* Multiple login attempts
## Troubleshooting
### Emails not being received
1. Check spam/junk folders
2. Verify email domain is not blocked
3. Check email template configuration in dashboard
4. Verify DKIM and SPF records (for custom domains)
### Code verification fails
1. Ensure code hasn't expired (10-minute window)
2. Check for typos in the code
3. Verify the email address matches
4. Check that the code hasn't been used already
### Session not created
1. Verify `session_expires_in` is within valid range (5 minutes to 366 days)
2. Check API key has correct permissions
3. Ensure backend is properly handling the response
# OAuth (Social Login)
Source: https://docs.streambird.io/authentication/user-authentication/authentication-methods/oauth
Enable users to sign in with Google, Apple, and other OAuth providers
# OAuth Authentication
MoonKey enables users to sign in using familiar OAuth providers like Google and Apple. OAuth authentication provides a seamless login experience that users are already comfortable with, integrated into your application in just a few lines of code.
Enable OAuth login methods in the [MoonKey Dashboard](https://dashboard.moonkey.fun) under **Login Methods** before implementing this feature.
Google OAuth login may not work in in-app browsers (IABs), such as those embedded in social apps, due to Google's restrictions in these environments. Apple OAuth and other providers are generally unaffected.
## Supported OAuth Providers
MoonKey currently supports the following OAuth providers:
* **Google** - Sign in with Google accounts
* **Apple** - Sign in with Apple ID
Additional OAuth providers (Microsoft, Discord, GitHub, etc.) are coming soon. Contact us if you need support for a specific provider.
## Configuration
### Dashboard Setup
Before implementing OAuth authentication, you must configure your OAuth credentials for each provider:
1. Log in to the [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Navigate to **Login Methods** → **OAuth**
3. Select the provider you want to enable (Google or Apple)
4. Enter your OAuth Client ID and Client Secret
5. Configure your redirect URLs
6. Save your changes
MoonKey does not provide default OAuth credentials. You must configure your own OAuth app credentials for each provider you want to support.
### Getting OAuth Credentials
#### Google OAuth
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the Google+ API
4. Go to **Credentials** → **Create Credentials** → **OAuth 2.0 Client ID**
5. Configure the consent screen
6. Add authorized redirect URIs provided by MoonKey
7. Copy the Client ID and Client Secret to the MoonKey Dashboard
#### Apple OAuth
1. Go to [Apple Developer](https://developer.apple.com/)
2. Navigate to **Certificates, Identifiers & Profiles**
3. Create a new **Services ID**
4. Enable **Sign in with Apple**
5. Configure the redirect URLs
6. Generate a private key
7. Copy the credentials to the MoonKey Dashboard
## Using the React SDK
MoonKey provides two ways to implement OAuth authentication:
1. **`useMoonKey` hook with `start()`** - Unified authentication flow with all login methods
2. **`useLoginWithOAuth` hook** - Dedicated OAuth hook for more control
### Import
```typescript theme={null}
import { useMoonKey } from '@moon-key/react-auth';
// or for dedicated OAuth flow
import { useLoginWithOAuth } from '@moon-key/react-auth';
```
## Method 1: Using useMoonKey
The `useMoonKey` hook provides a unified way to trigger authentication with multiple methods.
### Basic Usage
Open the authentication modal with OAuth providers:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function OAuthLogin() {
const { start, isAuthenticated, user } = useMoonKey();
if (isAuthenticated) {
return
Welcome, {user.email || user.google?.email}!
;
}
return (
);
}
```
### Google-only Login
Show only Google authentication:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function GoogleLogin() {
const { start } = useMoonKey();
return (
);
}
```
### Apple-only Login
Show only Apple authentication:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function AppleLogin() {
const { start } = useMoonKey();
return (
);
}
```
### Multiple OAuth Providers
Show multiple OAuth options:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function SocialLogin() {
const { start } = useMoonKey();
return (
);
}
```
### Combined with Other Methods
Combine OAuth with email and wallet authentication:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function AllLoginMethods() {
const { start } = useMoonKey();
return (
);
}
```
### Login-only (Disable Signup)
Prevent new user signups via OAuth:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function ExistingUsersOnly() {
const { start } = useMoonKey();
return (
);
}
```
## Method 2: Using useLoginWithOAuth
The `useLoginWithOAuth` hook provides dedicated OAuth functionality with more control over the authentication flow.
### Import and Usage
```typescript theme={null}
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function LoginWithOAuth() {
const { loginWithOAuth, state } = useLoginWithOAuth();
const handleGoogleLogin = async () => {
try {
await loginWithOAuth({ provider: 'google' });
// User is now authenticated
} catch (error) {
console.error('Login failed:', error);
}
};
return (
);
}
```
### loginWithOAuth Method
Initiates the OAuth authentication flow for a specific provider:
```typescript theme={null}
loginWithOAuth: ({
provider: 'google' | 'apple',
disableSignup?: boolean
}) => Promise
```
#### Parameters
The OAuth provider to use. Options: `'google'` or `'apple'`.
If true, prevents new user signups and only allows existing users to login.
### Tracking Flow State
Track the OAuth authentication state:
```typescript theme={null}
state:
| { status: 'initial' }
| { status: 'loading' }
| { status: 'done' }
| { status: 'error'; error: Error | null }
```
The current state of the OAuth flow.
The error that occurred during the OAuth flow (only present when status is 'error').
### Complete Example
```tsx theme={null}
import { useLoginWithOAuth, useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
export default function OAuthButtons() {
const { loginWithOAuth, state } = useLoginWithOAuth();
const { isAuthenticated, user } = useMoonKey();
const [error, setError] = useState(null);
const handleLogin = async (provider: 'google' | 'apple') => {
setError(null);
try {
await loginWithOAuth({ provider });
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
}
};
if (isAuthenticated) {
return
Welcome, {user.email}!
;
}
return (
{state.status === 'error' && (
Error: {state.error?.message}
)}
{error &&
{error}
}
);
}
```
### With Callbacks
You can pass callbacks to handle success and error cases:
```tsx theme={null}
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function OAuthWithCallbacks() {
const { loginWithOAuth } = useLoginWithOAuth({
onSuccess: ({ user, isNewUser }) => {
console.log('User logged in:', user);
if (isNewUser) {
// Handle new user onboarding
console.log('Welcome new user!');
}
},
onError: (error) => {
console.error('OAuth login failed:', error);
// Show error notification
}
});
return (
);
}
```
### Login-only Mode
Prevent new signups and only allow existing users:
```tsx theme={null}
import { useLoginWithOAuth } from '@moon-key/react-auth';
export default function ExistingUsersOnly() {
const { loginWithOAuth } = useLoginWithOAuth();
return (
);
}
```
## Provider Configuration
You can configure which OAuth providers are available by default in the `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
## Using the REST API
For backend implementations or custom integrations, use the MoonKey REST API.
### Initiate OAuth Flow
Start the OAuth authentication flow:
```bash theme={null}
curl -X GET "https://api.moonkey.fun/v1/auth/oauth/google/begin" \
-H "Authorization: Bearer sk_test_your_api_key"
```
This returns a redirect URL where you should send the user:
```json theme={null}
{
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
"state": "random_state_value"
}
```
### Verify OAuth Token
After the user completes OAuth authentication and is redirected back to your application, verify the OAuth code:
```bash theme={null}
curl -X POST "https://api.moonkey.fun/v1/auth/oauth/verify" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"code": "oauth_authorization_code",
"state": "random_state_value",
"session_expires_in": 10080
}'
```
#### Response
```json theme={null}
{
"session_token": "session_abc123...",
"session_jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_xyz789",
"email": "user@example.com",
"google": {
"email": "user@gmail.com",
"name": "John Doe",
"subject": "google_user_id"
},
"created_at": 1704067200,
"updated_at": 1704067200
},
"session": {
"id": "session_abc123",
"user_id": "user_xyz789",
"expires_at": 1704672000,
"created_at": 1704067200
}
}
```
### Backend Implementation Example
```javascript theme={null}
// Express.js example
import express from 'express';
const app = express();
// Initiate OAuth flow
app.get('/auth/google', async (req, res) => {
try {
const response = await fetch(
'https://api.moonkey.fun/v1/auth/oauth/google/begin',
{
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`
}
}
);
const data = await response.json();
// Store state in session for verification
req.session.oauthState = data.state;
// Redirect user to Google
res.redirect(data.url);
} catch (error) {
res.status(500).json({ error: 'Failed to initiate OAuth' });
}
});
// OAuth callback endpoint
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches
if (state !== req.session.oauthState) {
return res.status(400).json({ error: 'Invalid state parameter' });
}
try {
const response = await fetch(
'https://api.moonkey.fun/v1/auth/oauth/verify',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
code,
state,
session_expires_in: 10080 // 7 days
})
}
);
if (!response.ok) {
throw new Error('OAuth verification failed');
}
const data = await response.json();
// Store session token in cookie or return to client
res.cookie('session_token', data.session_token, {
httpOnly: true,
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
// Redirect to your app
res.redirect('/dashboard');
} catch (error) {
res.status(500).json({ error: 'OAuth verification failed' });
}
});
```
## Security Best Practices
### Redirect URL Validation
Configure allowed OAuth redirect URLs in the MoonKey Dashboard to restrict where users can be redirected after authentication:
1. Navigate to **Redirect URLs** in the dashboard
2. Add your application's callback URLs
3. Only whitelisted URLs will be accepted
Always validate redirect URLs to prevent open redirect vulnerabilities.
### State Parameter
The `state` parameter helps prevent CSRF attacks:
* MoonKey generates a unique state value for each OAuth flow
* Store this value in your session
* Verify it matches when handling the callback
* Reject requests with mismatched state values
### HTTPS Only
* Always use HTTPS in production
* Configure OAuth providers to only accept HTTPS redirect URLs
* Never use HTTP for OAuth flows
### Token Storage
* Store session tokens securely (HttpOnly cookies on web)
* Never expose session tokens in URLs or logs
* Use secure, httpOnly cookies when possible
## User Experience
### Loading States
Show appropriate loading states during OAuth flow:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
export default function OAuthLoginButton() {
const { start, isAuthenticated } = useMoonKey();
const [loading, setLoading] = useState(false);
const handleLogin = async () => {
setLoading(true);
try {
await start({ loginMethods: ['google'] });
} finally {
setLoading(false);
}
};
if (isAuthenticated) {
return
Logged in!
;
}
return (
);
}
```
### Error Handling
The MoonKey SDK automatically handles common OAuth errors:
* **User cancellation** - When users close the OAuth popup or decline
* **Invalid credentials** - When OAuth configuration is incorrect
* **Network errors** - Connection issues during the flow
* **State mismatch** - CSRF protection validation failures
### Popup vs Redirect
MoonKey uses popup windows for OAuth flows by default, which:
* Keep users on your page
* Provide a seamless experience
* Work across all modern browsers
For mobile or in-app browsers, MoonKey automatically falls back to redirect-based flows.
## Testing
### Test Mode
During development, use test API keys to:
* Test OAuth flows without affecting production users
* Validate your integration
* Debug callback handling
### Test with Real Providers
Always test with actual OAuth providers:
* Google test accounts
* Apple test accounts
* Verify the complete flow works end-to-end
### Common Test Scenarios
* First-time user signup via OAuth
* Existing user login via OAuth
* User cancels OAuth flow
* Network errors during OAuth
* Invalid or expired OAuth codes
* Multiple OAuth accounts linked to same user
## Troubleshooting
### OAuth flow doesn't start
**Possible causes:**
* OAuth provider not enabled in dashboard
* Invalid OAuth credentials configured
* Missing redirect URLs in dashboard
**Solutions:**
1. Verify provider is enabled in **Login Methods**
2. Check OAuth Client ID and Secret are correct
3. Add your callback URLs to **Redirect URLs**
### Redirect URL mismatch error
**Cause:** The redirect URL doesn't match what's configured in your OAuth provider.
**Solution:**
1. Check the redirect URL in MoonKey Dashboard
2. Ensure it matches exactly in your OAuth provider settings (Google Cloud Console, Apple Developer)
3. Include the protocol (`https://`) and any path components
### State parameter mismatch
**Cause:** The state value doesn't match between initial request and callback.
**Solutions:**
* Ensure you're storing the state in your session correctly
* Check for session timeout issues
* Verify cookies are enabled
### User information not available
**Cause:** OAuth scopes might not include profile information.
**Solution:**
Check your OAuth provider configuration includes the necessary scopes:
* **Google**: `email`, `profile`, `openid`
* **Apple**: `email`, `name`
## Provider-Specific Notes
### Google OAuth
* Supports email and profile information
* Works on web and most browsers
* May not work in some in-app browsers (Facebook, Instagram apps)
* Requires verified domain for production use
### Apple OAuth
* Requires Apple Developer account
* Email may be hidden/private on first login
* Name is only provided on first authentication
* Works across web, iOS, and macOS
# Authentication State
Source: https://docs.streambird.io/authentication/user-authentication/authentication-state
Check user authentication status throughout your application.
Throughout your app, you may want to gate certain user experiences based on whether the current user is authenticated or not. MoonKey makes it easy to check your user's authentication status and handle it appropriately.
## Using the React SDK
You can use the `useMoonKey` hook to determine if your user is authenticated and access their information.
### Import
```typescript theme={null}
import { useMoonKey } from '@moon-key/react-auth';
```
### Available Properties
The `useMoonKey` hook provides several properties for managing authentication state:
```typescript theme={null}
const {
isAuthenticated, // boolean - true if user is logged in
ready, // boolean - true when SDK has initialized
user, // User object or null
logout, // function to logout user
start // function to start authentication flow
} = useMoonKey();
```
Indicates whether the current user is authenticated or not.
Indicates whether the MoonKey SDK has finished initializing. Always check this before checking `isAuthenticated`.
The authenticated user object, or `null` if no user is logged in.
Function to logout the current user.
Function to initiate the authentication flow.
Before determining a user's auth status, you should verify that MoonKey has fully initialized and is **`ready`**.
## Basic Usage
Check if a user is authenticated:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function MyComponent() {
const { ready, isAuthenticated, user } = useMoonKey();
if (!ready) {
// Show loading state while SDK initializes
return
Loading...
;
}
if (!isAuthenticated) {
// User is not logged in
return
Please log in to continue
;
}
// User is authenticated
return
Welcome, {user.email}!
;
}
```
## Common Patterns
### Protected Routes
Redirect unauthenticated users to a login page:
```tsx theme={null}
import { useRouter } from 'next/router';
import { useMoonKey } from '@moon-key/react-auth';
import { useEffect } from 'react';
export default function ProtectedPage() {
const { ready, isAuthenticated, user } = useMoonKey();
const router = useRouter();
useEffect(() => {
if (ready && !isAuthenticated) {
// Redirect to login page if not authenticated
router.push('/login');
}
}, [ready, isAuthenticated, router]);
if (!ready) {
// Show loading state while SDK initializes
return
Loading...
;
}
if (!isAuthenticated) {
// Will redirect, but show loading in the meantime
return
Redirecting to login...
;
}
// User is authenticated, show protected content
return (
Protected Content
Welcome, {user.email}!
);
}
```
### Conditional Rendering
Show different UI based on authentication state:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function HomePage() {
const { ready, isAuthenticated, user, start, logout } = useMoonKey();
if (!ready) {
return
Loading...
;
}
return (
My App
{isAuthenticated ? (
Welcome, {user.email}!
) : (
)}
{isAuthenticated ? (
) : (
)}
);
}
```
### Authentication Wrapper Component
Create a reusable wrapper for protected content:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { ReactNode } from 'react';
interface RequireAuthProps {
children: ReactNode;
fallback?: ReactNode;
}
export function RequireAuth({ children, fallback }: RequireAuthProps) {
const { ready, isAuthenticated, start } = useMoonKey();
if (!ready) {
return
This content is only visible to authenticated users
);
}
```
### Loading States
Handle different loading and authentication states:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function UserProfile() {
const { ready, isAuthenticated, user } = useMoonKey();
// SDK is still initializing
if (!ready) {
return (
Initializing...
);
}
// SDK is ready, but user is not authenticated
if (!isAuthenticated) {
return (
);
}
```
## Accessing User Information
Once authenticated, you can access detailed user information:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function UserInfo() {
const { isAuthenticated, user } = useMoonKey();
if (!isAuthenticated || !user) {
return
Not logged in
;
}
return (
User Information
{/* Basic Info */}
User ID: {user.id}
Email: {user.email}
{/* OAuth Accounts */}
{user.google && (
Google Account
Email: {user.google.email}
Name: {user.google.name}
)}
{user.apple && (
Apple Account
Email: {user.apple.email}
)}
{/* Wallet Information */}
{user.wallet && (
Wallet
Address: {user.wallet.address}
Chain: {user.wallet.chainType}
)}
);
}
```
## Logout
Log out the current user:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function LogoutButton() {
const { isAuthenticated, logout } = useMoonKey();
if (!isAuthenticated) {
return null;
}
const handleLogout = async () => {
try {
await logout();
// User is now logged out
// Optionally redirect or show a message
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
);
}
```
## With Next.js App Router
Handle authentication state in Next.js App Router:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function DashboardPage() {
const { ready, isAuthenticated, user } = useMoonKey();
const router = useRouter();
useEffect(() => {
if (ready && !isAuthenticated) {
router.push('/login');
}
}, [ready, isAuthenticated, router]);
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return
Redirecting...
;
}
return (
Dashboard
Welcome back, {user.email}!
);
}
```
## With React Router
Protect routes with React Router:
```tsx theme={null}
import { Navigate, Outlet } from 'react-router-dom';
import { useMoonKey } from '@moon-key/react-auth';
export function ProtectedRoute() {
const { ready, isAuthenticated } = useMoonKey();
if (!ready) {
return
Loading...
;
}
return isAuthenticated ? : ;
}
// In your router configuration
const router = createBrowserRouter([
{
path: '/',
element: ,
children: [
{
path: 'login',
element:
},
{
element: ,
children: [
{
path: 'dashboard',
element:
},
{
path: 'profile',
element:
}
]
}
]
}
]);
```
## Server-Side Authentication
For server-side authentication checks, use the REST API to verify sessions:
```typescript theme={null}
// app/api/protected/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const sessionToken = request.cookies.get('session_token')?.value;
if (!sessionToken) {
return NextResponse.json(
{ error: 'Not authenticated' },
{ status: 401 }
);
}
// Verify session with MoonKey API
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_token: sessionToken })
});
if (!response.ok) {
return NextResponse.json(
{ error: 'Invalid session' },
{ status: 401 }
);
}
const { user } = await response.json();
// Return protected data
return NextResponse.json({
message: 'Protected data',
user
});
}
```
## Best Practices
### Always Check Ready State
Always verify the SDK is ready before checking authentication status:
```tsx theme={null}
// ❌ Bad - might check auth before SDK is ready
if (isAuthenticated) {
// ...
}
// ✅ Good - wait for SDK to be ready
if (ready && isAuthenticated) {
// ...
}
```
### Handle All States
Consider all possible states in your UI:
```tsx theme={null}
export default function MyComponent() {
const { ready, isAuthenticated } = useMoonKey();
// Loading state
if (!ready) {
return ;
}
// Unauthenticated state
if (!isAuthenticated) {
return ;
}
// Authenticated state
return ;
}
```
### Use useEffect for Side Effects
Use `useEffect` for redirects and other side effects:
```tsx theme={null}
useEffect(() => {
if (ready && !isAuthenticated) {
// Redirect or show modal
router.push('/login');
}
}, [ready, isAuthenticated, router]);
```
### Avoid Unnecessary Re-renders
Destructure only what you need:
```tsx theme={null}
// If you only need authentication status
const { ready, isAuthenticated } = useMoonKey();
// If you need user data too
const { ready, isAuthenticated, user } = useMoonKey();
```
## Common Patterns Summary
| Pattern | Use Case | Example |
| ------------------------- | --------------------------------------- | ----------------------- |
| **Protected Routes** | Require authentication for entire pages | Dashboard, Settings |
| **Conditional Rendering** | Show different UI for auth states | Login/Logout buttons |
| **Auth Wrapper** | Reusable protection for components | `` wrapper |
| **Loading States** | Handle SDK initialization | Loading spinners |
| **Server-Side Checks** | Verify auth on backend | API routes, SSR |
## Related Resources
Implement email OTP authentication
Set up Google and Apple OAuth
Learn about session handling
Get started with MoonKey
# Overview
Source: https://docs.streambird.io/authentication/user-authentication/sessions/overview
Learn how MoonKey manages user sessions and authentication state
MoonKey sessions allow you to track and manage authenticated user state across your application. Once a user successfully authenticates through any login method (OAuth, email OTP, wallet signature, etc.), MoonKey creates a session that represents their authenticated state.
## How sessions work
After successful authentication, MoonKey generates a session and returns session credentials to your application. These credentials are stored in your browser's IndexedDB and sent with subsequent requests to identify the user.
Each session contains:
* **User information** - The user's ID and associated account details
* **Authentication factors** - The methods used to authenticate (email, OAuth provider, wallet, etc.)
* **Device information** - Details about the device and browser used during login
* **Session metadata** - Creation time, expiration, and other session properties
## Session credentials
MoonKey provides two types of session credentials:
### Session Token
A standard unique token that doesn't contain user information. This opaque token must be verified through the MoonKey API on each request.
**Format:** `session_abc123xyz...`
**Best for:**
* Applications requiring real-time session revocation
* High-security scenarios where tokens should be opaque
* Simple implementation without JWT complexity
### Session JWT
A JSON Web Token (JWT) that contains the full session object cryptographically signed using RS256. JWTs can be verified independently using MoonKey's public keys without making an API call.
**Format:** `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...`
**Best for:**
* Performance-critical applications (no API call needed for verification)
* Integration with external systems that rely on JWT standards
* Offline or distributed systems
Learn more about the differences and when to use each in the [Session Token vs JWT](/get-started/sessions/session-token-vs-jwt) guide.
## Basic session lifecycle
### 1. User authenticates
```javascript theme={null}
// User logs in via email OTP, OAuth, or wallet
const { session_token, session_jwt, user } = await authenticate();
```
### 2. Session stored automatically
The MoonKey SDK automatically stores session credentials in IndexedDB. No manual storage implementation needed.
### 3. Session verified on requests
```javascript theme={null}
// Backend verifies the session on each request
const { session, user } = await verifySession(sessionToken);
```
### 4. Session ends
Sessions expire after a configured duration (default: 7 days) or when explicitly deleted by the user.
## Managing sessions
MoonKey provides several API endpoints for session management:
* **[Verify Session](/api-reference/sessions/verify-session)** - Check if a session is valid and optionally extend its duration
* **[List Sessions](/api-reference/sessions/list-sessions)** - View all active sessions for a user
* **[Delete Session](/api-reference/sessions/delete-session)** - Revoke a specific session
## Session duration
Configure session duration in the [MoonKey Dashboard](https://dashboard.moonkey.fun) under **App Settings**. Sessions can last from 1 hour to 30 days (default: 7 days).
Use shorter durations for high-security applications and longer durations for better user experience in low-risk applications.
## Using sessions with the React SDK
The MoonKey React SDK handles session management automatically:
```typescript theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function App() {
const { authenticated, user, logout } = useMoonKey();
if (!authenticated) {
return ;
}
return (
Welcome, {user.email}
);
}
```
The SDK automatically handles:
* Session storage in IndexedDB
* Session refresh when needed
* Session expiration handling
* Authentication state management
# Session Management
Source: https://docs.streambird.io/authentication/user-authentication/sessions/session-management
Implement advanced session management with MoonKey's REST API
This guide covers implementing session management using MoonKey's REST API. Learn how to create, verify, extend, and delete user sessions in your backend application.
## How session management works
Whenever a magic link, one-time passcode, or OAuth token is authenticated, MoonKey's verify endpoints ([**VerifyOTP**](/api-reference/otps/verify-otp-one-time-passcode), and [**VerifyOAuthToken**](/api-reference/oauth/verify-token)) can issue a session token if a `session_expires_in` parameter is provided.
The `session_expires_in` parameter sets the duration of the session in minutes from the current time. This session will be associated with:
* The authentication method used
* The user who successfully authenticated
* Device and browser information
All session endpoints require your secret API key and should only be used on your backend/server-side. **NEVER expose your API key** in client-side code.
## Creating a session
### During authentication
All verify endpoints support the `session_expires_in` parameter to create or extend a session:
```bash cURL - Email OTP theme={null}
curl -X POST 'https://api.moonkey.fun/v1/auth/otps/verify' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"code": "123456",
"email": "user@example.com",
"session_expires_in": 10080
}'
```
```bash cURL - Magic Link theme={null}
curl -X POST 'https://api.moonkey.fun/v1/auth/magic_links/verify' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"token": "magic_link_token_here",
"session_expires_in": 10080
}'
```
```bash cURL - OAuth theme={null}
curl -X POST 'https://api.moonkey.fun/v1/auth/oauth/verify' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"code": "oauth_code_here",
"session_expires_in": 10080
}'
```
### Session duration constraints
* **Minimum duration:** 5 minutes
* **Maximum duration:** 366 days (527,040 minutes)
* **Default duration:** 7 days (10,080 minutes)
The response will include both a session token and session JWT:
```json theme={null}
{
"session_token": "vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue",
"session_jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_abc123",
"email": "user@example.com",
// ... other user fields
},
"session": {
"id": "session_xyz789",
"user_id": "user_abc123",
"expires_at": 1704067200,
"created_at": 1703462400
}
}
```
Store the `session_token` or `session_jwt` on the client-side. The MoonKey SDK automatically handles this using IndexedDB.
## Verifying sessions
### Basic verification
Verify a session token to check if it's still valid and retrieve the associated user:
```bash cURL theme={null}
curl -X POST 'https://api.moonkey.fun/v1/auth/sessions/verify' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"session_token": "vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue"
}'
```
```javascript JavaScript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const sessionToken = request.headers.get('session-token');
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken
})
});
if (!response.ok) {
// Session is invalid or expired
return { error: 'Unauthorized' };
}
const { session, user } = await response.json();
// User is authenticated, proceed with request
```
```python Python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
session_token = request.headers.get('session-token')
response = requests.post(
'https://api.moonkey.fun/v1/auth/sessions/verify',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json={'session_token': session_token}
)
if response.status_code != 200:
# Session is invalid or expired
return {'error': 'Unauthorized'}, 401
data = response.json()
session = data['session']
user = data['user']
# User is authenticated, proceed with request
```
### Successful response
```json theme={null}
{
"session": {
"id": "session_xyz789",
"user_id": "user_abc123",
"expires_at": 1704067200,
"created_at": 1703462400,
"device_info": {
"user_agent": "Mozilla/5.0...",
"ip_address": "192.168.1.1"
}
},
"user": {
"id": "user_abc123",
"email": "user@example.com",
// ... other user fields
}
}
```
### Error response
When a session is invalid or expired:
```json theme={null}
{
"error": {
"code": "invalid_session",
"message": "Session not found or has expired"
}
}
```
When a session is invalid, immediately delete the token from the client-side and return an unauthorized response to the user.
## Extending sessions
You can extend an existing session's duration using the `session_expires_in` parameter:
```bash cURL theme={null}
curl -X POST 'https://api.moonkey.fun/v1/auth/sessions/verify' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"session_token": "vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue",
"session_expires_in": 10080
}'
```
```javascript JavaScript theme={null}
// Extend session by 7 days (10,080 minutes)
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken,
session_expires_in: 10080 // 7 days
})
});
```
The extended session will now expire N minutes from the current time.
### Common extension patterns
| Use Case | Duration (minutes) | Duration (days) |
| ---------------- | ------------------ | --------------- |
| Short session | 1,440 | 1 day |
| Standard session | 10,080 | 7 days |
| Remember me | 43,200 | 30 days |
| Maximum duration | 527,040 | 366 days |
## Listing active sessions
View all active sessions for a specific user using the [**ListSessions**](/api-reference/sessions/list-sessions) endpoint:
```bash cURL theme={null}
curl -X GET 'https://api.moonkey.fun/v1/auth/sessions/list?user_id=user_abc123' \
-H 'Authorization: Bearer sk_test_your_api_key'
```
```javascript JavaScript theme={null}
const response = await fetch(
`https://api.moonkey.fun/v1/auth/sessions/list?user_id=${userId}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
const { sessions } = await response.json();
```
### Response
```json theme={null}
{
"sessions": [
{
"id": "session_xyz789",
"user_id": "user_abc123",
"expires_at": 1704067200,
"created_at": 1703462400,
"device_info": {
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)...",
"ip_address": "192.168.1.1"
}
},
{
"id": "session_def456",
"user_id": "user_abc123",
"expires_at": 1704153600,
"created_at": 1703548800,
"device_info": {
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"ip_address": "192.168.1.5"
}
}
]
}
```
### Use case: Session management UI
Display a list of active sessions to users so they can manage their devices:
This allows users to:
* View all devices where they're logged in
* See when each session was created
* Revoke sessions from unfamiliar devices
## Deleting sessions
### Sign out of a session
Delete a specific session using the [**DeleteSession**](/api-reference/sessions/delete-session) endpoint:
```bash cURL theme={null}
curl -X DELETE 'https://api.moonkey.fun/v1/auth/sessions/delete' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"session_token": "vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue"
}'
```
```javascript JavaScript theme={null}
// User clicks "Logout"
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/delete', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken
})
});
if (response.ok) {
// Clear session from client-side storage
// Redirect to login page
}
```
```python Python theme={null}
response = requests.delete(
'https://api.moonkey.fun/v1/auth/sessions/delete',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json={'session_token': session_token}
)
if response.status_code == 200:
# Clear session from client
# Redirect to login
pass
```
### Delete by session ID
You can also delete a session by its ID (useful for revoking other devices):
```bash theme={null}
curl -X DELETE 'https://api.moonkey.fun/v1/auth/sessions/delete' \
-H 'Authorization: Bearer sk_test_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"session_id": "session_xyz789"
}'
```
## Implementation patterns
### Backend middleware
Implement session verification as middleware in your backend:
```javascript Express.js theme={null}
// middleware/auth.js
async function requireAuth(req, res, next) {
const sessionToken = req.headers['session-token'];
if (!sessionToken) {
return res.status(401).json({ error: 'No session token provided' });
}
try {
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_token: sessionToken })
});
if (!response.ok) {
return res.status(401).json({ error: 'Invalid session' });
}
const { session, user } = await response.json();
// Attach user to request
req.user = user;
req.session = session;
next();
} catch (error) {
return res.status(500).json({ error: 'Session verification failed' });
}
}
// Use in routes
app.get('/api/profile', requireAuth, (req, res) => {
res.json({ user: req.user });
});
```
```python Flask theme={null}
from functools import wraps
from flask import request, jsonify
import requests
import os
def require_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
session_token = request.headers.get('session-token')
if not session_token:
return jsonify({'error': 'No session token provided'}), 401
try:
response = requests.post(
'https://api.moonkey.fun/v1/auth/sessions/verify',
headers={
'Authorization': f'Bearer {os.environ.get("MOONKEY_SECRET_KEY")}',
'Content-Type': 'application/json'
},
json={'session_token': session_token}
)
if response.status_code != 200:
return jsonify({'error': 'Invalid session'}), 401
data = response.json()
request.user = data['user']
request.session = data['session']
return f(*args, **kwargs)
except Exception as e:
return jsonify({'error': 'Session verification failed'}), 500
return decorated_function
# Use in routes
@app.route('/api/profile')
@require_auth
def profile():
return jsonify({'user': request.user})
```
### Client-side session handling
When using manual REST API implementation (not the SDK):
```javascript theme={null}
// Store session after login
async function login(email, code) {
const response = await fetch('/api/auth/verify-otp', {
method: 'POST',
body: JSON.stringify({ email, code })
});
const { session_token, session_jwt, user } = await response.json();
// Store in IndexedDB or secure storage
await storeSession({ session_token, session_jwt, user });
}
// Include session in requests
async function makeAuthenticatedRequest(url, options = {}) {
const session = await getSession();
return fetch(url, {
...options,
headers: {
...options.headers,
'session-token': session.session_token
}
});
}
// Handle session expiration
async function handleResponse(response) {
if (response.status === 401) {
// Session expired or invalid
await clearSession();
redirectToLogin();
}
return response;
}
```
## Security best practices
### Backend verification
* **Always verify on the backend** - Never trust client-side session validation
* **Use middleware** - Centralize session verification logic
* **Check on every request** - Verify sessions for all protected endpoints
* **Handle errors gracefully** - Clear invalid sessions and redirect to login
### Session storage
* **Use IndexedDB** - More secure than localStorage for session storage
* **Secure cookies as alternative** - Use `httpOnly`, `secure`, and `sameSite` flags
* **Never expose in logs** - Don't log session tokens or JWTs
* **Clear on logout** - Always delete session data from client storage
### Session lifecycle
* **Implement proper logout** - Always call the delete session endpoint
* **Allow session revocation** - Let users manage and revoke active sessions
* **Rotate after sensitive actions** - Create new sessions after password changes
* **Monitor suspicious activity** - Track unusual session patterns
## Common use cases
### "Remember me" functionality
Implement different session durations based on user preference:
```javascript theme={null}
async function login(email, code, rememberMe) {
const sessionExpiresIn = rememberMe
? 43200 // 30 days
: 1440; // 1 day
const response = await verifyOTP(email, code, sessionExpiresIn);
return response;
}
```
### Automatic session extension
Extend sessions on user activity:
```javascript theme={null}
// Extend session on each verified request
async function verifyAndExtendSession(sessionToken) {
return await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken,
session_expires_in: 10080 // Reset to 7 days
})
});
}
```
## Troubleshooting
### Session not found
**Cause:** Session was deleted, expired, or never existed
**Solution:**
* Check if the session token is correct
* Verify the session hasn't expired
* Ensure the session wasn't manually deleted
* Clear client-side storage and redirect to login
### Unauthorized errors
**Cause:** API key is missing or incorrect
**Solution:**
* Verify your API key is correct
* Ensure you're using `Bearer` token format
* Check that the API key hasn't been revoked
* Confirm you're using the secret key (not public key)
### Session expired too quickly
**Cause:** Session duration is too short
**Solution:**
* Increase `session_expires_in` when creating sessions
* Implement automatic session extension on user activity
* Configure longer default duration in dashboard
# Session Token vs JWT
Source: https://docs.streambird.io/authentication/user-authentication/sessions/session-token-vs-jwt
Understand the differences between session tokens and JWTs to choose the right approach for your application.
MoonKey provides two types of session credentials after successful authentication: **session tokens** and **session JWTs**. Both represent the same authenticated session but serve different use cases and have distinct characteristics.
When a user successfully authenticates through any method (email OTP, OAuth, wallet signature, etc.), MoonKey returns both credentials. The MoonKey SDK automatically stores these in your browser's IndexedDB, and you can choose which one to use based on your application's needs.
## Overview
Each session contains:
* User information and identifiers
* Authentication factors used (email, OAuth provider, wallet, etc.)
* Device and browser information
* Session metadata (creation time, expiration, etc.)
**Session Token:** A standard opaque token that requires API verification on every use.
**Session JWT:** A cryptographically signed JSON Web Token that can be verified independently.
## Session Token
A session token is an opaque, unique string that doesn't contain any user or session information. It must be verified through the MoonKey API to retrieve session details.
### Format
```
session_vy9YGpubKjVn98cw1nT25Msj7jaIpHBinUfD45KLdAOgn9NqEuE4qGHOEchEG5Ue
```
### Characteristics
* **Opaque:** The token itself contains no user information
* **Requires verification:** Must call `/sessions/verify` API on every request
* **Easily revocable:** Can be instantly invalidated via `/sessions/delete` API
* **No expiration in token:** Expiration is managed server-side
### When to use session tokens
Session tokens are ideal for applications that prioritize security and real-time session control:
#### High-security applications
Since session tokens can be instantly revoked, there's no risk of using a token after the session has been invalidated. With JWTs, even if the underlying session is revoked, the JWT might still be considered valid until its 5-minute expiration.
#### Privacy-focused applications
Session tokens don't expose any user information if inspected. Unlike JWTs (which can be decoded to reveal `user_id`, `session_id`, and other claims), session tokens remain completely opaque.
#### Real-time session management
Applications that need immediate session revocation (e.g., admin panels, financial apps) benefit from the server-side verification model where session state is always current.
### Verification example
```javascript theme={null}
// Session token must be verified via API
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken
})
});
const { session, user } = await response.json();
```
### Advantages
✅ Instant revocation - Session is checked server-side on every request\
✅ Privacy - No user information exposed in the token\
✅ Simplicity - No JWT verification logic needed\
✅ Security - Server maintains complete control over session state
### Disadvantages
❌ Performance - Requires an API call for every verification\
❌ Availability - Dependent on MoonKey API being reachable\
❌ Latency - Adds network round-trip time to each request
## Session JWT
A session JWT is a JSON Web Token that contains the full session object, cryptographically signed using the RS256 algorithm. It can be verified independently without calling the MoonKey API.
### Format
```
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNlcl8xMjMiLCJzZXNzaW9uX2lkIjoic2Vzc2lvbl94eXoiLCJleHAiOjE3MDQwNjcyMDB9.signature
```
### Characteristics
* **Self-contained:** Contains full session and user information
* **Cryptographically signed:** Uses RS256 (RSA Signature with SHA-256)
* **Independently verifiable:** Can be verified using public keys from JWKS endpoint
* **Short-lived:** Expires after 5 minutes (300 seconds)
### How JWT verification works
MoonKey uses **RS256** (RSA Signature with SHA-256), an asymmetric signing algorithm:
1. **Signing:** MoonKey signs JWTs with a private key
2. **Verification:** You verify JWTs using a public key
3. **Public keys:** Available at `https://api.moonkey.fun/v1/auth/jwks/{app_id}`
This means anyone with the public key can verify the JWT's authenticity without needing the private key or calling the MoonKey API.
MoonKey leverages the [JSON Web Key (JWK) spec](https://www.rfc-editor.org/rfc/rfc7517) to represent the cryptographic keys used for signing tokens. Learn more about accessing your public keys in the [GetJWKsByApp API reference](/api-reference/sessions/getjwksbyapp).
### JWT expiration and refresh
Each JWT expires after **5 minutes** (specified in the `exp` claim). However, you can refresh the JWT by calling the `/sessions/verify` API:
```javascript theme={null}
// JWT has expired (past 5 minutes) but underlying session is still valid
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_jwt: expiredJWT
})
});
const { session_jwt: newJWT, session, user } = await response.json();
// Returns a fresh JWT with a new 5-minute expiration
```
**Important:** If the underlying session has been revoked or expired, even a valid (not-yet-expired) JWT will fail verification when checked against the MoonKey API.
### When to use session JWTs
Session JWTs are ideal for performance-critical and distributed systems:
#### Performance-critical applications
Since JWTs can be verified locally without an API call, you can eliminate network latency from your authentication flow. This is especially valuable for high-traffic applications.
#### Distributed systems
When your backend services are distributed across multiple servers or regions, JWTs allow each service to verify sessions independently without coordinating with a central authentication service.
#### External integrations
If you're integrating with external systems that rely on JWT standards (e.g., third-party APIs, microservices), session JWTs provide a standardized authentication mechanism.
#### Offline or low-connectivity scenarios
Applications that may operate with intermittent internet connectivity can verify JWTs locally without depending on API availability.
### Verification example
```javascript theme={null}
import { createRemoteJWKSet, jwtVerify } from 'jose';
// Fetch public keys from MoonKey
const JWKS = createRemoteJWKSet(
new URL(`https://api.moonkey.fun/v1/auth/jwks/${appId}`)
);
// Verify JWT locally without API call
try {
const { payload } = await jwtVerify(sessionJWT, JWKS, {
algorithms: ['RS256']
});
// JWT is valid, payload contains session data
const userId = payload.user_id;
const sessionId = payload.session_id;
// ... use session data
} catch (error) {
// JWT is invalid, expired, or signature doesn't match
console.error('JWT verification failed:', error);
}
```
### Advantages
✅ Performance - No API call needed for verification\
✅ Offline capability - Can verify without internet connection\
✅ Standards-based - Compatible with JWT ecosystem and libraries\
✅ Distributed - Each service can verify independently
### Disadvantages
❌ Delayed revocation - Valid JWTs remain usable until 5-minute expiration\
❌ Information exposure - JWT payload can be decoded (though not modified)\
❌ Complexity - Requires JWT verification logic and key management\
❌ Size - JWTs are larger than session tokens
## Comparison table
| Feature | Session Token | Session JWT |
| ------------------- | ------------------- | -------------------------------------- |
| **Verification** | API call required | Local verification with public key |
| **Revocation** | Instant | Up to 5-minute delay |
| **Performance** | Slower (API call) | Faster (no API call) |
| **Privacy** | Opaque token | User info visible (but not modifiable) |
| **Expiration** | Server-side managed | 5-minute token expiration |
| **Size** | Small (\~80 chars) | Large (\~500+ chars) |
| **Offline support** | No | Yes (during 5-min window) |
| **Complexity** | Simple | Requires JWT verification setup |
| **Standards** | MoonKey-specific | JWT standard (RFC 7519) |
## Choosing the right approach
### Use Session Tokens if:
* Security and instant revocation are top priorities
* You need real-time session control
* Privacy is important (no user data exposure)
* Simplicity is preferred over performance
* Your application is low to moderate traffic
### Use Session JWTs if:
* Performance is critical (high-traffic applications)
* You have distributed services that need to verify sessions
* You're integrating with JWT-based external systems
* Offline or low-connectivity operation is needed
* You can accept a 5-minute revocation delay
### Hybrid approach
You can also use both:
```javascript theme={null}
// Store both credentials
const { session_token, session_jwt } = authResponse;
// Use JWT for normal requests (fast)
const isValidJWT = await verifyJWTLocally(session_jwt);
// Use session token for sensitive operations (secure)
const { session, user } = await verifySessionToken(session_token);
```
This approach gives you performance benefits for most requests while maintaining security for critical operations.
## Best practices
### General
* **Store securely:** The MoonKey SDK uses IndexedDB for secure client-side storage
* **Never log tokens:** Don't include session tokens or JWTs in application logs
* **Use HTTPS:** Always transmit credentials over secure connections
* **Clear on logout:** Remove both tokens when users sign out
### For session tokens
* **Verify on every request:** Always check validity before processing requests
* **Handle expiration gracefully:** Redirect users to login when sessions expire
* **Cache minimally:** Session tokens should be verified fresh on each request
### For session JWTs
* **Refresh proactively:** Refresh JWTs before the 5-minute expiration
* **Validate claims:** Check `exp`, `iat`, and other claims during verification
* **Use reputable libraries:** Use well-maintained JWT libraries like `jose` or `jsonwebtoken`
* **Cache public keys:** Cache JWKS responses to avoid fetching on every verification
* **Re-verify for sensitive actions:** Use the API to double-check session status for critical operations
## Implementation examples
### Using both with fallback
```javascript theme={null}
async function verifySession(sessionToken, sessionJWT) {
// Try JWT first (fast)
try {
const jwtPayload = await verifyJWTLocally(sessionJWT);
// Check if JWT is about to expire
const expiresAt = jwtPayload.exp;
const now = Math.floor(Date.now() / 1000);
const timeRemaining = expiresAt - now;
if (timeRemaining < 60) {
// Less than 1 minute remaining, refresh via API
const refreshed = await refreshSessionViaAPI(sessionToken);
return refreshed;
}
return jwtPayload;
} catch (error) {
// JWT verification failed or expired, fallback to API
return await verifySessionViaAPI(sessionToken);
}
}
```
### Automatic JWT refresh
```javascript theme={null}
class SessionManager {
constructor(sessionToken, sessionJWT) {
this.sessionToken = sessionToken;
this.sessionJWT = sessionJWT;
this.refreshInterval = null;
}
startAutoRefresh() {
// Refresh JWT every 4 minutes (before 5-min expiration)
this.refreshInterval = setInterval(async () => {
try {
const response = await this.refreshJWT();
this.sessionJWT = response.session_jwt;
} catch (error) {
// Session expired or invalid
this.stopAutoRefresh();
this.handleSessionExpired();
}
}, 4 * 60 * 1000); // 4 minutes
}
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
async refreshJWT() {
const response = await fetch('https://api.moonkey.fun/v1/auth/sessions/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_jwt: this.sessionJWT
})
});
return response.json();
}
handleSessionExpired() {
// Clear session and redirect to login
localStorage.clear();
window.location.href = '/login';
}
}
```
## Related resources
Implement session management with the REST API
API reference for verifying sessions
Retrieve public keys for JWT verification
Learn the basics of MoonKey sessions
# Logging Users Out
Source: https://docs.streambird.io/authentication/user-authentication/signing-users-out
End a user's authenticated session and remove their access credentials from the device.
Logging out a user removes their session from the device and requires them to authenticate again to access protected resources.
## Using the React SDK
To log a user out, use the `logout` method from the `useMoonKey` hook:
```typescript theme={null}
logout: () => Promise
```
### Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function LogoutButton() {
const { ready, isAuthenticated, logout } = useMoonKey();
// Disable logout when SDK is not ready or user is not authenticated
const disableLogout = !ready || (ready && !isAuthenticated);
return (
);
}
```
### With Async Handling
Since `logout` returns a Promise, you can await it to run code after the user has been logged out:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useRouter } from 'next/router';
function LogoutButton() {
const { logout } = useMoonKey();
const router = useRouter();
const handleLogout = async () => {
try {
await logout();
// Perform actions after logout completes
console.log('User logged out successfully');
router.push('/');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
);
}
```
## Common Patterns
### Logout with Confirmation
Ask the user to confirm before logging out:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
function LogoutWithConfirmation() {
const { logout } = useMoonKey();
const [showConfirm, setShowConfirm] = useState(false);
const handleLogout = async () => {
await logout();
setShowConfirm(false);
};
return (
<>
{showConfirm && (
Are you sure you want to log out?
)}
>
);
}
```
### Logout with Loading State
Show a loading state during logout:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
function LogoutButton() {
const { logout } = useMoonKey();
const [isLoggingOut, setIsLoggingOut] = useState(false);
const handleLogout = async () => {
setIsLoggingOut(true);
try {
await logout();
// Redirect or show success message
} catch (error) {
console.error('Logout failed:', error);
} finally {
setIsLoggingOut(false);
}
};
return (
);
}
```
### Logout in Header/Navigation
Common pattern for a header component:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useRouter } from 'next/router';
function Header() {
const { ready, isAuthenticated, user, logout } = useMoonKey();
const router = useRouter();
const handleLogout = async () => {
await logout();
router.push('/');
};
if (!ready) {
return Loading...;
}
return (
);
}
```
### Logout with Redirect
Redirect to a specific page after logout:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useRouter } from 'next/router';
function LogoutButton() {
const { logout } = useMoonKey();
const router = useRouter();
const handleLogout = async () => {
await logout();
// Redirect to home page or login page
router.push('/login');
};
return (
);
}
```
### Dropdown Menu with Logout
Common pattern for user menus:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
function UserMenu() {
const { user, logout } = useMoonKey();
const [isOpen, setIsOpen] = useState(false);
const handleLogout = async () => {
setIsOpen(false);
await logout();
};
return (
);
}
```
## Effect of Logout
When you call `logout()`, MoonKey will:
1. **Clear the session** - Remove session tokens and JWTs from IndexedDB
2. **Reset authentication state** - Set `isAuthenticated` to `false`
3. **Clear user data** - Remove the user object from the SDK state
4. **Revoke session on server** - Invalidate the session on MoonKey's backend
After logout, the user will need to authenticate again to access protected resources.
## Using the REST API
For backend or custom implementations, you can logout by deleting the session via the REST API:
```bash theme={null}
curl -X DELETE "https://api.moonkey.fun/v1/auth/sessions/delete" \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"session_token": "session_abc123..."
}'
```
### Backend Implementation
```javascript theme={null}
// Express.js example
app.post('/api/logout', async (req, res) => {
const sessionToken = req.cookies.session_token;
if (!sessionToken) {
return res.status(400).json({ error: 'No session found' });
}
try {
const response = await fetch(
'https://api.moonkey.fun/v1/auth/sessions/delete',
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_token: sessionToken
})
}
);
if (!response.ok) {
throw new Error('Failed to delete session');
}
// Clear the cookie
res.clearCookie('session_token');
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Logout failed' });
}
});
```
## Best Practices
### Always Handle Errors
Logout can fail due to network issues or other reasons:
```tsx theme={null}
const handleLogout = async () => {
try {
await logout();
console.log('Logout successful');
} catch (error) {
console.error('Logout failed:', error);
// Still clear local state and redirect
// The session will be invalid on the server anyway
}
};
```
### Clear Local State
If you have additional local state, clear it on logout:
```tsx theme={null}
const handleLogout = async () => {
await logout();
// Clear any local storage
localStorage.removeItem('user_preferences');
// Reset any app state
resetAppState();
// Redirect
router.push('/');
};
```
### Redirect After Logout
Always redirect users to an appropriate page after logout:
```tsx theme={null}
const handleLogout = async () => {
await logout();
router.push('/'); // or '/login'
};
```
### Show Confirmation for Important Actions
For applications with unsaved data, confirm before logging out:
```tsx theme={null}
const handleLogout = async () => {
if (hasUnsavedChanges) {
const confirmed = window.confirm(
'You have unsaved changes. Are you sure you want to log out?'
);
if (!confirmed) return;
}
await logout();
router.push('/');
};
```
# Email OTP
Source: https://docs.streambird.io/authentication/user-authentication/ui-components/email-otp
The Email OTP screen is displayed when users authenticate via email. It prompts users to enter the one-time passcode (OTP) sent to their email address.
The Email OTP screen automatically appears after a user enters their email address in the [login modal](/authentication/user-authentication/ui-components/login).
## How it works
When a user selects email authentication:
1. User enters their email address in the login modal
2. MoonKey sends a one-time passcode to the email
3. The Email OTP screen appears, prompting the user to enter the code
4. User enters the 6-digit code
5. Upon successful verification, the user is authenticated
## Customizing the Email OTP screen
You can customize the Email OTP experience by configuring the `emailOtp` prop in your `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
## Configuration options
### Email OTP behavior
Skip showing the success screen after email verification and immediately proceed to the application.
When `true`, users won't see a success message and will be taken directly to your app after entering the correct OTP.
**Example:**
```tsx theme={null}
emailOtp: {
skipVerifiedSuccess: true // Skip success screen, authenticate immediately
}
```
Time in seconds before the OTP expires.
**Default:** `600` (10 minutes)
**Example:**
```tsx theme={null}
emailOtp: {
expiresIn: 300 // OTP expires after 5 minutes
}
```
Time in milliseconds to display the success message before closing the modal.
Only applies when `skipVerifiedSuccess` is `false`.
**Default:** `3000` (3 seconds)
**Example:**
```tsx theme={null}
emailOtp: {
verifiedDisplayTime: 2000 // Show success message for 2 seconds
}
```
Time in seconds before users can request a new OTP code.
**Default:** `30` (30 seconds)
**Example:**
```tsx theme={null}
emailOtp: {
resendCodeTime: 60 // Users must wait 60 seconds before resending
}
```
### UI customization
Title text displayed on the email verification screen.
**Example:**
```tsx theme={null}
emailOtp: {
verifyEmailTitle: 'Check your email'
}
```
Title text for the resend code prompt.
**Example:**
```tsx theme={null}
emailOtp: {
verifyEmailResendTitle: 'Need a new code?'
}
```
Success message shown to new users after verification.
Displayed when a user successfully verifies their email for the first time.
**Example:**
```tsx theme={null}
emailOtp: {
verifiedNewUserMessage: 'Account created successfully! Welcome aboard.'
}
```
Success message shown to existing users after verification.
Displayed when a returning user successfully verifies their email.
**Example:**
```tsx theme={null}
emailOtp: {
verifiedExistingUserMessage: 'Great to see you again! Signing you in...'
}
```
Generic success message shown after verification.
This is a fallback message when MoonKey cannot determine if the user is new or existing.
**Example:**
```tsx theme={null}
emailOtp: {
verifiedSuccessMessage: 'Verification complete!'
}
```
## Common configurations
### Quick verification (skip success screen)
For a streamlined experience, skip the success screen and authenticate users immediately:
```tsx theme={null}
{children}
```
### Custom timing
Adjust OTP expiration and resend cooldown for your security requirements:
```tsx theme={null}
{children}
```
### Custom messaging
Personalize the verification flow with custom messages:
```tsx theme={null}
{children}
```
### Enterprise security settings
For enterprise applications requiring stricter security:
```tsx theme={null}
{children}
```
## General appearance customization
In addition to Email OTP-specific settings, you can customize the general appearance that applies to all UI components, including the Email OTP screen:
```tsx theme={null}
{children}
```
Learn more about [configuring appearance](/get-started/frontend-sdks/react/advance/configure-appearance).
## Complete example
Here's a complete example with all Email OTP customization options:
```tsx theme={null}
'use client';
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
## User experience flow
Here's what the complete email authentication flow looks like with Email OTP:
User clicks "Sign in" button, opening the login modal.
User selects email authentication and enters their email address.
The Email OTP screen appears, prompting the user to enter the code.
User checks their email inbox for the 6-digit verification code.
User enters the code on the Email OTP screen.
MoonKey verifies the code. If valid:
* Shows success message (unless `skipVerifiedSuccess: true`)
* Authenticates the user
* Creates embedded wallet (if `createOnLogin: 'always'`)
* Closes the modal and returns user to the app
## Resending codes
If a user doesn't receive the OTP or it expires, they can request a new code:
1. User clicks "Resend code" link on the Email OTP screen
2. MoonKey enforces the `resendCodeTime` cooldown
3. A new OTP is sent to the user's email
4. User enters the new code
The resend cooldown prevents abuse and protects against spam attacks.
## Handling errors
The Email OTP screen automatically handles common errors:
* **Invalid code** - Shows an error message and allows the user to try again
* **Expired code** - Prompts the user to request a new code
* **Too many attempts** - May temporarily lock verification (contact support if this occurs)
## Best practices
* **Consumer apps**: 5-10 minutes is typical
* **Enterprise apps**: 3-5 minutes for higher security
* Consider your users' email access patterns
* **Recommended**: 30-60 seconds
* Prevents spam while allowing legitimate resend requests
* Too short: risk of abuse
* Too long: frustrates users
* Differentiate between new and returning users
* Use friendly, encouraging language
* Consider your brand voice
* For faster authentication, set `skipVerifiedSuccess: true`
* Best for apps where speed is critical
* Still provide feedback in your app after authentication
* Test with various email providers (Gmail, Outlook, etc.)
* Check spam folders to ensure delivery
* Verify timing configurations work as expected
* Test resend functionality
## TypeScript interface
For TypeScript users, here's the complete `EmailOtpConfig` interface:
```typescript theme={null}
interface EmailOtpConfig {
skipVerifiedSuccess?: boolean;
expiresIn?: number;
verifiedDisplayTime?: number;
verifyEmailTitle?: string;
verifyEmailResendTitle?: string;
resendCodeTime?: number;
verifiedNewUserMessage?: string;
verifiedExistingUserMessage?: string;
verifiedSuccessMessage?: string;
}
```
All fields are optional with sensible defaults.
## Next steps
Learn more about email authentication
Customize the login modal
Global appearance settings
Explore all UI components
# Login
Source: https://docs.streambird.io/authentication/user-authentication/ui-components/login
The fastest way to integrate MoonKey is with the built-in login modal, which your application can integrate in just a few lines of code.
[Configure your login methods](/get-started/dashboard/login-methods) in the MoonKey Dashboard before using the login modal.
## Opening the login modal
The `useMoonKey` hook exposes a `start` method that opens the login modal:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function LoginButton() {
const { start } = useMoonKey();
return (
);
}
```
Once the user logs in, the `user` object will be populated with the authenticated user's data, and the `isAuthenticated` state will be `true`.
### Showing the login modal automatically
By default, the login modal will not appear automatically. If you want it to appear automatically on page load (e.g., for unauthenticated users), you can call `start()` in a `useEffect`:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useEffect } from 'react';
export default function AutoLogin() {
const { start, isAuthenticated } = useMoonKey();
useEffect(() => {
// Only show login if user is not authenticated
if (!isAuthenticated) {
start();
}
}, [isAuthenticated, start]);
return
Welcome!
;
}
```
Be careful when automatically opening the login modal. It may interrupt the user experience if triggered too frequently. Consider using user intent signals (e.g., clicking a button, navigating to a protected route) instead.
## Customizing the login modal
You can customize the appearance of the login modal by configuring the `appearance` prop in your `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
### Available customization options
URL to your company logo. Displayed at the top of the login modal.
**Example:** `'https://myapp.com/logo.png'`
Custom title text displayed in the login modal header.
**Default:** `'Sign in'`
**Example:** `'Welcome Back'`
Custom description text displayed below the title in the login modal.
**Default:** `'Connect your wallet or sign in with email'`
**Example:** `'Sign in to access your account'`
Whether to hide the close button on the login modal.
**Default:** `false`
**Example:** `true` (useful for required authentication flows)
### Example: Complete customization
```tsx theme={null}
{children}
```
## Login methods
The login modal displays authentication options based on the `loginMethods` configured in your `MoonKeyProvider`. You can enable multiple methods to give users flexibility in how they authenticate:
### Email authentication
Enable email-based passwordless authentication:
```tsx theme={null}
{children}
```
When users select email login, they'll be prompted to enter their email address. MoonKey will send a one-time passcode (OTP) to verify their identity.
Learn more about [email authentication](/authentication/user-authentication/authentication-methods/email).
### OAuth providers
Enable social login with Google and Apple:
```tsx theme={null}
{children}
```
You must configure OAuth credentials in the [MoonKey Dashboard](/get-started/dashboard/login-methods) before using OAuth login methods.
**Supported OAuth providers:**
* `'google'` - Google OAuth
* `'apple'` - Apple Sign In
Additional OAuth providers (Microsoft, Discord, GitHub, etc) are coming soon.
Learn more about [OAuth authentication](/authentication/user-authentication/authentication-methods/oauth).
### Wallet authentication
Enable Web3 wallet login with Sign-In with Ethereum (SIWE) or Sign-In with Solana (SIWS):
```tsx theme={null}
{children}
```
This allows users to authenticate by connecting their external wallets (MetaMask, Phantom, etc.) and signing a message.
### Multiple login methods
You can enable multiple login methods to give users options:
```tsx theme={null}
{children}
```
The login modal will display all enabled authentication methods, allowing users to choose their preferred option.
## Handling authentication states
After the user completes authentication, you can access their information using the `useMoonKey` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function UserProfile() {
const { user, isAuthenticated, ready } = useMoonKey();
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return
Please sign in
;
}
return (
Welcome!
User ID: {user?.id}
Email: {user?.email?.address}
{user?.wallet && (
Wallet: {user.wallet.address}
)}
);
}
```
Learn more about [authentication state](/authentication/user-authentication/authentication-state).
## Closing the login modal
Users can close the login modal by:
* Clicking the close button (X) in the top-right corner (unless `hideClose` is `true`)
* Pressing the Escape key
* Clicking outside the modal (on the backdrop)
You can also programmatically close the modal after certain actions or events in your application logic.
## Creating embedded wallets
By default, MoonKey will not automatically create an embedded wallet when a user logs in. You can configure automatic wallet creation with the `embeddedWallets.createOnLogin` option:
```tsx theme={null}
{children}
```
**Options:**
* `'always'` - Automatically create an embedded wallet for every user on login
* `'none'` - Don't create wallets automatically (default). You can create them manually later using `useCreateWallet`
Learn more about [automatic wallet creation](/get-started/frontend-sdks/react/advance/automatic-wallet-creation).
## Email OTP configuration
When using email authentication, you can customize the OTP verification experience:
```tsx theme={null}
{children}
```
### Email OTP configuration options
Skip showing the success screen after email verification and immediately proceed to the application.
Time in seconds before the OTP expires (default: 600 = 10 minutes).
Time in milliseconds to display the success message before closing the modal (default: 3000 = 3 seconds).
Title text for the email verification screen.
Title text for the resend code prompt.
Time in seconds before users can request a new OTP code (default: 30 seconds).
Success message shown to new users after verification.
Success message shown to existing users after verification.
Generic success message shown after verification.
Learn more about [email authentication](/authentication/user-authentication/authentication-methods/email).
## Prefilling user information
You can prefill the login form with user information (e.g., email address) to streamline the authentication flow:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function PrefillLoginButton() {
const { start } = useMoonKey();
const handleLogin = () => {
start({
prefill: {
type: 'email',
value: 'user@example.com'
}
});
};
return ;
}
```
This is useful when you already know the user's email (e.g., from a marketing campaign, invitation link, or previous session).
### Prefill configuration
Prefill configuration for the login form.
The type of prefill data. Currently only `'email'` is supported.
The value to prefill (e.g., the email address).
## Complete example
Here's a complete example showing login modal integration with all customization options:
```tsx theme={null}
'use client';
import { MoonKeyProvider, useMoonKey } from '@moon-key/react-auth';
function App() {
return (
);
}
function Dashboard() {
const { user, isAuthenticated, ready, start } = useMoonKey();
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return (
Welcome to MyApp
);
}
return (
Dashboard
Welcome, {user?.email?.address}!
{user?.wallet && (
Your wallet: {user.wallet.address}
)}
);
}
export default App;
```
## Next steps
Customize the email verification screen
Learn about all appearance options
Explore available authentication methods
Managing user authentication state
# Overview
Source: https://docs.streambird.io/authentication/user-authentication/ui-components/overview
Easy user onboarding with MoonKey's customizable UI components.
MoonKey provides pre-built user interface components that handle authentication interactions. These components are designed to integrate seamlessly into your application while remaining highly customizable to match your brand.
[Configure your login methods](/get-started/dashboard/login-methods) in the MoonKey Dashboard before using the UI components.
See live demos of all UI components and customization options at [demo.moonkey.fun](https://demo.moonkey.fun).
## Available UI Components
MoonKey provides UI components for the complete authentication and wallet interaction flow:
### Authentication Components
The main authentication screen with support for email, OAuth, and wallet login methods.
Email verification code entry screen for passwordless authentication.
## Key Features
### Pre-built & Ready to Use
All UI components come ready to use out of the box. Simply call the appropriate method from the MoonKey SDK, and the UI will handle the rest:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function MyComponent() {
const { start } = useMoonKey();
// Opens the login modal
return ;
}
```
### Highly Customizable
Every UI component can be customized to match your brand:
* **Logo** - Display your company logo
* **Header Title** - Custom title for each screen
* **Header Description** - Custom description text
* **Close Button** - Show or hide the close button
## Customization
Configure the appearance of all UI components in your `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App() {
return (
{/* Your app */}
);
}
```
These appearance settings apply to all UI components throughout the authentication and wallet interaction flows.
Learn more about [configuring appearance](/get-started/frontend-sdks/react/advance/configure-appearance).
## Common Use Cases
### Seamless Onboarding
Create a smooth onboarding experience by combining multiple UI components:
1. User clicks "Sign in" → **Login Modal** appears
2. User enters email → **Email OTP** screen for verification
3. User verifies email → Automatically creates embedded wallet
4. Ready to interact with blockchain!
### Custom Branding
Maintain consistent branding across all screens:
```tsx theme={null}
{children}
```
## Component Details
Explore detailed documentation for each UI component:
Authentication with email, OAuth, and wallet
Email verification code entry
# Whitelabel
Source: https://docs.streambird.io/authentication/user-authentication/whitelabel
MoonKey's React SDK lets you fully customize authentication to match your app's brand and user experience. You can implement every authentication flow—including email OTP, OAuth (Google and Apple), and Web3 wallet signatures—with your own UI, while MoonKey manages security and backend logic.
All of MoonKey's authentication methods can be whitelabeled, allowing you to build completely custom authentication flows without using the default UI components.
## Available authentication methods
MoonKey provides headless authentication hooks that give you full control over your UI:
Passwordless email authentication with one-time codes
Social login with Google and Apple
Web3 authentication with SIWE and SIWS
## Email OTP Authentication
To whitelabel MoonKey's passwordless email flow, use the `useLoginWithEmail` hook. Call `sendCode` to send a verification code and `loginWithCode` to authenticate the user.
### Basic usage
```tsx theme={null}
import { useLoginWithEmail } from '@moon-key/react-auth';
function CustomEmailLogin() {
const { sendCode, loginWithCode, state } = useLoginWithEmail();
// Send code to user's email
await sendCode({ email: 'user@example.com' });
// Login with the code
await loginWithCode({ code: '123456' });
}
```
### With callbacks
You can pass callbacks to handle success and error states:
```tsx theme={null}
import { useLoginWithEmail } from '@moon-key/react-auth';
import { useState } from 'react';
function CustomEmailLogin() {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const { sendCode, loginWithCode, state } = useLoginWithEmail({
onComplete: (user) => {
console.log('✅ Login successful:', user);
// Redirect to dashboard or update UI
},
onError: (error) => {
console.error('❌ Login failed:', error);
// Show error message to user
}
});
const handleSendCode = async () => {
try {
await sendCode({ email });
alert(`Code sent to ${email}!`);
} catch (error) {
// Error handled by onError callback
}
};
const handleLogin = async () => {
try {
await loginWithCode({ code });
} catch (error) {
// Error handled by onError callback
}
};
const isLoading = state.status === 'sending-code' ||
state.status === 'verifying-code';
return (
);
}
```
### Flow state tracking
The `state` object tracks the current status of the authentication flow:
The current status of the authentication flow.
**Possible values:**
* `'initial'` - Flow hasn't started
* `'sending-code'` - Sending OTP to email
* `'waiting-code-input'` - Code sent, waiting for user input
* `'verifying-code'` - Verifying the entered code
* `'complete'` - Authentication successful
* `'error'` - An error occurred
Error object if an error occurred, `null` otherwise.
Learn more about [email authentication](/authentication/user-authentication/authentication-methods/email).
## OAuth Authentication
To whitelabel OAuth login with Google and Apple, use the `useLoginWithOAuth` hook and call `initOAuth` with your desired provider.
### Basic usage
```tsx theme={null}
import { useLoginWithOAuth } from '@moon-key/react-auth';
function CustomOAuthLogin() {
const { initOAuth } = useLoginWithOAuth();
// Initiate Google OAuth flow
initOAuth({ provider: 'google' });
// Initiate Apple OAuth flow
initOAuth({ provider: 'apple' });
}
```
### With callbacks
```tsx theme={null}
import { useLoginWithOAuth } from '@moon-key/react-auth';
function CustomOAuthLogin() {
const { initOAuth, state } = useLoginWithOAuth({
onSuccess: (user) => {
console.log('✅ OAuth login successful:', user);
// User is authenticated and stays on current page
},
onError: (error) => {
console.error('❌ OAuth login failed:', error);
// Handle error (e.g., show error message)
}
});
const handleGoogleLogin = () => {
initOAuth({ provider: 'google' });
};
const handleAppleLogin = () => {
initOAuth({ provider: 'apple' });
};
return (
{state.status === 'error' && state.error && (
Error: {state.error.message}
)}
);
}
```
### OAuth state tracking
The `state` object tracks the OAuth flow:
The current status of the OAuth flow.
**Possible values:**
* `'idle'` - No OAuth flow in progress
* `'loading'` - OAuth flow in progress
* `'done'` - OAuth authentication successful
* `'error'` - An error occurred
The authenticated user object after successful OAuth login.
Error object if an error occurred.
You must configure OAuth credentials in the [MoonKey Dashboard](/get-started/dashboard/login-methods) before using OAuth authentication.
Learn more about [OAuth authentication](/authentication/user-authentication/authentication-methods/oauth).
## Complete example
Here's a complete whitelabel authentication example with both email and OAuth:
```tsx theme={null}
'use client';
import { useLoginWithEmail, useLoginWithOAuth, useMoonKey } from '@moon-key/react-auth';
import { useState } from 'react';
export default function CustomAuthPage() {
const { isAuthenticated, user, logout, ready } = useMoonKey();
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [message, setMessage] = useState('');
// Email authentication hook
const { sendCode, loginWithCode, state: emailState } = useLoginWithEmail({
onComplete: (user) => {
console.log('✅ Email login successful:', user);
setMessage('Login successful!');
},
onError: (error) => {
console.error('❌ Email login failed:', error);
setMessage(`Error: ${error.message}`);
}
});
// OAuth authentication hook
const { initOAuth, state: oauthState } = useLoginWithOAuth({
onSuccess: (user) => {
console.log('✅ OAuth login successful:', user);
setMessage('OAuth login successful!');
},
onError: (error) => {
console.error('❌ OAuth login failed:', error);
setMessage(`OAuth error: ${error.message}`);
}
});
const handleSendCode = async () => {
setMessage('');
try {
await sendCode({ email });
setMessage(`Code sent to ${email}!`);
} catch (error) {
// Handled by onError
}
};
const handleLogin = async () => {
setMessage('');
try {
await loginWithCode({ code });
} catch (error) {
// Handled by onError
}
};
const handleLogout = async () => {
await logout();
setMessage('Logged out successfully');
setEmail('');
setCode('');
};
const isLoading = emailState.status === 'sending-code' ||
emailState.status === 'verifying-code' ||
oauthState.status === 'loading';
if (!ready) {
return
Loading MoonKey SDK...
;
}
if (isAuthenticated && user) {
return (
Welcome!
User ID: {user.id}
Email: {user.email?.address}
);
}
return (
Sign In
{message &&
{message}
}
{/* Email Authentication */}
Email Login
setEmail(e.target.value)}
placeholder="Enter your email"
disabled={isLoading}
/>
)}
```
Handle navigation and UI updates in callbacks:
```tsx theme={null}
const { sendCode } = useLoginWithEmail({
onComplete: (user) => {
// Navigate to dashboard
router.push('/dashboard');
},
onError: (error) => {
// Show toast notification
toast.error(error.message);
}
});
```
Use the flow state to show relevant UI:
```tsx theme={null}
// Show code input only after sending code
{state.status === 'waiting-code-input' && (
)}
// Show success message
{state.status === 'complete' && (
Authentication successful!
)}
```
Prevent users from modifying inputs during operations:
```tsx theme={null}
setEmail(e.target.value)}
disabled={isLoading || !ready}
/>
```
Check the `ready` state before rendering auth UI:
```tsx theme={null}
const { ready } = useMoonKey();
if (!ready) {
return
Loading...
;
}
```
## Advanced patterns
### Multi-step forms
Build multi-step authentication flows:
```tsx theme={null}
function MultiStepAuth() {
const [step, setStep] = useState<'email' | 'code' | 'complete'>('email');
const { sendCode, loginWithCode, state } = useLoginWithEmail({
onComplete: () => setStep('complete')
});
switch (step) {
case 'email':
return setStep('code')} />;
case 'code':
return setStep('email')} />;
case 'complete':
return ;
}
}
```
### Conditional authentication
Show different auth methods based on user type:
```tsx theme={null}
function ConditionalAuth({ userType }: { userType: 'new' | 'returning' }) {
if (userType === 'new') {
return ;
}
return ;
}
```
### Prefilled email
Pre-populate email for invited users:
```tsx theme={null}
function InviteAuth({ inviteEmail }: { inviteEmail: string }) {
const [email] = useState(inviteEmail);
const { sendCode } = useLoginWithEmail();
useEffect(() => {
// Automatically send code to invited email
sendCode({ email });
}, []);
return ;
}
```
## TypeScript types
For TypeScript users, here are the relevant types:
```typescript theme={null}
// Email authentication
interface UseLoginWithEmailOptions {
onComplete?: (user: User) => void;
onError?: (error: Error) => void;
}
interface EmailAuthState {
status: 'initial' | 'sending-code' | 'waiting-code-input' | 'verifying-code' | 'complete' | 'error';
error: Error | null;
}
// OAuth authentication
interface UseLoginWithOAuthOptions {
onSuccess?: (user: User) => void;
onError?: (error: Error) => void;
}
interface OAuthState {
status: 'idle' | 'loading' | 'done' | 'error';
user: User | null;
error: Error | null;
}
```
## Troubleshooting
If users don't receive the verification code:
* Check spam/junk folders
* Verify the email address is correct
* Wait a few moments before resending
* Check your email configuration in the MoonKey Dashboard
If OAuth redirects fail:
* Verify redirect URLs in the MoonKey Dashboard
* Check that OAuth credentials are configured
* Ensure your domain is whitelisted
* Test in an incognito window to rule out browser issues
If the state doesn't update correctly:
* Ensure you're using the latest SDK version
* Check that callbacks are properly defined
* Verify the SDK is initialized correctly
* Look for console errors
# About MoonKey
Source: https://docs.streambird.io/get-started/about
MoonKey provides authentication and wallet infrastructure that empowers developers to build seamless blockchain-powered applications. Whether you're building for web3-native users or complete newcomers to crypto, MoonKey's SDK helps you implement secure authentication, wallet management, and transaction signing in just a few lines of code.
## What you can build with MoonKey
**Seamless authentication flows** — Create frictionless login experiences using multiple authentication methods including OAuth providers (Google, Apple, Discord, GitHub, Microsoft), magic links, email/SMS OTPs, passkeys (WebAuthn), TOTP-based MFA, and Web3 wallet signatures. Users can authenticate however they prefer, while you maintain a unified user identity across all methods.
**Self-custodial wallet solutions** — Generate and manage embedded wallets for your users without compromising on security. Whether users need a single wallet or multiple wallets across different blockchains, MoonKey handles the complexity of key management while keeping users in full control of their assets.
## What makes MoonKey different
### Security-first architecture
User security isn't negotiable. Our infrastructure leverages distributed key sharding technology, meaning private keys are never stored in a single location—not even we can access them. Keys are only reconstructed within secure execution environments when transactions need to be signed.
### Developer-friendly integration
We designed MoonKey to fit into your existing workflow. Our React SDK provides ready-to-use authentication components that work out of the box, while our REST API gives you complete control when you need custom implementations. Start with our pre-built UI components, then customize or replace them as your product evolves.
### Built for scale
From authentication to wallet management, every component is designed to handle production-scale applications. Session management, device fingerprinting for fraud prevention, recovery mechanisms, and comprehensive user management APIs ensure your application remains reliable as you grow.
### True multi-chain support
Native compatibility with Ethereum, Solana, and all EVM/SVM-compatible chains means you're not locked into a single ecosystem. Deploy wallets and manage assets across any blockchain your application needs—today or in the future.
Whether you're building a DeFi platform, an NFT marketplace, a gaming application, or any product that touches blockchain technology, MoonKey provides the authentication and wallet infrastructure you need to focus on what makes your product unique.
# Create new app
Source: https://docs.streambird.io/get-started/dashboard/create-new-app
To start integrating MoonKey into your application, you'll first need to create an app in the MoonKey Dashboard. Each app provides unique credentials that authenticate your requests to MoonKey's APIs and SDKs.
Create separate apps for different environments (development, staging, production) to isolate credentials and user data across your deployment pipeline.
## Creating your first app
1. Log in to the [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Navigate to the Applications section
3. Click **Create New App**
4. Provide a name for your application and select your environment type
5. Your new app will be created with default configurations
## Retrieving your API credentials
Once your app is created, navigate to **API Keys** from the sidebar. Here you'll find two critical pieces of information:
**App ID (Public Key)** — This is a public identifier for your application. You can safely expose this in your frontend code, mobile apps, or anywhere public-facing. It's used to initialize the MoonKey SDK in client-side applications.
**App Secret (Private Key)** — This is a sensitive credential used to authenticate server-side API requests. Never expose this key in client-side code, version control, or public repositories. Store it securely in environment variables or secret management systems.
## Configuring authentication methods
Before your app can authenticate users, you'll need to enable at least one authentication method. MoonKey supports multiple login options including:
* OAuth providers (Google, Apple, etc)
* Email OTPs
* Web3 wallet signatures
* More to follow
Navigate to the **OAuth** and **SDK Settings** sections in your dashboard to configure which authentication methods are available to your users. You can enable multiple methods to give users flexibility in how they sign in.
[Learn how to configure login methods →](/get-started/dashboard/login-methods)
## Next steps
With your app created and credentials in hand, you're ready to integrate MoonKey:
* **Frontend integration**: Install the React SDK and initialize it with your App ID
* **Backend integration**: Set up the Node.js SDK with your App Secret
* **REST API**: Use your credentials to authenticate direct API requests
Choose your integration path based on your application architecture and requirements.
# Configure login methods
Source: https://docs.streambird.io/get-started/dashboard/login-methods
To enable user authentication in your application, you'll need to configure at least one login method in the MoonKey Dashboard. Choose from multiple authentication options to provide your users with flexible and secure access to their accounts.
We recommend enabling multiple authentication methods to ensure users can access their accounts even if they lose access to one method. Consider combining email with social providers or wallet login for better coverage.
## Basic login methods
### Email login
Email authentication provides a passwordless login experience using one-time passwords (OTPs) sent directly to users' email addresses.
**How it works:**
1. User enters their email address
2. MoonKey sends a 6-digit OTP to their email
3. User enters the OTP to complete authentication
4. OTP expires after 10 minutes
**To enable email login:**
1. Navigate to **SDK Settings** in your dashboard
2. Enable the **Email** authentication option
3. Optionally customize the email template in **Email Templates**
Email addresses are automatically verified through the OTP process. Users who authenticate via email will have their email marked as verified in their account.
### Wallet login
Wallet-based authentication allows users to sign in using their existing Web3 wallets through cryptographic signature verification.
**Supported wallet authentication:**
* **Sign In With Ethereum (SIWE)** — Authenticate users with any Ethereum or EVM-compatible wallet
* **Sign In With Solana (SIWS)** — Authenticate users with Solana wallets
**How it works:**
1. User connects their Web3 wallet (MetaMask, Phantom, etc.)
2. MoonKey generates a signature request
3. User signs the message in their wallet
4. MoonKey verifies the signature and authenticates the user
**To enable wallet login:**
1. Navigate to **SDK Settings** in your dashboard
2. Enable **Wallet** authentication
3. Select which blockchain networks to support (Ethereum, Solana, or both)
Wallet authentication is ideal for crypto-native applications where users already have Web3 wallets. It provides a familiar experience for blockchain users.
## Social providers (OAuth)
MoonKey supports OAuth-based authentication through popular social providers, allowing users to sign in with their existing social accounts.
### Supported OAuth providers
* **Google** — Let users sign in with their Google account
* **Apple** — Let users sign in with their Apple ID
Additional OAuth providers (Microsoft, Discord, GitHub, and more) are coming soon. Contact us if you need support for a specific provider.
### Configuring your OAuth credentials
To use OAuth authentication, you must configure your own OAuth credentials for each provider. MoonKey does not provide default credentials.
**Benefits of using OAuth:**
* Your branding appears on the authentication screen
* Users can leverage existing accounts they trust
* Streamlined onboarding without password creation
* Automatic email verification through the provider
### Configuring custom OAuth credentials
For each social provider you want to support, create an OAuth application through their developer portal:
* **Google**: [Google Cloud Console](https://console.cloud.google.com/)
* **Apple**: [Apple Developer Portal](https://developer.apple.com/)
During setup, you'll need to specify MoonKey's OAuth callback URL as your redirect URI:
```
https://auth.moonkey.fun/api/v1/oauth/callback
```
After creating your OAuth apps, navigate to the **OAuth** section in your MoonKey Dashboard:
1. Select the provider you want to configure
2. Enter your OAuth credentials (Client ID and Client Secret)
3. Save the configuration
OAuth credentials go live immediately upon saving. Always test in a development app with development credentials before configuring your production app.
Once your credentials are configured:
1. Navigate to **SDK Settings**
2. Enable the OAuth providers you want users to access
3. The authentication options will appear in your application's login flow
### Provider-specific setup guides
**Creating a Google OAuth application:**
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the Google+ API
4. Navigate to **Credentials** and create OAuth 2.0 credentials
5. Add `https://auth.moonkey.fun/api/v1/oauth/callback` as an authorized redirect URI
6. Copy your Client ID and Client Secret
**Required scopes:**
* `openid`
* `email`
* `profile`
Google OAuth may not work in in-app browsers (like those embedded in social media apps) due to Google's security restrictions. Consider enabling additional login methods for broad compatibility.
**Creating an Apple OAuth application:**
1. Visit [Apple Developer Portal](https://developer.apple.com/)
2. Create a new Services ID under **Certificates, Identifiers & Profiles**
3. Enable **Sign in with Apple** capability
4. Configure the redirect URL: `https://auth.moonkey.fun/api/v1/oauth/callback`
5. Create a private key for your Services ID
6. Note your Team ID, Services ID, and Key ID
You'll need to provide:
* **Team ID**: Your Apple Developer team identifier
* **Services ID**: Your application's services identifier
* **Key ID**: The identifier for your Sign in with Apple key
* **Private Key**: The .p8 private key file
## Testing your configuration
After enabling login methods, test each one in your development environment:
1. Initialize the MoonKey SDK with your development App ID
2. Attempt to authenticate using each enabled method
3. Verify users can successfully log in and their data appears in the dashboard
4. Test the user experience across different browsers and devices
Always test authentication flows in a development app before deploying to production. This prevents disruptions to existing users.
## Best practices
**Security:**
* Store OAuth credentials securely and never commit them to version control
* Use separate apps and credentials for development, staging, and production
* Regularly rotate OAuth secrets according to your security policies
**User experience:**
* Enable multiple authentication methods to accommodate different user preferences
* Provide clear messaging about which login methods are available
* Consider regional availability when choosing authentication options
**Compliance:**
* Review each OAuth provider's terms of service and usage requirements
* Ensure your privacy policy covers data collected through social logins
* Maintain compliance with data protection regulations (GDPR, CCPA, etc.)
# Dashboard
Source: https://docs.streambird.io/get-started/dashboard/overview
# Overview
After creating your account, the MoonKey Dashboard becomes your central hub for managing applications, configurations, and team access.
## Managing Applications
The Dashboard allows you to create and manage multiple applications within your account. Each app in MoonKey is an isolated environment with its own:
* API keys and credentials
* Authentication configurations
* User database
* Wallet infrastructure
* OAuth provider settings
Using separate apps is recommended for different environments (development, staging, production) or distinct projects, ensuring clean separation of data and configurations.
## Key Dashboard Sections
**API Keys** — Generate public and private keys for authenticating API requests and SDK initialization.
**Redirect URLs** — Configure verified redirect destinations for authentication flows.
**OAuth** — Set up third-party authentication providers like Google, Apple, Microsoft, Discord, and GitHub.
**Email Templates** — Customize email communications for login, signup, and invitation workflows.
**SDK Settings** — Configure SDK behavior and appearance for your client-side integrations.
**App Settings** — Manage environment settings, session policies, and access controls.
**Users** — View and manage your application's user accounts and their associated authentication methods.
The Dashboard provides everything you need to configure MoonKey for your specific use case while maintaining control over security and user experience.
# Team member roles
Source: https://docs.streambird.io/get-started/dashboard/team-member-roles
MoonKey provides role-based access control to help you manage your team securely. By assigning appropriate roles to team members, you can ensure that each person has the right level of access for their responsibilities while maintaining security over sensitive operations.
## Available roles
MoonKey supports three distinct roles, each designed for different team responsibilities:
| Role | Access Type | Best For |
| ------------- | -------------- | -------------------------------------------------------------------------- |
| **Admin** | Full access | Team owners, technical leads, and senior engineers who manage the account |
| **Developer** | Build & deploy | Engineers who integrate MoonKey and configure authentication settings |
| **Viewer** | Read-only | Product managers, support staff, and stakeholders who need visibility only |
## Role permissions
### Admin
Admins have complete control over your MoonKey account and all associated applications.
**Full dashboard access:**
* Create, configure, and delete applications
* Manage all authentication settings and configurations
* View user data and analytics
* Configure API keys and webhooks
**Team management:**
* Invite new team members
* Change team member roles
* Remove team members from the account
**Account administration:**
* Manage billing and subscription settings
* Update payment methods
* Access account-level settings
**Security operations:**
* Generate and revoke API secrets
* Rotate webhook signing keys
* Delete applications and sensitive data
* Configure security policies
### Developer
Developers can build and configure applications but cannot manage team settings or billing.
**Application management:**
* Create new applications
* Configure authentication methods (OAuth, email, wallets)
* Set up redirect URLs
* Customize email templates
* Configure SDK settings
* View and manage users
* Access API keys (view only for secrets)
**What Developers cannot do:**
* Invite or manage team members
* Change billing or payment information
* Delete applications
* Rotate or revoke API secrets
* Access account-level administrative settings
The Developer role is ideal for engineers who need to integrate MoonKey into your applications without requiring access to sensitive account management functions.
### Viewer
Viewers have read-only access to monitor applications and view data without making changes.
**Read-only permissions:**
* View all application configurations
* See authentication settings
* Access user data and analytics
* View API documentation and keys (secrets are hidden)
**What Viewers cannot do:**
* Create, edit, or delete any configurations
* Modify authentication settings
* Invite or manage team members
* Change billing information
* Access API secrets
Viewer access is perfect for stakeholders, customer support teams, or external consultants who need visibility into your MoonKey setup without editing capabilities.
## Managing your team
Only Admin users can manage team membership and roles.
### Adding team members
To invite someone to your MoonKey account:
1. Navigate to the **Team** section in your dashboard
2. Click **Invite Team Member**
3. Enter their email address
4. Select the appropriate role (Admin, Developer, or Viewer)
5. Click **Send Invite**
The invited person will receive an email with instructions to accept the invitation and access your MoonKey account.
### Updating roles
You can change a team member's role at any time:
1. Go to the **Team** section
2. Find the team member whose role you want to change
3. Click on their current role
4. Select the new role from the dropdown
5. Confirm the change
Role changes take effect immediately. Be cautious when upgrading someone to Admin or downgrading from Admin, as this affects their access to sensitive operations.
### Removing team members
To remove someone from your account:
1. Navigate to the **Team** section
2. Find the team member you want to remove
3. Click the **Remove** button next to their name
4. Confirm the removal
Once removed, the team member immediately loses all access to your MoonKey dashboard and applications.
## Security best practices
### Follow the principle of least privilege
Grant team members the minimum access level needed for their work. Start with Viewer or Developer access and only promote to Admin when necessary.
**Common role assignments:**
* **Frontend engineers**: Developer role to configure SDK and authentication
* **Backend engineers**: Developer role to manage API integrations
* **Engineering managers**: Admin role for full account control
* **Product managers**: Viewer role to monitor usage and configurations
* **Customer support**: Viewer role to check user data when troubleshooting
### Regular access audits
Periodically review your team members and their assigned roles:
* Remove access for team members who have left your organization
* Verify that current roles still match responsibilities
* Downgrade roles when team members change positions
* Check for unused or inactive accounts
### Protect Admin access
* Limit the number of Admin users to 2-3 trusted individuals
* Use Admin accounts only for administrative tasks
* Consider creating separate Developer accounts for day-to-day work
* Enable two-factor authentication for all Admin users (if available)
### Separation of environments
For maximum security, consider creating separate MoonKey apps for development, staging, and production environments, with different team access:
* **Development apps**: Grant Developer access liberally for experimentation
* **Staging apps**: Limit Developer access to engineers who deploy to staging
* **Production apps**: Restrict Admin access to senior engineers and leads only
When onboarding new team members, start them with Viewer access for a few days. This allows them to familiarize themselves with your MoonKey setup before receiving editing permissions.
## Frequently asked questions
**Can I have multiple Admins?**
Yes, you can have as many Admin users as needed. However, we recommend limiting Admin access to key personnel who require full account control.
**What happens if all Admins leave the organization?**
Contact MoonKey support immediately if you lose access to all Admin accounts. We can help you regain administrative access after verifying your identity and ownership.
**Can I create custom roles with specific permissions?**
Currently, MoonKey supports the three standard roles (Admin, Developer, Viewer). If you need custom permission configurations, please contact our team to discuss your requirements.
**Do team members need separate MoonKey accounts?**
No, team members are invited to your organization's MoonKey account. They'll use their own email and credentials to log in, but they'll access your shared applications and configurations.
**Are there limits on team size?**
No, you can have as many team members as required.
# Domain Whitelisting
Source: https://docs.streambird.io/get-started/domain-whitelisting
Configure and manage whitelisted domains for secure authentication flows in MoonKey
## What is domain whitelisting?
When your users authenticate via OAuth providers (like Google, Apple, GitHub) or receive magic links via email, MoonKey needs to ensure they're redirected to legitimate domains you control.
By whitelisting domains in the [MoonKey Dashboard](https://dashboard.moonkey.fun), you specify which domains are authorized for authentication flows. Once a domain is whitelisted, MoonKey automatically handles all authentication redirects to that domain—no need to configure specific URLs or paths.
## How it works
Simply whitelist a domain, and MoonKey takes care of the rest:
```
http://localhost:3000
```
Once whitelisted, all authentication flows (OAuth, magic links, etc.) will be valid for any path on that domain:
* ✅ `https://some-domain.com/login`
* ✅ `https://some-domain.com/auth/callback`
* ✅ `https://some-domain.com/welcome`
* ✅ `https://some-domain.com/some/path`
MoonKey validates the domain only—all paths under that domain are automatically allowed.
### Subdomains
Each subdomain must be whitelisted separately. For example, if you whitelist `https://some-domain.com`, the subdomain `https://welcome.some-domain.com` will not be automatically included.
To support multiple subdomains, add each one individually:
* `https://some-domain.com`
* `https://welcome.some-domain.com`
* `https://app.some-domain.com`
Subdomains are not automatically included. Make sure to whitelist each subdomain your application uses.
## Where domain whitelisting is used
Domain whitelisting secures the following authentication methods:
* **OAuth authentication** - Google, Apple, GitHub, Discord, Microsoft
* **Magic links** - Email-based passwordless authentication
* **Email OTP** - One-time passcode authentication flows
## Configuring whitelisted domains
To whitelist domains for your app:
1. Log in to the [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Select your app from the dropdown
3. Navigate to **Domain Whitelisting** in the sidebar
4. Click **Add Domain**
5. Enter your domain (e.g., `https://app.some-domain.com` or `http://localhost:3000`)
6. Click **Save**
Add separate domains for development, staging, and production environments to test authentication flows across your deployment pipeline. Remember to whitelist each subdomain separately.
## Best practices
### Security
* **Use HTTPS in production** - Never use HTTP domains in production environments
* **Whitelist only what you need** - Only add domains and subdomains you actively use for authentication
* **Include all subdomains** - Remember to whitelist each subdomain separately
* **Regular audits** - Review and remove unused domains periodically
### Development workflow
* **Localhost for development** - Add `http://localhost:3000` (or your local port) for development
* **Environment-specific domains** - Create separate entries for dev, staging, and production
* **Test thoroughly** - Verify authentication flows work correctly before going live
### Domain formats
* **Include protocol** - Always specify `http://` or `https://`
* **Port numbers** - Include port numbers when necessary (e.g., `http://localhost:3000`)
* **No paths required** - Just specify the domain; MoonKey handles all paths automatically
## Example whitelisted domains
Here are common domain configurations:
| Use Case | Whitelisted Domain |
| ------------------------------- | ----------------------------- |
| Production app | `https://app.example.com` |
| Marketing site | `https://example.com` |
| Staging environment | `https://staging.example.com` |
| Local development | `http://localhost:3000` |
| Local development (custom port) | `http://localhost:8080` |
## Troubleshooting
### Invalid domain error
If you receive an "Invalid domain" error during authentication:
1. Verify the domain is whitelisted in the [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Check that the protocol matches (`http://` vs `https://`)
3. Ensure the port number is included if using non-standard ports
4. If using a subdomain, verify it's whitelisted separately from the main domain
### Authentication redirect not working
If users aren't being redirected after authentication:
1. Confirm the domain is properly whitelisted
2. Check that you're using the correct protocol (HTTPS in production)
3. For localhost, ensure the port number matches your development server
4. Check browser console for any redirect or CORS errors
5. Verify your OAuth callback URLs match the whitelisted domain
### Subdomain issues
If authentication isn't working on a subdomain:
1. Verify the specific subdomain is whitelisted (subdomains are not automatically included)
2. Check that the subdomain format is correct (e.g., `https://app.example.com`)
3. Ensure both the protocol and subdomain match exactly
4. Add each subdomain separately in the dashboard
# Automatic Wallet Creation
Source: https://docs.streambird.io/get-started/frontend-sdks/react/advance/automatic-wallet-creation
MoonKey can automatically create embedded wallets for your users during the authentication flow. This provides a seamless onboarding experience where users get a self-custodial wallet immediately upon logging in.
## Configuration
Configure automatic wallet creation by setting the `embeddedWallets.createOnLogin` property in your `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Configuration Options
Determines when to create a wallet for the user.
* `'always'` — Create a wallet for every user on login
* `'none'` — Disable automatic wallet creation (create manually when needed)
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Configuration Options
Determines when to create a wallet for the user.
* `'always'` — Create a wallet for every user on login
* `'none'` — Disable automatic wallet creation (create manually when needed)
## When to Use Each Option
### 'always'
Create a wallet for every user, regardless of whether they already have one.
**Use cases:**
* Web3-first applications where every user needs a wallet
* Applications that require blockchain transactions for core functionality
* NFT platforms, DeFi apps, or blockchain games
**Behavior:**
* New users get a wallet immediately upon first login
* Returning users get a wallet on their next login if they don't have one
* Simple and straightforward approach for Web3 applications
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### 'none'
Disable automatic wallet creation entirely.
**Use cases:**
* Applications where wallet creation should be user-initiated
* When you want full control over when wallets are created
* Authentication-only implementations
**Behavior:**
* No wallets are created automatically
* You must manually create wallets using the `useCreateWallet` hook
* Gives you complete control over the wallet creation flow
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
When using `'none'`, create wallets manually:
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreateWallet = async () => {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
};
return ;
}
```
## How It Works
When automatic wallet creation is enabled, MoonKey checks the following during the login flow:
1. **User authenticates** — User completes authentication (email OTP, OAuth, wallet signature, etc.)
2. **Check configuration** — MoonKey checks the `createOnLogin` setting
3. **Evaluate conditions** — Based on the setting and user's current wallet status
4. **Create wallet** — If conditions are met, MoonKey generates a self-custodial embedded wallet
5. **User session** — User's session now includes wallet information
This happens seamlessly in the background without requiring additional user interaction.
## Accessing Created Wallets
Once a wallet is created (automatically or manually), you can access it through the `useMoonKey` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function WalletInfo() {
const { user, isAuthenticated } = useMoonKey();
if (!isAuthenticated) {
return
Please log in
;
}
if (!user?.wallet) {
return
No wallet found
;
}
return (
Your Wallet
Address: {user.wallet.address}
);
}
```
## Best Practices
### Choose the Right Strategy
| Application Type | Recommended Setting | Reason |
| ---------------- | ---------------------- | ----------------------------------------- |
| DeFi Platform | `'always'` | All users need wallets for trading |
| NFT Marketplace | `'always'` | Core functionality requires wallets |
| Gaming | `'always'` | In-game assets need wallet storage |
| Social + Crypto | `'always'` | Ensure all users can access Web3 features |
| Hybrid App | `'always'` or `'none'` | Choose based on how central wallets are |
| Auth Only | `'none'` | Manual wallet creation on demand |
### Consider User Experience
**For Web3-native applications:**
* Use `'always'` for immediate wallet access
* Users expect wallets to be available immediately
* Reduces friction in the onboarding flow
**For authentication-focused applications:**
* Use `'none'` and create wallets when users need them
* Allow users to understand your app before getting a wallet
* Provide education about what a wallet is and why they need it when you create it manually
## Environment-Specific Configuration
You can configure different automatic wallet creation settings for different environments:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
const isDevelopment = process.env.NODE_ENV === 'development';
{children}
```
## Combining with Manual Creation
You can combine automatic wallet creation with manual creation for more complex flows:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function WalletSetup() {
const { user } = useMoonKey();
const { createWallet } = useCreateWallet();
// Automatic creation is disabled (createOnLogin: 'none')
// Show onboarding and let user create wallet when ready
const handleGetStarted = async () => {
// Show onboarding modal/flow first
await showOnboarding();
// Then create wallet
const wallet = await createWallet();
console.log('Wallet ready:', wallet.address);
};
if (user?.wallet) {
return
Wallet: {user.wallet.address}
;
}
return (
Welcome to Our App!
You'll need a wallet to get started.
);
}
```
## Troubleshooting
### Wallet not created after login
**Possible causes:**
* `createOnLogin` is set to `'none'`
* Authentication didn't complete successfully
* Configuration error in the provider
**Solution:**
Check your configuration and user's wallet status:
```tsx theme={null}
const { user } = useMoonKey();
console.log('User has wallet:', !!user?.wallet);
```
### Multiple wallets created
**Possible causes:**
* Using `'always'` creates a wallet on every login
* User logged in multiple times during development/testing
**Solution:**
This is expected behavior with `'always'`. Each authentication creates a new wallet. If you want to prevent multiple wallets, use `'none'` and create wallets manually with checks:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function EnsureSingleWallet() {
const { user } = useMoonKey();
const { createWallet } = useCreateWallet();
const ensureWallet = async () => {
if (!user?.wallet) {
await createWallet();
}
};
return ;
}
```
### Need to create wallet immediately
**Possible causes:**
* Automatic creation is disabled
* Need wallet before user completes authentication
**Solution:**
Enable automatic creation with `'always'`:
```tsx theme={null}
config={{
embeddedWallets: {
createOnLogin: 'always'
}
}}
```
## Related Documentation
* [Manual Wallet Creation](/wallet-as-a-service/user-wallets/create-wallet) — Create wallets on-demand
* [Wallet Management](/wallet-as-a-service/overview) — Managing user wallets
* [Setup Guide](/get-started/frontend-sdks/react/setup) — Initial MoonKey configuration
* [Examples](/get-started/frontend-sdks/react/examples) — Complete code examples
# Configure Appearance
Source: https://docs.streambird.io/get-started/frontend-sdks/react/advance/configure-appearance
MoonKey's authentication UI is highly customizable to match your application's branding and design system. From logos and themes to custom colors and layout options, you can create a seamless experience that feels native to your app.
Want to see all customization options in action? Check out our [interactive demo](https://demo.moonkey.fun/) to explore different configurations and visual styles.
## Quick Start
Here's a basic example of customizing the MoonKey authentication UI:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
## Dashboard Configuration
Some appearance settings are configured in the MoonKey Dashboard and apply across your entire application.
### App Name
Configure your application name in the **Dashboard**:
1. Navigate to your MoonKey Dashboard
2. Select your app from the dropdown
3. Go to the **Customization** page
4. Enter your app name in the **App Name** field
Your app name appears throughout MoonKey's UI, including authentication modals and notification emails.
### Logo
Set your app logo in the **Dashboard**:
1. Go to the **Customization** page
2. Enter a URL for your logo in the **Logo** field
3. Use an image with a 2:1 aspect ratio (recommended: 180px × 90px)
4. PNG or JPG formats work best (SVGs have limited email client support)
This logo appears in authentication screens and emails. You can override it programmatically for specific screens.
## Provider Configuration
Fine-tune the authentication UI with the `appearance` config in your `MoonKeyProvider`.
### Logo
Set or override the logo for your authentication UI:
```tsx theme={null}
{children}
```
To hide the logo entirely, pass an empty string:
```tsx theme={null}
appearance: {
logo: ''
}
```
### Login Header Title
Customize the main heading text shown on the login screen:
```tsx theme={null}
{children}
```
**Tips:**
* Keep it short and welcoming (recommended: 35 characters or less)
* Use your brand's voice and tone
* Make it action-oriented or inviting
### Login Header Description
Add descriptive text below the header to guide users:
```tsx theme={null}
{children}
```
**Tips:**
* Keep it under 100 characters for optimal display
* Clearly explain what users will do or get
* Avoid jargon for better user comprehension
### Hide Close Button
Control whether users can close the authentication modal:
```tsx theme={null}
{children}
```
**Use cases:**
* `true` — Force users to complete authentication (use cautiously)
* `false` — Allow users to dismiss the modal (default)
Setting `hideClose: true` creates a more forceful user experience. Only use this when authentication is absolutely required to proceed.
## Complete Configuration Example
Here's a fully customized appearance configuration using all available options:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
## Advanced CSS Customization
For even deeper customization, MoonKey supports CSS variable overrides. Add these to your global CSS to customize specific UI elements:
```css theme={null}
body {
/* Border radius */
--moonkey-border-radius-sm: 4px;
--moonkey-border-radius-md: 8px;
--moonkey-border-radius-lg: 12px;
--moonkey-border-radius-full: 9999px;
/* Background colors */
--moonkey-color-background: #ffffff;
--moonkey-color-background-2: #f9fafb;
--moonkey-color-background-3: #f3f4f6;
/* Foreground colors */
--moonkey-color-foreground: #111827;
--moonkey-color-foreground-2: #6b7280;
--moonkey-color-foreground-3: #9ca3af;
--moonkey-color-foreground-4: #d1d5db;
--moonkey-color-foreground-accent: #4f46e5;
/* Accent colors */
--moonkey-color-accent: #6366f1;
--moonkey-color-accent-light: #818cf8;
--moonkey-color-accent-lightest: #c7d2fe;
--moonkey-color-accent-dark: #4f46e5;
--moonkey-color-accent-darkest: #4338ca;
/* Status colors */
--moonkey-color-success: #10b981;
--moonkey-color-error: #ef4444;
--moonkey-color-error-light: #fecaca;
}
```
Use your browser's developer tools to inspect MoonKey's UI and experiment with different CSS variable values in real-time.
## Common Use Cases
### Web3 Gaming Application
```tsx theme={null}
appearance: {
logo: 'https://game.com/logo.png',
loginHeaderTitle: 'Enter the Game',
loginHeaderDescription: 'Connect your wallet to start playing'
}
```
### DeFi Platform
```tsx theme={null}
appearance: {
logo: 'https://defi.com/logo.png',
loginHeaderTitle: 'Connect Your Wallet',
loginHeaderDescription: 'Start trading and managing your assets',
hideClose: true // Require authentication to proceed
}
```
### NFT Marketplace
```tsx theme={null}
appearance: {
logo: 'https://marketplace.com/logo.png',
loginHeaderTitle: 'Join the Marketplace',
loginHeaderDescription: 'Buy, sell, and trade exclusive NFTs'
}
```
### Social Platform with Web3 Features
```tsx theme={null}
appearance: {
logo: 'https://social.com/logo.png',
loginHeaderTitle: 'Welcome!',
loginHeaderDescription: 'Connect with friends and creators'
}
```
## Best Practices
### Branding Consistency
* Use the same logo across all authentication touchpoints
* Keep logo images consistent with your brand guidelines
* Align header titles and descriptions with your brand voice
* Test appearance changes across different screen sizes
### Copywriting
* Keep `loginHeaderTitle` short and welcoming (under 35 characters)
* Make `loginHeaderDescription` clear and action-oriented (under 100 characters)
* Avoid technical jargon that might confuse users
* Use consistent terminology across your app
### Accessibility
* Ensure logo images have appropriate alt text
* Test text readability on various backgrounds
* Use clear, simple language in headers and descriptions
* Test with screen readers and keyboard navigation
### User Experience
* Only use `hideClose: true` when authentication is absolutely required
* Test your appearance on multiple devices and screen sizes
* Ensure logos load quickly with optimized images
* Provide clear value propositions in descriptions
### Performance
* Host logo images on a CDN for fast loading
* Use optimized image formats (WebP when possible)
* Keep logo file sizes under 100KB
* Use SVGs cautiously (better for web, problematic in emails)
## Exploring More Options
MoonKey offers extensive customization beyond what's covered in this guide:
* **Custom modal layouts** through CSS customization
* **Branded email templates** configured in the dashboard
* **Multiple authentication methods** with different flows
* **Advanced styling** with CSS variables
Explore all customization options with our interactive demo. Try different configurations and see changes in real-time.
## Troubleshooting
### Logo not displaying
**Solutions:**
* Verify the logo URL is publicly accessible
* Check image format (PNG/JPG recommended)
* Ensure image loads in a new browser tab
* Try with HTTPS URLs only
* Check browser console for loading errors
### Text not updating
**Solutions:**
* Verify you're passing `loginHeaderTitle` and `loginHeaderDescription` correctly
* Clear browser cache and reload
* Check for typos in property names
* Ensure the provider is properly configured
### Close button not hiding
**Solutions:**
* Verify `hideClose` is set to `true` (boolean, not string)
* Check that the configuration is in the correct location
* Clear browser cache and reload
* Test in an incognito/private window
### CSS variables not applying
**Solutions:**
* Ensure CSS is loaded before MoonKey initializes
* Check CSS selector specificity
* Use browser dev tools to verify variable values
* Try applying `!important` for testing (not recommended for production)
## Related Documentation
* [Setup Guide](/get-started/frontend-sdks/react/setup) — Initial MoonKey configuration
* [Examples](/get-started/frontend-sdks/react/examples) — Complete code examples
* [Features](/get-started/frontend-sdks/react/features) — All SDK capabilities
* [Interactive Demo](https://demo.moonkey.fun/) — Try customization options live
# Configure EVM Networks
Source: https://docs.streambird.io/get-started/frontend-sdks/react/advance/configure-evm-networks
MoonKey is compatible with any EVM-compatible blockchain, making it easy to deploy your application on Ethereum, Layer 2s, sidechains, and custom networks.
Whether you're building on Ethereum mainnet, Base, Polygon, Arbitrum, Optimism, or a custom EVM chain, MoonKey provides flexible network configuration to match your needs.
## Overview
MoonKey exposes two key parameters for network configuration: a **default chain** and a list of **supported chains**.
If you don't configure these parameters, MoonKey will use its [default configuration](/get-started/frontend-sdks/react/advanced/configure-evm-networks#default-configuration) which supports popular networks out of the box.
### Default Chain
The **default chain** is the primary network that your application's wallets will use.
**For embedded wallets:**
* When users authenticate or create a wallet, MoonKey initializes it on the default chain
* The embedded wallet will use the default chain for all operations unless explicitly switched to another supported chain
**For external wallets:**
* When users connect, MoonKey will prompt them to switch to the default chain if their wallet supports it
* Users can decline the prompt and remain on their current network
* Users can manually switch their wallet's network at any time (MoonKey cannot prevent this)
Some wallets have limited network support. Rainbow Wallet's mobile app doesn't support testnets, and Trust Wallet SWIFT only supports specific networks (BSC, Polygon, Avalanche C-Chain, Arbitrum, OP Mainnet, Base, OpBNB).
### Supported Chains
The **supported chains** list defines which networks are permitted in your application. This acts as a guardrail to prevent accidental transactions on unintended networks.
**For embedded wallets:**
* Attempting to send transactions on unsupported chains will throw an error
* Attempting to switch to an unsupported chain will throw an error
**For external wallets:**
* Programmatic network switches to unsupported chains will throw an error
* Users can still manually switch their wallet to any network (this cannot be prevented)
**Configuration behavior:**
* If you set `supportedChains` but not `defaultChain`, embedded wallets default to the first entry in the supported chains list
* External wallets won't be prompted to switch networks on connection
* The default chain must be included in the supported chains list
## Configuration
MoonKey embedded wallets support any EVM-compatible chain. Here's how to configure them.
### Using viem-Supported Networks
If your network is supported by [viem/chains](https://viem.sh/docs/chains/introduction#chains), follow these instructions. Check the [full list of supported networks](https://github.com/wevm/viem/blob/main/src/chains/index.ts).
**Step 1: Install viem**
First, install the `viem` package which contains JSON representations of popular EVM networks:
```bash theme={null}
npm install viem
```
**Step 2: Import your desired chains**
Import the chains you want to use from `viem/chains`:
```tsx theme={null}
import { base, polygon, arbitrum, optimism } from 'viem/chains';
```
**Step 3: Configure the MoonKeyProvider**
Add the chains to your `MoonKeyProvider` configuration:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
import { base, polygon, arbitrum, optimism } from 'viem/chains';
export default function Providers({ children }) {
return (
{children}
);
}
```
**Important notes:**
* `supportedChains` cannot be an empty array
* `defaultChain` must be included in `supportedChains`
* The provider will throw an error if these rules are violated
That's it! Your app now supports multiple EVM networks. 🎉
### Using Custom Networks
If your network isn't supported by viem/chains, you can still use it with MoonKey by defining it manually.
**Step 1: Define your custom chain**
Use viem's `defineChain` method to create a chain configuration:
```tsx theme={null}
import { defineChain } from 'viem';
export const myCustomChain = defineChain({
id: 123456789, // Your chain's ID
name: 'My Custom Chain',
network: 'my-custom-chain',
nativeCurrency: {
decimals: 18,
name: 'Custom Token',
symbol: 'CUSTOM'
},
rpcUrls: {
default: {
http: ['https://rpc.my-custom-chain.network'],
webSocket: ['wss://ws.my-custom-chain.network']
}
},
blockExplorers: {
default: {
name: 'CustomScan',
url: 'https://explorer.my-custom-chain.network'
}
}
});
```
**Required fields:**
* `id` — Chain ID
* `name` — Human-readable name
* `network` — Network identifier
* `nativeCurrency` — Native token details (name, symbol, decimals)
* `rpcUrls` — RPC endpoints (HTTP and/or WebSocket)
* `blockExplorers` — Block explorer URL
**Step 2: Use your custom chain**
Pass your custom chain to the `MoonKeyProvider`:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
import { mainnet } from 'viem/chains';
import { myCustomChain } from './chains';
export default function Providers({ children }) {
return (
{children}
);
}
```
## Custom RPC Providers
By default, MoonKey uses its own RPC providers for transactions. These are subject to rate limits suitable for development and moderate production usage.
**For production applications with significant traffic, we strongly recommend using your own RPC providers** from services like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), [QuickNode](https://www.quicknode.com/), or [Blast](https://blastapi.io/).
Benefits of custom RPC providers:
* Control over rate limits and throughput
* Better monitoring and analytics
* Reduced latency with dedicated infrastructure
* SLA guarantees
### Overriding RPC URLs
To use a custom RPC provider with MoonKey:
**Step 1: Import the helper function**
```tsx theme={null}
import { addRpcUrlOverrideToChain } from '@moon-key/chains';
import { mainnet } from 'viem/chains';
```
**Step 2: Override the RPC URL**
```tsx theme={null}
const mainnetWithCustomRpc = addRpcUrlOverrideToChain(
mainnet,
'https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
);
```
**Step 3: Use the modified chain**
```tsx theme={null}
{children}
```
### Multiple chains with custom RPCs
Override multiple chains at once:
```tsx theme={null}
import { addRpcUrlOverrideToChain } from '@moon-key/chains';
import { mainnet, polygon, arbitrum } from 'viem/chains';
const mainnetOverride = addRpcUrlOverrideToChain(
mainnet,
'https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
);
const polygonOverride = addRpcUrlOverrideToChain(
polygon,
'https://polygon-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
);
const arbitrumOverride = addRpcUrlOverrideToChain(
arbitrum,
'https://arb-mainnet.g.alchemy.com/v2/YOUR-API-KEY'
);
{children}
```
## Default Configuration
If you don't specify `defaultChain` or `supportedChains`, MoonKey defaults to supporting popular EVM networks:
Need a network not listed below? Configure it using the instructions above.
### Default Supported Networks
| Network | Chain ID | Testnet |
| --------------------- | --------- | ------- |
| **Ethereum Mainnet** | 1 | |
| Ethereum Sepolia | 11155111 | ✅ |
| **Base** | 8453 | |
| Base Sepolia | 84532 | ✅ |
| **Polygon** | 137 | |
| Polygon Amoy | 80002 | ✅ |
| **Arbitrum One** | 42161 | |
| Arbitrum Sepolia | 421614 | ✅ |
| **Optimism** | 10 | |
| Optimism Sepolia | 11155420 | ✅ |
| **Avalanche C-Chain** | 43114 | |
| Avalanche Fuji | 43113 | ✅ |
| **BNB Smart Chain** | 56 | |
| BNB Testnet | 97 | ✅ |
| **Celo** | 42220 | |
| Celo Alfajores | 44787 | ✅ |
| **Linea** | 59144 | |
| Linea Testnet | 59140 | ✅ |
| **Zora** | 7777777 | |
| Zora Sepolia | 999999999 | ✅ |
**Default behavior:**
* Embedded wallets initialize on Ethereum mainnet (or the user's last-used network)
* External wallets aren't prompted to switch networks
* All networks in the table above are available for use
## Switching Networks
Once configured, you can programmatically switch networks using MoonKey hooks.
### Switch embedded wallet network
```tsx theme={null}
import { useSwitchChain } from '@moon-key/react-auth/ethereum';
import { polygon } from 'viem/chains';
function SwitchNetworkButton() {
const { switchChain } = useSwitchChain();
const handleSwitch = async () => {
await switchChain({ chainId: polygon.id });
};
return ;
}
```
### Get current network
```tsx theme={null}
import { useChainId } from '@moon-key/react-auth/ethereum';
function CurrentNetwork() {
const chainId = useChainId();
return
Current Chain ID: {chainId}
;
}
```
## Common Configurations
### Mainnet-only application
```tsx theme={null}
import { mainnet } from 'viem/chains';
{children}
```
### Layer 2 focused
```tsx theme={null}
import { base, polygon, arbitrum, optimism } from 'viem/chains';
{children}
```
### Development with testnets
```tsx theme={null}
import { sepolia, baseSepolia, polygonAmoy } from 'viem/chains';
{children}
```
### Multi-chain with custom RPC
```tsx theme={null}
import { addRpcUrlOverrideToChain } from '@moon-key/chains';
import { mainnet, polygon } from 'viem/chains';
const mainnetWithRpc = addRpcUrlOverrideToChain(
mainnet,
process.env.NEXT_PUBLIC_MAINNET_RPC
);
const polygonWithRpc = addRpcUrlOverrideToChain(
polygon,
process.env.NEXT_PUBLIC_POLYGON_RPC
);
{children}
```
## Best Practices
### Choose appropriate networks
* **Mainnet for production** — Use mainnet and established L2s for production deployments
* **Testnets for development** — Use Sepolia, Amoy, etc. during development to avoid real costs
* **Limit network count** — Only support networks your application actually uses
### Use environment variables
Store RPC URLs and chain configurations in environment variables:
```tsx theme={null}
// .env.local
NEXT_PUBLIC_MAINNET_RPC=https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY
NEXT_PUBLIC_POLYGON_RPC=https://polygon-mainnet.g.alchemy.com/v2/YOUR-KEY
```
```tsx theme={null}
const mainnetWithRpc = addRpcUrlOverrideToChain(
mainnet,
process.env.NEXT_PUBLIC_MAINNET_RPC!
);
```
### Monitor RPC usage
When using custom RPC providers:
* Set up monitoring and alerts for rate limits
* Track RPC call volumes and errors
* Have fallback providers configured
* Monitor response times and uptime
### Test network switching
Always test network switching functionality:
* Verify embedded wallets switch correctly
* Test external wallet prompts
* Handle user rejection of network switches
* Display clear network indicators in your UI
## Troubleshooting
### Chain not appearing in wallet
**Problem:** Users don't see your configured chain in their wallet.
**Solutions:**
* Verify the chain is in `supportedChains`
* Check that chain ID and RPC URL are correct
* For custom chains, ensure all required fields are defined
* Some wallets require manual network addition
### RPC rate limit errors
**Problem:** Transactions fail with rate limit errors.
**Solutions:**
* Use custom RPC providers (Alchemy, Infura, QuickNode)
* Implement RPC URL overrides
* Add fallback RPC endpoints
* Monitor your RPC usage and upgrade plans as needed
### Network switch fails
**Problem:** `switchChain` throws an error or doesn't work.
**Solutions:**
* Ensure target chain is in `supportedChains`
* For external wallets, check wallet supports the network
* Handle errors gracefully when users decline switches
* Verify RPC endpoints are accessible
### Configuration errors on startup
**Problem:** `MoonKeyProvider` throws an error during initialization.
**Solutions:**
* Ensure `defaultChain` is included in `supportedChains`
* Don't pass empty array to `supportedChains`
* Verify chain objects are properly formatted
* Check for typos in chain imports
## Related Resources
* [Viem Chains Documentation](https://viem.sh/docs/chains/introduction)
* [EVM Chains List](https://chainlist.org/)
* [Configure Solana Networks](/get-started/frontend-sdks/react/advanced/configure-solana-networks)
* [Wallet Management](/wallet-as-a-service/overview)
# Configure Solana Networks
Source: https://docs.streambird.io/get-started/frontend-sdks/react/advance/configure-solana-networks
MoonKey supports all [Solana clusters](https://solana.com/docs/core/clusters) including Mainnet Beta, Devnet, and Testnet.
This guide shows you how to configure Solana RPC endpoints and work with different Solana environments in your application.
## Solana Clusters
Solana provides three primary clusters for different purposes:
| Cluster | Purpose | Use Case |
| ---------------- | ------------------- | ------------------------------------------ |
| **Mainnet Beta** | Production network | Live applications with real SOL and tokens |
| **Devnet** | Development network | Testing and development with free test SOL |
| **Testnet** | Performance testing | Stress testing before mainnet deployment |
## Configuring RPC Endpoints
To configure custom RPC endpoints for Solana embedded wallets, set them in the `MoonKeyProvider` configuration:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function Providers({ children }) {
return (
{children}
);
}
```
Custom RPC configuration is only required for embedded wallet UIs. If you're using external Solana wallets (Phantom, Solflare, etc.) without embedded wallet functionality, you don't need to configure RPCs.
## Default Configuration
If you don't configure custom RPC endpoints, MoonKey uses default Solana cluster endpoints:
* **Mainnet Beta**: `https://api.mainnet-beta.solana.com`
* **Devnet**: `https://api.devnet.solana.com`
* **Testnet**: `https://api.testnet.solana.com`
These default endpoints work for development and moderate usage, but production applications should use dedicated RPC providers.
## Custom RPC Providers
For production applications, we strongly recommend using dedicated RPC providers to ensure reliability and performance.
### Popular Solana RPC Providers
| Provider | Features | Notes |
| --------------------------------------- | ---------------------------------------- | -------------------------- |
| [Helius](https://helius.dev/) | High-performance RPCs, webhooks, DAS API | Recommended for production |
| [QuickNode](https://www.quicknode.com/) | Global infrastructure, analytics | Enterprise features |
| [Alchemy](https://www.alchemy.com/) | Enhanced APIs, developer tools | Full-featured platform |
| [Triton](https://triton.one/) | Geyser plugin support | Advanced use cases |
### Configuring a Custom Provider
Example using Helius:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
const HELIUS_API_KEY = process.env.NEXT_PUBLIC_HELIUS_API_KEY;
export default function Providers({ children }) {
return (
{children}
);
}
```
## Custom SVM Networks
You can interact with custom SVM-compatible networks by creating a `Connection` instance with the custom RPC URL:
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/solana';
import { Connection, Transaction } from '@solana/web3.js';
export default function CustomNetworkTransaction() {
const { sendTransaction } = useSendTransaction();
const handleTransaction = async () => {
// Initialize connection with custom RPC
const connection = new Connection('https://custom-rpc-url.com');
const transaction = new Transaction().add(
// Add your instructions here
);
const signature = await sendTransaction({
transaction,
connection
});
console.log('Transaction sent:', signature);
};
return ;
}
```
## Environment-Based Configuration
Configure different RPC endpoints for different environments:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
const isDevelopment = process.env.NODE_ENV === 'development';
const MAINNET_RPC = process.env.NEXT_PUBLIC_SOLANA_MAINNET_RPC;
const DEVNET_RPC = process.env.NEXT_PUBLIC_SOLANA_DEVNET_RPC;
export default function Providers({ children }) {
return (
{children}
);
}
```
**Environment variables (.env.local):**
```bash theme={null}
NEXT_PUBLIC_SOLANA_MAINNET_RPC=https://mainnet.helius-rpc.com/?api-key=YOUR-KEY
NEXT_PUBLIC_SOLANA_DEVNET_RPC=https://devnet.helius-rpc.com/?api-key=YOUR-KEY
```
## Working with Different Clusters
### Switching Between Clusters
You can work with different clusters by creating separate `Connection` instances:
```tsx theme={null}
import { Connection } from '@solana/web3.js';
const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com');
const devnetConnection = new Connection('https://api.devnet.solana.com');
const testnetConnection = new Connection('https://api.testnet.solana.com');
// Use the appropriate connection for your needs
const balance = await mainnetConnection.getBalance(publicKey);
```
### Detecting Current Cluster
Determine which cluster a connection is using:
```tsx theme={null}
import { Connection, clusterApiUrl } from '@solana/web3.js';
function getClusterFromUrl(url: string): string {
if (url.includes('mainnet')) return 'mainnet-beta';
if (url.includes('devnet')) return 'devnet';
if (url.includes('testnet')) return 'testnet';
return 'custom';
}
const connection = new Connection('https://api.devnet.solana.com');
const cluster = getClusterFromUrl(connection.rpcEndpoint);
console.log('Current cluster:', cluster); // 'devnet'
```
## Common Configurations
### Development Setup (Devnet)
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Production Setup (Mainnet with Helius)
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
const HELIUS_KEY = process.env.NEXT_PUBLIC_HELIUS_API_KEY;
{children}
```
### Multi-Cluster Support
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
## Best Practices
### Use Dedicated RPCs for Production
* Default public RPCs have rate limits unsuitable for production
* Dedicated providers offer better performance and reliability
* Set up monitoring and alerts for RPC health
* Have fallback RPCs configured
### Secure Your API Keys
Store RPC API keys securely:
```bash theme={null}
# .env.local (never commit to git)
NEXT_PUBLIC_HELIUS_API_KEY=your-api-key-here
NEXT_PUBLIC_QUICKNODE_ENDPOINT=your-endpoint-here
```
```tsx theme={null}
// Use environment variables
const apiKey = process.env.NEXT_PUBLIC_HELIUS_API_KEY;
```
### Choose the Right Cluster
| Use Case | Cluster | Why |
| ------------ | ------------ | ---------------------------------- |
| Production | Mainnet Beta | Real transactions with real SOL |
| Development | Devnet | Free test SOL, stable for testing |
| Load Testing | Testnet | Performance testing before mainnet |
| CI/CD | Devnet | Automated testing with test tokens |
### Monitor RPC Performance
Track key metrics:
* Request latency
* Error rates
* Rate limit usage
* Websocket connection stability
### Use Websockets for Real-Time Data
For real-time updates (account changes, transaction confirmations), use websocket connections:
```tsx theme={null}
import { Connection, PublicKey } from '@solana/web3.js';
// Create connection with websocket support
const connection = new Connection(
'https://mainnet.helius-rpc.com/?api-key=YOUR-KEY',
{
wsEndpoint: 'wss://mainnet.helius-rpc.com/?api-key=YOUR-KEY'
}
);
// Subscribe to account changes
const subscriptionId = connection.onAccountChange(
publicKey,
(accountInfo) => {
console.log('Account updated:', accountInfo);
}
);
```
## Troubleshooting
### RPC Connection Errors
**Problem:** Cannot connect to Solana RPC.
**Solutions:**
* Verify RPC URL is correct and accessible
* Check API key is valid and not expired
* Ensure rate limits haven't been exceeded
* Try fallback RPC endpoints
### Transaction Timeouts
**Problem:** Transactions time out or fail to confirm.
**Solutions:**
* Use RPC providers with better uptime (Helius, QuickNode)
* Increase transaction confirmation timeout
* Implement retry logic with exponential backoff
* Check Solana network status for congestion
### Websocket Disconnections
**Problem:** Websocket connections drop frequently.
**Solutions:**
* Implement reconnection logic
* Use dedicated RPC providers with stable websockets
* Monitor connection health and reconnect proactively
* Consider using polling for less time-sensitive data
### Rate Limit Errors
**Problem:** Hitting RPC rate limits.
**Solutions:**
* Upgrade to paid RPC provider plan
* Implement request batching
* Cache responses when possible
* Use websockets instead of polling
## Testing on Different Networks
### Getting Test SOL
For development and testing:
**Devnet:**
```bash theme={null}
solana airdrop 2 YOUR_WALLET_ADDRESS --url devnet
```
Or use the [Solana Faucet](https://faucet.solana.com/)
**Testnet:**
```bash theme={null}
solana airdrop 2 YOUR_WALLET_ADDRESS --url testnet
```
### Network-Specific Testing
Test your application on different networks:
```tsx theme={null}
const networks = ['mainnet', 'devnet', 'testnet'];
networks.forEach(network => {
test(`Transaction works on ${network}`, async () => {
const connection = new Connection(getNetworkUrl(network));
// Your test code here
});
});
```
## Related Resources
* [Solana Clusters Documentation](https://solana.com/docs/core/clusters)
* [Solana RPC API Reference](https://solana.com/docs/rpc)
* [Configure EVM Networks](/get-started/frontend-sdks/react/advanced/configure-evm-networks)
* [Solana Web3.js Documentation](https://solana-labs.github.io/solana-web3.js/)
* [Helius RPC Documentation](https://docs.helius.dev/)
# Features
Source: https://docs.streambird.io/get-started/frontend-sdks/react/features
# React SDK Features
The MoonKey React SDK provides comprehensive authentication and wallet management capabilities for both Ethereum and Solana blockchains. This page outlines all supported features.
## Authentication Methods
The React SDK supports multiple authentication methods to accommodate different user preferences and use cases.
### Email Authentication
✅ **Email OTP (One-Time Password)**
* Passwordless authentication via 6-digit codes
* 10-minute code expiration
* Automatic email verification
* Resend code functionality
### Social Authentication (OAuth)
✅ **Google OAuth**
* Sign in with Google accounts
* Requires custom OAuth credentials
* Automatic email verification
✅ **Apple OAuth**
* Sign in with Apple ID
* Requires custom OAuth credentials
* Privacy-focused authentication
Additional OAuth providers (Microsoft, Discord, GitHub, etc) are coming soon. Currently, only Google and Apple are supported.
### Web3 Wallet Authentication
✅ **Sign In With Ethereum (SIWE)**
* EIP-4361 standard authentication
* Compatible with all EVM wallets (MetaMask, Coinbase Wallet, WalletConnect, etc.)
* Cryptographic signature verification
✅ **Sign In With Solana (SIWS)**
* Solana wallet signature authentication
* Compatible with Phantom, Solflare, and other Solana wallets
* Secure message signing
## Embedded Wallet Features
MoonKey's embedded wallets provide self-custodial wallet functionality directly within your application.
### Wallet Creation
| Feature | Ethereum | Solana |
| ----------------------------- | -------- | ------ |
| **Automatic wallet creation** | ✅ | ✅ |
| **Manual wallet creation** | ✅ | ✅ |
| **Create on login** | ✅ | ✅ |
| **HD wallet support** | ✅ | ✅ |
**Automatic wallet creation options:**
* `'always'` — Create wallet for every user on login
* `'none'` — Disable automatic creation
### Transaction Signing
| Feature | Ethereum | Solana |
| ----------------------------- | -------- | ------ |
| **Sign messages** | ✅ | ✅ |
| **Sign transactions** | ✅ | ✅ |
| **Send transactions** | ✅ | ✅ |
| **Sign typed data (EIP-712)** | ✅ | — |
| **Batch transactions** | ✅ | ✅ |
### Wallet Operations
| Feature | Ethereum | Solana |
| ----------------------------- | -------- | ------ |
| **Get wallet balance** | ✅ | ✅ |
| **Get wallet address** | ✅ | ✅ |
| **Export private key** | ✅ | ✅ |
| **Multiple wallets per user** | ✅ | ✅ |
## Blockchain Support
### Ethereum & EVM Chains
The React SDK (`@moon-key/react-auth/ethereum`) supports:
* **Ethereum Mainnet** — Production Ethereum network
* **All EVM-compatible chains** — Polygon, Arbitrum, Optimism, Base, BSC, Avalanche, etc.
* **Custom EVM networks** — Configure any EVM-compatible chain
* **Testnets** — Sepolia, Goerli, Mumbai, and more
### Solana
The React SDK (`@moon-key/react-auth/solana`) supports:
* **Solana Mainnet** — Production Solana network
* **Solana Devnet** — Development and testing
* **Solana Testnet** — Testing environment
* **Custom RPC endpoints** — Configure your own Solana nodes
## Library Integrations
MoonKey integrates seamlessly with popular blockchain libraries.
### Ethereum Libraries
| Library | Support | Usage |
| ---------------- | ------- | -------------------------------------- |
| **Viem** | ✅ | Recommended for Ethereum interactions |
| **Ethers.js v6** | ✅ | Full support for contract interactions |
| **Ethers.js v5** | ✅ | Legacy support |
| **Web3.js** | ✅ | Compatible via standard providers |
| **Wagmi** | ✅ | Works with MoonKey wallets |
### Solana Libraries
| Library | Support | Usage |
| ------------------- | ------- | ------------------ |
| **@solana/web3.js** | ✅ | Primary Solana SDK |
## Framework Compatibility
The MoonKey React SDK works across modern React frameworks and build tools.
### Supported Frameworks
| Framework | Version | Support |
| ------------------------ | ---------- | -------------- |
| **Create React App** | 5.x | ✅ Full support |
| **Next.js App Router** | 13.x, 14.x | ✅ Full support |
| **Next.js Pages Router** | 12.x, 13.x | ✅ Full support |
### Build Tools
* **Webpack** — Full support with polyfill configurations
* **Turbopack** — Native support in Next.js
* **Vite** — Optimized builds
* **esbuild** — Compatible
## Session Management
✅ **Persistent sessions** — Sessions persist across browser refreshes
✅ **Session tokens** — JWT-based authentication tokens
✅ **Token refresh** — Automatic token renewal
✅ **Session verification** — Server-side session validation
✅ **Multi-device support** — Users can be logged in on multiple devices
✅ **Session revocation** — Logout and session cleanup
## User Management
✅ **User profiles** — Access user data (email, ID, wallet addresses)
✅ **Linked accounts** — Multiple authentication methods per user
✅ **Account linking** — Connect additional auth methods to existing accounts
✅ **User metadata** — Store custom user data
## Security Features
✅ **Self-custodial wallets** — Users maintain full control of their keys
✅ **Distributed key sharding** — Keys never stored in a single location
✅ **Secure execution environment** — Keys only reconstructed during signing
✅ **HTTPS-only** — All communications encrypted
✅ **JWT verification** — Cryptographic token validation
✅ **CSRF protection** — Built-in security measures
## UI Components
✅ **Pre-built authentication modal** — Ready-to-use login UI
✅ **Customizable appearance** — Theme, colors, and branding
✅ **Responsive design** — Mobile and desktop optimized
✅ **Dark mode support** — Light and dark themes
✅ **Custom logo** — Add your brand identity
✅ **Localization ready** — Support for multiple languages (coming soon)
## Developer Experience
✅ **TypeScript support** — Full type definitions included
✅ **React hooks** — Idiomatic React integration
✅ **Async/await patterns** — Modern JavaScript syntax
✅ **Error handling** — Comprehensive error types
✅ **Loading states** — Built-in state management for async operations
✅ **Developer console** — Debug authentication flows
## Feature Comparison
### Ethereum vs Solana Features
| Feature | Ethereum | Solana | Notes |
| --------------------- | -------- | ------ | ------------------------------ |
| Email authentication | ✅ | ✅ | Same experience |
| OAuth authentication | ✅ | ✅ | Same providers |
| Wallet authentication | ✅ | ✅ | Different signature standards |
| Embedded wallets | ✅ | ✅ | Both self-custodial |
| Send transactions | ✅ | ✅ | Chain-specific implementations |
| Sign messages | ✅ | ✅ | Different message formats |
| Sign typed data | ✅ | ❌ | EIP-712 is Ethereum-only |
| External wallets | ✅ | ✅ | Different wallet providers |
## SDK Packages
MoonKey provides blockchain-specific packages for optimal tree-shaking and bundle size:
```bash theme={null}
# For Ethereum/EVM chains
@moon-key/react-auth/ethereum
# For Solana
@moon-key/react-auth/solana
```
Each package includes only the dependencies needed for that specific blockchain, resulting in smaller bundle sizes.
## Browser Compatibility
The MoonKey React SDK is compatible with all modern browsers:
* **Chrome/Edge** — Version 90+
* **Firefox** — Version 88+
* **Safari** — Version 14+
* **Opera** — Version 76+
Internet Explorer is not supported. Users on IE will need to use a modern browser.
## Questions about features?
Can't find a feature you're looking for? Have questions about implementation?
* Check the [Examples](/get-started/frontend-sdks/react/examples) for code samples
* Review the [API Reference](/api-reference/authentication) for detailed endpoint documentation
* Contact support for feature requests or technical guidance
## Migration Notes
If you're migrating from another auth provider, note that MoonKey focuses on:
* **Blockchain-native authentication** — Built specifically for Web3 applications
* **Self-custodial wallets** — Users always control their keys
* **Multi-chain from the start** — Ethereum and Solana support out of the box
* **Developer-friendly** — Simple APIs with powerful customization options
Need help with migration? We're here to help you transition smoothly.
# Installation
Source: https://docs.streambird.io/get-started/frontend-sdks/react/installation
Get started with MoonKey in your React or Next.js application by installing the React SDK.
## Requirements
Before installing the MoonKey React SDK, ensure your project meets these requirements:
* **React 18 or higher** — The SDK uses modern React features including hooks and concurrent rendering
* **TypeScript 5 or higher** — For TypeScript projects (recommended but optional)
## Install the SDK
Add the MoonKey React SDK to your project using your preferred package manager:
```bash npm theme={null}
npm install @moon-key/react-auth@latest
```
```bash pnpm theme={null}
pnpm install @moon-key/react-auth@latest
```
```bash yarn theme={null}
yarn add @moon-key/react-auth@latest
```
## Additional dependencies
### Solana support
If your application needs to support Solana wallets and transactions, install the Solana Web3.js library:
```bash npm theme={null}
npm install @solana/web3.js
```
```bash pnpm theme={null}
pnpm install @solana/web3.js
```
```bash yarn theme={null}
yarn add @solana/web3.js
```
Solana dependencies are only required if you plan to create Solana wallets or execute Solana transactions. EVM-only applications can skip this step.
### Next.js configuration
If you're using Next.js with webpack (not Turbopack), you may need to add polyfills for Node.js modules. Add the following to your `next.config.js`:
```js next.config.js theme={null}
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
}
return config;
},
};
```
Next.js 13+ with Turbopack handles these polyfills automatically. This configuration is only needed for webpack-based builds.
## Verify installation
After installation, verify the SDK is working by importing it in your code:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
```
If the import succeeds without errors, the SDK is installed correctly and you're ready to configure it.
## Next steps
Now that you've installed the MoonKey React SDK, continue to:
* [Configure the MoonKey Provider](/get-started/frontend-sdks/react/configuration) to initialize the SDK
* [Add authentication](/get-started/frontend-sdks/react/authentication) to your application
* [Create embedded wallets](/get-started/frontend-sdks/react/wallets) for your users
# Quickstart
Source: https://docs.streambird.io/get-started/frontend-sdks/react/quick-start
Get up and running with MoonKey in minutes. This guide walks you through authenticating users, creating embedded wallets, and executing blockchain transactions.
## Prerequisites
Before starting, make sure you've completed the [Setup guide](/get-started/frontend-sdks/react/setup) and have the `MoonKeyProvider` configured in your application.
## Step 1: Authenticate users with email
MoonKey makes authentication simple with email-based one-time passwords (OTPs). While this guide focuses on email authentication, MoonKey supports multiple methods including OAuth providers, wallet signatures, and WebAuthn.
Want to explore other authentication options? Check out our [Authentication methods](/methods/overview) documentation to learn about social logins, passkeys, and Web3 wallet authentication.
### Using the email authentication hook
The `useLoginWithEmail` hook provides everything you need to implement email OTP authentication:
```tsx theme={null}
import { useLoginWithEmail } from '@moon-key/react-auth';
function LoginComponent() {
const { sendCode, loginWithCode } = useLoginWithEmail();
// Use these methods to authenticate your user
}
```
Make sure your component is wrapped by `MoonKeyProvider` to use MoonKey hooks. See the [Setup guide](/get-started/frontend-sdks/react/setup) for details.
### Building a complete login flow
Here's a complete example showing how to collect an email, send an OTP, and authenticate the user:
```tsx theme={null}
import { useState } from 'react';
import { useLoginWithEmail } from '@moon-key/react-auth';
export default function EmailLogin() {
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [codeSent, setCodeSent] = useState(false);
const { sendCode, loginWithCode, state } = useLoginWithEmail();
const handleSendCode = async () => {
try {
await sendCode({ email });
setCodeSent(true);
} catch (error) {
console.error('Failed to send code:', error);
}
};
const handleLogin = async () => {
try {
await loginWithCode({ code: otp });
// User is now authenticated!
} catch (error) {
console.error('Failed to login:', error);
}
};
if (!codeSent) {
return (
Log in with Email
setEmail(e.target.value)}
/>
);
}
return (
Enter your code
We sent a 6-digit code to {email}
setOtp(e.target.value)}
maxLength={6}
/>
);
}
```
The OTP is valid for 10 minutes. If it expires, users can request a new code by re-entering their email.
## Step 2: Create embedded wallets
MoonKey can automatically create self-custodial wallets for your users during the authentication flow. Configure this in your `MoonKeyProvider`:
```tsx theme={null}
{children}
```
**Wallet creation options:**
* `'always'` — Automatically create a wallet for every user upon login
* `'none'` — Disable automatic wallet creation (create manually when needed)
MoonKey supports embedded wallet creation on both **Ethereum** (and all EVM chains) and **Solana**. Choose your blockchain when importing the provider.
### Manual wallet creation
Alternatively, you can create wallets on-demand when users need them:
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreateWallet = async () => {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
};
return ;
}
```
## Step 3: Send transactions
Once users have embedded wallets, your app can prompt them to sign and send blockchain transactions.
Use the `useSendTransaction` hook to send transactions on Ethereum and EVM-compatible chains:
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
export default function SendEthButton() {
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
try {
const txHash = await sendTransaction({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: '1000000000000000', // 0.001 ETH in wei
});
console.log('Transaction sent:', txHash);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return ;
}
```
Use the `useSendTransaction` hook to send transactions on Solana:
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/solana';
import {
Connection,
Transaction,
SystemProgram,
PublicKey,
LAMPORTS_PER_SOL
} from '@solana/web3.js';
export default function SendSolButton() {
const { sendTransaction } = useSendTransaction();
const connection = new Connection('https://api.mainnet-beta.solana.com');
const handleSend = async () => {
try {
// Create a transfer transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: userWallet.publicKey,
toPubkey: new PublicKey('RECIPIENT_ADDRESS_HERE'),
lamports: 0.01 * LAMPORTS_PER_SOL // 0.01 SOL
})
);
const signature = await sendTransaction({
transaction,
connection
});
console.log('Transaction sent:', signature);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return ;
}
```
## Accessing user data
After authentication, access user information with the `useMoonKey` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function UserProfile() {
const { user, isAuthenticated } = useMoonKey();
if (!isAuthenticated) {
return
Please log in
;
}
return (
Welcome!
Email: {user?.email?.address}
User ID: {user?.id}
Wallet: {user?.wallet?.address}
);
}
```
## Handling logout
Allow users to log out and clear their session:
```tsx theme={null}
import { useLogout } from '@moon-key/react-auth';
function LogoutButton() {
const { logout } = useLogout();
return ;
}
```
## Troubleshooting
### "User is not authenticated" error
Make sure you're checking the `isAuthenticated` state before attempting wallet operations:
```tsx theme={null}
const { isAuthenticated } = useMoonKey();
if (!isAuthenticated) {
return
Please log in first
;
}
```
### Transactions failing
Common reasons transactions fail:
* Insufficient balance in the wallet
* Invalid recipient address
* Network congestion or RPC issues
* User rejected the transaction
Always wrap transaction calls in try-catch blocks to handle errors gracefully.
### Hook errors
If you see "Hook can only be used inside MoonKeyProvider", ensure:
1. Your component is wrapped by `MoonKeyProvider`
2. You're importing from the correct package (`@moon-key/react-auth/ethereum` or `@moon-key/react-auth/solana`)
3. The provider is mounted before components using hooks
# Setup
Source: https://docs.streambird.io/get-started/frontend-sdks/react/setup
Configure the MoonKey React SDK in your application to enable authentication and wallet functionality.
## Before you start
Make sure you have:
* [Installed the MoonKey React SDK](/get-started/frontend-sdks/react/installation)
* [Created a MoonKey app](/get-started/dashboard/create-new-app) and obtained your App ID from the dashboard
Working with multiple environments or domains? You can create separate MoonKey apps for test (development) and production to isolate configurations and user data.
## Initialize the MoonKey Provider
The `MoonKeyProvider` component wraps your application and provides authentication and wallet context to all child components. Import it from the appropriate blockchain-specific entry point and add it to your app's root level.
**Choose your import based on the blockchain you're supporting:**
* For Ethereum/EVM chains: `@moon-key/react-auth/ethereum`
* For Solana: `@moon-key/react-auth/solana`
Place the `MoonKeyProvider` as high in your component tree as possible. Any component that needs to use MoonKey hooks must be wrapped by the provider.
```tsx Next.js (App Router) theme={null}
'use client';
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
```tsx Create React App theme={null}
import React from 'react';
import ReactDOM from 'react-dom/client';
import { MoonKeyProvider } from '@moon-key/react-auth';
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
);
```
```tsx Next.js (App Router) theme={null}
'use client';
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
```tsx Create React App theme={null}
import React from 'react';
import ReactDOM from 'react-dom/client';
import { MoonKeyProvider } from '@moon-key/react-auth';
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
);
```
## Provider configuration
The `MoonKeyProvider` component accepts the following props:
Your MoonKey App ID from the dashboard. This identifies your application and determines which configuration settings to use.
Configuration options for authentication methods, embedded wallets, appearance, and behavior.
**Common configuration options:**
* `loginMethods`: Array of authentication methods to enable (`'email'`, `'google'`, `'apple'`, `'wallet'`)
* `embeddedWallets`: Settings for automatic wallet creation and supported chains
* `appearance`: Customize the UI theme and branding
See the [configuration guide](/get-started/frontend-sdks/react/configuration) for all available options.
## Waiting for initialization
When the `MoonKeyProvider` first mounts, the SDK performs initialization tasks including:
* Checking for active user sessions
* Refreshing authentication tokens
* Loading user profile and wallet data
* Restoring previous authentication state
**Always wait for the SDK to finish initializing before accessing user state or triggering authentication flows.** Use the `ready` property from the `useMoonKey` hook to determine when initialization is complete.
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function YourComponent() {
const { ready, isAuthenticated, user } = useMoonKey();
// Show loading state while MoonKey initializes
if (!ready) {
return
Loading...
;
}
// Now it's safe to use authentication state
if (isAuthenticated) {
return
Welcome back, {user?.email}!
;
}
return
Please log in
;
}
```
The `ready` state ensures you don't show incorrect UI (like a login button for an already-authenticated user) during the brief initialization period.
## Next.js considerations
### App Router (Next.js 13+)
When using Next.js App Router, your provider component must include the `'use client'` directive since MoonKey relies on React hooks and browser APIs:
```tsx providers.tsx theme={null}
'use client';
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
Then use this provider in your root layout:
```tsx app/layout.tsx theme={null}
import Providers from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
### Pages Router (Next.js 12 and earlier)
For Pages Router, wrap your app in `_app.tsx`:
```tsx pages/_app.tsx theme={null}
import type { AppProps } from 'next/app';
import { MoonKeyProvider } from '@moon-key/react-auth';
export default function App({ Component, pageProps }: AppProps) {
return (
);
}
```
## Example configurations
### Minimal setup
The simplest configuration with just an App ID (Ethereum):
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Email-only authentication
For applications that only need email authentication:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Wallet-first experience
For Web3-native applications with Solana:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
### Custom branding
Customize the appearance to match your brand:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
## Troubleshooting
### "MoonKey is not ready" errors
If you encounter errors about MoonKey not being initialized, ensure you're:
1. Checking the `ready` state before using MoonKey hooks
2. Wrapping your components with `MoonKeyProvider`
3. Not trying to use MoonKey hooks in server components (Next.js App Router)
### Hooks can only be used inside MoonKeyProvider
This error means you're trying to use a MoonKey hook outside the provider's scope. Make sure:
1. Your `MoonKeyProvider` wraps all components that use MoonKey hooks
2. You haven't accidentally placed the provider inside a component that's trying to use MoonKey
3. The provider is rendered before any child components try to use hooks
## Next steps
Learn how to implement login and logout functionality
Generate embedded wallets for your users
Match MoonKey's UI to your brand
Access and manage user profiles
# Choose your platform
Source: https://docs.streambird.io/get-started/platform-selection
MoonKey provides flexible integration options to fit your application architecture. Whether you're building a web application, managing backend operations, or need direct API access, MoonKey offers the tools you need to implement authentication and wallet functionality.
## Integration options
### REST API
The MoonKey REST API provides direct access to all platform features through standard HTTP requests. Use the REST API when you need maximum flexibility, are working in an environment without SDK support, or want to build custom integrations.
**Key capabilities:**
* Create and manage user accounts
* Generate self-custodial wallets on EVM and Solana chains
* Handle authentication flows (OAuth, magic links, OTPs, WebAuthn)
* Manage sessions and verify tokens
* Execute and track blockchain transactions
The REST API is ideal for custom backend implementations, serverless functions, or any language that can make secure HTTPS requests.
[Explore the REST API documentation →](/api-reference/authentication)
### React SDK
The MoonKey React SDK provides ready-to-use hooks and components for building authentication and wallet functionality into React and Next.js applications. Designed for modern web development, the SDK integrates seamlessly with React's ecosystem.
**What's included:**
* React hooks for authentication state management
* Pre-built UI components for login and wallet interactions
* Wallet connection and transaction signing
* Support for multiple authentication methods
* Session management and token verification
* Compatible with Next.js App Router and Pages Router
Perfect for web applications that need quick integration with customizable UI components.
[Get started with React SDK →](/sdks/javascript/setup)
### Node.js SDK
The MoonKey Node.js SDK is designed for server-side applications, providing a streamlined interface to the REST API with TypeScript support. Use it to securely manage user data, verify authentication tokens, and coordinate wallet operations from your backend.
**Server-side capabilities:**
* Verify user sessions and JWT tokens
* Manage user accounts and data
* Generate and manage wallets server-side
* Execute administrative operations
* Handle webhook integrations
Ideal for Express, Fastify, Next.js API routes, and any Node.js backend framework.
[Set up the Node.js SDK →](/get-started/backend-sdks)
## Blockchain support
MoonKey supports wallet creation and transaction management across multiple blockchain ecosystems:
**EVM Chains** — Full support for Ethereum and all EVM-compatible chains including Polygon, Arbitrum, Optimism, Base, BSC, Avalanche, and custom EVM networks.
**Solana** — Native support for Solana mainnet, devnet, and testnet environments.
Whether you're building for a specific chain or need multi-chain functionality, MoonKey handles the complexity of key management and transaction signing across both ecosystems.
## Choosing your integration path
**Building a web app?** → Start with the React SDK for the fastest integration with pre-built components.
**Need backend control?** → Use the Node.js SDK to manage authentication and wallets from your server.
**Want maximum flexibility?** → Integrate directly with the REST API for complete control over your implementation.
**Building for mobile or other platforms?** → Use the REST API to build custom integrations for any environment.
# Quickstart
Source: https://docs.streambird.io/get-started/rest-api/quick-start
This guide walks you through creating a wallet, signing a message, and sending your first transaction using the MoonKey REST API.
## Prerequisites
Before you begin, make sure you have:
* Completed the [REST API setup guide](/get-started/rest-api/setup)
* Your MoonKey API key (test or live)
* A test user ID (or follow step 0 to create one)
All examples in this guide use test API keys. Replace `sk_test_your_api_key_here` with your actual API key.
## Create a User (Optional)
If you want your wallet to be owned by a specific user, first create a user account:
```bash cURL theme={null}
curl -X POST "https://api.moonkey.fun/v1/auth/users/create" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'
```
```javascript JavaScript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const response = await fetch('https://api.moonkey.fun/v1/auth/users/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com'
})
});
const user = await response.json();
console.log('User created:', user.id);
```
```python Python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
url = "https://api.moonkey.fun/v1/auth/users/create"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"email": "user@example.com"
}
response = requests.post(url, json=payload, headers=headers)
user = response.json()
print(f"User created: {user['id']}")
```
The response will include the user ID:
```json theme={null}
{
"id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI",
"app_id": "app_24ydphdixx2ydhF0E5WUFUKWNqi",
"email": "user@example.com",
"created_at": 1659638371,
"updated_at": 1659638371
}
```
Save the `id` field from the response. You'll need it in the next step to create a wallet for this user.
## 1. Create a Wallet
Let's create an Ethereum wallet for your user:
```bash cURL theme={null}
curl -X POST "https://api.moonkey.fun/v1/wallets/create" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"wallet_type": "ethereum",
"user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI"
}'
```
```javascript JavaScript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const userId = 'user_2Cu2uVhYy0OVgRcO913OsqIVaPI';
const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_type: 'ethereum',
user_id: userId
})
});
const wallet = await response.json();
console.log('Wallet created:', wallet.public_address);
```
```python Python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
user_id = 'user_2Cu2uVhYy0OVgRcO913OsqIVaPI'
url = "https://api.moonkey.fun/v1/wallets/create"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"wallet_type": "ethereum",
"user_id": user_id
}
response = requests.post(url, json=payload, headers=headers)
wallet = response.json()
print(f"Wallet created: {wallet['public_address']}")
```
The response will include the wallet ID and public address:
```json theme={null}
{
"id": "wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK",
"app_id": "app_24ydphdixx2ydhF0E5WUFUKWNqi",
"user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI",
"public_address": "0xf1347fd847f19c250b4c9678ecaa27b0f6ce8804",
"wallet_type": "ethereum",
"verified": true,
"is_default": true,
"is_read_only": false,
"is_imported": false,
"created_at": 1659638371,
"updated_at": 1659638371
}
```
If a wallet of the same type already exists for the user, MoonKey will return the existing wallet instead of creating a new one.
Save the wallet `id` and `public_address`. You'll need these for the next steps.
## 2. Sign a Message
Now let's sign a message with your newly created wallet:
```bash cURL theme={null}
curl -X POST "https://api.moonkey.fun/v1/wallets/wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK/sign_message" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"message": "Hello from MoonKey!"
}'
```
```javascript JavaScript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const walletId = 'wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK';
const response = await fetch(
`https://api.moonkey.fun/v1/wallets/${walletId}/sign_message`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Hello from MoonKey!'
})
}
);
const result = await response.json();
console.log('Message signed:', result.signature);
```
```python Python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
wallet_id = 'wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK'
url = f"https://api.moonkey.fun/v1/wallets/{wallet_id}/sign_message"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"message": "Hello from MoonKey!"
}
response = requests.post(url, json=payload, headers=headers)
result = response.json()
print(f"Message signed: {result['signature']}")
```
The response will contain the signed message:
```json theme={null}
{
"wallet_id": "wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK",
"wallet": {
"id": "wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK",
"app_id": "app_25ldv51seNohTaYRsxdfoxMlAa2",
"user_id": "user_26l6ha8syVN8oqmaHaFShTxZ5RC",
"public_address": "0xb6acedc0cdcab7a4bb6c236976bb7df63bbcd567",
"wallet_type": "ethereum",
"verified": true,
"is_default": true,
"is_read_only": false,
"is_imported": false,
"created_at": 1647985945,
"updated_at": 1647985945
},
"signature": "0xc8723cfad978035ad3b960beade5237477aa91a9b01ba238d859e2a6e0fbded322e4732d3252afc4f7bec0018cc7bdddfe90547cc4fa985a400744315316198e01"
}
```
## 3. Send a Transaction
Finally, let's send a transaction on Ethereum's testnet, [Sepolia](https://sepolia.etherscan.io/):
```bash cURL theme={null}
curl -X POST "https://api.moonkey.fun/v1/wallets/wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK/sign_transaction" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"eth_transaction": {
"to": "0xE9534C9fAF3cDBc636c25E1981A80EfaE03f187a",
"value": "0x38d7ea4c68000",
"nonce": "0x8",
"maxPriorityFeePerGas": "0x77359400",
"maxFeePerGas": "0xdf8475800",
"gas": "0x5208",
"chainId": "0xaa36a7"
}
}'
```
```javascript JavaScript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const walletId = 'wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK';
const response = await fetch(
`https://api.moonkey.fun/v1/wallets/${walletId}/sign_transaction`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
eth_transaction: {
to: '0xE9534C9fAF3cDBc636c25E1981A80EfaE03f187a',
value: '0x38d7ea4c68000', // 0.001 ETH in wei (hex)
nonce: '0x8',
maxPriorityFeePerGas: '0x77359400',
maxFeePerGas: '0xdf8475800',
gas: '0x5208',
chainId: '0xaa36a7' // Sepolia testnet
}
})
}
);
const result = await response.json();
console.log('Transaction signed:', result.signed_transaction);
```
```python Python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
wallet_id = 'wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK'
url = f"https://api.moonkey.fun/v1/wallets/{wallet_id}/sign_transaction"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"eth_transaction": {
"to": "0xE9534C9fAF3cDBc636c25E1981A80EfaE03f187a",
"value": "0x38d7ea4c68000", # 0.001 ETH in wei (hex)
"nonce": "0x8",
"maxPriorityFeePerGas": "0x77359400",
"maxFeePerGas": "0xdf8475800",
"gas": "0x5208",
"chainId": "0xaa36a7" # Sepolia testnet
}
}
response = requests.post(url, json=payload, headers=headers)
result = response.json()
print(f"Transaction signed: {result['signed_transaction']}")
```
You'll need to fund your wallet with Sepolia ETH for this step. Use a [Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) to send test ETH to your wallet's `public_address`.
The response will contain the signature and signed transaction:
```json theme={null}
{
"wallet_id": "wallet_26l6hdwAXQr0y573AhQTXNDkyqK",
"wallet": {
"id": "wallet_26l6hdwAXQr0y573AhQTXNDkyqK",
"app_id": "app_25ldv51seNohTaYRsxdfoxMlAa2",
"user_id": "user_26l6ha8syVN8oqmaHaFShTxZ5RC",
"public_address": "0xb6acedc0cdcab7a4bb6c236976bb7df63bbcd567",
"wallet_type": "ethereum",
"verified": true,
"is_default": true,
"is_read_only": false,
"is_imported": false,
"created_at": 1647985945,
"updated_at": 1647985945
},
"signature": "0x744b61c44cdb250b89438709a9438a0b9e30c9c46e29776c93fb5e150e56021a",
"signed_transaction": "0xb87502f87205088477359400850df847580082520894e9534c9faf3cdbc636c25e1981a80efae03f187a87038d7ea4c6800080c001a0cd31b38c3e3a48230bee2d5c687a0b2a5efcb298c58cfc3b43449eefd17857cfa03f95b8bcad72f7bdca59025e797781f4ba3fde826f62b9d6997aaf19a7d7591b"
}
```
### Broadcasting the Transaction
The `signed_transaction` is ready to be broadcast to the Ethereum network. You can use it with the `eth_sendRawTransaction` RPC method:
```javascript JavaScript theme={null}
const signedTx = result.signed_transaction;
// Using a public RPC endpoint
const rpcResponse = await fetch('https://rpc.sepolia.org', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_sendRawTransaction',
params: [signedTx],
id: 1
})
});
const txResult = await rpcResponse.json();
console.log('Transaction hash:', txResult.result);
```
```python Python theme={null}
import requests
signed_tx = result['signed_transaction']
# Using a public RPC endpoint
rpc_url = "https://rpc.sepolia.org"
rpc_payload = {
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": [signed_tx],
"id": 1
}
rpc_response = requests.post(rpc_url, json=rpc_payload)
tx_result = rpc_response.json()
print(f"Transaction hash: {tx_result['result']}")
```
View your transaction on [Sepolia Etherscan](https://sepolia.etherscan.io/) by searching for the transaction hash.
## Understanding Transaction Parameters
When sending a transaction, you need to provide several parameters:
| Parameter | Description | Example |
| ---------------------- | --------------------------------- | -------------------------------------------- |
| `to` | Recipient address | `0xE9534C9fAF3cDBc636c25E1981A80EfaE03f187a` |
| `value` | Amount in wei (hex) | `0x38d7ea4c68000` (0.001 ETH) |
| `nonce` | Transaction count for the address | `0x8` |
| `maxPriorityFeePerGas` | Priority fee (hex) | `0x77359400` |
| `maxFeePerGas` | Max total fee (hex) | `0xdf8475800` |
| `gas` | Gas limit (hex) | `0x5208` (21000 for simple transfer) |
| `chainId` | Network chain ID (hex) | `0xaa36a7` (Sepolia) |
Use tools like [eth-converter.com](https://eth-converter.com/) to convert between decimal and hex values for ETH amounts and gas prices.
## Creating Solana Wallets
MoonKey also supports Solana wallets. To create a Solana wallet, simply change the `wallet_type`:
```bash cURL theme={null}
curl -X POST "https://api.moonkey.fun/v1/wallets/create" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"wallet_type": "solana",
"user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI"
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_type: 'solana',
user_id: userId
})
});
const wallet = await response.json();
console.log('Solana wallet created:', wallet.public_address);
```
## Best Practices
### Security
* **Never expose API keys** - Keep your secret API keys secure on your backend
* **Use test keys for development** - Test thoroughly before switching to live keys
* **Validate user actions** - Always verify that requests come from authenticated users
### Transaction Safety
* **Verify addresses** - Always double-check recipient addresses before sending transactions
* **Test on testnet first** - Use Sepolia for Ethereum or Devnet for Solana before going to mainnet
* **Monitor transaction status** - Track transactions and handle failures gracefully
* **Use appropriate gas limits** - Ensure transactions have enough gas to complete
### Error Handling
Always implement proper error handling for API requests:
```javascript theme={null}
try {
const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_type: 'ethereum',
user_id: userId
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create wallet');
}
const wallet = await response.json();
return wallet;
} catch (error) {
console.error('Wallet creation failed:', error.message);
// Handle error appropriately for your application
}
```
## Troubleshooting
### Common Issues
**401 Unauthorized**
* Verify your API key is correct
* Ensure you're using the `Bearer` token format
* Check that your API key hasn't been revoked
**404 Not Found**
* Verify the wallet ID is correct
* Ensure the wallet belongs to your app
* Check that the user ID exists
**Transaction Fails**
* Verify the wallet has sufficient balance
* Check that gas limits and fees are appropriate
* Ensure the recipient address is valid
* Verify you're on the correct network (testnet vs mainnet)
**Rate Limiting**
* Implement exponential backoff for retries
* Cache wallet data when possible
* Batch operations where appropriate
## Support
Need help?
* Check the [API Reference](/api-reference/authentication) for detailed endpoint documentation
* Review [authentication setup](/get-started/rest-api/setup) for configuration help
* Contact support for technical assistance
# Setup
Source: https://docs.streambird.io/get-started/rest-api/setup
The MoonKey REST API provides programmatic access to all authentication and wallet management functionality. Use the API to manage users, create wallets, handle authentication flows, and more from your backend.
## Prerequisites
Before you begin, make sure you have:
* [Created a MoonKey app](/get-started/dashboard/create-new-app) in the dashboard
* Obtained your API keys from the dashboard
## Base URL
All requests to the MoonKey API must be made to the following base URL:
```
https://api.moonkey.fun
```
**HTTPS is required for all requests.** HTTP requests will be rejected.
## Authentication
The MoonKey API uses **Bearer token authentication**. You must include your API key in the `Authorization` header of every request.
### API Keys
MoonKey provides two types of API keys:
| Key Type | Format | Usage | Security |
| ------------- | ------------- | ----------------------- | ---------------------------------------- |
| **Test Keys** | `sk_test_...` | Development and testing | Safe to use in test environments |
| **Live Keys** | `sk_live_...` | Production | Keep secure, never expose in client code |
Never share your secret API keys in publicly accessible areas such as GitHub, client-side code, or browser developer tools. Always use environment variables to store your keys.
### Authentication Header
Include your API key in the `Authorization` header using the Bearer scheme:
```
Authorization: Bearer {your_api_key}
```
### Required Headers
Every API request must include these headers:
Bearer token with your MoonKey API key (test or live).
Example: `Bearer sk_test_pRqweh3wvWmJAAVYv7Z0T5iPLzFM4ql0muoyQcjOxGeN3p1r`
Set to `application/json` for all requests with a body.
## Getting Your API Keys
Retrieve your API keys from the MoonKey Dashboard:
1. Log in to your [MoonKey Dashboard](https://dashboard.moonkey.fun)
2. Select your app from the dropdown
3. Navigate to **API Keys** in the sidebar
4. Copy your test or live API key
5. Store it securely in your environment variables
Start with test keys during development. Switch to live keys only when you're ready to go to production.
## Example Requests
Here are examples of making authenticated requests to the MoonKey API in different languages:
```bash theme={null}
curl -X GET "https://api.moonkey.fun/v1/auth/users/user_123" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json"
```
```javascript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const response = await fetch('https://api.moonkey.fun/v1/auth/users/user_123', {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
```
```javascript theme={null}
const axios = require('axios');
const apiKey = process.env.MOONKEY_SECRET_KEY;
const response = await axios.get(
'https://api.moonkey.fun/v1/auth/users/user_123',
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
console.log(response.data);
```
```python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
url = "https://api.moonkey.fun/v1/auth/users/user_123"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
data = response.json()
print(data)
```
```go theme={null}
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("MOONKEY_SECRET_KEY")
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.moonkey.fun/v1/auth/users/user_123", nil)
req.Header.Add("Authorization", "Bearer "+apiKey)
req.Header.Add("Content-Type", "application/json")
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
## Making POST Requests
For requests that create or update resources, include a JSON body:
```bash theme={null}
curl -X POST "https://api.moonkey.fun/v1/auth/users" \
-H "Authorization: Bearer sk_test_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'
```
```javascript theme={null}
const apiKey = process.env.MOONKEY_SECRET_KEY;
const response = await fetch('https://api.moonkey.fun/v1/auth/users', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com'
})
});
const data = await response.json();
```
```python theme={null}
import requests
import os
api_key = os.environ.get('MOONKEY_SECRET_KEY')
url = "https://api.moonkey.fun/v1/auth/users"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"email": "user@example.com"
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
```
## Environment Variables
Store your API keys securely using environment variables:
```bash .env theme={null}
# Never commit this file to version control!
MOONKEY_SECRET_KEY=sk_test_your_api_key_here
```
**Loading environment variables:**
```javascript theme={null}
// Using dotenv package
require('dotenv').config();
const apiKey = process.env.MOONKEY_SECRET_KEY;
```
```python theme={null}
# Using python-dotenv package
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.environ.get('MOONKEY_SECRET_KEY')
```
```javascript theme={null}
// Next.js automatically loads .env.local files
// Use NEXT_PUBLIC_ prefix only for client-side variables
const apiKey = process.env.MOONKEY_SECRET_KEY;
```
## Response Format
All API responses are returned in JSON format with consistent structure:
### Success Response
```json theme={null}
{
"id": "user_123",
"email": "user@example.com",
"created_at": "2024-01-15T10:30:00Z",
// ... additional fields
}
```
### Error Response
```json theme={null}
{
"error": {
"code": "invalid_request",
"message": "Missing required field: email"
}
}
```
## HTTP Status Codes
The MoonKey API uses standard HTTP status codes:
| Status Code | Meaning |
| ----------- | ------------------------------------------------------- |
| `200` | Success - Request completed successfully |
| `201` | Created - Resource created successfully |
| `400` | Bad Request - Invalid request parameters |
| `401` | Unauthorized - Invalid or missing API key |
| `403` | Forbidden - API key doesn't have required permissions |
| `404` | Not Found - Resource doesn't exist |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Internal Server Error - Something went wrong on our end |
## Rate Limiting
The MoonKey API implements rate limiting to ensure service reliability:
* **Test environment**: 100 requests per minute
* **Production environment**: 1000 requests per minute
When you exceed the rate limit, you'll receive a `429` status code. The response includes headers indicating when you can retry:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000000
```
Implement exponential backoff when you receive rate limit errors to ensure your application handles limits gracefully.
## Best Practices
### Security
* **Never expose API keys in client-side code** - API keys should only be used from your backend
* **Use environment variables** - Store keys in `.env` files and never commit them to version control
* **Rotate keys regularly** - Generate new keys periodically and revoke old ones
* **Use test keys in development** - Keep live keys separate and only use them in production
### Error Handling
Always implement proper error handling:
```javascript theme={null}
try {
const response = await fetch('https://api.moonkey.fun/v1/auth/users/user_123', {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const data = await response.json();
return data;
} catch (error) {
console.error('API request failed:', error.message);
// Handle error appropriately
}
```
## Testing Your Integration
Test your API integration with these steps:
1. **Use test API keys** - Start with `sk_test_...` keys
2. **Test authentication** - Verify your API key works by fetching user data
3. **Handle errors** - Test error scenarios (invalid keys, missing parameters, etc.)
4. **Check rate limits** - Ensure your application handles rate limiting properly
5. **Validate responses** - Verify response structure matches documentation
## Next Steps
Now that you've set up API authentication, explore the available endpoints:
Manage user authentication and sessions
Create and manage user accounts
Create and manage user wallets
Verify and manage user sessions
## Support
Need help with the REST API?
* Check the [API Reference](/api-reference/authentication) for detailed endpoint documentation
* Review [code examples](/get-started/frontend-sdks/react/examples) for common patterns
* Contact support for technical assistance
# Setup an account
Source: https://docs.streambird.io/get-started/setup-account
Getting started with MoonKey begins by creating your account on the [MoonKey Dashboard](https://dashboard.moonkey.fun). The dashboard serves as your control center for managing applications, API credentials, authentication configurations, and user data.
During the initial setup, you'll provide information about your project and technical requirements. This helps MoonKey configure optimal default settings tailored to your specific use case and technology stack.
## Understanding the dashboard
Once your account is created, the MoonKey dashboard gives you access to several key management areas:
### API Keys
Generate and manage your public and private API keys for authenticating with the MoonKey API and SDK. These credentials are essential for integrating MoonKey into your application and making secure API calls.
### Redirect URLs
Define and verify the redirect URLs used throughout your authentication flows. This includes URLs for login redirects, sign-up completions, and invitation acceptance. Proper redirect URL configuration ensures seamless user experiences and prevents unauthorized redirect attacks.
### OAuth Configuration
Set up third-party authentication providers like Google, Microsoft, and Apple. The OAuth section allows you to configure client IDs, secrets, and scopes for each provider you want to enable in your application.
### Email Templates
Customize the email templates used for various authentication scenarios including login emails, sign-up confirmations, and user invitations. Tailor the messaging and branding to match your application's identity.
### App Settings
Configure core application settings such as your app's environment (development, staging, production), session duration and behavior, and access control policies. This is where you fine-tune how MoonKey operates within your application's context.
## Next steps
With your account set up and your dashboard configured, you're ready to integrate MoonKey into your application. Head over to our SDK setup guides to begin implementing authentication and wallet functionality.
# Customize your application
Source: https://docs.streambird.io/recipes/dashboard/customize
Configure your app's brand settings in the MoonKey Dashboard
Navigate to **Configuration > UI components** in the [MoonKey Dashboard](https://dashboard.moonkey.fun) to customize your application's branding, including your product name, logo, and accent color.
## Name
Set your product name to control how MoonKey displays your brand to users. This name appears in OTP authentication messages and throughout the MoonKey interface when users interact with your app.
## Logo
Specify a logo URL for your product's branding. For optimal display, use an image with a 2:1 aspect ratio (recommended: 180px × 90px). Note that SVG format is not supported due to email client compatibility issues.
Your logo will appear in:
* Passwordless email login messages sent to users
* The MoonKey authentication modal during login
While you can override the modal logo through the SDK for custom branding, maintaining a logo in the dashboard ensures consistent branding in email communications.
## Brand color
Define your application's accent color using a hexadecimal color code. This color theme will be applied to interactive elements like buttons and links across all MoonKey UI components in your application.
# Overview
Source: https://docs.streambird.io/recipes/overview
Comprehensive, production-ready examples for common MoonKey integration patterns. Each recipe includes complete code, styling examples, and best practices.
## Authentication
Full-featured email OTP authentication with error handling and loading states
Protect routes and require authentication before access
## Dashboard & User Interface
Complete user dashboard integrating authentication, wallet display, and transactions
Display wallet connection status with visual indicators
## Ethereum Transactions
Send ERC-20 tokens using the embedded wallet
Mint NFTs by calling smart contract functions
Handle complex multi-step flows like approve + swap
## Solana Development
Interact with Solana programs using the embedded wallet
## Styling & Design
CSS examples for authentication, dashboard, and UI components
## What's included
Each recipe provides:
* **Complete, working code** - Copy and paste ready implementations
* **Best practices** - Error handling, loading states, and user feedback
* **TypeScript support** - Fully typed examples
* **Responsive design** - Works on mobile and desktop
* **Styling examples** - Ready-to-use CSS
* **Common patterns** - Variations and advanced use cases
## Getting started
1. Choose a recipe that matches your use case
2. Copy the code into your project
3. Customize the styling and behavior
4. Add your own business logic
All recipes are built with React and TypeScript, and work with Next.js, Create React App, Vite, and other React frameworks.
## Need help?
Can't find what you're looking for? Check out our comprehensive documentation:
Get started with MoonKey
Authentication methods
Wallet features
# Complete Authentication Flow
Source: https://docs.streambird.io/recipes/wallets/complete-authentication-flow
A full-featured authentication component with email OTP, including error handling, loading states, and user experience improvements.
## Implementation
```tsx theme={null}
import { useState } from 'react';
import { useLoginWithEmail, useMoonKey } from '@moon-key/react-auth';
export default function AuthenticationFlow() {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [codeSent, setCodeSent] = useState(false);
const [error, setError] = useState('');
const { sendCode, loginWithCode, state } = useLoginWithEmail();
const { user, isAuthenticated, ready } = useMoonKey();
// Wait for MoonKey to initialize
if (!ready) {
return (
Loading MoonKey...
);
}
// Show user profile if authenticated
if (isAuthenticated) {
return (
Welcome back!
Email: {user?.email?.address}
User ID: {user?.id}
{user?.wallet &&
Wallet: {user.wallet.address}
}
);
}
const handleSendCode = async () => {
setError('');
try {
await sendCode({ email });
setCodeSent(true);
} catch (err) {
setError('Failed to send code. Please check your email and try again.');
console.error(err);
}
};
const handleLogin = async () => {
setError('');
try {
await loginWithCode({ code });
// User is now authenticated - component will re-render
} catch (err) {
setError('Invalid code. Please try again or request a new code.');
console.error(err);
}
};
const handleResendCode = async () => {
setCode('');
setCodeSent(false);
setError('');
};
if (!codeSent) {
return (
);
}
```
## Key concepts
* **ERC-20 ABI** - Minimal ABI containing only the transfer function
* **Function encoding** - Uses `encodeFunctionData` from viem to encode the function call
* **Amount handling** - Amount should be in the token's smallest unit (considering decimals)
* **Contract interaction** - Sends transaction to the token contract address with encoded data
## Important notes
The amount should be specified in the token's smallest unit. For a token with 18 decimals, to send 1 token, you'd specify `1000000000000000000` (1 × 10^18).
Make sure the user has approved your contract to spend tokens if you're building a dApp that needs to transfer tokens on behalf of users.
## Next steps
Learn how to mint NFTs
Handle approve + transfer flows
# Multi-Step Transaction Flow
Source: https://docs.streambird.io/recipes/wallets/multi-step-transaction-flow
Handle complex multi-step transaction flows with proper state management, visual feedback, and error handling.
## Implementation
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
type FlowStep = 'idle' | 'approving' | 'approved' | 'swapping' | 'complete' | 'error';
export default function MultiStepTransaction() {
const { sendTransaction } = useSendTransaction();
const [step, setStep] = useState('idle');
const [error, setError] = useState('');
const handleTokenSwap = async () => {
try {
// Step 1: Approve token spending
setStep('approving');
const approveTx = await sendTransaction({
to: '0xTOKEN_ADDRESS',
data: '0x...' // approve function call data
});
console.log('Approval tx:', approveTx);
setStep('approved');
// Wait a bit for confirmation
await new Promise(resolve => setTimeout(resolve, 2000));
// Step 2: Execute swap
setStep('swapping');
const swapTx = await sendTransaction({
to: '0xSWAP_CONTRACT',
data: '0x...' // swap function call data
});
console.log('Swap tx:', swapTx);
setStep('complete');
} catch (err) {
setStep('error');
setError(err instanceof Error ? err.message : 'Transaction failed');
console.error(err);
}
};
return (
);
}
```
## Key concepts
* **State management** - Tracks progress through multiple steps
* **Visual feedback** - Shows loading indicators and checkmarks for each step
* **Error handling** - Catches errors at any step and allows retry
* **User experience** - Disables button during processing
* **Confirmation delays** - Waits for transaction confirmation before proceeding
## Common patterns
### With transaction confirmation
```tsx theme={null}
import { waitForTransaction } from 'wagmi';
const handleMultiStep = async () => {
setStep('approving');
const approveTx = await sendTransaction({...});
// Wait for confirmation
await waitForTransaction({ hash: approveTx });
setStep('approved');
// Continue with next step
setStep('swapping');
const swapTx = await sendTransaction({...});
await waitForTransaction({ hash: swapTx });
setStep('complete');
};
```
### With progress percentage
```tsx theme={null}
const [progress, setProgress] = useState(0);
const handleFlow = async () => {
setProgress(0);
setProgress(25);
await step1();
setProgress(50);
await step2();
setProgress(75);
await step3();
setProgress(100);
};
```
### With cancellation support
```tsx theme={null}
const [isCancelled, setIsCancelled] = useState(false);
const handleFlow = async () => {
setIsCancelled(false);
await step1();
if (isCancelled) return;
await step2();
if (isCancelled) return;
await step3();
};
const handleCancel = () => setIsCancelled(true);
```
## Important notes
Always wait for transaction confirmation before proceeding to the next step to avoid errors from transactions not being mined yet.
Consider adding retry logic for failed steps, especially for network-related errors.
## Styling suggestion
```css theme={null}
.steps {
display: flex;
gap: 2rem;
margin: 2rem 0;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
opacity: 0.5;
transition: opacity 0.2s;
}
.step.active {
opacity: 1;
}
.step-number {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.step.active .step-number {
background: #6366f1;
color: white;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #e5e7eb;
border-top-color: #6366f1;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.checkmark {
color: #10b981;
font-size: 20px;
}
```
## Next steps
Token transfer basics
Style your components
# NFT Minting (Ethereum)
Source: https://docs.streambird.io/recipes/wallets/nft-minting-etheruem
Mint an NFT by calling a smart contract's mint function with the embedded wallet.
## Implementation
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';
const NFT_MINT_ABI = [
{
name: 'mint',
type: 'function',
stateMutability: 'payable',
inputs: [{ name: 'quantity', type: 'uint256' }],
outputs: []
}
] as const;
export default function MintNFT() {
const { sendTransaction } = useSendTransaction();
const handleMint = async () => {
const nftContractAddress = '0x...'; // Your NFT contract
const mintPrice = '10000000000000000'; // 0.01 ETH in wei
try {
const data = encodeFunctionData({
abi: NFT_MINT_ABI,
functionName: 'mint',
args: [1n] // Mint 1 NFT
});
const txHash = await sendTransaction({
to: nftContractAddress,
value: mintPrice,
data
});
console.log('NFT minted:', txHash);
} catch (error) {
console.error('Minting failed:', error);
}
};
return (
Mint NFT
);
}
```
## Key concepts
* **Payable function** - The mint function accepts ETH (specified in `value`)
* **Function encoding** - Encodes the mint function call with quantity parameter
* **Value in wei** - ETH amount must be specified in wei (1 ETH = 10^18 wei)
* **Contract call** - Sends transaction to NFT contract with both value and data
## Common patterns
### Dynamic quantity
```tsx theme={null}
const [quantity, setQuantity] = useState(1);
const handleMint = async () => {
const data = encodeFunctionData({
abi: NFT_MINT_ABI,
functionName: 'mint',
args: [BigInt(quantity)]
});
const txHash = await sendTransaction({
to: nftContractAddress,
value: (parseFloat(mintPrice) * quantity).toString(),
data
});
};
```
### With status tracking
```tsx theme={null}
const [mintStatus, setMintStatus] = useState<'idle' | 'minting' | 'success' | 'error'>('idle');
const handleMint = async () => {
setMintStatus('minting');
try {
await sendTransaction({...});
setMintStatus('success');
} catch (error) {
setMintStatus('error');
}
};
```
## Important notes
Make sure the user has sufficient ETH balance to cover both the mint price and gas fees.
The ABI should match your NFT contract's mint function signature. Common variations include `mint()`, `mint(uint256 quantity)`, or `mint(address to, uint256 quantity)`.
## Next steps
Interact with Solana programs
Handle complex transaction flows
# Protected Routes with Authentication
Source: https://docs.streambird.io/recipes/wallets/protected-routes-with-authentication
Protect routes and require authentication before allowing access to specific pages in your application.
## Implementation
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
interface ProtectedRouteProps {
children: ReactNode;
}
export function ProtectedRoute({ children }: ProtectedRouteProps) {
const { isAuthenticated, ready } = useMoonKey();
// Wait for MoonKey to initialize
if (!ready) {
return (
Loading...
);
}
// Redirect to login if not authenticated
if (!isAuthenticated) {
return ;
}
// Render protected content
return <>{children}>;
}
// Usage in your router:
//
//
//
// } />
```
## Usage examples
### React Router v6
```tsx theme={null}
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ProtectedRoute } from './ProtectedRoute';
function App() {
return (
} />
} />
{/* Protected routes */}
}
/>
}
/>
}
/>
);
}
```
### Next.js App Router
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useRouter } from 'next/navigation';
import { useEffect, ReactNode } from 'react';
export function ProtectedRoute({ children }: { children: ReactNode }) {
const { isAuthenticated, ready } = useMoonKey();
const router = useRouter();
useEffect(() => {
if (ready && !isAuthenticated) {
router.push('/login');
}
}, [ready, isAuthenticated, router]);
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return null; // Will redirect in useEffect
}
return <>{children}>;
}
// Usage in app/dashboard/page.tsx:
// export default function DashboardPage() {
// return (
//
//
//
// );
// }
```
## Advanced patterns
### With redirect to original destination
```tsx theme={null}
import { Navigate, useLocation } from 'react-router-dom';
export function ProtectedRoute({ children }: { children: ReactNode }) {
const { isAuthenticated, ready } = useMoonKey();
const location = useLocation();
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
// Redirect to login, but save the current location
return ;
}
return <>{children}>;
}
// In your login component, redirect back after login:
// const location = useLocation();
// const from = location.state?.from?.pathname || '/dashboard';
// navigate(from, { replace: true });
```
### With role-based access
```tsx theme={null}
interface ProtectedRouteProps {
children: ReactNode;
requiredRole?: string;
}
export function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
const { user, isAuthenticated, ready } = useMoonKey();
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return ;
}
if (requiredRole && user?.role !== requiredRole) {
return ;
}
return <>{children}>;
}
// Usage:
//
//
//
// } />
```
### With wallet requirement
```tsx theme={null}
export function WalletProtectedRoute({ children }: { children: ReactNode }) {
const { user, isAuthenticated, ready } = useMoonKey();
if (!ready) {
return
Loading...
;
}
if (!isAuthenticated) {
return ;
}
if (!user?.wallet) {
return ;
}
return <>{children}>;
}
```
## Key concepts
* **Authentication check** - Verifies user is logged in
* **Loading state** - Shows loading while SDK initializes
* **Automatic redirect** - Sends unauthenticated users to login
* **Preserve location** - Can remember where user tried to go
* **Flexible protection** - Can add additional requirements (roles, wallets, etc.)
## Important notes
Always check the `ready` state before checking `isAuthenticated` to avoid redirecting users while MoonKey is still initializing.
Use `replace` prop in `Navigate` to prevent users from going back to protected routes using the browser back button.
## Next steps
Build your login page
Create protected dashboard
# Solana Program Interaction
Source: https://docs.streambird.io/recipes/wallets/solana-program-interaction
Interact with Solana programs using the embedded wallet and the Solana web3.js library.
## Implementation
```tsx theme={null}
import { useSendTransaction, useWallet } from '@moon-key/react-auth/solana';
import {
Connection,
Transaction,
PublicKey,
SystemProgram,
LAMPORTS_PER_SOL
} from '@solana/web3.js';
export default function SolanaProgram() {
const { sendTransaction } = useSendTransaction();
const { wallet } = useWallet();
const connection = new Connection('https://api.mainnet-beta.solana.com');
const handleProgramInteraction = async () => {
if (!wallet) return;
try {
// Example: Create an account
const newAccount = PublicKey.unique();
const transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: newAccount,
lamports: 0.001 * LAMPORTS_PER_SOL,
space: 0,
programId: SystemProgram.programId
})
);
const signature = await sendTransaction({
transaction,
connection
});
console.log('Program interaction successful:', signature);
} catch (error) {
console.error('Program interaction failed:', error);
}
};
return (
Solana Program Interaction
);
}
```
## Key concepts
* **Connection** - Connects to Solana RPC endpoint
* **Transaction** - Builds transaction with instructions
* **Program interaction** - Calls Solana program instructions
* **Wallet integration** - Uses embedded wallet's public key
## Common patterns
### SPL Token Transfer
```tsx theme={null}
import { getAssociatedTokenAddress, createTransferInstruction } from '@solana/spl-token';
const handleTokenTransfer = async () => {
const tokenMint = new PublicKey('YOUR_TOKEN_MINT');
const recipient = new PublicKey('RECIPIENT_ADDRESS');
const senderAta = await getAssociatedTokenAddress(tokenMint, wallet.publicKey);
const recipientAta = await getAssociatedTokenAddress(tokenMint, recipient);
const transaction = new Transaction().add(
createTransferInstruction(
senderAta,
recipientAta,
wallet.publicKey,
100 * Math.pow(10, 6) // Amount with decimals
)
);
await sendTransaction({ transaction, connection });
};
```
### Custom Program Instruction
```tsx theme={null}
import { TransactionInstruction } from '@solana/web3.js';
const handleCustomInstruction = async () => {
const programId = new PublicKey('YOUR_PROGRAM_ID');
const instruction = new TransactionInstruction({
keys: [
{ pubkey: wallet.publicKey, isSigner: true, isWritable: true },
// Add other account keys as needed
],
programId,
data: Buffer.from([/* instruction data */])
});
const transaction = new Transaction().add(instruction);
await sendTransaction({ transaction, connection });
};
```
## Important notes
Make sure you're using the correct RPC endpoint for your environment (mainnet, devnet, or testnet).
Always verify that the wallet has sufficient SOL to pay for transaction fees and any required lamports for the operation.
## Next steps
Learn more about Solana transactions
Deep dive into Solana web3.js
# Wallet Connection Status Indicator
Source: https://docs.streambird.io/recipes/wallets/wallet-connection-status-indicator
Display the user's wallet connection status with a visual indicator, showing wallet address and connection state.
## Implementation
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
export default function WalletStatus() {
const { user, isAuthenticated, ready } = useMoonKey();
if (!ready) {
return
);
}
```
## Key features
* **Connection state** - Shows connecting, connected, or disconnected status
* **Address formatting** - Truncates long addresses for better display
* **Visual indicator** - Colored dot shows connection status at a glance
* **Additional info** - Can display email or other user information
* **Copy functionality** - Easy to add clipboard copy feature
* **Extensible** - Can be expanded with menus, dropdowns, etc.
## Important notes
The status indicator automatically updates when the user logs in or out, thanks to MoonKey's reactive hooks.
Place the wallet status in your app's header or navigation bar for constant visibility.
## Next steps
Build a complete dashboard
More styling examples
# Chain Support
Source: https://docs.streambird.io/wallet-as-a-service/chain-support
MoonKey provides comprehensive wallet infrastructure for two major blockchain ecosystems: Ethereum and Solana. These chains receive full native support with complete functionality across all MoonKey features.
## Fully supported chains
MoonKey offers complete, production-ready support for the following blockchain ecosystems:
Full support for Ethereum and all EVM-compatible networks including Base, Arbitrum, Polygon, Optimism, and more.
Complete support for Solana and all SVM-compatible networks including mainnet, devnet, and testnet.
## What "fully supported" means
Chains with full MoonKey support include:
### Complete wallet functionality
* Create and manage embedded wallets
* Automatic wallet creation on user login
* Manual wallet creation when needed
* Private key export for maximum portability
### Transaction capabilities
* Sign messages for authentication and verification
* Sign transactions without broadcasting
* Send transactions with automatic broadcasting
* Full support for smart contract interactions
### User experience features
* Pre-built, customizable UI components
* Headless APIs for custom interfaces
* Real-time transaction status
* Clear error messages and handling
### Developer tools
* React SDK with TypeScript support
* REST API for server-side operations
* Comprehensive documentation
* Code examples and guides
## Ethereum ecosystem
MoonKey provides native support for Ethereum and the entire EVM ecosystem:
### Mainnet networks
* **Ethereum** - Ethereum mainnet
* **Base** - Coinbase's L2 solution
* **Arbitrum** - Optimistic rollup L2
* **Optimism** - Another optimistic rollup
* **Polygon** - Fast and low-cost EVM sidechain
* **And more** - Any EVM-compatible network
### Testnets
* **Sepolia** - Ethereum testnet
* **Base Sepolia** - Base testnet
* **Arbitrum Sepolia** - Arbitrum testnet
* **Other testnets** - All major EVM testnets
### Configuration
Configure EVM networks in your MoonKey provider:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
import { base, arbitrum, polygon } from 'viem/chains';
{children}
```
Learn more about [configuring EVM networks](/get-started/frontend-sdks/react/advance/configure-evm-networks).
## Solana ecosystem
MoonKey offers complete support for Solana and SVM-compatible networks:
### Networks
* **Mainnet Beta** - Solana production network
* **Devnet** - Development and testing network
* **Testnet** - Public testing network
* **Custom SVM** - Other SVM-compatible chains
### Configuration
Configure Solana networks with custom RPC endpoints:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
Learn more about [configuring Solana networks](/get-started/frontend-sdks/react/advance/configure-solana-networks).
## Features by chain
Both Ethereum and Solana ecosystems receive identical feature support:
| Feature | Ethereum/EVM | Solana/SVM |
| ------------------ | ------------ | ---------- |
| Embedded wallets | ✓ | ✓ |
| Automatic creation | ✓ | ✓ |
| Sign messages | ✓ | ✓ |
| Sign transactions | ✓ | ✓ |
| Send transactions | ✓ | ✓ |
| Export private key | ✓ | ✓ |
| UI components | ✓ | ✓ |
| REST API | ✓ | ✓ |
| Custom RPC | ✓ | ✓ |
## Custom networks
Both Ethereum and Solana support custom networks:
### Custom EVM networks
Add any EVM-compatible network using `viem`'s `defineChain`:
```tsx theme={null}
import { defineChain } from 'viem';
const customChain = defineChain({
id: 12345,
name: 'Custom Chain',
network: 'custom',
nativeCurrency: {
decimals: 18,
name: 'Custom Token',
symbol: 'CTK',
},
rpcUrls: {
default: {
http: ['https://custom-rpc-url.com'],
},
},
blockExplorers: {
default: {
name: 'Explorer',
url: 'https://explorer.custom-chain.com',
},
},
});
```
### Custom SVM networks
Connect to custom SVM-compatible networks with custom RPC endpoints:
```tsx theme={null}
import { Connection } from '@solana/web3.js';
const connection = new Connection('https://custom-svm-rpc.com');
```
## Choosing your blockchain
When building with MoonKey, consider these factors:
* **DeFi applications** - Largest DeFi ecosystem
* **NFT marketplaces** - Most NFT activity
* **Smart contracts** - Mature tooling and libraries
* **Institutional adoption** - Widely recognized
* **L2 solutions** - Fast and cheap with Base, Arbitrum, etc.
* **High performance** - Fast transaction speeds
* **Low costs** - Minimal transaction fees
* **Gaming** - Real-time blockchain gaming
* **NFT minting** - Fast and affordable minting
* **Payment applications** - Micropayments and instant transfers
## Future chain support
MoonKey's architecture is designed to support additional blockchains in the future. We're continuously evaluating new chains and ecosystems to add to our platform.
Interested in support for a specific blockchain? [Contact us](mailto:support@moonkey.fun) to discuss your requirements.
## Best practices
* Develop on testnets (Sepolia, Devnet)
* Test thoroughly before mainnet deployment
* Use separate API keys for test and production
* Use reliable RPC providers (Alchemy, QuickNode, etc.)
* Consider rate limits for free RPC endpoints
* Set up fallback RPC URLs for reliability
* Allow users to switch chains easily
* Show current chain clearly in your UI
* Handle transactions for the correct chain
* Test wallets on both Ethereum and Solana
* Verify proper chain isolation
* Test network switching flows
## Getting started
Set up Ethereum and EVM-compatible chains
Set up Solana and SVM-compatible networks
Build your first multi-chain wallet app
Manage wallets via REST API
# Create a Wallet
Source: https://docs.streambird.io/wallet-as-a-service/embedded-wallets/create-a-wallet/overview
MoonKey enables you to create embedded wallets for your users programmatically. These self-custodial wallets are owned by the authenticated user and secured using distributed key sharding.
You can create wallets either automatically when users authenticate, or manually when needed using the SDK.
MoonKey supports automatically creating embedded wallets for your users when they log in to your app. View the [automatic wallet creation guide](/get-started/frontend-sdks/react/advance/automatic-wallet-creation) to learn more.
## React SDK
To create an Ethereum wallet with the React SDK, use the `createWallet` method from the `useCreateWallet` hook:
### Usage
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreate = async () => {
try {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
} catch (error) {
console.error('Failed to create wallet:', error);
}
};
return ;
}
```
### Returns
A `Promise` for the wallet object containing the created wallet's details.
The wallet's Ethereum address (e.g., `0x1234...5678`)
The chain type, always `'ethereum'` for Ethereum wallets
The wallet client instance for performing operations
### Callbacks
You can optionally register `onSuccess` or `onError` callbacks on the `useCreateWallet` hook:
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
const { createWallet } = useCreateWallet({
onSuccess: (wallet) => {
console.log('Wallet created successfully:', wallet.address);
// Navigate to wallet setup or show success message
},
onError: (error) => {
console.error('Failed to create wallet:', error);
// Show error message to user
}
});
```
Optional callback to run after a user successfully creates a wallet.
Optional callback to run if there is an error during wallet creation.
### Complete example
```tsx theme={null}
'use client';
import { useCreateWallet, useMoonKey } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function WalletCreation() {
const { user, isAuthenticated } = useMoonKey();
const [isCreating, setIsCreating] = useState(false);
const { createWallet } = useCreateWallet({
onSuccess: (wallet) => {
console.log('Wallet created:', wallet.address);
alert(`Wallet created: ${wallet.address}`);
},
onError: (error) => {
console.error('Error:', error);
alert('Failed to create wallet');
}
});
if (!isAuthenticated) {
return
);
}
```
To create a Solana wallet with the React SDK, use the `createWallet` method from the `useCreateWallet` hook:
### Usage
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/solana';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreate = async () => {
try {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
} catch (error) {
console.error('Failed to create wallet:', error);
}
};
return ;
}
```
### Returns
A `Promise` for the wallet object containing the created wallet's details.
The wallet's Solana address (base58 encoded)
The chain type, always `'solana'` for Solana wallets
The wallet's public key as a Solana `PublicKey` instance
### Callbacks
You can optionally register `onSuccess` or `onError` callbacks on the `useCreateWallet` hook:
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/solana';
const { createWallet } = useCreateWallet({
onSuccess: (wallet) => {
console.log('Wallet created successfully:', wallet.address);
// Navigate to wallet setup or show success message
},
onError: (error) => {
console.error('Failed to create wallet:', error);
// Show error message to user
}
});
```
Optional callback to run after a user successfully creates a wallet.
Optional callback to run if there is an error during wallet creation.
### Complete example
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/solana';
import { useState } from 'react';
export default function WalletCreation() {
const { user, isAuthenticated } = useMoonKey();
const [isCreating, setIsCreating] = useState(false);
const { createWallet } = useCreateWallet({
onSuccess: (wallet) => {
console.log('Wallet created:', wallet.address);
alert(`Wallet created: ${wallet.address}`);
},
onError: (error) => {
console.error('Error:', error);
alert('Failed to create wallet');
}
});
if (!isAuthenticated) {
return
);
}
```
## REST API
To create a new wallet for a user via the REST API, make a `POST` request to:
```bash theme={null}
POST https://api.moonkey.fun/v1/wallets/create
```
You must specify the user ID as the owner of the wallet. You can obtain a user ID by first [creating a user](/api-reference/users/create-user) before creating the wallet.
### Authentication
Include your Secret API Key in the `Authorization` header:
```bash theme={null}
Authorization: Bearer sk_test_YOUR_SECRET_KEY
```
### Request body
The type of wallet to create.
* `'ethereum'` - Create an Ethereum/EVM wallet
* `'solana'` - Create a Solana/SVM wallet
The ID of the user who will own this wallet (e.g., `user_2Cu2uVhYy0OVgRcO913OsqIVaPI`).
### Response
Unique ID of the created wallet. This is the primary identifier for the wallet.
The ID of your MoonKey application.
The ID of the user who owns this wallet.
The wallet's public address (Ethereum address or Solana public key).
The type of wallet created.
Whether the wallet has been verified. Always `true` for newly created wallets.
Whether this is the user's default wallet for this chain type.
Unix timestamp (in seconds) when the wallet was created.
Unix timestamp (in seconds) when the wallet was last updated.
### Example request
```bash cURL theme={null}
curl --request POST \
--url https://api.moonkey.fun/v1/wallets/create \
--header 'Authorization: Bearer sk_test_YOUR_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"wallet_type": "ethereum",
"user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI"
}'
```
```javascript Node.js theme={null}
const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_type: 'ethereum',
user_id: 'user_2Cu2uVhYy0OVgRcO913OsqIVaPI'
})
});
const wallet = await response.json();
console.log('Wallet created:', wallet.public_address);
```
```python Python theme={null}
import requests
response = requests.post(
'https://api.moonkey.fun/v1/wallets/create',
headers={
'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
'Content-Type': 'application/json'
},
json={
'wallet_type': 'ethereum',
'user_id': 'user_2Cu2uVhYy0OVgRcO913OsqIVaPI'
}
)
wallet = response.json()
print(f"Wallet created: {wallet['public_address']}")
```
### Example response
```json theme={null}
{
"id": "wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK",
"app_id": "app_24ydphdixx2ydhF0E5WUFUKWNqi",
"user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI",
"public_address": "0xf1347fd847f19c250b4c9678ecaa27b0f6ce8804",
"wallet_type": "ethereum",
"verified": true,
"is_default": true,
"is_read_only": false,
"is_imported": false,
"created_at": 1659638371,
"updated_at": 1659638371
}
```
## Automatic vs manual wallet creation
MoonKey provides two approaches to wallet creation:
### Automatic creation
Configure MoonKey to automatically create wallets when users authenticate:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
**Best for:**
* Apps where all users need wallets
* Gaming applications
* NFT marketplaces
* DeFi applications
* Simplest onboarding experience
Learn more about [automatic wallet creation](/get-started/frontend-sdks/react/advance/automatic-wallet-creation).
### Manual creation
Use the `useCreateWallet` hook to create wallets programmatically:
```tsx theme={null}
const { createWallet } = useCreateWallet();
await createWallet();
```
**Best for:**
* Apps with optional blockchain features
* Progressive onboarding flows
* Users who opt-in to web3 features
* When you want to delay wallet creation for UX reasons
## Checking if a user has a wallet
Before creating a wallet, check if the user already has one:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function MyComponent() {
const { user } = useMoonKey();
if (user?.wallet) {
// User already has a wallet
console.log('Wallet address:', user.wallet.address);
} else {
// User doesn't have a wallet yet
// Show create wallet button
}
}
```
## Common use cases
### Onboarding flow with wallet creation
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function OnboardingFlow() {
const { user, isAuthenticated } = useMoonKey();
const { createWallet } = useCreateWallet();
const [step, setStep] = useState<'welcome' | 'creating' | 'complete'>('welcome');
if (!isAuthenticated) {
return
);
}
```
### Optional wallet creation
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
export default function OptionalWalletFeature() {
const { user } = useMoonKey();
const { createWallet } = useCreateWallet();
if (user?.wallet) {
return ;
}
return (
Unlock Blockchain Features
Create a wallet to access NFTs, tokens, and more
);
}
```
### Server-side wallet creation
```typescript theme={null}
// On your backend
async function createWalletForUser(userId: string) {
const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet_type: 'ethereum',
user_id: userId
})
});
if (!response.ok) {
throw new Error('Failed to create wallet');
}
const wallet = await response.json();
return wallet;
}
// Example usage in an API endpoint
app.post('/api/setup-wallet', async (req, res) => {
const { userId } = req.body;
try {
const wallet = await createWalletForUser(userId);
res.json({ success: true, wallet });
} catch (error) {
res.status(500).json({ error: 'Failed to create wallet' });
}
});
```
## Best practices
Always check if a user already has a wallet before attempting to create one:
```tsx theme={null}
if (user?.wallet) {
console.log('User already has a wallet');
return;
}
await createWallet();
```
Provide clear feedback when wallet creation fails:
```tsx theme={null}
try {
await createWallet();
toast.success('Wallet created successfully!');
} catch (error) {
toast.error('Failed to create wallet. Please try again.');
console.error('Wallet creation error:', error);
}
```
Display loading indicators during wallet creation:
```tsx theme={null}
const [isCreating, setIsCreating] = useState(false);
const handleCreate = async () => {
setIsCreating(true);
try {
await createWallet();
} finally {
setIsCreating(false);
}
};
```
Explain what a wallet is and why they're creating one:
```tsx theme={null}
What is a wallet?
A wallet lets you own digital assets like NFTs and tokens.
Your wallet is self-custodial - only you can access it.
```
Handle navigation or UI updates after successful wallet creation:
```tsx theme={null}
const { createWallet } = useCreateWallet({
onSuccess: (wallet) => {
// Log analytics
analytics.track('wallet_created', { address: wallet.address });
// Navigate to next step
router.push('/wallet/funded');
}
});
```
Never expose your Secret API Key in client-side code:
```typescript theme={null}
// ❌ WRONG - Never do this
const apiKey = 'sk_live_...';
// ✅ CORRECT - Use environment variables on server
const apiKey = process.env.MOONKEY_SECRET_KEY;
```
## Error handling
Common errors and how to handle them:
```tsx theme={null}
try {
await createWallet();
} catch (error) {
if (error.message.includes('already has a wallet')) {
toast.info('You already have a wallet');
} else if (error.message.includes('not authenticated')) {
toast.error('Please sign in first');
} else if (error.message.includes('network')) {
toast.error('Network error. Please check your connection.');
} else {
toast.error('Failed to create wallet. Please try again.');
}
}
```
## Next steps
Configure automatic wallet creation on login
Learn how to send transactions with the wallet
Sign messages for authentication
Allow users to export their private key
# Export a Wallet
Source: https://docs.streambird.io/wallet-as-a-service/embedded-wallets/export-a-wallet
**MoonKey enables your users to export the private key for their embedded wallet**. This allows them to use their embedded wallet address with another wallet client, such as MetaMask or Phantom, or to back up their wallet for recovery purposes.
Exporting private keys is a sensitive operation. Once exported, the private key gives complete control over the wallet and all its assets. Users should be educated about proper key storage and security practices.
## Overview
When users export their wallet, they receive:
* **Full private key** for their embedded wallet
* **Complete control** over their wallet, even outside your application
* **Ability to import** into other wallet clients (MetaMask, Phantom, etc.)
* **Self-custody** of their assets
When your user exports their embedded wallet, their private key is assembled in a secure environment separate from your app. This means **neither you nor MoonKey can ever access your user's full private key**. Your user is the only party that can ever access it.
## React SDK
MoonKey provides a simple hook-based API for wallet export in React applications.
To enable wallet export for Ethereum wallets, use the `useExportKey` hook from `@moon-key/react-auth/ethereum`:
### Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/ethereum';
function ExportWalletButton() {
const { user, isAuthenticated, ready } = useMoonKey();
const { exportKey } = useExportKey();
const handleExport = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
await exportKey({
wallet: user.wallet
});
console.log('Export initiated');
} catch (error) {
console.error('Export failed:', error);
}
};
// Check if user has a wallet
const hasWallet = user?.wallet !== undefined;
return (
);
}
```
### With Custom UI Configuration
You can customize the export modal's appearance by passing a `uiConfig` object:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/ethereum';
function ExportWalletButton() {
const { user } = useMoonKey();
const { exportKey } = useExportKey();
const handleExport = async () => {
if (!user?.wallet) return;
try {
await exportKey({
wallet: user.wallet,
uiConfig: {
title: 'Backup Your Wallet',
privateKeyDescription: 'Your private key provides complete control over this wallet. Store it securely.',
neverShareDescription: 'Never share your private key with anyone, including MoonKey support.',
loadingExportOptionsText: 'Preparing secure export...'
}
});
} catch (error) {
console.error('Export cancelled or failed:', error);
}
};
return (
);
}
```
### Complete Example
Here's a complete example with proper validation and error handling:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function WalletExportSection() {
const { user, isAuthenticated, ready } = useMoonKey();
const { exportKey } = useExportKey();
const [isExporting, setIsExporting] = useState(false);
const [error, setError] = useState(null);
const handleExport = async () => {
// Clear previous errors
setError(null);
// Validate user has a wallet
if (!user?.wallet) {
setError('No wallet found. Please create a wallet first.');
return;
}
// Confirm user intent
const confirmed = window.confirm(
'WARNING: Your private key gives complete control of your wallet. ' +
'Make sure you store it in a secure location. Continue?'
);
if (!confirmed) return;
setIsExporting(true);
try {
await exportKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Private Key',
privateKeyDescription: 'This key provides full access to your wallet and funds. Keep it safe and offline.',
neverShareDescription: 'Never share this key. MoonKey will never ask for it.',
loadingExportOptionsText: 'Loading secure export options...'
}
});
alert('Private key export initiated. Follow the instructions on screen.');
} catch (err: any) {
console.error('Export error:', err);
setError(err.message || 'Failed to export wallet. Please try again.');
} finally {
setIsExporting(false);
}
};
// Loading state
if (!ready) {
return
Loading...
;
}
// Not authenticated
if (!isAuthenticated || !user) {
return (
Please sign in to export your wallet
);
}
// No wallet
if (!user.wallet) {
return (
No wallet found. Create a wallet to enable export.
);
}
return (
Export Wallet
⚠️ Exporting your private key gives you complete control of your wallet,
but also complete responsibility for its security.
{error && (
{error}
)}
What you'll receive:
Your wallet's private key
Instructions for importing into MetaMask
Security best practices
);
}
```
To enable wallet export for Solana wallets, use the `useExportKey` hook from `@moon-key/react-auth/solana`:
### Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/solana';
function ExportWalletButton() {
const { user, isAuthenticated, ready } = useMoonKey();
const { exportKey } = useExportKey();
const handleExport = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
await exportKey({
wallet: user.wallet
});
console.log('Export initiated');
} catch (error) {
console.error('Export failed:', error);
}
};
// Check if user has a wallet
const hasWallet = user?.wallet !== undefined;
return (
);
}
```
### With Custom UI Configuration
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/solana';
function ExportWalletButton() {
const { user } = useMoonKey();
const { exportKey } = useExportKey();
const handleExport = async () => {
if (!user?.wallet) return;
try {
await exportKey({
wallet: user.wallet,
uiConfig: {
title: 'Backup Your Solana Wallet',
privateKeyDescription: 'Your private key provides complete control over this Solana wallet. Store it securely.',
neverShareDescription: 'Never share your private key with anyone, including MoonKey support.',
loadingExportOptionsText: 'Preparing secure export...'
}
});
} catch (error) {
console.error('Export cancelled or failed:', error);
}
};
return (
);
}
```
### Complete Example
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/solana';
import { useState } from 'react';
export default function SolanaWalletExport() {
const { user, isAuthenticated, ready } = useMoonKey();
const { exportKey } = useExportKey();
const [isExporting, setIsExporting] = useState(false);
const handleExport = async () => {
if (!user?.wallet) return;
const confirmed = window.confirm(
'Your private key gives complete control of your Solana wallet. ' +
'Store it securely. Continue?'
);
if (!confirmed) return;
setIsExporting(true);
try {
await exportKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Solana Wallet',
privateKeyDescription: 'Use this key to import your wallet into Phantom or other Solana wallets.',
neverShareDescription: 'Keep this key private. Anyone with it can access your funds.',
}
});
} catch (error) {
console.error('Export failed:', error);
} finally {
setIsExporting(false);
}
};
if (!ready || !isAuthenticated || !user?.wallet) {
return null;
}
return (
);
}
```
## UI Configuration Options
The `exportKey` method accepts an optional `uiConfig` object to customize the export modal:
Custom title displayed at the top of the export modal.
**Example:**
```tsx theme={null}
uiConfig: {
title: 'Backup Your Wallet'
}
```
Description explaining what the private key is and its importance.
**Example:**
```tsx theme={null}
uiConfig: {
privateKeyDescription: 'Your private key gives full control of your wallet. Store it securely offline.'
}
```
Warning message emphasizing that the key should never be shared.
**Example:**
```tsx theme={null}
uiConfig: {
neverShareDescription: 'Never share your private key with anyone, including MoonKey support.'
}
```
Loading text displayed while the export modal is initializing.
**Example:**
```tsx theme={null}
uiConfig: {
loadingExportOptionsText: 'Preparing secure export...'
}
```
Description for HD (Hierarchical Deterministic) wallet exports.
**Example:**
```tsx theme={null}
uiConfig: {
hdDescription: 'Your seed phrase can restore all wallets derived from this account.'
}
```
URL to your application's logo to display in the export modal.
**Example:**
```tsx theme={null}
uiConfig: {
logoUrl: 'https://myapp.com/logo.png'
}
```
## Common Use Cases
### Wallet Backup
Allow users to back up their wallet for recovery:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportKey } from '@moon-key/react-auth/ethereum';
function BackupWalletButton() {
const { user } = useMoonKey();
const { exportKey } = useExportKey();
const handleBackup = async () => {
if (!user?.wallet) return;
await exportKey({
wallet: user.wallet,
uiConfig: {
title: 'Backup Your Wallet',
privateKeyDescription: 'Save your private key in a secure location for wallet recovery.',
neverShareDescription: 'Store this key offline. Never share it with anyone.'
}
});
};
return (
);
}
```
## Best Practices
Use confirmation dialogs before exporting to ensure users understand the implications:
```tsx theme={null}
const confirmed = window.confirm(
'WARNING: Your private key gives complete control of your wallet. ' +
'Make sure you store it securely. Continue?'
);
if (!confirmed) return;
```
Provide clear warnings and best practices:
```tsx theme={null}
Security Best Practices:
Store your private key in a password manager or offline
Never share your key with anyone
Be cautious of phishing attempts
Consider using a hardware wallet for large amounts
```
Always check that a wallet exists before attempting export:
```tsx theme={null}
if (!user?.wallet) {
setError('No wallet found. Create a wallet first.');
return;
}
```
Provide clear error messages and recovery options:
```tsx theme={null}
try {
await exportKey({ wallet: user.wallet });
} catch (error: any) {
if (error.code === 'USER_CANCELLED') {
console.log('User cancelled export');
} else {
setError('Export failed. Please try again or contact support.');
}
}
```
Your application should never log, store, or transmit exported private keys:
```tsx theme={null}
// ❌ NEVER DO THIS
const key = await exportKey({ wallet: user.wallet });
console.log('Private key:', key); // NEVER LOG KEYS
localStorage.setItem('key', key); // NEVER STORE KEYS
await fetch('/api/save-key', { body: key }); // NEVER SEND KEYS
// ✅ CORRECT - Just trigger the export UI
await exportKey({ wallet: user.wallet });
// MoonKey handles key display securely
```
Use `uiConfig` to match your application's branding:
```tsx theme={null}
uiConfig: {
title: 'Backup Your Wallet',
logoUrl: 'https://myapp.com/logo.png',
privateKeyDescription: 'Custom message for your users...'
}
```
## Security Considerations
**Private key export is a high-risk operation.** Ensure your users understand:
* Anyone with the private key has **complete control** of the wallet
* Lost private keys **cannot be recovered**
* Stolen private keys can result in **permanent loss of funds**
* Private keys should **never be shared** with anyone
### What happens during export
1. **User initiates export** through your application
2. **MoonKey opens secure modal** in an isolated environment
3. **Private key is assembled** client-side in a separate origin
4. **User copies key** directly from the secure modal
5. **No logging or storage** occurs in your application or MoonKey's servers
### Security features
* **Isolated origin**: Keys are displayed in a separate security context
* **No server access**: Neither your app nor MoonKey can access the full key
* **User-only access**: Only the user can view their exported key
* **No automatic storage**: Keys are never automatically saved anywhere
* **Ephemeral display**: Keys are only shown during the export flow
## Troubleshooting
Check these conditions:
* SDK is ready (`ready === true`)
* User is authenticated (`isAuthenticated === true`)
* User has a wallet (`user?.wallet !== undefined`)
```tsx theme={null}
console.log('SDK ready:', ready);
console.log('Is authenticated:', isAuthenticated);
console.log('Has wallet:', !!user?.wallet);
```
Common causes and solutions:
* **User cancelled**: User closed the modal - this is expected behavior
* **No wallet found**: Ensure wallet creation is complete before export
* **Network error**: Check internet connection and try again
* **SDK not ready**: Wait for `ready` state before calling export
Verify:
* Correct import path for your chain type
* `useExportKey` is called within a component
* Component is wrapped by `MoonKeyProvider`
* No popup blockers are interfering
Ensure:
* Copying the complete private key (including `0x` prefix for Ethereum)
* Using correct import method in MetaMask (Import Account → Private Key)
* Private key format matches wallet type (hex for Ethereum, base58 for Solana)
## Importing Exported Wallets
After exporting, users can import their wallet into various wallet clients:
### MetaMask (Ethereum)
1. Open MetaMask
2. Click account icon → "Import Account"
3. Select "Private Key" option
4. Paste the exported private key (with `0x` prefix)
5. Click "Import"
### Phantom (Solana)
1. Open Phantom
2. Settings → Add / Connect Wallet
3. Select "Import Private Key"
4. Paste the exported private key
5. Click "Import"
### Other Wallet Clients
Most wallet clients support private key import through similar flows. Consult the specific wallet's documentation for detailed instructions.
## Related Documentation
Detailed UI component documentation and customization
Learn how to access user wallets
Create embedded wallets for users
Complete guide to MoonKey's wallet infrastructure
## Next Steps
After implementing wallet export:
1. **Test the flow** with test wallets on testnet
2. **Educate your users** about private key security
3. **Provide support documentation** for common issues
4. **Consider adding** recovery mechanisms for users who lose keys
5. **Monitor usage** to understand how users interact with export
For more details on customizing the export UI, see the [Export Key UI Component](/authentication/user-authentication/ui-components/export-key) documentation.
# Get User Wallets
Source: https://docs.streambird.io/wallet-as-a-service/embedded-wallets/get-a-wallet/get-user-connected-wallets
MoonKey makes it easy to access a user's embedded wallet so you can help them take onchain actions. Every authenticated user with an embedded wallet has access to their wallet through the SDK.
MoonKey currently supports embedded wallets only. Each user can have one embedded wallet per chain type (Ethereum or Solana).
**Import paths:**
* `useMoonKey` comes from `@moon-key/react-auth` (the base package)
* `useWallets` comes from `@moon-key/react-auth/ethereum` for Ethereum or `@moon-key/react-auth/solana` for Solana
## React SDK
### Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function MyComponent() {
const { user, ready } = useMoonKey();
if (!ready) {
return
);
}
```
### Wallet properties
The `user.wallet` object contains the following properties:
The wallet's Ethereum address (e.g., `0x1234...5678`)
The chain type, always `'ethereum'` for Ethereum wallets
The wallet client instance from `viem` for performing operations
### Alternative: Using useWallets
You can also access Ethereum wallets using the `useWallets` hook from `@moon-key/react-auth/ethereum`, which returns an array of all wallets:
```tsx theme={null}
import { useWallets } from '@moon-key/react-auth/ethereum';
function MyComponent() {
const { wallets, ready } = useWallets();
if (!ready) {
return
Loading...
;
}
if (wallets.length === 0) {
return
No wallets found
;
}
// Get the first (and typically only) wallet
const wallet = wallets[0];
console.log('Wallet address:', wallet.address);
return (
{wallets.map((wallet) => (
Wallet: {wallet.address}
))}
);
}
```
## Waiting for wallets to be ready
When your page loads in the user's browser, the MoonKey SDK determines what wallet the user has by loading the secure wallet infrastructure. This process happens asynchronously.
To determine if MoonKey has fully loaded the user's wallet information, use the `ready` boolean returned by the `useMoonKey` or `useWallets` hooks.
The `ready` flag will be:
* `false` while MoonKey is loading wallet information
* `true` once MoonKey has determined the current wallet state
### Example with ready state
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function WalletDisplay() {
const { user, ready, isAuthenticated } = useMoonKey();
// SDK is still loading
if (!ready) {
return (
Loading wallet...
);
}
// User is not authenticated
if (!isAuthenticated || !user) {
return
Please sign in to view your wallet
;
}
// User doesn't have a wallet yet
if (!user.wallet) {
return (
You don't have a wallet yet.
);
}
// Wallet is ready to use
return (
Your Wallet
Address: {user.wallet.address}
);
}
```
## useMoonKey vs useWallets
Both hooks provide access to wallet information, but they serve slightly different purposes and have different import paths:
### useMoonKey
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
const { user, ready, isAuthenticated } = useMoonKey();
const wallet = user?.wallet;
```
**Import from:** `@moon-key/react-auth` (base package)
**Use when:**
* You need access to the full user object
* You want authentication state (`isAuthenticated`)
* You need user profile information
* You're working with a single wallet per user
**Returns:**
* Complete user object with all linked accounts
* Authentication state
* Wallet as part of the user object
### useWallets
```tsx theme={null}
import { useWallets } from '@moon-key/react-auth/ethereum';
const { wallets, ready } = useWallets();
const wallet = wallets[0];
```
**Import from:** `@moon-key/react-auth/ethereum` (for Ethereum wallets)
```tsx theme={null}
import { useWallets } from '@moon-key/react-auth/solana';
const { wallets, ready } = useWallets();
const wallet = wallets[0];
```
**Import from:** `@moon-key/react-auth/solana` (for Solana wallets)
**Use when:**
* You only need wallet information
* You want to work with an array of wallets
* You prefer a wallet-centric API
* Future-proofing for multiple wallets per user
**Returns:**
* Array of wallet objects (chain-specific)
* Ready state for wallet loading
For most use cases, `useMoonKey` is recommended as it provides both user and wallet information in a single hook.
## Common patterns
### Checking for wallet existence
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function WalletRequired() {
const { user, ready } = useMoonKey();
const hasWallet = user?.wallet !== undefined;
if (!ready) {
return ;
}
if (!hasWallet) {
return (
Wallet Required
You need a wallet to access this feature
);
}
return ;
}
```
### Getting wallet for transactions
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
function SendEthButton() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
const result = await sendTransaction({
transaction: {
to: '0xRecipient...',
value: '0x38d7ea4c68000'
},
wallet: user.wallet
});
console.log('Transaction hash:', result.transactionHash);
};
return (
);
}
```
### Displaying wallet information
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
function WalletInfo() {
const { user, ready } = useMoonKey();
if (!ready) return
Loading...
;
if (!user?.wallet) return
No wallet
;
const { address, chainType } = user.wallet;
// Format address for display
const shortAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
return (
{chainType}
{shortAddress}
);
}
```
### Finding a specific wallet (Ethereum)
```tsx theme={null}
import { useWallets } from '@moon-key/react-auth/ethereum';
function FindWalletByAddress({ targetAddress }: { targetAddress: string }) {
const { wallets, ready } = useWallets();
if (!ready) return
);
}
```
## Best practices
Wait for the SDK to be ready before accessing wallet information:
```tsx theme={null}
const { user, ready } = useMoonKey();
if (!ready) {
return ;
}
// Now safe to access user.wallet
```
Not all users will have wallets. Provide clear next steps:
```tsx theme={null}
if (!user?.wallet) {
return (
You don't have a wallet yet
);
}
```
Always pass the wallet object to transaction methods:
```tsx theme={null}
await sendTransaction({
transaction: txData,
wallet: user.wallet // Required
});
```
TypeScript helps catch wallet-related errors:
```tsx theme={null}
const wallet: Wallet | undefined = user?.wallet;
if (wallet) {
// TypeScript knows wallet exists here
console.log(wallet.address);
}
```
Show shortened addresses in UI for better UX:
```tsx theme={null}
const formatAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};
{formatAddress(wallet.address)}
```
## Troubleshooting
If `user.wallet` is undefined:
* Check if the user is authenticated
* Verify the SDK is ready (`ready === true`)
* Confirm the user has created a wallet
* Check automatic wallet creation configuration
If `ready` stays false:
* Verify MoonKeyProvider is properly configured
* Check browser console for errors
* Ensure appId is correct
* Try refreshing the page
If wallet properties are inaccessible:
* Verify you're using the correct import path
* Check that you're using the right SDK for your chain (ethereum vs solana)
* Ensure the user object has loaded
# Configuration
Source: https://docs.streambird.io/wallet-as-a-service/funding/configure
To enable wallet funding for your users, you'll need to configure funding settings in the MoonKey Dashboard.
## Dashboard configuration
Visit the [MoonKey Dashboard](https://dashboard.moonkey.fun) and select your app from the **App Dropdown** in the sidebar. Then, navigate to the **Account Funding** page for your selected app.
## Enable funding methods
MoonKey currently supports card-based funding methods:
* **Debit/credit cards** - Visa, Mastercard, and other major card providers
* **Apple Pay** - For Safari and iOS users
* **Google Pay** - For Chrome and Android users
All card funding is processed through MoonPay.
Make sure you have MoonKey's authentication UI components integrated in your app, as they are required for funding flows to work properly.
## Set default chain and amount
Once you've enabled funding methods, configure the default settings for your users:
### Default chain
Select **Ethereum** as your blockchain and choose a default network:
* Ethereum Mainnet
* Base
* Polygon
* Arbitrum
* Optimism
* Other EVM networks
Users will fund their wallets with the native currency of the selected network (e.g., ETH on Ethereum Mainnet).
Select **Solana** as your blockchain.
Users will fund their wallets with SOL (Solana's native currency).
### Default amount
Set a default amount that users should fund their wallets with. Users can adjust this amount before confirming their purchase.
**Recommended amounts:**
* **Ethereum**: 0.01 - 0.1 ETH
* **Base/L2s**: 0.05 - 0.2 ETH (lower gas fees)
* **Solana**: 0.1 - 1 SOL
Choose an amount that makes sense for your application's typical transaction costs.
## Override in code
You can override dashboard defaults programmatically when calling the funding function:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWallet() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
// Override dashboard defaults
await fundWallet(selectedWallet.address, {
chain: 'eip155:1', // Ethereum mainnet
amount: '0.05' // Custom amount
});
};
return ;
}
```
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWallet() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
// Override dashboard defaults
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.5' // Custom amount
});
};
return ;
}
```
## Sandbox mode for testing
During development, you can enable sandbox mode to test funding flows without processing real transactions. This is useful for testing your integration before going live.
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWallet() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
// Enable sandbox mode for testing
await fundWallet(selectedWallet.address, {
chain: 'eip155:1',
amount: '0.05',
useSandbox: true // Default is false
});
};
return ;
}
```
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWallet() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
// Enable sandbox mode for testing
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.5',
useSandbox: true // Default is false
});
};
return ;
}
```
Sandbox mode allows you to test the complete funding flow without processing real payments. Always set `useSandbox: false` or omit it entirely for production environments.
## Best practices
* Consider typical transaction costs in your app
* Account for gas fees on the network
* Don't set amounts too low (users may not have enough for transactions)
* Don't set amounts too high (may discourage new users)
* Use Layer 2s (Base, Arbitrum, Optimism) for lower fees
* Match the network to where your contracts are deployed
* Consider user familiarity with different networks
* Test the funding flow end-to-end on mainnet
* Verify the default amount makes sense for your use case
* Ensure the funding modal displays correctly
## Next steps
Learn how to integrate card-based funding
Overview of MoonKey's funding features
# Ethereum
Source: https://docs.streambird.io/wallet-as-a-service/funding/fund-types/ethereum
With funding methods enabled for your app, MoonKey will prompt users to fund their wallets at two points in their experience:
1. **Manually** - When you call MoonKey's `fundWallet` method
2. **Automatically** - When the user attempts to send a transaction but has insufficient funds
You can configure the chain, asset, and amount that users should fund their wallets with directly in code.
## Manually invoking funding
Once you've enabled funding methods for your app in the Dashboard, use the `useFundWallet` hook to invoke the funding flow:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWalletButton() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:1',
amount: '0.1'
});
};
return ;
}
```
Once invoked, the `fundWallet` method will open a modal with funding options for the user. If only one funding method is enabled, MoonKey will navigate directly to that flow.
Purchases with third-party providers (like MoonPay) are not always instantaneous. There may be some time before the user completes their purchase and the funds are available in their wallet.
## Automatically invoking funding
When a user attempts to send a transaction but doesn't have sufficient funds, MoonKey will automatically show them an "Add funds" button in the transaction modal that enables them to invoke funding flows.
This helps users complete their intended actions without leaving your application.
## Setting funding parameters in code
You can override your Dashboard configuration by passing parameters to `fundWallet`:
### Basic parameters
The wallet address to fund.
The Ethereum chain to fund on. Use `'eip155:1'` for Ethereum mainnet, `'eip155:8453'` for Base, etc.
If not specified, defaults to the chain configured in your Dashboard.
The amount of the asset to fund as a decimal string (e.g., `'0.1'`).
If not specified, defaults to the amount configured in your Dashboard.
The asset to fund with:
* `'native-currency'` - The chain's native currency (ETH, etc.)
* `'USDC'` - USDC stablecoin
* `{erc20: '0x...'}` - Any ERC-20 token by contract address
Defaults to `'native-currency'`.
Testnets are not supported. Only use mainnet chain IDs.
## Examples
### Fund with ETH
Fund with the native currency (ETH):
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWithETH() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:8453', // Base
amount: '0.01' // Defaults to 'native-currency' (ETH)
});
};
return ;
}
```
### Fund with USDC
Fund with USDC stablecoin:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWithUSDC() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:8453', // Base
amount: '15',
asset: 'USDC'
});
};
return ;
}
```
### Fund with arbitrary ERC-20
Fund with any ERC-20 token:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWithCustomToken() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:8453', // Base
amount: '30',
asset: {
erc20: '0x0578d8A44db98B23BF096A382e016e29a5Ce0ffe' // Custom token
}
});
};
return ;
}
```
### Fund on different networks
Fund on various EVM networks:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function NetworkSelector() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const fundOnNetwork = async (chainId: string, chainName: string) => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: chainId,
amount: '0.05'
});
};
return (
);
}
```
## Common chain IDs
| Network | Chain ID |
| ----------------- | -------------- |
| Ethereum Mainnet | `eip155:1` |
| Base | `eip155:8453` |
| Polygon | `eip155:137` |
| Arbitrum One | `eip155:42161` |
| Optimism | `eip155:10` |
| Avalanche C-Chain | `eip155:43114` |
| BNB Smart Chain | `eip155:56` |
## Callbacks
To understand when users have gone through a funding flow, use the `onUserExited` callback with the `useFundWallet` hook:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
import { useRouter } from 'next/navigation';
function OnboardingWithFunding() {
const router = useRouter();
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance }) {
if (balance < 1000n) {
router.push('/insufficient-funds');
} else {
router.push('/dashboard');
}
}
});
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:1',
amount: '0.1'
});
};
return ;
}
```
### Callback parameters
The `onUserExited` callback receives an object with:
The wallet address that was funded.
The chain ID where funding occurred.
The funding method used (e.g., `'card'`).
The current balance of the wallet being funded.
## Onboarding flow example
Prompt new users to fund their wallet as part of onboarding:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
import { useLogin } from '@moon-key/react-auth';
import { useRouter } from 'next/navigation';
export default function LoginWithFunding() {
const router = useRouter();
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance }) {
if (balance < 1000n) {
router.push('/insufficient-funds');
} else {
router.push('/dashboard');
}
}
});
const { login } = useLogin({
onComplete(user, isNewUser) {
if (isNewUser && wallets.length > 0) {
// Prompt new users to fund their wallet
fundWallet(wallets[0].address, {
chain: 'eip155:8453', // Base
amount: '0.05'
});
} else {
router.push('/dashboard');
}
}
});
return ;
}
```
## Customizing the UI
Customize the "Receive funds" screen with the `uiConfig` option:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function CustomizedFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
chain: 'eip155:8453',
amount: '0.05',
uiConfig: {
receiveFundsTitle: 'Receive 0.05 ETH',
receiveFundsSubtitle: 'Scan this code or copy your wallet address to receive funds on Base.'
}
});
};
return ;
}
```
### UI configuration options
Custom title for the "Receive funds" screen.
Custom subtitle for the "Receive funds" screen.
## Complete example
Here's a complete example with amount selection and funding:
```tsx theme={null}
'use client';
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function FlexibleFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance, fundingMethod }) {
console.log('Funding complete');
console.log('Method:', fundingMethod);
console.log('New balance:', balance.toString());
}
});
const [amount, setAmount] = useState('0.05');
const [asset, setAsset] = useState<'native-currency' | 'USDC'>('native-currency');
const [isFunding, setIsFunding] = useState(false);
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
setIsFunding(true);
try {
await fundWallet(selectedWallet.address, {
chain: 'eip155:8453', // Base
amount,
asset
});
} catch (error) {
console.error('Funding failed:', error);
} finally {
setIsFunding(false);
}
};
if (wallets.length === 0) {
return
No wallet found
;
}
return (
Fund Your Wallet
setAmount(e.target.value)}
placeholder="0.05"
/>
Wallet: {wallets[0].address}
);
}
```
## Best practices
* Consider typical transaction costs on the target network
* Use smaller amounts for L2s (Base, Arbitrum, Optimism) where gas is cheap
* Use larger amounts for Ethereum mainnet where gas is expensive
* Make amounts easy to adjust before purchase
* Use L2s for better user experience (lower fees)
* Match the network to where your contracts are deployed
* Consider which networks MoonPay supports
* Explain that purchases are not instant
* Show expected delivery times
* Provide transaction status updates
* Handle edge cases gracefully
* Track funding completion with `onUserExited`
* Navigate users appropriately based on balance
* Log funding events for analytics
* Handle insufficient balance scenarios
# Solana
Source: https://docs.streambird.io/wallet-as-a-service/funding/fund-types/solana
With funding methods enabled for your app, MoonKey will prompt users to fund their wallets at two points in their experience:
1. **Manually** - When you call MoonKey's `fundWallet` method
2. **Automatically** - When the user attempts to send a transaction but has insufficient funds
You can configure the cluster, asset, and amount that users should fund their wallets with directly in code.
## Manually invoking funding
Once you've enabled funding methods for your app in the Dashboard, use the `useFundWallet` hook to invoke the funding flow:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWalletButton() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.1'
});
};
return ;
}
```
Once invoked, the `fundWallet` method will open a modal with funding options for the user. If only one funding method is enabled, MoonKey will navigate directly to that flow.
Purchases with third-party providers (like MoonPay) are not always instantaneous. There may be some time before the user completes their purchase and the funds are available in their wallet.
## Automatically invoking funding
When a user attempts to send a transaction but doesn't have sufficient funds, MoonKey will automatically show them an "Add funds" button in the transaction modal that enables them to invoke funding flows.
This helps users complete their intended actions without leaving your application.
## Setting funding parameters in code
You can override your Dashboard configuration by passing parameters to `fundWallet`:
### Basic parameters
The wallet address to fund.
The Solana cluster to fund on. Use `'solana:mainnet'` for mainnet, `'solana:devnet'` for devnet.
If not specified, defaults to the cluster configured in your Dashboard.
The amount of the asset to fund as a decimal string (e.g., `'0.1'`).
If not specified, defaults to the amount configured in your Dashboard.
The asset to fund with:
* `'native-currency'` - SOL (Solana's native currency)
* `'USDC'` - USDC stablecoin
Defaults to `'native-currency'`.
Only use `'solana:mainnet'` for production. Testnets are not supported for card funding.
## Examples
### Fund with SOL
Fund with the native currency (SOL):
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWithSOL() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.1' // Defaults to 'native-currency' (SOL)
});
};
return ;
}
```
### Fund with USDC
Fund with USDC stablecoin:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWithUSDC() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '15',
asset: 'USDC'
});
};
return ;
}
```
### Fund with custom amounts
Allow users to choose their funding amount:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
import { useState } from 'react';
function CustomAmountFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const [amount, setAmount] = useState('0.5');
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount
});
};
return (
setAmount(e.target.value)}
placeholder="Amount in SOL"
/>
);
}
```
## Callbacks
To understand when users have gone through a funding flow, use the `onUserExited` callback with the `useFundWallet` hook:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
import { useRouter } from 'next/navigation';
function OnboardingWithFunding() {
const router = useRouter();
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance }) {
if (balance < 1000n) {
router.push('/insufficient-funds');
} else {
router.push('/dashboard');
}
}
});
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.5'
});
};
return ;
}
```
### Callback parameters
The `onUserExited` callback receives an object with:
The wallet address that was funded.
The Solana cluster where funding occurred.
The funding method used (e.g., `'card'`).
The current balance of the wallet being funded (in lamports).
## Onboarding flow example
Prompt new users to fund their wallet as part of onboarding:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
import { useLogin } from '@moon-key/react-auth';
import { useRouter } from 'next/navigation';
export default function LoginWithFunding() {
const router = useRouter();
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance }) {
// Check if balance is sufficient (in lamports)
const minBalance = 100_000_000n; // 0.1 SOL
if (balance < minBalance) {
router.push('/insufficient-funds');
} else {
router.push('/dashboard');
}
}
});
const { login } = useLogin({
onComplete(user, isNewUser) {
if (isNewUser && wallets.length > 0) {
// Prompt new users to fund their wallet
fundWallet(wallets[0].address, {
cluster: 'solana:mainnet',
amount: '0.5'
});
} else {
router.push('/dashboard');
}
}
});
return ;
}
```
## Customizing the UI
Customize the "Receive funds" screen with the `uiConfig` option:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function CustomizedFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
const selectedWallet = wallets[0];
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.5',
uiConfig: {
receiveFundsTitle: 'Receive 0.5 SOL',
receiveFundsSubtitle: 'Scan this code or copy your wallet address to receive funds on Solana.'
}
});
};
return ;
}
```
### UI configuration options
Custom title for the "Receive funds" screen.
Custom subtitle for the "Receive funds" screen.
## Complete example
Here's a complete example with amount and asset selection:
```tsx theme={null}
'use client';
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
import { useState } from 'react';
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
export default function FlexibleFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet({
onUserExited({ balance, fundingMethod }) {
console.log('Funding complete');
console.log('Method:', fundingMethod);
console.log('New balance:', (Number(balance) / LAMPORTS_PER_SOL).toFixed(4), 'SOL');
}
});
const [amount, setAmount] = useState('0.5');
const [asset, setAsset] = useState<'native-currency' | 'USDC'>('native-currency');
const [isFunding, setIsFunding] = useState(false);
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
setIsFunding(true);
try {
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount,
asset
});
} catch (error) {
console.error('Funding failed:', error);
} finally {
setIsFunding(false);
}
};
if (wallets.length === 0) {
return
No wallet found
;
}
return (
Fund Your Wallet
setAmount(e.target.value)}
placeholder="0.5"
/>
Wallet: {wallets[0].address}
💳 Pay with card, Apple Pay, or Google Pay
⚡ Funds typically arrive within minutes
🔒 Secure payment processing via MoonPay
);
}
```
## Understanding Solana balances
Solana balances are measured in **lamports**, where 1 SOL = 1,000,000,000 lamports:
```tsx theme={null}
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
// Convert lamports to SOL
const solBalance = Number(balanceInLamports) / LAMPORTS_PER_SOL;
// Convert SOL to lamports
const lamports = solAmount * LAMPORTS_PER_SOL;
```
When checking balances in the `onUserExited` callback, remember that the balance is in lamports:
```tsx theme={null}
const { fundWallet } = useFundWallet({
onUserExited({ balance }) {
// balance is in lamports
const solBalance = Number(balance) / LAMPORTS_PER_SOL;
console.log(`Balance: ${solBalance.toFixed(4)} SOL`);
// Check minimum balance (0.1 SOL = 100,000,000 lamports)
if (balance < 100_000_000n) {
alert('Please add more funds');
}
}
});
```
## Best practices
* Solana has very low transaction fees (typically \< \$0.01)
* Smaller amounts work well (0.1 - 1 SOL is often sufficient)
* Consider typical transaction costs in your app
* Don't require users to fund more than necessary
* Explain that purchases are not instant
* Show expected delivery times (usually faster than Ethereum)
* Provide transaction status updates
* Handle edge cases gracefully
* Always use `'solana:mainnet'` for production
* Test your integration thoroughly on mainnet with small amounts
* Devnet is not supported for card funding
* USDC provides price stability for users
* Useful for applications with fixed pricing
* Still allows for Solana transactions
* May be preferred by users concerned about volatility
* Track funding completion with `onUserExited`
* Navigate users appropriately based on balance
* Log funding events for analytics
* Handle insufficient balance scenarios
* Solana accounts require minimum balance for rent exemption
* Ensure funded amounts are sufficient for account creation
* Consider rent costs when setting minimum balances
* Guide users if their balance is below rent exemption
## Common issues
**Possible causes:**
* Purchase not yet complete
* MoonPay processing payment
* Network congestion
**Solutions:**
* Wait a few minutes for processing
* Check MoonPay transaction status
* Refresh wallet balance after delay
**Possible causes:**
* Funding not enabled in Dashboard
* Incorrect cluster configuration
* Browser blocking popup
**Solutions:**
* Verify Dashboard configuration
* Check console for errors
* Ensure popups are allowed
**Possible causes:**
* Funded amount too small
* Account rent not covered
* Multiple transactions depleting balance
**Solutions:**
* Suggest higher funding amounts
* Explain Solana rent requirements
* Provide balance warnings before transactions
## Next steps
Fund Ethereum wallets
Learn about card payment options
Set up funding in the Dashboard
Use funded wallets to send transactions
# Card Payments
Source: https://docs.streambird.io/wallet-as-a-service/funding/methods/card
The **pay with card** funding option enables users to purchase cryptocurrency with debit or credit cards, including browser payment methods like **Apple Pay** and **Google Pay**. This is particularly useful for users who may not hold crypto outside of your application and are purchasing cryptocurrency for the first time.
Users must be authenticated through MoonKey to use card funding methods. A valid access token is required for the user to proceed with card funding.
MoonKey facilitates card purchases through MoonPay, a trusted onramp provider embedded directly within your app. This provides a seamless experience where users never need to leave your application to fund their wallets.
**Mainnet only:** Card and fiat onramp purchases are supported on mainnets only. On testnets (e.g. Sepolia, Base Sepolia, Solana Devnet), onramps cannot purchase testnet tokens, so this flow will not be available.
## How it works
When a user initiates a card purchase:
1. **Select amount** - User chooses how much cryptocurrency to purchase
2. **Choose payment method** - User selects debit/credit card, Apple Pay, or Google Pay
3. **MoonPay onboarding** - User completes any required verification steps (KYC/AML)
4. **Complete payment** - User enters payment details and confirms the purchase
5. **Funds delivered** - Cryptocurrency is delivered to the user's embedded wallet
The entire flow happens within your application, providing a seamless user experience.
## Payment methods
### Debit and credit cards
Users can pay directly with:
* **Debit cards** - Visa, Mastercard, and other major providers
* **Credit cards** - Visa, Mastercard, and other major providers
**Debit cards generally have higher approval rates** than credit cards for cryptocurrency purchases. Consider guiding users toward debit card payments when possible.
### Apple Pay
Users on Safari or iOS devices can use Apple Pay for faster checkout:
* Stored payment methods from Apple Wallet
* Biometric authentication (Face ID, Touch ID)
* One-tap payment experience
### Google Pay
Users on Chrome or Android devices can use Google Pay:
* Stored payment methods from Google account
* Quick and secure checkout
* Simplified payment flow
## Supported assets
MoonPay supports purchasing a variety of cryptocurrencies across different networks. Users can purchase:
* **Native tokens** - ETH, SOL, and other network currencies
* **Stablecoins** - USDC, USDT, and other stablecoins
* **Popular tokens** - Various ERC-20 and SPL tokens
For a complete list of supported cryptocurrencies and networks, see [MoonPay's supported assets documentation](https://support.moonpay.com/customers/docs/list-of-supported-cryptocurrencies).
## Integration
### React SDK
Use the MoonKey React SDK to trigger the card funding flow:
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/ethereum';
function FundWalletButton() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
try {
await fundWallet(selectedWallet.address, {
chain: 'eip155:1',
amount: '0.1'
});
console.log('Funding initiated');
} catch (error) {
console.error('Funding failed:', error);
}
};
return (
);
}
```
```tsx theme={null}
import { useWallets, useFundWallet } from '@moon-key/react-auth/solana';
function FundWalletButton() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const handleFund = async () => {
if (wallets.length === 0) return;
const selectedWallet = wallets[0];
try {
await fundWallet(selectedWallet.address, {
cluster: 'solana:mainnet',
amount: '0.1'
});
console.log('Funding initiated');
} catch (error) {
console.error('Funding failed:', error);
}
};
return (
);
}
```
### Parameters
The wallet address to fund.
Configuration options for the funding request.
#### EthereumFundingConfig
The Ethereum chain to fund on. Use `'eip155:1'` for Ethereum mainnet, or other chain IDs for different networks.
**Example:** `'eip155:1'` (Ethereum mainnet)
The amount of ETH to purchase.
**Example:** `'0.1'`
#### SolanaFundingConfig
The Solana cluster to fund on. Use `'solana:mainnet'` for mainnet.
**Example:** `'solana:mainnet'`
The amount of SOL to purchase.
**Example:** `'0.1'`
## User experience
### First-time users
For users making their first purchase through MoonPay:
1. **Account creation** - User creates a MoonPay account
2. **Identity verification** - User completes KYC verification (name, address, ID)
3. **Payment** - User completes the purchase
4. **Future purchases** - Faster checkout with saved information
KYC verification is typically quick but may take longer in some cases. This is a one-time process required by regulations.
### Returning users
For users who have previously used MoonPay:
1. **Pre-filled information** - Saved payment methods and details
2. **Quick checkout** - Streamlined purchase flow
3. **Faster processing** - No additional verification needed
## Complete example
Here's a complete example with wallet funding:
```tsx theme={null}
'use client';
import { useWallets } from '@moon-key/react-auth/ethereum';
import { useFundWallet } from '@moon-key/react-auth';
import { useState } from 'react';
export default function WalletFunding() {
const { wallets } = useWallets();
const { fundWallet } = useFundWallet();
const [isFunding, setIsFunding] = useState(false);
const handleFund = async () => {
if (wallets.length === 0) {
alert('No wallet found');
return;
}
const selectedWallet = wallets[0];
setIsFunding(true);
try {
await fundWallet(selectedWallet.address, {
chain: 'eip155:1',
amount: '0.1'
});
alert('Purchase initiated! Funds will arrive shortly.');
} catch (error) {
console.error('Funding failed:', error);
alert('Purchase failed. Please try again.');
} finally {
setIsFunding(false);
}
};
if (wallets.length === 0) {
return
);
}
```
## Next steps
Set up funding in your application
Learn about MoonKey's funding features
# Overview
Source: https://docs.streambird.io/wallet-as-a-service/funding/overview
**MoonKey makes it easy for your users to fund their wallets**, enabling them to seamlessly take onchain actions within your apps such as purchasing goods, swapping tokens, minting NFTs, and more.
MoonKey currently supports funding wallets via debit/credit card, Apple Pay, and Google Pay.
## How it works
MoonKey provides a seamless funding experience that allows your users to add funds to their embedded wallets using familiar payment methods. This removes the complexity of acquiring cryptocurrency through exchanges and makes it simple for users to get started with onchain activities.
### Supported funding methods
Purchase cryptocurrency directly with debit cards, credit cards, Apple Pay, or Google Pay. Fast and convenient funding for immediate use.
## Key benefits
### For users
**Familiar payment methods**
* Use credit cards, debit cards, Apple Pay, or Google Pay
* No need to create exchange accounts
* Instant purchasing experience
**Seamless experience**
* Fund wallets without leaving your application
* Clear pricing with no hidden fees
* Immediate access to purchased funds
**Security and trust**
* Secure payment processing
* Industry-standard encryption
* Trusted payment providers
### For developers
**Simple integration**
* Easy-to-use SDK and API
* Pre-built UI components
* Customizable funding flows
**Better user onboarding**
* Reduce drop-off rates during onboarding
* Enable users to start transacting immediately
* Seamless path from signup to first transaction
**Increased conversion**
* Remove barriers to user engagement
* Enable in-app purchases and transactions
* Support various payment methods your users prefer
## Getting started
To integrate wallet funding into your application:
1. **Configure funding methods** - Set up your preferred payment providers in the MoonKey Dashboard
2. **Integrate the SDK** - Add funding functionality to your application using the MoonKey React SDK
3. **Customize the experience** - Tailor the funding UI to match your brand and user flow
4. **Test the integration** - Verify the funding flow works smoothly for your users
Set up funding methods in your application
Enable credit card, debit card, and mobile wallet payments
## Supported assets
MoonKey supports funding with popular cryptocurrencies and stablecoins:
* **Native currencies** - ETH (Ethereum), SOL (Solana), and other network tokens
* **Stablecoins** - USDC and other stablecoins across supported chains
* **Custom tokens** - Support for additional tokens based on your configuration
The available assets depend on the blockchain network and your MoonKey configuration.
## Fees and pricing
Funding fees vary based on the payment method and cryptocurrency purchased. Fees are displayed to users before they confirm their purchase.
**Transparent pricing:**
* All fees are shown upfront before purchase
* No hidden charges or surprise fees
* Competitive rates across payment methods
# Export Key
Source: https://docs.streambird.io/wallet-as-a-service/ui-components/export-key
The Export Key screen allows users to export their wallet's private key. This screen includes security warnings and confirmation steps to ensure users understand the risks of exposing their private key.
Exporting private keys is a sensitive operation. Users should be warned that anyone with access to their private key has full control of their wallet and funds.
## How it works
When a user requests to export their private key:
1. Your app calls the export key method from the MoonKey SDK
2. The Export Key screen appears with security warnings
3. User reviews the warnings and understands the risks
4. User confirms they want to proceed
5. The private key is displayed to the user
6. User can copy the private key for backup or import into another wallet
## Triggering the Export Key screen
Use the `useExportPrivateKey` hook from the MoonKey SDK to trigger the Export Key screen:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportPrivateKey } from '@moon-key/react-auth/ethereum';
export default function ExportKeyButton() {
const { user } = useMoonKey();
const { exportPrivateKey } = useExportPrivateKey();
const handleExport = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const privateKey = await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Private Key',
privateKeyDescription: 'Your private key provides full access to this wallet and all funds',
neverShareDescription: 'Never share your private key with anyone, including MoonKey support',
loadingExportOptionsText: 'Preparing export options...'
}
});
console.log('Private key exported');
// The UI will display the key to the user
} catch (error) {
console.error('User cancelled export:', error);
}
};
return ;
}
```
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportPrivateKey } from '@moon-key/react-auth/solana';
export default function ExportKeyButton() {
const { user } = useMoonKey();
const { exportPrivateKey } = useExportPrivateKey();
const handleExport = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const privateKey = await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Private Key',
privateKeyDescription: 'Your private key provides full access to this wallet and all funds',
neverShareDescription: 'Never share your private key with anyone, including MoonKey support',
loadingExportOptionsText: 'Preparing export options...'
}
});
console.log('Private key exported');
// The UI will display the key to the user
} catch (error) {
console.error('User cancelled export:', error);
}
};
return ;
}
```
## Customizing the Export Key screen
You can customize the Export Key screen by passing UI configuration:
```tsx theme={null}
const privateKey = await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Backup Your Wallet',
hdDescription: 'Your HD wallet seed phrase can restore all your wallets',
privateKeyDescription: 'Your private key provides complete control over this specific wallet',
neverShareDescription: 'Never share these with anyone. MoonKey will never ask for them.',
loadingExportOptionsText: 'Loading export options...',
logoUrl: 'https://your-app.com/logo.png'
}
});
```
## Configuration options
Custom title text displayed at the top of the Export Key screen.
**Example:**
```tsx theme={null}
uiConfig: {
title: 'Backup Your Wallet'
}
```
Description text for HD (Hierarchical Deterministic) wallet seed phrase export option.
This text explains what an HD seed phrase is and its purpose.
**Example:**
```tsx theme={null}
uiConfig: {
hdDescription: 'Your seed phrase can restore all wallets derived from it'
}
```
Description text for the private key export option.
This text explains what the private key is and how it's used.
**Example:**
```tsx theme={null}
uiConfig: {
privateKeyDescription: 'Your private key gives full access to this wallet and all its funds'
}
```
Warning text emphasizing that private keys should never be shared.
This critical security warning is prominently displayed.
**Example:**
```tsx theme={null}
uiConfig: {
neverShareDescription: 'Never share your private key. MoonKey support will never ask for it.'
}
```
Text displayed while export options are being loaded.
**Example:**
```tsx theme={null}
uiConfig: {
loadingExportOptionsText: 'Preparing secure export...'
}
```
URL to your company logo. Displayed at the top of the Export Key screen.
**Example:**
```tsx theme={null}
uiConfig: {
logoUrl: 'https://myapp.com/logo.png'
}
```
## Security warnings
The Export Key screen displays multiple security warnings to users:
Anyone with access to your private key has complete control over your wallet and all funds in it.
Never share your private key with anyone. MoonKey support will never ask for your private key.
If you choose to store your private key, keep it in a secure location like a password manager or offline storage.
Be cautious of phishing attempts. Only export your key when you initiated the action.
## Use cases
### Wallet backup
Allow users to backup their wallet for recovery purposes:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useExportPrivateKey } from '@moon-key/react-auth/ethereum';
export default function BackupWallet() {
const { user } = useMoonKey();
const { exportPrivateKey } = useExportPrivateKey();
const handleBackup = async () => {
if (!user?.wallet) return;
try {
await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Backup Your Wallet',
privateKeyDescription: 'Save your private key in a secure location for wallet recovery',
neverShareDescription: 'Store securely. Never share with anyone.'
}
});
// Log backup action
await logUserAction('wallet_backup', {
userId: user.id,
timestamp: Date.now()
});
} catch (error) {
console.error('Backup cancelled:', error);
}
};
return (
Wallet Backup
Create a backup of your wallet to prevent loss of access.
);
}
```
### Migrate to another wallet
Help users migrate their wallet to another application:
```tsx theme={null}
const migrateWallet = async () => {
if (!user?.wallet) return;
try {
await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Export for Migration',
privateKeyDescription: 'Use this private key to import your wallet into MetaMask, Phantom, or other compatible wallets',
neverShareDescription: 'Only use this key for importing into trusted wallet applications'
}
});
alert('You can now import this private key into MetaMask, Phantom, or any compatible wallet.');
} catch (error) {
console.error('Migration cancelled:', error);
}
};
```
### Advanced users self-custody
Provide power users with their private key for self-custody:
```tsx theme={null}
const enableSelfCustody = async () => {
if (!user?.wallet) return;
try {
await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Enable Self-Custody',
privateKeyDescription: 'Take full control of your wallet by exporting your private key',
neverShareDescription: 'Keep this key secure for complete self-custody'
}
});
// Update user preference
await updateUserSettings(user.id, {
selfCustodyEnabled: true,
exportedAt: Date.now()
});
} catch (error) {
console.error('Self-custody setup cancelled:', error);
}
};
```
## Complete example
Here's a complete example with proper warnings and logging:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useExportPrivateKey } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function ExportKeyDemo() {
const { user, isAuthenticated } = useMoonKey();
const { exportPrivateKey } = useExportPrivateKey();
const [exported, setExported] = useState(false);
if (!isAuthenticated) {
return
Please sign in first
;
}
if (!user?.wallet) {
return
No wallet found
;
}
const handleExport = async () => {
// Show additional confirmation
const confirmed = confirm(
'WARNING: Your private key gives complete control of your wallet. ' +
'Only export if you understand the risks. Continue?'
);
if (!confirmed) return;
try {
await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Private Key',
privateKeyDescription: 'Your private key provides full access to your wallet and all funds',
neverShareDescription: 'Never share your private key with anyone. MoonKey will never ask for it.',
loadingExportOptionsText: 'Preparing secure export...'
}
});
setExported(true);
// Log the export action for security audit
await fetch('/api/log-security-event', {
method: 'POST',
body: JSON.stringify({
event: 'private_key_export',
userId: user.id,
walletAddress: user.wallet.address,
timestamp: Date.now()
})
});
console.log('Private key exported successfully');
} catch (error) {
console.error('Export failed or cancelled:', error);
}
};
return (
Export Private Key
Wallet: {user.wallet.address}
⚠️ Security Warning
Your private key gives complete control of your wallet
Never share your private key with anyone
MoonKey support will never ask for your private key
Store it securely in a password manager or offline
{exported && (
✓ Private key exported successfully. Make sure to store it securely!
)}
);
}
```
## User experience flow
User clicks button to export their private key.
The Export Key screen displays comprehensive security warnings about the risks.
User must acknowledge they understand the risks before proceeding.
The private key is displayed on screen, typically with a copy button.
User copies the private key to store it securely.
User confirms they have securely stored the key before closing the screen.
## Best practices
Require additional confirmation before showing the Export Key screen:
```tsx theme={null}
const handleExport = async () => {
const confirmed = confirm(
'WARNING: This will reveal your private key. Only proceed if you understand the security risks.'
);
if (!confirmed) return;
await exportPrivateKey({ wallet: user.wallet });
};
```
Keep a security audit log of private key exports:
```tsx theme={null}
await exportPrivateKey({ wallet: user.wallet });
// Log for security audit
await logSecurityEvent({
type: 'private_key_export',
userId: user.id,
walletAddress: user.wallet.address,
timestamp: Date.now(),
ipAddress: req.ip
});
```
Provide educational content about key security:
```tsx theme={null}
Before you export
Understand that your private key = full wallet access
Never share it with anyone, including support
Store it in a password manager or write it down offline
Seed phrase backup - More secure than raw private key
Hardware wallet - Best security for long-term storage
Multi-sig wallet - Requires multiple approvals
```
## Security considerations
Private key export is one of the most sensitive operations in a wallet application. Implement multiple layers of security and education.
**For developers:**
1. **Multiple confirmations** - Require users to confirm multiple times
2. **Clear warnings** - Display unmistakable security warnings
3. **Audit logging** - Log all export events with timestamps and IP addresses
4. **Rate limiting** - Limit how often users can export keys
5. **Re-authentication** - Require fresh login before export
6. **User education** - Provide resources about key security
7. **Alternative options** - Suggest safer backup methods when possible
8. **Compliance** - Ensure export functionality meets regulatory requirements
**For users:**
The Export Key screen helps users by:
* Displaying clear warnings about the risks
* Explaining what a private key is and why it's sensitive
* Requiring explicit confirmation before revealing the key
* Providing guidance on secure storage
* Warning against common phishing attempts
**Common threats to warn about:**
* **Phishing websites** that trick users into entering their private key
* **Malicious browser extensions** that steal private keys
* **Screen recording malware** that captures the displayed key
* **Social engineering** where attackers impersonate support
* **Insecure storage** like plaintext files or unencrypted notes
## Displaying the private key
After user confirmation, the private key should be displayed securely:
```tsx theme={null}
Your Private Key
{privateKey}
⚠️ Never share this key with anyone
⚠️ Store it in a secure location
⚠️ Anyone with this key can access your funds
```
# Overview
Source: https://docs.streambird.io/wallet-as-a-service/ui-components/overview
Easy user onboarding with MoonKey's customizable UI components.
MoonKey provides pre-built user interface components that handle wallet interactions. These components are designed to integrate seamlessly into your application while remaining highly customizable to match your brand.
See live demos of all UI components and customization options at [demo.moonkey.fun](https://demo.moonkey.fun).
## Key Features
### Highly Customizable
Every UI component can be customized to match your brand:
* **Logo** - Display your company logo
* **Header Title** - Custom title for each screen
* **Header Description** - Custom description text
* **Close Button** - Show or hide the close button
### Wallet Operations
Handle all wallet interactions with pre-built UIs:
* **Sign Message** - For proving wallet ownership or signing in to third-party services
* **Sign Transaction** - For approving smart contract interactions
* **Send Transaction** - For transferring tokens or native currency
* **Export Key** - For users who want to use their wallet elsewhere
### Custom Branding
Maintain consistent branding across all screens:
```tsx theme={null}
{children}
```
## Component Details
Explore detailed documentation for each UI component:
Message signing confirmation
Transaction signing with details
Token and currency transfers
Private key export with warnings
# Send Transaction
Source: https://docs.streambird.io/wallet-as-a-service/ui-components/send-transaction
The Send Transaction screen is displayed when your application requests to sign and broadcast a blockchain transaction. This screen shows users the transaction details, signs it with their wallet, and automatically broadcasts it to the blockchain network.
Send Transaction combines signing and broadcasting in one step. For more control over when a transaction is broadcast, use [Sign Transaction](/authentication/user-authentication/ui-components/sign-transaction) instead.
## How it works
When your application requests to send a transaction:
1. Your app calls the send transaction method from the MoonKey SDK
2. The Send Transaction screen appears, displaying transaction details
3. User reviews the transaction information (recipient, amount, gas, etc.)
4. User approves or rejects the transaction
5. If approved, MoonKey signs the transaction with the user's wallet
6. MoonKey automatically broadcasts the signed transaction to the blockchain
7. The transaction hash is returned to your application
## Triggering the Send Transaction screen
Use the `useSendTransaction` hook from the MoonKey SDK to trigger the Send Transaction screen:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
export default function SendTransactionButton() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const result = await sendTransaction({
transaction: {
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
value: '0x38d7ea4c68000', // 0.001 ETH in hex
data: '0x'
},
wallet: user.wallet,
uiConfig: {
title: 'Send 0.001 ETH',
description: 'Transfer 0.001 ETH to the recipient',
confirmButtonText: 'Send',
showWalletUI: true,
success: {
title: 'Transaction Sent!',
description: 'Your transaction is being processed'
}
}
});
console.log('Transaction hash:', result.transactionHash);
alert(`Transaction sent! Hash: ${result.transactionHash}`);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return ;
}
```
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/solana';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
export default function SendTransactionButton() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
// Create a Solana transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(user.wallet.address),
toPubkey: new PublicKey('RecipientAddressHere'),
lamports: 0.001 * LAMPORTS_PER_SOL
})
);
const result = await sendTransaction({
transaction,
wallet: user.wallet,
uiConfig: {
title: 'Send 0.001 SOL',
description: 'Transfer 0.001 SOL to the recipient',
confirmButtonText: 'Send',
showWalletUI: true,
success: {
title: 'Transaction Sent!',
description: 'Your transaction is being processed'
}
}
});
console.log('Transaction signature:', result.signature);
alert(`Transaction sent! Signature: ${result.signature}`);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return ;
}
```
## Customizing the Send Transaction screen
You can customize the Send Transaction screen by passing UI configuration in the `uiConfig` object:
```tsx theme={null}
const result = await sendTransaction({
transaction: transactionData,
wallet: user.wallet,
uiConfig: {
logoUrl: 'https://your-app.com/logo.png',
title: 'Send Payment',
description: 'Review and send this payment',
confirmButtonText: 'Send Now',
cancelButtonText: 'Cancel',
showWalletUI: true,
waitBeforeCloseTime: 2000,
advancedTransaction: {
title: 'Advanced Settings',
closeButtonText: 'Close'
},
success: {
title: 'Payment Sent!',
description: 'Your payment is on its way'
},
failed: {
title: 'Transaction Failed',
description: 'Your transaction could not be completed',
errorTitle: 'Error Details',
closeButtonText: 'Close'
}
}
});
```
## Configuration options
### Basic UI Options
URL to your company logo. Displayed at the top of the Send Transaction screen.
**Example:**
```tsx theme={null}
uiConfig: {
logoUrl: 'https://myapp.com/logo.png'
}
```
Custom title text displayed at the top of the Send Transaction screen.
**Example:**
```tsx theme={null}
uiConfig: {
title: 'Send Payment'
}
```
Custom description text displayed below the title, providing context for the transaction.
**Example:**
```tsx theme={null}
uiConfig: {
description: 'Review the payment details before sending'
}
```
Custom text for the confirm/send button.
**Default:** `'Send'`
**Example:**
```tsx theme={null}
uiConfig: {
confirmButtonText: 'Send Now'
}
```
Custom text for the cancel button.
**Default:** `'Cancel'`
**Example:**
```tsx theme={null}
uiConfig: {
cancelButtonText: 'Go Back'
}
```
Whether to show wallet information (address, balance) in the Send Transaction screen.
**Default:** `true`
**Example:**
```tsx theme={null}
uiConfig: {
showWalletUI: true // Show balance to prevent insufficient funds errors
}
```
Time in milliseconds to wait before automatically closing the screen after a successful transaction.
**Default:** `1500` (1.5 seconds)
**Example:**
```tsx theme={null}
uiConfig: {
waitBeforeCloseTime: 2000 // Wait 2 seconds before closing
}
```
### Advanced Transaction Options
Configuration for the advanced transaction settings screen.
Title for the advanced transaction settings screen.
**Example:**
```tsx theme={null}
uiConfig: {
advancedTransaction: {
title: 'Advanced Settings'
}
}
```
Text for the close button in the advanced settings screen.
**Example:**
```tsx theme={null}
uiConfig: {
advancedTransaction: {
closeButtonText: 'Done'
}
}
```
### Success Screen Options
Configuration for the success screen displayed after the transaction is sent.
Title text displayed on the success screen.
**Example:**
```tsx theme={null}
uiConfig: {
success: {
title: 'Transaction Sent Successfully!'
}
}
```
Description text displayed on the success screen.
**Example:**
```tsx theme={null}
uiConfig: {
success: {
description: 'Your transaction has been broadcast and is being confirmed'
}
}
```
### Failed Screen Options
Configuration for the error screen displayed if the transaction fails.
Title text displayed on the failed screen.
**Example:**
```tsx theme={null}
uiConfig: {
failed: {
title: 'Transaction Failed'
}
}
```
Description text displayed on the failed screen.
**Example:**
```tsx theme={null}
uiConfig: {
failed: {
description: 'Your transaction could not be completed. Please try again.'
}
}
```
Title for the error details section.
**Example:**
```tsx theme={null}
uiConfig: {
failed: {
errorTitle: 'Error Details'
}
}
```
Text for the close button on the failed screen.
**Example:**
```tsx theme={null}
uiConfig: {
failed: {
closeButtonText: 'Close'
}
}
```
## Common use cases
### Simple ETH/SOL transfer
Send native currency to a recipient:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { parseEther } from 'viem';
export default function SendETH() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const sendETH = async (recipient: string, amount: string) => {
if (!user?.wallet) return;
try {
const result = await sendTransaction({
transaction: {
to: recipient,
value: parseEther(amount).toString(16) // Convert to hex
},
wallet: user.wallet,
uiConfig: {
title: `Send ${amount} ETH`,
description: `Transfer ${amount} ETH to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
confirmButtonText: 'Send ETH',
showWalletUI: true,
success: {
title: 'ETH Sent!',
description: 'Your ETH transfer is being confirmed on the blockchain'
}
}
});
console.log('Transaction hash:', result.transactionHash);
return result.transactionHash;
} catch (error) {
console.error('Failed to send ETH:', error);
throw error;
}
};
return (
);
}
```
### Token transfer
Send ERC-20 or SPL tokens:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';
export default function SendTokens() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const sendTokens = async (tokenAddress: string, recipient: string, amount: bigint) => {
if (!user?.wallet) return;
try {
// Encode ERC-20 transfer
const data = encodeFunctionData({
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
]
}],
functionName: 'transfer',
args: [recipient, amount]
});
const result = await sendTransaction({
transaction: {
to: tokenAddress,
data,
value: '0x0'
},
wallet: user.wallet,
uiConfig: {
title: 'Send USDC',
description: 'Transfer 100 USDC to recipient',
confirmButtonText: 'Send Tokens',
success: {
title: 'Tokens Sent!',
description: 'Your token transfer is being processed'
}
}
});
console.log('Transaction hash:', result.transactionHash);
return result.transactionHash;
} catch (error) {
console.error('Failed to send tokens:', error);
throw error;
}
};
return (
);
}
```
### NFT transfer
Transfer an NFT to another wallet:
```tsx theme={null}
const transferNFT = async (nftContract: string, tokenId: string, recipient: string) => {
if (!user?.wallet) return;
// Encode ERC-721 transferFrom
const data = encodeFunctionData({
abi: [{
name: 'transferFrom',
type: 'function',
inputs: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenId', type: 'uint256' }
]
}],
functionName: 'transferFrom',
args: [user.wallet.address, recipient, BigInt(tokenId)]
});
const result = await sendTransaction({
transaction: {
to: nftContract,
data,
value: '0x0'
},
wallet: user.wallet,
uiConfig: {
title: 'Transfer NFT',
description: `Transfer NFT #${tokenId} to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
confirmButtonText: 'Transfer',
showWalletUI: true,
success: {
title: 'NFT Transferred!',
description: 'Your NFT has been sent to the recipient'
}
}
});
return result.transactionHash;
};
```
### Mint NFT
Mint an NFT with payment:
```tsx theme={null}
const mintNFT = async (nftContract: string, mintPrice: string) => {
if (!user?.wallet) return;
const result = await sendTransaction({
transaction: {
to: nftContract,
data: encodedMintFunction,
value: mintPrice // e.g., '0x16345785D8A0000' for 0.1 ETH
},
wallet: user.wallet,
uiConfig: {
title: 'Mint NFT',
description: 'Mint your NFT for 0.1 ETH',
confirmButtonText: 'Mint Now',
showWalletUI: true,
success: {
title: 'NFT Minted!',
description: 'Your NFT is being minted. Check your wallet in a few moments.'
}
}
});
console.log('Mint transaction hash:', result.transactionHash);
return result.transactionHash;
};
```
### Contract interaction with gas estimation
Send a transaction with custom gas settings:
```tsx theme={null}
const interactWithContract = async () => {
if (!user?.wallet) return;
const result = await sendTransaction({
transaction: {
to: '0xContractAddress',
data: encodedFunctionCall,
value: '0x0',
gas: '0x30d40', // 200,000 gas limit
gasPrice: '0x4a817c800' // 20 gwei
},
wallet: user.wallet,
uiConfig: {
title: 'Execute Contract Function',
description: 'Call the contract function with the specified parameters',
confirmButtonText: 'Execute',
success: {
title: 'Contract Called!',
description: 'Your transaction is being confirmed'
}
}
});
return result.transactionHash;
};
```
## Complete example
Here's a complete example with transaction tracking:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
import { parseEther } from 'viem';
export default function SendTransactionDemo() {
const { user, isAuthenticated } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const [txHash, setTxHash] = useState(null);
const [sending, setSending] = useState(false);
const [recipient, setRecipient] = useState('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');
const [amount, setAmount] = useState('0.001');
if (!isAuthenticated) {
return
Please sign in first
;
}
if (!user?.wallet) {
return
No wallet found
;
}
const handleSendTransaction = async () => {
setSending(true);
try {
const result = await sendTransaction({
transaction: {
to: recipient,
value: `0x${parseEther(amount).toString(16)}`,
data: '0x'
},
wallet: user.wallet,
uiConfig: {
title: `Send ${amount} ETH`,
description: `Transfer ${amount} ETH to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
confirmButtonText: 'Send Transaction',
cancelButtonText: 'Cancel',
showWalletUI: true,
success: {
title: 'Transaction Sent!',
description: 'Your transaction is being confirmed on the blockchain'
},
failed: {
title: 'Transaction Failed',
description: 'Please try again or check your balance',
closeButtonText: 'Close'
}
}
});
setTxHash(result.transactionHash);
console.log('Transaction sent:', result.transactionHash);
alert('Transaction sent successfully!');
} catch (error) {
console.error('Failed to send transaction:', error);
alert('Transaction failed or was cancelled');
} finally {
setSending(false);
}
};
return (
);
}
```
## User experience flow
Your application calls `sendTransaction()` with transaction data, wallet, and optional UI options.
The customized Send Transaction screen is displayed with transaction details.
User reviews the transaction information including recipient, amount, gas fees, and any contract interactions.
User clicks the send button to proceed, or cancels (if `isCancellable: true`) to reject.
If approved, MoonKey signs the transaction and automatically broadcasts it to the blockchain network.
The success screen appears with the configured `successHeader` and `successDescription`.
The transaction hash (or signature for Solana) is returned to your application for tracking.
## Send Transaction vs. Sign Transaction
Understanding when to use each:
* **Signs and broadcasts** in one step
* Automatically submits to the blockchain
* Returns transaction hash immediately
* Simpler API for basic operations
* Best for immediate transactions
* User can't control broadcast timing
* Most common for standard transfers
* Example: Send ETH, transfer tokens, mint NFTs
* **Only signs** the transaction
* Does not broadcast automatically
* Returns signed transaction data
* Requires manual broadcasting
* More control over when to broadcast
* Can sign offline
* Good for batch operations or complex flows
* Example: Multi-step flows, conditional broadcasting
Use **Send Transaction** for most use cases. Only use Sign Transaction when you need explicit control over the broadcasting step.
## Tracking transaction status
After sending a transaction, you typically want to track its confirmation status:
```tsx theme={null}
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// After sending
const result = await sendTransaction({
transaction: txData,
wallet: user.wallet
});
console.log('Transaction hash:', result.transactionHash);
// Track confirmation using viem
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
// Wait for transaction receipt
const receipt = await publicClient.waitForTransactionReceipt({
hash: result.transactionHash as `0x${string}`
});
console.log('Transaction confirmed!', receipt);
console.log('Block number:', receipt.blockNumber);
console.log('Gas used:', receipt.gasUsed);
console.log('Status:', receipt.status); // 'success' or 'reverted'
// Get transaction details
const transaction = await publicClient.getTransaction({
hash: result.transactionHash as `0x${string}`
});
console.log('Transaction details:', transaction);
```
```tsx theme={null}
import { Connection } from '@solana/web3.js';
// After sending
const result = await sendTransaction({
transaction,
wallet: user.wallet
});
console.log('Transaction signature:', result.signature);
// Track confirmation using Solana web3.js
const connection = new Connection('https://api.mainnet-beta.solana.com');
// Wait for confirmation
const confirmation = await connection.confirmTransaction(
result.signature,
'confirmed' // commitment level
);
console.log('Transaction confirmed!', confirmation);
// Get transaction details
const txDetails = await connection.getTransaction(result.signature, {
maxSupportedTransactionVersion: 0
});
console.log('Transaction details:', txDetails);
console.log('Slot:', txDetails?.slot);
console.log('Block time:', txDetails?.blockTime);
```
## Best practices
Always provide clear information about what the transaction does:
```tsx theme={null}
uiConfig: {
title: 'Send 100 USDC',
description: 'Transfer 100 USDC to alice.eth. Estimated gas: $2.50'
}
```
Check transaction validity before showing the UI:
```tsx theme={null}
// Validate recipient address
if (!isAddress(recipient)) {
alert('Invalid recipient address');
return;
}
// Check sufficient balance
const balance = await getBalance(user.wallet.address);
if (balance < amount + estimatedGas) {
alert('Insufficient funds');
return;
}
// Then send
const result = await sendTransaction({ ... });
```
Different errors require different handling:
```tsx theme={null}
try {
const result = await sendTransaction({
transaction: txData,
wallet: user.wallet
});
toast.success('Transaction sent!');
} catch (error) {
if (error.code === 4001) {
// User rejected
toast.info('Transaction cancelled');
} else if (error.message?.includes('insufficient funds')) {
toast.error('Insufficient balance for transaction');
} else if (error.message?.includes('gas')) {
toast.error('Gas estimation failed. Transaction may fail.');
} else {
toast.error('Transaction failed. Please try again.');
}
console.error('Transaction error:', error);
}
```
Help users track their transaction:
```tsx theme={null}
const result = await sendTransaction({ ... });
// Show link to block explorer
const explorerUrl = `https://etherscan.io/tx/${result.transactionHash}`;
toast.success(
);
// Store for user's transaction history
await saveTransaction({
hash: result.transactionHash,
from: user.wallet.address,
to: recipient,
amount,
timestamp: Date.now()
});
```
Always show wallet UI so users can verify they have sufficient funds:
```tsx theme={null}
uiConfig: {
showWalletUI: true // Critical for preventing failed transactions
}
```
For complex transactions, set appropriate gas limits:
```tsx theme={null}
// Estimate gas first
const estimatedGas = await publicClient.estimateGas({
to: recipient,
data,
value: amount
});
// Add 20% buffer
const gasLimit = (estimatedGas * 120n) / 100n;
const result = await sendTransaction({
transaction: {
to: recipient,
data,
value: amount,
gas: `0x${gasLimit.toString(16)}`
},
wallet: user.wallet
});
```
Keep a record of sent transactions for user reference:
```tsx theme={null}
const result = await sendTransaction({ ... });
// Store in local storage or database
const txRecord = {
hash: result.transactionHash,
from: user.wallet.address,
to: recipient,
amount,
timestamp: Date.now(),
status: 'pending'
};
await saveTxHistory(txRecord);
// Update status when confirmed
const receipt = await waitForReceipt(result.transactionHash);
await updateTxStatus(result.transactionHash, receipt.status);
```
Inform users about next steps:
```tsx theme={null}
uiConfig: {
success: {
title: 'Payment Sent!',
description: 'Your payment is being confirmed. This usually takes 15-30 seconds.'
}
}
```
## Error handling
Common errors and how to handle them:
```tsx theme={null}
try {
const result = await sendTransaction({ ... });
return result.transactionHash;
} catch (error) {
// User rejected the transaction
if (error.code === 4001) {
console.log('User cancelled transaction');
return null;
}
// Insufficient funds
if (error.message?.includes('insufficient funds')) {
alert('Not enough balance to complete transaction');
return null;
}
// Gas estimation failed
if (error.message?.includes('gas required exceeds allowance')) {
alert('Transaction would fail. Please check parameters.');
return null;
}
// Network error
if (error.message?.includes('network')) {
alert('Network error. Please check your connection.');
return null;
}
// Generic error
console.error('Transaction error:', error);
alert('Transaction failed. Please try again.');
return null;
}
```
## Security considerations
Always validate transaction parameters before sending. Malicious applications could trick users into sending funds to unintended recipients or approving dangerous contract interactions.
**For developers:**
1. **Validate all inputs** - Check addresses, amounts, and data before sending
2. **Show full details** - Display recipient, amount, gas fees, and contract interactions
3. **Estimate gas** - Provide accurate gas estimates to prevent failed transactions
4. **Check balances** - Verify sufficient funds before attempting to send
5. **Rate limit** - Prevent abuse by limiting transaction frequency
6. **Log transactions** - Keep records for debugging and support
7. **Handle errors** - Provide clear error messages for different failure scenarios
**For users:**
The Send Transaction screen helps users by:
* Displaying full transaction details before sending
* Showing wallet balance to prevent insufficient funds errors
* Providing clear context via title and description
* Allowing cancellation if `isCancellable` is true
* Showing success confirmation with transaction hash
## Next steps
Sign transactions without broadcasting
Sign messages for authentication
Explore all UI components
Global appearance settings
# Sign Message
Source: https://docs.streambird.io/wallet-as-a-service/ui-components/sign-message
The Sign Message screen is displayed when your application requests a wallet signature for an arbitrary message. This screen shows users the message content and allows them to approve or reject the signature request.
Message signing is commonly used for authentication, proving wallet ownership, or authorizing actions without sending a blockchain transaction.
## How it works
When your application requests a message signature:
1. Your app calls the sign message method from the MoonKey SDK
2. The Sign Message screen appears, displaying the message content
3. User reviews the message details
4. User approves or rejects the signature request
5. If approved, the signed message is returned to your application
## Triggering the Sign Message screen
Use the `useSignMessage` hook from the MoonKey SDK to trigger the Sign Message screen:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
export default function SignMessageButton() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const result = await signMessage({
message: 'Welcome to MyApp! Sign this message to prove you own this wallet.',
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Message',
description: 'Please sign this message to continue',
buttonText: 'Sign',
showWalletUI: true
}
}
});
console.log('Signature:', result.signature);
} catch (error) {
console.error('User rejected signature:', error);
}
};
return ;
}
```
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/solana';
export default function SignMessageButton() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
// Convert string message to Uint8Array for Solana
const messageBytes = new TextEncoder().encode(
'Welcome to MyApp! Sign this message to prove you own this wallet.'
);
const result = await signMessage({
message: messageBytes,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Message',
description: 'Please sign this message to continue',
buttonText: 'Sign',
showWalletUI: true
}
}
});
console.log('Signature:', result.signature);
} catch (error) {
console.error('User rejected signature:', error);
}
};
return ;
}
```
## Customizing the Sign Message screen
You can customize the Sign Message screen by passing UI options in the `options.uiOptions` object when calling `signMessage`:
```tsx theme={null}
const result = await signMessage({
message: 'Your message content here',
wallet: user.wallet,
options: {
uiOptions: {
title: 'Verify Wallet Ownership',
description: 'Sign this message to prove you control this wallet address',
buttonText: 'Sign & Verify',
showWalletUI: true
}
}
});
```
## Configuration options
### Basic UI Options
Custom title text displayed at the top of the Sign Message screen.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
title: 'Verify Your Identity'
}
}
```
Custom description text displayed below the title, providing context for the signature request.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
description: 'Sign this message to prove you own this wallet and access your account'
}
}
```
Custom text for the confirmation button.
**Default:** `'Sign'`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
buttonText: 'Approve & Sign'
}
}
```
Whether to show wallet information (address, balance) in the Sign Message screen.
**Default:** `true`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
showWalletUI: false // Hide wallet details for a cleaner UI
}
}
```
### Solana-specific Options
The commitment level to use for Solana transactions.
**Available values:** `'processed'`, `'confirmed'`, `'finalized'`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
commitment: 'confirmed'
}
}
```
The encoding format for the Solana transaction.
**Available values:** `'base58'`, `'base64'`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
encoding: 'base64'
}
}
```
## Common use cases
### Authentication verification
Prove wallet ownership for authentication:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
export default function VerifyWallet() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage();
const verifyOwnership = async () => {
if (!user?.wallet) return;
try {
const result = await signMessage({
message: `Sign this message to verify you own this wallet.\n\nTimestamp: ${Date.now()}`,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Verify Wallet Ownership',
description: 'Please sign to confirm you control this wallet',
buttonText: 'Verify',
showWalletUI: true
}
}
});
// Send signature to your backend for verification
await fetch('/api/verify-wallet', {
method: 'POST',
body: JSON.stringify({ signature: result.signature })
});
alert('Wallet verified successfully!');
} catch (error) {
console.error('Verification failed:', error);
}
};
return ;
}
```
### Third-party service login
Sign in to external services with wallet signature:
```tsx theme={null}
const loginToThirdParty = async () => {
if (!user?.wallet) return;
const nonce = await fetchNonceFromService();
const result = await signMessage({
message: `Sign in to ThirdPartyApp\n\nNonce: ${nonce}`,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign In to ThirdPartyApp',
description: 'Authorize connection to ThirdPartyApp with your wallet',
buttonText: 'Sign In',
showWalletUI: true
}
}
});
// Send signature to third-party service
await authenticateWithService(result.signature);
};
```
### Action authorization
Authorize specific actions without gas fees:
```tsx theme={null}
const authorizeAction = async (actionType: string, details: string) => {
if (!user?.wallet) return;
const result = await signMessage({
message: `Authorize Action: ${actionType}\n\nDetails: ${details}\n\nTimestamp: ${Date.now()}`,
wallet: user.wallet,
options: {
uiOptions: {
title: `Authorize ${actionType}`,
description: 'Sign this message to authorize the action',
buttonText: 'Authorize',
showWalletUI: false
}
}
});
return result.signature;
};
```
### Terms and conditions acceptance
Cryptographically sign terms acceptance:
```tsx theme={null}
const acceptTerms = async () => {
if (!user?.wallet) return;
const result = await signMessage({
message: `I accept the Terms of Service and Privacy Policy of MyApp.\n\nVersion: 2.0\nDate: ${new Date().toISOString()}`,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Accept Terms of Service',
description: 'Sign to acknowledge that you have read and agree to our terms',
buttonText: 'I Accept',
showWalletUI: false
}
}
});
// Store signature as proof of acceptance
await storeTermsAcceptance(result.signature);
};
```
## Complete example
Here's a complete example with custom UI configuration:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function SignMessageDemo() {
const { user, isAuthenticated } = useMoonKey();
const { signMessage } = useSignMessage();
const [signature, setSignature] = useState(null);
if (!isAuthenticated) {
return
Please sign in first
;
}
if (!user?.wallet) {
return
No wallet found
;
}
const handleSignMessage = async () => {
try {
const result = await signMessage({
message: `Welcome to MyApp!\n\nWallet: ${user.wallet.address}\nTimestamp: ${Date.now()}\n\nSign this message to verify your identity.`,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Verify Your Identity',
description: 'Sign this message to prove you control this wallet',
buttonText: 'Sign & Verify',
showWalletUI: true
}
}
});
setSignature(result.signature);
console.log('Message signed successfully:', result.signature);
} catch (error) {
console.error('Failed to sign message:', error);
alert('Signature rejected or failed');
}
};
return (
Sign Message Example
Wallet: {user.wallet.address}
{signature && (
Signature:
{signature}
)}
);
}
```
## User experience flow
Your application calls `signMessage()` with a message, wallet, and optional UI options.
The customized Sign Message screen is displayed to the user.
User reviews the message content, title, and description.
User clicks the confirm button to sign, or cancels/closes the modal to reject.
If approved, MoonKey signs the message with the user's wallet private key.
The signature is returned to your application as an object containing the signature string (Ethereum) or Uint8Array (Solana).
## Message signing vs. transaction signing
It's important to understand the difference:
* Signs arbitrary text or data
* **Does not** cost gas fees
* **Does not** modify blockchain state
* Used for authentication, verification, or authorization
* Instant and free
* Example: "Sign this message to log in"
* Signs blockchain transactions
* **Costs gas fees** (requires native currency)
* **Modifies** blockchain state (transfers tokens, calls contracts, etc.)
* Used for actual blockchain operations
* Requires network confirmation
* Example: "Sign this transaction to send 1 ETH"
## Best practices
Always include a clear `description` explaining why the user needs to sign:
```tsx theme={null}
options: {
uiOptions: {
title: 'Verify Wallet',
description: 'Sign this message to prove you own this wallet. This will not cost any gas.'
}
}
```
Prevent replay attacks by including unique data:
```tsx theme={null}
message: `Action: Login\nTimestamp: ${Date.now()}\nNonce: ${randomNonce}`
```
Make it clear what the user is signing:
```tsx theme={null}
options: {
uiOptions: {
buttonText: 'Sign & Verify' // Better than just "Sign"
}
}
```
Users may decline to sign. Always handle errors:
```tsx theme={null}
try {
const result = await signMessage({
message: '...',
wallet: user.wallet
});
console.log('Signature:', result.signature);
} catch (error) {
// User rejected - show friendly message
toast.error('Signature required to continue');
}
```
Use `showWalletUI: true` when wallet context is important:
```tsx theme={null}
options: {
uiOptions: {
showWalletUI: true // Show address and balance
}
}
```
Set to `false` for a cleaner UI when wallet details aren't relevant.
Users are more likely to read and understand shorter messages:
* Use clear, simple language
* Break long messages into lines with `\n`
* Highlight important information
Always verify the user has a wallet before attempting to sign:
```tsx theme={null}
if (!user?.wallet) {
alert('Please create a wallet first');
return;
}
```
## Global appearance settings
The Sign Message screen also respects global appearance settings from `MoonKeyProvider`:
```tsx theme={null}
{children}
```
Learn more about [configuring appearance](/get-started/frontend-sdks/react/advance/configure-appearance).
## Security considerations
Never sign messages without reviewing their content. Malicious applications could trick users into signing messages that authorize unwanted actions.
**For developers:**
1. **Verify signatures server-side** - Always validate signatures on your backend before trusting them
2. **Use nonces** - Include unique identifiers to prevent replay attacks
3. **Include context** - Clearly state what the signature authorizes
4. **Set expiration** - Include timestamps and reject old signatures
5. **Validate message format** - Ensure the signed message matches your expected format
**For users:**
The Sign Message screen helps users by:
* Displaying the full message content
* Showing their wallet address
* Providing clear context via title and description
* Allowing them to reject the request
## Verifying signatures
After obtaining a signature, you typically want to verify it on your backend:
```typescript theme={null}
import { verifyMessage } from 'viem';
// On your frontend, after signing
const result = await signMessage({
message: 'Your message',
wallet: user.wallet
});
// Send result.signature to your backend
const response = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify({
message: 'Your message',
signature: result.signature,
address: user.wallet.address
})
});
// On your backend
const isValid = await verifyMessage({
address: userWalletAddress,
message: originalMessage,
signature: receivedSignature
});
if (isValid) {
// Signature is valid - user owns the wallet
console.log('Signature verified!');
}
```
```typescript theme={null}
import { PublicKey } from '@solana/web3.js';
import nacl from 'tweetnacl';
// On your frontend, after signing
const result = await signMessage({
message: messageBytes,
wallet: user.wallet
});
// Convert Uint8Array to base64 for transmission
const signatureBase64 = Buffer.from(result.signature).toString('base64');
// Send to your backend
const response = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify({
message: Buffer.from(messageBytes).toString('base64'),
signature: signatureBase64,
address: user.wallet.address
})
});
// On your backend
const publicKey = new PublicKey(userWalletAddress);
const messageBytes = Buffer.from(receivedMessage, 'base64');
const signatureBytes = Buffer.from(receivedSignature, 'base64');
const isValid = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKey.toBytes()
);
if (isValid) {
console.log('Signature verified!');
}
```
## Next steps
Sign blockchain transactions
Send transactions with UI
Explore all UI components
Global appearance settings
# Sign Transaction
Source: https://docs.streambird.io/wallet-as-a-service/ui-components/sign-transaction
The Sign Transaction screen is displayed when your application requests a wallet signature for a blockchain transaction. This screen shows users the transaction details and allows them to approve or reject the transaction before it's signed.
Transaction signing creates a signed transaction that can be broadcast to the blockchain. This is different from [Send Transaction](/authentication/user-authentication/ui-components/send-transaction), which both signs and broadcasts the transaction automatically.
## How it works
When your application requests a transaction signature:
1. Your app calls the sign transaction method from the MoonKey SDK
2. The Sign Transaction screen appears, displaying transaction details
3. User reviews the transaction information (recipient, amount, gas, etc.)
4. User approves or rejects the signature request
5. If approved, the signed transaction is returned to your application
6. Your application can then broadcast the signed transaction to the blockchain
## Triggering the Sign Transaction screen
Use the `useSignTransaction` hook from the MoonKey SDK to trigger the Sign Transaction screen:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
export default function SignTransactionButton() {
const { user } = useMoonKey();
const { signTransaction } = useSignTransaction();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const result = await signTransaction({
transaction: {
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
value: '0x38d7ea4c68000', // 0.001 ETH in hex
data: '0x',
gas: '0x5208', // 21000 in hex
gasPrice: '0x4a817c800' // 20 gwei in hex
},
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Transaction',
description: 'Review and sign this transaction',
buttonText: 'Sign',
showWalletUI: true,
successHeader: 'Transaction Signed!',
successDescription: 'Your transaction has been signed successfully',
isCancellable: true
}
}
});
console.log('Signed transaction:', result.signature);
// Now you can broadcast the signed transaction
// await broadcastTransaction(result.signature);
} catch (error) {
console.error('User rejected transaction:', error);
}
};
return ;
}
```
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/solana';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
export default function SignTransactionButton() {
const { user } = useMoonKey();
const { signTransaction } = useSignTransaction();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
// Create a Solana transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(user.wallet.address),
toPubkey: new PublicKey('ReceipientAddressHere'),
lamports: 0.001 * LAMPORTS_PER_SOL
})
);
// Serialize the transaction
const serialized = transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
});
const result = await signTransaction({
transaction: serialized,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Transaction',
description: 'Review and sign this transaction',
buttonText: 'Sign',
showWalletUI: true,
successHeader: 'Transaction Signed!',
successDescription: 'Your transaction has been signed successfully',
isCancellable: true
}
}
});
console.log('Signed transaction:', result.signedTransaction);
// Now you can broadcast the signed transaction
// await connection.sendRawTransaction(result.signedTransaction);
} catch (error) {
console.error('User rejected transaction:', error);
}
};
return ;
}
```
## Customizing the Sign Transaction screen
You can customize the Sign Transaction screen by passing UI options in the `options.uiOptions` object:
```tsx theme={null}
const result = await signTransaction({
transaction: transactionData,
wallet: user.wallet,
options: {
uiOptions: {
title: 'Approve Transaction',
description: 'Please review the transaction details before signing',
buttonText: 'Approve & Sign',
showWalletUI: true,
successHeader: 'Success!',
successDescription: 'Transaction signed and ready to broadcast',
isCancellable: true,
transactionInfo: {
// Custom transaction display options
}
}
}
});
```
## Configuration options
### Basic UI Options
Custom title text displayed at the top of the Sign Transaction screen.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
title: 'Approve Transaction'
}
}
```
Custom description text displayed below the title, providing context for the transaction.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
description: 'Review the transaction details carefully before signing'
}
}
```
Custom text for the confirmation button.
**Default:** `'Sign'`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
buttonText: 'Approve & Sign'
}
}
```
Whether to show wallet information (address, balance) in the Sign Transaction screen.
**Default:** `true`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
showWalletUI: false // Hide wallet details for a cleaner UI
}
}
```
### Success Screen Options
Header text displayed on the success screen after the transaction is signed.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
successHeader: 'Transaction Signed Successfully!'
}
}
```
Description text displayed on the success screen after the transaction is signed.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
successDescription: 'Your transaction is ready to be broadcast to the network'
}
}
```
### Behavior Options
Whether the user can cancel the transaction signing request.
**Default:** `true`
**Example:**
```tsx theme={null}
options: {
uiOptions: {
isCancellable: false // Require user to sign or close the entire modal
}
}
```
Custom options for displaying transaction information in the UI.
This allows you to customize how transaction details are presented to the user.
**Example:**
```tsx theme={null}
options: {
uiOptions: {
transactionInfo: {
// Custom transaction display configuration
}
}
}
```
## Common use cases
### Token transfer transaction
Sign a transaction to transfer ERC-20 tokens:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';
export default function SignTokenTransfer() {
const { user } = useMoonKey();
const { signTransaction } = useSignTransaction();
const signTokenTransferTx = async () => {
if (!user?.wallet) return;
try {
// Encode ERC-20 transfer function call
const data = encodeFunctionData({
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
]
}],
functionName: 'transfer',
args: ['0xRecipientAddress', BigInt(1000000000000000000)] // 1 token with 18 decimals
});
const result = await signTransaction({
transaction: {
to: '0xTokenContractAddress',
data,
value: '0x0'
},
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Token Transfer',
description: 'Transfer 1 TOKEN to recipient',
buttonText: 'Sign Transfer',
successHeader: 'Transfer Signed!',
successDescription: 'Token transfer is ready to broadcast'
}
}
});
console.log('Signed transaction:', result.signature);
// Broadcast the signed transaction
} catch (error) {
console.error('Failed to sign:', error);
}
};
return ;
}
```
### Smart contract interaction
Sign a transaction to interact with a smart contract:
```tsx theme={null}
const signContractInteraction = async () => {
if (!user?.wallet) return;
const result = await signTransaction({
transaction: {
to: '0xSmartContractAddress',
data: encodedFunctionCall,
value: '0x0',
gas: '0x30d40' // 200,000 gas limit
},
wallet: user.wallet,
options: {
uiOptions: {
title: 'Sign Contract Interaction',
description: 'Approve this transaction to interact with the smart contract',
buttonText: 'Approve',
showWalletUI: true,
isCancellable: true
}
}
});
return result.signature;
};
```
### NFT minting transaction
Sign a transaction to mint an NFT:
```tsx theme={null}
const signNFTMint = async () => {
if (!user?.wallet) return;
const result = await signTransaction({
transaction: {
to: '0xNFTContractAddress',
data: mintFunctionData,
value: '0x16345785D8A0000' // 0.1 ETH mint price
},
wallet: user.wallet,
options: {
uiOptions: {
title: 'Mint NFT',
description: 'Sign to mint your NFT for 0.1 ETH',
buttonText: 'Mint Now',
successHeader: 'NFT Mint Signed!',
successDescription: 'Your NFT mint transaction is ready to broadcast',
showWalletUI: true
}
}
});
// Broadcast the signed transaction
const txHash = await broadcastTransaction(result.signature);
console.log('Transaction hash:', txHash);
};
```
### Batch signing multiple transactions
Sign multiple transactions in sequence:
```tsx theme={null}
const signMultipleTransactions = async (transactions: any[]) => {
if (!user?.wallet) return;
const signedTransactions = [];
for (let i = 0; i < transactions.length; i++) {
try {
const result = await signTransaction({
transaction: transactions[i],
wallet: user.wallet,
options: {
uiOptions: {
title: `Sign Transaction ${i + 1} of ${transactions.length}`,
description: 'Please sign each transaction to proceed',
buttonText: 'Sign',
isCancellable: true
}
}
});
signedTransactions.push(result.signature);
} catch (error) {
console.error(`Failed to sign transaction ${i + 1}:`, error);
break; // Stop if user cancels
}
}
return signedTransactions;
};
```
## Complete example
Here's a complete example with full transaction signing flow:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function SignTransactionDemo() {
const { user, isAuthenticated } = useMoonKey();
const { signTransaction } = useSignTransaction();
const [signedTx, setSignedTx] = useState(null);
const [broadcasting, setBroadcasting] = useState(false);
if (!isAuthenticated) {
return
Please sign in first
;
}
if (!user?.wallet) {
return
No wallet found
;
}
const handleSignTransaction = async () => {
try {
const result = await signTransaction({
transaction: {
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
value: '0x38d7ea4c68000', // 0.001 ETH
data: '0x'
},
wallet: user.wallet,
options: {
uiOptions: {
title: 'Send 0.001 ETH',
description: 'Sign this transaction to send 0.001 ETH',
buttonText: 'Sign Transaction',
showWalletUI: true,
successHeader: 'Transaction Signed!',
successDescription: 'Ready to broadcast to the network',
isCancellable: true
}
}
});
setSignedTx(result.signature);
console.log('Transaction signed:', result.signature);
} catch (error) {
console.error('Failed to sign transaction:', error);
alert('Transaction signing failed or was cancelled');
}
};
const handleBroadcast = async () => {
if (!signedTx) return;
setBroadcasting(true);
try {
// Broadcast the signed transaction using your preferred method
const response = await fetch('/api/broadcast-transaction', {
method: 'POST',
body: JSON.stringify({ signedTransaction: signedTx })
});
const data = await response.json();
alert(`Transaction broadcast! Hash: ${data.txHash}`);
} catch (error) {
console.error('Failed to broadcast:', error);
alert('Failed to broadcast transaction');
} finally {
setBroadcasting(false);
}
};
return (
Sign Transaction Example
Wallet: {user.wallet.address}
{signedTx && (
Signed Transaction:
{signedTx}
)}
);
}
```
## User experience flow
Your application calls `signTransaction()` with transaction data, wallet, and optional UI options.
The customized Sign Transaction screen is displayed with transaction details.
User reviews the transaction information including recipient, amount, gas fees, and any contract interactions.
User clicks the confirm button to sign, or cancels (if `isCancellable: true`) to reject.
If approved, MoonKey signs the transaction with the user's wallet private key.
The success screen appears with the configured `successHeader` and `successDescription`.
The signed transaction is returned to your application for broadcasting.
## Sign Transaction vs. Send Transaction
Understanding the difference between these two operations:
* **Only signs** the transaction
* **Does not broadcast** to the blockchain
* Returns a signed transaction that you must broadcast manually
* Useful when you need control over broadcasting timing
* Can sign transactions offline
* Good for batch operations or complex flows
* Example: Sign now, broadcast later when conditions are met
* **Signs and broadcasts** in one step
* Automatically submits the transaction to the blockchain
* Returns the transaction hash after broadcasting
* Simpler for basic send operations
* Requires active network connection
* Best for immediate transactions
* Example: Send ETH/tokens directly to a recipient
## Broadcasting signed transactions
After signing a transaction, you need to broadcast it to the blockchain:
```tsx theme={null}
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// After signing
const result = await signTransaction({
transaction: txData,
wallet: user.wallet
});
// Broadcast using viem
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
const txHash = await publicClient.sendRawTransaction({
serializedTransaction: result.signature as `0x${string}`
});
console.log('Transaction hash:', txHash);
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash: txHash
});
console.log('Transaction confirmed:', receipt);
```
```tsx theme={null}
import { Connection } from '@solana/web3.js';
// After signing
const result = await signTransaction({
transaction: serializedTx,
wallet: user.wallet
});
// Broadcast using Solana web3.js
const connection = new Connection('https://api.mainnet-beta.solana.com');
const signature = await connection.sendRawTransaction(
result.signedTransaction
);
console.log('Transaction signature:', signature);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature);
console.log('Transaction confirmed:', confirmation);
```
## Best practices
Always explain what the transaction does in the `description`:
```tsx theme={null}
options: {
uiOptions: {
title: 'Transfer USDC',
description: 'Send 100 USDC to 0x1234...5678. This will cost approximately $2 in gas fees.'
}
}
```
Enable `showWalletUI` so users can verify they have sufficient balance:
```tsx theme={null}
options: {
uiOptions: {
showWalletUI: true // Show balance to prevent insufficient funds errors
}
}
```
Inform users about next steps after signing:
```tsx theme={null}
options: {
uiOptions: {
successHeader: 'Transaction Signed!',
successDescription: 'Broadcasting to the network. This may take a few seconds.'
}
}
```
Users may reject transactions or encounter errors:
```tsx theme={null}
try {
const result = await signTransaction({
transaction: txData,
wallet: user.wallet
});
await broadcastTransaction(result.signature);
} catch (error) {
if (error.code === 4001) {
// User rejected
toast.info('Transaction cancelled');
} else {
// Other error
toast.error('Failed to sign transaction');
}
}
```
Check transaction validity before prompting the user:
```tsx theme={null}
// Check balance
if (balance < transactionAmount + estimatedGas) {
alert('Insufficient funds for transaction and gas');
return;
}
// Then proceed with signing
const result = await signTransaction({ ... });
```
Set `isCancellable: true` for most transactions:
```tsx theme={null}
options: {
uiOptions: {
isCancellable: true // Let users back out if needed
}
}
```
Only set `isCancellable: false` for critical flows where cancellation would break your app state.
Before broadcasting, verify the signed transaction matches your intent:
```tsx theme={null}
const result = await signTransaction({ transaction: txData, wallet: user.wallet });
// Store signature for later
await savePendingTransaction({
signature: result.signature,
originalData: txData,
timestamp: Date.now()
});
// Verify before broadcast
const verified = await verifyTransaction(result.signature);
if (verified) {
await broadcastTransaction(result.signature);
}
```
## Security considerations
Always review transaction details carefully before signing. Malicious applications could trick users into signing transactions that drain their wallets or approve unlimited token spending.
**For developers:**
1. **Display full transaction details** - Show recipient, amount, gas fees, and any contract interactions
2. **Validate transaction data** - Verify the transaction is well-formed before requesting signature
3. **Check balances** - Ensure users have sufficient funds before signing
4. **Rate limit signing** - Prevent abuse by limiting how many transactions can be signed
5. **Log signed transactions** - Keep records for debugging and user support
6. **Use secure RPC endpoints** - Broadcast to trusted nodes only
**For users:**
The Sign Transaction screen helps users by:
* Displaying transaction details (recipient, amount, gas)
* Showing their wallet balance
* Providing clear context via title and description
* Allowing them to cancel if `isCancellable` is true
* Showing success confirmation before closing
## Next steps
Sign and send transactions in one step
Sign messages for authentication
Explore all UI components
Global appearance settings
# Ethereum Provider
Source: https://docs.streambird.io/wallet-as-a-service/using-wallets/ethereum/ethereum-provider
Access your embedded wallet's EIP-1193 provider to integrate with popular Web3 libraries like viem, ethers, and wagmi.
## Overview
All MoonKey embedded wallets expose a standard [EIP-1193 provider](https://eips.ethereum.org/EIPS/eip-1193) that enables you to:
* Send JSON-RPC requests directly to the wallet
* Integrate with Web3 libraries (viem, ethers, wagmi)
* Use familiar Ethereum API methods
[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) is the standardized Ethereum JavaScript API for how applications request information, signatures, and transactions from wallets.
## Getting the Provider
To get a wallet's EIP-1193 provider, use the `getEthereumProvider()` method:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/ethereum';
function MyComponent() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const getProvider = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
console.log('Provider:', provider);
return provider;
};
return (
);
}
```
## Using with Web3 Libraries
### With Viem
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/ethereum';
import { createWalletClient, custom } from 'viem';
import { mainnet } from 'viem/chains';
function ViemIntegration() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const sendWithViem = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
// Create viem wallet client
const walletClient = createWalletClient({
account: wallet.address as `0x${string}`,
chain: mainnet,
transport: custom(provider)
});
// Send transaction using viem
const hash = await walletClient.sendTransaction({
to: '0xRecipientAddress...',
value: 1000000000000000n // 0.001 ETH
});
console.log('Transaction hash:', hash);
};
return (
);
}
```
### With Ethers
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/ethereum';
import { BrowserProvider } from 'ethers';
function EthersIntegration() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const sendWithEthers = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
// Create ethers provider
const ethersProvider = new BrowserProvider(provider);
const signer = await ethersProvider.getSigner();
// Send transaction using ethers
const tx = await signer.sendTransaction({
to: '0xRecipientAddress...',
value: '1000000000000000' // 0.001 ETH in wei
});
await tx.wait();
console.log('Transaction hash:', tx.hash);
};
return (
);
}
```
## Direct JSON-RPC Requests
You can also send JSON-RPC requests directly to the provider:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/ethereum';
function DirectRPCExample() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const getBalance = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
// Get balance using eth_getBalance
const balance = await provider.request({
method: 'eth_getBalance',
params: [wallet.address, 'latest']
});
console.log('Balance (wei):', balance);
};
const signMessage = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
// Sign message using personal_sign
const signature = await provider.request({
method: 'personal_sign',
params: ['Hello from MoonKey!', wallet.address]
});
console.log('Signature:', signature);
};
return (
);
}
```
## Common JSON-RPC Methods
The provider supports standard Ethereum JSON-RPC methods:
| Method | Description |
| ---------------------------- | ------------------------- |
| `eth_accounts` | Get wallet addresses |
| `eth_chainId` | Get current chain ID |
| `eth_getBalance` | Get wallet balance |
| `eth_sendTransaction` | Send a transaction |
| `eth_signTransaction` | Sign a transaction |
| `personal_sign` | Sign a message |
| `eth_signTypedData_v4` | Sign typed data (EIP-712) |
| `wallet_switchEthereumChain` | Switch network |
## When to Use the Provider
Use the EIP-1193 provider when you need to:
* **Integrate with existing Web3 libraries** (viem, ethers, wagmi)
* **Send custom JSON-RPC requests** not covered by MoonKey's SDK
* **Use library-specific features** (e.g., viem's contract interactions)
* **Migrate from other wallet providers** with minimal code changes
For common operations like sending transactions or signing messages, consider using MoonKey's native hooks (`useSendTransaction`, `useSignMessage`) for better UI customization and error handling.
## Complete Example
Here's a complete example showing provider usage with viem for contract interaction:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/ethereum';
import { createWalletClient, custom, parseAbi } from 'viem';
import { mainnet } from 'viem/chains';
const ERC20_ABI = parseAbi([
'function transfer(address to, uint256 amount) returns (bool)',
'function balanceOf(address account) view returns (uint256)'
]);
export default function ContractInteraction() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const transferTokens = async () => {
if (wallets.length === 0) {
alert('No wallet found');
return;
}
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider();
// Create viem client
const walletClient = createWalletClient({
account: wallet.address as `0x${string}`,
chain: mainnet,
transport: custom(provider)
});
try {
// Call contract method
const hash = await walletClient.writeContract({
address: '0xTokenContractAddress...',
abi: ERC20_ABI,
functionName: 'transfer',
args: ['0xRecipientAddress...', 1000000n] // Transfer amount
});
console.log('Transfer successful:', hash);
alert(`Transfer successful! Tx: ${hash}`);
} catch (error) {
console.error('Transfer failed:', error);
alert('Transfer failed');
}
};
return (
ERC-20 Token Transfer
);
}
```
## Related Documentation
Use MoonKey's native transaction sending
Use MoonKey's native message signing
Official EIP-1193 documentation
Learn more about viem
## Next Steps
* Learn about [viem wallet clients](https://viem.sh/docs/clients/wallet) for advanced usage
* Explore [ethers.js providers](https://docs.ethers.org/v6/api/providers/) and signers
* Understand [JSON-RPC methods](https://ethereum.org/en/developers/docs/apis/json-rpc/) available on Ethereum
# Send Transaction
Source: https://docs.streambird.io/wallet-as-a-service/using-wallets/ethereum/send-transaction
Send transactions from a user's embedded Ethereum wallet to interact with smart contracts, transfer ETH, or execute any onchain action.
## Overview
The `useSendTransaction` hook from MoonKey's React SDK provides a simple interface for sending transactions from embedded wallets. It handles transaction preparation, signing, and broadcasting to the network.
MoonKey automatically handles transaction population (gas estimation, nonce management, etc.) and prompts users for confirmation before broadcasting.
## React SDK
To send a transaction, use the `sendTransaction` method from the `useSendTransaction` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
function SendEthButton() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const result = await sendTransaction({
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
value: '0x38d7ea4c68000' // 0.001 ETH in wei (hex)
});
console.log('Transaction sent:', result.transactionHash);
alert(`Success! Hash: ${result.transactionHash}`);
} catch (error) {
console.error('Transaction failed:', error);
alert('Transaction failed');
}
};
return (
);
}
```
Customize the transaction confirmation modal:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
function CustomTransactionButton() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
if (!user?.wallet) return;
try {
const result = await sendTransaction({
to: '0xRecipient...',
value: '0x38d7ea4c68000'
}, {
uiConfig: {
title: 'Send Payment',
description: 'Sending 0.001 ETH to recipient',
confirmButtonText: 'Confirm Payment',
cancelButtonText: 'Cancel',
transactionInfo: {
amount: '0.001 ETH',
recipient: '0xd8dA...6045'
}
}
});
console.log('Payment sent:', result.transactionHash);
} catch (error) {
console.error('Payment failed:', error);
}
};
return (
);
}
```
## Parameters
### Transaction Object
The first parameter to `sendTransaction` is the transaction object:
The recipient address for the transaction.
**Example:**
```tsx theme={null}
to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
```
The amount of ETH to send in wei, as a hexadecimal string. Omit for contract interactions with no ETH transfer.
**Example:**
```tsx theme={null}
value: '0x38d7ea4c68000' // 0.001 ETH
```
The encoded function call data for contract interactions, as a hexadecimal string.
**Example:**
```tsx theme={null}
data: '0x095ea7b3000000000000000000000000...'
```
The chain ID for the transaction. If not provided, uses the wallet's current chain.
**Example:**
```tsx theme={null}
chainId: 8453 // Base
```
Optional gas limit as a hexadecimal string. MoonKey estimates gas automatically if not provided.
**Example:**
```tsx theme={null}
gasLimit: '0x5208' // 21000 in decimal
```
Optional maximum fee per gas for EIP-1559 transactions, as a hexadecimal string.
**Example:**
```tsx theme={null}
maxFeePerGas: '0x59682f00' // 1.5 gwei
```
Optional maximum priority fee per gas for EIP-1559 transactions, as a hexadecimal string.
**Example:**
```tsx theme={null}
maxPriorityFeePerGas: '0x3b9aca00' // 1 gwei
```
Optional nonce for the transaction. MoonKey determines this automatically if not provided.
**Example:**
```tsx theme={null}
nonce: 5
```
### Options Object
The second parameter is an optional configuration object:
Configuration for the transaction confirmation modal UI.
Custom title for the confirmation modal.
Description text shown in the modal.
Text for the confirm button.
Text for the cancel button.
Additional transaction details to display.
Whether to show the confirmation modal. Set to `false` to send without confirmation.
Specific wallet to use for the transaction. If not provided, uses the user's default wallet.
**Example:**
```tsx theme={null}
wallet: user.wallet
```
## Returns
The `sendTransaction` method returns a Promise that resolves with:
The hash of the broadcasted transaction.
**Example:**
```tsx theme={null}
"0x7c91ba85d67ef92cc15f3e9c8d8c5788e982cf83fabe9bfcc66a747aa0bd3701"
```
The chain ID where the transaction was sent.
A successful return indicates the transaction was **broadcasted** to the network. It may still fail during confirmation. Always verify transaction status onchain or listen for confirmation events.
## Complete Examples
### Basic ETH Transfer
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function SendEthComponent() {
const { user, isAuthenticated } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const [recipientAddress, setRecipientAddress] = useState('');
const [amount, setAmount] = useState('');
const [txHash, setTxHash] = useState(null);
const [error, setError] = useState(null);
const [isSending, setIsSending] = useState(false);
const handleSend = async () => {
if (!user?.wallet) {
setError('No wallet found');
return;
}
if (!recipientAddress || !amount) {
setError('Please fill in all fields');
return;
}
setError(null);
setTxHash(null);
setIsSending(true);
try {
// Convert ETH amount to wei (hex)
const amountInWei = BigInt(parseFloat(amount) * 10**18);
const valueHex = '0x' + amountInWei.toString(16);
const result = await sendTransaction({
to: recipientAddress,
value: valueHex
});
setTxHash(result.transactionHash);
alert('Transaction sent successfully!');
} catch (err: any) {
console.error('Transaction failed:', err);
setError(err.message || 'Transaction failed');
} finally {
setIsSending(false);
}
};
if (!isAuthenticated || !user?.wallet) {
return
);
}
```
## UI Customization
Customize the transaction confirmation modal to match your application's branding:
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
const { sendTransaction } = useSendTransaction();
await sendTransaction({
to: '0x...',
value: '0x38d7ea4c68000'
}, {
uiConfig: {
title: 'Confirm Payment',
description: 'You are about to send 0.001 ETH',
confirmButtonText: 'Send Payment',
cancelButtonText: 'Cancel',
transactionInfo: {
amount: '0.001 ETH',
recipient: 'vitalik.eth',
fee: '~$0.50'
}
}
});
```
### Hide Modal
For certain flows, you may want to send transactions without showing the UI modal:
```tsx theme={null}
await sendTransaction({
to: '0x...',
value: '0x38d7ea4c68000'
}, {
uiConfig: {
showWalletUI: false
}
});
```
Hiding the confirmation modal removes the user's ability to review transaction details before signing. Only use this for trusted operations or after obtaining explicit user consent.
## Working with Viem
MoonKey's embedded wallets are compatible with [viem](https://viem.sh), allowing you to leverage its powerful utilities:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { parseEther, encodeFunctionData } from 'viem';
function ViemExample() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const sendWithViem = async () => {
if (!user?.wallet) return;
// Use viem utilities
const value = parseEther('0.001'); // Returns bigint
const result = await sendTransaction({
to: '0xRecipient...',
value: '0x' + value.toString(16) // Convert to hex
});
console.log('Transaction sent:', result.transactionHash);
};
return ;
}
```
## Error Handling
Handle common transaction errors gracefully:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
function TransactionWithErrorHandling() {
const { user } = useMoonKey();
const { sendTransaction } = useSendTransaction();
const handleTransaction = async () => {
if (!user?.wallet) return;
try {
const result = await sendTransaction({
to: '0x...',
value: '0x38d7ea4c68000'
});
console.log('Success:', result.transactionHash);
} catch (error: any) {
// Handle specific error types
if (error.code === 'USER_REJECTED') {
console.log('User cancelled the transaction');
alert('Transaction cancelled');
} else if (error.code === 'INSUFFICIENT_FUNDS') {
console.log('Insufficient balance');
alert('Not enough ETH for this transaction');
} else if (error.code === 'NETWORK_ERROR') {
console.log('Network error');
alert('Network error. Please try again');
} else {
console.error('Transaction failed:', error);
alert('Transaction failed: ' + error.message);
}
}
};
return ;
}
```
## Best Practices
Validate addresses and amounts before sending transactions:
```tsx theme={null}
import { isAddress } from 'viem';
const handleSend = async () => {
// Validate address
if (!isAddress(recipientAddress)) {
alert('Invalid recipient address');
return;
}
// Validate amount
if (parseFloat(amount) <= 0) {
alert('Amount must be greater than 0');
return;
}
// Proceed with transaction
await sendTransaction({ to: recipientAddress, value: amountInWei });
};
```
Verify sufficient balance before attempting to send:
```tsx theme={null}
import { useWalletBalance } from '@moon-key/react-auth/ethereum';
const { balance } = useWalletBalance();
const handleSend = async () => {
const amountInWei = BigInt(parseFloat(amount) * 10**18);
if (balance && BigInt(balance) < amountInWei) {
alert('Insufficient balance');
return;
}
await sendTransaction({ to: recipient, value: '0x' + amountInWei.toString(16) });
};
```
Keep users informed throughout the transaction process:
```tsx theme={null}
const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
const handleSend = async () => {
setStatus('pending');
try {
const result = await sendTransaction({ to: '0x...', value: '0x38d7ea4c68000' });
setStatus('success');
showToast('Transaction sent! Hash: ' + result.transactionHash);
} catch (error) {
setStatus('error');
showToast('Transaction failed');
}
};
```
Let MoonKey handle gas estimation, but allow overrides when needed:
```tsx theme={null}
// MoonKey handles gas estimation automatically
await sendTransaction({
to: '0x...',
value: '0x38d7ea4c68000'
// gasLimit is optional - only provide if you need a specific value
});
// Or provide custom gas parameters for advanced use cases
await sendTransaction({
to: '0x...',
value: '0x38d7ea4c68000',
gasLimit: '0x5208',
maxFeePerGas: '0x59682f00',
maxPriorityFeePerGas: '0x3b9aca00'
});
```
Wait for transaction confirmations before considering it final:
```tsx theme={null}
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
const handleSend = async () => {
const result = await sendTransaction({ to: '0x...', value: '0x38d7ea4c68000' });
console.log('Transaction broadcasted:', result.transactionHash);
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash: result.transactionHash as `0x${string}`
});
console.log('Transaction confirmed:', receipt.status);
};
```
Help users track their transactions on block explorers:
```tsx theme={null}
const getExplorerUrl = (txHash: string, chainId: number) => {
const explorers: Record = {
1: 'https://etherscan.io',
8453: 'https://basescan.org',
137: 'https://polygonscan.com',
42161: 'https://arbiscan.io'
};
const baseUrl = explorers[chainId] || 'https://etherscan.io';
return `${baseUrl}/tx/${txHash}`;
};
// After transaction
View on Explorer
```
## Troubleshooting
If transactions fail without clear errors:
* Check that the user's wallet has sufficient balance
* Verify the recipient address is valid
* Ensure the contract address is correct for contract interactions
* Check that the encoded data is properly formatted
* Try on a testnet first to debug
```tsx theme={null}
// Add detailed logging
try {
console.log('Sending transaction:', { to, value, data });
const result = await sendTransaction({ to, value, data });
console.log('Transaction sent:', result);
} catch (error) {
console.error('Full error:', error);
console.error('Error details:', JSON.stringify(error, null, 2));
}
```
Handle user cancellations gracefully:
```tsx theme={null}
catch (error: any) {
if (error.code === 'USER_REJECTED' || error.message.includes('User rejected')) {
console.log('User cancelled the transaction');
// Don't show error - this is expected behavior
return;
}
// Handle other errors
alert('Transaction failed: ' + error.message);
}
```
If transactions fail due to gas issues:
* MoonKey usually handles gas estimation automatically
* For complex transactions, consider providing a custom gas limit
* Ensure the wallet has enough ETH for both value + gas
```tsx theme={null}
// Check if error is gas-related
if (error.message.includes('gas') || error.message.includes('insufficient funds')) {
alert('Insufficient ETH for gas. Please add more ETH to your wallet.');
}
```
If a transaction appears stuck:
* Check the network status (congestion, downtime)
* Verify the transaction on a block explorer
* Consider the gas price used - low gas prices can cause delays
* Wait for network confirmation (can take minutes during congestion)
```tsx theme={null}
// Implement a timeout
const TIMEOUT = 60000; // 60 seconds
const sendWithTimeout = async () => {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Transaction timeout')), TIMEOUT)
);
try {
const result = await Promise.race([
sendTransaction({ to: '0x...', value: '0x38d7ea4c68000' }),
timeoutPromise
]);
console.log('Transaction sent:', result);
} catch (error: any) {
if (error.message === 'Transaction timeout') {
alert('Transaction is taking longer than expected. Check your wallet or explorer.');
}
}
};
```
Ensure transactions are sent on the correct network:
```tsx theme={null}
import { useWalletChain } from '@moon-key/react-auth/ethereum';
const { chainId, switchChain } = useWalletChain();
const handleSend = async () => {
const targetChainId = 8453; // Base
if (chainId !== targetChainId) {
try {
await switchChain(targetChainId);
} catch (error) {
alert('Please switch to Base network');
return;
}
}
await sendTransaction({ to: '0x...', value: '0x38d7ea4c68000' });
};
```
## Related Documentation
Sign transactions without broadcasting
Sign arbitrary messages
Customize the transaction confirmation UI
Switch between Ethereum networks
## Next Steps
* Learn about [signing transactions](/wallet-as-a-service/using-wallets/ethereum/sign-transaction) without broadcasting
* Explore [contract interactions](/wallet-as-a-service/using-wallets/ethereum/interfacing-libraries) with viem and ethers
* Understand [chain management](/wallet-as-a-service/using-wallets/ethereum/switch-chains) for multi-network apps
* Customize the [transaction UI](/wallet-as-a-service/ui-components/send-transaction) to match your brand
# Sign EIP-7702 Authorization
Source: https://docs.streambird.io/wallet-as-a-service/using-wallets/ethereum/sign-eip7702-authorization
Sign EIP-7702 authorizations to enable your embedded wallets to gain account abstraction capabilities such as transaction bundling, gas sponsorship, and custom permissions.
[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) enables externally owned accounts (EOAs) to delegate their execution to smart contract code, allowing EOA wallets to function like smart contract wallets.
## Overview
EIP-7702 is a groundbreaking proposal that allows EOA wallets to temporarily delegate their code execution to a smart contract. This enables powerful account abstraction features without requiring users to migrate to new wallet addresses.
### Key Benefits
* **Transaction batching**: Execute multiple operations in a single transaction
* **Gas sponsorship**: Enable gasless transactions for users
* **Custom permissions**: Implement spending limits and approval logic
* **Session keys**: Allow temporary key access with limited permissions
* **Social recovery**: Add recovery mechanisms to EOA wallets
* **Backwards compatibility**: EOAs can revert to normal operation anytime
### How It Works
1. User signs an EIP-7702 authorization delegating to a contract
2. Authorization is included in a transaction
3. During that transaction, the EOA behaves like the delegated contract
4. After the transaction, the EOA returns to normal
5. Authorization can be revoked or updated at any time
## React SDK
To sign an EIP-7702 authorization, use the `sign7702Authorization` method from the `useSign7702Authorization` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSign7702Authorization } from '@moon-key/react-auth/ethereum';
function Sign7702Button() {
const { user } = useMoonKey();
const { sign7702Authorization } = useSign7702Authorization();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const authorization = await sign7702Authorization({
contractAddress: '0x1234567890abcdef1234567890abcdef12345678',
chainId: 1, // Ethereum mainnet
nonce: 0 // Optional
});
console.log('Authorization signed:', authorization);
alert('Successfully signed EIP-7702 authorization!');
// Use authorization with your AA provider
} catch (error) {
console.error('Failed to sign authorization:', error);
}
};
return (
);
}
```
Customize the authorization signing modal:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSign7702Authorization } from '@moon-key/react-auth/ethereum';
function CustomSign7702Button() {
const { user } = useMoonKey();
const { sign7702Authorization } = useSign7702Authorization();
const handleSign = async () => {
if (!user?.wallet) return;
try {
const authorization = await sign7702Authorization({
contractAddress: '0x1234567890abcdef1234567890abcdef12345678',
chainId: 8453, // Base
nonce: 0
}, {
uiConfig: {
title: 'Enable Smart Features',
description: 'Allow your wallet to use advanced features like gas sponsorship and batched transactions',
confirmButtonText: 'Enable Features',
cancelButtonText: 'Cancel'
}
});
console.log('Authorization created:', authorization);
} catch (error) {
console.error('Authorization failed:', error);
}
};
return (
);
}
```
## Parameters
The address of the smart contract whose code the EOA will delegate to. This is typically an account implementation contract from your AA provider.
**Example:**
```tsx theme={null}
contractAddress: '0x1234567890abcdef1234567890abcdef12345678'
```
The chain ID where the authorization will be used.
**Example:**
```tsx theme={null}
chainId: 1 // Ethereum mainnet
```
The nonce for the authorization. If not provided, the current transaction count for the wallet will be used automatically.
**Example:**
```tsx theme={null}
nonce: 0
```
### Options Object
The second parameter is an optional configuration object:
Configuration for the authorization confirmation modal.
Custom title for the confirmation modal.
Description text shown in the modal.
Text for the confirm button.
Text for the cancel button.
Whether to show the confirmation modal. Set to `false` to sign without confirmation.
Specific wallet to use for signing. If not provided, uses the user's default wallet.
## Returns
The signed EIP-7702 authorization object.
The smart contract address the EOA is delegating to.
The chain ID for the authorization.
The nonce used in the authorization.
The r value of the ECDSA signature.
The s value of the ECDSA signature.
The yParity value of the ECDSA signature (0 or 1).
## Complete Examples
### Enable Gas Sponsorship
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSign7702Authorization } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function GasSponsorshipSetup() {
const { user, isAuthenticated } = useMoonKey();
const { sign7702Authorization } = useSign7702Authorization();
const [isEnabled, setIsEnabled] = useState(false);
const [isEnabling, setIsEnabling] = useState(false);
const handleEnable = async () => {
if (!user?.wallet) {
alert('Please connect your wallet first');
return;
}
setIsEnabling(true);
try {
// Your AA provider's account implementation contract
const accountImplementation = '0xGasSponsorshipImplementation...';
// Sign the EIP-7702 authorization
const authorization = await sign7702Authorization({
contractAddress: accountImplementation,
chainId: 8453 // Base
}, {
uiConfig: {
title: 'Enable Gasless Transactions',
description: 'Sign to enable gas sponsorship for your transactions',
confirmButtonText: 'Enable Gasless'
}
});
console.log('Authorization created:', authorization);
// Register with your gas sponsorship service
const response = await fetch('/api/gas-sponsorship/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
walletAddress: user.wallet.address,
authorization,
chainId: 8453
})
});
if (response.ok) {
setIsEnabled(true);
alert('Gasless transactions enabled!');
} else {
throw new Error('Failed to register for gas sponsorship');
}
} catch (error) {
console.error('Failed to enable gas sponsorship:', error);
alert('Failed to enable gasless transactions');
} finally {
setIsEnabling(false);
}
};
if (!isAuthenticated) {
return
Please sign in to enable gas sponsorship
;
}
return (
Gasless Transactions
Enable gas sponsorship to send transactions without paying gas fees
{!isEnabled ? (
) : (
✅ Gas sponsorship is enabled
You can now send transactions without gas fees!
)}
);
}
```
## UI Customization
Customize the authorization signing modal:
```tsx theme={null}
await sign7702Authorization({
contractAddress: '0x...',
chainId: 1
}, {
uiConfig: {
title: 'Enable Smart Features',
description: 'This allows your wallet to use advanced account abstraction features',
confirmButtonText: 'Enable',
cancelButtonText: 'Cancel'
}
});
```
### Hide Modal
```tsx theme={null}
await sign7702Authorization({
contractAddress: '0x...',
chainId: 1
}, {
uiConfig: {
showWalletUI: false
}
});
```
Hiding the modal removes the user's ability to review the authorization before signing. Only use this for trusted operations or after obtaining explicit user consent.
## Best Practices
Always verify the contract address before delegating:
```tsx theme={null}
// ✅ Good - Validate contract
const isValidImplementation = await verifyContract(contractAddress);
if (!isValidImplementation) {
throw new Error('Invalid or untrusted contract');
}
await sign7702Authorization({
contractAddress,
chainId: 1
});
```
Ensure the chain ID matches your target network:
```tsx theme={null}
const CHAIN_IDS = {
mainnet: 1,
base: 8453,
optimism: 10,
arbitrum: 42161
};
// Use correct chain ID
await sign7702Authorization({
contractAddress: accountImplementation,
chainId: CHAIN_IDS.base
});
```
Let MoonKey handle nonces automatically unless you have specific requirements:
```tsx theme={null}
// ✅ Good - Let MoonKey handle nonce
await sign7702Authorization({
contractAddress,
chainId: 1
// nonce is automatically determined
});
// Only specify nonce if you need precise control
await sign7702Authorization({
contractAddress,
chainId: 1,
nonce: specificNonce
});
```
Clearly explain what authorization means:
```tsx theme={null}
const handleEnable = async () => {
const confirmed = confirm(
'This will allow your wallet to use smart contract features. ' +
'You maintain full control and can revoke this at any time. Continue?'
);
if (!confirmed) return;
await sign7702Authorization({
contractAddress,
chainId: 1
});
};
```
Allow users to revoke authorization:
```tsx theme={null}
async function revokeAuthorization(walletAddress: string) {
// Call your AA provider's revocation endpoint
await fetch('/api/aa-provider/revoke', {
method: 'POST',
body: JSON.stringify({ walletAddress })
});
alert('Authorization revoked. Your wallet is back to normal.');
}
```
Provide clear error messages:
```tsx theme={null}
try {
await sign7702Authorization({
contractAddress,
chainId: 1
});
} catch (error: any) {
if (error.code === 'USER_REJECTED') {
console.log('User cancelled authorization');
return;
} else if (error.message?.includes('nonce')) {
alert('Nonce error. Please try again.');
} else if (error.message?.includes('chain')) {
alert('Wrong network. Please switch to the correct network.');
} else {
alert('Failed to authorize: ' + error.message);
}
}
```
## Troubleshooting
Common issues:
* **Wrong contract address**: Verify the account implementation contract
* **Chain ID mismatch**: Ensure authorization chain matches transaction chain
* **Nonce issues**: Let MoonKey handle nonce automatically
* **Contract not compatible**: Ensure contract supports EIP-7702
```tsx theme={null}
// Debug authorization
const authorization = await sign7702Authorization({
contractAddress,
chainId
});
console.log('Authorization details:', {
contract: authorization.contractAddress,
chain: authorization.chainId,
nonce: authorization.nonce
});
```
Handle cancellations gracefully:
```tsx theme={null}
try {
const authorization = await sign7702Authorization({
contractAddress,
chainId: 1
});
} catch (error: any) {
if (error.code === 'USER_REJECTED') {
// User deliberately cancelled - don't show error
console.log('Authorization cancelled by user');
return;
}
// Handle other errors
alert('Authorization failed: ' + error.message);
}
```
Ensure user is on the correct network:
```tsx theme={null}
import { useWalletChain } from '@moon-key/react-auth/ethereum';
const { chainId, switchChain } = useWalletChain();
const targetChainId = 8453; // Base
if (chainId !== targetChainId) {
try {
await switchChain(targetChainId);
} catch (error) {
alert('Please switch to Base network');
return;
}
}
await sign7702Authorization({
contractAddress,
chainId: targetChainId
});
```
Validate contract addresses:
```tsx theme={null}
import { isAddress } from 'viem';
if (!isAddress(contractAddress)) {
alert('Invalid contract address');
return;
}
// Optionally verify contract is deployed
const code = await publicClient.getBytecode({
address: contractAddress as `0x${string}`
});
if (!code || code === '0x') {
alert('Contract not found at this address');
return;
}
await sign7702Authorization({
contractAddress,
chainId: 1
});
```
## Related Documentation
Official EIP-7702 documentation
Sign transactions without broadcasting
Send transactions to the network
Sign messages for authentication
## Next Steps
* Learn about [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) specification details
* Explore account abstraction providers and their implementations
* Understand how to [batch transactions](https://docs.alchemy.com/docs/bundler-api-quickstart) with AA
* Implement [gas sponsorship](https://docs.pimlico.io/paymaster) for your users
* Set up [session keys](https://docs.zerodev.app/sdk/advanced/session-keys) for automated transactions
# Sign Message
Source: https://docs.streambird.io/wallet-as-a-service/using-wallets/ethereum/sign-message
Sign arbitrary messages with a user's embedded Ethereum wallet using the `personal_sign` method. Message signing is commonly used for authentication, proof of ownership, and off-chain authorization.
This method uses Ethereum's `personal_sign` RPC method, which prefixes messages with `"\x19Ethereum Signed Message:\n"` before signing. For raw hash signatures, see [Sign Raw Hash](/wallet-as-a-service/using-wallets/ethereum/sign-raw-hash).
## Overview
The `useSignMessage` hook from MoonKey's React SDK provides a simple interface for signing messages. Common use cases include:
* **User authentication**: Prove wallet ownership without passwords
* **Off-chain authorization**: Sign permissions without gas fees
* **Proof of ownership**: Verify control of an Ethereum address
* **Session tokens**: Generate authenticated session credentials
## React SDK
To sign a message, use the `signMessage` method from the `useSignMessage` hook:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
function SignMessageButton() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage();
const handleSign = async () => {
if (!user?.wallet) {
alert('No wallet found');
return;
}
try {
const result = await signMessage({
message: 'Welcome to My dApp! Sign this message to authenticate.'
});
console.log('Message signed:', result.signature);
alert('Message signed successfully!');
} catch (error) {
console.error('Signing failed:', error);
}
};
return (
);
}
```
Use callbacks to handle success and error cases:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
function SignWithCallbacks() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage({
onSuccess: ({ signature }) => {
console.log('Signature:', signature);
alert('Successfully signed message!');
// Submit signature to your backend
fetch('/api/verify-signature', {
method: 'POST',
body: JSON.stringify({ signature })
});
},
onError: (error) => {
console.error('Signing error:', error);
alert('Failed to sign message');
}
});
const handleSign = async () => {
if (!user?.wallet) return;
await signMessage({
message: 'Authenticate with My App'
});
};
return (
);
}
```
Customize the signing modal:
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
function CustomSignButton() {
const { user } = useMoonKey();
const { signMessage } = useSignMessage();
const handleSign = async () => {
if (!user?.wallet) return;
try {
const result = await signMessage({
message: 'I vote for Proposal #42'
}, {
uiConfig: {
title: 'Sign Your Vote',
description: 'You are voting for Proposal #42',
confirmButtonText: 'Sign Vote',
cancelButtonText: 'Cancel'
}
});
console.log('Vote signed:', result.signature);
} catch (error) {
console.error('Vote signing failed:', error);
}
};
return (
);
}
```
## Parameters
### Message Object
The first parameter to `signMessage`:
The message to sign with the wallet. Can be any string.
**Example:**
```tsx theme={null}
message: 'Welcome to My dApp! Please sign to authenticate.'
```
### Options Object
The second parameter is an optional configuration object:
Configuration for the signing confirmation modal UI.
Custom title for the confirmation modal.
Description text shown in the modal.
Text for the confirm button.
Text for the cancel button.
Whether to show the confirmation modal. Set to `false` to sign without confirmation.
Specific wallet to use for signing. If not provided, uses the user's default wallet.
**Example:**
```tsx theme={null}
wallet: user.wallet
```
### Hook Callbacks
Configure callbacks when initializing the hook:
Callback executed after a user successfully signs a message. Receives the signature.
**Example:**
```tsx theme={null}
const { signMessage } = useSignMessage({
onSuccess: ({ signature }) => {
console.log('Signed:', signature);
submitToBackend(signature);
}
});
```
Callback executed if signing fails or user cancels. Receives the error.
**Example:**
```tsx theme={null}
const { signMessage } = useSignMessage({
onError: (error) => {
console.error('Failed:', error);
showErrorNotification(error.message);
}
});
```
## Returns
The `signMessage` method returns a Promise that resolves with:
The hex-encoded signature produced by the wallet.
**Example:**
```tsx theme={null}
"0x9c29f0c3a6f3e55f8f7b5e9f8c1b8e2a4d5c6b7a8e9f0c1b2a3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1b"
```
The encoding format of the signature. Currently always `'hex'` for Ethereum.
## Complete Examples
### User Authentication
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignMessage } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
export default function AuthenticateWithSignature() {
const { user, isAuthenticated } = useMoonKey();
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authToken, setAuthToken] = useState(null);
const { signMessage } = useSignMessage({
onSuccess: async ({ signature }) => {
console.log('Signature obtained:', signature);
// Send signature to backend for verification
try {
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
address: user?.wallet?.address,
signature,
message: 'Sign in to My dApp'
})
});
const data = await response.json();
setAuthToken(data.token);
alert('Authentication successful!');
} catch (error) {
console.error('Backend verification failed:', error);
alert('Authentication failed');
} finally {
setIsAuthenticating(false);
}
},
onError: (error) => {
console.error('Signing failed:', error);
alert('Authentication cancelled or failed');
setIsAuthenticating(false);
}
});
const handleAuthenticate = async () => {
if (!user?.wallet) {
alert('Please connect your wallet first');
return;
}
setIsAuthenticating(true);
await signMessage({
message: 'Sign in to My dApp'
}, {
uiConfig: {
title: 'Sign In',
description: 'Sign this message to authenticate with My dApp',
confirmButtonText: 'Sign In'
}
});
};
if (!isAuthenticated) {
return
);
}
```
## Broadcasting Signed Transactions
After signing a transaction, you can broadcast it using Solana's connection:
```tsx theme={null}
import { Connection } from '@solana/web3.js';
// Broadcast signed transaction
const connection = new Connection('https://api.devnet.solana.com');
const signature = await connection.sendRawTransaction(signedTransaction);
console.log('Transaction signature:', signature);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) {
console.error('Transaction failed');
} else {
console.log('Transaction confirmed!');
}
```
### With Custom Commitment
```tsx theme={null}
// Use different commitment levels
await connection.confirmTransaction(signature, 'processed'); // Fast, less secure
await connection.confirmTransaction(signature, 'confirmed'); // Balanced
await connection.confirmTransaction(signature, 'finalized'); // Slow, most secure
```
## Transaction Serialization
### Serialize to Base64
```tsx theme={null}
import { Transaction } from '@solana/web3.js';
// Convert signed transaction to base64
const transaction = Transaction.from(signedTransaction);
const base64Tx = Buffer.from(signedTransaction).toString('base64');
console.log('Base64 transaction:', base64Tx);
```
### Deserialize from Base64
```tsx theme={null}
// Convert base64 back to transaction
const txBuffer = Buffer.from(base64Tx, 'base64');
const transaction = Transaction.from(txBuffer);
```
## UI Customization
Customize the signing modal:
```tsx theme={null}
await signTransaction({
transaction
}, {
uiConfig: {
title: 'Sign Transaction',
description: 'Review and sign this transaction',
confirmButtonText: 'Sign',
cancelButtonText: 'Cancel',
transactionInfo: {
action: 'Transfer SOL',
cost: '0.001 SOL'
}
}
});
```
### Hide Modal
```tsx theme={null}
await signTransaction({
transaction
}, {
uiConfig: {
showWalletUI: false
}
});
```
Hiding the modal removes the user's ability to review the transaction before signing. Only use this for trusted operations or after obtaining explicit user consent.
## Best Practices
Always fetch a recent blockhash before signing:
```tsx theme={null}
// ✅ Good - Fresh blockhash
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
// ❌ Bad - Reusing old blockhash
// Blockhashes expire after ~60 seconds
```
Always specify the fee payer:
```tsx theme={null}
transaction.feePayer = new PublicKey(user.wallet.address);
```
If storing signed transactions, use secure storage:
```tsx theme={null}
// Encrypt before storing
const encrypted = encryptData(signedTransaction);
localStorage.setItem('signedTx', encrypted);
// Decrypt when broadcasting
const decrypted = decryptData(localStorage.getItem('signedTx'));
await connection.sendRawTransaction(decrypted);
```
Signed transactions can expire if blockhash is old:
```tsx theme={null}
try {
const signature = await connection.sendRawTransaction(signedTransaction);
} catch (error: any) {
if (error.message?.includes('blockhash')) {
alert('Transaction expired. Please sign again.');
// Re-sign with fresh blockhash
}
}
```
Check transaction validity before signing:
```tsx theme={null}
// Validate recipient address
if (!isValidSolanaAddress(recipient)) {
alert('Invalid recipient address');
return;
}
// Check balance
const balance = await connection.getBalance(
new PublicKey(user.wallet.address)
);
if (balance < transferAmount) {
alert('Insufficient balance');
return;
}
// Now sign
await signTransaction({ transaction });
```
Always wait for confirmation after broadcasting:
```tsx theme={null}
const signature = await connection.sendRawTransaction(signedTransaction);
// Wait for confirmation with timeout
const confirmation = await connection.confirmTransaction(
signature,
'confirmed'
);
if (confirmation.value.err) {
throw new Error('Transaction failed');
}
```
## Troubleshooting
Signed transactions expire if blockhash is too old:
```tsx theme={null}
// Ensure fresh blockhash when signing
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
// Check if still valid before broadcasting
const currentBlockHeight = await connection.getBlockHeight();
if (currentBlockHeight > lastValidBlockHeight) {
alert('Transaction expired, please sign again');
return;
}
await connection.sendRawTransaction(signedTransaction);
```
Ensure transaction is properly constructed:
```tsx theme={null}
// ✅ Correct - Set all required fields
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(user.wallet.address);
// All signers must be specified
transaction.sign(); // If additional signers needed
```
Use proper serialization/deserialization:
```tsx theme={null}
import { Transaction } from '@solana/web3.js';
// Serialize
const serialized = signedTransaction;
// Deserialize
const transaction = Transaction.from(serialized);
```
Handle cancellations gracefully:
```tsx theme={null}
const { signTransaction } = useSignTransaction({
onError: (error: any) => {
if (error.code === 'USER_REJECTED') {
console.log('User cancelled signing');
return;
}
console.error('Signing error:', error);
}
});
```
## Related Documentation
Sign and broadcast transactions
Sign messages with Solana wallet
Official Solana JavaScript library
Solana transaction structure
## Next Steps
* Learn about [sending transactions](/wallet-as-a-service/using-wallets/solana/send-transaction)
* Understand [Solana's transaction lifecycle](https://docs.solana.com/developing/programming-model/transactions)
* Explore [transaction confirmation strategies](https://docs.solana.com/developing/clients/jsonrpc-api#confirming-transactions)
* Implement [transaction retry logic](https://docs.solana.com/developing/transaction_confirmation) for reliability
# Solana Web3.js Integration
Source: https://docs.streambird.io/wallet-as-a-service/using-wallets/solana/solana-web3-js
MoonKey wallets are fully compatible with `@solana/web3.js`, allowing you to use standard Solana development patterns and tools.
## Overview
MoonKey's embedded Solana wallets work seamlessly with the official Solana JavaScript library. You can:
* Use standard `Transaction` and `Connection` classes
* Leverage existing Solana tools and libraries
* Follow familiar Solana development patterns
* Integrate with Solana ecosystem libraries
MoonKey wallets implement the Solana wallet standard, making them compatible with any library that supports standard Solana wallets.
## Basic Usage
```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useWallets } from '@moon-key/react-auth/solana';
import {
Connection,
Transaction,
SystemProgram,
PublicKey,
LAMPORTS_PER_SOL
} from '@solana/web3.js';
function SolanaWeb3Example() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const handleTransaction = async () => {
if (wallets.length === 0) return;
const wallet = wallets[0];
// Create connection
const connection = new Connection('https://api.devnet.solana.com');
// Create transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(wallet.address),
toPubkey: new PublicKey('RecipientAddressHere...'),
lamports: 0.001 * LAMPORTS_PER_SOL
})
);
// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(wallet.address);
// Serialize transaction
const serializedTx = transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
});
// Sign and send using MoonKey hooks
// (Use useSendTransaction hook as shown in other examples)
};
return (
);
}
```
## Common Use Cases
### Get Account Balance
```tsx theme={null}
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
async function getBalance(address: string) {
const connection = new Connection('https://api.devnet.solana.com');
const publicKey = new PublicKey(address);
const balance = await connection.getBalance(publicKey);
const solBalance = balance / LAMPORTS_PER_SOL;
console.log(`Balance: ${solBalance} SOL`);
return solBalance;
}
```
### Get Account Info
```tsx theme={null}
import { Connection, PublicKey } from '@solana/web3.js';
async function getAccountInfo(address: string) {
const connection = new Connection('https://api.devnet.solana.com');
const publicKey = new PublicKey(address);
const accountInfo = await connection.getAccountInfo(publicKey);
if (accountInfo) {
console.log('Owner:', accountInfo.owner.toBase58());
console.log('Lamports:', accountInfo.lamports);
console.log('Executable:', accountInfo.executable);
}
return accountInfo;
}
```
### SPL Token Operations
```tsx theme={null}
import { Connection, PublicKey } from '@solana/web3.js';
import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';
async function getTokenBalance(
walletAddress: string,
tokenMintAddress: string
) {
const connection = new Connection('https://api.devnet.solana.com');
const walletPublicKey = new PublicKey(walletAddress);
const mintPublicKey = new PublicKey(tokenMintAddress);
// Get associated token account
const tokenAccount = await getAssociatedTokenAddress(
mintPublicKey,
walletPublicKey
);
// Get token account info
const accountInfo = await getAccount(connection, tokenAccount);
console.log('Token balance:', accountInfo.amount.toString());
return accountInfo.amount;
}
```
## Working with Programs
```tsx theme={null}
import {
Connection,
Transaction,
TransactionInstruction,
PublicKey
} from '@solana/web3.js';
async function callProgram(
walletAddress: string,
programId: string
) {
const connection = new Connection('https://api.devnet.solana.com');
// Create instruction
const instruction = new TransactionInstruction({
keys: [
{
pubkey: new PublicKey(walletAddress),
isSigner: true,
isWritable: true
}
],
programId: new PublicKey(programId),
data: Buffer.from([]) // Your instruction data
});
// Create transaction
const transaction = new Transaction().add(instruction);
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(walletAddress);
return transaction;
}
```
## Complete Example
Here's a complete example showing balance checking and transfers:
```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction, useWallets } from '@moon-key/react-auth/solana';
import {
Connection,
Transaction,
SystemProgram,
PublicKey,
LAMPORTS_PER_SOL
} from '@solana/web3.js';
import { useState, useEffect } from 'react';
export default function SolanaWeb3Integration() {
const { user } = useMoonKey();
const { wallets } = useWallets();
const { sendTransaction } = useSendTransaction();
const [balance, setBalance] = useState(0);
useEffect(() => {
if (wallets.length > 0) {
fetchBalance();
}
}, [wallets]);
const fetchBalance = async () => {
if (wallets.length === 0) return;
const connection = new Connection('https://api.devnet.solana.com');
const publicKey = new PublicKey(wallets[0].address);
const lamports = await connection.getBalance(publicKey);
setBalance(lamports / LAMPORTS_PER_SOL);
};
const handleTransfer = async (recipient: string, amount: number) => {
if (wallets.length === 0) return;
const wallet = wallets[0];
const connection = new Connection('https://api.devnet.solana.com');
// Create transaction using @solana/web3.js
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(wallet.address),
toPubkey: new PublicKey(recipient),
lamports: amount * LAMPORTS_PER_SOL
})
);
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(wallet.address);
try {
// Send using MoonKey
const signature = await sendTransaction({
transaction,
connection
});
console.log('Transfer successful:', signature);
// Refresh balance
await fetchBalance();
} catch (error) {
console.error('Transfer failed:', error);
}
};
if (!user || wallets.length === 0) {
return
Please connect your wallet
;
}
return (
Solana Web3.js Integration
Address: {wallets[0].address}
Balance: {balance.toFixed(4)} SOL
);
}
```
## Install Dependencies
```bash npm theme={null}
npm install @solana/web3.js @solana/spl-token
```
```bash pnpm theme={null}
pnpm install @solana/web3.js @solana/spl-token
```
```bash yarn theme={null}
yarn add @solana/web3.js @solana/spl-token
```
## Related Documentation
Use MoonKey's native transaction sending
Sign transactions without broadcasting
Official Solana JavaScript library
SPL Token program documentation
## Next Steps
* Learn about [Solana's programming model](https://docs.solana.com/developing/programming-model/overview)
* Explore [SPL token operations](https://spl.solana.com/token)
* Understand [Solana account model](https://docs.solana.com/developing/programming-model/accounts)
* Build with [Anchor framework](https://www.anchor-lang.com/)
# Wallets Overview
Source: https://docs.streambird.io/wallet-as-a-service/wallets-overview
MoonKey provides wallet infrastructure that empowers users to transact on Ethereum, Solana, and all EVM- and SVM-compatible blockchains. Create self-custodial embedded wallets for your users that are directly integrated into your application—no browser extensions or separate wallet apps required.
These embedded wallets can be automatically created during user authentication or manually created when needed, giving you complete flexibility in how you onboard users to web3. Users maintain full custody of their wallets without needing to manage seed phrases or private keys, though they can export their keys at any time for maximum portability.
MoonKey embedded wallets use distributed key sharding to ensure only the rightful owner can control their wallet or access its keys. Neither MoonKey nor your application ever sees the user's complete private key.
## Embedded wallets
MoonKey's embedded wallet system lets you build self-custodial wallets directly into your app. Users get a seamless blockchain experience without the technical complexity of traditional wallet management.
### How it works
When a user authenticates with your app, MoonKey can automatically create an embedded wallet for them based on your configuration. The wallet's private key is securely sharded across multiple locations using distributed key management, ensuring that:
* **Users maintain full custody** - Only the user can access their complete private key
* **No seed phrase management** - Users don't need to write down or remember recovery phrases
* **Seamless experience** - Wallets work instantly without any setup
* **Maximum portability** - Users can export their private key at any time
### Key features
Create and manage wallets on Ethereum, Solana, and all EVM/SVM-compatible chains including Base, Polygon, Arbitrum, and more.
Users have complete control over their wallets. Private keys are securely sharded and only reconstituted for signing operations.
Configure wallets to be created automatically when users log in, or create them manually when needed.
Simple APIs for sending transactions, signing messages, and interacting with smart contracts.
Users can export their private keys to use their wallet in other applications like MetaMask or Phantom.
Pre-built UI components for signing, transactions, and key export with full customization options.
## User wallets
MoonKey specializes in creating self-custodial embedded wallets for your users. This means users have a blockchain wallet experience that feels native to your application.
### Benefits for users
**No technical complexity**
* No seed phrases to write down or store
* No browser extensions to install
* No separate apps to download
* Seamless authentication and wallet access
**Full ownership**
* Complete control over their assets
* Ability to export private keys
* Freedom to move to other wallets
* Self-custodial by default
**Instant onboarding**
* Wallets created in seconds
* No blockchain knowledge required
* Start transacting immediately
* Familiar login methods (email, OAuth)
### Benefits for developers
**Simple integration**
* Easy-to-use React SDK
* Pre-built UI components
* Comprehensive REST API
* TypeScript support
**Flexible configuration**
* Automatic or manual wallet creation
* Customizable UI components
* Multiple authentication methods
* Support for multiple chains
**Production ready**
* Secure key management
* High uptime and reliability
* Transaction broadcasting
* Error handling
## Wallet creation
MoonKey offers two approaches to wallet creation:
### Automatic wallet creation
Configure MoonKey to automatically create wallets when users authenticate:
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
**When to use:**
* Your app requires all users to have a wallet
* You want the simplest onboarding experience
* Users will interact with blockchain features immediately
Learn more about [automatic wallet creation](/get-started/frontend-sdks/react/advance/automatic-wallet-creation).
### Manual wallet creation
Create wallets programmatically when needed:
```tsx theme={null}
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreate = async () => {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
};
return ;
}
```
**When to use:**
* Not all users need a wallet
* Users opt-in to blockchain features
* You want to delay wallet creation for UX reasons
## Multi-chain support
MoonKey wallets work across multiple blockchain ecosystems:
### Ethereum and EVM chains
Create wallets compatible with:
* **Ethereum** - Mainnet and testnets
* **Layer 2s** - Base, Arbitrum, Optimism, Polygon
* **Sidechains** - Polygon, Gnosis Chain, and more
* **Custom EVMs** - Any EVM-compatible network
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
import { base, polygon, arbitrum } from 'viem/chains';
{children}
```
### Solana and SVM chains
Create wallets compatible with:
* **Solana** - Mainnet, Devnet, Testnet
* **Custom SVM** - SVM-compatible networks
```tsx theme={null}
import { MoonKeyProvider } from '@moon-key/react-auth';
{children}
```
Learn more about [configuring networks](/get-started/frontend-sdks/react/advance/configure-evm-networks).
## Wallet operations
MoonKey provides simple APIs for common wallet operations:
### Sign messages
Sign arbitrary messages for authentication or verification:
```tsx theme={null}
import { useSignMessage } from '@moon-key/react-auth/ethereum';
const { signMessage } = useSignMessage();
const result = await signMessage({
message: 'Welcome to MyApp!',
wallet: user.wallet
});
```
### Sign transactions
Sign transactions without broadcasting them:
```tsx theme={null}
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
const { signTransaction } = useSignTransaction();
const result = await signTransaction({
transaction: {
to: '0xRecipient...',
value: '0x38d7ea4c68000'
},
wallet: user.wallet
});
```
### Send transactions
Sign and broadcast transactions in one step:
```tsx theme={null}
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
const { sendTransaction } = useSendTransaction();
const result = await sendTransaction({
transaction: {
to: '0xRecipient...',
value: '0x38d7ea4c68000'
},
wallet: user.wallet
});
console.log('Transaction hash:', result.transactionHash);
```
### Export private key
Allow users to export their private key:
```tsx theme={null}
import { useExportPrivateKey } from '@moon-key/react-auth/ethereum';
const { exportPrivateKey } = useExportPrivateKey();
await exportPrivateKey({
wallet: user.wallet,
uiConfig: {
title: 'Export Private Key',
privateKeyDescription: 'Your private key provides full access to your wallet'
}
});
```
Learn more about [wallet operations](/authentication/user-authentication/ui-components/overview).
## UI components
MoonKey provides pre-built UI components for all wallet operations:
Message signing interface
Transaction signing UI
Transaction sending interface
Private key export UI
All UI components are fully customizable to match your brand. Learn more about [UI components](/authentication/user-authentication/ui-components/overview).
## Headless wallet operations
For complete control over your UI, use MoonKey's headless wallet operations. Build custom interfaces while MoonKey handles the security and blockchain complexity.
Learn more about [whitelabel wallet operations](/wallet-as-a-service/whitelabel/overview).
## Security and custody
MoonKey wallets are designed with security as the top priority:
### Distributed key sharding
Private keys are split into multiple shards that are distributed across different secure locations. The complete key is only reconstituted in a secure environment when needed for signing operations.
**Benefits:**
* No single point of failure
* Neither MoonKey nor your app sees complete keys
* Users maintain full custody
* Resistant to common attack vectors
### User control
Users have complete control over their wallets:
* **Self-custodial** - Users own their keys, not MoonKey or your app
* **Exportable** - Users can export private keys at any time
* **Portable** - Use exported keys in any compatible wallet
* **No lock-in** - Users can leave your platform with their assets
### Secure operations
All wallet operations follow security best practices:
* **Transaction signing** - Performed in secure environments
* **Key storage** - Encrypted and sharded across locations
* **API authentication** - Secure session tokens and API keys
* **User confirmation** - Clear UI for approving operations
## Common use cases
### Gaming
Create wallets for gamers to own in-game assets:
* Automatically create wallets on signup
* Seamless NFT minting and trading
* In-game token transactions
* Cross-game asset portability
### DeFi applications
Enable users to interact with DeFi protocols:
* Swap tokens
* Provide liquidity
* Stake assets
* Yield farming
### NFT marketplaces
Let users buy, sell, and trade NFTs:
* Mint NFTs directly in your app
* List items for sale
* Make offers and bids
* Transfer NFTs between wallets
### Social applications
Add blockchain features to social apps:
* Token-gated communities
* Reward systems
* Tipping and micropayments
* Proof of ownership
### E-commerce
Accept crypto payments seamlessly:
* Checkout with crypto
* Issue refunds
* Loyalty tokens
* Gift cards and vouchers
## REST API
For server-side wallet management, MoonKey provides a comprehensive REST API:
```bash theme={null}
# Create a wallet
curl -X POST https://api.moonkey.fun/v1/wallets/create \
-H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"wallet_type": "ethereum",
"user_id": "user_123"
}'
```
Learn more about the [REST API](/get-started/rest-api/setup).
## Comparison with external wallets
| Feature | MoonKey Embedded Wallets | External Wallets (MetaMask, Phantom) |
| ------------------- | ----------------------------- | ------------------------------------ |
| **Setup** | Automatic, no installation | Requires browser extension or app |
| **User experience** | Seamless, built into your app | Separate popup/app |
| **Custody** | Self-custodial by user | Self-custodial by user |
| **Seed phrases** | Optional (can export key) | Required for user to manage |
| **Onboarding** | Instant, with email/OAuth | Must have wallet already |
| **UX control** | Full control over UI | Limited customization |
| **Cross-device** | Works anywhere user logs in | Requires extension on each device |
You can support both embedded and external wallets in the same app. Let users choose their preferred wallet experience.
## Best practices
* Use automatic creation for apps where all users need wallets
* Use manual creation for apps with optional blockchain features
* Consider user onboarding flow and experience
* Clearly explain that users own their wallets
* Show how to export private keys
* Explain what self-custodial means
* Provide wallet security education
* Show clear error messages for failed transactions
* Explain insufficient balance errors
* Guide users on how to fund wallets
* Provide transaction status updates
* Use Sepolia (Ethereum) or Devnet (Solana) for development
* Test all transaction flows thoroughly
* Verify gas estimation and fees
* Check error handling scenarios
* Test wallet operations on mobile devices
* Ensure UI is responsive
* Consider mobile network speeds
* Provide mobile-friendly interfaces
## Get started
Install the MoonKey React SDK
Configure MoonKey in your app
Build your first wallet integration
Configure automatic wallet creation
Use pre-built wallet UI components
Manage wallets from your backend
## Next steps
Set up custom networks and RPC providers
Learn about signing and transactions
Build custom wallet UIs
Secure your wallet integration