Skip to main content

Permit

Reference for the permit token feature and the Platform API routes that sign a permit, relay it, and read its signing domain and nonce.

The permit feature lets a token holder approve a spender with a signature instead of an approve transaction, and the Console and the Platform API sign, store and relay that signature.

A token feature is a module attached to one token. An account with the token's governance role attaches, detaches and configures it after issuance where the feature allows it, and each entry names any other role an action needs. The DALP Library is the catalogue of instrument templates that ship with the platform, and each entry says which of them include the feature.

Permit

Lets a holder approve a spender with an off-chain signature instead of an approve transaction, following EIP-2612, the Ethereum standard for signed approvals. The signed message is a permit. It lets a spender, an address such as an exchange or a payment contract, move up to a set amount of the holder's tokens until a deadline. The amount the spender may move is its allowance, and the permit sets the allowance when someone submits it. Anyone can submit the signed permit. The feature checks the deadline, consumes the owner's nonce first, verifies the signature, and sets the allowance on the token. The nonce is a counter that the feature keeps for each owner. It starts at 0 and rises by one whenever one of the owner's permits is used, so a signature works once and only in order. The standard form accepts signatures from ordinary wallets. A second form checks signatures through EIP-1271, the standard that lets a contract wallet vouch for a signature, so a smart wallet can sign. The signature is bound to the token's name at deployment, and a second submission of the same signature fails because the nonce has moved. The feature restricts no token operation. Choose it when a spender should not need a separate approve transaction. The DALP Library includes it on 32 templates, all except Carbon Credit, Controlling Class Share, Custody receipt, and Share-Class Mirror.

The feature has no parameters. A holder signs a permit with “Create permit approval” or through the Platform API. The button sits on the permit page, which “View permit details” on the Permit card of the Features tab opens. The review step states “A Custodian relays this permit.” On the chain anyone can submit a permit. On the platform an account with the custodian role relays it, from Actions or from the permit page, and the organisation pays the network fee, through its paymaster when it sponsors gas. Signing and relaying each ask for wallet verification. That is a second check, such as a PIN code or a one-time code, before the platform signs or submits on your behalf. A signed permit that nobody relays before its deadline expires, and the holder signs a new one. After issuance, governance attaches and detaches it. The Console attaches it from the “Attach a feature” list on the token's Features tab and detaches it behind a typed confirmation. It cannot be rotated. Attaching it again after a detach restores the same contract with its nonces unchanged, as the nonce section of the Permit API below describes. A permit with a passed deadline is rejected with ERC2612ExpiredSignature. It runs on EVM only. Smart wallet covers the signing side for a contract wallet.

Permit API

Two Platform API routes handle a permit. One signs it and stores it, and the other relays it so that the allowance changes on the chain. The generated API reference lists the request bodies, the responses, and the error codes of both under “Sign and store a permit” and “Relay a signed permit”.

Route
Who calls it
What it does
POST /api/v2/tokens/{assetId}/features/permit/signatures
The holder. The platform signs with the wallet that executes for the holder, after wallet verification, so the caller needs no token role
Signs a permit for the spender, value, and deadline in the body, stores it as pending, and returns it with its id and nonce. It sends no transaction
POST /api/v2/tokens/{assetId}/features/permit/permits
An account with the custodian role
Sets the allowance on the chain. The body names a stored permit by signedPermitId, or gives owner, spender, value, and deadline with either v, r, and s or one packed signature
GET /api/v2/tokens/{assetId}/permit-info
Any signed-in caller
Reads the feature address, the domain separator, the permit type hash, the token name, and, when you send owner, that owner's current nonce. It answers 200 with data: null when the token has no attached Permit feature
GET /api/v2/tokens/{assetId}/features/permit/signatures
Any signed-in caller
Lists stored permits with their status (pending, relayed, or expired) and relayable
GET /api/v2/tokens/{assetId}/permit/replay-history
Any signed-in caller
Lists the nonces that were already used on the chain

Amounts such as value are whole numbers in the token's smallest unit, sent as strings or integers up to the largest uint256. A deadline is a Unix timestamp in seconds. Both write routes take a walletVerification object with a secretVerificationCode and a verificationType of PINCODE, OTP, SECRET_CODES, or OIDC_MFA. A permit reaches the chain in one of two ways. In the platform-signed path, the holder calls the sign route and a custodian then relays the stored permit by signedPermitId. In the externally signed path, your signer reads permit-info, signs the typed data itself, and a custodian relays the signature with owner, spender, value, deadline, and either v, r, s or signature. For a contract wallet, send the bytes that the wallet's EIP-1271 check accepts as signature. The relay route answers 202 with a transactionId, a status, and a statusUrl by default. Send Prefer: wait=<seconds> to wait for settlement and receive 200 with the token. The platform keeps wait between 5 and 99 seconds. Idempotency and async settlement covers the 202 handle and the Prefer header.

