> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magic.link/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript SDK Reference

> The Magic SDK for JavaScript is your entry-point to secure, passwordless authentication for your application. This guide will cover some important topics for getting started with JavaScript APIs and to make the most of Magic's features.

## Getting Started

The Magic class is the entry-point to the Magic SDK. It must be instantiated with a Magic **publishable** key.

### Installation

To use Magic in your application, install the `magic-sdk` dependency.

<Info>
  **NOTE**

  ⁠If you're integrating with [OAuth](/embedded-wallets/sdk/client-side/javascript#oauth-module), additional dependencies are needed. You'll find the installation instructions in its dedicated section within this documentation.
</Info>

<CodeGroup>
  ```bash NPM icon="npm" theme={null}
  ⁠npm install magic-sdk
  ```

  ```bash Yarn icon="yarn" theme={null}
  yarn add magic-sdk
  ```
</CodeGroup>

### Constructor

`Magic()`

Configure and construct your Magic SDK instance.

|                            |                  |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Parameter                  | Type             | Definition                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `apiKey`                   | String           | Your **publishable** API key retrieved from the Magic Dashboard.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `options.locale?`          | String           | Customize the language of Magic's modal, email and confirmation screen. See [Localization](/embedded-wallets/authentication/customization/localization) for more.                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `options.network?`         | String \| Object | (String): A representation of the connected Ethereum network (mainnet or goerli).<br /><br />⁠(Object): A custom Ethereum Node configuration with the following shape:<br /><br />⁠`rpcUrl` (String): A URL pointing to your custom Ethereum Node.⁠⁠<br /><br />⁠`chainId?` (Number): Some Node infrastructures require you to pass an explicit chain ID. If you are aware that your Node requires this configuration, pass it here as an integer.<br /><br /><Note>**EVM Extension**: When using the EVM Extension, the `network` parameter is not required. Network configuration is handled through the EVM Extension instead.</Note> |
| `options.endpoint?`        | String           | A URL pointing to the Magic `<iframe>` application.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `options.deferPreload?`    | Boolean          | An optional flag to delay the loading of the Magic Iframe's static assets until an SDK function is explicitly invoked. ⁠⁠⁠Set this to true if latency bottlenecks are a concern.                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `options.useStorageCache?` | Boolean          | An optional flag to allow the usage of the local storage as cache. Currently it is only used for faster calls to `isLoggedIn`. When set to `true`, the `magic.user.onUserLoggedOut` event listener needs to be used.                                                                                                                                                                                                                                                                                                                                                                                                                     |

### Initialization

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

let magic;

// Construct with an API key:
magic = new Magic('PUBLISHABLE_API_KEY');

// Construct with an API key and locale:
magic = new Magic('PUBLISHABLE_API_KEY', { locale: 'es' });

// Construct with an API key and defer the loading of the Magic Iframe's assets
magic = new Magic('PUBLISHABLE_API_KEY', { deferPreload: true });

// Construct with an API key plus options:
magic = new Magic('PUBLISHABLE_API_KEY', { network: 'sepolia', endpoint: '...' });

// Construct with API key and custom node options:
const customNodeOptions = {
  rpcUrl: 'https://polygon-rpc.com', // your ethereum, polygon, or optimism mainnet/testnet rpc URL
  chainId: 137 // corresponding chainId for your rpc url
}

magic = new Magic('PUBLISHABLE_API_KEY', {
  network: customNodeOptions, // connected to Polygon Mainnet!
});
```

### Global Methods

Global methods and properties are accessible on the Magic SDK instance itself.

#### `preload`

Starts downloading the static assets required to render the Magic iframe context.

<Info>
  **NOTE**

  As of magic-sdk version [21.0.0](https://www.npmjs.com/package/magic-sdk/v/21.0.0), the SDK constructor will preload the iframe's static assets by default, unless the `deferPreload` flag is passed into the constructor options. See [Release Notes](https://github.com/magiclabs/magic-js/releases/tag/magic-sdk%4021.0.0)
</Info>

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY', { deferPreload: true });

// ...

magic.preload;
```

## Auth Module

The Auth Module and it's members are accessible on the Magic SDK instance by the `auth` property.

### `loginWithEmailOTP`

Authenticate a user passwordlessly using an email one-time code sent to the specified user's email address.

**Arguments**

* `email` (String): The user email to log in with
* `lifespan?` (Number): Set the lifespan of the resolved Decentralize ID token. Defaults to 900s (15 mins)
* `showUI?` (Boolean): If `true`, show an out-of-the-box UI to accept the OTP from user. Defaults to `true`
* `deviceCheckUI?` (Boolean): The default value is `true`. It shows Magic branded UI securing sign-ins from new devices. If set to `false`, the UI will remain hidden. However, this false value only takes effect when you have also set `showUI: false`. If you enable Device Verification in the Magic Dashboard and are passing `showUI: false` you must also explicitly pass `deviceCheckUI: false`

⁠

Available from `magic-sdk@19.1.0`

**Returns**

* `PromiEvent<string | null>`: The promise resolves upon authentication request success and rejects with a specific error code if the request fails. The resolved value is a Decentralized ID token with a default 15-minute lifespan.

**Example**

```javascript JavaScript icon="square-js" theme={null}

import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

// log in a user by their email
try {
await magic.auth.loginWithEmailOTP({ email: 'hello@example.com' });
} catch {
// Handle errors if required!
}

// log in a user by their email, without showing an out-of-the box UI.
try {
await magic.auth.loginWithEmailOTP({ email: 'hello@example.com', showUI: false });
} catch {
// Handle errors if required!
}
```

**Event Handling**

A white-label OTP login flow is available when passing `showUI: false` to this login method. Here's a short example to illustrate listening for and emitting events during the login flow:

```javascript JavaScript icon="square-js" theme={null}
import {
  Magic,
  LoginWithEmailOTPEventOnReceived,
  LoginWithEmailOTPEventEmit,
  RecencyCheckEventOnReceived,
  RecencyCheckEventEmit,
  DeviceVerificationEventEmit,
} from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  // Initiate login flow
  const handle = magic.auth.loginWithEmailOTP({ email: "hello@example.com", showUI: false, deviceCheckUI: false });

  handle
  .on(LoginWithEmailOTPEventOnReceived.EmailOTPSent, () => {
    // The email has been sent to the user

    // Prompt the user for the OTP
    const otp = window.prompt('Enter Email OTP');

    // Send the OTP for verification
    handle.emit(LoginWithEmailOTPEventEmit.VerifyEmailOtp, otp);
  })
  .on(LoginWithEmailOTPEventOnReceived.InvalidEmailOtp, () => {
    // User entered invalid OTP

    // Have the user retry entering the OTP and emit via VerifyEmailOtp.
    // You can limit retries and emit Cancel to cancel login flow.

    // cancel login request
    handle.emit(LoginWithEmailOTPEventEmit.Cancel);
  })
  .on('done', (result) => {
    // is called when the Promise resolves

    // convey login success to user
    alert('Login complete!');

    // DID Token returned in result
    const didToken = result;
  })
  .on('error', (reason) => {
    // is called if the Promise rejects
    console.error(reason);
  })
  .on('settled', () => {
    // is called when the Promise either resolves or rejects
  })

  //** MFA Verification Events (if enabled for app)

  .on(LoginWithEmailOTPEventOnReceived.MfaSentHandle, () => {
    // Prompt the user for the MFA TOTP
    const mfa_totp = window.prompt('Enter MFA TOTP');

    // Send the MFA TOTP for verification
    handle.emit(LoginWithEmailOTPEventEmit.VerifyMfaCode, mfa_totp)
  })
  .on(LoginWithEmailOTPEventOnReceived.InvalidMfaOtp, () => {
    // User entered invalid OTP

    // Have the user retry entering the MFA OTP and emit via VerifyMfaCode.
    // You can limit retries and emit Cancel to cancel login flow.

    // cancel login request
    handle.emit(LoginWithEmailOTPEventEmit.Cancel);
  })

  //** Device Verification Events (if enabled for app)

  .on(DeviceVerificationEventOnReceived.DeviceNeedsApproval, () => {
    // is called when device is not recognized and requires approval
  })
  .on(DeviceVerificationEventOnReceived.DeviceVerificationEmailSent, () => {
    // is called when the device verification email is sent
  })
  .on(DeviceVerificationEventOnReceived.DeviceApproved, () => {
    // is called when the device has been approved
  })
  .on(DeviceVerificationEventOnReceived.DeviceVerificationLinkExpired, () => {
    // is called when the device verification link is expired

    // Retry device verification
    handle.emit(DeviceVerificationEventEmit.Retry);
  });

  // LoginWithEmailOTPEventEmit.Cancel can always be emitted to terminate the unresolved request.

} catch (err) {
  // handle errors
}
```

