Skip to main content
This guide covers the full implementation of delegated wallets: registering end users, creating wallets they control, and letting them perform actions with their passkeys.
See a complete working example in the nextjs-delegated SDK example.

Setup: service account client

All backend operations use your service account. Set up the client once — it’s used for registration, login, and wallet creation.
import { DfnsApiClient } from '@dfns/sdk'
import { AsymmetricKeySigner } from '@dfns/sdk-keysigner'

const signer = new AsymmetricKeySigner({
  credId: process.env.DFNS_SERVICE_ACCOUNT_CRED_ID,
  privateKey: process.env.DFNS_SERVICE_ACCOUNT_PRIVATE_KEY,
})

const dfns = new DfnsApiClient({
  baseUrl: process.env.DFNS_API_URL,
  authToken: process.env.DFNS_SERVICE_ACCOUNT_TOKEN,
  signer,
})

Step 1: Register and log in end users

Registration (new users)

Use the Delegated Registration flow. Your service account creates the user, your frontend collects the passkey.
1

Create a registration challenge

Authenticate the user with your own system first, then call Create Delegated Registration Challenge.
const challenge = await dfns.auth.createDelegatedRegistrationChallenge({
  body: { email: user.email, kind: 'EndUser' },
})
2

User creates a passkey (frontend)

Send the challenge to your frontend. The browser prompts the user to create a passkey.
Frontend
import { WebAuthn } from '@dfns/sdk-browser'

const webauthn = new WebAuthn({ rpId: 'your-domain.com' })
const attestation = await webauthn.create(challenge)
3

Complete registration

Send the attestation back to your backend. Use Complete User Registration with the temporaryAuthenticationToken from step 1 as the auth token.
const dfnsWithTempToken = new DfnsApiClient({
  baseUrl: process.env.DFNS_API_URL,
  authToken: challenge.temporaryAuthenticationToken,
  signer,
})

const newUser = await dfnsWithTempToken.auth.register({
  body: { firstFactorCredential: attestation },
})

// Store mapping: your user ID -> Dfns user ID
await db.users.update({
  where: { id: yourUserId },
  data: { dfnsUserId: newUser.id },
})

Login (returning users)

Use Delegated Login. Authenticate the user with your system first, then get their Dfns auth token with a single call.
const { token } = await dfns.auth.delegatedLogin({
  body: { username: user.email },
})
The returned token is the end user’s auth token — you’ll need it for step 3.

Step 2: Create delegated wallets

We recommend creating wallets after registration rather than during it — this gives you more control over when and which networks to provision. Two approaches:

By your service account

Your service account creates a wallet and delegates it to a user via the delegateTo field on Create Wallet. This is the simplest approach — the service account’s signer handles action signing automatically.
const wallet = await dfns.wallets.createWallet({
  body: {
    network: 'EthereumSepolia',
    delegateTo: userId, // end user ID from registration
  },
})

By the end user

The end user can also create their own wallets using the delegated client. This follows the Init/Complete pattern — the user must sign with their passkey.
const challenge = await delegatedClient.wallets.createWalletInit({
  body: { network: 'EthereumSepolia' },
})

// ... user signs challenge with passkey on frontend ...

const wallet = await delegatedClient.wallets.createWalletComplete(
  { body: { network: 'EthereumSepolia' } },
  signedChallenge
)
Wallets created by the end user are automatically delegated to them — no delegateTo field needed.
Policies do not apply to delegated wallets. By design, delegated wallets bypass the policy engine — the end user has full control without organizational approval requirements.

Step 3: Delegated actions

Once a user is logged in, they can perform actions on their wallets (transfers, signatures, etc.). These write operations require the user to sign with their passkey.

The delegated client

Use DfnsDelegatedApiClient (TypeScript) or DfnsDelegatedClient (Python) with the end user’s auth token. Unlike the service account client, this client does not have a signer — signing happens on the user’s device.
import { DfnsDelegatedApiClient } from '@dfns/sdk'

const delegatedClient = new DfnsDelegatedApiClient({
  baseUrl: process.env.DFNS_API_URL,
  authToken: endUserToken, // token from delegated login
})

The Init / Complete pattern

Every write method on the delegated client is split into two calls:
  • methodInit() — sends the request payload to Dfns, returns a challenge
  • methodComplete() — sends the same payload + the user’s signed challenge, executes the action
This split exists because the passkey lives on the user’s device, not on your server.
1

Init — get a challenge from Dfns

// Every write method has an Init variant: transferAssetInit, generateSignatureInit, etc.
const challenge = await delegatedClient.wallets.transferAssetInit({
  walletId,
  body: { kind: 'Native', to: '0xe5a2...', amount: '1000000' },
})

return { challenge } // send to frontend
2

Sign — user approves with their passkey (frontend)

Frontend
import { WebAuthnSigner } from '@dfns/sdk-browser'

const webauthn = new WebAuthnSigner({
  relyingParty: { id: 'your-domain.com', name: 'Your App' },
})

// Triggers passkey prompt (Touch ID, Face ID, etc.)
const signedChallenge = await webauthn.sign(challenge)

// Send signedChallenge back to your backend
3

Complete — execute the action

const transfer = await delegatedClient.wallets.transferAssetComplete(
  { walletId, body: { kind: 'Native', to: '0xe5a2...', amount: '1000000' } },
  signedChallenge
)
This pattern applies to all write operations: transferAssetInit/Complete, generateSignatureInit/Complete, createWalletInit/Complete, etc. See the signing flows reference for details including direct API calls without the SDK.

Alternative: social registration

Instead of delegated registration, users can authenticate directly with an identity provider (like Google). No service account is needed.
See the auth-social SDK example for a working implementation, and the Social Registration flow / Social Login API for the API reference.

Security considerations

  • Never expose your service account credentials to the frontend
  • Validate tokens on your backend before creating Dfns users
  • Store the mapping between your user IDs and Dfns user IDs securely
  • Implement rate limiting on registration endpoints

Next steps

Last modified on March 2, 2026