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”.
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.
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_ when they differ, and with TOKEN_ 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
pendingwithrelayable: falseuntil 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_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 409PERMIT_ SIGNING_ PENDING TOKEN_, and you sign again.PERMIT_ NONCE_ CONFLICT - 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 theIdempotency-Keyheader. While a relay of that permit is queued or running, a repeat call answers 202 with thetransactionIdof the first relay and sends no second transaction. After the relay settled, a repeat call answers 409TOKEN_, or 409PERMIT_ ALREADY_ RELAYED TOKEN_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.PERMIT_ NOT_ RELAYABLE - Relay with a signature. No stored permit exists, so the header does the work. Send the same
Idempotency-Keyon 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.