**Event Enums**

**Email OTP**

|                     |                                                                                 |
| ------------------- | ------------------------------------------------------------------------------- |
| Event Name          | Definition                                                                      |
| `email-otp-sent`    | Dispatched when the OTP email has been successfully sent from the Magic server. |
| `verify-email-otp`  | Emit along with the OTP to verify the code from user.                           |
| `invalid-email-otp` | Dispatched when the OTP sent fails verification.                                |
| `cancel`            | Emit to cancel the login request.                                               |

**Device Verification**

|                                    |                                                                       |
| ---------------------------------- | --------------------------------------------------------------------- |
| Event Name                         | Definition                                                            |
| `device-needs-approval`            | Dispatched when the device is unrecognized and requires user approval |
| `device-verification-email-sent`   | Dispatched when the device verification email is sent                 |
| `device-approved`                  | Dispatched when the user has approved the unrecongized device         |
| `device-verification-link-expired` | Dispatched when the email verification email has expired              |
| `device-retry`                     | Emit to restart the device registration flow                          |

**Error Handling**

To achieve a fully white-labeled experience, you will need to implement some custom error handling according to your UI needs. Here's a short example to illustrate how errors can be caught and identified by their code:

```javascript JavaScript icon="square-js" theme={null}
import { Magic, RPCError, RPCErrorCode } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  await magic.auth.loginWithEmailOTP({ email: 'hello@example.com' });
} catch (err) {
  if (err instanceof RPCError) {
    switch (err.code) {
      case RPCErrorCode.MagicLinkExpired
      case RPCErrorCode.UserAlreadyLoggedIn:
      // Handle errors accordingly
      break;
    }
  }
}
```

### `loginWithSMS`

Authenticate a user passwordlessly using a one-time code sent to the specified phone number.

[List of Currently Blocked Country Codes](/home/faq#questions-around-sms)

**Arguments**

* `phoneNumber` (String): E.164 formatted phone number
* `lifespan?` (Number): Set the lifespan of the resolved Decentralize ID token. Defaults to 900s (15 mins)

**Returns**

* `PromiEvent<string | null>`: The promise resolves upon authentication request success and rejects with a specific error code if the request fails. The resolved value is a Decentralized ID token with a default 15-minute lifespan.

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

// log in a user by their phone number
try {
  await magic.auth.loginWithSMS({ '+14151231234' });
} catch {
  // Handle errors if required!
}
```

**Error Handling**

**Relevant Error Codes**

To achieve a fully white-labeled experience, you will need to implement some custom error handling according to your UI needs. Here's a short example to illustrate how errors can be caught and identified by their code:

```javascript JavaScript icon="square-js" theme={null}
import { Magic, RPCError, RPCErrorCode } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  await magic.auth.loginWithSMS({ phoneNumber: "+14151231234" });
} catch (err) {
  if (err instanceof RPCError) {
    switch (err.code) {
      case RPCErrorCode.AccessDeniedToUser:
      case RPCErrorCode.MagicLinkRateLimited:
      case RPCErrorCode.UserAlreadyLoggedIn:
      // Handle errors accordingly
      break;
    }
  }
}
```

### `updateEmailWithUI`

Initiates the update email flow that allows a user to change their email address.

**Arguments**

* `email` (String): The new email to update to

* `showUI?` (Boolean): If `true`, shows an out-of-the-box pending UI which includes instructions on which step of the confirmation process the user is on. Dismisses automatically when the process is complete

**Returns**

* `PromiEvent<boolean>`: The promise resolves with a true boolean value if update email is successful and rejects with a specific error code if the request fails

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('API_KEY');

// Initiates the flow to update a user's current email to a new one.
try {
  ...
  /* Assuming user is logged in */
  await magic.auth.updateEmailWithUI({ email: 'new_user_email@example.com' });
} catch {
  // Handle errors if required!
}

/**
 * Initiates the flow to update a user's current email to a new one,
 * without showing an out-of-the box UI.
 */
try {
  /* Assuming user is logged in */
  await magic.auth.updateEmailWithUI({ email: 'new_user_email@example.com', showUI: false });
} catch {
  // Handle errors if required!
}
```

**Event Handling**

A white-label update email flow is available when passing `showUI: false` to this method. **The white-label flow is only supported in Magic SDK v22.0.0 and above**. Here's a short example to illustrate listening for and emitting events during the flow:

