Always check allowance over inputs
As we have seen in the confidential token example, most of your external facing functions that expose a confidential input will want to be declared twice like so.msg.sender.isAllowed(value).
This is important because the caller may use an existing handle that it has not access to but that the contract
has access to. In this case, depending on the contract, the caller may be to deduce or gain access to the value in used as input.
Think in terms of information leakage
When designing an app, you need to be mindful not only to who receives access over the ciphertexts, but also about what can be deduced from the information that is publicly available. For example, if we were to naively port Uniswap pools to Inco, we may want to accept confidential input amounts for a swap and send confidential output amounts. If the price of the pool is public, then the swapped amount can be deduced by comparing the price before and after the swap. In another example, if you are holding a secret auction where the current highest bidder is continuously updated, one can deduce the current highest bid by submitting increasingly large bids until it becomes the highest bidder. This kind of possible deductions are called information leakage, and may show up often in your dapps.Don’t lose access over your ciphertexts
Don’t forget to calle.allowThis() and e.allow after an operation. By default, after the transaction is
included, no one retains access to the new handles being created and the contract will not be able to compute over them in the future,
and the user won’t be able to see them if not granted access.
Always verify the intended handle when verifying attestations
When accepting attested decryption or computation results on-chain, always verify that the attestation corresponds to the intended ciphertext handle. This prevents malicious actors from submitting valid attestations for different handles that may have been swapped in transit.decryption.handle matches the specific handle you expect to decrypt.
Be extra careful of delegatecalls
A contract being delegatecalled can decrypt any ciphertext your contract holds or share access to it.Session keys grant unbounded access — scope and lifetime matter
A session voucher (see Allowance Voucher) gives the holder access to all of the signer’s encrypted handles across every Inco-enabled dApp, not just the handles created by your application. This is by design — the verifier checks only expiry and decrypter identity, not the origin of the handle being accessed. Practical guidance for dApp developers:- Keep expiry short. Prefer session lifetimes measured in minutes. A 24-hour session is rarely necessary and leaves a long window for misuse.
- Revoke promptly. Call
updateActiveVouchersSessionNonce()after the session is no longer needed. This immediately invalidates all outstanding vouchers regardless of their expiry timestamp. - Do not store or forward vouchers. The signed voucher is a capability token. Logging it, storing it in localStorage beyond the session, or forwarding it to a backend grants that storage or service the same access as the user.
- Show users what they are signing. The
warningfield in theAllowanceVoucherEIP-712 payload is rendered by wallets as a named field, and it is placed first in the struct so it appears before the unreadable byte fields. Do not suppress or alter it — the on-chain verifier rejects any proof whose voucher does not contain the exact required warning text (reverts withInvalidVoucherWarning), and it is also the user’s only in-wallet indication of the scope of what they are authorising.
Don’t hesitate to contact us for any question or if you want to discuss your
dapp design. We are also interested in your feedback on this doc.