> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Implement delegated wallets

> Register end users, create delegated wallets, and let users transact with their passkeys.

This guide covers the full implementation of [delegated wallets](/advanced/delegated-wallets): registering end users, creating wallets they control, and letting them perform actions with their passkeys.

<Tip>
  See a complete working example in the [nextjs-delegated SDK example](https://github.com/dfns/dfns-sdk-ts/tree/m/examples/sdk/nextjs-delegated).
</Tip>

## Setup: service account client

All backend operations use your [service account](/guides/developers/service-account). Set up the client once. It's used for registration, login, and wallet creation.

<CodeGroup>
  ```typescript Backend TypeScript theme={null}
  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,
  })
  ```

  ```python Backend Python theme={null}
  import os
  from dfns_sdk import DfnsClient, DfnsClientConfig, KeySigner

  signer = KeySigner(
      credential_id=os.environ["DFNS_SERVICE_ACCOUNT_CRED_ID"],
      private_key=os.environ["DFNS_SERVICE_ACCOUNT_PRIVATE_KEY"],
      app_origin=os.environ.get("DFNS_APP_ORIGIN", "https://app.dfns.io"),
  )

  config = DfnsClientConfig(
      auth_token=os.environ["DFNS_SERVICE_ACCOUNT_TOKEN"],
      base_url=os.environ.get("DFNS_API_URL", "https://api.dfns.io"),
      signer=signer,
  )
  ```
</CodeGroup>

## Step 1: Register and log in end users

### Registration (new users)

Use the [Delegated Registration flow](/api-reference/auth/registration-flows#delegated-users-registration-flow). Your service account creates the user, your frontend collects the passkey.

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App as Your App
    participant Dfns

    User->>App: 1. Authenticate (your auth system)
    App->>Dfns: 2. Create user (service account)
    Dfns-->>App: 3. Registration challenge
    App->>User: 4. Create passkey
    User-->>App: 5. Passkey attestation
    App->>Dfns: 6. Complete registration
    Dfns-->>App: 7. User created
```

<Steps>
  <Step title="Create a registration challenge">
    Authenticate the user with your own system first, then call [Create Delegated Registration Challenge](/api-reference/auth/create-delegated-registration-challenge).

    <CodeGroup>
      ```typescript Backend TypeScript theme={null}
      const challenge = await dfns.auth.createDelegatedRegistrationChallenge({
        body: { email: user.email, kind: 'EndUser' },
      })
      ```

      ```python Backend Python theme={null}
      with DfnsClient(config) as dfns:
          challenge = dfns.auth.create_delegated_registration_challenge({
              "email": user["email"],
              "kind": "EndUser",
          })
      ```
    </CodeGroup>
  </Step>

  <Step title="User creates a passkey (frontend)">
    Send the challenge to your frontend. The browser prompts the user to create a passkey.

    ```typescript Frontend theme={null}
    import { WebAuthn } from '@dfns/sdk-browser'

    const webauthn = new WebAuthn({ rpId: 'your-domain.com' })
    const attestation = await webauthn.create(challenge)
    ```
  </Step>

  <Step title="Complete registration">
    Send the attestation back to your backend. Use [Complete User Registration](/api-reference/auth/complete-user-registration) with the `temporaryAuthenticationToken` from step 1 as the auth token.

    <CodeGroup>
      ```typescript Backend TypeScript theme={null}
      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 },
      })
      ```

      ```python Backend Python theme={null}
      temp_config = DfnsClientConfig(
          auth_token=challenge["temporaryAuthenticationToken"],
          base_url=os.environ.get("DFNS_API_URL", "https://api.dfns.io"),
          signer=signer,
      )

      with DfnsClient(temp_config) as dfns_temp:
          new_user = dfns_temp.auth.complete_user_registration({
              "firstFactorCredential": attestation,
          })

      # Store mapping: your user ID -> Dfns user ID
      db.users.update(user_id=your_user_id, dfns_user_id=new_user["id"])
      ```
    </CodeGroup>
  </Step>
</Steps>

### Login (returning users)

Use [Delegated Login](/api-reference/auth/login-flows#delegated-users-login-flow). Authenticate the user with your system first, then get their Dfns auth token with a single call.

<CodeGroup>
  ```typescript Backend TypeScript theme={null}
  const { token } = await dfns.auth.delegatedLogin({
    body: { username: user.email },
  })
  ```

  ```python Backend Python theme={null}
  with DfnsClient(config) as dfns:
      result = dfns.auth.delegated_login({"username": user["email"]})
      token = result["token"]
  ```
</CodeGroup>

The returned `token` is the end user's auth token. You'll need it for [step 3](#step-3-delegated-actions).

## 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](/api-reference/wallets/create-wallet). This is the simplest approach. The service account's signer handles action signing automatically.

<CodeGroup>
  ```typescript Backend TypeScript theme={null}
  const wallet = await dfns.wallets.createWallet({
    body: {
      network: 'EthereumSepolia',
      delegateTo: userId, // end user ID from registration
    },
  })
  ```

  ```python Backend Python theme={null}
  with DfnsClient(config) as dfns:
      wallet = dfns.wallets.create_wallet({
          "network": "EthereumSepolia",
          "delegateTo": user_id,  # end user ID from registration
      })
  ```
</CodeGroup>

### By the end user

The end user can also create their own wallets using the delegated client. This follows the [Init/Complete pattern](#the-init--complete-pattern). The user must sign with their passkey.

<CodeGroup>
  ```typescript Backend TypeScript theme={null}
  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
  )
  ```

  ```python Backend Python theme={null}
  challenge = delegated_client.wallets.create_wallet_init(
      body={"network": "EthereumSepolia"},
  )

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

  wallet = delegated_client.wallets.create_wallet_complete(
      body={"network": "EthereumSepolia"},
      signed_challenge={
          "challengeIdentifier": challenge["challengeIdentifier"],
          "firstFactor": signed_challenge,
      },
  )
  ```
</CodeGroup>

Wallets created by the end user are automatically delegated to them. No `delegateTo` field needed.

<Warning>
  **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.
</Warning>

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

<CodeGroup>
  ```typescript Backend TypeScript theme={null}
  import { DfnsDelegatedApiClient } from '@dfns/sdk'

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

  ```python Backend Python theme={null}
  from dfns_sdk import DfnsDelegatedClient, DfnsDelegatedClientConfig

  delegated_config = DfnsDelegatedClientConfig(
      auth_token=end_user_token,  # token from delegated login
      base_url="https://api.dfns.io",
  )

  delegated_client = DfnsDelegatedClient(delegated_config)
  ```
