# Cometh Documentation

## What is Cometh?

Cometh is building the DeFi-native Banking-as-a-Service (BaaS) infrastructure for Europe.

Our platform enables fintechs, asset managers, and corporates to launch regulated crypto services—custody, staking, swaps, and more via a unified API and smart wallet infrastructure. \
We combine a fully composable tech stack (Crosschain Safe, ERC-4337) with a MiCA-aligned regulatory perimeter to deliver a modular, secure, and scalable foundation for the next generation of financial services.

> If you want to see what our infrastructure enables, try [Louis.finance](https://louis.finance), our live showcase for compliant stablecoin yield solutions.
>
> The full documentation for the DeFi-as-a-Service API layer is coming soon.

***

## Cometh Connect (4337 Wallet SDK)&#x20;

The Cometh Connect SDK is already fully documented and available.

Cometh Connect is a **white-labeled** SDK (Web/TS) that enables applications to leverage **account abstraction and ERC4337** and provide their users a smart wallet controlled with biometrics. It is built on the latest stack available (viem/wagmi/bun) for performance.

Coupled with a web2 authentication system (or standalone to stay anonymous), users are onboarded with web2 convenience and web3 security: biometric signatures (passwordless, non custodial), gasless transactions, account recovery, etc.

Smart wallets deployed with Connect are based on industry standards that power millions of identities (WebAuthn) and hold wallets with >70b$ (Gnosis Safe).


# Getting started

Estimated time: 3min

You want to try Cometh Connect ? [Request your access to the dashboard](https://app.cometh.io/register?product=connect\&utm_source=doc)

In this tutorial, we'll create a smart wallet and execute a transaction to increment a counter smart contract, using Cometh Connect.&#x20;

## Get Access to Cometh Connect

To use Cometh Connect, the first step is to get your api keys at [https://app.cometh.io](https://app.cometh.io/login?callbackUrl=/dashboard). If this is your first time using Cometh Connect, please [reach out](https://calendly.com/aurelien-gm/30min?utm_source=alembic-dashboard-marketplace) so we can set you up with a Connect account and apiKey.

<figure><img src="/files/UQdVG7pU0NUsMZTGIwAm" alt="" width="470"><figcaption></figcaption></figure>

## Setting up your project

Once you validate the email registration, you'll have access to that panel:

<figure><img src="/files/AcwfoYbQbR503xyxiuTb" alt="" width="563"><figcaption></figcaption></figure>

By choosing Connect, you'll be able to create your first Cometh Connect project. For this tutorial, we will create a project on the Polygon testnet network.

<figure><img src="/files/b6adPFO8jfZKtweZVQ3U" alt="" width="468"><figcaption></figcaption></figure>

You will then have access to your dashboard.

## Get your API Keys

On your dashboard, you can click on the apiKey button to access all your project's credentials.

<figure><img src="/files/Rq0VZqp8mhbvlmiv6Pk2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/LTr4XoeVB5fBfOi3yI57" alt="" width="436"><figcaption></figcaption></figure>

You now have your project API key, congrats ! It will be used in your front-end to authenticate public API calls.

## Test the boilerplate

Going back to the apiKey button, by clicking on the getting started button you'll be able to download the tutorial repository and start trying it out !

<figure><img src="/files/WZTah2WFLvSIEiGLQLta" alt=""><figcaption></figcaption></figure>

Once done, you can run the examples using bun:

{% hint style="info" %}
The project runs on bun, you might need to [install bun](https://bun.sh/docs/installation\)) if you do not have it already:For each example, you'll have to create an env file with the associated values:
{% endhint %}

```
bun install
bun dev
```

You can now go and test the demo live, here is what you should be able to do:

<figure><img src="/files/5OlPWY3b1p4T3lKW7Z30" alt=""><figcaption></figcaption></figure>


# Supported Networks

Cometh Connect 4337 is getting compatible with all EVM Chains (and their **testnet**)

**Already available on:**

* Arbitrum One  (42161) / Abitrum Sepolia (421614)
* Base (8453) / Base Sepolia (84532)
* Polygon POS (137) / Polygon Amoy (80002)
* Gnosis chain (100)
* Optimism (10) / Optimism Sepolia (11155420)
* Berachain (80094) / Berachain bArtio (80084)
* Binance Smart Chain (56) / BSC testnet (97)
* Linea (59144) / Linea Sepolia (59141)

[**Contact us** ](https://calendly.com/aurelien-gm/30min)**to activate Cometh Connect on the following networks:**

* Avalanche
* Celo
* Cronos
* Fantom
* Mantle
* MoonRiver
* MoonBeam
* Others...


# Create a Wallet

Onboard your user with a few lines of code

## Install

{% tabs %}
{% tab title="npm" %}

```
npm i @cometh/connect-sdk-4337 @viem
```

{% endtab %}

{% tab title="yarn" %}

```
yarn @cometh/connect-sdk-4337 @viem
```

{% endtab %}
{% endtabs %}

## Create a new wallet

{% tabs %}
{% tab title="TS" %}

```typescript
import { createSafeSmartAccount, 
  createSmartAccountClient } from "@cometh/connect-sdk-4337";
import { http } from "viem"

const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const publicClient = createPublicClient({
    chain,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});


const smartAccount = await createSafeSmartAccount({
    apiKey,
    publicClient,
    chain,
 });
 
const walletAddress = smartAccount.address

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
})

```

{% endtab %}
{% endtabs %}

You'll be prompted to create a passKey for your current domain. Depending on the user's device, the UX might be different.

<figure><img src="/files/yZB3NdnmNO3wB5SbbjKO" alt="" width="563"><figcaption></figcaption></figure>

Thanks to these credentials, your wallet address will be predicted and can already be used to receive funds.

However, note that at this point the wallet has not been created on-chain yet: the Safe is deployed on the first transaction of the wallet.

To **get the address** of the created wallet, you'll have to call:

```typescript
const address = smartAccount.address
```

{% hint style="info" %}
**You must store the wallet address of your user.** Not saving this address will prevent your user from accessing the wallet it in the future.&#x20;

It is recommended to store the wallet address in your backend, linked to your user. For a quick demo or Proof of Concept (POC), you may use local storage.
{% endhint %}

## Connect to an existing connect wallet

When you already have created your user's wallet through Cometh Connect, just pass the wallet address to the connect method in order to instantiate it.

{% tabs %}
{% tab title="TS" %}

```typescript
import { createSafeSmartAccount, 
  createSmartAccountClient } from "@cometh/connect-sdk-4337";

const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const publicClient = createPublicClient({
    chain,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});


const smartAccount = await createSafeSmartAccount({
    apiKey,
    publicClient,
    chain: arbitrumSepolia,
    smartAccountAddress: WALLET_ADDRESS,
 });
        

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl)
})
```

{% endtab %}
{% endtabs %}

## Advanced signer configuration

When instantiating the sdk, you are able to configure some optional parameters:

* **webAuthnOptions:** Allows you to customize your webAuthn credentials ([authenticatorSelection](https://www.w3.org/TR/webauthn-2/#dictdef-authenticatorselectioncriteria), [extensions](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/WebAuthn_extensions)...). By default we use [platform authentication](https://www.w3.org/TR/webauthn-2/#platform-authenticators), but you can customize it the way you like.
* **disableEoaFallback:** By default we provide a local wallet solution in the rare case of browser not fully supports platform authentication. You have the ability to disable that feature using this boolean.
* **passKeyName**: Allows to name the webAuthn credential that you create through cometh connect.
* **sessionKeysEnabled**: Allows to use sessionkeys for your project.

{% tabs %}
{% tab title="TS" %}

```typescript
import { 
createSafeSmartAccount, 
createSmartAccountClient,
webAuthnOptions
} from "@cometh/connect-sdk-4337";

const publicClient = createPublicClient({
    chain,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});

const comethSignerConfig = {
// These are the default values we use
    webAuthnOptions: webAuthnOptions = {
    authenticatorSelection: {
      authenticatorAttachment:"platform",
      residentKey: "preferred",
      userVerification: "preferred",
    },
    }
    passKeyName: "Cometh Connect",
    disableEoaFallback: false
}

const sessionKeysEnabled = true
  
const smartAccount = await createSafeSmartAccount({
    apiKey,
    publicClient,
    chain,
    comethSignerConfig,
    sessionKeysEnabled
 });
        

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    publicClient,
})
```

{% endtab %}
{% endtabs %}


# Send transactions

Sending user operations has never been easier

Considering that the authentication part is done, you can start to send transactions. You'll just need to create the transaction and send it to the SDK.&#x20;

Depending on the UX you want to provide, there are different ways of sending transactions.

{% hint style="info" %}
For **non sponsored transactions**, each wallet will have to pay a prefund to the Entrypoint contract or be filled with native tokens.

To enable **sponsored transactions**, see the [following documentation](/core-features/go-gasless).
{% endhint %}

### Send a single transaction

{% tabs %}
{% tab title="TS" %}

```typescript
import { encodeFunctionData } from "viem";
import countContractAbi from "@/contract/counterABI.json";

const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const calldata = encodeFunctionData({
  abi: countContractAbi,
  functionName: "count",
});

const txHash =  await smartAccountClient.sendTransaction(
  {
    to: COUNTER_CONTRACT_ADDRESS,
    data: calldata,
  }
);
```

{% endtab %}
{% endtabs %}

<figure><img src="/files/R1wZYHtBEXC7sllKcMBg" alt="" width="563"><figcaption></figcaption></figure>

### Send transactions batches

With Cometh Connect, you can execute multiple actions in the same transaction.

{% tabs %}
{% tab title="TS" %}

```typescript
import { encodeFunctionData } from "viem";
import countContractAbi from "@/contract/counterABI.json";

const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const txHash =  await smartAccountClient.sendUserOperation(
      {
        calls: [
          {
            to: COUNTER_CONTRACT_ADDRESS,
            data: calldata,
          },
          {
            to: COUNTER_CONTRACT_ADDRESS,
            data: calldata,
          },
        ],
      }
    )

```

{% endtab %}
{% endtabs %}


# Go Gasless

With Cometh Connect, you can pay the transaction gas fees of your users. You will need to add the contract address of your transaction as a sponsored address for your project. Remember, the contract address corresponds to the "to" field of your transaction.&#x20;

To authorize the sponsorship of a given contract address, you need to add it to your sponsored addresses in the dashboard. From there, we will accept sponsoring transactions targeting this contract.

<figure><img src="/files/soKWQVrACsmLaTAjxhMa" alt=""><figcaption><p>Add a sponsored contract address</p></figcaption></figure>

{% hint style="info" %}
At the end of each month, you will receive an invoice with the total amount of gas fees covered. This fee is then billed through the payment method in your Cometh Connect account.

With Cometh Connect, there is no overhead on the price you pay. Depending on your license type, you have a max number of transactions you can sponsor each month.
{% endhint %}

More details about the Cometh paymaster API :

{% content-ref url="/pages/wahxvj734Q7seLkBBbBp" %}
[Paymaster API](/paymaster/paymaster-api)
{% endcontent-ref %}

## Paymaster client

You'll first need to instantiate a paymaster Client, you'll need to get a **paymasterUrl from the cometh dashboard.**

{% tabs %}
{% tab title="TS" %}

```typescript
import { createComethPaymasterClient } from "@cometh/connect-sdk-4337";

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
    publicClient,
})
```

{% endtab %}
{% endtabs %}

You'll then need to add a paymaster methods on your client creation: **getUserOperationGasPrice**.

{% tabs %}
{% tab title="TS" %}

```typescript
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
})
```

{% endtab %}
{% endtabs %}

Here is the full overview:

{% tabs %}
{% tab title="TS" %}

```typescript
import { createSafeSmartAccount, 
createSmartAccountClient,
createComethPaymasterClient } from "@cometh/connect-sdk-4337";
import { encodeFunctionData } from "viem";
import countContractAbi from "@/contract/counterABI.json";

const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.4337_PAYMASTER_URL

 const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});


const smartAccount = await createSafeSmartAccount({
    apiKey,
    publicClient,
    chain: arbitrumSepolia,
});

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
    publicClient,
})
    
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})

// Counter address that is sponsored
const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const calldata = encodeFunctionData({
  abi: countContractAbi,
  functionName: "count",
});

const txHash =  await smartAccountClient.sendTransaction(
  {
    to: COUNTER_CONTRACT_ADDRESS,
    data: calldata,
  }
);
```

{% endtab %}
{% endtabs %}

###


# Sign/Verify a message

You can sign and verify messages in just 1 line of code.

With Cometh Connect, we deploy a smart account for our users.&#x20;

With Connect, we deploy the smart account at the first transaction of the user, allowing to reduce gas cost for our clients (it's a feature called lazy deployment). You might want to verify the signature of a wallet that is not yet deployed. We follow the [ERC6492 standard t](https://eips.ethereum.org/EIPS/eip-6492)o enable this.

In practice, this is what it looks like:

```typescript
import { createPublicClient, http } from "viem"

const message = "hello world";
const signature = await smartAccountClient.account.signMessage({ message });

export const publicClient = createPublicClient({
  chain,
  transport: http()
})

const valid = await publicClient.verifyMessage({
  address: smartAccountClient.account.address,
  message,
  signature,
})
```


# Retrieve a wallet address

Retrieve your user wallet without a backend

With Cometh Connect, you can **retrieve the walletAddress** of your user **without interacting with a backend**.&#x20;

To do so, you'll need to let your user sign with one of the passkeys that was used to create a wallet. With the signature, we'll be able to retrieve and send back the wallet address linked to the user.&#x20;

This is the code needed to implement that feature.

{% tabs %}
{% tab title="TS" %}

```typescript
import { retrieveAccountAddressFromPasskeys } from "@cometh/connect-sdk-4337";

const apiKey = process.env.COMETH_API_KEY;
const chain = gnosis;

const walletAddress = await retrieveAccountAddressFromPasskeys({apiKey, chain});

```

{% endtab %}
{% endtabs %}

The user will have to **select the passkey** and **sign a message** in order to retrieve the wallet address.

He will then be able to connect to the wallet.

<figure><img src="/files/s6FQyloUFt0i7wpHGHTI" alt="" width="563"><figcaption></figcaption></figure>


# Handle owners

You can easily add, remove or get all your wallet owners.

You can easily **get/add/remove** all the owners of your smart wallet.

The [**getEnrichedOwners**](#get-owners) method allows you to get more infos regarding your passkey owners (device information, creationDate...).

## Add owners

{% tabs %}
{% tab title="TS" %}

<pre class="language-typescript"><code class="lang-typescript">const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})

<strong>const txHash = await smartAccountClient.addOwner({ownerToAdd: ADDRESS_TO_ADD});
</strong>
</code></pre>

{% endtab %}
{% endtabs %}

## Remove owners

{% tabs %}
{% tab title="TS" %}

```typescript
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})


const txHash = await smartAccountClient.removeOwner({ownerToRemove: ADDRESS_TO_REMOVE});
```

{% endtab %}
{% endtabs %}

## Get owners

{% tabs %}
{% tab title="TS" %}

```typescript
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})

// get all owners
const owners = smartAccountClient.getOwners()

// get owners with passkey details (creation date, device data...)
const owners = smartAccountClient.getEnrichedOwners()
```

{% endtab %}
{% endtabs %}


# Import a safe into connect

Import safe into Cometh Connect

{% hint style="warning" %}

* The import function **only supports safe version 1.3.0 and 1.4.1**
* The import function **only works for deployed safe**
  {% endhint %}

This import will migrate your safe and make it usable with passkeys in Cometh Connect.

What will happen:

* The Safe account is migrated to version 1.4.1 (only happens for version 1.3.0).&#x20;
* A passkey will be created and added as an owner

### Import a safe 1.3.0

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
    importSafeActions,
} from "@cometh/connect-sdk-4337";
import { http, encodeFunctionData } from "viem";
import { gnosis } from "viem/chains";
import countContractAbi from "../contract/counterABI.json";
import { privateKeyToAccount } from "viem/accounts";

const apiKey = process.env.NEXT_PUBLIC_COMETH_4337_API_KEY;
const chain = gnosis;

const bundlerUrl = "https://bundler.cometh.io/"+CHAIN_ID+"?apikey="+COMETH_4337_API_KEY;
const paymasterUrl =  "https://paymaster.cometh.io/"+CHAIN_ID+"?apikey="+COMETH_4337_API_KEY;

// Step 1 -  This is the address of you safe using the connect legacy
const smartAccountAddress = WALLET_ADDRESS_TO_IMPORT

// Your current owner
const signer = privateKeyToAccount(PK)

const safe4337SmartAccount = await createSafeSmartAccount({
    apiKey,
    chain,
    smartAccountAddress: legacyWalletAddress,
    signer,
});


const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
})

const extendedClient = smartAccountClient.extend(importSafeActions());

const importMessage = await extendedClient.prepareImportSafe1_3Tx();

const signature = (await extendedClient.signTransactionByExternalOwner({
    signer,
    tx: importMessage.tx,
})) as Hex;

await extendedClient.importSafe({
    signature,
    ...importMessage
});
```

### Import a safe 1.4.0

<pre class="language-typescript"><code class="lang-typescript"><strong>//previous code is the same as 1.3.0
</strong><strong>
</strong><strong>const importMessage = await extendedClient.prepareImportSafe1_4Tx();
</strong>
// after code is the same as 1.3.0
</code></pre>

### After import

When the import has been done, you can then use a regular Cometh Connect client:

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
} from "@cometh/connect-sdk-4337";
import { http, encodeFunctionData } from "viem";
import { gnosis } from "viem/chains";
import countContractAbi from "../contract/counterABI.json";

const smartAccountAddress = WALLET_ADDRESS_IMPORTED

const importedAccount = await createSafeSmartAccount({
    apiKey,
    chain: gnosis,
    smartAccountAddress,
    entryPoint: ENTRYPOINT_ADDRESS_V07,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    publicClient,
})

const calldata = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count"
});

//You can send transaction
const txHash = await smartAccountClient.sendTransaction({
    to: COUNTER_CONTRACT_ADDRESS,
    data: calldata
});
```


# Session Keys

No need to sign each transactions !

{% hint style="info" %}
To activate session keys, we will **switch the fallback handler of your safe to the ERC7579 implementation.**
{% endhint %}

We implement the 7579 smart sessions audited implementation:

{% embed url="<https://github.com/erc7579/smartsessions>" %}


# Tutorial

Example of a session key with an action policy.

### 1 - Create a Session Key

```typescript
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import {
    type ComethSmartAccountClient,
    type SafeSigner,
    erc7579Actions,
    smartSessionActions,
} from "@cometh/connect-sdk-4337";

export const COUNTER_CONTRACT_ADDRESS =
    "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";


const safe7559AccountClient = smartAccountClient.extend(smartSessionActions())
            .extend(erc7579Actions());

const privateKey = generatePrivateKey();
const sessionOwner = privateKeyToAccount(privateKey);

const createSessionsResponse = await safe7559AccountClient.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
            actionPoliciesInfo: [
                {
                    contractAddress: COUNTER_CONTRACT_ADDRESS,
                    functionSelector: toFunctionSelector(
                        "function count()"
                    ) as Hex,
                },
            ],
        },
    ],
});

await safe7559AccountClient.waitForUserOperationReceipt({
    hash: createSessionsResponse.userOpHash,
});

```

### 2 - Store the Session Key

In our example, we will store the session key details in local storage. You are free to store it wherever you want.

<pre class="language-typescript"><code class="lang-typescript">import { SmartSessionMode } from "@cometh/connect-sdk-4337";
<strong>
</strong><strong>const sessionData = {
</strong>    granter: smartAccountClient?.account?.address as Address,
    privateKey: privateKey,
    sessionPublicKey: sessionOwner.address,
    description: `Session to increment a counter`,
    moduleData: {
        permissionIds: createSessionsResponse.permissionIds,
        action: createSessionsResponse.action,
        mode: SmartSessionMode.USE,
        sessions: createSessionsResponse.sessions,
    },
};

// This is for example purposes.
const sessionParams = stringify(sessionData);

localStorage.setItem(
    `session-key-${smartAccountClient?.account?.address}`,
    sessionParams
);
</code></pre>

### 3 - Use the Session Key

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
    smartSessionActions,
    toSmartSessionsSigner
} from "@cometh/connect-sdk-4337";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";

const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.4337_PAYMASTER_URL
const publicClient = createPublicClient({
    chain,
    transport: http(),
});


const stringifiedSessionData = localStorage.getItem(
    `session-key-${WALLETADDRESS}`
);
const sessionData = parse(stringifiedSessionData);

const sessionKeySigner = await toSmartSessionsSigner(safe7559Account, 
{
    moduleData: sessionData.moduleData,
    signer: privateKeyToAccount(sessionData.privateKey),
})

const sessionKeyAccount = await createSafeSmartAccount({
    apiKey,
    chain,
    smartAccountAddress: smartAccountClient?.account?.address,
    smartSessionSigner: sessionKeySigner,
});

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
});

const sessionKeyClient = createSmartAccountClient({
    account: sessionKeyAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
}).extend(smartSessionActions());

const callData = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count",
});