```javascript JavaScript icon="square-js" theme={null}
import {
  Magic,
  RecencyCheckEventOnReceived,
  RecencyCheckEventEmit,
  UpdateEmailEventOnReceived,
  UpdateEmailEventEmit,
} from "magic-sdk";

const magic = new Magic('API_KEY');

try {
/* Initiates update email flow to update a user's current email to a new one */
const handle = await magic.auth.updateEmailWithUI({
  email: 'new_user_email@example.com',
  showUI: false,
});

/*
Recency Check Events & Emit
*/

let recencyCheckRetries = 5;

handle
  .on(RecencyCheckEventOnReceived.EmailSent, () => {
    // Email OTP has been sent to the user's primary email

    // Prompt the user for the OTP
    const otp = window.prompt('Primary Email OTP');

    // Send the OTP for verification
    handle.emit(RecencyCheckEventEmit.VerifyEmailOtp, otp);

  })
  .on(RecencyCheckEventOnReceived.PrimaryAuthFactorVerified, () => {
    window.alert('Primary Factor has been verified');
  })
  .on(RecencyCheckEventOnReceived.EmailNotDeliverable, () => {
    // Email OTP was undeliverable to user's primary email

    // Cancel update email request
    handle.emit(RecencyCheckEventEmit.Cancel);
    window.alert('Email Not Deliverable');

  })
  .on(RecencyCheckEventOnReceived.EmailExpired, () => {
    // User entered expired OTP

    handle.emit(RecencyCheckEventEmit.Cancel);
    window.alert('Expired OTP');

  })
  .on(RecencyCheckEventOnReceived.InvalidEmailOtp, () => {
    // User entered invalid OTP; you may limit retries and cancel the request

    if (!recencyCheckRetries) {
      // Cancel update email request
      alert('Too many attempts');
      handle.emit(RecencyCheckEventEmit.Cancel);
    } else {
      const otp = window.prompt(
        `Invalid code, Please enter OTP again. Retries left: ${recencyCheckRetries}`,
      );
      recencyCheckRetries--;

      // Send the OTP for verification
      handle.emit(RecencyCheckEventEmit.VerifyEmailOtp, otp);
    }
  });

/*
Update Email Events & Emit
*/

let updateEmailRetries = 5;

handle
  .on(UpdateEmailEventOnReceived.EmailSent, () => {
    // Email OTP has been sent to the user's secondary email

    // Prompt the user for the OTP
    const otp = window.prompt('Enter new Email OTP');

    // Send the OTP for verification
    handle.emit(UpdateEmailEventEmit.VerifyEmailOtp, otp);
  })
  .on(UpdateEmailEventOnReceived.InvalidEmail, () => {
    // Email OTP was undeliverable to user's secondary email

    const newEmail = window.prompt('Invalid Email, Enter a new Email');

    // Try same or new email address
    handle.emit(
      UpdateEmailEventEmit.RetryWithNewEmail,
      newEmail || email,
    );

  })
  .on(UpdateEmailEventOnReceived.EmailAlreadyExists, () => {
    // Account already exists for new email address

    const newEmail = window.prompt('Email address already in use, Enter a different Email');

    // Try same or new email address
    handle.emit(
      UpdateEmailEventEmit.RetryWithNewEmail,
      newEmail || email,
    );

  })
  .on(UpdateEmailEventOnReceived.InvalidEmailOtp, () => {
    // User entered invalid OTP; you may limit retries and cancel the request

    if (!updateEmailRetries) {
      // Cancel update email request
      alert('Too many attempts');
      handle.emit(UpdateEmailEventEmit.Cancel);
    } else {
      const otp = window.prompt(
        `Invalid code, Please enter OTP again. Retries left: ${updateEmailRetries}`,
      );
      updateEmailRetries--;

      // Send the OTP for verification
      handle.emit(UpdateEmailEventEmit.VerifyEmailOtp, otp);
    }

  })
  .on(UpdateEmailEventOnReceived.EmailUpdated, () => {
    // Update email successful
    alert('Email Updated');
  })

  handle
    .on('error', () => {
      // is called if the Promise rejects
      alert('Error occurred');
    });

try {
  const res = await handle;
  console.log(res);

  // Can also handle successful email update here
alert('Email Updated');
} catch {
  // Handle errors if required!
}
```

**Error Handling**

﻿[Relevant Error Codes](/embedded-wallets/sdk/client-side/javascript#error-codes)﻿

To achieve a fully white-labeled experience, you will need to implement some custom error handling according to your UI needs. Here's a short example to illustrate how errors can be caught and identified by their code:

```javascript JavaScript icon="square-js" theme={null}
import { Magic, RPCError, RPCErrorCode } from 'magic-sdk';

const magic = new Magic('API_KEY');

try {
  await magic.auth.updateEmailWithUI({ email: 'hello@example.com', showUI: false });
} catch (err) {
  if (err instanceof RPCError) {
    switch (err.code) {
      case RPCErrorCode.UpdateEmailFailed:
        // Handle errors accordingly
        break;
    }
  }
}
```

## Wallet Module

The Wallet Module and it's members are accessible on the Magic SDK instance by the `wallet` property.

<Note>
  The Wallet Module is currently only **compatible with Ethereum, Polygon, Base, Arbitrum, Optimism, and Flow (no NFTs).**
</Note>

### `connectWithUI`

Renders a simple login form UI to collect the user's email address and authenticate them passwordlessly using a one-time passcode (OTP) sent to their email address they input.

**Arguments**

* *None*

**Returns**

* A `promiEvent` which returns an `String[]` when resolved: An array of user accounts that are connected, with the first element being the current public address of the user. You can read more on PromiEvents [here](#promievents).

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk"

const accounts = await magic.wallet.connectWithUI();
```

### `showUI`

Displays the fully [navigable wallet](/embedded-wallets/wallets/customization/widget-ui) to the user that adheres to the toggled configurations on your developer dashboard’s Widget UI tab. ⁠ ⁠This is only supported for users who login with email or Google. User must be signed in for this method to return or else it will throw an error.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

⁠Optionally, add a `.on()` handler to catch the `disconnect` event emitted when the user logs out from the wallet widget.

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showUI()
```

### `showAddress`

Displays an iframe with the current user’s wallet address in a QR Code.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showAddress()
```

### `showBalances`

Displays an iframe that displays the user’s token balances from the currently connected network.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showBalances()
```

### `showNFTs`

Displays an iframe that shows the user’s NFTs in both an aggregated and detailed individual view. **Supported only on Ethereum and Polygon.** Ensure this is enabled in your developer dashboard via the ‘Widget UI’ tab.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showNFTs()
```

### `showSendTokensUI`

Displays an iframe with UI to help the user transfer tokens from their account to another address.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showSendTokensUI()
```

### `showOnRamp`

Displays an iframe modal with various on ramp providers for the user to purchase crypto from directly to their wallet.

To use the fiat onramp, **you will need to contact us to KYB with the payment provider prior to use.** Once approved, ensure this toggle is enabled in your developer dashboard via the 'Widget UI' tab.

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

await magic.wallet.showOnRamp()
```

### `sign7702Authorization`

Signs an [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization that delegates your EOA to a smart contract implementation. This authorization can then be included in a Type-4 transaction via [`send7702Transaction`](#send7702transaction).

<Note>Available as of `magic-sdk@33.4.0`. See the [EIP-7702 feature guide](/embedded-wallets/wallets/features/eip-7702) for a complete walkthrough.</Note>

**Arguments**

* `contractAddress` (String): The smart contract implementation address to delegate to
* `chainId` (Number): The chain ID for the authorization
* `nonce?` (Number): The account nonce. If omitted, fetched from the network automatically

**Returns**

* `Promise<object>`:
  * `contractAddress` (String): The contract address that was authorized
  * `chainId` (Number): The chain ID for the authorization
  * `nonce` (Number): The nonce used in the authorization
  * `v` (Number): The `v` component of the signature (27 or 28)
  * `r` (String): The `r` component of the signature
  * `s` (String): The `s` component of the signature

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY', {
  network: {
    rpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_KEY',
    chainId: 11155111,
  },
});

const authorization = await magic.wallet.sign7702Authorization({
  contractAddress: '0x000000004F43C49e93C970E84001853a70923B03',
  chainId: 11155111,
});

console.log('Authorization:', authorization);
```

### `send7702Transaction`

Sends a Type-4 (EIP-7702) transaction that includes signed authorizations. This executes the delegation on-chain.

<Note>Available as of `magic-sdk@33.4.0`. See the [EIP-7702 feature guide](/embedded-wallets/wallets/features/eip-7702) for a complete walkthrough.</Note>

**Arguments**

* `to` (String): The recipient address
* `authorizationList` (Array): Array of signed authorizations returned by `sign7702Authorization`
* `value?` (String): Value to send in wei (hex string). Defaults to `'0x0'`
* `data?` (String): Transaction calldata. Defaults to `'0x'`
* `gas?` (String): Gas limit (hex string). If omitted, estimated automatically
* `gasLimit?` (String): Alias for `gas`
* `maxFeePerGas?` (String): Max fee per gas (hex string). If omitted, fetched from the network
* `maxPriorityFeePerGas?` (String): Max priority fee per gas (hex string). If omitted, fetched from the network
* `nonce?` (Number): Transaction nonce. If omitted, fetched from the network

**Returns**

* `Promise<object>`:
  * `transactionHash` (String): The hash of the submitted transaction

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY', {
  network: {
    rpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_KEY',
    chainId: 11155111,
  },
});

