Skip to content

How to use a Dynamic signer with permissionless.js

permissionless.js allows you to plug in custom signers to control the accounts that you create. Dynamic is an embedded wallet provider that allows you to easily onboard users to your dapp. It is possible to use Dynamic as a signer with permissionless.js, allowing you to use Dynamic to create and control smart accounts and sign transactions.

Install the dependencies

npm i @dynamic-labs/sdk-react-core @dynamic-labs/wagmi-connector @dynamic-labs/ethereum permissionless viem wagmi

Create the Dynamic provider

Following Dynamic's quickstart guide, set up the Dynamic provider in your app. Also integrate the DynamicWagmiConnector, which will allow you to use Dynamic as a signer with permissionless.js.

import {
  DynamicContextProvider,
  DynamicWidget,
} from "@dynamic-labs/sdk-react-core";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
 
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
 
export const App = () => {
  return (
    <DynamicContextProvider
      settings={{
        // Find your environment id at https://app.dynamic.xyz/dashboard/developer
        environmentId: "REPLACE-WITH-YOUR-ENVIRONMENT-ID",
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      <DynamicWagmiConnector>
        <DynamicWidget />
      </DynamicWagmiConnector>
    </DynamicContextProvider>
  );
};

Create the SmartAccountClient

Create the smart account client using the Dynamic signer. Note: DynamicWagmiConnector internally sets up the WagmiConfig, so there is no need to do it separately. This is where you would configure what smart account implementation (e.g. Safe, Kernel, Biconomy, TrustWallet SimpleAccount) and what paymaster logic you want to use.

import { createSmartAccountClient, walletClientToSmartAccountSigner, ENTRYPOINT_ADDRESS_V06 } from "permissionless";
import { signerToSimpleSmartAccount } from "permissionless/accounts";
import { useWalletClient } from "wagmi";
import { createPublicClient, http, zeroAddress } from "viem";
import { sepolia } from "viem/chains";
 
const {
  data: walletClient
} = useWalletClient()
 
const publicClient = createPublicClient({
  chain: sepolia, // or whatever chain you are using
  transport: http()
})
 
const signer = walletClientToSmartAccountSigner(walletClient)
 
const simpleSmartAccountClient = await signerToSimpleSmartAccount(publicClient, {
  entryPoint: ENTRYPOINT_ADDRESS_V06,
  signer,
})
 
const smartAccountClient = createSmartAccountClient({
  account: simpleSmartAccountClient,
  entryPoint: ENTRYPOINT_ADDRESS_V06,
  chain: sepolia, // or whatever chain you are using
  bundlerTransport: http("<bundler_endpoint>", {
    timeout: 30_000 // optional
  }),
  middleware: {
    gasPrice: async () => (await bundlerClient.getUserOperationGasPrice()).fast,
    sponsorUserOperation: pimlicoPaymaster.sponsorUserOperation,
  },
})

Send a transaction

You can now send transactions as normal. The sponsorUserOperation function will be called before each transaction is signed and sent, applying the custom paymaster logic you have set.

const txHash = await smartAccountClient.sendTransaction({
    to: zeroAddress,
    data: "0x",
    value: BigInt(0)
})