const hash = await sessionKeyClient.usePermission({
    actions: [
        {
            target: COUNTER_CONTRACT_ADDRESS,
            callData: callData,
            value: BigInt(0),
        },
    ],
});
```


# Remove a session key

Remove a permission

```typescript
import {
    erc7579Actions,
    smartSessionActions,
} from "@cometh/connect-sdk-4337";

// At session key creation, you'll get the associated permissionId that you need to store
export const permissionId = "0x...";

const safe7559AccountClient = smartAccountClient.extend(smartSessionActions())
            .extend(erc7579Actions());

const removeSessionsResponse = await safe7559AccountClient.removePermission({
    permissionId: permissionId,
});

await safe7559AccountClient.waitForUserOperationReceipt({
    hash: removeSessionsResponse.userOpHash,
});
```


# Policies

For now, our session keys only allow the whitelisting of contract with functions, soon we'll add spending limits, timeframe and other features.


# Sudo policy

The sudo policy gives full permission to the signer. The signer will be able to send any UserOps.

```typescript
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
        },
    ],
});
```


# Action policy

The action policy limits the target (either contract or EOA) that the UserOp can interact with.

```typescript
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
            actionPoliciesInfo: [
                {
                    contractAddress: COUNTER_CONTRACT_ADDRESS,
                    functionSelector: toFunctionSelector(
                        "function count()"
                    ) as Hex,
                },
            ],
        },
    ],
});
```


# ERC7579 actions


# 7579 Fallback methods

7579 methods

Check if the 7579 fallback is installed:

```typescript

const is7579FallbackInstalled = await smartAccountClient.is7579Installed()

```

Set the 7579 fallback for your smart account:

```typescript

const hash = await smartAccountClient.setFallbackTo7579()

```


# Install a module

Installs a ERC-7579 module to the smart account.

```typescript
import { getSmartSessionsValidator } from "@rhinestone/module-sdk";

const sessionKeyValidator = getSmartSessionsValidator({})

const userOpHash = await smartAccountClient.installModule({
   type: smartSessions.type,
   address: smartSessions.address,
   context: smartSessions.initData,
})
 
const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash: userOpHash })
```


# Uninstall a Module

Uninstalls a ERC-7579 module from the smart account.

```typescript
const ownableExecutorModule = "0x4Fd8d57b94966982B62e9588C27B4171B55E8354"
const moduleData = encodePacked(["address"], ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"])

const userOpHash = await smartAccountClient.uninstallModule({
    type: "executor",
    address: ownableExecutorModule,
    context: moduleData,
})

const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash: userOpHash })
```


# isModuleInstalled

Checks if an ERC-7579 module is installed on the smart account.

```typescript
const ownableExecutorModule = "0x4Fd8d57b94966982B62e9588C27B4171B55E8354"
const moduleData = encodePacked(["address"], ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"])

const isInstalled = await smartAccountClient.isModuleInstalled({
    type: "executor",
    address: ownableExecutorModule,
    context: moduleData,
})
```


# Social recovery

How to recover a wallet using recovery

### What is a recovery <a href="#what-is-a-recovery" id="what-is-a-recovery"></a>

Users of Cometh Connect will generate and use a Safe as their wallet, with exclusive ownership and access to the funds it contains. Nonetheless, in the event of a lost key (such as misplacing their device or any other unforeseen circumstance), we aim to facilitate user's recovery of wallet access without compromising their security or self-custody. To achieve this, we have implemented a default feature in any Safe deployed to allow a guardian to initiate a recovery request. Upon the completion of a recovery request, the ownership structure of the Safe will be modified.

Our decentralized recovery model implementation is based on [Safe{RecoveryHub}](https://help.safe.global/en/articles/110656-account-recovery-with-safe-recoveryhub).

{% hint style="info" %}
For now, the recovery process works with **Cometh as guardian.**&#x20;

We have a default setting of **24h for recovery cooldown** period and  **7 days for recovery expiration**.
{% endhint %}

### Prerequesites <a href="#prerequesites" id="prerequesites"></a>

Before initiating a recovery request, the following conditions are assumed to be met:

* The application using Cometh SDK should have identified their users before starting any recovery procedure.

### Recovery flow <a href="#recovery-flow" id="recovery-flow"></a>

A recovery request consists of three phases:

1. Activate the recovery module: User need to activate the recovery module on his smart wallet (This has to be done only once per wallet)
2. Creating a new passkey owner: end user must create a signer that will be the new owner of his lost wallet.
3. User identification and submitting the recovery request: after identification of the user by the application, the recovery request can be created and sent to the guardian for signature.
4. Finalizing the recovery request: after Cometh's verification, provided the cooldown period is over (24h by default), the request can be processed. This action effectively modifies the ownership structure of the user's Safe.

### 1. User activates the social recovery module (if not already done, [check](#check-if-social-recovery-module-is-active)) <a href="#id-1.-user-creates-a-new-owner-for-their-wallet" id="id-1.-user-creates-a-new-owner-for-their-wallet"></a>

The wallet created by Cometh Connect does not have the module right away, the user needs to activate it by signing a transaction. Here is the transaction:

```typescript
import { createSafeSmartAccount, 
  createSmartAccountClient, 
  createComethPaymasterClient 
  } from "@cometh/connect-sdk-4337";
import { http } from "viem"
import { arbitrumSepolia } from "viem/chains";

const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const chain = arbitrumSepolia

const publicClient = createPublicClient({
    chain,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});


const smartAccount = await createSafeSmartAccount({
    apiKey,
    publicClient,
    chain
 });
 
const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
    publicClient,
})
    
const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    paymaster: paymasterClient,
    bundlerTransport: http(bundlerUrl),
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
})
        

await smartAccountClient.setUpRecoveryModule({});
```

### 2. User creates a new owner for their wallet <a href="#id-1.-user-creates-a-new-owner-for-their-wallet" id="id-1.-user-creates-a-new-owner-for-their-wallet"></a>

In the case of lost access to the wallet, you can start a recovery procedure by calling the **createNewSignerWithAccountAddress** function from the SDK. It will return an object that contains the new signer that will be the new owner of your wallet.

```typescript
const signer = await createNewSignerWithAccountAddress({
    apiKey: API_KEY_CONNECT,
    smartAccountAddress: smartAccount.account.address,
});

/*
In the case of passkeys, the signer object should look like:
{ 
    signerAddress: "0x...",
    deviceData: {
        browser: "Firefox",
        os: "macOS",
        platform: "desktop",
    };
    publicKeyId: "0x...";
    publicKeyX: "0x...";
    publicKeyY: "0x...";
}
*/
```

### 3. Submit the recovery request <a href="#id-2.-submit-the-recovery-request" id="id-2.-submit-the-recovery-request"></a>

Following the creation of the new owner, they can then initiate a recovery request. This process is similar to a "Forgot password" feature, requiring the user to specify the new addresses they wish to designate as the new owners of their Safe.

From a technical standpoint, after identification of the user at the application level, this is done by calling Cometh Connect API.

{% hint style="info" %}
Recovery endpoints are protected with an **apisecret,** indicating that requests should be done from your backend for privacy concerns.
{% endhint %}

There are two ways to perform a recovery. If you want to remove all existing owners of the safe and replace them with a new owner, use the `/recovery/start` service. If the safe uses the SafeWebAuthnSharedSigner, you can simply modify the passkey associated with your wallet on this contract. In this case, use `/recovery/start-shared`.

#### /recovery/start

```typescript
export const api = axios.create({
    baseURL:"https://api.4337.cometh.io"
});

api.defaults.headers.common["apisecret"] = process.env.COMETH_API_SECRET;

const body = {
    chainId: arbitrumSepolia.id.toString(),
    walletAddress: smartAccountClient.account.address,
    newOwner: signer.signerAddress,
    publicKeyId: signer.publicKeyId,
    publicKeyX: signer.publicKeyX,
    publicKeyY: signer.publicKeyY,
    deviceData: signer.deviceData
};

await api.post("/recovery/start", body);
```

#### /recovery/start-with-shared

```typescript
export const api = axios.create({
    baseURL:"https://api.4337.cometh.io"
});

api.defaults.headers.common["apisecret"] = process.env.COMETH_API_SECRET;

const body = {
    chainId: arbitrumSepolia.id.toString(),
    walletAddress: smartAccountClient.account.address,
    publicKeyId: signer.publicKeyId, // Returned by the system, or you can concatenate X and Y  
    publicKeyX: signer.publicKeyX,
    publicKeyY: signer.publicKeyY,
    deviceData: signer.deviceData // {"os":"iOS"}
};

await api.post("/recovery/start-with-shared", body);
```

## 4. Finalize the recovery request

Once the 24h recovery period is over without the owner of the Safe canceling the recovery, the request can be finalized. This step does not require any signature and can be executed by any party. Either Cometh, the project or the user can finalize the request.

An easy way to finalize the recovery request is by calling Cometh Connect API:

```typescript
export const api = axios.create({
    baseURL:"https://api.4337.cometh.io"
});

api.defaults.headers.common["apisecret"] = process.env.COMETH_API_SECRET;

const body = {
  chainId: arbitrumSepolia.id.toString(),
  walletAddress: WALLET_ADDRESS,
};
            
await api.post(`/recovery/finalize`, body);
```

## Check if social recovery module is active

You can get the current recovery request using the **isRecoveryActive** method of the SDK.

**Parameters**

* `effectiveDelayAddress` (string, optional): The address of the delay module. By default, the delay module address related to the guardian address of Cometh is used.

Here is the method to call from the SDK:

```typescript
await smartAccountClient.isRecoveryActive({publicClient});

