WrappedToken
ERC-7984 ERC-20 wrapper interface — shield, unshield, allowance.
WrappedToken is the high-level interface for an ERC-7984 ERC-20 wrapper. It extends Token, so it supports the base confidential-token operations (balanceOf, confidentialTransfer, setOperator, etc.) and adds wrapper-specific methods for converting between the public ERC-20 and its confidential form.
The wrapper is the confidential token. Pass the wrapper contract address directly; there is no separate token / wrapper pair in the SDK object.
Import
import { WrappedToken } from "@zama-fhe/sdk";Construction
const wrappedToken = sdk.createWrappedToken("0xWrapper");
await wrappedToken.shield(1000n);
await wrappedToken.confidentialTransfer("0xRecipient", 500n);
await wrappedToken.unshield(250n);Inherited Token API
WrappedToken extends Token. Use the inherited methods for ERC-7984 confidential-token reads and writes:
balanceOf(owner)confidentialBalanceOf(owner)decryptBalanceAs(params)confidentialTransfer(to, amount, options?)confidentialTransferFrom(from, to, amount, callbacks?)setOperator(operator, until?)isOperator(holder, spender)name(),symbol(),decimals()isConfidential(),isWrapper()
Wrapper Reads
underlying
() => Promise<Address>
Reads the underlying public ERC-20 token address from the wrapper contract. The result is cached per WrappedToken instance.
allowance
(owner: Address) => Promise<bigint>
Reads the ERC-20 allowance that owner granted to this wrapper contract.
isPayable
() => Promise<boolean>
Checks whether the underlying ERC-20 supports ERC-1363. shield() uses this internally to route between transferAndCall and approve + wrap. The result is cached per WrappedToken instance.
Shield
shield
(amount: bigint, options?: ShieldOptions) => Promise<TransactionResult>
Shields public ERC-20 tokens into confidential tokens. The SDK validates the public ERC-20 balance before submitting.
The execution path is selected automatically:
transferAndCall
The underlying ERC-20 supports ERC-1363
1
approve + wrap
The underlying ERC-20 does not support ERC-1363
2
Options:
approvalStrategy
"exact" | "max" | "skip"
"exact"
Controls approval on the approve + wrap path
to
Address
signer
Recipient of the confidential balance
onApprovalSubmitted
(txHash: Hex) => void
—
Called after the approval tx is submitted
onShieldSubmitted
(txHash: Hex) => void
—
Called after the shield tx is submitted
approvalStrategy is ignored on the ERC-1363 transferAndCall path because there is no allowance step.
approveUnderlying
(amount?: bigint) => Promise<TransactionResult>
Approves this wrapper contract to spend the underlying ERC-20. Defaults to uint256.max. If an existing non-zero allowance is present, the SDK resets it to zero first for compatibility with tokens such as USDT.
Most apps should use shield() directly and let it manage approvals.
wrap
(amount: bigint, options?: WrapOptions) => Promise<TransactionResult>
Wraps already-approved underlying ERC-20 into confidential tokens — the second half of the manual approve + wrap flow. Validates the ERC-20 balance and the wrapper's allowance before submitting: throws InsufficientERC20BalanceError if the balance is too low, and InsufficientAllowanceError if the allowance is below amount (call approveUnderlying() first).
Most apps should use shield(), which routes ERC-1363 tokens through transferAndCall and manages approval automatically. Reach for wrap() only when you need the approve and wrap signatures as separate, independently-triggered steps — see Shield tokens → Manual approve + wrap.
Options:
to
Address
signer
Recipient of the confidential balance
onWrapSubmitted
(txHash: Hex) => void
—
Called after the wrap tx is submitted
Unshield
unshield
(amount: bigint, options?: UnshieldOptions) => Promise<TransactionResult>
Unshields a specific confidential amount back to public ERC-20. This orchestrates the two-step protocol:
Submit
unwrap.Wait for the unwrap receipt and public decryption proof.
Submit
finalizeUnwrap.
The returned txHash and receipt are for the finalization transaction.
Options:
skipBalanceCheck
boolean
false
Skip the confidential-balance pre-flight check
onUnwrapSubmitted
(txHash: Hex) => void
—
Called after the unwrap tx is submitted
onFinalizing
() => void
—
Called before waiting for the finalize proof
onFinalizeSubmitted
(txHash: Hex) => void
—
Called after the finalize tx is submitted
unshieldAll
(callbacks?: UnshieldCallbacks) => Promise<TransactionResult>
Unshields the entire confidential balance by using the on-chain encrypted balance handle directly.
resumeUnshield
(unwrapTxHash: Hex, callbacks?: UnshieldCallbacks) => Promise<TransactionResult>
Resumes an interrupted unshield after the unwrap transaction has already been submitted. The SDK reads the unwrap receipt, extracts the unwrap request id, waits for the proof, and submits finalizeUnwrap. On success it clears the persisted pending state.
If the unwrap request was already finalized on-chain, it clears the persisted pending state and throws UnshieldAlreadyFinalizedError instead of submitting a transaction that would revert. The funds already arrived; treat the error as completion.
getPendingUnshield
() => Promise<Hex | null>
Returns the unwrap transaction hash of an unshield that was interrupted between its two phases, or null if none is pending for this wrapper. The SDK persists this automatically when unshield() / unshieldAll() submit phase 1, and clears it once phase 2 finalizes.
The SDK verifies the persisted hash on-chain before reporting it: if the unwrap request was already finalized, it clears the record and returns null. If the verification read fails, the hash is returned unverified; a network error never deletes recovery state.
Resuming stays caller-driven — surface a "resume" prompt and call resumeUnshield(), rather than finalizing on load and triggering a wallet transaction the user did not initiate.
Low-Level Unwrap Primitives
Most apps should use unshield() or unshieldAll(). The low-level methods are escape hatches for custom two-phase flows.
unwrap
(amount: bigint) => Promise<UnwrapResult>
Encrypts amount and submits the unwrap request. Finalization is not automatic. The returned UnwrapResult extends TransactionResult with the unwrapRequestId decoded from the UnwrapRequested event, so you can finalize without parsing the receipt yourself.
unwrapAll
() => Promise<UnwrapResult>
Submits an unwrap request for the full confidential balance using the current encrypted balance handle. Like unwrap, it returns the decoded unwrapRequestId on the result.
finalizeUnwrap
(unwrapRequestId: EncryptedValue) => Promise<TransactionResult>
Completes an unwrap after the gateway has publicly decrypted the unwrap request. Pass the unwrapRequestId from the UnwrapResult that unwrap / unwrapAll returned.
Related
Token — base ERC-7984 confidential-token API
ZamaSDK — creates
WrappedTokenviacreateWrappedToken()Shield tokens — full shield flow
Unshield tokens — full unshield flow
useWrappedToken — React hook returning a
WrappedToken
Last updated