PYUSDX Cross-chain (Portal)
Purpose
The Portal contract is the entry point for cross-chain transfers of PYUSDX and its extensions. It uses a burn-and-mint mechanism for bridging.
To reduce reliance on the implementations and standards of individual bridging providers, the Portal is designed with pluggable bridge adapters.
Pluggable bridge adapters
The Portal delegates cross-chain communication to bridge adapters. Adapters can be implemented using multiple cross-chain protocols (for example LayerZero, Wormhole, or Hyperlane), giving the system the flexibility to switch between them as needed. The Portal has no direct knowledge of the underlying bridging implementations when sending or receiving cross-chain messages; all functionality is abstracted behind the adapter interface. Similarly, each bridge adapter has no awareness of protocol-specific logic; it simply sends and receives byte-encoded messages.
If multiple bridge implementations exist for the same pathway, the admin can configure a default option, or a sender can specify the preferred one when sending a cross-chain message.
LayerZeroBridgeAdapter is the first adapter implementation. It translates the Portal's generic sendMessage / receiveMessage interface into LayerZero V2 Endpoint calls. Other adapters (Hyperlane, Wormhole, and so on) can be added without modifying the Portal.
Cross-chain token transfer
A cross-chain transfer proceeds as follows:
- The sender requests a quote by calling
quoteon thePortal. - The sender approves the
Portalto spend thesourceToken. - The sender calls
sendToken, passing the fee amount from step 1 asmsg.value. - The
PortaltransferssourceTokenfrom the sender. - If
sourceTokenis an extension, thePortalunwraps it to PYUSDX viaISwapFacility.swapOut. - The
Portalburns the PYUSDX viaIPYUSDX.burn. - The
Portalencodes the token-transfer message. - The
PortalcallssendMessageon theLayerZeroBridgeAdapter. - The
LayerZeroBridgeAdaptersends the message by callingsendon the LayerZeroEndpoint. - The LayerZero
Endpointon the destination receives the message. - The
EndpointcallslzReceiveon theLayerZeroBridgeAdapter. - The
LayerZeroBridgeAdaptercallsreceiveMessageon thePortal. - The
Portaldecodes the payload. It validates that the decodedtargetChainIdmatchescurrentChainId()and the decodedtargetBridgeAdaptermatchesmsg.sender, reverting withInvalidTargetChainorInvalidTargetBridgeAdapteron mismatch (defense-in-depth, independent of LayerZero's own guarantees). - If
destinationTokenis PYUSDX, thePortalmints it to the recipient. - If
destinationTokenis an extension, thePortalwraps PYUSDX to it viaISwapFacility.swapInand transfers it to the recipient. - If wrapping fails, the recipient receives PYUSDX instead of the extension.
The cross-chain message is a fixed-size, abi.encodePacked token-transfer payload carrying the destination chain ID and peer, a message ID, the amount, the destination token, and the sender and recipient. The exact byte layout is on the PYUSDX specification page.
Frozen recipient handling
PYUSDX and each of its extensions maintain independent freeze lists. To prevent total-supply inconsistencies (where tokens are burned on the source chain but cannot be minted on the destination because the recipient becomes frozen while a transfer is in flight), the Portal uses a fallbackRecipient address. This address is admin-controlled and is expected to remain unfrozen.
The Portal checks the freeze list only on PYUSDX. If the recipient is frozen on PYUSDX, the tokens are redirected to the fallbackRecipient to prevent a revert during minting. If the recipient is frozen on a PYUSDX extension but not on PYUSDX, the wrapping step via SwapFacility fails and the recipient receives PYUSDX directly instead. In practice, any address subject to regulatory restrictions is expected to be frozen consistently across PYUSDX and all of its extensions.
| Frozen on PYUSDX | Frozen on Extension | Destination Token | Behavior |
|---|---|---|---|
| No | No | PYUSDX | Mint PYUSDX to recipient |
| No | No | Extension | Mint PYUSDX to Portal, wrap to Extension for recipient |
| Yes | (either) | PYUSDX | Redirect: mint PYUSDX to fallbackRecipient |
| Yes | (either) | Extension | Redirect: mint PYUSDX to Portal, wrap Extension to fallbackRecipient |
| No | Yes | Extension | Wrap fails → WrapFailed → mint PYUSDX directly to recipient |
| Yes | (either) | Extension (wrap fails) | Redirect + WrapFailed: mint PYUSDX directly to fallbackRecipient |
| Yes (both recipient & fallback) | — | Any | Transaction reverts. Message not marked processed → retryable after admin rotates fallbackRecipient |
Fallback recipient
When the intended recipient of a cross-chain transfer is frozen on PYUSDX, tokens are redirected to the fallbackRecipient. This prevents the bridge transaction from reverting and stranding funds. It is set by DEFAULT_ADMIN_ROLE.
setFallbackRecipient is gated by DEFAULT_ADMIN_ROLE (not OPERATOR_ROLE) because the fallback recipient custodies redirected PYUSDX from frozen accounts and must be controlled by the highest-trust role.Pause model
The Portal maintains separate send and receive pause states:
pauseSend()/unpauseSend(): blocks outbound transfers.pauseReceive()/unpauseReceive(): blocks inbound transfers.pauseAll()/unpauseAll(): both directions.
Payload gas limit
setPayloadGasLimit(destinationChainId, gasLimit) (gated by OPERATOR_ROLE) configures the destination-chain execution gas limit per payload type. Passing gasLimit = 0 unsets the limit for that chain; quote and transfer to that destination then revert until a non-zero limit is configured again.
Reentrancy protection
The Portal uses ReentrancyLock, a transient-storage (TSTORE / TLOAD) guard that stores msg.sender as the lock, enabling msgSender() to resolve the original caller through the lock.
Deployed contract addresses are listed on the PYUSDX platform deployments page.
PYUSDX Specification
Low-level reference for PYUSDX: token parameters, roles, initialization parameters, rounding matrices, invariants, the cross-chain message format, errors, and events.
PYUSDX Compliance & Security
PYUSDX compliance primitives and security model: roles, account freezing, forced transfers, pausing, upgradeability, and reentrancy protection.