/* returned params:
{
    isDelayModuleDeployed = true,
    guardianAddress = "0x..."
}
*/
```

## Get a recovery Request

You can get the current recovery request using the **getRecoveryRequest** method of the SDK. You'll get the creation date of the request and the hash of the transaction.

**Parameters**

* `effectiveDelayAddress` (string, optional): The address of the delay module. By default, the delay module address related to the guardian address of Cometh is used.

Here is the method to call from the SDK:

<pre class="language-typescript"><code class="lang-typescript">await smartAccountClient.getRecoveryRequest({  });

or 

await smartAccountClient.getRecoveryRequest({ effectiveDelayAddress });

/* returned params:
{
<strong>    txCreatedAt = UNIX_TIMESTAMP,
</strong>    txHash = "0x..."
}
*/
</code></pre>

## Cancel a recovery request

If a recovery request is still in the cooldown period, the user can cancel it if he has access to a device with a valid signer. This is done using the SDK method **cancelRecoveryRequest**, triggering an onchain transaction that will cancel the recovery request.

**Parameters**

* `effectiveDelayAddress` (string, optional): The address of the delay module. By default, the delay module address related to the guardian address of Cometh is used.

```typescript
await smartAccountClient.cancelRecoveryRequest({effectiveDelayAddress});
```

## Additional functions

### Setup a Custom Delay Module

This function sets up a delay module for the wallet. The setup involves deploying the delay module, enabling it on the wallet, and configuring it with the provided guardian address.

**Parameters**

* `guardianAddress` (string): The address of the guardian.
* `expiration` (number, optional): The expiration time for the delay module, 0 by default.
* `cooldown` (number, optional): The cooldown period for the delay module, 600 by default.

Here is the method to call from the SDK:

```typescript
const txHash = await smartAccountClient.setupCustomDelayModule({
    guardianAddress:GUARDIAN_ADDRESS,
    expiration: 3800,
    cooldown: 40800,
})
```

### Get delay module address given expiration and cooldown&#x20;

This function retrieves the delay module address for the wallet based on the specified expiration time and cooldown period.

**Parameters**

* `expiration` (number): The expiration time for the delay module.
* `cooldown` (number): The cooldown period for the delay module.

Here is the method to call from the SDK:

```typescript
const delayModuleAddress = await smartAccountClient.getDelayModuleAddress({
    expiration: 3800,
    cooldown: 40800,
});
```

### Get Guardian Address

This function retrieves the guardian address for the specified delay module address. It ensures that the provided delay module address is enabled on the wallet before fetching the guardian address.

**Parameters**

* `delayModuleAddress` (string): The address of the delay module from which the guardian address will be retrieved.

Here is the method to call from the SDK:

<pre class="language-typescript"><code class="lang-typescript"><strong>const guardianAddress = await smartAccountClient.getGuardianAddress({
</strong>    delayModuleAddress:DELAY_MODULE_ADDRESS,
});
</code></pre>

### Disable Guardian

This function disables a guardian's access to the delay module associated with the wallet by revoking the permissions previously granted to the specified guardian.

**Parameters**

* `guardianAddress` (string): The address of the guardian whose access is being disabled.
* `expiration` (number, optional): The expiration time for the delay module.&#x20;
* `cooldown` (number, optional): The cooldown period for the delay module.

It requires the expiration and cooldown parameters only when the delay module has been set up using the `setupDelayModule` function with specific expiration and cooldown periods.

Here is the method to call from the SDK:

```typescript
const txHash = await smartAccountClient.disableGuardian({
    guardianAddress:GUARDIAN_ADDRESS,
    expiration: 3800,
    cooldown: 40800,
});
```

### Add Guardian

This function allows to reactivate a guardian for the wallet. This function can only be used to reactivate a guardian that has been deactivated. It is important to note that at any given time, only **one guardian can be linked to the delay module**.

**Parameters**

* `delayModuleAddress` (string): The address of the delay module associated with the wallet. This module must already be enabled on the wallet.
* `guardianAddress` (string): The address of the new guardian to be added.&#x20;

Here is the method to call from the SDK:

```typescript
const txHash = await smartAccountClient.addGuardian(
    {
        delayModuleAddress:DELAY_MODULE_ADDRESS,
        guardianAddress:GUARDIAN_ADDRESS,
    }
);
```


# Add a passkey signer on a different OS

Allow your user to access their wallet from several devices with different OS environment

To add a new device as a passkey signer, the user's wallet must already exist in our system. When the user attempts to connect to their wallet on a new secondary device, no signer is available.

{% hint style="info" %}
If you have synchronized your devices through IOS or Google profile, you are able to retrieve the same passkey on all those devices.&#x20;

This flow concerns the scenario where you would want to use passkeys on different OS (for example a mac and an android phone) as signers of your smart wallet.
{% endhint %}

### 1. Create a new signer object on another device&#x20;

From the SDK, you need to call createNewSigner on the secondary device. It will create a signer and initiate a new signer request. This request, once validated on the primary device, will grant access to the secondary signer on the Safe smart wallet.

```typescript
import { createNewSigner } from "@cometh/connect-sdk-4337";


const signerObject = await createNewSigner({
    smartAccountAddress: TARGET_ACCOUNT_ADDRESS,
});

/*
In the case of passkeys, the signer object should look like:
{ 
    signerAddress: SIGNER_ADDRESS,
    deviceData: {
        browser: "Firefox",
        os: "macOS",
        platform: "desktop",
    };
    publicKeyId: "0x...";
    publicKeyX: "0x...";
    publicKeyY: "0x...";
}
*/

```

At the end of this stage you have created a signer on your new device, you'll then need to validate that signer on your main device.

### 2. Prepare the new device validation

After getting the new signer details, you will need to send this payload data to a page of your choosing where the validation will happen. To facilitate that, we included a function that serialize the above payload created an url with a given url:

```typescript
import { createNewSigner, serializeUrlWithSignerPayload } from "@cometh/connect-sdk-4337";

const signerObject = await createNewSigner({
    smartAccountAddress: TARGET_ACCOUNT_ADDRESS,
});

const validationPageUrl= YOUR_CREATED_URL

const validationUrl = await serializeUrlWithSignerPayload(validationPageUrl, signerObject)
```

You can even create a QR code that will send the user to your given url.

```typescript
import { createNewSigner, generateQRCodeUrl } from "@cometh/connect-sdk-4337";

const signerObject = await createNewSigner({
    smartAccountAddress: TARGET_ACCOUNT_ADDRESS,
});

const validationPageUrl= YOUR_CREATED_URL

const qrCode = await generateQRCodeUrl(validationPageUrl, signerObject)
```

### 3. Validate the new device

On your validation page, after extracting the data from the url,  you'll be able to call the validateAddDevice method on your main device. This method will **deploy a passkey signer** for your safe and **add it as a new owner.**

```typescript

await smartAccountClient.validateAddDevice({ signer });

```

You'll then be able to use your new device the same way you use the main one.


# Capabilities

**Connect 4337** supports capability requests defined in EIP-5792, enabling dApps to interact securely with smart accounts. It includes **sendCalls** for executing transactions, **getCallsStatus** to track their progress, **getCapabilities** to check available wallet features, and **grantPermissions** for managing user-approved access.&#x20;


# sendCalls

Requests the wallet to sign and broadcast a batch of calls (transactions) to the network in a single operation.

```typescript
import { encodeFunctionData } from "viem";
import countContractAbi from "@/contract/counterABI.json";

const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const txHash =  await smartAccountClient.sendCalls(
      {
        calls: [
          {
            to: COUNTER_CONTRACT_ADDRESS,
            value: 0,
            data: calldata,
          },
          {
            to: COUNTER_CONTRACT_ADDRESS,
            value: 0,
            data: calldata,
          },
        ],
      }
    )

