---
name: omnisea
description: Create Omnisea OFTs from existing ERC-20 tokens or ONFTs from existing ERC-721 collections, resolve exact Omniasset identities, quote fees, and transfer holders' assets across supported EVM chains. Accept official asset links, origin-token-creator identities, origin chain plus assetId, or a registered OFT/adapter address plus source chain. Designed for Bankr and other wallet-enabled agents.
title: Omnisea agent skill
---

# Omnisea agent skill

Install or read the complete skill at [https://www.omnisea.io/skill.md](https://www.omnisea.io/skill.md). This Markdown is also rendered at [Omnisea documentation](https://www.omnisea.io/docs/agent-skill/).

```text
Install the Omnisea skill from https://www.omnisea.io/skill.md
```

This document includes the instructions, deployments and exact call signatures. Bankr signs through the user’s wallet; Omnisea does not need a hosted MCP server. Wallet capabilities and installed skills may differ by execution surface. Test read-only resolution and quotes after installing.

Create and transfer omnichain assets through the existing Omnisea contracts. Execute from the requesting user's verified wallet. Creation and holder transfers are separate operations; no wrapper or protocol deployment is needed.

## Inputs

**Creation:** require OFT or ONFT, origin chain, and origin token or collection address. Use the connected wallet as creator. This skill creates open-mode assets with `compliance = address(0)`; explain that open mode and creator identity are permanent. It does not launch a new underlying token or collection. Requests for custom compliance need a separate issuer workflow.

**Transfer:** accept exactly one of these identifiers:

1. An official HTTPS Omnisea asset URL:
   - `https://www.omnisea.io/en/tokens/{originChainId}/{originToken}/{creator}` for OFT.
   - `https://www.omnisea.io/en/collections/{originChainId}/{originCollection}/{creator}` for ONFT.
   - Accept only hostname `www.omnisea.io` or `omnisea.io`, with the exact `/en/tokens/...` or `/en/collections/...` path. An optional trailing slash is allowed. Reject credentials, explicit ports, query strings, fragments, encoded path segments, redirects, extra segments, and lookalike domains. Parse the URL; do not scrape its page or follow a shortened URL as identity authority.
2. OFT/ONFT + origin chain name or chain ID + origin asset address + creator wallet address.
3. Exact origin chain + a nonzero 32-byte `assetId`. Resolve its type against both origin routers; an optional explicit OFT/ONFT type disambiguates.
4. For OFT transfers: the exact deployed OFT or OFTAdapter address + explicit source chain. Verify it against that chain's Omnisea router using `assetIdOf(address)` and `getAsset(id)`, then resolve its canonical origin and creator. An original underlying ERC-20 address, unregistered contract, or generic LayerZero OFT does not qualify. On the origin, holdings and approval are in the adapter's underlying token, not an adapter ERC-20 balance.

Do not accept a symbol, name, X handle, bare underlying address, or a facade address without its source chain as the complete transfer identifier. Request one of the formats above. A creator is an on-chain wallet address, not necessarily an EOA and not necessarily the transferring user. Source-address shorthand is OFT-only in this version; ONFTs use the other three formats.

For every transfer also require **source chain**, **destination chain**, and **amount for OFT / token ID for ONFT**. Never infer source from the origin in the identifier. Resolve the recipient to the user's verified destination wallet by default, or validate the explicitly requested recipient. Do not assume wallet control on an unsupported destination.

## Workflow

1. Confirm the active wallet/account and its available execution chains. Read [Bankr execution](#bankr-execution) in Bankr. Protocol support and wallet support are different.
2. Use the pinned [deployments](#deployment-snapshot), verify RPC chain ID, router code and `localEid`, and resolve the exact identity using [resolution helpers](#resolve-an-exact-asset). An `assetId` is shared across all representations. Read it from chain; do not hash only the three URL fields.
3. For explicit creation, follow [creation](#create-an-oft-or-onft). If this wallet already created the asset, return its identity and page link. Do not send automatically.
4. For transfers, follow [OFT transfers](#transfer-oft) or [ONFT transfers](#transfer-onft). Verify source holdings, destination route/readiness, recipient, approvals and fresh quotes. Present source, destination, creator, amount/token ID, recipient, minimum received and native fee before signing within the user's authorization. Do not request authorization again if the session already covers this concrete operation and costs.
5. Execute approvals and sends sequentially through the user's wallet. Wait for successful approval receipts, then re-read and re-quote. Use exact approvals; never introduce a shared wrapper. Use the contract signatures and procedures in this document directly through the wallet tools; no local scripts or repository checkout are required.
6. Follow [delivery and recovery](#delivery-retry-and-restore). Return the source transaction, message GUID, canonical asset link, and honest delivery state. A source receipt is not destination completion. On an ambiguous submission, inspect the wallet history/receipt before another send.

## Input and lookup failures

| Condition | Response |
|---|---|
| Incomplete/malformed identity | Explain the accepted format and ask only for missing fields. |
| Valid identity, successful lookup returns no asset | Say no matching asset exists on that origin/type. Never create it as a side effect of a transfer. |
| RPC, decoding, or deployment verification failure | Say verification is unavailable; do not report nonexistent. |
| Wrong origin, conflicting URL/type, or multiple matching types | Explain the conflict and request a corrected identifier. |
| Source representation absent or insufficient balance/NFT ownership | Explain the source-chain issue; do not change source silently. |
| Route not ready | Explain the required owner enablement/sync; do not perform management actions under transfer authorization. |
| Wallet cannot execute on source | Report the limitation; do not substitute another account, chain, or bridge. |

## Examples

Placeholders below must be replaced with real addresses, amounts, or IDs. These are user requests, not instructions to transact now.

- `@bankrbot create an OFT on Omnisea for the ERC-20 at [token address] on Base.`
- `@bankrbot create an ONFT on Omnisea for the NFT collection at [collection address] on Ethereum.`
- `@bankrbot transfer 100 tokens from Base to Ethereum, to my wallet, using https://www.omnisea.io/en/tokens/8453/0xB2000000000000000000002fEb517dFeC7415344/0xc4556EF6d584592ef0181eBd107E96EE78501a27`
- `@bankrbot transfer 50 tokens from Arbitrum to Base, to my wallet, using [official Omnisea token page URL].`
- `@bankrbot transfer NFT #42 from Base to Ethereum, to my wallet, using [official Omnisea collection page URL].`
- `@bankrbot transfer 100 OFT tokens from Arbitrum to Base via Omnisea. Origin: Ethereum. Origin token: [address]. Creator: [wallet address]. Recipient: my wallet.`
- `@bankrbot transfer NFT #42 from Base to Arbitrum via Omnisea. Origin: Ethereum. Asset ID: [32-byte assetId]. Recipient: my wallet.`
- `@bankrbot transfer 25 tokens from Base to Ethereum to [recipient address] using [official Omnisea token page URL].`
- `@bankrbot send 100 [OFT address on Base] from Base to Ethereum via Omnisea, to my wallet.`
- `@bankrbot send 25 [OFTAdapter address on Base] from Base to Ethereum via Omnisea, to my wallet.`

For `bridge TOKEN to Base`, ask for an accepted asset identifier, source chain, and amount. For a valid URL with no matching origin asset, say: `No OFT/ONFT was found for this origin asset and creator. Check the link or ask separately to create one from your wallet.`

## Scope

Plain EVM cross-chain transfers only: empty compose/options/command fields, native LayerZero fees, and the user's own holdings. Creating assets does not require holding the underlying. Transferring does. Fee-on-transfer and rebasing ERC-20s are unsupported; contract code alone cannot detect them reliably. NFT metadata size and compliance can affect delivery, so use the live quote and simulation.

Owner helper signatures are documented in [management helpers](#contract-helpers-for-routes-and-owner-operations) to diagnose route/configuration issues. This skill's creation/transfer request does not authorize changing issuer fees, compliance, metadata, royalties or trusted chains. Do not deploy contracts, install a forwarding wallet, request private keys, or claim generic LayerZero OFTs are Omnisea assets.

## Resolve an exact asset

Use the expanded [signatures](#exact-contract-signatures). These are reads; no signing is required. Always distinguish EVM chain ID (wallet/RPC) from LayerZero EID (contract routes).

### Resolve an exact asset: Origin identity

For a URL or complete tuple, select the origin chain and router by type, then read:

```text
assetId = originRouter.assetOf(originAsset, creator)
asset = originRouter.getAsset(assetId)
```

Zero ID or `exists == false` after successful reads means absent. RPC failures, wrong RPC chain, missing router code, and ABI failures mean verification unavailable. They do not mean absent.

For origin chain + assetId without a type, read `getAsset(assetId)` on BOTH origin routers. Exactly one must return `exists == true`. If both do, require OFT/ONFT; if either read fails, type resolution is incomplete. Verify `originEid` equals the supplied origin chain's EID: a remote copy on the supplied chain is not proof it is the origin.

Verify the origin record's `creator` and `originToken` (OFT) / `originCollection` (ONFT) against the requested identity. These are bytes32-encoded addresses; require the high 12 bytes to be zero before converting to an EVM address. Verify `keccak256(descriptorOf(assetId)) == assetId` and the reverse `assetOf(originAsset, creator) == assetId`.

The asset ID is the hash of the full creation descriptor, including initial metadata, compliance mode and pre-enabled routes (and ONFT ownerVerified). It is NOT just a hash of origin + address + creator. It remains identical across all representations; changing fees or enabling later chains does not create a different asset ID. An ONFT asset ID identifies a collection's Omniasset, while tokenId identifies one NFT.

Return the canonical page with checksummed addresses:

```text
https://www.omnisea.io/en/tokens/{originChainId}/{originToken}/{creator}
https://www.omnisea.io/en/collections/{originChainId}/{originCollection}/{creator}
```

### Resolve an exact asset: Source token

Read `sourceRouter.getAsset(assetId)` on the explicitly supplied SOURCE chain. Require `exists`, matching origin EID/creator/original asset, matching descriptor hash, and deployed code at `asset.token`. Verify `sourceRouter.assetIdOf(asset.token) == assetId` and `asset.token.omnisea() == sourceRouter`.

`asset.token` is the call target for `quoteSend` and `send`: the adapter on the origin or the representation on a remote. `assetIdOf` maps a facade, not an arbitrary origin ERC-20/ERC-721, to its ID.

### Resolve an exact asset: OFT address plus source chain

For `send 100 [address] from Base to Ethereum`, treat the address as a candidate OFT/adapter on Base. Verify the source router first. Read `assetIdOf(address)`; zero means it is not a registered Omnisea facade. Do not search for another creator or auto-create an asset. Require `getAsset(id).exists`, its token equals the supplied address, deployed code, and facade `omnisea()` equals the pinned source router. Derive the origin chain from `originEid`, then verify the origin record, descriptor hash, creator, original token and reverse lookup there. Use that canonical identity for all subsequent operations. The transfer source must equal the address's lookup chain. This removes creator ambiguity without trusting the token's name or self-reported ID. This shorthand is OFT-only; ONFT identifiers remain URL, full tuple or origin + assetId.

The source token's `token()` gives the balance/approval contract: underlying ERC-20/ERC-721 on origin, itself on remotes. `approvalRequired()` is true on origin adapters, false on remote representations. Do not confuse the router with the spender.

### Resolve an exact asset: Destination and route readiness

On both source and destination, verify the pinned router, `localEid`, matching peer router bytes32, `peerVm == 1` (EVM), `routeFrozen`, and `routeFrozenAt + ROUTE_ACTIVATION_DELAY <= block.timestamp` for the relevant direction. Source `isChainEnabled(assetId, destinationEid)` must be true.

- If destination `getAsset(assetId).exists`, verify its descriptor/identity and `isChainEnabled(assetId, sourceEid)`.
- If absent, first arrival can deploy it. A successful source quote alone does not prove its incoming asset route will be accepted. Inspect source `preEnabledEids(assetId)`, the origin EID and destination route maturity. An absent destination can initially trust a source that is the origin or a pre-enabled chain, with a mature local route. Otherwise request creator-origin enablement/synchronization before transferring. Compliance-mode destinations require explicit configuration; do not assume first-arrival configuration exists.

`sourceRouter.assetPeer(assetId, destinationEid)` returns a bytes32 prediction when that source route is enabled. Zero can mean disabled/unknown/non-EVM; a nonzero result does NOT mean deployment or destination readiness. Read the destination registry and code. `predictOFT(id)` / `predictONFT(id)` and `predictAdapter(id)` predict local addresses only. Do not substitute the underlying address for an origin adapter.

For a third-chain hop between remotes, both representations use the SAME assetId. The URL remains anchored to the origin.

## Create an OFT or ONFT

Required request: type + origin chain + origin asset address. This skill's creation profile is open mode, `compliance = address(0)`. The wallet calling createAsset becomes permanent creator. No balance in the underlying is needed to create. No token approval is needed for creation.

1. Resolve the user's execution wallet and origin network. Read the pinned router's code/hash and localEid. Validate the origin address has code and is an ERC-20 (OFT) or ERC-721 (ONFT). For ONFT check ERC-165 `supportsInterface(0x80ac58cd)`; ERC-1155 is not supported. Reject Omnisea facades and the router vault as underlying.
2. Read `assetOf(originAsset, wallet)`. If nonzero, verify the record and return the existing page. Do not silently replace its creator or compliance mode. If creation races, read back after AssetAlreadyExists; it is not a reason to change identity.
3. Read token metadata or collection name/symbol and simulate creation from this wallet. For ERC-20, establish plain-transfer behavior from reliable token documentation/source: fee-on-transfer and rebasing tokens are unsupported. Metadata reads alone cannot establish compatibility. If uncertain, say so and resolve compatibility before execution.
4. Explain origin, asset, creator and permanent open mode. The creator may later manage fees and enable chains; open mode cannot later become compliance mode. Review active initial routes with `lens.activePeerEids(router)`; the creation-time route set is what the transaction registers.
5. Call the selected origin router, from the user's wallet, with native value zero:

```solidity
createAsset(address originAsset, address compliance)
// compliance = 0x0000000000000000000000000000000000000000
```

6. Wait for a successful transaction receipt. Decode `AssetCreated` ONLY from the expected router. Transactions do not expose Solidity return values as normal receipt fields. Cross-check `assetOf`, `getAsset`, deployed adapter and descriptor hash. Return type, assetId, creator, adapter, canonical page URL and transaction hash.

The ONFT `ownerVerified` flag reflects whether the calling wallet matched the underlying collection's owner() at creation. Any wallet can otherwise register an asset; this flag grants no extra management power.

Creation deploys the origin adapter. Remote OFTs/ONFTs appear on first transfer or a creator's enabling sync. Do not promise a deployed token address on every remote just because prediction succeeds. A creation request does not authorize an immediate transfer.

## Transfer OFT

Resolve with [resolution.md](#resolve-an-exact-asset). Require explicit source, destination, positive decimal amount, and a verified recipient. Source and destination must differ. Use the wallet that owns the SOURCE holdings, not the creator's wallet. Reject zero recipient and destination router, vault or selected asset facade addresses.

Read source `getAsset(id).localDecimals` and `sharedDecimals`. Convert the human amount exactly into base units using strings/big integers. Reject more fractional digits than localDecimals instead of rounding. Source facade `token()` identifies the balance contract; require `balanceOf(wallet) >= amountLD`.

### Transfer OFT: Quote

Build the standard IOFT SendParam on the source facade:

```text
dstEid       = destination LayerZero EID (NOT EVM chainId)
to           = recipient address left-padded to bytes32
amountLD     = amount in source local units
minAmountLD  = 0 for initial discovery quote only
extraOptions = 0x
composeMsg   = 0x
oftCmd       = 0x
```

Call with `account/from = wallet`:

```text
(limit, feeDetails, receipt) = facade.quoteOFT(sendParam)
sendParam.minAmountLD = receipt.amountReceivedLD
fee = facade.quoteSend(sendParam, false)
```

For this plain-transfer profile, default minimum received to the exact quoted received amount, or use an explicitly user-authorized minimum. Requote if this cannot be met; never silently loosen it. Reject zero output. Amount sent may be lower than requested because shared-decimal dust is retained; show actual sent, dust, owner token fee and received amount. `quoteSend` includes the flat asset fee and LayerZero delivery fee. Native execution gas is additional. Quotes do not prove compliance or future token behavior.

`fee.lzTokenFee` must be zero. `fee.nativeFee` is the source native fee in base units. There is no fixed bridging fee and no hardcoded destination gas; first deployment and configured destination gas floors affect the quote.

### Transfer OFT: Approval and send

If `approvalRequired()` is true, read `underlying.allowance(wallet, facade)`. If insufficient, approve this adapter for the requested amount. For an existing nonzero allowance, reset to zero if needed before setting the new exact amount; wait and read back each successful receipt. Never approve the router/vault or grant an unlimited amount. Remote OFTs require no approval to send their owner's balance.

After approval, refresh route checks, holdings and both quotes. Respect the accepted minimum and native fee budget. Simulate the exact send from the holder, then execute:

```text
facade.send(sendParam, fee, wallet)
msg.value = fee.nativeFee
```

The last parameter is the native-fee refund address. Refund it to the requesting wallet. The router requires `msg.value == fee.nativeFee`; do not attach an extra buffer without changing the fee tuple and verifying the wallet's cost policy. Prefer a fresh exact quote.

Use the expanded tuple signature in [signatures.md](#exact-contract-signatures). Do NOT call `Omnisea.send(from,...)` directly; only the registered facade can. Do not wrap the call in a generic multicall contract: that would become the recorded sender. Wallet-native batching requires separately verified support.

Record source receipt, `TransferSent.guid`/`OFTSent.guid`, id and expected destination. Follow [status.md](#delivery-retry-and-restore). Do not resubmit an already successful source send while delivery is pending.

## Transfer ONFT

Resolve with [resolution.md](#resolve-an-exact-asset). Require explicit source and destination chains, a base-10 uint256 tokenId (zero is valid), and a verified recipient. Source and destination must differ. Reject zero recipient and destination router, vault or selected asset facade addresses.

Read source facade `token()` to locate the underlying collection on origin or representation on a remote. Require `ownerOf(tokenId) == requestingWallet`. An approved NFT operator cannot send the holder's NFT as itself: the facade forwards its caller as the sender.

### Transfer ONFT: Quote

Build the IONFT721 SendParam:

```text
dstEid       = destination LayerZero EID
to           = recipient left-padded to bytes32
tokenId      = bigint parsed without floating point
extraOptions = 0x
composeMsg   = 0x
onftCmd      = 0x
```

From the holder account read `facade.quoteSend(sendParam, false)`. It returns `(nativeFee,lzTokenFee)`; require lzTokenFee zero. This covers the flat asset fee plus delivery, including first deployment and the actual metadata payload. Source execution gas is extra. Do not reuse an OFT SendParam or invent an amount/minAmountLD for NFTs. There is no quoteOFT equivalent needed for ONFT.

Current ONFT messages carry full token URI, contract URI and royalty snapshots. Long metadata affects fees/gas and route limits; unreadable metadata can make the quote fail. Do not truncate metadata to force a quote.

### Transfer ONFT: Approval and send

On origin, if `approvalRequired()` is true, read `getApproved(tokenId)` and `isApprovedForAll(wallet, facade)` on the underlying collection. If neither authorizes the adapter, submit `collection.approve(facade, tokenId)`. Prefer this token-specific approval over setApprovalForAll. Wait for success and read it back.

On a remote, the holder calls the ONFT representation directly; no approval is needed. Refresh ownerOf, route readiness and quote after any approval. Simulate and execute from the owner:

```text
facade.send(sendParam, fee, wallet)
msg.value = fee.nativeFee
```

Respect the accepted native fee budget and refund address. Use [signatures.md](#exact-contract-signatures) for tuple expansion. Do not call OmniseaNFT.send directly or forward through a shared wrapper.

Record source receipt and `TransferSent.guid`/`ONFTSent.guid`. Verify destination `TransferReceived` for the same message/id/tokenId/recipient and the resulting owner. A return to origin releases the original NFT; other destinations receive its representation. Follow [status.md](#delivery-retry-and-restore).

## Deployment snapshot

Snapshot: 2026-10-09. Exported from confirmed local release manifests, not a guarantee of current route readiness or Bankr support. Recheck RPC chain ID, router bytecode hash, localEid, peers and live asset routes before execution. Unknown chains are unsupported by this skill version. No secret RPC URLs are bundled.

| Chain | EVM chain ID | LayerZero EID | Native fee unit (18 decimals) |
|---|---|---|---|
| Ethereum | 1 | 30101 | ETH |
| Base | 8453 | 30184 | ETH |
| Arbitrum | 42161 | 30110 | ETH |
| Optimism | 10 | 30111 | ETH |
| Polygon | 137 | 30109 | POL |
| BNB Chain | 56 | 30102 | BNB |
| Robinhood Chain | 4663 | 30416 | ETH |
| Ink | 57073 | 30339 | ETH |
| Abstract | 2741 | 30324 | ETH |
| Arc | 5042 | 30417 | USDC |

| Type | Router | Lens |
|---|---|---|
| OFT | 0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89 | 0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29 |
| ONFT | 0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734 | 0xC6254fdC3F65Bd88F57657273353B2f9edD6353C |

These releases use the listed addresses on all ten chains. Per-chain addresses, router runtime hashes and public RPC defaults are in the deployment JSON below. Do not infer a future deployment from address equality. Arc transaction value is native 18-decimal USDC, not an ERC-20 approval.

Exact readable signatures are in [signatures.md](#exact-contract-signatures). Snapshot sources are the core-v1-abstract-20261008, core-v1-arc-20261008 and onft-v1-arc-abstract-20261008 release manifests. The export reads public contract/release data only. It does not copy deployment signers, environment variables, private keys or operator scripts.

## Bankr execution

Use the requesting user's active Bankr wallet, not a deployer, shared wrapper or the Omniasset creator's wallet. Resolve the authenticated account through Bankr's wallet tools (`bankr whoami` or `GET /wallet/me` in an API integration). That endpoint returns wallet addresses and linked social accounts. A username mentioned as creator is not an accepted asset identifier. [Wallet identity](https://docs.bankr.bot/wallet-api/wallet-info/)

Read and encode calls with the [expanded signatures](#exact-contract-signatures). Use Bankr's existing contract-call tools when no script runtime is available. Installing supporting files does not establish that Node or wallet batching is available. [Skill installation](https://docs.bankr.bot/skills/in-bankr/from-github/)

### Bankr execution: Value units

Record both native base units and formatted whole units when quoting. Omnisea quote values are base units. Bankr's chat contract-call interface currently describes attached native value in WHOLE units, whereas the Wallet API expects base units. State the amount and native currency explicitly in chat, inspect the resulting transaction and ensure its on-chain value equals fee.nativeFee. Never paste a wei integer into a whole-unit input. Arbitrary calls need native gas funding; they are not gas-sponsored. [Bankr arbitrary transactions](https://raw.githubusercontent.com/BankrBot/skills/main/bankr/references/arbitrary-transaction.md)

### Bankr execution: API execution (when already configured)

The current Wallet API shape is:

```json
{
  "transaction": {
    "to": "<verified facade or creation router>",
    "chainId": 8453,
    "data": "<ABI-encoded call>",
    "value": "<native base-unit integer string>"
  },
  "description": "Omnisea transfer",
  "waitForConfirmation": true
}
```

Submit through `POST /wallet/submit` only with the user's existing authorized wallet integration. Do not ask the user to paste API keys into chat or store them in this skill. A failure response containing a transaction hash may already have broadcast. This endpoint has no idempotency key: inspect that transaction before another attempt. [Submit reference](https://raw.githubusercontent.com/BankrBot/skills/main/bankr/references/sign-submit-api.md)

### Bankr execution: Capability boundary

Check current Bankr support for the source chain before approving or sending. At the 2026-10-09 documentation check, its raw-transaction list overlaps this skill on Ethereum, Base, Polygon, Arbitrum, BNB Chain, Robinhood Chain and Arc. Optimism, Ink and Abstract appear in Omnisea's deployment snapshot but not that Bankr list. Do not claim Bankr execution there without a newer supported capability. A destination also requires a verified controlled recipient; having a protocol route alone does not establish wallet access. [Supported execution chains](https://raw.githubusercontent.com/BankrBot/skills/main/bankr/references/arbitrary-transaction.md)

Follow Bankr's active wallet authorization, security settings and spend limits. Do not change those settings to bypass a rejection. Use separate creation/approval/send calls unless the actual wallet integration demonstrably supports batching while preserving the wallet as msg.sender. A social request or successful skill install alone does not prove a specific transaction was executed.

## Delivery, retry and restore

Save the source chain, transaction hash, source facade/router, assetId, destination EID, recipient, amount/tokenId and message GUID when sending. Read logs only from the expected contracts and match all fields.

1. A reverted source receipt means the send did not succeed. An unknown receipt means submission is uncertain; inspect history/nonce/receipt rather than resubmit.
2. A successful source receipt plus TransferSent confirms source submission, not delivery. Report `submitted; destination pending` until destination evidence exists.
3. On destination, query the expected router's `TransferReceived` logs for the GUID, confirm assetId/recipient/amount or tokenId and `restored == false`, and obtain the destination transaction receipt. OFTReceived/ONFTReceived on the verified facade provide additional evidence. Check resulting holdings as corroboration; a balance alone is not proof of this message and can change through other activity.
4. Query `failedMessages(guid)` on destination if delivery stalls. Nonzero messageHash records a router-handled failure. Zero does NOT prove success: the endpoint may not have delivered the message, it may have failed before the router handler, or it may have been cleared by recovery.
5. Use wallet/indexer or LayerZero message tracking for diagnosis if available. Do not invent a delivered status from elapsed time. Bound polling and return a pending status when evidence is unavailable.

### Delivery, retry and restore: Helpers

Both lenses expose `restoreMessageOf(message)` and `quoteRestore(router,guid,message,extraOptions)`. Both routers expose `retry(guid,message)` and payable `restore(guid,message,extraOptions)`; read their ABI exactly. The original message bytes must match the recorded messageHash. Do not construct them from a guessed amount or URI.

Restore is called on the failed destination and sends a new message returning assets to their original sender on the source. During the grace period only the named restorer (or default original sender) can initiate it; afterwards the protocol allows anyone. The receiver of restored assets never becomes the person calling restore merely because they paid for it. In this skill, direct wallet sends keep the user as original sender.

A retry of the same GUID is not a new transfer. Recovery may involve new native fees; diagnose first and follow the user's actual recovery authorization. Never replay the source send to fix a delivery failure. Endpoint-level failures may require a different LayerZero recovery path; do not claim router.retry fixes every pending message.

## Contract helpers for routes and owner operations

These helpers support diagnosis and separately requested creator actions. Holding a token does not grant creator rights. All actions below must use the wallet whose address matches the asset's creator, except public read helpers and documented recovery operations. The protocol creator is fixed and facade transferOwnership always reverts.

### Contract helpers for routes and owner operations: Shared resolving reads

| Target | Read | Meaning |
|---|---|---|
| Router | assetOf(originAsset, creator) | Origin lookup only. |
| Router | getAsset(assetId) | Local record, including facade and configuration. |
| Router | assetIdOf(facade) | Reverse lookup for a registered facade. |
| Router | descriptorOf(assetId) | Canonical descriptor whose hash is assetId. |
| Router | assetPeer(assetId, eid) | Conditional remote address prediction; not proof of deployment. |
| Router | isChainEnabled(assetId, eid) | Asset trust plus local route maturity. |
| Router | preEnabledEids / enabledEids | Initial descriptor routes / explicit enablements; not substitutes for isChainEnabled. |
| Router | descriptorSent(assetId, eid) | Source descriptor state, not destination delivery confirmation. |
| Router | routeFrozen, routeFrozenAt, peers, peerVm | Protocol route checks. |
| Lens | activePeerEids(router) | Routes a new open-mode asset would pre-enable now. |

### Contract helpers for routes and owner operations: Enabling and synchronization

On the origin, `enableChain(id,eid,remoteCompliance,extraOptions)` enables and sends a sync to that chain. Open-mode remoteCompliance is zero bytes32. Existing compliance assets require their destination compliance contract. After origin enablement, other existing remotes may need origin `syncAsset` to learn/confirm the expanded set.

On a remote, enableChain is a LOCAL owner opt-in only, with zero remoteCompliance and zero native value. It does not send a message. Direct local management requires the creator to control the same address on that chain. Do not assume cross-chain smart-account control.

| Operation on origin | OFT Lens | ONFT Lens |
|---|---|---|
| enableChain | quoteSync(router,id,eid,remoteCompliance,options) | quoteEnableChain(router,id,eid,remoteCompliance,options) |
| syncAsset | quoteSync(router,id,eid,zeroBytes32,options) | quoteSync(router,id,eid,zeroBytes32,options) |
| setRemoteCompliance | quoteSync(router,id,eid,remoteCompliance,options) | quoteSync(router,id,eid,remoteCompliance,options) |

The OFT lens's quoteSync builds an enabled list including eid; the ONFT lens distinguishes enabling from plain sync. Use a fresh quote and simulate the actual operation. A quote does not grant permission or enable a route. Protocol-enforced route maturity still applies.

### Contract helpers for routes and owner operations: Fee and metadata helpers

- OFT: local `setFees(id,flatFee,feeBps)` and `claimFees(id,recipient)`; origin `setRemoteFees` and `withdrawRemoteFees` have corresponding lens `quoteSetRemoteFees` / `quoteWithdrawRemoteFees`.
- ONFT: local `setFees(id,flatFee)` and `claimFees`; origin uses `sendOwnerAction`. NFTLens `encodeSetFees`, `encodeWithdrawFees`, `encodeSetTokenURI`, `encodeSetContractURI`, `encodeSetRoyalty`, `encodeSetTokenRoyalty`, `encodeClaimRoyalties` return `(op,opData)`. Pass them to `quoteOwnerAction(router,id,eid,op,opData,options)` and then `sendOwnerAction`.
- Both: setCompliance only works for assets already created in compliance mode. It cannot convert an open-mode asset.

These methods and output layouts are in [signatures.md](#exact-contract-signatures). Never infer selector/tuple layout from a method name alone.

## Exact contract signatures

Generated from the verified contract ABI snapshot. Tuple fields are expanded for Bankr function-call tools. Use the deployed address in the machine-readable deployment registry below. Router send/quote methods are intentionally excluded: holders call the asset facade.

### Exact contract signatures: Omnisea

```solidity
function ROUTE_ACTIVATION_DELAY() external view returns (uint64);
function assetIdOf(address token) external view returns (bytes32);
function assetOf(address originToken,address creator) external view returns (bytes32);
function assetPeer(bytes32 assetId,uint32 eid) external view returns (bytes32);
function claimFees(bytes32 assetId,address recipient) external;
function createAsset(address originToken,address compliance) external returns (bytes32 assetId,address token);
function descriptorOf(bytes32 assetId) external view returns (bytes);
function descriptorSent(bytes32 assetId,uint32 eid) external view returns (bool);
function enableChain(bytes32 assetId,uint32 eid,bytes32 remoteCompliance,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function enabledEids(bytes32 assetId) external view returns (uint32[]);
function failedMessages(bytes32 guid) external view returns (bytes32 messageHash,uint32 srcEid,uint64 nonce,uint64 failedAt);
function getAsset(bytes32 assetId) external view returns ((address token,uint32 originEid,uint8 localDecimals,uint8 sharedDecimals,bool complianceMode,bool exists,uint16 feeBps,address underlying,address compliance,uint128 flatFee,bytes32 creator,bytes32 originToken));
function isChainEnabled(bytes32 assetId,uint32 eid) external view returns (bool);
function localEid() external view returns (uint32);
function ownerNativeFees(bytes32 assetId) external view returns (uint256);
function ownerTokenFees(bytes32 assetId) external view returns (uint256);
function peerEids() external view returns (uint32[]);
function peerVm(uint32 eid) external view returns (uint8 vm);
function peers(uint32 eid) external view returns (bytes32 peer);
function preEnabledEids(bytes32 assetId) external view returns (uint32[]);
function predictAdapter(bytes32 assetId) external view returns (address);
function predictOFT(bytes32 assetId) external view returns (address);
function restore(bytes32 guid,bytes message,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function retry(bytes32 guid,bytes message) external;
function routeFrozen(uint32 eid) external view returns (bool);
function routeFrozenAt(uint32 eid) external view returns (uint64);
function setCompliance(bytes32 assetId,address compliance) external;
function setFees(bytes32 assetId,uint128 flatFee,uint16 feeBps) external;
function setRemoteCompliance(bytes32 assetId,uint32 eid,bytes32 compliance,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
function setRemoteFees(bytes32 assetId,uint32 eid,uint128 flatFee,uint16 feeBps,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
function syncAsset(bytes32 assetId,uint32 eid,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
function vault() external view returns (address);
function withdrawRemoteFees(bytes32 assetId,uint32 eid,bytes32 recipient,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
```

### Exact contract signatures: OmniseaLens

```solidity
function activePeerEids(address omnisea) external view returns (uint32[] active);
function quoteRestore(address omnisea,bytes32 guid,bytes message,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteSetRemoteFees(address omnisea,bytes32 assetId,uint32 eid,uint128 flatFee,uint16 feeBps,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteSync(address omnisea,bytes32 assetId,uint32 eid,bytes32 remoteCompliance,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteWithdrawRemoteFees(address omnisea,bytes32 assetId,uint32 eid,bytes32 recipient,bytes extraOptions) external view returns (uint256 nativeFee);
function restoreMessageOf(bytes message) external pure returns (bytes restoreMessage,bytes32 sender,bytes32 restorer);
```

### Exact contract signatures: OFT

```solidity
function approvalRequired() external pure returns (bool);
function approve(address spender,uint256 value) external returns (bool);
function assetId() external view returns (bytes32);
function balanceOf(address account) external view returns (uint256);
function combineOptions(uint32 eid,uint16 msgType,bytes extraOptions) external view returns (bytes);
function creator() external view returns (bytes32);
function decimalConversionRate() external view returns (uint256);
function decimals() external view returns (uint8);
function enforcedOptions(uint32 eid,uint16 msgType) external view returns (bytes);
function name() external view returns (string);
function omnisea() external view returns (address);
function owner() external view returns (address);
function peers(uint32 eid) external view returns (bytes32);
function quoteOFT((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam) external view returns ((uint256 minAmountLD,uint256 maxAmountLD),(int256 feeAmountLD,string description)[],(uint256 amountSentLD,uint256 amountReceivedLD));
function quoteSend((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam,bool payInLzToken) external view returns ((uint256 nativeFee,uint256 lzTokenFee));
function send((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam,(uint256 nativeFee,uint256 lzTokenFee) fee,address refundAddress) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt,(uint256 amountSentLD,uint256 amountReceivedLD) oftReceipt);
function sharedDecimals() external view returns (uint8);
function symbol() external view returns (string);
function token() external view returns (address);
```

### Exact contract signatures: OFTAdapter

```solidity
function approvalRequired() external pure returns (bool);
function assetId() external view returns (bytes32);
function combineOptions(uint32 eid,uint16 msgType,bytes extraOptions) external view returns (bytes);
function creator() external view returns (bytes32);
function decimalConversionRate() external view returns (uint256);
function decimals() external view returns (uint8);
function enforcedOptions(uint32 eid,uint16 msgType) external view returns (bytes);
function name() external view returns (string);
function omnisea() external view returns (address);
function owner() external view returns (address);
function peers(uint32 eid) external view returns (bytes32);
function quoteOFT((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam) external view returns ((uint256 minAmountLD,uint256 maxAmountLD),(int256 feeAmountLD,string description)[],(uint256 amountSentLD,uint256 amountReceivedLD));
function quoteSend((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam,bool payInLzToken) external view returns ((uint256 nativeFee,uint256 lzTokenFee));
function send((uint32 dstEid,bytes32 to,uint256 amountLD,uint256 minAmountLD,bytes extraOptions,bytes composeMsg,bytes oftCmd) sendParam,(uint256 nativeFee,uint256 lzTokenFee) fee,address refundAddress) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt,(uint256 amountSentLD,uint256 amountReceivedLD) oftReceipt);
function sharedDecimals() external view returns (uint8);
function symbol() external view returns (string);
function token() external view returns (address);
```

### Exact contract signatures: OmniseaNFT

```solidity
function ROUTE_ACTIVATION_DELAY() external view returns (uint64);
function assetIdOf(address token) external view returns (bytes32);
function assetOf(address collection,address creator) external view returns (bytes32);
function assetPeer(bytes32 assetId,uint32 eid) external view returns (bytes32);
function claimFees(bytes32 assetId,address recipient) external;
function claimRoyalties(bytes32 assetId,address royaltyToken,address recipient) external;
function createAsset(address collection,address compliance) external returns (bytes32 assetId,address token);
function descriptorOf(bytes32 assetId) external view returns (bytes);
function descriptorSent(bytes32 assetId,uint32 eid) external view returns (bool);
function enableChain(bytes32 assetId,uint32 eid,bytes32 remoteCompliance,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function enabledEids(bytes32 assetId) external view returns (uint32[]);
function failedMessages(bytes32 guid) external view returns (bytes32 messageHash,uint32 srcEid,uint64 nonce,uint64 failedAt);
function getAsset(bytes32 assetId) external view returns ((address token,uint32 originEid,bool complianceMode,bool ownerVerified,bool exists,address collection,address compliance,uint128 flatFee,bytes32 creator,bytes32 originCollection));
function isChainEnabled(bytes32 assetId,uint32 eid) external view returns (bool);
function localEid() external view returns (uint32);
function originContractURI(bytes32 assetId) external view returns (bytes);
function ownerNativeFees(bytes32 assetId) external view returns (uint256);
function peerEids() external view returns (uint32[]);
function peerVm(uint32 eid) external view returns (uint8 vm);
function peers(uint32 eid) external view returns (bytes32 peer);
function preEnabledEids(bytes32 assetId) external view returns (uint32[]);
function predictAdapter(bytes32 assetId) external view returns (address);
function predictONFT(bytes32 assetId) external view returns (address);
function restore(bytes32 guid,bytes message,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function retry(bytes32 guid,bytes message) external;
function routeFrozen(uint32 eid) external view returns (bool);
function routeFrozenAt(uint32 eid) external view returns (uint64);
function sendOwnerAction(bytes32 assetId,uint32 eid,uint8 op,bytes opData,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function setCompliance(bytes32 assetId,address compliance) external;
function setContractURI(bytes32 assetId,string uri) external;
function setFees(bytes32 assetId,uint128 flatFee) external;
function setRemoteCompliance(bytes32 assetId,uint32 eid,bytes32 compliance,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
function setRoyalty(bytes32 assetId,uint16 bps) external;
function setTokenRoyalty(bytes32 assetId,uint256 tokenId,uint16 bps) external;
function setTokenURI(bytes32 assetId,uint256 tokenId,string uri) external;
function syncAsset(bytes32 assetId,uint32 eid,bytes extraOptions) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee));
function vault() external view returns (address);
```

### Exact contract signatures: OmniseaNFTLens

```solidity
function activePeerEids(address omnisea) external view returns (uint32[] active);
function encodeClaimRoyalties(bytes32 token,bytes32 recipient) external pure returns (uint8 op,bytes opData);
function encodeSetContractURI(string uri) external pure returns (uint8 op,bytes opData);
function encodeSetFees(uint128 flatFee) external pure returns (uint8 op,bytes opData);
function encodeSetRoyalty(uint16 bps) external pure returns (uint8 op,bytes opData);
function encodeSetTokenRoyalty(uint256 tokenId,uint16 bps) external pure returns (uint8 op,bytes opData);
function encodeSetTokenURI(uint256 tokenId,string uri) external pure returns (uint8 op,bytes opData);
function encodeWithdrawFees(bytes32 recipient) external pure returns (uint8 op,bytes opData);
function quoteEnableChain(address omnisea,bytes32 assetId,uint32 eid,bytes32 remoteCompliance,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteOwnerAction(address omnisea,bytes32 assetId,uint32 eid,uint8 op,bytes opData,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteRestore(address omnisea,bytes32 guid,bytes message,bytes extraOptions) external view returns (uint256 nativeFee);
function quoteSync(address omnisea,bytes32 assetId,uint32 eid,bytes32 compliance,bytes extraOptions) external view returns (uint256 nativeFee);
function restoreMessageOf(bytes message) external pure returns (bytes restoreMessage,bytes32 sender,bytes32 restorer);
```

### Exact contract signatures: ONFT721

```solidity
function approvalRequired() external pure returns (bool);
function approve(address to,uint256 tokenId) external;
function assetId() external view returns (bytes32);
function balanceOf(address owner) external view returns (uint256);
function combineOptions(uint32 eid,uint16 msgType,bytes extraOptions) external view returns (bytes);
function creator() external view returns (bytes32);
function enforcedOptions(uint32 eid,uint16 msgType) external view returns (bytes);
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner,address operator) external view returns (bool);
function name() external view returns (string);
function omnisea() external view returns (address);
function owner() external view returns (address);
function ownerOf(uint256 tokenId) external view returns (address);
function peers(uint32 eid) external view returns (bytes32);
function quoteSend((uint32 dstEid,bytes32 to,uint256 tokenId,bytes extraOptions,bytes composeMsg,bytes onftCmd) sendParam,bool payInLzToken) external view returns ((uint256 nativeFee,uint256 lzTokenFee));
function send((uint32 dstEid,bytes32 to,uint256 tokenId,bytes extraOptions,bytes composeMsg,bytes onftCmd) sendParam,(uint256 nativeFee,uint256 lzTokenFee) fee,address refundAddress) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
function symbol() external view returns (string);
function token() external view returns (address);
function tokenURI(uint256 tokenId) external view returns (string);
```

### Exact contract signatures: ONFT721Adapter

```solidity
function approvalRequired() external pure returns (bool);
function assetId() external view returns (bytes32);
function combineOptions(uint32 eid,uint16 msgType,bytes extraOptions) external view returns (bytes);
function creator() external view returns (bytes32);
function enforcedOptions(uint32 eid,uint16 msgType) external view returns (bytes);
function name() external view returns (string);
function omnisea() external view returns (address);
function owner() external view returns (address);
function peers(uint32 eid) external view returns (bytes32);
function quoteSend((uint32 dstEid,bytes32 to,uint256 tokenId,bytes extraOptions,bytes composeMsg,bytes onftCmd) sendParam,bool payInLzToken) external view returns ((uint256 nativeFee,uint256 lzTokenFee));
function send((uint32 dstEid,bytes32 to,uint256 tokenId,bytes extraOptions,bytes composeMsg,bytes onftCmd) sendParam,(uint256 nativeFee,uint256 lzTokenFee) fee,address refundAddress) external payable returns ((bytes32 guid,uint64 nonce,(uint256 nativeFee,uint256 lzTokenFee) fee) receipt);
function symbol() external view returns (string);
function token() external view returns (address);
```

## Machine-readable deployment registry

The runtime hashes are keccak256 of deployed router code. Verify eth_chainId and eth_getCode against this snapshot before contract calls. Public RPC defaults may be replaced by a trusted provider for the same chain. This snapshot is dated independently from the instructions; do not infer support for unlisted chains.

```json
{
  "snapshotDate": "2026-10-09",
  "networks": [
    {
      "name": "Ethereum",
      "slug": "ethereum",
      "chainId": 1,
      "eid": 30101,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://ethereum-rpc.publicnode.com",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0x2989b7ba154d8e3303cac1c728ac54f44de8b6666ec6b9210e8458e5a4519bdc"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xba3f2c3619a40a237378706922047dc9fce2ef1fbaa8850ac60fec4820f7a764"
      }
    },
    {
      "name": "Base",
      "slug": "base",
      "chainId": 8453,
      "eid": 30184,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://mainnet.base.org",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0x30af477ed9d319202baf6e62927e84f6ed0a81f03819c8d11d2272faacac0dd0"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0x804a2fe842feb4558fe5ccc2d4ea76caf0fcaadd33fe9e24c179b4dcca40f875"
      }
    },
    {
      "name": "Arbitrum",
      "slug": "arbitrum",
      "chainId": 42161,
      "eid": 30110,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://arb1.arbitrum.io/rpc",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0xf926712fe6085d0e99d3037cb150af3284fa3ed4c2a052faa67f6eacde4786f2"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xea0fab8aa331b7ff23c27dd061a3b88081de7414e6876edbc273b9c6baea0dd8"
      }
    },
    {
      "name": "Optimism",
      "slug": "optimism",
      "chainId": 10,
      "eid": 30111,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://mainnet.optimism.io",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0x6e4871e4556758c4eded758663bc84c6796bb2dbc60de8384594600efcc6badf"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xe0d16f885c0ca5398198adf2cb6d6b62f24b0846af02f7a168d00baa7cf4316a"
      }
    },
    {
      "name": "Polygon",
      "slug": "polygon",
      "chainId": 137,
      "eid": 30109,
      "nativeSymbol": "POL",
      "nativeDecimals": 18,
      "rpc": "https://polygon.drpc.org",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0xf8868ecda5ac66794952b318e54910c89338e30e9f700ebbf4a83aa4c13759ec"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xb82dae946b04ca0b86142cb4fe467cff0df9f4cd41830fb8511f9e50e6c1c895"
      }
    },
    {
      "name": "BNB Chain",
      "slug": "bnb",
      "chainId": 56,
      "eid": 30102,
      "nativeSymbol": "BNB",
      "nativeDecimals": 18,
      "rpc": "https://bsc-dataseed.binance.org",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0x2cfef983c6e7d13682ee47cde430f549d9c9d219f4e8fff38f39450c92fae250"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xe8abc42b9a1aa22772a71565e0ffa371be67f4725e080fb16cec514ebfd1e601"
      }
    },
    {
      "name": "Robinhood Chain",
      "slug": "robinhood",
      "chainId": 4663,
      "eid": 30416,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://rpc.mainnet.chain.robinhood.com",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0xae2dc3d082d3a14bba592403981eefab64f0119eeea47ea07ace29527dc966a3"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xf7dae51f5f02216c54ed34f4e6ef8ad01f56e21d8618676676641b281b976677"
      }
    },
    {
      "name": "Ink",
      "slug": "ink",
      "chainId": 57073,
      "eid": 30339,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://rpc-gel.inkonchain.com",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0xc9af3cb2485a75f594846110661e97c400d723887a996d650fa9f66720e0a4c5"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0x5f40ab6fd0474b970f22c39f1519748d9f04f1ca75bb88326e3e9b5c4ba2f8e1"
      }
    },
    {
      "name": "Abstract",
      "slug": "abstract",
      "chainId": 2741,
      "eid": 30324,
      "nativeSymbol": "ETH",
      "nativeDecimals": 18,
      "rpc": "https://api.mainnet.abs.xyz",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0x77a63a4c32f3faa267bb747a6541587b4303fec117a0fcf15b70e3e5b864b536"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0x35ef206b2511e6bd11cdf650f37640e07e0d8e1234db632823359d6c2242a077"
      }
    },
    {
      "name": "Arc",
      "slug": "arc",
      "chainId": 5042,
      "eid": 30417,
      "nativeSymbol": "USDC",
      "nativeDecimals": 18,
      "rpc": "https://rpc.mainnet.arc.io",
      "OFT": {
        "router": "0x96c756BAEFf18BC1A7871b2F5d4173E958be2d89",
        "lens": "0x31f4D19cE904b523abFe78ECD4Cd0F1150403a29",
        "runtimeHash": "0xf115d3053f30ff0f0381e6695cfbee8f28f4eadbd5f6a319372b426330ae9dd9"
      },
      "ONFT": {
        "router": "0x4ee0a410385c7b9A0746D2De0F24aa004ecC0734",
        "lens": "0xC6254fdC3F65Bd88F57657273353B2f9edD6353C",
        "runtimeHash": "0xd48192cc5fd5a95498561132b77310b4109afa8df5f3d220da260baf1aa8c350"
      }
    }
  ]
}
```

## Standard token reads and transaction evidence

Use these underlying-token functions as appropriate. For an origin adapter, token() returns the underlying token/collection. For a remote representation it returns itself.

```solidity
function balanceOf(address account) external view returns (uint256);
function allowance(address owner,address spender) external view returns (uint256);
function approve(address spender,uint256 amountOrTokenId) external returns (bool);
function ownerOf(uint256 tokenId) external view returns (address);
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner,address operator) external view returns (bool);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
```

The approve return value above is ERC-20-specific; ERC-721 approve(address,uint256) has no output. Some ERC-20s also omit a bool return: use their verified ABI and confirm allowance after the receipt. Never assume approval succeeded from a transaction hash alone.

### Router receipt event ABIs

Decode only logs from the verified router on the relevant chain.

```json
{
  "Omnisea": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "originToken",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "creator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "complianceMode",
          "type": "bool"
        },
        {
          "indexed": false,
          "internalType": "uint32[]",
          "name": "preEnabledEids",
          "type": "uint32[]"
        }
      ],
      "name": "AssetCreated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "bytes4",
          "name": "reason",
          "type": "bytes4"
        }
      ],
      "name": "MessageFailed",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "restoreGuid",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }
      ],
      "name": "MessageRestored",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }
      ],
      "name": "MessageRetried",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "from",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "restored",
          "type": "bool"
        }
      ],
      "name": "TransferReceived",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amountSentLD",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amountReceivedLD",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "flatFee",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "feeLD",
          "type": "uint256"
        }
      ],
      "name": "TransferSent",
      "type": "event"
    }
  ],
  "OmniseaNFT": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "collection",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "creator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "complianceMode",
          "type": "bool"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "ownerVerified",
          "type": "bool"
        },
        {
          "indexed": false,
          "internalType": "uint32[]",
          "name": "preEnabledEids",
          "type": "uint32[]"
        }
      ],
      "name": "AssetCreated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "bytes4",
          "name": "reason",
          "type": "bytes4"
        }
      ],
      "name": "MessageFailed",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "restoreGuid",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }
      ],
      "name": "MessageRestored",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }
      ],
      "name": "MessageRetried",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "from",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "tokenId",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bool",
          "name": "restored",
          "type": "bool"
        }
      ],
      "name": "TransferReceived",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "assetId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "tokenId",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "flatFee",
          "type": "uint256"
        }
      ],
      "name": "TransferSent",
      "type": "event"
    }
  ]
}
```
