# OmniToken Tech Spec ## Overview OmniToken is specification for bringing assets issued on NEAR to all chains using Chain Signatures. Technically this is simlar to Rainbow Bridge Token Connector design, expanding to supporting Chain Signature and other bridges as communication protocols. | Contract | VM | Description | | - | - | - | | omni-token-connector | NEAR | Omni token connector that locks/unlocks funds | | your-token | NEAR | any token supporting NEP-141 | | erc-20 | EVM | Extended erc20 to allow multichain transfer | | spl | Solana | Extending spl22? | ## Context Historically Rainbow Bridge only supported going between NEAR and Ethereum. This design proposes to expand Rainbow Bridge to OmniBridge: supporting bridging NEAR tokens to any chain supported by Chain Signatures and some cross-chain messaging protocol. As part of this proposal, we suggest to move Rainbow Bridge NEAR -> Ethereum to using this standard going forward. This offers a number of benefits: - No need for NEAR light client on Ethereum (cost reduction) - No need to wait for on Ethereum side for optimistic verification of light client (time reduction) ## Multichain transfer standard Address: `<chain-id>:<address>` Smart contract on NEAR maintains mapping between `<chain-id>` and specific way to resolve it to localize transaction. E.g. which VM it is and what is specific chain id in that VM (`eth:` is `EVM(1)`, `base:` is `EVM(8453)`, `near:` is `NEARVM`, `sol:` is `SVM`, etc) Relevant documents: - https://eips.ethereum.org/EIPS/eip-3770 - Vitalik's tweet mentioning unified addresses: https://x.com/VitalikButerin/status/1803459321423868298 ## Models There are two models we can support: - NEAR-first token - NEP-141 is the originating token - Foreign token - foreign token that gets first connected to NEAR - USDC - special case because of their [Bridged USDC Standard](https://www.circle.com/blog/bridged-usdc-standard) For new tokens contracts that support conditional mint/burn, this can enable supporting maintaing single total supply. ## Omni Token Connector ```rust! /// Smart contract handling locking NEAR tokens and emitting Chain Signatures to mint them on the other chain. /// If recevied request to withdraw, validates with a prover smart contract and struct OmniTokenConnector { prover_id: AccountId, signer_id: AccountId, chains: HashMap<String, ChainKind>, tokens: UnorderedMap<AccountId, Token>, } impl OmniTokenConnector { /// Adds chain alias. Only DAO. pub fn register_chain(&mut self, chain_alias: String, chain_kind: ChainKind); /// Register new token. /// Requires storage deposit to ensure Sybil resistance. pub fn register_token(&mut self, token_id: AccountId); /// Withdraw funds from connector to address provided in proof. /// Supports OmniAddress to withdrawing directly to another chain. pub fn withdraw(&mut self, chain_alias: String, proof: Vec<u8>); } impl OnFungibleTokenReceiveal for OmniTokenConnector { /// Handles ft_transfer_call from existing tokens to transfer to other chains. /// `msg` contains OmniAddress to which this tokens should be routed. fn ft_on_transfer(sender_id: AccountId, amount: Balance, msg: string) -> Balance } ``` Token information: ```rust! struct Token { /// Set of deployed chains. chains: HashSet<ChainKind>, /// Locked amount. Useful for checking internal consistency. locked_amount: Balance, } ``` ## OmniProver This is a prover that combines a number of different bridges to validate ```rust! struct BridgeMessage { token_id: OmniAddress, sender: OmniAddress, receiver: OmniAddress, amount: Balance, } enum BridgeKind { Raindbow = "rainbow.near", Wormhole = "wormhole.near", AuroraAxelar = "<address from aurora transport>", AuroraLayerZero = "<address from aurora transport>", MultisigOracle = "omni-token-oracle", } /// OmniProver serves a single end point to validate a proof from another chain across a vareity of different bridges. struct OmniProver { chains: UnorderedMap<ChainKind, BridgeKind>, } impl OmniProver { pub fn set_bridge pub fn validate_proof(self, chain_kind: ChainKind, message: bytes[]) -> Result<BridgeMessage, BridgeError>; } ``` ## Tooling Indexer Explorer ### Analytics What kind of analytics do we need? - Per token show all chains it's available on. NEAR chain > EVM NEAR chain > Solana EVM > NEAR chain Solana > NEAR chain EVM > EVM X chain to X chain Auction process: - anyone can submit a price -- interface needs to have max amount of fees - team will launch a relayer - or user can take an action or automatic cancel fee oracle in contract from market makers # Integrations & Support We need to make sure: - Centralized exchanges are comfortable with these tokens - Wallet and exchanges supporting OmniToken API - ## Appendix High level decisions: Decision 1: - using Rainbow’s design for TokenConnector to send/receive funds on another chain. (See implementation - https://github.com/Near-One/rainbow-token-connector / https://github.com/Near-One/rainbow-token-connector/blob/master/token-locker/src/lib.rs) - Pros: supports existing NEAR tokens, supports existing Rainbow approach. we have already A frontend for this. We don't need to extend Ethereum/Solana contracts - they also just send it to the respective Connector contract on remote chain / calling a method that does `transferFrom`. Instead of passing NEAR address or EVM address we use `OmniAddress` - Cons: mostly positioning as OmniToken in this case vs reality as it's not modifying the token standard - Use Prover to aggregate a number of different bridge/message passing protocols. We are going to make it upgradable. - Pros: we can launch with Prover just supporting Eth light client state prover and extend with others later. If there are multiple chains - Cons: maintaining multiple bridges integrations can become expensive as they change things. For new chains there is no bridge. Decision 2: - add new methods to NEP-141/ERC-20 to allow to send multichain transfers: `ft_ca_transfer(receiver: OmniAddress, amount: Balance, max_fee: Balance)` and `erc3770_transfer(string _receiver, uint256 _value, uint256 _max_fee)` - these methods can call into TokenConnector or have implementation direclty in the contract (not recommended) - Pros: actually makes a feel of OmniToken. Can be working with Token Connector format in tandem. - Cons: doesn't work with existing tokens. Standard is hard to stabilize on our timeline. > Alternative - API interface instead of ABI. Can be a library that everyone can use that supports on different chains? - Pros: no changes to existing projecs - Cons: potentially hard to coordinate implementation but mitigatable with quality library ## Outdated ### NEAR interface We extend NEP-141 with ability to `ft_transfer` to an address on another chain. For backward compatibility with existing tokens, one can do `ft_transfer_call` to the `omni-token-factory` by calling a method that will transfer to another chain. ```rust /// Supports defaulting to NEAR when provided without ":" type OmniAddress = String; ... fn ft_transfer(account_id: OmniAddress) ``` Question: - do we want to burn the tokens on NEAR or just park them in the `omni-token-factory` -- this is compatible with Connector. ### EVM interface We extend ERC-20 standard with a new transfer method that supports `OmniAddress` to allow to transfer to other chains. ```solidity /// `_to` is OmniAddress in the format `<chain>:<address>`. function ft_transfer(string _to, uint256 _value) public returns (bool success) /// Receive funds across chains to a given address with given value. /// Can be only called by owner of the contract. function ft_on_transfer(string _to, uint256 _value) onlyOwner() return () ``` ### SVM interface ```rust TODO ``` ### Other Functionality: - creating contracts on other chains - holds EOAs of other chains that control mint/burn of those accounts via Chain Signatures - sends messages to EVM, SVM, etc to mint new tokens on request - register new omni token if it was created outside of factory scope - bridge aggregation to receive messages back from other chains - wraps existing NEAR tokens to make them omni tokens (e.g. handles `ft_transfer_call` to send to another chain) ```rust /// Chain kind of executing enum ChainKind { NEAR, EVM(chainId), SVM, } /// Standard for OmniAddresses, must be <chain>:<address> type OmniAddress = string; ``` Omni Token Factory serves as a bridge aggregator. Bridge aggregator supports next functionality: - Maintains list of all accessible read bridges on NEAR. - Able to parse specific bridge messages into specific instructions for tokens. Returns which token/network and who transferred to whom. ```rust struct BridgeMessage { token_id: OmniAddress, sender: OmniAddress, receiver: OmniAddress, amount: Balance, } enum BridgeKind { Raindbow = "rainbow.near", Wormhole = "wormhole.near", AuroraAxelar = "<address from aurora transport>", AuroraLayerZero = "<address from aurora transport>", MultisigOracle = "omni-token-oracle", } /// Serves as a bridge aggregator struct BridgeManager { } impl BridgeManager { fn fetch_state(self, bridge_kind: BridgeKind); fn parse_bridge_message(self, predecessor_id: AccountId, message: bytes[]) -> Result<BridgeMessage, BridgeError>; } ``` ```rust /// Information about the given token. struct TokenInfo { /// Owned is true when token was created by the factory. owned: bool, bridged_amount: Balance, /// Contians set of all networks where remote contracts have been deployed. networks: HashMap<ChainId, OmniAddress>, } struct OmniTokenFactory { bridge_manager: BridgeManager, tokens: LookupMap<AccountId, TokenInfo>, } impl OmniTokenFactory { /// Register existing token. fn register_token(self, token_id: AccountId); /// Add support for given network for provided token fn add_network(self, token_id: AccountId, chain_id: ChainId); /// Internal method to create transfer transaction and sending it to Chain Signatures. /// Validates that given token has added given network. fn internal_transfer(self, token_id: AccountId, receiver: OmniAddress, amount: Balance); /// TODO: Actually may be not worse doing and keep only on_ft_ /// Protected methods to only tokens that are created via factory. Predecessor must be registered token. /// If validated, calls `self.internal_transfer` fn on_ft_transfer(self, chain_id: ChainId, receiver: OmniAddress, amount: Balance); /// Gets called when bridged information arrives. /// Passes to BridgeManager for parsing. fn on_bridged(self, message: bytes[]); } impl OnFungibleTokenReceiveal for OmniTokenFactory { /// Handles ft_transfer_call from existing tokens to transfer to other chains. fn ft_on_transfer(sender_id: AccountId, amount: Balance, msg: string) -> Balance { /// Handles tokens which are not created by the factory. /// If validated, calls `self.internal_transfer()` based on the message parsed into OmniAddress of receiver. } } ``` ## Other https://docs.layerzero.network/v2/home/token-standards/oft-standard https://docs.wormhole.com/wormhole/native-token-transfers/overview