```

## Returns

`string`

The request returns the **UserOperation hash**, which can be used to track the transaction status via **getCallsStatus** within the session.


# getCallsStatus

Retrieves the status and receipts of a batch call previously sent via **sendCalls**, using its **UserOperation hash**.

```typescript
const { status, receipts } = await smartAccountClient.getCallsStatus({ 
  id: userOpHash,
})
```

## Returns

The request returns the call batch status (**PENDING** or **CONFIRMED**) and, if confirmed, the transaction receipts, including logs, block details, gas used, and the transaction hash.

```json
{
  "status": "PENDING"
}
```

or

```json
{
  "status": "CONFIRMED",
  "receipts": [
    {
      "logs": [
        {
          "address": "0x1234567890abcdef1234567890abcdef12345678",
          "data": "0x...",
          "topics": ["0x...", "0x..."]
        }
      ],
      "status": "0x1",
      "blockHash": "0xabcdef...",
      "blockNumber": "0x10d4f",
      "gasUsed": "0x5208",
      "transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  ]
}
```


# getCapabilities

Extracts the capabilities supported by the connected wallet, grouped by chain ID. This includes features like **atomic batching**, **paymaster services**, and **permissions**.

```typescript
const capabilities = await smartAccountClient.getCapabilities()
```

## Returns

A JSON object detailing the wallet’s supported capabilities per chain.

```json
{
  "0x8453": {
    "atomicBatch": {
      "supported": true
    },
    "paymasterService": {
      "supported": true
    },
    "permissions": {
      "supported": true,
      "signerTypes": ["account"],
      "permissionTypes": ["sudo", "contract-call"]
    }
  }
}
```


# grantPermissions

Grants specific permissions to a session key or account, allowing controlled execution of actions such as contract calls. Permissions are time-limited and require an expiration timestamp.

### contract-call

```typescript
const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const grantParams = {
  chainId,
  signer: {
    type: "account",
    data: { address: sessionOwner.address },
  },
  permissions: [
    {
      type: "contract-call",
      data: {
        contractAddress: COUNTER_CONTRACT_ADDRESS,
        functionSelector: "function count()",
      },
      policies: [],
    },
  ],
  expiry: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
};

const response = await smartAccountClient.grantPermissions(grantParams);
```

### sudo

```typescript
const grantParams = {
  chainId,
  signer: {
    type: "account",
    data: { address: sessionOwner.address },
  },
  permissions: [
    {
      type: "sudo",
      policies: [],
    },
  ],
  expiry: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
};

const response = await smartAccountClient.grantPermissions(grantParams);
```

## Returns

A JSON object containing the granted permissions, their expiry, and the associated **UserOperation hash** for tracking.


# Other signers (Auth Providers)


# EOA wallets (Metamask, Phantom...)

## Metamask with EIP1193

EIP-1193 is a standard interface for Ethereum providers, such as MetaMask or hardware wallets, where the key material is hosted externally rather than on the local client.&#x20;

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
    providerToSmartAccountSigner
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const signer = await providerToSmartAccountSigner(
    window.ethereum
);

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
  
```

## Metamask with Viem integration

A [Wallet Client](https://viem.sh/docs/clients/wallet.html) is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc through Wallet Actions.

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
    providerToSmartAccountSigner
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const signer = walletClientToSmartAccountSigner(walletClient);

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
  
```


# Magic signer

[Magic](https://magic.link/) is a popular embedded wallet provider that supports social logins, making it easier for users to onboard without managing private keys. However, users still need to acquire crypto to pay for gas, which can create friction.

By integrating **Magic** with **Connect 4337**, you can offer a seamless social login experience while using Cometh Connect's **smart wallets** to sponsor gas fees, batch transactions, and more. This combination allows you to abstract blockchain complexities and enhance the UX of your dApp.

## Setup

To use **Magic** with **Connect 4337**, first create an application that integrates with **Magic**.

* Refer to the [Magic documentation site](https://magic.link/docs/home/welcome) for instructions on setting up an application with the Magic SDK.
* For a quick start, Magic provides a CLI to create a starter project, available [here](https://magic.link/docs/home/quickstart/cli#run-make-magic).

## Integration

After following the Magic documentation, you will have access to a `MagicBase` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import { OAuthExtension } from "@magic-ext/oauth"
import { Magic as MagicBase } from "magic-sdk"
import { providerToSmartAccountSigner } from "@cometh/connect-sdk-4337";


const rpcUrl = process.env.RPC_URL;
const magicApiKey = process.env.MAGIC_API_KEY;

const magic = new MagicBase(magicApiKey as string, {
	network: {
		rpcUrl,
		chainId: arbitrumSepolia.id,
	},
	extensions: [new OAuthExtension()],
})
 
// Get the Provider from Magic and convert it to a signer
const magicProvider = await magic.wallet.getProvider()
const signer = await providerToSmartAccountSigner(magicProvider);
  
```

#### Use it with Connect 4337:

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
  
```


# Web3Auth signer

[Web3Auth](https://web3auth.io/) is a popular embedded wallet provider that supports social logins, making it easier for users to onboard without managing private keys. However, users still need to acquire crypto to pay for gas, which can create friction.

By integrating **Web3Auth** with **Connect 4337**, you can offer a seamless social login experience while leveraging **Cometh Connect's smart wallets** to sponsor gas fees, batch transactions, and more. This combination simplifies blockchain interactions and improves the UX of your dApp.

## **Setup**

To use **Web3Auth** with **Connect 4337**, first create an application that integrates with Web3Auth.

* Refer to the [Web3Auth documentation site](https://web3auth.io/docs/index.html) for instructions on setting up an application with the Web3Auth.
* For a quick start, Web3Auth provides example starter projects, available [here](https://web3auth.io/docs/examples?product=Plug+and+Play\&sdk=Plug+and+Play+Web+Modal+SDK).

## Integration

After following the Web3Auth documentation, you will have access to a `web3auth` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

<pre class="language-typescript"><code class="lang-typescript">import { CHAIN_NAMESPACES, WEB3AUTH_NETWORK } from "@web3auth/base"
import { Web3Auth } from "@web3auth/modal"
import type { EIP1193Provider } from "viem"
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-provider"
import { providerToSmartAccountSigner } from "@cometh/connect-sdk-4337"


const rpcUrl = process.env.RPC_URL;

const chainConfig = {
    chainNamespace: CHAIN_NAMESPACES.EIP155,
    chainId: "0x66eee", // Hex of 421614
    rpcTarget: rpcUrl,
    displayName: "Arbitrum Sepolia Testnet",
    blockExplorerUrl: "https://sepolia.arbiscan.io/",
    ticker: "AETH",
    tickerName: "AETH",
    logo: "https://cryptologos.cc/logos/arbitrum-arb-logo.png",
  };

const privateKeyProvider = new EthereumPrivateKeyProvider({
    config: { chainConfig },
  });


const web3auth = new Web3Auth({
  clientId,
  uiConfig: {},
  web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
  privateKeyProvider,
<strong>});
</strong>
// Initialize for PnP Modal SDK
await web3auth.initModal();
// Trigger the login
await web3auth.connect();
 
// Get the Provider and EOA address (this will be the address of the signer) from Web3Auth
const web3authProvider = web3auth.provider as EIP1193Provider

if (!web3authProvider) {
	throw new Error("No provider found")
}

const signer = await providerToSmartAccountSigner(web3authProvider);
  
</code></pre>

#### Use it with Connect 4337:

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
```


# Turnkey signer

[Turnkey](https://www.turnkey.com/) is a key infrastructure provider with a powerful developer API and a robust security policy engine, enabling secure and flexible key management for blockchain applications.

By integrating **Turnkey** with **Connect 4337**, you can create **custodial Account Abstraction (AA) wallets**, where Turnkey ensures the security of private keys while using **Cometh Connect’s smart wallets** to sponsor gas, batch transactions, and enhance overall dApp functionality.

#### **Setup**

To use **Turnkey** with **Connect 4337**, first create an application that integrates with Turnkey.

* Refer to the [Turnkey documentation site](https://docs.turnkey.com/) for instructions on setting up an application with the Turnkey.
* For a quick start, Turnkey provides examples, available [here](https://docs.turnkey.com/getting-started/examples).

## Integration

After following the Turnkey documentation, you will have access to a `TurnkeyClient` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import { TurnkeyClient } from "@turnkey/http"
import { createAccount } from "@turnkey/viem"


// Param options here will be specific to your project.  See the Turnkey docs for more info.
const turnkeyClient = new TurnkeyClient({ baseUrl: "" }, stamper)
 
const turnkeySigner = await createAccount({
	client: turnkeyClient,
	organizationId: subOrganizationId, // Your subOrganization id
	signWith: signWith, // Your suborganization `signWith` param.
})
  
```

#### Use it with Connect 4337:

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer: turnkeySigner,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
  
```


# Privy signer

[Privy](https://www.privy.io/) is an embedded wallet provider that simplifies user onboarding for dApps, enabling seamless authentication and key management.

By integrating **Privy** with **Connect 4337**, you can use Privy as a **signer** to create and manage **smart wallets** while using **Connect 4337** gas sponsorship, batched transactions, and more.

## Create the Privy provider

Follow Privy’s [quickstart guide](https://docs.privy.io/guide/quickstart), to set up the Privy provider in your app.

```tsx
import { PrivyProvider } from '@privy-io/react-auth';
import {WagmiProvider} from '@privy-io/wagmi'; 
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {createConfig} from '@privy-io/wagmi'; 

import { http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const queryClient = new QueryClient(); 
 
const config = createConfig({ 
  chains: [arbitrumSepolia], 
  transports: { 
    [arbitrumSepolia.id]: http(), 
  }, 
}); 

<PrivyProvider
  appId={"<Privy-App-Id>"}
  config={{
    embeddedWallets: {
      createOnLogin: "all-users",
    },
  }}
>
   <QueryClientProvider client={queryClient}>
    <WagmiProvider config={config}>
        {children}
     </WagmiProvider>
  </QueryClientProvider>
</PrivyProvider>;
 
```

## Integration

In your app, set Privy's embedded wallet as the active wallet for wagmi by using the **useWallets** react hook (after[ Privy login](https://docs.privy.io/guide/react/authentication/login/)).

```typescript
import { useWallets } from "@privy-io/react-auth";


const { wallets } = useWallets();
const embeddedWallet = wallets.find(
  (wallet) => wallet.walletClientType === "privy"
);

```

#### Use it with Connect 4337:

```typescript
import {
    createComethPaymasterClient,
    createSafeSmartAccount,
    createSmartAccountClient,
    providerToSmartAccountSigner
} from "@cometh/connect-sdk-4337";
import { type Hex, type PublicClient, createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const apiKey = process.env.COMETH_API_KEY;
const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

if (!embeddedWallet) throw new Error("User does not have an embedded wallet");

const privyProvider = await embeddedWallet!.getEthereumProvider()
const signer = await providerToSmartAccountSigner(privyProvider);

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
}) as PublicClient;

const smartAccount = await createSafeSmartAccount({
    apiKey,
    signer,
    chain: arbitrumSepolia,
    publicClient,
    smartAccountAddress //if smart account already exists
 });
 
const walletAddress = smartAccount.address

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
});
```


# React hooks

## Install

```
npm i @cometh/connect-react-hooks
```

## Note

The hooks will throw an error if it is used outside of a `ConnectProvider`. Ensure that your component tree includes `ConnectProvider` to provide the necessary context.


# ConnectProvider

## Description

The `ConnectProvider` component in TypeScript React sets up a context provider for managing the ConnectSmartAccount related state and functionality.

## Parameters

* **apiKey** : Cometh Connect public api key. This can be retrieved from the Cometh dashboard
* **networksConfig**: \
  \- **bundlerUrl**: The URL of the Cometh bundler. This can be retrieved from the Cometh 4337 docs and dashboard.\
  \- **paymasterUrl** - optional: The paymaster API key. This can be retrieved from the Cometh dashboard\
  \- **chain**: Your required viem chain.\
  \- **rpcUrl** - optional
* **signer** - optional: A signer from an [external auth provider ](/advanced/other-signers-auth-providers)(metamask...) that will control the account

## Example

<pre class="language-typescript"><code class="lang-typescript"><strong>"use client";
</strong>
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Inter } from "next/font/google";
import "./lib/ui/globals.css";

import { ConnectProvider } from "@cometh/connect-react-hooks";
import { arbitrumSepolia } from "viem/chains";

const queryClient = new QueryClient();

const inter = Inter({
    subsets: ["latin"],
});

const apiKey = process.env.NEXT_PUBLIC_COMETH_API_KEY;
const bundlerUrl = process.env.NEXT_PUBLIC_4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;
const rpcUrl = "https://arbitrum-sepolia.blockpi.network/v1/rpc/public";

const networksConfig = [
    {
        chain: arbitrumSepolia,
        bundlerUrl,
        paymasterUrl,
        rpcUrl
    },
];


export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        &#x3C;html lang="en">
            &#x3C;QueryClientProvider client={queryClient}>
                &#x3C;ConnectProvider
                    config={{
                        apiKey,
                        networksConfig
                    }}
                    queryClient={queryClient}
                >
                    &#x3C;body className={inter.className}>{children}&#x3C;/body>
                &#x3C;/ConnectProvider>
            &#x3C;/QueryClientProvider>
        &#x3C;/html>
    );
}
</code></pre>


# useAccount

## Description

This hook provides an easy way to access the current status and information of a smart account in a React application, providing essential details such as the smart account address, client instance, and connection status.&#x20;

## Returns

* `address` (Address | undefined)
* `smartAccountClient` (ContextComethSmartAccountClient | null)
* `isConnected` (boolean)
* `isDisconnected` (boolean)
* `status` (AccountStatus)

## Example

```tsx
import React from 'react';
import { useAccount } from "@cometh/connect-react-hooks";

const AccountInfo = () => {
  const { address, isConnected, status } = useAccount();

  return (
    <div>
      <h1>Account Information</h1>
      <p>Status: {status}</p>
      {isConnected ? (
        <p>Connected to account: {address}</p>
      ) : (
        <p>No account connected</p>
      )}
    </div>
  );
};

export default AccountInfo;

```


# useConnect

## Description

This hook allows you to initiate a connection to a smart account with optional parameters and can handle the process both synchronously and asynchronously.

## Parameters

The `useConnect` hook itself does not take parameters. However, the primary functions returned by the hook, `connect` and `connectAsync`, accept the following optional parameters:

```typescript
type ConnectParameters = {
    address?: Address;
    passKeyName?: string;
};
```

## Returns

* **connect** (`(params?: ConnectParameters) => void`): Initiates the connection process. It accepts an optional `params` object to specify connection details. It handles state changes for pending status and errors internally.
* **connectAsync** (`(params?: ConnectParameters) => Promise<void>`): An asynchronous function similar to `connect`, but returns a promise, allowing for usage with `async/await` syntax.
* **isPending** (`boolean`): A boolean value indicating whether the connection process is currently pending (`true`) or not (`false`).
* **error** (`Error | null`): An error object if an error occurred during the connection process, otherwise `null`.

## Example

```tsx
import React, { useState } from 'react';
import { useConnect } from "@cometh/connect-react-hooks";

const ConnectAccount = () => {
  const { connect, isPending, error } = useConnect();
  const [address, setAddress] = useState('');

  const handleConnect = () => {
    connect({ address });
  };

  return (
    <div>
      <h1>Connect to Smart Account</h1>
      <input
        type="text"
        value={address}
        onChange={(e) => setAddress(e.target.value)}
        placeholder="Enter smart account address"
      />
      <button onClick={handleConnect} disabled={isPending}>
        {isPending ? 'Connecting...' : 'Connect'}
      </button>
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
    </div>
  );
};

export default ConnectAccount;

```


# useDisconnect

## Description

This hook provides functionality for disconnecting from a smart account. It handles the disconnection process and manages related states, such as whether the process is pending or if an error occurred. The hook can execute both synchronously and asynchronously.

## Returns

The `useDisconnect` hook returns an object containing the following properties and functions:

* **disconnect** (`() => void`): A synchronous function to disconnect from the smart account. It manages internal states such as pending and error states.
* **disconnectAsync** (`() => Promise<void>`): An asynchronous function to disconnect from the smart account, returning a promise. This function allows the use of `async/await` syntax for handling the disconnection process.
* **isPending** (`boolean`): A boolean indicating whether the disconnection process is currently pending.
* **error** (`Error | null`): An error object if an error occurred during the disconnection process, otherwise `null`.

## Example

```tsx
import React from 'react';
import { useDisconnect } from "@cometh/connect-react-hooks";

const DisconnectButton = () => {
  const { disconnect, isPending, error } = useDisconnect();

  return (
    <div>
      <button onClick={disconnect} disabled={isPending}>
        {isPending ? 'Disconnecting...' : 'Disconnect'}
      </button>
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
    </div>
  );
};

export default DisconnectButton;

```


# useGetGasPrice

## Description

This hook fetches the current gas price for transactions on the connected blockchain. It leverages a public client to estimate the gas fees and provides both `maxFeePerGas` and `maxPriorityFeePerGas` values.&#x20;

The `maxFeePerGas` is calculated with a buffer, doubling the estimated fee to accommodate potential fluctuations in gas prices.&#x20;

This hook is useful for determining the cost of transactions in real-time.

## Parameters

* **rpcUrl?** (`string`) : An optional RPC URL to use for the public client. If not provided, the default RPC URL associated with the smart account will be used.

## Returns

* **data** (`GasPriceResult | undefined`): The fetched gas price data, containing `maxFeePerGas` and `maxPriorityFeePerGas`.
* **isLoading** (`boolean`): A boolean indicating whether the data is currently being fetched.
* **error** (`Error | null`): An error object if an error occurred during the fetching process, otherwise `null`.

## Example

```tsx
import { useGetGasPrice } from "@cometh/connect-react-hooks";
import { formatEther } from "viem";
 
export const GasPriceDisplay = () => {
 const { data: gasPrice, isLoading, error } = useGetGasPrice();
 
 if (isLoading) return <p>Loading gas prices...</p>;
 if (error) return <p>Error fetching gas prices: {error.message}</p>;
 
 return (
   <div>
     <h2>Current Gas Prices</h2>
     <p>Max Fee Per Gas: {formatEther(gasPrice.maxFeePerGas)} ETH</p>
     <p>Max Priority Fee Per Gas: {formatEther(gasPrice.maxPriorityFeePerGas)} ETH</p>
   </div>
  );
};
```

## Usage context

The `useGetGasPrice` hook must be used within a context that provides access to `useSmartAccount`. This ensures that the `smartAccountClient` and `queryClient` are available, and it avoids errors related to missing context or data.


# useSendTransaction

## Description

This hook provides functionality to send either a single transaction or multiple transactions in batch. It uses the smart account client to process and send these transactions.

It supports both synchronous and asynchronous methods for sending transactions, making it versatile for various use cases.

## Returns

* **data** (`Hash | undefined`): The hash of the transaction if it was successfully sent, otherwise `undefined`.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **sendTransaction** (`SendTransactionMutate`): A function that sends the transaction(s) without waiting for a result.&#x20;
* **sendTransactionAsync** (`SendTransactionMutateAsync`): A function that sends the transaction(s) and returns a promise that resolves to the transaction hash.

## Example

```tsx
import { useSendTransaction } from "@cometh/connect-react-hooks";
import { useState } from "react";
import { parseEther, Address } from "viem";
 
export const TransactionSender = () => {
  const { sendTransaction, sendTransactionAsync, isLoading, isError, error, isSuccess, data } = useSendTransaction();
  const [recipient, setRecipient] = useState<Address>();
  const [amount, setAmount] = useState<string>("0");
 
  const handleSendTransaction = () => {
    if (recipient) {
      sendTransaction({
       calls: {
          to: recipient,
          value: parseEther(amount),
          data: "0x",
        }
      });
    }
  };
 
  const handleSendBatchTransactions = async () => {
    if (recipient) {
      try {
       const hash = await sendTransactionAsync({
         calls: [
           {
              to: recipient,
              value: parseEther(amount),
              data: "0x",
           },
           {
             to: recipient,
             value: parseEther((Number(amount) * 2).toString()),
             data: "0x",
           }
          ]
        });
        console.log("Batch transactions sent! Hash:", hash);
      } catch (error) {
        console.error("Error sending batch transactions:", error);
      }
    }
  };
 
  return (
    <div>
      <input
        placeholder="Recipient address"
        onChange={(e) => setRecipient(e.target.value as Address)}
      />
      <input
        type="number"
        placeholder="Amount in ETH"
        onChange={(e) => setAmount(e.target.value)}
      />
      <button onClick={handleSendTransaction} disabled={isLoading}>
        Send Transaction
      </button>
      <button onClick={handleSendBatchTransactions} disabled={isLoading}>
        Send Batch Transactions
      </button>
      {isError && <p>Error: {error.message}</p>}
      {isSuccess && <p>Transaction sent! Hash: {data}</p>}
    </div>
  );
};
```


# useSignMessage

## Description

This hook provides functionality to sign an arbitrary message.

## Returns

* **data** (`Hex`): The signature generated.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **signMessage** : A function that signs a message and returns the signature.&#x20;
* **signMessageAsync** : A function that  signs a message and returns a promise that resolves to the signature.

## Example

```typescript
import { useSignMessage } from "@cometh/connect-react-hooks";

function MessageSigner() {
  const { signMessageAsync, isPending, isError, error, data } = useSignMessage();
 
  const handleSign = async () => {
    try {
      const signature = await signMessageAsync({ message: "Hello, World!" });
      console.log("Message signed:", signature);
    } catch (err) {
      console.error("Error signing message:", err);
    }
  };

  return (
    <div>
      <button onClick={handleSign} disabled={isPending}>
        Sign Message
      </button>
      {isPending && <p>Signing message...</p>}
      {isError && <p>Error: {error?.message}</p>}
      {data && <p>Signature: {data}</p>}
    </div>
  );
}
```


# useVerifyMessage

## Description

This hook provides functionality to verify a signature for a given message.

## Returns

* **data** (`boolean`): The signature generated.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **verifyMessage** : A function that verify if the signature corresponds to the given message.&#x20;
* **verifyMessageAsync** : A function that verify if the signature and returns a promise that resolves to the verification result.

## Example

```typescript
import { useVerifyMessage } from "@cometh/connect-react-hooks";
import { useState } from "react";
import type { Hex } from "viem";

function MessageVerifier() {
  const [message, setMessage] = useState("");
  const [signature, setSignature] = useState<Hex>("");
  const { verifyMessageAsync, isPending, isError, error, data } = useVerifyMessage();

  const handleVerify = async () => {
    try {
      const isValid = await verifyMessageAsync({ message, signature });
      console.log("Signature is valid:", isValid);
    } catch (err) {
      console.error("Error verifying message:", err);
    }
  };

  return (
    <div>
      <input
        placeholder="Enter message"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <input
        placeholder="Enter signature (hex)"
        value={signature}
        onChange={(e) => setSignature(e.target.value as Hex)}
      />
      <button onClick={handleVerify} disabled={isPending}>
        Verify Signature
      </button>
      {isPending && <p>Verifying signature...</p>}
      {isError && <p>Error: {error as string}</p>}
      {data !== undefined && <p>Signature is {data ? "valid" : "invalid"}</p>}
    </div>
  );
}

export default MessageVerifier;
```


# useWriteContract

## Description

This hook provides functionality to send either a single transaction or multiple transactions in batch. It uses the smart account client to process and send these transactions.

It supports both synchronous and asynchronous methods for sending transactions, making it versatile for various use cases.

## Returns

* **data** (`Hash | undefined`): The hash of the transaction if it was successfully sent, otherwise `undefined`.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **writeContract** (`SendTransactionMutate`): A function that sends the transaction(s) without waiting for a result.&#x20;
* **writeContractAsync** (`SendTransactionMutateAsync`): A function that sends the transaction(s) and returns a promise that resolves to the transaction hash.

## Example

```typescript
import { useWriteContract } from "@cometh/connect-react-hooks";
import { useState } from "react";
import { parseEther, Address } from "viem";
import { abi } from './contractABI';
 
export const ContractWriter = () => {
    const { writeContract, isLoading, isError, error, isSuccess, data } = useWriteContract();
    const [recipient, setRecipient] = useState<Address>();
    const [amount, setAmount] = useState<string>("0");

    const handleWriteContract = async () => {
        if (recipient) {
            try {
                const hash = await writeContract({
                    abi,
                    address: '0xYourContractAddress',
                    functionName: 'transfer',
                    args: [recipient, parseEther(amount)],
                });
                console.log("Contract write successful! Hash:", hash);
            } catch (error) {
                console.error("Error writing to contract:", error);
            }
        }
    };

    return (
        <div>
            <input
                placeholder="Recipient address"
                onChange={(e) => setRecipient(e.target.value as Address)}
            />
            <input
                type="number"
                placeholder="Amount in ETH"
                onChange={(e) => setAmount(e.target.value)}
            />
            <button onClick={handleWriteContract} disabled={isLoading}>
                Write to Contract
            </button>
            {isError && <p>Error: {error.message}</p>}
            {isSuccess && <p>Contract write successful! Hash: {data}</p>}
        </div>
    );
};

```


# Handle owners


# useRemoveOwner

## Description

This hook provides functionality to remove an owner of your wallet.

## Returns

* **data** (`Hash | undefined`): The hash of the transaction if it was successfully sent, otherwise `undefined`.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **removeOwner**: A function to trigger the removing of an owner.
* **removeOwnerAsync**: A function to trigger the removing of an owner and return a promise that resolves to the transaction hash.&#x20;


# useValidateAddDevice

## Description

This hook uses the `validateAddDevice` method from the smart account client to add a new signer to the user's account, either synchronously or asynchronously.

It's typically used in the process of adding a new device or recovery method to a user's account.

## Returns

* **data** (`Hash | undefined`): The hash of the transaction if it was successfully sent, otherwise `undefined`.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **validateAddDevice** (`ValidateAddDeviceMutate`): A function to trigger the validation process without waiting for the result.&#x20;
* **validateAddDeviceAsync** (`ValidateAddDeviceMutateAsync`): A function to trigger the validation process and wait for the result. This returns a promise that resolves to the transaction hash.


# useCreateNewSigner

## Description

Tis hook provides functionality for creating a new passkey signer in the context of a smart account. It handles the creation process and manages loading and error states.

## Returns

## Example

```tsx
import { useCreateNewSigner } from "@cometh/connect-react-hooks";
 
const MyComponent = () => {
 const { createSigner, isLoading, error, data } = useCreateNewSigner('your-api-key', 'https://api.example.com');
 
 const handleCreateSigner = async () => {
   try {
     const newSigner = await createSigner({
       smartAccountAddress: '0x1234...', // Replace with actual address
       passKeyName: 'MyNewPasskey'
     });
     console.log('New signer created:', newSigner);
   } catch (err) {
     console.error('Error creating signer:', err);
   }
  };

  return (
   <div>
      <button onClick={handleCreateSigner} disabled={isLoading}>
        Create New Signer
      </button>
      {isLoading && <p>Creating signer...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && <p>Signer created successfully!</p>}
    </div>
  );
};
```


# useAddOwner

## Description

This hook provides functionality to add a new address as owner of your wallet.

## Returns

* **data** (`Hash | undefined`): The hash of the transaction if it was successfully sent, otherwise `undefined`.
* **error** (`Error | null`): An error object if the transaction failed, otherwise `null`.
* **isPending** (`boolean`): A boolean indicating whether the transaction is currently pending.
* **isSuccess** (`boolean`): A boolean indicating whether the transaction was successfully sent.
* **isError** (`boolean`): A boolean indicating whether an error occurred during the transaction process.
* **addOwner**: A function to trigger the adding of a new owner.
* **addOwnerAsync**: A function to trigger the adding of a new owner and return a promise that resolves to the transaction hash.&#x20;


# useGetOwners/EnrichedOwners

## Description of useGetOwners

It's typically used in the process of getting the list of address that are owners of the wallet.

## Returns

* **data** (`Address[ | undefined`): Returns the array of owners addresses.
* **isLoading** (`boolean`): A boolean indicating whether the data is currently being fetched.
* **error** (`Error | null`): An error object if an error occurred during the fetching process, otherwise `null`.

## Description of useGetEnrichedOwners

It gives your the list of owners of the wallet with additional information for specific signers like passkeys (device used for the creation, creation date...)

## Type

EnrichedOwners:&#x20;

* address
* deviceData (browser, os, platform)
* creationDate

## Returns

* **data** (`Address[ | undefined`): Returns the array of owners addresses.
* **isLoading** (`boolean`): A boolean indicating whether the data is currently being fetched.
* **error** (`Error | null`): An error object if an error occurred during the fetching process, otherwise `null`.

##


# Session Keys


# useGrantPermission

This hook provides functionality to asynchronously grant permissions and monitor the status of the transaction.

## Returns:

| Property             | Type                                                                                                       | Description                                                                                                                 |
| -------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| data                 | GrantPermissionMutateResponse or undefined                                                                 | The response object containing the transaction hash and session details if the mutation is successful, otherwise undefined. |
| error                | Error or null                                                                                              | An error object if the transaction failed, otherwise null.                                                                  |
| isPending            | boolean                                                                                                    | A boolean indicating whether the transaction is currently pending.                                                          |
| isSuccess            | boolean                                                                                                    | A boolean indicating whether the transaction was successfully sent.                                                         |
| isError              | boolean                                                                                                    | A boolean indicating whether an error occurred during the transaction process.                                              |
| grantPermission      | (variables: GrantPermissionParameters\<ComethSafeSmartAccount>) => void                                    | A function to trigger the grant permission for a sessionKey.                                                                |
| grantPermissionAsync | (variables: GrantPermissionParameters\<ComethSafeSmartAccount>) => Promise\<GrantPermissionMutateResponse> | A function to trigger the grant permission for a sessionKey and return a promise resolving to the transaction details.      |

GrantPermissionMutateResponse Structure:

```typescript
type GrantPermissionMutateResponse = {
  txHash: Hash;
  createSessionsResponse: GrantPermissionResponse;
};
```

## Explanation:

• txHash: The hash of the transaction that was sent to the blockchain.

• createSessionsResponse: The response from the grantPermission operation, including details about the session and its status.

## Usage Example:

```typescript
import { useGrantPermission } from "path/to/hook";

const Component = () => {
  const {
    data,
    error,
    isPending,
    isSuccess,
    isError,
    grantPermission,
    grantPermissionAsync,
  } = useGrantPermission();

  const handleGrantPermission = () => {
    grantPermission(permission);
  };

  const handleAsyncGrantPermission = async () => {
    try {
      const result = await grantPermissionAsync(permission);
      console.log("Transaction hash:", result.txHash);
    } catch (err) {
      console.error("Permission grant failed:", err);
    }
  };

  return (
    <div>
      {isPending && <p>Granting permission...</p>}
      {isSuccess && <p>Permission granted successfully!</p>}
      {isError && <p>Error: {error?.message}</p>}
      <button onClick={handleGrantPermission}>Grant Permission</button>
      <button onClick={handleAsyncGrantPermission}>Grant Permission (Async)</button>
    </div>
  );
};
```

## How It Works:

The hook relies on the smartAccountClient to extend actions from the cometh/connect-sdk-4337 package. It executes the following steps:

1\. Extends the smart account with necessary actions (erc7579Actions, smartSessionActions).

2\. Calls the grantPermission method with the provided parameters.

3\. Waits for confirmation using waitForUserOperationReceipt.

4\. Returns the transaction hash and the session response.

## Example of Expected Response:

```json
{
  "txHash": "0x123abc456def...",
  "createSessionsResponse": {
    "userOpHash": "0x789xyz...",
    "session": ...,
    "permissionIds": ...;
    "action": ...;
  }
}
```

## Error Handling:

If the mutation fails, the error object provides details about the failure, which can be used to display custom error messages to users.

## Mutation Props (mutationProps):

The hook accepts optional custom mutation parameters through the MutationOptionsWithoutMutationFn object, allowing you to adjust its behavior if needed.


# useSendPermission

This hook handles the process of sending permissions and monitors the transaction’s status.

## Returns:

| Property            | Type                                                   | Description                                                                                          |
| ------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| data                | Hash or undefined                                      | The hash of the transaction if it was successfully sent, otherwise undefined.                        |
| error               | Error or null                                          | An error object if the transaction failed, otherwise null.                                           |
| isPending           | boolean                                                | A boolean indicating whether the transaction is currently pending.                                   |
| isSuccess           | boolean                                                | A boolean indicating whether the transaction was successfully sent.                                  |
| isError             | boolean                                                | A boolean indicating whether an error occurred during the transaction process.                       |
| sendPermission      | (variables: UsePermissionParameters) => void           | A function to trigger the permission request using a sessionKey.                                     |
| sendPermissionAsync | (variables: UsePermissionParameters) => Promise\<Hash> | A function to trigger the permission request and return a promise resolving to the transaction hash. |

## Usage Example:

<pre class="language-typescript"><code class="lang-typescript">import { useSendPermission } from "path/to/hook";

const Component = () => {
  const {
    data,
    error,
    isPending,
    isSuccess,
    isError,
    sendPermission,
    sendPermissionAsync,
  } = useSendPermission({
    sessionData: ..., 
    privateKey: ...,
  });

  const handleSendPermission = () => {
    sendPermission({
      actions: [
        {
          target: 0x012345...",
          callData: "0x012345...",
          value: BigInt(0),
        },
      ],
    });
  };

  const handleAsyncSendPermission = async () => {
    try {
      const txHash = await sendPermissionAsync({
        actions: [
          {
            target: 0x012345...",
            callData: "0x012345...",
            value: BigInt(0),
          },
        ],
      });
<strong>      console.log("Transaction hash:", txHash);
</strong>    } catch (err) {
      console.error("Permission sending failed:", err);
    }
  };

  return (
    &#x3C;div>
      {isPending &#x26;&#x26; &#x3C;p>Sending permission...&#x3C;/p>}
      {isSuccess &#x26;&#x26; &#x3C;p>Permission sent successfully!&#x3C;/p>}
      {isError &#x26;&#x26; &#x3C;p>Error: {error?.message}&#x3C;/p>}
      &#x3C;button onClick={handleSendPermission}>Send Permission&#x3C;/button>
      &#x3C;button onClick={handleAsyncSendPermission}>Send Permission (Async)&#x3C;/button>
    &#x3C;/div>
  );
};
</code></pre>

## How It Works:

The hook relies on the smartAccountClient and sessionKeySigner to handle permission operations via the cometh/connect-sdk-4337. The mutation follows these steps:

1\. Initializes the session key signer using useSessionKeySigner.

2\. Verifies the required context and API key are available.

3\. Creates a sessionKeyClient using createSessionSmartAccountClient.

4\. Calls the usePermission method with the provided parameters.

5\. Waits for confirmation using waitForUserOperationReceipt.

6\. Returns the transaction hash of the operation.

## Example of Expected Response:

```
"0x123abc456def..."
```

## Error Handling:

If the mutation fails, the error object provides information about what went wrong, allowing you to display user-friendly error messages.

## Mutation Props (mutationProps):

You can pass optional mutation options through the mutationProps parameter. This allows you to customize aspects of the mutation, like retry mechanisms, onSuccess callbacks, and more.


# useSessionKeyClient

This hook initializes and returns a session key client using the provided API key, session data, and private key.

## Returns:

| Property  | Type                            | Description                                                                   |
| --------- | ------------------------------- | ----------------------------------------------------------------------------- |
| data      | SmartSessionClient or undefined | The session key client if it was successfully created, otherwise undefined.   |
| error     | Error or null                   | An error object if the client creation failed, otherwise null.                |
| isPending | boolean                         | A boolean indicating whether the client is currently being created.           |
| isSuccess | boolean                         | A boolean indicating whether the session key client was successfully created. |
| isError   | boolean                         | A boolean indicating whether an error occurred during the client creation.    |

## Usage Example:

```typescript
import { useSessionKeyClient } from "path/to/hook";

const Component = () => {
  const { data: sessionKeyClient, error, isPending, isSuccess, isError } =
    useSessionKeyClient({
      apiKey: "YOUR_API_KEY",
      sessionData: ..., 
      privateKey: ...,
    });

  if (isPending) return <p>Initializing session client...</p>;
  if (isError) return <p>Error: {error?.message}</p>;

  return (
    <div>
      {isSuccess && <p>Session Key Client Initialized!</p>}
      {/* Example usage */}
      <button
        onClick={() => {
          if (sessionKeyClient) {
            // Call methods on the session key client
            console.log("Session Key Client:", sessionKeyClient);
          }
        }}
      >
        Use Session Key Client
      </button>
    </div>
  );
};
```

## How It Works:

This hook leverages wagmi’s useQuery to initialize the SmartSessionClient asynchronously. The process involves:

1\. Using the useSessionKeySigner hook to retrieve the session key signer based on the provided session data and private key.

2\. Verifying that the smartAccountClient and sessionKeySigner are available.

3\. Calling createSessionSmartAccountClient to initialize the session key client.

4\. Returning the query result containing the client and its state.

## Error Handling:

If an error occurs during the client initialization, the error object will provide details about what went wrong. You can use this to display user-friendly error messages.

## Customization:

You can customize the behavior of the query by extending it through wagmi options (e.g., setting stale time, retries, or refetching conditions if needed).


# useSessionKeySigner

This hook returns a session key signer based on the provided session data and private key. The signer is used to interact with smart sessions and execute permissioned actions.

## Returns:

| Property  | Type                                               | Description                                                                   |
| --------- | -------------------------------------------------- | ----------------------------------------------------------------------------- |
| data      | SafeSigner<"safeSmartSessionsSigner"> or undefined | The session key signer if it was successfully created, otherwise undefined.   |
| error     | Error or null                                      | An error object if the signer creation failed, otherwise null.                |
| isPending | boolean                                            | A boolean indicating whether the signer is currently being created.           |
| isSuccess | boolean                                            | A boolean indicating whether the session key signer was successfully created. |
| isError   | boolean                                            | A boolean indicating whether an error occurred during the signer creation.    |

## Usage Example:

```typescript
import { useSessionKeySigner } from "path/to/hook";

const Component = () => {
  const { data: sessionKeySigner, error, isPending, isSuccess, isError } =
    useSessionKeySigner({
      sessionData: { permissionIds: [...], action: "...", sessions: [...] },
      privateKey: "0xYOUR_PRIVATE_KEY",
    });

  if (isPending) return <p>Initializing session key signer...</p>;
  if (isError) return <p>Error: {error?.message}</p>;

  return (
    <div>
      {isSuccess && <p>Session Key Signer Initialized!</p>}
      <button
        onClick={() => {
          if (sessionKeySigner) {
            // Example usage: signing an operation
            console.log("Session Key Signer:", sessionKeySigner);
          }
        }}
      >
        Use Session Key Signer
      </button>
    </div>
  );
};
```

## How It Works:

The hook follows these steps to create the session key signer:

1\. Uses the smartAccountClient obtained from useSmartAccount.

2\. Extends the client using smartSessionActions and erc7579Actions.

3\. Converts the smart account into a session signer using the toSmartSessionsSigner utility.

4\. Returns the signer, which can then be used to sign permissioned operations.

Example of Expected Response:

```typescript
{
  "type": "safeSmartSessionsSigner",
  "signer": "Signer instance with session capabilities"
}
```

## Error Handling:

If an error occurs during the creation of the session key signer, the error object will provide information about what went wrong. This can be used to display helpful error messages to the user.

## Customization:

The query is controlled using wagmi’s useQuery. You can pass additional options (e.g., enabling/disabling the query dynamically) as needed.


# Recovery


# useIsRecoveryActive

## Description

This hook allows you to check if the recovery module is activated.

## Parameters

```typescript
type IsRecoveryActiveParams = {
    rpcUrl?: string;
};
```

## Example

```typescript
import { useIsRecoveryActive } from "@/hooks/useIsRecoveryActive";

export const RecoveryStatus = () => {
  const { data, isLoading, isError, error } = useIsRecoveryActive({
    rpcUrl: 'https://my-rpc-url.com',
  });

  if (isLoading) return <p>Loading recovery status...</p>;
  if (isError) return <p>Error: {error?.message}</p>;

  return (
    <div>
      <p>Recovery Module Deployed: {data?.isDelayModuleDeployed ? 'Yes' : 'No'}</p>
      <p>Guardian Address: {data?.guardianAddress || 'Not set'}</p>
    </div>
  );
};
```


# useSetUpRecovery

## Description

This hook allows you to set up the recovery module.

## Parameters

```typescript
type SetUpRecoveryModuleParams = {
    passKeyName?: string;
    webAuthnOptions?: webAuthnOptions;
    rpcUrl?: string;
};
```

## Example

```typescript
import { useSetUpRecoveryModule } from "@/hooks/useSetUpRecoveryModule";

export const RecoverySetup = () => {
  const {
    setUpRecoveryModule,
    setUpRecoveryModuleAsync,
    isLoading,
    isError,
    error,
    isSuccess,
    data
  } = useSetUpRecoveryModule();

  const handleSetUp = async () => {
    try {
      const result = await setUpRecoveryModuleAsync({
        passKeyName: 'myPassKey',
        rpcUrl: 'https://my-rpc-url.com',
        // other necessary parameters
      });
      console.log('Recovery module set up successfully:', result);
    } catch (error) {
      console.error('Error setting up recovery module:', error);
    }
  };

  return (
    <div>
      <button onClick={handleSetUp} disabled={isLoading}>
        Set Up Recovery Module
      </button>
      {isLoading && <p>Setting up recovery module...</p>}
      {isError && <p>Error: {error?.message}</p>}
      {isSuccess && <p>Recovery module set up successfully. Hash: {data}</p>}
    </div>
  );
};
```


# useGetRecoveryRequest

## Description

This hook allows you to query a recovery request.

## Parameters

```typescript
type GetRecoveryRequestParams = {
    rpcUrl?: string;
};
```

## Example

```typescript
import { useGetRecoveryRequest } from "@/hooks/useGetRecoveryRequest";

export const RecoveryRequestStatus = () => {
  const { data, isLoading, isError, error } = useGetRecoveryRequest({
    rpcUrl: 'https://my-rpc-url.com',
  });

  if (isLoading) return <p>Loading recovery request status...</p>;
  if (isError) return <p>Error: {error?.message}</p>;

  return (
    <div>
      {data ? (
        <>
          <p>Recovery Request Active</p>
          <p>New Owner: {data.newOwner}</p>
          <p>Execution Time: {new Date(data.executionTime * 1000).toLocaleString()}</p>
        </>
      ) : (
        <p>No active recovery request</p>
      )}
    </div>
  );
};
```


# useCancelRecoveryRequest

## Description

This hook allows you to cancel a recovery request.

## Parameters

```typescript
type CancelRecoveryRequestParams = {
    rpcUrl?: string;
};
```

## Example

```typescript
import { useCancelRecoveryRequest } from "@/hooks/useCancelRecoveryRequest";

export const CancelRecoveryButton = () => {
  const {
    cancelRecoveryRequest,
    cancelRecoveryRequestAsync,
    isLoading,
    isError,
    error,
    isSuccess,
    data
  } = useCancelRecoveryRequest();

  const handleCancel = async () => {
    try {
      const result = await cancelRecoveryRequestAsync({
        rpcUrl: 'https://my-rpc-url.com',
      });
      console.log('Recovery request canceled successfully:', result);
    } catch (error) {
      console.error('Error canceling recovery request:', error);
    }
  };

  return (
    <div>
      <button onClick={handleCancel} disabled={isLoading}>
        Cancel Recovery Request
      </button>
      {isLoading && <p>Canceling recovery request...</p>}
      {isError && <p>Error: {error?.message}</p>}
      {isSuccess && <p>Recovery request canceled successfully. Hash: {data}</p>}
    </div>
  );
};
```


# Mobile SDKs

With the support of Worldcoin, we worked on native versions of a 4337/passkeys/safe sdk to bring the best UX on mobile !


# IOS

We developed the first version of an IOS SDK using passkeys and safe smart wallet as a public good. See below to start integration:

{% embed url="<https://github.com/cometh-hq/swift4337>" %}


# Android

We developed the first version of an Android SDK using passkeys and safe smart wallet as a public good. See below to start integration:

{% embed url="<https://github.com/cometh-hq/android4337>" %}


# React Native

We developed the first version of an react native SDK using passkeys and safe smart wallet as a public good. See below to start integration:

{% embed url="<https://github.com/cometh-hq/rtn-4337>" %}


# Wagmi

### Install

<pre><code><strong>npm i @cometh/connect-sdk-4337 wagmi viem
</strong></code></pre>

### Configure Wagmi Provider (Next v14 example) <a href="#installing" id="installing"></a>

See [the boilerplate example](https://github.com/cometh-hq/connect-sdk-4337/tree/develop/packages/example-wagmi)

```typescript
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Inter } from "next/font/google";
import "./lib/ui/globals.css";

import { smartAccountConnector } from "@cometh/connect-sdk-4337";
import type { Hex } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { http, WagmiProvider, createConfig } from "wagmi";

const inter = Inter({
    subsets: ["latin"],
});

const queryClient = new QueryClient();

const apiKey = process.env.NEXT_PUBLIC_COMETH_API_KEY;
const bundlerUrl = process.env.NEXT_PUBLIC_4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;
const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});

const connector = smartAccountConnector({
    apiKey,
    bundlerUrl,
    publicClient,
    paymasterUrl
});

const config = createConfig({
    chains: [arbitrumSepolia],
    connectors: [connector],
    transports: {
        [arbitrumSepolia.id]: http(),
    },
    ssr: true,
});

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html lang="en">
            <WagmiProvider config={config}>
                <QueryClientProvider client={queryClient}>
                    <body className={inter.className}>{children}</body>
                </QueryClientProvider>
            </WagmiProvider>
        </html>
    );
}

```


# SDK Core

This Core version of the Cometh Connect SDK 4337 provides the essential functions for managing smart accounts. It supports various authentication providers such as EOA, Magic, Web3Auth, Turnkey, and Privy, allowing developers to create and interact with smart accounts efficiently.

The Core SDK **does not require an API key**. However, some advanced features, such as the **Cometh Passkey signer**, are not available in this version.

### Install

<pre><code><strong>npm i @cometh/connect-core-sdk viem
</strong></code></pre>

### Setup

```typescript
import { 
    createComethPaymasterClient, 
    createSafeSmartAccount, 
    createSmartAccountClient,
    providerToSmartAccountSigner
} from "@cometh/connect-core-sdk";
import { arbitrumSepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { http } from "viem";

const bundlerUrl = process.env.NEXT_PUBLIC_4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const signer = await providerToSmartAccountSigner(
    window.ethereum
);

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});
​
const smartAccount = await createSafeSmartAccount({
    chain: arbitrumSepolia,
    publicClient,
    signer,
})

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

 const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})
```

### SDK Core Features

#### Send a transaction

```typescript
import { smartAccountClient } from "./client";
import countContractAbi from "../contract/counterABI.json";

const calldata = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count",
});
  
const txHash =  await smartAccountClient.sendTransaction({
    to: COUNTER_CONTRACT_ADDRESS,
    data: calldata,
});
```

#### Send batch transactions

<pre class="language-typescript"><code class="lang-typescript">const txHash =  await smartAccountClient.sendTransaction({
<strong>calls: [
</strong>      {
        to: COUNTER_CONTRACT_ADDRESS,
        data: calldata,
      },
      {
        to: COUNTER_CONTRACT_ADDRESS,
        data: calldata,
      },
    ],
});
</code></pre>

#### Sign a message

```typescript
const signature = await smartAccountClient.signMessage({message: "Hello world"});
```

#### Encode contract calls&#x20;

```typescript
const encodeCalls = await smartAccount.account.encodeCalls([
    {
        to: COUNTER_CONTRACT_ADDRESS,
        data: encodeFunctionData({
            abi: countContractAbi,
            functionName: "count",
        }),
    },
]);
```

### Specify contract addresses

By default, the SDK Core uses the following contract addresses:

```typescript
const defaultSafeContractConfig = {
    safeProxyFactoryAddress: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
    safeSingletonAddress: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762",
    multisendAddress: "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526",
    setUpContractAddress: "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47",
    safe4337ModuleAddress: "0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226",
};
```

If you want to specify custom contract addresses, you can pass the `safeContractConfig` parameter when creating a smart account:

```typescript
const smartAccount = await createSafeSmartAccount({
    chain: arbitrumSepolia,
    publicClient,
    signer,
    safeContractConfig
})
```

#### Type of `safeContractConfig`:

```typescript
type SafeContractParams = {
  safeProxyFactoryAddress: Address;
  safeSingletonAddress: Address;
  multisendAddress: Address;
  setUpContractAddress: Address;
  safe4337ModuleAddress?: Address;
};
```


# Signers (Auth Providers)


# EOA wallets (Metamask, Phantom...)

## Metamask with EIP1193

EIP-1193 is a standard interface for Ethereum providers, such as MetaMask or hardware wallets, where the key material is hosted externally rather than on the local client.&#x20;

You will have access to a signer object that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import {
    providerToSmartAccountSigner
} from "@cometh/connect-core-sdk";

const signer = await providerToSmartAccountSigner(
    window.ethereum
);
```

## Metamask with Viem integration

A [Wallet Client](https://viem.sh/docs/clients/wallet.html) is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc through Wallet Actions.

```typescript
const signer = walletClientToSmartAccountSigner(walletClient);
```


# Magic signer

[Magic](https://magic.link/) is a popular embedded wallet provider that supports social logins, making it easier for users to onboard without managing private keys.

## Setup

To use **Magic** with **SDK Core** , first create an application that integrates with **Magic**.

* Refer to the [Magic documentation site](https://magic.link/docs/home/welcome) for instructions on setting up an application with the Magic SDK.
* For a quick start, Magic provides a CLI to create a starter project, available [here](https://magic.link/docs/home/quickstart/cli#run-make-magic).

## Integration

After following the Magic documentation, you will have access to a `MagicBase` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import { OAuthExtension } from "@magic-ext/oauth"
import { Magic as MagicBase } from "magic-sdk"
import { providerToSmartAccountSigner } from "@cometh/connect-core-sdk";


const rpcUrl = process.env.RPC_URL;
const magicApiKey = process.env.MAGIC_API_KEY;

const magic = new MagicBase(magicApiKey as string, {
	network: {
		rpcUrl,
		chainId: arbitrumSepolia.id,
	},
	extensions: [new OAuthExtension()],
})
 
// Get the Provider from Magic and convert it to a signer
const magicProvider = await magic.wallet.getProvider()
const signer = await providerToSmartAccountSigner(magicProvider);
  
```


# Web3Auth signer

[Web3Auth](https://web3auth.io/) is a popular embedded wallet provider that supports social logins, making it easier for users to onboard without managing private keys.&#x20;

## **Setup**

To use **Web3Auth** with **SDK Core**, first create an application that integrates with Web3Auth.

* Refer to the [Web3Auth documentation site](https://web3auth.io/docs/index.html) for instructions on setting up an application with the Web3Auth.
* For a quick start, Web3Auth provides example starter projects, available [here](https://web3auth.io/docs/examples?product=Plug+and+Play\&sdk=Plug+and+Play+Web+Modal+SDK).

## Integration

After following the Web3Auth documentation, you will have access to a `web3auth` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

<pre class="language-typescript"><code class="lang-typescript">import { CHAIN_NAMESPACES, WEB3AUTH_NETWORK } from "@web3auth/base"
import { Web3Auth } from "@web3auth/modal"
import type { EIP1193Provider } from "viem"
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-provider"
import { providerToSmartAccountSigner } from "@cometh/connect-core-sdk"


const rpcUrl = process.env.RPC_URL;

const chainConfig = {
    chainNamespace: CHAIN_NAMESPACES.EIP155,
    chainId: "0x66eee", // Hex of 421614
    rpcTarget: rpcUrl,
    displayName: "Arbitrum Sepolia Testnet",
    blockExplorerUrl: "https://sepolia.arbiscan.io/",
    ticker: "AETH",
    tickerName: "AETH",
    logo: "https://cryptologos.cc/logos/arbitrum-arb-logo.png",
  };

const privateKeyProvider = new EthereumPrivateKeyProvider({
    config: { chainConfig },
  });


const web3auth = new Web3Auth({
  clientId,
  uiConfig: {},
  web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
  privateKeyProvider,
<strong>});
</strong>
// Initialize for PnP Modal SDK
await web3auth.initModal();
// Trigger the login
await web3auth.connect();
 
// Get the Provider and EOA address (this will be the address of the signer) from Web3Auth
const web3authProvider = web3auth.provider as EIP1193Provider

if (!web3authProvider) {
	throw new Error("No provider found")
}

const signer = await providerToSmartAccountSigner(web3authProvider);
  
</code></pre>


# Turnkey signer

[Turnkey](https://www.turnkey.com/) is a key infrastructure provider with a powerful developer API and a robust security policy engine, enabling secure and flexible key management for blockchain applications.

#### **Setup**

To use **Turnkey** with **SDK Core**, first create an application that integrates with Turnkey.

* Refer to the [Turnkey documentation site](https://docs.turnkey.com/) for instructions on setting up an application with the Turnkey.
* For a quick start, Turnkey provides examples, available [here](https://docs.turnkey.com/getting-started/examples).

## Integration

After following the Turnkey documentation, you will have access to a `TurnkeyClient` object as shown below that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import { TurnkeyClient } from "@turnkey/http"
import { createAccount } from "@turnkey/viem"


// Param options here will be specific to your project.  See the Turnkey docs for more info.
const turnkeyClient = new TurnkeyClient({ baseUrl: "" }, stamper)
 
const turnkeySigner = await createAccount({
	client: turnkeyClient,
	organizationId: subOrganizationId, // Your subOrganization id
	signWith: signWith, // Your suborganization `signWith` param.
})
  
```


# Privy signer

[Privy](https://www.privy.io/) is an embedded wallet provider that simplifies user onboarding for dApps, enabling seamless authentication and key management.

## Create the Privy provider

Follow Privy’s [quickstart guide](https://docs.privy.io/guide/quickstart), to set up the Privy provider in your app.

```tsx
import { PrivyProvider } from '@privy-io/react-auth';
import {WagmiProvider} from '@privy-io/wagmi'; 
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {createConfig} from '@privy-io/wagmi'; 

import { http } from "viem";
import { arbitrumSepolia } from "viem/chains";


const queryClient = new QueryClient(); 
 
const config = createConfig({ 
  chains: [arbitrumSepolia], 
  transports: { 
    [arbitrumSepolia.id]: http(), 
  }, 
}); 

<PrivyProvider
  appId={"<Privy-App-Id>"}
  config={{
    embeddedWallets: {
      createOnLogin: "all-users",
    },
  }}
>
   <QueryClientProvider client={queryClient}>
    <WagmiProvider config={config}>
        {children}
     </WagmiProvider>
  </QueryClientProvider>
</PrivyProvider>;
 
```

## Integration

In your app, set Privy's embedded wallet as the active wallet for wagmi by using the **useWallets** react hook (after[ Privy login](https://docs.privy.io/guide/react/authentication/login/)).

```typescript
import { useWallets } from "@privy-io/react-auth";


const { wallets } = useWallets();
const embeddedWallet = wallets.find(
  (wallet) => wallet.walletClientType === "privy"
);

```

You will have access to a Privy signer object as shown below that you can pass as an owner to `createSafeSmartAccount`:

```typescript
import {
    providerToSmartAccountSigner
} from "@cometh/connect-core-sdk";


if (!embeddedWallet) throw new Error("User does not have an embedded wallet");

const privyProvider = await embeddedWallet!.getEthereumProvider()
const signer = await providerToSmartAccountSigner(privyProvider);

```


# Handle owners

You can easily add, remove or get all your wallet owners.

You can easily **get/add/remove** all the owners of your smart wallet.

## Add owners

{% tabs %}
{% tab title="TS" %}

<pre class="language-typescript"><code class="lang-typescript"><strong>const txHash = await smartAccountClient.addOwner({ownerToAdd: ADDRESS_TO_ADD});
</strong></code></pre>

{% endtab %}
{% endtabs %}

## Remove owners

{% tabs %}
{% tab title="TS" %}

```typescript
const txHash = await smartAccountClient.removeOwner({ownerToRemove: ADDRESS_TO_REMOVE});
```

{% endtab %}
{% endtabs %}

## Get owners

{% tabs %}
{% tab title="TS" %}

```typescript
const owners = smartAccountClient.getOwners()
```

{% endtab %}
{% endtabs %}


# Capabilities

**SDK Core** supports capability requests defined in EIP-5792, enabling dApps to interact securely with smart accounts. It includes **sendCalls** for executing transactions, **getCallsStatus** to track their progress, and **getCapabilities** to check available wallet features.


# sendCalls

Requests the wallet to sign and broadcast a batch of calls (transactions) to the network in a single operation.

```typescript
import { encodeFunctionData } from "viem";
import countContractAbi from "@/contract/counterABI.json";

const COUNTER_CONTRACT_ADDRESS = "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";

const txHash =  await smartAccountClient.sendCalls(
      {
        calls: [
          {
            to: COUNTER_CONTRACT_ADDRESS,
            value: 0,
            data: calldata,
          },
          {
            to: COUNTER_CONTRACT_ADDRESS,
            value: 0,
            data: calldata,
          },
        ],
      }
    )

```

## Returns

`string`

The request returns the **UserOperation hash**, which can be used to track the transaction status via **getCallsStatus** within the session.


# getCallsStatus

Retrieves the status and receipts of a batch call previously sent via **sendCalls**, using its **UserOperation hash**.

```typescript
const { status, receipts } = await smartAccountClient.getCallsStatus({ 
  id: userOpHash,
})
```

## Returns

The request returns the call batch status (**PENDING** or **CONFIRMED**) and, if confirmed, the transaction receipts, including logs, block details, gas used, and the transaction hash.

```json
{
  "status": "PENDING"
}
```

or

```json
{
  "status": "CONFIRMED",
  "receipts": [
    {
      "logs": [
        {
          "address": "0x1234567890abcdef1234567890abcdef12345678",
          "data": "0x...",
          "topics": ["0x...", "0x..."]
        }
      ],
      "status": "0x1",
      "blockHash": "0xabcdef...",
      "blockNumber": "0x10d4f",
      "gasUsed": "0x5208",
      "transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  ]
}
```


# getCapabilities

Extracts the capabilities supported by the connected wallet, grouped by chain ID. This includes features like **atomic batching**, **paymaster services**, and **permissions**.

```typescript
const capabilities = await smartAccountClient.getCapabilities()
```

## Returns

A JSON object detailing the wallet’s supported capabilities per chain.

```json
{
  "0x8453": {
    "atomicBatch": {
      "supported": true
    },
    "paymasterService": {
      "supported": true
    },
    "permissions": {
      "supported": true,
      "signerTypes": ["account"],
      "permissionTypes": ["sudo", "contract-call"]
    }
  }
}
```


# SDK Session Keys

The Session Key SDK provides functionality to manage ERC-7579-compliant Session Keys.

It is designed to operate independently of Connect Core and is fully compatible with other account abstraction SDKs that support Safe Accounts.

The SDK enables the registration, revocation, and configuration of granular permissions of Session Keys.

{% hint style="info" %}
To activate session keys, we will **switch the fallback handler of your safe to the ERC7579 implementation.**
{% endhint %}

We implement the 7579 smart sessions audited implementation:

{% embed url="<https://github.com/erc7579/smartsessions>" %}

### Install

```
npm i @cometh/session-keys viem
```


# Setup Smart Account Client

How to setup your smart account client :

{% tabs %}
{% tab title="SDK Core" %}

```typescript
import { 
    createComethPaymasterClient, 
    createSafeSmartAccount, 
    createSmartAccountClient,
    providerToSmartAccountSigner
} from "@cometh/connect-core-sdk";
import { arbitrumSepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { http } from "viem";

const bundlerUrl = process.env.NEXT_PUBLIC_4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const signer = await providerToSmartAccountSigner(
    window.ethereum
);

const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(),
    cacheTime: 60_000,
    batch: {
        multicall: { wait: 50 },
    },
});
​
const smartAccount = await createSafeSmartAccount({
    chain: arbitrumSepolia,
    publicClient,
    signer,
})

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain: arbitrumSepolia,
    publicClient,
});

 const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    }
})
```

{% endtab %}

{% tab title="Permissionless" %}

```typescript
import { RHINESTONE_ATTESTER_ADDRESS, getSmartSessionsValidator } from "@rhinestone/module-sdk";
import { toSafeSmartAccount } from "permissionless/accounts";
import { createSmartAccountClient} from "permissionless";
import { createPaymasterClient, entryPoint07Address } from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { http, type Hex, type PublicClient, createPublicClient } from "viem";
import { arbitrumSepolia } from "viem/chains";


const bundlerUrl = process.env.NEXT_PUBLIC_4337_BUNDLER_URL;
const paymasterUrl = process.env.NEXT_PUBLIC_4337_PAYMASTER_URL;

const smartSessions = getSmartSessionsValidator({});
const smartAccount = await toSafeSmartAccount({
    client: publicClient,
    owners: [signer],
    version: "1.4.1",
    entryPoint: {
        address: entryPoint07Address,
        version: "0.7",
    },
    safe4337ModuleAddress: "0x7579EE8307284F293B1927136486880611F20002",
    erc7579LaunchpadAddress: "0x7579011aB74c46090561ea277Ba79D510c6C00ff",
    attesters: [
        RHINESTONE_ATTESTER_ADDRESS,
    ],
    attestersThreshold: 1,
    validators: [
        {
            address: smartSessions.address,
            context: smartSessions.initData,
        },
    ],
});

const paymasterClient = await createPaymasterClient({
    transport: http(paymasterUrl)
});

const pimlicoClient = createPimlicoClient({
    transport: http(paymasterUrl),
    entryPoint: {
        address: entryPoint07Address,
        version: "0.7",
    },
})

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain: arbitrumSepolia,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return (await pimlicoClient.getUserOperationGasPrice()).fast
        },
    },
});

```

{% endtab %}
{% endtabs %}


# Manage session keys

Example of a session key with an action policy.

### 1 - Create a Session Key

{% hint style="info" %}
When using a **Permissionless account** configured like in section **Setup Smart Account Client**, you have to skip the **Safe 7579** module installation step.&#x20;

This is unnecessary because the **Permissionless flow automatically handles module installation** during Smart Account creation via the ERC-7579 Launchpad. Including this step may lead to redundant or conflicting behavior.
{% endhint %}

{% tabs %}
{% tab title="SDK Core" %}

```typescript
import type { Address, PublicClient } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import {
    erc7579Actions,
    smartSessionActions,
    type SafeSigner,
} from "@cometh/session-keys";
import { isSmartAccountDeployed } from "permissionless";

export const COUNTER_CONTRACT_ADDRESS =
    "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";


const safe7559Account = smartAccountClient.extend(smartSessionActions())
            .extend(erc7579Actions());

const privateKey = generatePrivateKey();
const sessionOwner = privateKeyToAccount(privateKey);

 if (!(await isSmartAccountDeployed(
     safe7559Account?.account?.client as PublicClient, 
     safe7559Account?.account?.address as Address,
 ))) {
     safe7559Account.addSafe7579Module()
 }
        
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
            actionPoliciesInfo: [
                {
                    contractAddress: COUNTER_CONTRACT_ADDRESS,
                    functionSelector: toFunctionSelector(
                        "function count()"
                    ) as Hex,
                },
            ],
        },
    ],
});

await safe7559Account.waitForUserOperationReceipt({
    hash: createSessionsResponse.userOpHash,
});

```

{% endtab %}

{% tab title="Permissionless" %}

```typescript
import type { Address, PublicClient } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import {
    erc7579Actions,
    smartSessionActions,
    type SafeSigner,
} from "@cometh/session-keys";
import { isSmartAccountDeployed } from "permissionless";

export const COUNTER_CONTRACT_ADDRESS =
    "0x4FbF9EE4B2AF774D4617eAb027ac2901a41a7b5F";


const safe7559Account = smartAccountClient.extend(smartSessionActions())
            .extend(erc7579Actions());

const privateKey = generatePrivateKey();
const sessionOwner = privateKeyToAccount(privateKey);
        
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
            actionPoliciesInfo: [
                {
                    contractAddress: COUNTER_CONTRACT_ADDRESS,
                    functionSelector: toFunctionSelector(
                        "function count()"
                    ) as Hex,
                },
            ],
        },
    ],
});

