A user operation is the signed instruction your client sends for a smart wallet in place of a raw transaction, as the ERC-4337 account-abstraction standard defines it. Most integrations never call these routes: an ordinary platform write from a smart wallet makes the platform build and bundle the user operation itself, as User operations describes. Use these routes when your client builds and signs user operations itself. A bundler is the service that packs user operations into one transaction and submits it. The platform runs the bundler for your organisation, so you do not run one yourself. You estimate gas, sign the operation and send it. The platform then bundles it, pays for the bundle from the organisation's bundler wallet and records the outcome.
Two interfaces reach the same bundler: the REST routes under / and the ERC-4337 JSON-RPC endpoint at /bundler/{chainId}. chainId is the decimal chain ID of the network.
Every read is scoped to the organisation and the network in the path. An operation stored for another organisation or another network reads as null.
The examples on this page read DALP_URL as your instance's base URL, CHAIN_ID as the network's decimal chain ID and DALP_API_KEY as a read-write key.
Read the EntryPoint
The EntryPoint is the shared ERC-4337 contract that validates and executes each user operation. Each network has one, and the signature is collected over it, so read it before you prepare an operation.
curl "$DALP_URL/api/v2/bundler/$CHAIN_ID/supported-entry-points" \
--header "x-api-key: $DALP_API_KEY"The response is an array with exactly one address. This address is illustrative:
["0x2222222222222222222222222222222222222222"]Submission accepts only that address and refuses any other. Gas estimation and paymaster data also accept the network's configured Directory contract, the platform's registry of system contracts on the network, in its place, and refuse any other address.
Estimate gas before signing
POST /api/ takes the EntryPoint and the unsigned operation. The platform simulates the operation and returns the gas limits to put into it before you sign. In the example, callData is a placeholder: replace it with the encoded call your smart wallet executes.
curl --request POST "$DALP_URL/api/v2/bundler/$CHAIN_ID/estimate" \
--header "x-api-key: $DALP_API_KEY" \
--header "content-type: application/json" \
--data '{
"entryPoint": "0x2222222222222222222222222222222222222222",
"userOperation": {
"sender": "0x1111111111111111111111111111111111111111",
"nonce": "0x0",
"callData": "0x<encoded call>"
}
}'The response holds three gas values. For an operation that names a paymaster, a contract that pays gas for the sender, it also holds paymaster and paymasterPostOpGasLimit. These numbers are illustrative:
{
"preVerificationGas": "0x1d4c0",
"verificationGasLimit": "0x30d40",
"callGasLimit": "0x186a0"
}The platform simulates the call at the call gas limit it returns, so the call finishes within that limit. A call that does not fit inside one transaction on the network is refused. Over JSON-RPC the message names the gas the call needs and the gas one transaction leaves for it.
The returned preVerificationGas is the price send checks the signed operation against, so sign that value or a higher one. The price counts the signature, the paymaster data and the fixed-width nonce, gas limit and fee fields at the non-zero rate whatever they hold, so it depends only on the sender, the initCode, the callData and the lengths of the paymaster data and the signature. A stub signature priced at estimation costs the same as the real signature of the same length, and a nonce, fee or gas limit that changes after the estimate does not move the price. Estimate with a stub as long as the signature you will send, because a longer signature raises the price.
Quantity fields accept a decimal string, a hexadecimal quantity or a non-negative integer. The examples on this page use hexadecimal quantities. The platform reads the upper 192 bits of nonce as the nonce key and the lower 64 bits as the sequence within that key.
Sign the operation
The platform identifies an operation by its ERC-4337 hash: the EIP-712 hash of the packed operation under the domain name ERC4337, version 1, the network's chain ID and the EntryPoint address. Your client computes this hash itself and signs it before it calls send, which returns the same hash. A signature over any other EntryPoint does not match it. The signature format is the one the smart wallet's validator expects, which Smart Wallet describes.
Send a signed operation
POST /api/ takes the EntryPoint and the signed operation. The operation must carry callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas and signature. A request without one of them is refused. The values in this example are illustrative, and a real signature is longer.
curl --request POST "$DALP_URL/api/v2/bundler/$CHAIN_ID/send" \
--header "x-api-key: $DALP_API_KEY" \
--header "content-type: application/json" \
--data '{
"entryPoint": "0x2222222222222222222222222222222222222222",
"userOperation": {
"sender": "0x1111111111111111111111111111111111111111",
"nonce": "0x0",
"callData": "0x<encoded call>",
"callGasLimit": "0x186a0",
"verificationGasLimit": "0x30d40",
"preVerificationGas": "0x1d4c0",
"maxFeePerGas": "0x3b9aca00",
"maxPriorityFeePerGas": "0x5f5e100",
"signature": "0x4f2a"
}
}'The response is the user operation hash:
"0x8c1f0b7a3e5d4c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b"The platform stores the operation and answers with its hash before any bundle exists. Keep the hash for the status and receipt reads. Before it stores the operation, the platform runs these checks in order:
- The priority fee must reach the deployment's configured minimum. A lower fee is refused. A network whose fee quote is zero for both fee components skips this check.
maxFeePerGasmust reach 1.5 times the base fee of the network's latest block, zero on a network whose blocks carry none, plus the operation's ownmaxPriorityFeePerGas, the same headroom the bundler signs its bundle with. A lower value is refused withUser, or, on a network whose blocks carry no base fee,Operation max Fee Per Gas <signed> is below <floor>, 1. 5 times the chain's base fee plus the operation's max Priority Fee Per Gas User. An operation that signs zero for both fee fields on a network whose fee quote is zero for both fee components skips this check and the next one.Operation max Fee Per Gas <signed> is below its own max Priority Fee Per Gas <tip> preVerificationGasmust reach the gas the bundler pays to carry the operation, the price the estimate returns. The price is 21,000 plus the larger of two sums, over the operation's ABI-encoded bytes with the signature, the paymaster data and the fixed-width nonce, gas limit and fee fields counted as non-zero. The first is 85,800 plus those bytes at 4 gas a zero byte and 16 a non-zero byte, plus 10 gas a 32-byte word. The second is the network's calldata floor over those bytes plus the 132 bytes of thehandleOpscall around them, priced at the floor's non-zero rate. From the Prague fork that floor is 10 gas a zero byte and 40 a non-zero byte (EIP-7623). On the Amsterdam fork it is 64 gas a byte (EIP-7976). A lower value is refused withUser. Both messages print each value as a hexadecimal quantity followed by its decimal value.Operation pre Verification Gas <signed> is below <price>, the gas the bundler pays to carry it on this chain - On a network whose blocks carry a calldata floor, the operation's packed, ABI-encoded size must not exceed the deployment's limit, 8,192 bytes by default, the ERC-7562 limit. A larger operation is refused with
User. An operation exempt from steps 2 and 3 skips this check too.Operation is <size> bytes ABI- encoded, above the <limit> bytes the bundler accepts on a chain that charges a calldata floor - Validation is simulated with the operation's own paymaster fields, so a sponsored operation is checked as sponsored. A failed simulation is refused, and so is an operation whose account or paymaster keeps it valid for less than 60 seconds from now (
validUntil): the bundler could not include it in time. - The payer must hold enough funds for the prefund: the sum of the operation's gas limits multiplied by its
maxFeePerGas. A self-paid operation counts the sender's native balance plus its deposit at the EntryPoint. A sponsored operation counts the paymaster's deposit at the EntryPoint. The check adds this operation's prefund to the prefund of the payer's other operations still in flight. A sender that falls short is refused withAA21, and a paymaster withAA31. To see the shortfall, read the sender's balance and its deposit at the EntryPoint, or the paymaster's deposit. The check is skipped on a network that charges no gas, for an operation whose prefund is zero, and when the platform cannot read the payer's funds.
Two limits answer 429 with code BUNDLER_ (DALP-14439), or JSON-RPC error -32504. Both are worth retrying unchanged:
A sender refusal applies only on the organisation and network whose bundle reverted, and only to the sender of the operation the revert names. The other senders in that bundle are not refused, and their operations go back to waiting. The sender's own other waiting operations fail with an error that names the refusal. Only a revert that names the account's own validation (AA21, AA23, AA24), a nonce another operation already used (AA25), or a paymaster the platform does not run (AA31, AA33, AA34) refuses the sender. A revert on the platform's own paymaster refuses no one. To stay clear of it, keep a sender's nonce, balance and EntryPoint deposit unchanged while its operations wait, and do not send the same operation to another bundler.
An operation that signs zero for both fee fields on a network whose fee quote is zero for both fee components skips checks 2, 3 and 4 while the network's latest block carries no base fee. When that block charges a base fee, the operation is refused with User, because no bundle can carry it.
When the bundler builds a bundle, it runs checks 2 and 3 again on each signed operation, against the block it builds on, because the base fee or the network's fork can change after send stored the operation:
- An operation whose
maxFeePerGasis below 1.5 times that block's base fee plus its ownmaxPriorityFeePerGasstaysPENDINGand is checked again at every new block. It joins a bundle once the base fee falls far enough for itsmaxFeePerGasto reach that floor. Later operations from the same sender and nonce key wait behind it. To send it sooner, replace it with higher fees. An operation still below the floor an hour after the platform last stored it (when it was sent, replaced, released fromBLOCKED, or returned by a dropped bundle) becomesFAILED. - An operation whose
preVerificationGasis below the price at that block can never be carried, for example one stored before the network moved to a fork with a higher calldata floor. It becomesFAILED. - An operation whose
maxFeePerGasis below its ownmaxPriorityFeePerGascannot pay at any base fee, and an operation that signs zero for both fee fields cannot be carried on a network whose blocks charge a base fee. Both becomeFAILED.
Each of these failures reads with error set to PRE_FLIGHT_REJECTED, the code the bundler also uses for an operation it refuses for its size or for a failed bundle simulation. Send a new operation with the estimate's preVerificationGas and fees that reach the current floor. An operation that signs zero for both fee fields on a network whose blocks carry no base fee skips the preVerificationGas check, because a bundle that carries it is filed at zero fees. Operations the platform prepares itself are priced when they are prepared and are not checked again.
Sending the same signed operation again returns the same hash and stores nothing new. When the stored operation is PENDING, the repeat hands it to the bundler again, and the bundler checks it as above. In every other state the repeat returns the hash and changes nothing, so a retry after a lost response is safe. A repeat does not resubmit an operation that failed. Send a new operation with a new signature instead.
Replace a pending operation
To reprice an operation that is waiting, send a new signed operation from the same sender with the same nonce to the same send route. Both maxFeePerGas and maxPriorityFeePerGas must reach the original value multiplied by 1.1, rounded down to a whole wei. A successful replacement swaps the stored operation for the new one, and the original hash then reads as null.
The platform refuses a replacement in three cases. The JSON-RPC message names each one:
The original's prefund does not count against the payer's funds when the platform checks the replacement.
Read the status
GET /api/ reads the stored operation.
curl "$DALP_URL/api/v2/bundler/$CHAIN_ID/user-operations/$USER_OPERATION_HASH" \
--header "x-api-key: $DALP_API_KEY"This response is illustrative. transactionHash, blockNumber and error appear only when the platform has recorded them. In this response blockNumber, when present, is a JSON number, while the receipt carries it as a hexadecimal quantity.
{
"id": "0192f6c0-7b4d-7f2a-9c6e-3f1a2b3c4d5e",
"userOpHash": "0x8c1f0b7a3e5d4c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b",
"sender": "0x1111111111111111111111111111111111111111",
"nonce": "0x0",
"nonceKey": "0x0",
"callData": "0x<encoded call>",
"status": "BUNDLED",
"createdAt": 1794000000000,
"updatedAt": 1794000004000,
"idempotencyKey": "0192f6c0-7b4d-7f2a-9c6e-3f1a2b3c4d5e",
"transactionHash": "0x3a9e7c1d5b2f4a6e8c0d2b4f6a8e0c2d4b6f8a0e2c4d6b8f0a2e4c6d8b0f2a4e"
}Stop polling at MINED or FAILED. The schema in the API explorer also lists PREPARING, SUBMITTING, CONFIRMED and EXPIRED, which this route does not return. A MINED operation whose call reverted carries the revert data in error. The platform does not re-sign it with more gas, so send a new operation instead.
An operation can stay BUNDLED while an earlier transaction of the bundler wallet is missing, because the chain mines the wallet's transactions in order. The platform fills a skipped nonce itself with a zero-value transfer from the wallet to itself, so a skipped nonce does not block later bundles permanently. No call on your side is needed, and resending the operation does not speed it up. See what happens to a nonce the bundler skipped.
Read the receipt
GET /api/ returns the on-chain outcome.
curl "$DALP_URL/api/v2/bundler/$CHAIN_ID/user-operations/$USER_OPERATION_HASH/receipt" \
--header "x-api-key: $DALP_API_KEY"The response is null until the operation has settled on chain as confirmed or reverted. It is also null for a hash the organisation does not hold on that network. This response is illustrative:
{
"userOpHash": "0x8c1f0b7a3e5d4c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b",
"sender": "0x1111111111111111111111111111111111111111",
"nonce": "0x0",
"success": true,
"transactionHash": "0x3a9e7c1d5b2f4a6e8c0d2b4f6a8e0c2d4b6f8a0e2c4d6b8f0a2e4c6d8b0f2a4e",
"blockHash": "0x5d7f9b1e3c5a7d9f1b3e5c7a9d1f3b5e7c9a1d3f5b7e9c1a3d5f7b9e1c3a5d7f",
"blockNumber": "0x1a2b3c"
}success is false when the call reverted, and reason then carries the revert data. Your operation succeeded when the receipt is present and success is true.
Use the ERC-4337 JSON-RPC endpoint
POST /bundler/ speaks the ERC-4337 bundler protocol. It takes the same X-Api-Key header and applies the same sender, EntryPoint and network rules as the REST routes. Every method needs a read-write key, the reads and eth_chainId included: the endpoint refuses a read key with HTTP 403, a JSON-RPC error whose data.id is DALP-0036, and data.retryable set to false.
It supports these methods:
eth_chainIdeth_supportedEntryPointseth_estimate User Operation Gas eth_sendUserOperationeth_get User Operation By Hash eth_get User Operation Receipt pm_getPaymasterStubDatapm_getPaymasterData
curl --request POST "$DALP_URL/bundler/$CHAIN_ID" \
--header "x-api-key: $DALP_API_KEY" \
--header "content-type: application/json" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getUserOperationReceipt",
"params": ["0x8c1f0b7a3e5d4c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b"]
}'The answer is a JSON-RPC 2.0 envelope whose result holds the receipt from the receipt route, or null. The two read methods return the shapes this page shows for the REST reads. The receipt has no gas-used, gas-cost or log fields, and eth_ returns the stored operation, so a standard ERC-4337 client library that parses the full receipt or operation object does not parse these shapes. Map them in your client.
eth_sendUserOperation and eth_ take the user operation and the EntryPoint address as their two parameters. The two paymaster methods take the user operation, the EntryPoint, the chain ID and a context value. Their chain ID must match the network in the path. They answer only when the organisation pays gas on that network through a paymaster, and otherwise refuse with Paymaster sponsorship is not enabled. The REST route POST /api/ returns the same paymaster data as pm_getPaymasterData. pm_getPaymasterStubData returns the same fields with a placeholder signature, for gas estimation only: an operation sent with it fails validation. Only pm_getPaymasterData and the REST route sign a ticket.
A batch holds 1 to 100 requests. A request or batch that holds only notifications answers HTTP 204 with no body. A GET on the endpoint answers HTTP 405.
Refusals
The two interfaces report the same refusal in different detail. A REST route answers with the platform's error envelope: an HTTP status, a stable code and the catalogue message for that code. The JSON-RPC endpoint answers with a JSON-RPC error whose message carries the specific reason. The messages this page quotes are JSON-RPC messages. Over REST, branch on the HTTP status and code:
The REST answer does not name the ERC-4337 code. To read it, send the same operation to eth_sendUserOperation on the JSON-RPC endpoint. If the operation passes this time, that call stores and bundles it.
Most of these refusals are caller errors: fix the field before you send again. Two can clear without a change to the request. A prefund refusal counts the payer's other operations still in flight, so it can pass once those settle or once you fund the payer. A network whose deployment is not active refuses until the deployment finishes. The REST routes answer that case with HTTP 400 TRANSACTION_. The JSON-RPC endpoint answers it with HTTP 409 and a JSON-RPC error whose data.retryable is false: a retry does not help, so check the network's status in organisation settings and send again once it is active.
Next steps
- User operations explains how the platform bundles and settles operations, including one a bundle did not execute.
- Smart Wallet explains the validators, signer weights and threshold that authorise an operation.
- Operator Wallet explains the bundler wallet that pays for each bundle.
- The v2 API explorer holds the full request and response schema for each route.