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:

  1. Predict -- get the wallet address before it exists on-chain, along with the fee requirements
  2. Fund -- transfer the required fee tokens to that predicted address
  3. Create -- deploy the wallet on-chain (only succeeds if the address is funded)
If a transaction fee token is configured (e.g., a relayer fee), the predicted address must have sufficient balance before you can create the wallet. The create step will reject the request if the balance is insufficient.

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:

bash
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:

json
{
  "statusCode": 200,
  "content": {
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "deployed": false,
    "fee": {
      "required": true,
      "tokenAddress": "0x2a54bCF2Bd338E1e6A7a8Dd1eCFB9598E8C71D0c",
      "tokenSymbol": "ALB",
      "tokenDecimals": 18,
      "amountPerTransaction": "1000000000000000000",
      "currentBalance": "0",
      "sufficient": false
    }
  }
}
Fee FieldDescription
requiredWhether a fee token balance is needed to use the wallet
tokenAddressThe ERC-20 contract address of the fee token
tokenSymbolToken symbol (e.g. ALB, USDC)
tokenDecimalsToken decimals (e.g. 18)
amountPerTransactionFee charged per transaction (in smallest unit)
currentBalanceCurrent fee token balance at the predicted address
sufficientWhether 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.

The address exists deterministically on-chain even before the wallet contract is deployed. You can safely transfer tokens to it -- they will be accessible once the wallet is created.

Transfer at least fee.amountPerTransaction of the fee token (fee.tokenAddress) to the predicted address. For example, using ethers.js:

javascript
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.

The wallet's passkey is bound to your domain, and the address is derived from the public key you supply. Without this step, anyone able to call your integration could have a wallet created against an email they do not control — and every later recovery, notification and approval flows through that address.

Endpoint: POST /baas/wallets/request-email-verification

Required scope: wallet:create

bash
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:

bash
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"
  }'
FieldTypeRequiredDescription
qxstringYesP-256 public key X coordinate (hex, up to 64 chars, optional 0x prefix)
qystringYesP-256 public key Y coordinate (hex, up to 64 chars, optional 0x prefix)
rpIdHashstringYesSHA-256 hash of the RP ID (relying party domain). Must match a registered domain.
labelstringNoA human-readable name for the wallet (max 50 chars)
backupAddressstringNoOptional backup Ethereum address for recovery
withFeebooleanNoWhether to pay creation fee (default: false)
externalUserIdstringNoYour internal user ID (max 255 chars)
externalUserLabelstringNoYour internal user display name (max 255 chars)
verificationCodestringYesThe 6-digit code emailed by request-email-verification. Creation is refused without it.

Response (200 OK):

json
{
  "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):

javascript
const crypto = require('crypto');
const rpId = 'app.empresa.com';
const rpIdHash = '0x' + crypto.createHash('sha256').update(rpId).digest('hex');

What Happens Behind the Scenes

  1. Validates your API key and checks the wallet:create scope
  2. Validates the P-256 public key coordinates are on the secp256r1 curve
  3. Validates the rpIdHash matches a registered domain for your organization
  4. Predicts the wallet address and checks fee token balance
  5. Rejects if fee is configured but balance is insufficient
  6. Deploys a smart contract wallet on-chain via the Factory contract
  7. Links the wallet to your organization with the external user ID
  8. Returns the on-chain wallet address and deployment transaction hash

Error Cases

400 Bad Request -- Insufficient Fee Balance

json
{
  "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

json
{
  "statusCode": 400,
  "content": {
    "message": "Wallet already deployed at this address"
  }
}

400 Bad Request -- rpIdHash Mismatch

json
{
  "statusCode": 400,
  "content": {
    "message": "rpIdHash does not match any registered domain for this organization"
  }
}

403 Forbidden -- Wrong Scope

json
{
  "statusCode": 403,
  "content": {
    "message": "Insufficient scopes. Required: wallet:create"
  }
}
Store the wallet address in your database. You will need it to check balances, prepare token transfers, and execute transactions. The on-chain address never changes.