await safe7559Account.waitForUserOperationReceipt({
    hash: createSessionsResponse.userOpHash,
});

```

{% endtab %}
{% endtabs %}

### 2 - Store the Session Key

In our example, we will store the session key details in local storage. You are free to store it wherever you want.

<pre class="language-typescript"><code class="lang-typescript">import { SmartSessionMode } from "@cometh/session-keys";
<strong>
</strong><strong>const sessionData = {
</strong>    granter: safe7559Account?.account?.address as Address,
    privateKey: privateKey,
    sessionPublicKey: sessionOwner.address,
    description: `Session to increment a counter`,
    moduleData: {
        permissionIds: createSessionsResponse.permissionIds,
        action: createSessionsResponse.action,
        mode: SmartSessionMode.USE,
        sessions: createSessionsResponse.sessions,
    },
};

// This is for example purposes.
const sessionParams = stringify(sessionData);

localStorage.setItem(
    `session-key-${safe7559Account?.account?.address}`,
    sessionParams
);
</code></pre>

### 3 - Use the Session Key

{% tabs %}
{% tab title="SDK Core" %}

```typescript
import {
    createComethPaymasterClient,
    createSmartAccountClient,
} from "@cometh/connect-core-sdk";
import {
    toSmartSessionsSigner
    smartSessionActions,
    toSmartSessionsAccount,
} from "@cometh/session-keys";
import { privateKeyToAccount } from "viem/accounts";

