Home
Guides
Passkeys with the Safe{Core} SDK

Passkeys with the Safe{Core} SDK

This guide will teach you how to create and execute multiple Safe transactions grouped in a batch from a Safe Smart Account that uses a passkey as an owner. To have a good user experience, we will use an ERC-4337 compatible Safe with sponsored transactions using Pimlico infrastructure. During this guide, we will create a new passkey, add it to the Safe as an owner, and use it to sign the user operations.

This guide uses Pimlico (opens in a new tab) as the service provider, but any other provider compatible with the ERC-4337 can be used.

Note: Please always use a combination of passkeys and other authentication methods to ensure the security of your users' assets.

Prerequisites

Install dependencies


_10
yarn add @safe-global/relay-kit
_10
yarn add @safe-global/protocol-kit

Steps

Imports

Here are all the necessary imports for the script we implement in this guide.


_10
import { Safe4337Pack } from '@safe-global/relay-kit'
_10
import { extractPasskeyData } from '@safe-global/protocol-kit'

Create a passkey

Firstly, we need to generate a passkey credential using the WebAuthn API in a supporting browser environment.


_25
const RP_NAME = 'Safe Smart Account'
_25
const USER_DISPLAY_NAME = 'User display name'
_25
const USER_NAME = 'User name'
_25
_25
const passkeyCredential = await navigator.credentials.create({
_25
publicKey: {
_25
pubKeyCredParams: [
_25
{
_25
alg: -7,
_25
type: 'public-key'
_25
}
_25
],
_25
challenge: crypto.getRandomValues(new Uint8Array(32)),
_25
rp: {
_25
name: RP_NAME
_25
},
_25
user: {
_25
displayName: USER_DISPLAY_NAME,
_25
id: crypto.getRandomValues(new Uint8Array(32)),
_25
name: USER_NAME
_25
},
_25
timeout: 60_000,
_25
attestation: 'none',
_25
},
_25
})

After generating the passkeyCredential object, we need to create a new object with the PasskeyArgType type that will contain the rawId and the coordinates information.


_10
if (!passkeyCredential) {
_10
throw Error("Passkey creation failed: No credential was returned.");
_10
}
_10
_10
const passkey = await extractPasskeyData(passkeyCredential);

At this point, it's critical to securely store the information in the passkey object in a persistent service. Losing access to this data will result in the user being unable to access their passkey and, therefore, their Safe Smart Account.

Initialize the Safe4337Pack

Once the passkey is created and secured, we can use the Safe4337Pack class exported from the Relay Kit to create, sign, and submit Safe user operations.

To instantiate this class, the static init() method allows connecting existing Safe accounts (as long as they have the Safe4337Module enabled) or setting a custom configuration to deploy a new Safe account at the time where the first Safe transaction is submitted. For this guide, we will deploy a new Safe account, configure the paymaster options to get all the transactions sponsored and connect our passkey to add it as the only owner.


_19
const PIMLICO_API_KEY = // ...
_19
const RPC_URL = 'https://rpc.ankr.com/eth_sepolia'
_19
_19
const safe4337Pack = await Safe4337Pack.init({
_19
provider: RPC_URL,
_19
signer: passkey,
_19
bundlerUrl: `https://api.pimlico.io/v1/sepolia/rpc?apikey=${PIMLICO_API_KEY}`,
_19
paymasterOptions: {
_19
isSponsored: true,
_19
paymasterUrl: `https://api.pimlico.io/v2/sepolia/rpc?apikey=${PIMLICO_API_KEY}`,
_19
paymasterAddress: '0x...',
_19
paymasterTokenAddress: '0x...',
_19
sponsorshipPolicyId // Optional value to set the sponsorship policy id from Pimlico
_19
},
_19
options: {
_19
owners: [],
_19
threshold: 1
_19
}
_19
})

Create a user operation

To create a Safe user operation, use the createTransaction() method, which takes the array of transactions to execute and returns a SafeOperation object.


_10
// Define the transactions to execute
_10
const transaction1 = { to, data, value }
_10
const transaction2 = { to, data, value }
_10
_10
// Build the transaction array
_10
const transactions = [transaction1, transaction2]
_10
_10
// Create the SafeOperation with all the transactions
_10
const safeOperation = await safe4337Pack.createTransaction({ transactions })

The safeOperation object has the data and signatures properties, which contain all the information about the transaction batch and the signatures of the Safe owners, respectively.

Sign a user operation

Before sending the user operation to the bundler, the safeOperation object must be signed with the connected passkey. The user is now requested to authenticate with the associated device and sign in with a biometric sensor, PIN, or gesture.

The signSafeOperation() method, which receives a SafeOperation object, generates a signature that will be checked when the Safe4337Module validates the user operation.


_10
const signedSafeOperation = await safe4337Pack.signSafeOperation(
_10
safeTransaction
_10
)

Submit the user operation

Once the safeOperation object is signed with the passkey, we can call the executeTransaction() method to submit the user operation to the bundler.


_10
const userOperationHash = await safe4337Pack.executeTransaction({
_10
executable: signedSafeOperation
_10
})

Check the transaction status

To check the transaction status, we can use the getTransactionReceipt() method, which returns the transaction receipt after it's executed.


_10
let userOperationReceipt = null
_10
_10
while (!userOperationReceipt) {
_10
// Wait 2 seconds before checking the status again
_10
await new Promise((resolve) => setTimeout(resolve, 2000))
_10
userOperationReceipt = await safe4337Pack.getUserOperationReceipt(
_10
userOperationHash
_10
)
_10
}

In addition, we can use the getUserOperationByHash() method with the returned hash to retrieve the user operation object we sent to the bundler.


_10
const userOperationPayload = await safe4337Pack.getUserOperationByHash(
_10
userOperationHash
_10
)

Recap and further reading

After following this guide, we are able to deploy a new ERC-4337 compatible Safe Smart Account setup with a passkey and create, sign, and execute Safe transactions signing them with the passkey. Learn more about passkeys and how Safe supports them in detail by following these links:

Was this page helpful?