// Step 1: Sign the authorization
const authorization = await magic.wallet.sign7702Authorization({
  contractAddress: '0x000000004F43C49e93C970E84001853a70923B03',
  chainId: 11155111,
});

// Step 2: Send the Type-4 transaction
const { transactionHash } = await magic.wallet.send7702Transaction({
  to: '0x1234567890123456789012345678901234567890',
  data: '0x',
  authorizationList: [authorization],
});

console.log('Transaction hash:', transactionHash);
```

## User Module

The User Module and it's members are accessible on the Magic SDK instance by the `user` property.

### `getIdToken`

Generates a [Decentralized Id Token](/embedded-wallets/authentication/features/decentralized-id) which acts as a proof of authentication to resource servers.

**Arguments**

* `lifespan?` (Number): Will set the lifespan of the generated token. Defaults to 900s (15 mins)

**Returns**

* `PromiEvent<string>`: Base64-encoded string representation of a JSON tuple representing `[proof, claim]`

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

// Assumes a user is already logged in
try {
  const idToken = await magic.user.getIdToken();
} catch {
  // Handle errors if required!
}
```

### `generateIdToken`

Generates a [Decentralized ID token](/embedded-wallets/authentication/features/decentralized-id) with optional serialized data.

**Arguments**

* `lifespan?` (Number): Will set the lifespan of the generated token. Defaults to 900s (15 mins)
* `attachment?` (String): Will set a signature of serialized data in the generated token. Defaults to `"none"`

**Returns**

* `PromiEvent<string>`: Base64-encoded string representation of a JSON tuple representing `[proof, claim]`

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

// Assumes a user is already logged in
try {
  const idToken = await magic.user.generateIdToken({ attachment: 'SERVER_SECRET' });
} catch {
  // Handle errors if required!
}
```

### `getInfo`

Retrieves information for the authenticated user.

<Warning>
  **Breaking Change Notice**: Starting with SDK v30.0.0, the `publicAddress` field has been removed from the response and replaced with the structured `wallets` object. Please see the response structure below for more details.
</Warning>

**Arguments**

* *None*

**Returns**

The response structure depends on your SDK version:

<Tabs>
  <Tab title="SDK v30.0.0+">
    **Response with Wallets Object**

    * `PromiEvent<object>`:
      * `issuer` (String): The Decentralized ID of the user. In server-side use-cases, we recommend this value to be used as the user ID in your own tables.
      * `email` (String): Email address of the authenticated user
      * `phoneNumber` (String): The phone number of the authenticated user
      * `isMfaEnabled` (Boolean): Whether or not multi-factor authentication is enabled for the user
      * `recoveryFactors` (Array): Any recovery methods that have been enabled (ex. `[{ type: 'phone_number', value: '+99999999' }]`)
      * `wallets` (Object):  Organized wallet addresses by blockchain and network:
        * `[chainName]` (Object): Each supported blockchain (e.g., `ethereum`, `solana`, `bitcoin`, `hedera`)
          * `publicAddress` (String | null): The mainnet public address for this chain, or `null` if no mainnet wallet exists
          * `subAccounts` (Array): Additional network addresses for this chain
            * `name` (String): Network name (e.g., `testnet`)
            * `publicAddress` (String): Public address for this network

    **Example Response:**

    ```json theme={null}
    {
      "issuer": "did:ethr:0x...",
      "email": "user@example.com",
      "phoneNumber": null,
      "isMfaEnabled": false,
      "recoveryFactors": [],
      "wallets": {
        "ethereum": {
          "publicAddress": "0x742d35Cc6634C0532925a3b8D4C9db96590b0b65",
          "subAccounts": [
            {
              "name": "testnet",
              "publicAddress": "0x8ba1f109551bD432803012645Hac136c22C501e"
            }
          ]
        },
        "solana": {
          "publicAddress": "DRiP2Pn2K6fuMLKQmt5rZWxa91wRx1jRhTdY5L6kh5Kp",
          "subAccounts": []
        },
        "bitcoin": {
          "publicAddress": null,
          "subAccounts": [
            {
              "name": "testnet", 
              "publicAddress": "tb1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
            }
          ]
        }
      }
    }
    ```
  </Tab>

  <Tab title="SDK v29.x and below">
    **Legacy Response**

    * `PromiEvent<object>`:
      * `issuer` (String): The Decentralized ID of the user
      * `email` (String): Email address of the authenticated user
      * `phoneNumber` (String): The phone number of the authenticated user
      * `publicAddress` (String): The authenticated user's public address (a.k.a.: public key)
      * `isMfaEnabled` (Boolean): Whether or not multi-factor authentication is enabled for the user
      * `recoveryFactors` (Array): Any recovery methods that have been enabled

    **Example Response:**

    ```json theme={null}
    {
      "issuer": "did:ethr:0x...",
      "email": "user@example.com", 
      "phoneNumber": null,
      "publicAddress": "0x742d35Cc6634C0532925a3b8D4C9db96590b0b65",
      "isMfaEnabled": false,
      "recoveryFactors": []
    }
    ```
  </Tab>
</Tabs>

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

// Assumes a user is already logged in
try {
  const userInfo = await magic.user.getInfo();
} catch {
  // Handle errors if required!
}
```

### `isLoggedIn`

Checks if a user is currently logged in to the Magic SDK.

**Arguments**

* *None*

**Returns**

* `PromiEvent<boolean>`

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
const isLoggedIn = await magic.user.isLoggedIn();
console.log(isLoggedIn); // => `true` or `false`
} catch {
// Handle errors if required!
}
```

### `logout`

Logs out the currently authenticated Magic user

**Arguments**

* *None*

**Returns**

* `PromiEvent<boolean>`

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  await magic.user.logout();
  console.log(await magic.user.isLoggedIn()); // => `false`
} catch {
  // Handle errors if required!
}
```

### `showSettings`

Displays an iframe with the current user’s settings. Allows for users to update their email address, enable multi-factor authentication, and add a recovery factor.

<Note>
  Access to [MFA](/embedded-wallets/authentication/features/mfa) and [account
  recovery](/embedded-wallets/authentication/features/account-recovery) require paid add-ons.
</Note>

**Arguments**

* `page?` (String): Optional argument to deeplink to a specific page (`'mfa' | 'update-email' | 'recovery'`)

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  await magic.user.showSettings();
} catch {
  // Handle errors if required!
}

