# TypeScript / JavaScript SDK (/get-started/sdks/client-side/typescript)

> For the complete documentation index, see [llms.txt](/llms.txt)



The ZeroDev TypeScript SDK (`@zerodev/sdk`) is the core package for building smart-account apps in JavaScript/TypeScript. This tutorial walks you through minting an NFT without paying gas.

<Callout type="info">
  Impatient?  Check out [the complete example here](https://github.com/zerodevapp/zerodev-examples/tree/main/tutorial/completed.ts).
</Callout>

Before you start, [set up a ZeroDev project](/get-started/sdks/setup-project) so you have an RPC URL with a gas-sponsorship policy.

## Write the code [#write-the-code]

Clone the [ZeroDev examples repo](https://github.com/zerodevapp/zerodev-examples).  Then, inside the directory, install all dependencies:

<Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
  <Tab value="npm">
    ```bash
    npm install
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm install
    ```
  </Tab>

  <Tab value="bun">
    ```bash
    bun install
    ```
  </Tab>
</Tabs>

Create a `.env` file with the following line, using the RPC URL from your ZeroDev project:

```
ZERODEV_RPC=<YOUR_RPC>
```

If all goes well, you should be able to run:

```bash
npx ts-node tutorial/completed.ts
```

Now open the [`tutorial/template.ts`](https://github.com/zerodevapp/zerodev-examples/blob/main/tutorial/template.ts) file in your editor.  This will be the template where you will write your code.  You can always refer to [`tutorial/completed.ts`](https://github.com/zerodevapp/zerodev-examples/blob/main/tutorial/completed.ts) to see the completed tutorial code.

<div className="fd-steps">
  <div className="fd-step">
    ### Create a signer [#create-a-signer]

    Kernel accounts support many different signing methods, including ECDSA keys and passkeys.  In this tutorial, we will use ECDSA keys which are the same type of keys that MetaMask and other Ethereum wallets use.

    Let's start by generating a random key.  Add the following code to the `main` function:

    ```typescript
    import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"

    const main = async () => {
      const privateKey = generatePrivateKey()  // [!code focus]
      const signer = privateKeyToAccount(privateKey)  // [!code focus]
    }
    ```
  </div>

  <div className="fd-step">
    ### Create a validator [#create-a-validator]

    Each Kernel account handles validation through a smart contract known as a "validator."  In this case, we will be using the ECDSA validator.

    Add the following code to create the ECDSA validator:

    ```typescript
    import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"

    const main = async () => {
      // other code...

      const ecdsaValidator = await signerToEcdsaValidator(publicClient, { // [!code focus]
        signer,  // [!code focus]
        entryPoint,  // [!code focus]
      })  // [!code focus]
    }
    ```
  </div>

  <div className="fd-step">
    ### Create an account [#create-an-account]

    We are now ready to create an account.  Add the following code:

    ```typescript
    import { createKernelAccount } from "@zerodev/sdk"
     
    const main = async () => {
      // other code...

      const account = await createKernelAccount(publicClient, {  // [!code focus]
        plugins: {  // [!code focus]
          sudo: ecdsaValidator,  // [!code focus]
        },  // [!code focus]
        entryPoint,  // [!code focus]
      })  // [!code focus]
    }
    ```
  </div>

  <div className="fd-step">
    ### Creating a Kernel client [#creating-a-kernel-client]

    Finally, we are going to create an "account client" which serves as the connection between your account and some AA infra (i.e. bundlers and paymasters).  The connection is necessary for you to actually send UserOps.

    Add the following code:

    ```typescript
    const main = async () => {
      // ... other code

      const zerodevPaymaster = createZeroDevPaymasterClient({
        chain,
        transport: http(ZERODEV_RPC),
      })

      const kernelClient = createKernelAccountClient({
        account,
        chain,
        bundlerTransport: http(ZERODEV_RPC),
        client: publicClient,
        paymaster: {
            getPaymasterData(userOperation) {
                return zerodevPaymaster.sponsorUserOperation({userOperation})
            }
        },
      })

      const accountAddress = kernelClient.account.address
      console.log("My account:", accountAddress)
    }
    ```

    Run this script with `npx ts-node tutorial/template.ts` and confirm that it prints an address.
  </div>

  <div className="fd-step">
    ### Send a UserOp [#send-a-userop]

    Now that you have an account client, it's time to send your first UserOp!  For this tutorial, we will mint an NFT from a contract deployed on Sepolia.

    Add the following import and code:

    ```typescript
    import { encodeFunctionData } from "viem"

    const main = async () => {
      // ... other code

      const userOpHash = await kernelClient.sendUserOperation({
          callData: await kernelClient.account.encodeCalls([{
            to: contractAddress,
            value: BigInt(0),
            data: encodeFunctionData({
              abi: contractABI,
              functionName: "mint",
              args: [accountAddress],
            })
          }])
      })
      console.log("Submitted UserOp:", userOpHash)
    }
    ```

    There's quite a bit of code going on, so let's go through it.

    * We start by calling `kernelClient.sendUserOperation`, which takes a `userOperation` argument.
    * Inside `userOperation`, we specify a `callData` field.  This is the equivalent of the calldata field for a normal Ethereum transaction.
    * Since we want to call the `mint(address)` function on the NFT contract, we use Viem's helper function `encodeFunctionData` and give it the ABI, function name, and function argument.
    * `kernelClient.sendUserOperation` returns a "UserOperation hash."  This is the equivalent of a transaction hash but for a UserOp.

    Run the script again with `npx ts-node tutorial/template.ts` and confirm that it prints the UserOp hash.  At this point, you can go to a UserOp explorer such as [JiffyScan](https://jiffyscan.xyz/) and find your UserOp with the hash!
  </div>

  <div className="fd-step">
    ### Waiting for the UserOp [#waiting-for-the-userop]

    When you call `sendUserOperation`, the call returns as soon as the UserOp has been submitted to the bundler, but it doesn't wait for the UserOp to be "confirmed" on-chain.  To wait for the UserOp to be confirmed, add the following import and code:

    ```typescript

    const main = async () => {
      // ... other code

      const receipt = await kernelClient.waitForUserOperationReceipt({
        hash: userOpHash,
      })
      console.log("UserOp confirmed:", receipt.userOpHash)
    }
    ```

    Let's break down the code:

    * `waitForUserOperationReceipt` is a bundler action.  If you are unfamiliar with the concept of "actions," you can read more about it on [Viem's documentation](https://viem.sh/docs/actions/public/introduction).
    * This function returns a "receipt" object.  If you are curious, you can print the full object and see what it contains.
  </div>

  <div className="fd-step">
    ### Read contract state [#read-contract-state]

    Now let's confirm that we actually minted an NFT.  Add the following import and code:

    ```typescript
    import { publicActions } from "viem"

    const main = async () => {
      // ... other code

      const nftBalance = await publicClient.readContract({
        address: contractAddress,
        abi: contractABI,
        functionName: 'balanceOf',
        args: [accountAddress],
      })
      console.log(`NFT balance: ${nftBalance}`)
    }
    ```

    Run the script again.  You should see that it prints `NFT balance: 1`, confirming that you have minted an NFT!
  </div>
</div>

## Next steps [#next-steps]

In this tutorial, we were able to mint an NFT without paying gas, thanks to gas sponsorship.

For next steps:

* Check out [the core API](/onboarding/create-a-smart-account) to learn more about the SDK
* Read some [code examples](https://github.com/zerodevapp/zerodev-examples) of using ZeroDev
