On this page
Enable chains and manage fees
Manage your asset from its origin chain. Remote changes are authenticated messages, so you do not need to operate the same wallet address on every destination. You can also act directly on each chain.
Enable a destination
Assets without an issuer compliance contract already include the peers registered when they were created. For a later peer, read isChainEnabled(assetId, dstEid) on the origin. If false, use the Lens quoteSync(router, assetId, dstEid, remoteCompliance, extraOptions), then call enableChain with that native value from the permanent creator.
For assets without an issuer compliance contract, use bytes32 zero for remoteCompliance. For assets with an issuer compliance contract, use the destination's compliance contract encoded as bytes32. A peer must exist first. This is a lasting trust decision: the protocol has no asset-level disable-chain action.
ACTION=enable in the starter supports assets without an issuer compliance contract. Track SyncSent.guid until destination delivery, then read destination state. The source's ChainEnabled alone does not prove the destination is ready.
Update another remote source
Suppose A is the origin, B already has your OFT, and C is newly enabled. Enabling C on A does not broadcast to every other chain. As the owner, call syncAsset(assetId, B, extraOptions) on A to propagate the current enabled list to B, paying the Lens quoteSync with zero compliance. Wait for that sync before offering B → C. Alternatively, enable C directly on B: no message, no fee. Syncing is owner-only because a sync also confirms, on B, the routes B has had frozen for at least ROUTE_ACTIVATION_DELAY (5 minutes).
For compliance assets, a plain sync carries no new compliance address; use setRemoteCompliance to configure policy. A registered remote OFT is not by itself proof that its policy is ready.
Set fees
| Where the setting applies | Method | Quote |
|---|---|---|
| Origin | setFees(assetId, flatFee, feeBps) on the origin router | No LayerZero message |
| Remote chain, from the origin | setRemoteFees(assetId, eid, flatFee, feeBps, extraOptions) on the origin router | Lens quoteSetRemoteFees |
| Remote chain, directly | setFees(assetId, flatFee, feeBps) on that chain's router | No LayerZero message |
Read the target router's immutable minFlatFee and maxFlatFee; the flat fee is in that chain's native base units. feeBps is at most 100 (1%). Settings are independent per chain. Native prices are not automatically converted from dollars.
The fee reference explains the protocol/issuer split and rounding. A remote fee action outside destination bounds can be delivered but rejected. Check FeesUpdated, OwnerActionRejected and StaleConfigIgnored, then read the resulting state.
Claim revenue
Read ownerNativeFees(assetId) and ownerTokenFees(assetId) on each chain. On the origin, call claimFees(assetId, recipient). To claim remotely, quote quoteWithdrawRemoteFees, then call withdrawRemoteFees(assetId, eid, recipientBytes32, extraOptions) from the origin.
Remote claims pay a recipient on that remote chain; they do not bridge revenue home. Choose a recipient you control there. Track the message and OwnerFeesClaimed. The amount paid is what has accrued when the action executes, not necessarily the amount you previewed.
Act directly on a remote chain
Every owner setting can also be changed on the chain it applies to. Call the same methods on that chain's router: setFees, setCompliance, claimFees, and enableChain(assetId, eid, bytes32(0), "0x") with no native value. Nothing is sent to other chains: each call changes only the chain you call it on.
The call must come from the creator address on that chain: a wallet, smart account or multisig. Make sure you control that address on the chain first.
If the creator is a Safe or another smart account
A regular wallet (EOA) has the same key, and so the same owner, on every chain. A smart account exists only on the chains where it has been deployed.
- Safe. A Safe created in Safe{Wallet} is deployed with CREATE2, and its address commits to its original setup: owners, threshold and modules. Deploying it on another chain (for example with "Add another network") produces the same address only with that same original setup, so no one else can take the address.
- Deploy before you rotate signers. Deploying on a new chain always reuses the original setup, not the current one. If you change owners or threshold on the origin, the original signer set can still deploy and control the Safe on any chain where it does not exist yet. Deploy your Safe on every chain where the asset lives before rotating signers, then rotate on each chain.
- Check before acting. Before a direct action on a chain, confirm your Safe is deployed there and that its owners and threshold match the origin.
- Legacy and unusual deployments. A Safe created with the legacy
createProxy(CREATE, nonce-based) can be claimed by someone else on a new chain, as in the Wintermute incident on Optimism. The same applies to smart accounts whose address does not commit to the owner (for example, deployed first and initialized afterwards). On chains with different address rules, such as zkSync Era, your Safe cannot exist at the same address at all. In any of these cases, do not act directly there: manage that chain from the origin, which never depends on the creator's address on other chains.
A direct change overrides the origin updates of the same kind that the chain had already verified in order, so a late execution or retry of those cannot undo it. Origin updates still in flight apply when they arrive. After a route outage, check the setting again once queued origin updates have landed, and repeat your direct change if needed.
Open a detour when a route is down
If the route between your origin A and a chain B stops delivering, your holders on B can still redeem through a detour chain C:
- Choose C: a chain your asset already uses with working routes to A and B, or a new chain Omnisea adds.
- If C is not enabled on A, call
enableChain(assetId, C, remoteCompliance, extraOptions)on A, as usual. - Call
enableChain(assetId, C, bytes32(0), "0x")directly on B's router, with no native value. This needs no message, so the dead route does not matter. - Holders on B send to C, then from C to A.
For a compliance asset, also make sure C's compliance contract allows these holders.
Next: monitor owner messages and handle failures.