// Deeplink to MFA view
try {
 await magic.user.showSettings({ page: 'mfa' });
} catch {
  // Handle errors if required!
}
```

**Whitelabel**

Whitelabel the flow starting from where the user needs to add a recovery factor phone number. This allows you to implement your own UI, but you will need to implement some custom event handling according to your needs.

Access to [account recovery](/embedded-wallets/authentication/features/account-recovery) require paid add-ons.

**Arguments**

* `page` (String): `'recovery'`
* `showUI` (Boolean): `false`

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  let handle = magic.user.showSettings({ showUI: false, page: 'recovery' });

  handle.emit(RecoveryFactorEventEmit.StartEditPhoneNumber);
    // Handle start of adding or editing recovery factor phone number

  handle.on(
    RecencyCheckEventOnReceived.PrimaryAuthFactorNeedsVerification, () => {
      // Event triggered if primary auth factor (email) needs verification
    },
  );

  handle.on(RecencyCheckEventOnReceived.EmailSent, () => {
    // Send OTP to email if primary auth factor needs verification
    // Prompt for OTP and emit it to verify primary auth factor

    const code = window.prompt('Please enter the code which was sent to your email');
    handle.emit(RecencyCheckEventEmit.VerifyEmailOtp, code);
  });

  handle.on(RecencyCheckEventOnReceived.PrimaryAuthFactorVerified, () => {
    // Event triggered if email verification success
  });

  handle.on(RecoveryFactorEventOnReceived.EnterNewPhoneNumber, () => {
    // Prompt for secondary factor (phone number) and emit it to send SMS OTP

    const phoneNumber = window.prompt('Enter a phone number');
    handle.emit(RecoveryFactorEventEmit.SendNewPhoneNumber, phoneNumber);
  });

  handle.on(RecoveryFactorEventOnReceived.EnterOtpCode, () => {
    // Prompt for SMS OTP and emit it to verify secondary auth factor

    const otp = window.prompt('Enter SMS OTP');
    handle.emit(RecoveryFactorEventEmit.SendOtpCode, otp);
  });

  handle.on('done', () => {
    // Successfully added secondary recovery factor
  });

  handle.on(RecoveryFactorEventOnReceived.MalformedPhoneNumber, () => {
    // Event triggered if submitted phone number is invalid

    // Prompt for phone number and emit it to send SMS OTP
    // Add your own retry logic to limit retries
    const phoneNumber = window.prompt(
      'You entered an invalid phone number, please try again'
    );
    handle.emit(RecoveryFactorEventEmit.SendNewPhoneNumber, phoneNumber);
  });

  handle.on(RecoveryFactorEventOnReceived.RecoveryFactorAlreadyExists, () => {
    // Event triggered if user is trying to add a phone number that's already in place
  });

  handle.on(RecoveryFactorEventOnReceived.InvalidOtpCode, () => {
    // Event triggered when submitted SMS OTP is invalid

    const code = window.prompt('Invalid OTP code. Please try one more time');
    handle.emit(RecoveryFactorEventEmit.SendOtpCode, code);
  });
} catch {
  // Handle errors if required!
}
```

### `recoverAccount`

A user can recover their email account if they've setup a phone number as a recovery factor. Calling this method will display a modal for the user to submit the sent SMS OTP.

Access to [account recovery](/embedded-wallets/authentication/features/account-recovery) require
paid add-ons.

**Arguments**

* `email` (String): The email address (primary auth factor) of the user

**Returns**

* `PromiEvent<boolean>`: The promise resolves with a true boolean value if the recovery is successful and rejects if the request fails

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  magic.user.recoverAccount({ email: email })
} catch {
  // Handle errors if required!
}
```

### `enableMFA`

Displays an iframe deep linked to the beginning of the enable MFA flow. This is the same as calling `showSettings({ page: 'mfa' })`

<Note>
  Access to [MFA](/embedded-wallets/authentication/features/mfa) require paid add-ons. Available as of `magic-sdk v28.11.0`
</Note>

**Arguments**

* `showUI?` (Boolean): If `true`, show an out-of-the-box UI to take user through flow to enable MFA. Defaults to `true`

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';
import {
  EnableMFAEventEmit,
  EnableMFAEventOnReceived,
} from '@magic-sdk/types';

const magic = new Magic('PUBLISHABLE_API_KEY');

// user enables MFA through Magic UI
try {
  await magic.user.enableMFA();
} catch {
  // Handle errors if required!
}

// user enables MFA through whitelabel UI
try {
  const handle = magic.user.enableMFA({ showUI: false });

  handle
  .on(EnableMFAEventOnReceived.MFASecretGenerated, ({ QRCode, key }) => {
⁠    // Display QR code
    window.alert(`QRCode: ${QRCode}\nKey:${key}`);

    // Prompt for MFA TOTP and emit it to enable MFA
    const totp = window.prompt('Scan QR code and enter TOTP from MFA app');
    handle.emit(EnableMFAEventEmit.VerifyMFACode, totp);
  })
  .on(EnableMFAEventOnReceived.InvalidMFAOtp, ({ errorCode }) => {
⁠    // User entered invalid MFA TOTP

    // Have the user retry entering the MFA TOTP and emit via VerifyMFACode.
    // You may display error and limit retries by emitting Cancel.
    handle.emit(EnableMFAEventEmit.Cancel);
  })
  .on(EnableMFAEventOnReceived.MFARecoveryCodes, ({ recoveryCode }) => {
    // Enable MFA success, user entered valid MFA TOTP, display Recovery Code
    window.alert(`MFA enabled! Recovery code - ${recoveryCode}`);
  })
  .on('error', (error) => {
⁠    // Handle enable MFA errors
⁠  )};
} catch {
  // Handle errors if required!
}
```

**Events**

|                        |                                                            |
| ---------------------- | ---------------------------------------------------------- |
| Event Name             | Definition                                                 |
| `mfa-secret-generated` | Dispatched when the user starts the enable MFA process.    |
| `invalid-mfa-otp`      | Dispatched when the MFA TOTP submitted by user is invalid. |
| `mfa-recovery-codes`   | Dispatched when the MFA TOTP submitted by user is valid.   |
| `verify-mfa-code`      | Emit to submit MFA TOTP submitted by user.                 |
| `cancel-mfa-setup`     | Emit to cancel the MFA enable flow.                        |

### `disableMFA`

Displays an iframe deep linked to the beginning of the disable MFA flow. This method assumes MFA has been enabled for the logged in user. This cannot be achieved by calling `showSettings()` .

Access to [MFA](/embedded-wallets/authentication/features/mfa) require paid add-ons. Available
as of `magic-sdk v28.11.0`

**Arguments**

* `showUI?` (Boolean): If `true`, show an out-of-the-box UI to take user through flow to disable MFA. Defaults to `true`

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';
import {
  DisableMFAEventEmit,
  DisableMFAEventOnReceived,
} from '@magic-sdk/types';

const magic = new Magic('PUBLISHABLE_API_KEY');

// user disables MFA through Magic UI
try {
await magic.user.disableMFA();
} catch {
// Handle errors if required!
}

