How to create a smart contract wallet for your user through the Passkallet BaaS API.
Create a Wallet
What is a Wallet?
A wallet in Passkallet is a smart contract deployed on-chain. It is not a traditional crypto wallet with a private key. Instead, it is controlled by passkeys (biometric authentication using P-256 keys).
Wallet Creation Flow
Creating a wallet is a 3-step process:
- Predict -- get the wallet address before it exists on-chain, along with the fee requirements
- Fund -- transfer the required fee tokens to that predicted address
- Create -- deploy the wallet on-chain (only succeeds if the address is funded)
Step 1: Predict Address
Endpoint: POST /baas/wallets/predict
Required scope: wallet:create
Send the passkey coordinates to get the predicted on-chain address and fee requirements:
curl -X POST https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/wallets/predict \
-H "x-api-key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"qx": "0x1f2e3d4c5b6a79889786766554433221100ffeeddccbbaa99887766554433221",
"qy": "0xa0b1c2d3e4f50617283940516273849506172839405162738495061728394050",
"rpIdHash": "0x8fc154601bf086c9415d3c6cfbba676749925045017427c57e37cdbb94d84454"
}'Response:
{
"statusCode": 200,
"content": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"deployed": false,
"fee": {
"required": true,
"tokenAddress": "0x2a54bCF2Bd338E1e6A7a8Dd1eCFB9598E8C71D0c",
"tokenSymbol": "ALB",
"tokenDecimals": 18,
"amountPerTransaction": "1000000000000000000",
"currentBalance": "0",
"sufficient": false
}
}
}| Fee Field | Description |
|---|---|
required | Whether a fee token balance is needed to use the wallet |
tokenAddress | The ERC-20 contract address of the fee token |
tokenSymbol | Token symbol (e.g. ALB, USDC) |
tokenDecimals | Token decimals (e.g. 18) |
amountPerTransaction | Fee charged per transaction (in smallest unit) |
currentBalance | Current fee token balance at the predicted address |
sufficient | Whether the current balance covers at least 1 transaction |
Step 2: Fund the Address
If fee.required is true and fee.sufficient is false, you need to transfer fee tokens to the predicted address before creating the wallet.
Transfer at least fee.amountPerTransaction of the fee token (fee.tokenAddress) to the predicted address. For example, using ethers.js:
const token = new ethers.Contract(feeTokenAddress, ERC20_ABI, signer);
await token.transfer(predictedAddress, amountPerTransaction);After funding, you can call predict again to verify sufficient: true.
Step 3: Verify the user's email
Creating a wallet requires a code sent to the end user's email address. This is not optional and it is not a setting: POST /baas/wallets rejects a request without a valid verificationCode.
Endpoint: POST /baas/wallets/request-email-verification
Required scope: wallet:create
curl -X POST https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/wallets/request-email-verification \
-H "x-api-key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{ "email": "alice@example.com" }'Passkallet emails a 6-digit code. Collect it from your user and pass it to the create call below. To check whether a request is still outstanding, useGET /baas/wallets/pending-verification?email=....
Step 4: Create the Wallet
Endpoint: POST /baas/wallets
Required scope: wallet:create
Once the address is funded (or no fee is required), create the wallet:
curl -X POST https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/wallets \
-H "x-api-key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"qx": "0x1f2e3d4c5b6a79889786766554433221100ffeeddccbbaa99887766554433221",
"qy": "0xa0b1c2d3e4f50617283940516273849506172839405162738495061728394050",
"rpIdHash": "0x8fc154601bf086c9415d3c6cfbba676749925045017427c57e37cdbb94d84454",
"label": "Alice Wallet",
"externalUserId": "user_alice_001",
"externalUserLabel": "Alice Smith",
"verificationCode": "074799"
}'| Field | Type | Required | Description |
|---|---|---|---|
qx | string | Yes | P-256 public key X coordinate (hex, up to 64 chars, optional 0x prefix) |
qy | string | Yes | P-256 public key Y coordinate (hex, up to 64 chars, optional 0x prefix) |
rpIdHash | string | Yes | SHA-256 hash of the RP ID (relying party domain). Must match a registered domain. |
label | string | No | A human-readable name for the wallet (max 50 chars) |
backupAddress | string | No | Optional backup Ethereum address for recovery |
withFee | boolean | No | Whether to pay creation fee (default: false) |
externalUserId | string | No | Your internal user ID (max 255 chars) |
externalUserLabel | string | No | Your internal user display name (max 255 chars) |
verificationCode | string | Yes | The 6-digit code emailed by request-email-verification. Creation is refused without it. |
Response (200 OK):
{
"statusCode": 200,
"content": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"label": "Alice Wallet",
"transactionHash": "0xabc123def456789..."
}
}How to compute rpIdHash
The rpIdHash is the SHA-256 hash of your domain (the WebAuthn Relying Party ID):
const crypto = require('crypto');
const rpId = 'app.empresa.com';
const rpIdHash = '0x' + crypto.createHash('sha256').update(rpId).digest('hex');What Happens Behind the Scenes
- Validates your API key and checks the
wallet:createscope - Validates the P-256 public key coordinates are on the secp256r1 curve
- Validates the
rpIdHashmatches a registered domain for your organization - Predicts the wallet address and checks fee token balance
- Rejects if fee is configured but balance is insufficient
- Deploys a smart contract wallet on-chain via the Factory contract
- Links the wallet to your organization with the external user ID
- Returns the on-chain wallet address and deployment transaction hash
Error Cases
400 Bad Request -- Insufficient Fee Balance
{
"statusCode": 400,
"content": {
"message": "Insufficient fee balance at 0x742d35... Required: 1000000000000000000 ALB, Current: 0 ALB. Fund the address before creating the wallet."
}
}The predicted address does not have enough fee tokens. Transfer at least the required amount before retrying.
400 Bad Request -- Already Deployed
{
"statusCode": 400,
"content": {
"message": "Wallet already deployed at this address"
}
}400 Bad Request -- rpIdHash Mismatch
{
"statusCode": 400,
"content": {
"message": "rpIdHash does not match any registered domain for this organization"
}
}403 Forbidden -- Wrong Scope
{
"statusCode": 403,
"content": {
"message": "Insufficient scopes. Required: wallet:create"
}
}address in your database. You will need it to check balances, prepare token transfers, and execute transactions. The on-chain address never changes.