curl -X POST "https://your-instance.example.com/api/v2/tokens/0x7a1c0b9e5d3f4a8b2c6e1d9f0a3b5c7d9e1f2a4b/features/permit/signatures" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "spender": "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be",
    "value": "500000000000000000000",
    "deadline": 1793750400,
    "walletVerification": { "verificationType": "PINCODE", "secretVerificationCode": "123456" }
  }'

The response holds the stored permit. Its id is the value to relay.

{
  "data": {
    "id": "0198a3c4-5b7e-7d2a-9c41-6f0e8b3d5a12",
    "tokenAddress": "0x7a1c0b9e5d3f4a8b2c6e1d9f0a3b5c7d9e1f2a4b",
    "owner": "0x9d4e2b6a8c0f1e3d5b7a9c2e4f6a8b0d1c3e5f7a",
    "spender": "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be",
    "value": "500000000000000000000",
    "deadline": "1793750400",
    "nonce": "3",
    "signatureKind": "ecdsa",
    "status": "pending",
    "createdAt": "2026-10-28T13:05:11.000Z"
  },
  "links": { "self": "/api/v2/tokens/0x7a1c0b9e5d3f4a8b2c6e1d9f0a3b5c7d9e1f2a4b/features/permit/signatures" }
}

A custodian relays that permit and waits up to 30 seconds for the result.

curl -X POST "https://your-instance.example.com/api/v2/tokens/0x7a1c0b9e5d3f4a8b2c6e1d9f0a3b5c7d9e1f2a4b/features/permit/permits" \
  -H "X-Api-Key: sm_dalp_..." \
  -H "Prefer: wait=30" \
  -H "Content-Type: application/json" \
  -d '{
    "signedPermitId": "0198a3c4-5b7e-7d2a-9c41-6f0e8b3d5a12",
    "walletVerification": { "verificationType": "PINCODE", "secretVerificationCode": "123456" }
  }'

The signing domain

A permit signature follows EIP-712, the standard for signing structured data. The domain a signer must use has four fields, and the permit feature contract answers for them.

Domain field
Value
name
The token's name when the permit feature was deployed
version
1
chainId
The chain the token runs on
verifyingContract
The permit feature contract, which permit-info returns as featureAddress

The signed type is Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline). permit-info returns the token's current indexed name as tokenName. If your hash does not match domainSeparator, the name at deployment differs, and domainSeparator is the value to trust. permit-info also returns the hash of the domain as domainSeparator and the hash of the type as permitTypeHash, so you can compare them with what your signer computes before you sign. The domain separator is indexed and asOfBlockNumber says which block it was read at. The nonce is read live. The sign route compares the domain that the feature declares with the indexed separator and stops with TOKEN_PERMIT_FEATURE_UNAVAILABLE when they differ, and with TOKEN_PERMIT_READ_REFUSED when the separator is not indexed yet.

Nonces

Use the nonce from permit-info when you build a signature yourself. The chain accepts a permit only when its nonce equals the owner's current nonce, and the use of the permit raises that nonce by one.

  • Signing through the platform. The sign route starts at the owner's current nonce and skips every nonce that a pending stored permit uses. Two permits from one holder therefore get consecutive nonces. A permit whose nonce is above the current nonce stays pending with relayable: false until the permits before it are used. An expired permit does not advance the nonce: sign a new permit, which takes the released nonce, and relay it first.
  • A permit held for custody approval. When a custody approval rule, which sends the signature request to a named approver before it is signed, holds the signature, the sign route answers 409 TOKEN_PERMIT_SIGNING_PENDING and keeps the nonce for that permit. Another permit from the same holder takes the next free nonce. After the approver approves, sign again with the same spender, value, and deadline to receive the signature over the held nonce. If a different permit claims the nonce in the meantime, the losing call answers 409 TOKEN_PERMIT_NONCE_CONFLICT, and you sign again.
  • Detaching and attaching Permit again. Detaching Permit removes it from the token's list and leaves its contract in place. Attaching it again restores that same contract, at the same address, so the nonces keep their values and the signing domain does not change. While Permit is detached, the sign and relay routes refuse every request because the token has no attached Permit feature, and a stored permit cannot be relayed. After the new attach, a stored permit whose deadline has not passed and whose nonce equals the owner's current nonce relays again, and a signature that you built outside the platform stays valid on the same terms. A permit whose deadline passes while Permit is detached expires as usual.