// user disables MFA through whitelabel UI
try {
  const handle = await magic.user.disableMFA({ showUI: false });

  handle
    .on(DisableMFAEventOnReceived.MFACodeRequested, () => {
      // Prompt user for MFA TOTP and emit it to disable MFA
      const totp = window.prompt('Submit MFA TOTP');
      handle.emit(DisableMFAEventEmit.VerifyMFACode, totp);

      // If user lost device and needs to recover with recovery code
      const recoveryCode = window.prompt('Enter Recovery Code');
      handle.emit(DisableMFAEventEmit.LostDevice, recoveryCode);
    })
    .on(DisableMFAEventOnReceived.InvalidMFAOtp, ({ errorCode }) => {
      // Handle user entered invalid MFA TOTP

      // Have the user retry entering the MFA TOTP and emit via VerifyMFACode.
      // You may display error and limit retries by emitting Cancel.
      handle.emit(DisableMFAEventEmit.Cancel);
    })
    .on(DisableMFAEventOnReceived.InvalidRecoveryCode, () => {
      // Handle user entered invalid Recovery Code

      // Have the user retry entering the Recovery Code and emit via LostDevice.
      // You may display error and limit retries by emitting Cancel.
      handle.emit(DisableMFAEventEmit.Cancel);
    })
    .on('done', () => {
      // Handle successful disable MFA
    })
    .on('error', (error) => {
      // Handle disable MFA errors
    });

  const res = await handle;
  console.log(res);
} catch {
  // Handle errors if required!
}
```

**Events**

|                         |                                                                 |
| ----------------------- | --------------------------------------------------------------- |
| Event Name              | Definition                                                      |
| `mfa-code-requested`    | Dispatched when the user starts the disable MFA process.        |
| `invalid-mfa-otp`       | Dispatched when the MFA TOTP submitted by user is invalid.      |
| `invalid-recovery-code` | Dispatched when the Recovery Code submitted by user is invalid. |
| `verify-mfa-code`       | Emit to submit MFA TOTP submitted by user.                      |
| `lost-device`           | Emit to submit Recovery Code submitted by user.                 |
| `cancel-mfa-disable`    | Emit to cancel the MFA disable flow.                            |

### `revealEVMPrivateKey`

Displays an iframe revealing the current user's EVM private key. Allows for users to take their private key to another wallet. Neither Magic nor the developer can see this key; only the end user can.

<Warning>
  **Breaking Change**: As of SDK version 31.0.0, `revealPrivateKey()` has been renamed to `revealEVMPrivateKey()` for EVM chains. For non-EVM chains, use the specific extension's `revealPrivateKey()` method instead.
</Warning>

**Arguments**

* *None*

**Returns**

* `Promise` which resolves when the user closes the window

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';

const magic = new Magic('PUBLISHABLE_API_KEY');

try {
  await magic.user.revealEVMPrivateKey();
} catch {
  // Handle errors if required!
}
```

### `onUserLoggedOut`

When the useStorageCache is enabled, there might be situations where the isLoggedIn function returns true despite the user being logged out. In such instances, an event will be emitted after a few milliseconds, providing an opportunity to manage the user's logged-out state, such as when a session expires.

<Note>
  Only necessary with when the `useStorageCache` option is set to `true`.
</Note>

**Arguments**

* `callback` (`(isLoggedOut: boolean) => void`): The callback function when the event is emitted

**Returns**

* A `function` that can be called to unsubscribe from the event

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";

⁠// Create Magic instance with useStorageCache set to true
const magic = new Magic('PUBLISHABLE_API_KEY', {
  useStorageCache: true
});

magic.user.onUserLoggedOut((isLoggedOut: boolean) => {
  // Do something when user is logged out
  navigation.navigate('LoginScreen')
})
```

## OAuth Module

The OAuth Module and it's members are accessible on the Magic SDK instance by the `oauth2` property.

To use the OAuth Module in your application, install `@magic-ext/oauth2` along with `magic-sdk`.

### `loginWithRedirect`

Starts the OAuth 2.0 login flow.

**Arguments**

* `provider` (String): The OAuth provider being used for login
* `redirectURI` (String): A URL a user is sent to after they successfully log in
* `scope?` (Array): Defines the specific permissions an application requests from a user

**Returns**

* *None*

**Valid Providers**

|           |               |
| --------- | ------------- |
| Name      | Argument      |
| Google    | `'google'`    |
| Facebook  | `'facebook'`  |
| Twitter   | `'twitter'`   |
| Apple     | `'apple'`     |
| Discord   | `'discord'`   |
| GitHub    | `'github'`    |
| LinkedIn  | `'linkedin'`  |
| Bitbucket | `'bitbucket'` |
| GitLab    | `'gitlab'`    |
| Twitch    | `'twitch'`    |
| Microsoft | `'microsoft'` |
| Steam     | `'steam'`     |

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";
import { OAuthExtension } from "@magic-ext/oauth2";

const magic = new Magic('PUBLISHABLE_API_KEY', {
  extensions: [new OAuthExtension()],
});

await magic.oauth2.loginWithRedirect({
  provider: '...' /* 'google', 'facebook', 'apple', etc. */,
  redirectURI: 'https://your-app.com/your/oauth/callback',
  scope: ['user:email'] /* optional */,
});
```

### `getRedirectResult`

Returns the final OAuth 2.0 result.

**Arguments**

* `lifespan?` (Number): Set the lifespan of the resolved Decentralize ID token. Defaults to 900s (15 mins)

**Returns**

* `PromiEvent<object>`:
  * ⁠`magic` (Object): Information about the authenticated Magic user. Comprised of `idToken` and `userMetadata`.
  * `oauth` (Object): Information about the authenticated OAuth user. Comprised of `accessToken`, `provider`, `scope`, `userHandle`, and `userInfo`.

**Example**

```typescript JavaScript theme={null}
import { Magic } from "magic-sdk";
import { OAuthExtension } from "@magic-ext/oauth2";

const magic = new Magic('PUBLISHABLE_API_KEY', {
  extensions: [new OAuthExtension()],
});

// optionally pass in number to method to set Decentralized ID token lifespan
const result = await magic.oauth2.getRedirectResult();

// Result has the following interface
interface OAuthRedirectResult {
  magic: {
  idToken: string;
  userMetadata: MagicUserMetadata;
},
  oauth: {
  provider: string;
  scope: string[];
  accessToken: string;
  userHandle: string;
  userInfo: ...;
  }
};
```