const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.4337_PAYMASTER_URL

const stringifiedSessionData = localStorage.getItem(
    `session-key-${WALLETADDRESS}`
);
const sessionData = parse(stringifiedSessionData);

const sessionKeySigner = await toSmartSessionsSigner(safe7559Account, 
{
    moduleData: sessionData.moduleData,
    signer: privateKeyToAccount(sessionData.privateKey),
})

const sessionKeyAccount = await toSmartSessionsAccount(
    safe7559Account?.account, 
    sessionKeySigner
)

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
});

const sessionKeyClient = createSmartAccountClient({
    account: sessionKeyAccount,
    chain,
    bundlerTransport: http(bundlerUrl),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
}).extend(smartSessionActions());

const callData = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count",
});

const hash = await sessionKeyClient.usePermission({
    actions: [
        {
            target: COUNTER_CONTRACT_ADDRESS,
            callData: callData,
            value: BigInt(0),
        },
    ],
});
```

{% endtab %}

{% tab title="Permissionless" %}

```typescript
import {
    toSmartSessionsSigner
    smartSessionActions,
    toSmartSessionsAccount,
} from "@cometh/session-keys";
import { privateKeyToAccount } from "viem/accounts";
import { entryPoint07Address } from "viem/account-abstraction";
import { createSmartAccountClient} from "permissionless";
import { createPaymasterClient } from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { http } from "viem";