Retries and idempotency

  • Sign. The sign route does not deduplicate. Each successful call stores one more permit with the next free nonce, so a retry after a lost response creates a second permit. List the stored permits before you retry. A permit that waits for custody approval is the exception: the same spender, value, and deadline receive the same nonce.
  • Relay by signedPermitId. The platform deduplicates by the stored permit and not by the Idempotency-Key header. While a relay of that permit is queued or running, a repeat call answers 202 with the transactionId of the first relay and sends no second transaction. After the relay settled, a repeat call answers 409 TOKEN_PERMIT_ALREADY_RELAYED, or 409 TOKEN_PERMIT_NOT_RELAYABLE when the platform has not recorded the result and the nonce has moved. Read the permit's status either way. When the first relay failed, the next call submits a new transaction.
  • Relay with a signature. No stored permit exists, so the header does the work. Send the same Idempotency-Key on every retry, and the transaction queue attaches the retry to the first request. The queue matches the retry to the first request, and the route itself gives no replay guarantee. Without the header the platform generates a new key for each call, a retry sends a second transaction, and the chain rejects it because the nonce is already used.

Errors

A request with a missing or wrong wallet verification, a caller without the custodian role on the relay route, and a token without an attached Permit feature use the platform's standard errors. Those need a change to the request or the caller before any retry, except a wallet verification attempt limit (429), which clears after the wait that the response states. The permit-specific errors are below. The error reference lists every code with its fix.

Code
Status
Retry
Route
Cause
TOKEN_PERMIT_DEADLINE_EXPIRED, DALP-9495
422
No
Sign
The deadline is not after the current chain time. Sign again with a later deadline
TOKEN_PERMIT_WALLET_UNAVAILABLE, DALP-9496
409
No
Sign
No executor wallet, the organisation wallet that signs and submits on a user's behalf, is set for this organisation and chain. Configure one
TOKEN_PERMIT_NOT_FOUND, DALP-9497
404
No
Relay
No stored permit matches this organisation, chain, token, and signedPermitId
TOKEN_PERMIT_ALREADY_RELAYED, DALP-9498
409
No
Relay
The permit was already submitted. Read the permit and its transaction instead
TOKEN_PERMIT_NOT_RELAYABLE, DALP-9499
409
No
Relay
The permit expired, its deadline passed, or its nonce is not the owner's current nonce, for example because the chain already used it. Read the permit, then sign a new one if needed
TOKEN_PERMIT_NONCE_CONFLICT, DALP-9500
409
Yes
Sign
Another permit took the nonce while this one was stored. Sign again
TOKEN_PERMIT_FEATURE_UNAVAILABLE, DALP-9501
503
Yes
Sign
The owner's nonce or the token name is not available, or the domain that the feature declares does not match the indexed one. Retry after indexing
TOKEN_PERMIT_SIGNING_FAILED, DALP-9502
503
Yes
Sign
The custody signer did not complete the signature
TOKEN_FEATURE_PERSISTENCE_FAILED, DALP-9266
503
Yes
Both
The platform could not read or save the token feature data that the request needs
TOKEN_PERMIT_PERSISTENCE_FAILED, DALP-9505
503
Yes
Both
The platform could not read or save the stored permit. Retry without changing the request
TOKEN_PERMIT_SIGNING_PENDING, DALP-14037
409
No
Sign
A custody approval holds the signature. The response includes the approval reference and its expiry, which is never later than the permit deadline
TOKEN_PERMIT_CUSTODY_APPROVAL_CLOSED, DALP-14038
409
No
Sign
The custody provider rejected the signature, it failed, or the approval expired. Sign a new permit
TOKEN_PERMIT_READ_REFUSED, DALP-13001
422
No
Both
A nonce or domain read failed for the reason in the response, for example a domain separator that is not indexed yet. Report it with the request ID if it persists
CONTRACT_ERROR, DALP-9026, DALP-9027, DALP-9069
422
No
Relay
The chain rejected a permit sent with a signature: the deadline passed (9026), the recovered signer is not the owner (9027), or a contract wallet's signature failed verification (9069). Rebuild the signature with the current nonce and domain, or sign a new permit with a later deadline

Was this page helpful?