Overview
An investor can hold their own key in an embedded wallet from Privy, a third-party wallet provider. Your front end embeds the Privy SDK, signs the investor in to Privy with a token that DALP issues, and then calls the Platform API to link the wallet, create the identity, deploy the smart wallet and send transfers. DALP never holds the key: the investor's Privy wallet signs, and DALP prepares and submits what it signs.
The investor signs in to DALP, and Privy trusts the DALP token. Your front end needs no second login and no Privy account system of its own. Because Privy signs the investor in on that token, anyone who can sign in to DALP as the investor can also sign in to their Privy wallet, so protect the DALP sign-in as you would the wallet.
The embedded-wallet path applies to an investor who joins an organisation. Onboard an investor through the API covers managed custody, where the platform holds the key. Let an investor choose custody covers the choice between custody kinds and the browser-wallet path. Custody Provider describes both kinds.
Gas is sponsored when the organisation's gas payer is the paymaster. The smart wallet then needs no native currency, so an investor with an empty wallet can still transfer. When the organisation pays gas another way, the smart wallet pays its own gas and preparing a transfer from an empty wallet is refused with DALP-14034, which names the smart wallet and the shortfallWei it is missing. On and off ramps stay a Privy feature in your front end. The flow on this page has no fiat step.
The sequence at a glance
Steps 1 and 2 happen before any wallet exists, and steps 3 to 5 repeat only until the investor is set up.
Embed the Privy SDK
Embed @privy-io/react-auth. The DALP Investor portal pins version 3.51.3, so match that version unless you have tested another. Wrap your application in PrivyProvider and set these options:
appIdis the value DALP returns for the offered provider, and step 1 reads it. You do not choose it. A Privy configuration runs on the deployment's own Privy credentials or on the organisation's, so the Privy app behindappId, and its dashboard settings in Set up Privy to trust DALP, belong to the deployment operator or to the organisation.clientIdis set when DALP returns one.externalisWallets. disable All External Wallets true, because the investor signs with the embedded wallet only.embeddedisWallets. ethereum. create On Login "off". Create the wallet when the investor reaches that step, with the SDK's create wallet hook.supportedChainsanddefaultChainname the organisation's network, whichGET /api/returns asv2/ organizations/ :organization Id/ joinable- participants chainId.
Set up Privy to trust DALP
The Privy dashboard needs custom authentication, which Privy calls JWT-based auth. The values are:
<platform URL> is the public URL of your DALP deployment. Privy fetches the JWKS from that address, so the deployment must be reachable from the internet. The sub claim of the token is the investor's DALP user id.
Step 1: read the offered providers
Call GET /api/. The response answers data, the organisation's designated investor provider or null, and options, every configuration the organisation offers to investors on the request's network, the default first. Each entry carries its id, its provider, its custody label and its clientConfiguration. Render options as they arrive: the platform has already dropped what the organisation does not offer and what does not serve the network, so a front end derives no label, default or network filter of its own. An entry for a Privy configuration carries clientConfiguration with kind set to privy, the tenant, the appId and, when set, the clientId. Take the Privy app id from there.
If no option offers Privy, do not show the Privy option. Which providers are offered is a setting, covered in Which provider is offered.
Step 2: sign the investor in to Privy
The investor is signed in to DALP already. Call GET /api/ with that session, and DALP answers { "token": "<jwt>" }. Hand the token to Privy as the external JWT: with @privy-io/react-auth, pass a function that returns it to use as getExternalJwt, together with isAuthenticated (true once the DALP session names a user) and isLoading (true while that session is still loading). Privy verifies the token against GET /api/ and signs the investor in. Call the token route again each time Privy asks, because the token expires.
Create the embedded Ethereum wallet once Privy reports the investor as authenticated. The wallet address is the signing address you link in step 3.
Step 3: create the participant and prove the key
These calls run in this order, with the investor's DALP session:
GET /api/reads the organisation's network and whether it uses account abstraction (v2/ organizations/ :organization Id/ joinable- participants aaEnabled). It also lists participants the investor can reuse.POST /api/withv2/ participants organizationId, theproviderConfigurationIdfrom step 1 andwalletVerificationcreates a pending participant. Skip it when you resume an existing one.GET /api/withv2/ users/ me/ custody- accounts participantIdlists the participant's custody accounts. If one already links the Privy configuration on this network, reuse it and connect the same wallet.POST /api/withv2/ users/ me/ custody- account- control- preparations providerConfigurationId,participantIdand the wallet address assigningAddressreturns amessage, abindingand anaccount.- The investor signs
messagewith their Privy wallet as a personal message. POST /api/withv2/ users/ me/ custody- accounts/ lifecycles proofset tobinding,accountand anartifactreturns arequestId. Theartifactis the JSON string{"kind":"evm-.self- custody- control","signature":"<signature>"} GET /api/withv2/ users/ me/ custody- account- requests/ :request Id participantIdreads the request. Follow it until its state issucceeded. The stream at the same path with/streamappended reports each change.
The account is ready when GET /api/ shows accountLifecycle and contextLifecycle both active. Step 3b of Let an investor choose custody shows calls 4 to 7 with their requests and responses.
Step 4: create the identity
Call GET /api/ with the participant, custody account and network in the request context headers. It answers 200 with status set to NOT_PROVISIONED for a wallet with no identity, and 404 with IDENTITY_SCOPE_NOT_FOUND while the network's identity registry is not indexed yet. Treat both as no identity. In that case call POST /api/ with the wallet address and walletVerification. The first call is refused with a 409, DALP-12389, whose data. holds the identity factory's typed data. The investor signs it with their Privy wallet through eth_signTypedData_v4. Repeat the call with external, then wait for the write to settle. Idempotency and async settlement covers the settlement signal. Registration and verification follow Onboard an investor through the API from step 3.
Step 5: deploy the smart wallet
Skip this step when aaEnabled is false. Otherwise call POST /api/ with no body fields. The first call is refused with a 409 whose data. holds the typed data to sign. The investor signs that typed data with their Privy wallet. Repeat the call with external. The response names the smart wallet address. The wallet needs no native currency for this step.
Step 6: send a sponsored transfer
A transfer is three calls. First prepare it: POST /api/ with the custodyAccountId, executionMode set to prefer-aa, a summary and the operation (kind, contractAddress, callData and an optional value). The response carries preparedUserOperation with a userOperationHash and the previewData of the operation. Where the organisation's gas payer is the paymaster, previewData names the paymaster and carries its paymasterData, so the smart wallet pays nothing. The paymaster ticket in paymasterData expires after a window the deployment operator sets, 300 seconds by default, so sign and finalise within that window or prepare again.
Then the investor signs. Build the EIP-712 typed data of the user operation from previewData for the EntryPoint in preview (the viem get helper builds it), check that its hash equals userOperationHash, and sign it with eth_signTypedData_v4 in Privy. A plain message signature fails, because it adds a text prefix.
Last, finalise: POST /api/ with the finalize action and this artifact in its body.
{
"action": "finalize",
"artifact": {
"_tag": "Eip4337UserOperationAuthorization",
"kind": "eip-4337-user-operation",
"userOperationHash": "0x...",
"signature": "0x..."
}
}The response carries a transactionRequestId. Follow it on the transaction status stream, GET /api/, until it reaches a terminal state.
Which provider is offered
The deployment decides which providers exist. The switch custody. turns a provider on for the whole deployment. Each organisation then decides which enabled providers its investors see: a provider configuration is offered when its offeredToInvestors flag is set, and the configuration with the investorProvider flag is the default. Offering Privy alone is a configuration change and needs no code.
Errors
Steps 3 to 5 return the refusals in The refusals each step returns.
DALP error index lists every code. Read and handle DALP errors covers the envelope these codes arrive in.
Next steps
- Onboard an investor through the API registers the identity and verifies the investor.
- Idempotency and async settlement covers the headers and status URLs these writes use.
- Custody Provider explains what each provider holds.