<Note>
  The shape of `magic.userMetadata` (typed as `MagicUserMetadata`) differs by SDK version. On **SDK v30.0.0+**, wallet addresses are nested under a `wallets` object — access the Ethereum address via `result.magic.userMetadata.wallets?.ethereum?.publicAddress`. On **SDK v29.x and below**, the address is available as a flat field: `result.magic.userMetadata.publicAddress`. See the [`getInfo` method](#getinfo) above for the full response shapes of each version.
</Note>

## Smart Account Module

The Smart Account Module and its members are accessible on the Magic SDK instance by the `smartAccount` property.

To use the Smart Account Module in your application, install `@magic-ext/smart-account` along with `magic-sdk`.

<Note>
  For more detailed information, see the [Smart Account](/embedded-wallets/wallets/features/smart-account) documentation.
</Note>

### `sendTransaction`

Sends one or more calls through an EIP-7702 smart account via Alchemy's Smart Wallet infrastructure.

**Arguments**

* `params` (Object):
  * `chainId` (Number): The chain ID for the transaction
  * `calls` (Array): Array of call objects, each containing:
    * `to` (String): The recipient or contract address
    * `data` (String, optional): Encoded calldata
    * `value` (String, optional): Value to send in wei

**Returns**

* `Promise<{ id: string; transactionHash: string | undefined; chainId: number; }>`: Resolves with the call bundle ID, transaction hash, and chain ID

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';
import { SmartAccountExtension } from '@magic-ext/smart-account';

const magic = new Magic('YOUR_API_KEY', {
  extensions: [
    new SmartAccountExtension({
      apiKey: 'YOUR_ALCHEMY_API_KEY',
      paymasterPolicyId: 'YOUR_POLICY_ID', // optional — enables gas sponsorship
    }),
  ],
});

// Send a single call
const result = await magic.smartAccount.sendTransaction({
  chainId: 11155111,
  calls: [
    {
      to: '0x1234567890123456789012345678901234567890',
      value: '1000000000000000',
      data: '0x',
    },
  ],
});

console.log('Transaction hash:', result.transactionHash);

// Send batched calls (atomic)
const batchResult = await magic.smartAccount.sendTransaction({
  chainId: 11155111,
  calls: [
    { to: '0xContractA', data: '0x...' },
    { to: '0xContractB', data: '0x...' },
  ],
});
```

## EVM Module

The EVM Module and it's members are accessible on the Magic SDK instance by the `evm` property.

To use the EVM Module in your application, install `@magic-ext/evm` along with `magic-sdk`.

### `switchChain`

Switches the active EVM chain to the specified chain ID.

<Note>
  For more detailed information, see the [EVM Chain Switching](/embedded-wallets/wallets/features/evm-chain-switching) documentation.
</Note>

**Arguments**

* `chainId` (Number): The chain ID of the target EVM network

**Returns**

* `Promise<{ network: string | { rpcUrl: string; chainId?: number; chainType?: string; } | undefined; }>`: Resolves with network configuration when the network switch is complete

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';
import { EVMExtension } from '@magic-ext/evm';
import { ethers } from 'ethers';

const magic = new Magic('YOUR_API_KEY', {
  extensions: [
    new EVMExtension([
      { rpcUrl: 'https://polygon-rpc.com/', chainId: 137, default: true },
      { rpcUrl: 'https://mainnet.optimism.io', chainId: 10 },
    ]),
  ],
});

// Switch to Optimism network
await magic.evm.switchChain(10);

// Verify the network switch
const provider = new ethers.BrowserProvider(magic.rpcProvider);
const network = await provider.getNetwork();
console.log(network.chainId); // => 10
```

## Blockchain Modules

Magic SDK provides blockchain-specific modules for various networks through extensions.

### Available Blockchain Extensions

* **[Algorand](/embedded-wallets/blockchains/non-evm/algorand)**: `@magic-ext/algorand`
* **[Aptos](/embedded-wallets/blockchains/non-evm/aptos)**: `@magic-ext/aptos`
* **[Avalanche](/embedded-wallets/blockchains/non-evm/avalanche)**: `@magic-ext/avalanche`
* **[Bitcoin](/embedded-wallets/blockchains/non-evm/bitcoin)**: `@magic-ext/bitcoin`
* **[Cosmos](/embedded-wallets/blockchains/non-evm/cosmos)**: `@magic-ext/cosmos`
* **[Flow](/embedded-wallets/blockchains/non-evm/flow)**: `@magic-ext/flow`
* **[Hedera](/embedded-wallets/blockchains/evm/hedera)**: `@magic-ext/hedera`
* **[Icon](/embedded-wallets/blockchains/non-evm/icon)**: `@magic-ext/icon`
* **[Kadena](/embedded-wallets/blockchains/non-evm/kadena)**: `@magic-ext/kadena`
* **[Near](/embedded-wallets/blockchains/non-evm/near)**: `@magic-ext/near`
* **[Polkadot](/embedded-wallets/blockchains/non-evm/polkadot)**: `@magic-ext/polkadot`
* **[Solana](/embedded-wallets/blockchains/non-evm/solana)**: `@magic-ext/solana`
* **[Tezos](/embedded-wallets/blockchains/non-evm/tezos)**: `@magic-ext/tezos`
* **[Zilliqa](/embedded-wallets/blockchains/non-evm/zilliqa)**: `@magic-ext/zilliqa`

### Common Methods

All blockchain extensions provide these common methods:

#### `getPublicAddress`

Retrieves the public address for the specific blockchain network.

**Arguments**

* *None*

**Returns**

* `Promise<string>`: The public address for the blockchain network

**Example**

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from 'magic-sdk';
import { SolanaExtension } from '@magic-ext/solana';
import { BitcoinExtension } from '@magic-ext/bitcoin';

const magic = new Magic('YOUR_API_KEY', {
  extensions: [
    new SolanaExtension({ rpcUrl: 'SOLANA_RPC_NODE_URL' }),
    new BitcoinExtension({ rpcUrl: 'BITCOIN_RPC_NODE_URL', network: 'testnet' }),
  ],
});

// Get Solana public address
const solanaAddress = await magic.solana.getPublicAddress();
console.log(solanaAddress); // => "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"

// Get Bitcoin public address
const bitcoinAddress = await magic.bitcoin.getPublicAddress();
console.log(bitcoinAddress); // => "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
```

#### `revealPrivateKey`

Displays an iframe revealing the private key for the specific blockchain network. This method shows a secure interface where users can view and copy their private key.

**Arguments**

* *None*

**Returns**

* `Promise<void>`: Resolves when the user closes the private key display window

**Example**

```javascript JavaScript icon="square-js" theme={null}
// Reveal Solana private key
await magic.solana.revealPrivateKey();

// Reveal Bitcoin private key
await magic.bitcoin.revealPrivateKey();

// Reveal Hedera private key
await magic.hedera.revealPrivateKey();
```

## Response and Error Handling

There are three types of error class to be aware of when working with Magic's client-side JavaScript SDK:

* `SDKError`: Raised by the SDK to indicate missing parameters, communicate deprecation notices, or other internal issues. A notable example would be a MISSING\_API\_KEY error, which informs the required API key parameter was missing from new Magic(...).
* `RPCError`: Errors associated with specific method calls to the Magic `<iframe>` context. These methods are formatted as [JSON RPC 2.0](https://www.jsonrpc.org/specification) payloads, so they return error codes as integers. This type of error is raised by methods like [`AuthModule.loginWithMagicLink`](#loginwithmagiclink).
* `ExtensionError`: Errors associated with method calls to Magic SDK Extensions. Extensions are an upcoming/experimental feature of Magic SDK. More information will be available once Extensions are officially released.

### `SDKError`

The `SDKError` class is exposed for `instanceof` operations.

```javascript JavaScript icon="square-js" theme={null}
import { SDKError } from "magic-sdk";

try {
  // Something async...
} catch (err) {
  if (err instanceof SDKError) {
    // Handle...
  }
}
```

`SDKError` instances expose the `code` field which may be used to deterministically identify the error. Additionally, an enumeration of error codes is exposed for convenience and readability:

```javascript JavaScript icon="square-js" theme={null}
import { SDKErrorCode } from "magic-sdk";

SDKErrorCode.MissingApiKey;
SDKErrorCode.ModalNotReady;
SDKErrorCode.MalformedResponse;
// and so forth...
// Please reference the `Enum Key` column of the error table below.
```

### Error Codes

|                           |                                                                                                                                                                                                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Enum Key                  | Description                                                                                                                                                                                                                                                    |
| `MissingApiKey`           | Indicates the required Magic API key is missing or invalid.                                                                                                                                                                                                    |
| `ModalNotReady`           | Indicates the Magic iframe context is not ready to receive events. This error should be rare and usually indicates an environmental issue or improper `async/await` usage.                                                                                     |
| `MalformedResponse`       | Indicates the response received from the Magic iframe context is malformed. We all make mistakes (even us), but this should still be a rare exception. If you encounter this, please be aware of phishing!                                                     |
| `InvalidArgument`         | Raised if an SDK method receives an invalid argument. Generally, TypeScript saves us all from simple bugs, but there are validation edge cases it cannot solve—this error type will keep you informed!                                                         |
| `ExtensionNotInitialized` | Indicates an extension method was invoked before the Magic SDK instance was initialized. Make sure to access extension methods only from the Magic SDK instance to avoid this error.                                                                           |
| `IncompatibleExtension`   | Indicates that incompatible extensions were detected during the initialization of the Magic SDK. The error message specifies the incompatible extensions and their compatibility requirements based on the current Magic SDK version and platform environment. |

### `RPCError`

The `RPCError` class is exposed for `instanceof` operations:

```javascript JavaScript icon="square-js" theme={null}
import { RPCError } from "magic-sdk";

try {
  // Something async...
} catch (err) {
  if (err instanceof RPCError) {
    // Handle...
  }
}
```

`RPCError` instances expose the `code` field which may be used to deterministically identify the error. Additionally, an enumeration of error codes is exposed for convenience and readability:

```javascript JavaScript icon="square-js" theme={null}
import { RPCErrorCode } from "magic-sdk";

RPCErrorCode.UserAlreadyLoggedIn;
RPCErrorCode.ParseError;
RPCErrorCode.MethodNotFound;
RPCErrorCode.InternalError;
// and so forth...
// Please reference the `Enum Key` column of the error table below.
```

### Magic Error Codes

|        |                         |                                                                                                                                       |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Code   | Enum Key                | Description                                                                                                                           |
| -10003 | `UserAlreadyLoggedIn`   | A user is already logged in. If a new user should replace the existing user, make sure to call [`logout`](#logout) before proceeding. |
| -10004 | `UpdateEmailFailed`     | An update email request was unsuccessful, either due to an invalid email being supplied or the user canceled the action.              |
| -10005 | `UserRequestEditEmail`  | The user has stopped the login request because they want to edit the provided email.                                                  |
| -10010 | `InactiveRecipient`     | The recipient account is currently inactive. The user should verify and activate their account to resolve this issue.                 |
| -10011 | `AccessDeniedToUser`    | User access is denied. The user lacks the necessary permissions or credentials to perform the requested action.                       |
| -10015 | `RedirectLoginComplete` | The redirect login process has been successfully completed.                                                                           |

### Standard JSON RPC 2.0 Error Codes

|        |                  |                                                                                                                                                         |
| ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Code   | Enum Key         | Description                                                                                                                                             |
| -32700 | `ParseError`     | Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.                                                   |
| -32600 | `InvalidRequest` | The JSON sent is not a valid Request object.                                                                                                            |
| -32601 | `MethodNotFound` | The method does not exist / is not available.                                                                                                           |
| -32602 | `InvalidParams`  | Invalid method parameter(s).                                                                                                                            |
| -32603 | `InternalError`  | Internal JSON-RPC error. These can manifest as different generic issues (i.e.: attempting to access a protected endpoint before the user is logged in). |

### `ExtensionError`

The `ExtensionError` class is exposed for `instanceof` operations:

```javascript JavaScript icon="square-js" theme={null}
import { ExtensionError } from "magic-sdk";

try {
  // Something async...
} catch (err) {
  if (err instanceof ExtensionError) {
    // Handle...
  }
}
```

`ExtensionError` instances expose the `code` field which may be used to deterministically identify the error. Magic SDK does not export a global enumeration of Extension error codes. Instead, Extension authors are responsible for exposing and documenting error codes relevant to the Extension's use-case.

### PromiEvents

Magic SDK provides a flexible interface for handling methods which encompass multiple "stages" of an action. `Promises` returned by Magic SDK resolve when a flow has reached finality, but certain methods also contain life-cycle events that dispatch throughout. We refer to this interface as a `**PromiEvent**`. There is prior art to inspire this approach in Ethereum's [Web3](https://web3js.readthedocs.io/en/v1.2.1/callbacks-promises-events.html) standard.

`**PromiEvent**` is a portmanteau of `Promise` and [`EventEmitter`](https://github.com/primus/eventemitter3). Browser and React Native SDK methods return this object type, which is a native JavaScript `Promise` overloaded with `EventEmitter` methods. This value can be `awaited` in modern `async/await` code, or you may register event listeners to handle method-specific life-cycle hooks. Each `PromiEvent` contains the following default event types:

* `**"done"**`: Called when the `Promise` resolves. This is equivalent to `Promise.then`.
* `**"error"**`: Called if the `Promise` rejects. This is equivalent to `Promise.catch`.
* `**"settled"**`: Called when the `Promise` either resolves or rejects. This is equivalent to `Promise.finally`.

Look for additional event types documented near the method they relate to. Events are strongly-typed by TypeScript to offer developer hints and conveniant IDE auto-complete.

```javascript JavaScript icon="square-js" theme={null}
const req = magic.auth.loginWithMagicLink({ email: 'hello@magic.link' });

req
  .on('email-sent', () => {/* ... */})
  .then(DIDToken => {/* ... */})
  .once('email-not-deliverable', () => {/* ... */})
  .catch(error => {/* ... */})
  .on('error', error => {/* ... */});
```

## EVM RPC Methods

Magic supports the following EVM RPC Methods that can be called through a web3 provider library such as `ethers.js`.

**Note**: starting from `magic-sdk@17.0.0`, `eth_accounts` will return an empty array if no user is logged in, instead of prompting the login form. To prompt the login form, use `connectWithUI()`.

* `eth_accounts`
* `get_balance`
* `eth_estimateGas`
* `eth_gasPrice`
* `eth_sendTransaction`
* `personal_sign`
* `eth_signTypedData_v3`
* `eth_signTypedData_v4`

## Examples

### Re-authenticate Users

A user's Magic SDK session persists up to 7 days by default, so re-authentication is usually friction-less.

Note: the session length is customizable by the developer through the [Magic Dashboard](https://dashboard.magic.link).

Before re-authenticating a user, [install the Magic Client SDK​](#installation).

```javascript JavaScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";
const magic = new Magic("PUBLISHABLE_API_KEY");

const email = 'example@magic.link';

if (await magic.user.isLoggedIn()) {
  const didToken = await magic.user.getIdToken();

  // Do something with the DID token.
  // For instance, this could be a `fetch` call
  // to a protected backend endpoint.
  document.getElementById('your-access-token').innerHTML = didToken;
} else {
  // Log in the user
  const user = await magic.auth.loginWithMagicLink({ email });
}
```

## Resources

* [GitHub](https://github.com/magiclabs/magic-js)
* [Quickstart](/embedded-wallets/quickstart/overview)
* [NPM](https://www.npmjs.com/package/magic-sdk)

## Versions

All changes to the SDK are covered in our [latest release notes](https://github.com/magiclabs/magic-js/releases).