const bundlerUrl = process.env.4337_BUNDLER_URL;
const paymasterUrl = process.env.4337_PAYMASTER_URL

const stringifiedSessionData = localStorage.getItem(
    `session-key-${WALLETADDRESS}`
);
const sessionData = parse(stringifiedSessionData);

const sessionKeySigner = await toSmartSessionsSigner(safe7559Account, 
{
    moduleData: sessionData.moduleData,
    signer: privateKeyToAccount(sessionData.privateKey),
})

const sessionKeyAccount = await toSmartSessionsAccount(
    safe7559Account?.account, 
    sessionKeySigner
)

const paymasterClient = await createPaymasterClient({
    transport: http(paymasterUrl)
});

const pimlicoClient = createPimlicoClient({
    transport: http(paymasterUrl),
    entryPoint: {
        address: entryPoint07Address,
        version: "0.7",
    },
})

const sessionKeyClient = createSmartAccountClient({
    account: sessionKeyAccount,
    chain,
    bundlerTransport: http(bundlerUrl, {
        retryCount: 5,
        retryDelay: 1000,
        timeout: 20_000,
    }),
    paymaster: paymasterClient,
    userOperation: {
        estimateFeesPerGas: async () => {
            return (await pimlicoClient.getUserOperationGasPrice()).fast
        },
    },
}).extend(smartSessionActions());

const callData = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count",
});

const hash = await sessionKeyClient.usePermission({
    actions: [
        {
            target: COUNTER_CONTRACT_ADDRESS,
            callData: callData,
            value: BigInt(0),
        },
    ],
});
```

{% endtab %}
{% endtabs %}


# Policies

For now, our session keys only allow the whitelisting of contract with functions, soon we'll add spending limits, timeframe and other features.


# Sudo policy

The sudo policy gives full permission to the signer. The signer will be able to send any UserOps.

```typescript
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
        },
    ],
});
```


# Action policy

The action policy limits the target (either contract or EOA) that the UserOp can interact with.

```typescript
const createSessionsResponse = await safe7559Account.grantPermission({
    sessionRequestedInfo: [
        {
            sessionPublicKey: sessionOwner.address,
            actionPoliciesInfo: [
                {
                    contractAddress: COUNTER_CONTRACT_ADDRESS,
                    functionSelector: toFunctionSelector(
                        "function count()"
                    ) as Hex,
                },
            ],
        },
    ],
});
```


# Bundler API

## What is a bundler

A Bundler, as introduced by [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), is a pivotal infrastructure component enabling account abstraction on Ethereum Virtual Machine (EVM) networks.&#x20;

Its core function is to manage a mempool of User Operations (UserOps), aggregating them and submitting them to an Entry Point Contract for on-chain execution.&#x20;

This process involves a decentralized, permissionless peer-to-peer network of bundlers, ensuring that UserOps are validated and compliant with rigorous security protocols designed to prevent various attack vectors, including Denial of Service (DoS) attacks.&#x20;

## Endpoints

{% hint style="info" %}
**We support multiple chains** for the bundler service, you just need to modify the **chainId** and **apiKey** to your network. Here the[ list of our current supported networks.](/quick-start/supported-networks)
{% endhint %}

<table><thead><tr><th>URL</th><th data-hidden>Network</th></tr></thead><tbody><tr><td>https://bundler.cometh.io/<strong>CHAIN_ID</strong>?apikey=<strong>API_KEY</strong></td><td>Arbitrum</td></tr></tbody></table>

**Example for Arbitrum Sepolia** : <https://bundler.cometh.io/**421614**?apikey=**API\\_KEY>\*\*

## Bundler RPC Method

Here are all the available methods for a Bundler in the context of ERC-4337. Each method facilitates the submission, gas estimation, retrieval of information, and management of User Operations (UserOps) efficiently and securely on the Ethereum network.

1. [**eth\_sendUserOperation**](/bundler/bundler-api/eth_senduseroperation): This method submits a User Operation (UserOp) to the mempool. If the operation is accepted, it returns a userOpHash. If unsuccessful, it returns an error. This ensures that UserOps are included in the blockchain network for execution.
2. [**eth\_estimateUserOperationGas**](/bundler/bundler-api/eth_estimateuseroperationgas) : Estimates the gas values required for a given User Operation, including PreVerificationGas, VerificationGas, and CallGasLimit. It can also simulate different states using optional state overrides, making it useful for scenarios where precise gas estimation is needed without actual execution.
3. [**eth\_getUserOperationByHash**](/bundler/bundler-api/eth_getuseroperationbyhash): Retrieves a User Operation and its transaction context based on a given userOpHash. Provides detailed information about the UserOp, including its status and related transaction details.
4. [**eth\_getUserOperationReceipt**](/bundler/bundler-api/eth_getuseroperationreceipt): Fetches the receipt of a User Operation based on a given userOpHash. The receipt includes metadata and the final status of the UserOp, such as whether it was successfully executed and any logs generated during its execution.
5. [**eth\_supportedEntryPoints**](/bundler/bundler-api/eth_supportedentrypoints): Returns an array of supported EntryPoint addresses as specified in the configuration. The first element in the array is the preferred EntryPoint of the bundler. This helps in identifying which EntryPoints can be used for submitting UserOps.

##


# eth\_sendUserOperation

This method submits a user operation to be included on-chain.&#x20;

It returns the **userOpHash** if accepted otherwise returns an error.

### Request

```json
{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "eth_sendUserOperation",
    "params": [
        {
            "sender": "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB",
            "nonce": "0x0",
            "initCode": "0xDFF6208C96701bC589737b252A5A4B862f15E00b738767320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000ffdb39fe2100b45e81d8e51f942785d2eeecee0",
            "callData": "0x9faf00f4d9c8df66a69fd6242d468aa8a31a439d14fc6c7af3868a06ed392233bc7e39475df25ad2b52bd5e19e1d438277207a415cb4d4ce8ad192464c55ddf1a9559ff900000000000000000000000073da77f0f2daaa88b908413495d3d0e37458212e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000039946fd82c9c86c9a61bceed86fbdd284590bdd90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
            "paymasterAndData": "0x",
            "signature": "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000245eab87439bd309d66de800303f1db9a3eecb735e308681e212aef8037c3d8438b8a50ad8bfff30f44786dde7f37ea2474e016a7224107eb6557a017ee5661525000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22656b435663303542377a544a6934426a6e7453304b5a665f55317a78504a4e52365964704d32664c48786f222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303030222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000",
            "maxFeePerGas": "0x35000396b",
            "maxPriorityFeePerGas": "0xb0d24a9d",
            "callGasLimit": "0x30d40",
            "verificationGasLimit": "0x186a00",
            "preVerificationGas": "0x30d40"
        },
        "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
    ]
}
```

### Response

```json
{
	"jsonrpc": "2.0",
	"id": 3,
	"result": "0x23852fe813cf76730a0423a81699d626359a6f2971528404495b0d0a737c3786"
}
```


# eth\_estimateUserOperationGas

This method provides estimates for **PreVerificationGas**, **VerificationGas**, and **CallGasLimit** based on a given UserOperation and EntryPoint address.&#x20;

{% hint style="info" %}
It does not validate the signature field or the current gas values; however, to ensure the most accurate results, a dummy signature, such as a correctly formatted and appropriately lengthened signature, should be used.
{% endhint %}

### Request

```json
{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "eth_estimateUserOperationGas",
    "params": [
        {
            "sender": "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB",
            "nonce": "0x1",
            "initCode":"0x",
            "callData": "0x9faf00f4d9c8df66a69fd6242d468aa8a31a439d14fc6c7af3868a06ed392233bc7e39475df25ad2b52bd5e19e1d438277207a415cb4d4ce8ad192464c55ddf1a9559ff900000000000000000000000073da77f0f2daaa88b908413495d3d0e37458212e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000039946fd82c9c86c9a61bceed86fbdd284590bdd90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
            "paymasterAndData": "0x",
            "signature": "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000245eab87439bd309d66de800303f1db9a3eecb735e308681e212aef8037c3d8438b8a50ad8bfff30f44786dde7f37ea2474e016a7224107eb6557a017ee5661525000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22656b435663303542377a544a6934426a6e7453304b5a665f55317a78504a4e52365964704d32664c48786f222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303030222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000",
            "maxFeePerGas": "0x35000396b",
            "maxPriorityFeePerGas": "0xb0d24a9d",
            "callGasLimit": "0x30d40",
            "verificationGasLimit": "0x186a00",
            "preVerificationGas": "0x30d40"
        },
        "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
    ]
}
```

### Response

```json
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "preVerificationGas": "0xc178",
        "verificationGasLimit": "0x2a768",
        "callGasLimit": "0x225ec"
    }
}
```

### Optional state override set

Gas estimation for a UserOperation can also be performed under various states, which is useful in multiple scenarios.&#x20;

For instance, you might need to estimate the gas for an ERC-20 transfer without encountering an RPC error due to insufficient funds.&#x20;

This is achieved by providing a parameter that maps addresses to their override settings.&#x20;

It works the same way as [eth\_call](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth#eth-call).

### Example

```json
{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "eth_estimateUserOperationGas",
    "params": [
        {
            "sender": "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB",
            "nonce": "0x1",
            "initCode":"0x",
            "callData": "0x9faf00f4d9c8df66a69fd6242d468aa8a31a439d14fc6c7af3868a06ed392233bc7e39475df25ad2b52bd5e19e1d438277207a415cb4d4ce8ad192464c55ddf1a9559ff900000000000000000000000073da77f0f2daaa88b908413495d3d0e37458212e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000039946fd82c9c86c9a61bceed86fbdd284590bdd90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
            "paymasterAndData": "0x",
            "signature": "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000245eab87439bd309d66de800303f1db9a3eecb735e308681e212aef8037c3d8438b8a50ad8bfff30f44786dde7f37ea2474e016a7224107eb6557a017ee5661525000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22656b435663303542377a544a6934426a6e7453304b5a665f55317a78504a4e52365964704d32664c48786f222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303030222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000",
            "maxFeePerGas": "0x35000396b",
            "maxPriorityFeePerGas": "0xb0d24a9d",
            "callGasLimit": "0x30d40",
            "verificationGasLimit": "0x186a00",
            "preVerificationGas": "0x30d40"
        },
        "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
        {
            "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB": {
                "balance": "0xff345742234"
            }
        }
    ]
}
```


# eth\_getUserOperationByHash

Fetches the **UserOperation** and based on a given **userOpHash**

### Request

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "jsonrpc": "2.0",
    "method": "eth_getUserOperationByHash",
    "params": ["0x23852fe813cf76730a0423a81699d626359a6f2971528404495b0d0a737c3786"],
    "id": 1
}    
</code></pre>

### Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
        "userOperation":  {
            "sender": "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB",
            "nonce": "0x0",
            "initCode": "0xDFF6208C96701bC589737b252A5A4B862f15E00b738767320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000ffdb39fe2100b45e81d8e51f942785d2eeecee0",
            "callData": "0x9faf00f4d9c8df66a69fd6242d468aa8a31a439d14fc6c7af3868a06ed392233bc7e39475df25ad2b52bd5e19e1d438277207a415cb4d4ce8ad192464c55ddf1a9559ff900000000000000000000000073da77f0f2daaa88b908413495d3d0e37458212e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000039946fd82c9c86c9a61bceed86fbdd284590bdd90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
            "paymasterAndData": "0x",
            "signature": "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000245eab87439bd309d66de800303f1db9a3eecb735e308681e212aef8037c3d8438b8a50ad8bfff30f44786dde7f37ea2474e016a7224107eb6557a017ee5661525000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000867b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22656b435663303542377a544a6934426a6e7453304b5a665f55317a78504a4e52365964704d32664c48786f222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303030222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000000000000000000000",
            "maxFeePerGas": "0x35000396b",
            "maxPriorityFeePerGas": "0xb0d24a9d",
            "callGasLimit": "0x30d40",
            "verificationGasLimit": "0x186a00",
            "preVerificationGas": "0x30d40"
        },
        "entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
        "transactionHash": "0x57465d20d634421008a167cfcfcde94847dba9d6b5d3652b071d4b84e5ce74ff",
        "blockHash": "0xeaeec1eff4095bdcae44d86574cf1bf08b14b26be571b7c2290f32f9f250c103",
        "blockNumber": "0x31de70e"
    }
}
```