</CodeGroup>

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

<Steps>
  <Step title="Init: get a challenge from Dfns">
    <CodeGroup>
      ```typescript Backend TypeScript theme={null}
      // 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
      ```

      ```python Backend Python theme={null}
      # Every write method has an _init variant: transfer_asset_init, generate_signature_init, etc.
      challenge = delegated_client.wallets.transfer_asset_init(
          wallet_id,
          body={"kind": "Native", "to": "0xe5a2...", "amount": "1000000"},
      )
      ```
    </CodeGroup>
  </Step>

  <Step title="Sign: user approves with their passkey (frontend)">
    ```typescript Frontend theme={null}
    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
    ```
  </Step>

  <Step title="Complete: execute the action">
    <CodeGroup>
      ```typescript Backend TypeScript theme={null}
      const transfer = await delegatedClient.wallets.transferAssetComplete(
        { walletId, body: { kind: 'Native', to: '0xe5a2...', amount: '1000000' } },
        signedChallenge
      )
      ```

      ```python Backend Python theme={null}
      transfer = delegated_client.wallets.transfer_asset_complete(
          wallet_id,
          body={"kind": "Native", "to": "0xe5a2...", "amount": "1000000"},
          signed_challenge={
              "challengeIdentifier": challenge["challengeIdentifier"],
              "firstFactor": signed_challenge,
          },
      )
      ```
    </CodeGroup>
  </Step>
</Steps>

This pattern applies to all write operations: `transferAssetInit/Complete`, `generateSignatureInit/Complete`, `createWalletInit/Complete`, etc. See the [signing flows reference](/api-reference/auth/signing-flows) 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.

<Tip>
  See the [auth-social SDK example](https://github.com/dfns/dfns-sdk-ts/tree/m/examples/sdk/auth-social) for a working implementation, and the [Social Registration flow](/api-reference/auth/registration-flows#social-registration-flow) / [Social Login API](/api-reference/auth/social-login) for the API reference.
</Tip>

## 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

<CardGroup cols={2}>
  <Card title="End-user recovery" icon="rotate-right" href="/guides/developers/end-user-recovery">
    Implement recovery flows so users can regain access
  </Card>

  <Card title="Configure WebAuthn" icon="key" href="/guides/developers/webauthn-configuration">
    Configure passkeys for your domain
  </Card>

  <Card title="Signing flows" icon="file-signature" href="/api-reference/auth/signing-flows">
    Full Init/Complete signing reference
  </Card>

  <Card title="Registration flows" icon="user-plus" href="/api-reference/auth/registration-flows">
    API reference for user registration
  </Card>
</CardGroup>