# eth\_getUserOperationReceipt

Returns the **receipt** associated with a given **userOpHash**.

### Request

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "jsonrpc": "2.0",
    "method": "eth_getUserOperationReceipt",
    "params": ["0x23852fe813cf76730a0423a81699d626359a6f2971528404495b0d0a737c3786"],
    "id": 1
}    
</code></pre>

### Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
        "userOpHash": "0x23852fe813cf76730a0423a81699d626359a6f2971528404495b0d0a737c3786",
        "sender": "0x7C6EdFcdEc67c0D39AB28D77AA6933fd7Bd385AB",
        "nonce": "0x0",
        "actualGasUsed": "0x7f550",
        "actualGasCost": "0x4b3b147f788710",
        "success": true,
        "logs": [
            // ...
        ],
        "receipt": {
                "transactionHash": "0x9f2a1d30b5a5473b009b87f87b3c6e832ea88c3d7a7cdbf2d7e4e5c8c6a4e7b1",
                "transactionIndex": "0x1b",
                "blockHash": "0xf5f4d8cfe5092ad9be67c3e94a2dfb08a9c17b3fa6c2e7f2280d42f9d6d7e101",
                "blockNumber": "0x45cd80f",
                "from": "0x72948d84A3Cfc7B76D3BD7677dE973d02Fdfc239",
                "to": "0x6AA237E4b1FCDF59EbB40d8EF67E689b037e378a",
                "cumulativeGasUsed": "0x5829d4",
                "gasUsed": "0x8bf6b",
                "contractAddress": null,
                "logs": [
                    // ...
                ],
                "logsBloom": "0x0200080010000400000000800000000000000800000000000000200000080000001000000020000004250832000000000821000000000000400004800000000010000000440020008000001000000500000800000000004000020000200000000000000014000000000000001001000000000008220008101000220504000000000000004000000000000000000000000000400000800000000000000480080400004000000000000800000000004000280000000000000000004400000008000000004000000000042000000000000000000000001000100210040000040000000100000000000000000000000000000000000000000000210000000204000",
                "status": "0x1",
                "effectiveGasPrice": "0x92c1a9f57"
        }
    }
}
```


# eth\_supportedEntryPoints

Returns all the **EntryPoint addresses** supported by the bundler. The first address in the array is the preferred EntryPoint.

### Request

```json
{
    "jsonrpc": "2.0",
    "method": "eth_supportedEntryPoints",
    "params": [],
    "id": 1
}
```

### Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"]
}
```


# Paymaster API

## What is a Paymaster ?

Paymasters are smart contracts designed to facilitate transaction sponsorship.&#x20;

They allow third parties to sponsor the gas fees for transactions, enabling users to interact with the blockchain without having to pay for gas themselves.&#x20;

This can be particularly useful for onboarding new users, running promotional campaigns, or supporting decentralized applications where the end-user experience is improved by abstracting away transaction costs.

## How to sponsor transactions

To authorize the sponsorship of a given contract address, you need to add it to your sponsored addressesed in the dashboard. From there, we will accept sponsoring transactions targeting this contract.

<figure><img src="/files/soKWQVrACsmLaTAjxhMa" alt=""><figcaption><p>Add a sponsored contract address</p></figcaption></figure>

{% hint style="info" %}
At the end of each month, you will receive an invoice with the total amount of gas fees covered. This fee is then billed through the payment method in your Cometh Connect account.

With Cometh Connect, there is no overhead on the price you pay. Depending on your license type, you have a max number of transactions you can sponsor each month.
{% endhint %}

Have a look at the "Gasless with Paymaster" section of the SDK documentation to see how to integrate it seamlessly.

{% content-ref url="/pages/vsQ8xNGKvrZKWR4uP1EE" %}
[Go Gasless](/core-features/go-gasless)
{% endcontent-ref %}

## Endpoints

{% hint style="info" %}
**We support multiple chains** for the paymaster services, you just need to modify the **chainId** and **apiKey** to your network. Here the[ list of our current supported networks.](/quick-start/supported-networks)
{% endhint %}

<table><thead><tr><th>URL</th><th data-hidden>Network</th></tr></thead><tbody><tr><td>https://paymaster.cometh.io/<strong>CHAIN_ID</strong>?apikey=<strong>API_KEY</strong></td><td>Arbitrum</td></tr></tbody></table>

**Example for Arbitrum Sepolia** : <https://paymaster.cometh.io/**421614**?apikey=**API\\_KEY>\*\*

## pm\_sponsorUserOperation

Submit a UserOperation to the paymaster. If approved for sponsorship, it returns the paymasterAndData along with updated gas values

**Request**

```json
{
    "jsonrpc": "2.0",
    "id": 13,
    "method": "pm_sponsorUserOperation",
    "params": [
        {
            "sender": "0x8Fb8E6461F278c1651cb0Be92f27817e7Af128D9",
            "nonce": "0x09",
            "callData": "0x7bb374280000000000000000000000004fbf9ee4b2af774d4617eab027ac2901a41a7b5f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000406661abd00000000000000000000000000000000000000000000000000000000",
            "callGasLimit": "0x0",
            "verificationGasLimit": "0x0",
            "preVerificationGas": "0x0",
            "maxFeePerGas": "0xe4e1c00",
            "maxPriorityFeePerGas": "0x7270e00",
            "signature": "0xecececececececececececececececececececececececececececececececec"
        },
        "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
    ]
}

```

**Response**

```json
{
	"jsonrpc": "2.0",
	"id": 13,
	"result": {
		"paymaster": "0xc49d6e93bB127A2FDf349FAdBD90De6853Bf40ff",
		"paymasterData": "0x0000000000000000000000000000000000000000000000000000000067108be20000000000000000000000000000000000000000000000000000000000001234bbdf2f2f5c456f14c1c72a3fb87c73a64dc8157caa5a8243213f4e0beab664335c31160514ee572b2006e78b77dee3749303d737f3ecf58b27cbeddf24120bac1c",
		"paymasterPostOpGasLimit": "0x01",
		"paymasterVerificationGasLimit": "0x0927c0",
		"hash": "0x2a079aad99c4d17456e487153e689bcedbb55707146b597172b05ab6721cf2b5",
		"signature": "0xbbdf2f2f5c456f14c1c72a3fb87c73a64dc8157caa5a8243213f4e0beab664335c31160514ee572b2006e78b77dee3749303d737f3ecf58b27cbeddf24120bac1c",
		"preVerificationGas": "0x51a257",
		"verificationGasLimit": "0x1987c",
		"callGasLimit": "0x19afb"
	}
}
```

## pm\_supportedEntryPoints

Returns an array of supported EntryPoint addresses.

**Request**

```json
{
   "method":"pm_supportedEntryPoints",
   "id":1,
   "params":[],
   "jsonrpc":"2.0"
}

```

**Response**

```json
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": [
		"0x0000000071727De22E5E9d8BAf0edAc6f37da032"
	]
}
```

## Routes for Sponsored Addresses

**Retrieve Sponsored Addresses**

Use the following `GET` request to obtain the list of sponsored addresses for a specific chain.

```bash
curl --request GET \
  --url 'https://paymaster.cometh.io/sponsored-address/CHAIN_ID/?apikey=APIKEY' \
  --header 'Content-Type: application/json' \
  --header 'User-Agent: insomnia/9.2.0'
```

**Set Sponsored Address**

To add a new sponsored address, send a `POST` request with the target address and chain ID.

```bash
curl --request POST \
  --url 'https://paymaster.cometh.io/sponsored-address/?apisecret=API_SECRET' \
  --header 'Content-Type: application/json' \
  --header 'User-Agent: insomnia/9.2.0' \
  --data '{
    "targetAddress": "0xE1e5072de1d9B120Cc33C57EbADBCD33DBC6dD62",
    "chainId": 100
}'
```

## Routes for Sponsored Chains

**Retrieve Sponsored Chains**

The following `GET` request can be used to get information about sponsored chains.

```bash
curl --request GET \
  --url 'https://paymaster.cometh.io/sponsored-chain/CHAIN_ID/?apikey=APIKEY' \
  --header 'Content-Type: application/json' \
  --header 'User-Agent: insomnia/9.2.0'
```

**Set Sponsored Chain**

This route enables users to sponsor an entire chain, allowing transactions on the chain to benefit from sponsorship.

```bash
curl --request POST \
  --url 'https://paymaster.cometh.io/sponsored-chain/' \
  --header 'Content-Type: application/json' \
  --header 'apisecret: API_SECRET' \
  --data '{
    "chainId": 100
}'
```


# Migrate from the connect legacy SDK

Migrate an account from the legacy SDK to the 4337 SDK

{% hint style="info" %}
If you already have a connect license, **please contact us so that we update you to the new 4337 licensing model**.
{% endhint %}

The migration will allow you to use Safe 1.3.0 accounts created with Connect Legacy in the Connect 4337 SDK.

What will happen:

* The Safe account is migrated to version 1.4.1.&#x20;
* If the user was using a Passkey as signer, the [safeWebAuthnSharedSigner](https://github.com/safe-global/safe-modules/blob/main/modules/passkey/contracts/4337/SafeWebAuthnSharedSigner.sol) will be added as an owner and configured to work with the user's Passkey.

{% hint style="warning" %}
If you have multiple passkeys controlling the account, **only the passkey on the device you are doing the migration will be usable**. Other passkeys won't be working and should be removed.
{% endhint %}

This is the flow you are supposed to follow:

```typescript
import {
    createComethPaymasterClient,
    createLegacySafeSmartAccount,
    createSafeSmartAccount,
    createSmartAccountClient
} from "@cometh/connect-sdk-4337";
import { http, encodeFunctionData } from "viem;
import { gnosis } from "viem/chains";
import countContractAbi from "../contract/counterABI.json";


const apiKeyLegacy = process.env.NEXT_PUBLIC_COMETH_LEGACY_API_KEY;
const apiKey4337 = process.env.NEXT_PUBLIC_COMETH_4337_API_KEY;

const chain = gnosis;

const bundlerUrl = "https://bundler.cometh.io/"+CHAIN_ID+"?apikey="+COMETH_4337_API_KEY;
const paymasterUrl =  "https://paymaster.cometh.io/"+CHAIN_ID+"?apikey="+COMETH_4337_API_KEY;


// Step 1 -  This is the address of you safe using the connect legacy
const smartAccountAddress = LEGACY_ADDRESS

// Step 2 - Create the legacy ts object to available migration  
const legacyClient = await createLegacySafeSmartAccount({
    apiKeyLegacy,
    apiKey4337,
    chain,
    smartAccountAddress
})

// Step 3 - Migrate the safe 
await legacyClient.migrate()


// Your Safe is now migrated, you can use the 4337 SDK
// Create the new ts object to handle the upgraded safe
const updatedSmartAccount = await createSafeSmartAccount({
    apiKey,
    chain,
    smartAccountAddress,
});

const paymasterClient = await createComethPaymasterClient({
    transport: http(paymasterUrl),
    chain,
});

const smartAccountClient = createSmartAccountClient({
    account: smartAccount,
    chain,
    paymaster: paymasterClient,
    bundlerTransport: http(bundlerUrl),
    userOperation: {
        estimateFeesPerGas: async () => {
            return await paymasterClient.getUserOperationGasPrice();
        },
    },
})

const calldata = encodeFunctionData({
    abi: countContractAbi,
    functionName: "count
});

//You can send transaction witht the new sdk
const txHash = await smartAccountClient.sendTransaction({
    to: COUNTER_CONTRACT_ADDRESS,
    data: calldata
});

```

You can also check if the safe account has been migrated:

```typescript
const isTheAccountMigrated = await legacyClient.hasMigrated()
```


# Connect Legacy SDKs (Unity, JS)

Before the release of Connect 4337, we built other versions of the product that already used safe and passkeys, providing account abstraction to games (Unity) and dapps (TS).

If you are interested, you can find all the details about this version in the [Connect Legacy documentation.](https://docs.cometh.io/connect)

If you want to migrate from legacy to the 4337 SDK, please [see here.](/resources/migrate-from-the-connect-legacy-sdk)


# FAQ

### Passkeys have specificities that you have to keep in mind&#x20;

You might be running into these situations when interacting with the SDK:

* **Passkeys only works in** [**secure contexts**](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS) and localhost. If you try to run it on HTTP context, passkeys won't be available.
* **Passkeys are bound to domain** (and subdomains), if you create a signer on "cometh.io" it won't be available on "battle.io", but it will be available on "marketplace.cometh.io" (the reverse won't work: passkey created on subdomain won't be available on root domain). The same way, if you create a signer on localhost it won't be available in a deployed domain.

### Can I use the same wallet on multiple devices?

To use the same wallet on multiple device, there's 2 ways:

* Your browser/os allows [**synced passkeys**](https://passkeys.dev/docs/reference/terms/#synced-passkey). For example with Apple, using iCloud keychain, enables you to synchronize your passkeys through iPhones and MacBook computers using iCloud (Google also support it with Chrome profiles). That way you can connect to a wallet using the same signer on multiple devices.
* Through the [**Add device feature**](broken://pages/USYPjqonHCU6boDZC7lb)**.** You can add devices as new signer for your account, which will allow you to connect from multiple devices.

### Using the Add device feature, are all signers equal?

Yes, the main signer created at account creation and all new signers created through the add device feature have equal rights.

### Can I give a name to a created signer?

Yes, you can give a name to a created signer using the passkeyName field when [**creating the account** ](broken://pages/fDXCsUJ6X7NLJB2pgGaa#init-the-cometh-connect-sdk)or when you [**initiate a signer request**](broken://pages/USYPjqonHCU6boDZC7lb#init-a-new-signer-request).

### Is my browser/OS compatible with passkeys?

* Android v9+
* iOS v16+
* MacOS v13+
* From [Windows 11, version 22H2](https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/webauthn-apis)&#x20;
* Linux support is very limited (working cases on Ubuntu 18+)

You can find more information through this link:

{% embed url="<https://passkeys.dev/device-support/>" %}


