# 📝 IOTA Deep Links - TIP 1st Draft ###### date: 07/21/2022 ###### tags: `documentation` `iota` `deep-links` `tip` ## Summary This document proposes a standardization of **deep links** to support functionality of the IOTA protocol. It draws inspiration from the following standards and specifications: - [BIP-21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) - [EIP-681](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-681.md) - [EIP-1138](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1138.md) - [Solana Pay Specification](https://github.com/solana-labs/solana-pay/blob/master/SPEC.md) ## Motivation The overall aim of this standard is to provide slightly more interoperability between software that built on top of IOTA, ultimately to improve the user experience[^1] and by that also increase adoption. Additionally, developers will be afforded some expressivity in their applications with the exposed functionality of the IOTA protocol, which will potentially lead to interesting new developments. Some examples of this include: - Embedding a deep link into a QR code or NFC tag to be scanned - Cross-platform authentication via isolated secret managers - Quick payments in text-based messages ## Detailed Design The structure is as follows: 1. [Base Scheme](#Base-Scheme) 2. [Wallet Context](#Wallet-Context) 3. [Foundry Context](#Foundry-Context) 4. [Governance Context](#Governance-Context) 5. [Security Concerns](#Security-Concerns) ### Base Scheme The overall design takes inspiration from RPC-based APIs, where URLs are *action-based* and meant to expose functionality as execution calls that accept various parameters. This is combined with the notion that deep links correspond to in-app resources, like funds on an address in a wallet. The following is a simplified syntax of how IOTA deep links are based: ``` <protocol>:<version>/<context>/<operation>?[param=param] ``` - `protocol` - the particular base protocol to use (`iota` or `shimmer`) - `version` - the version of the deep link, e.g. `v1`, `v2` - `context` - a context of functionality within the IOTA ecosystem - `operation` - an operation within a specific context - `param` - mostly `operation`-specific query parameters; some available in all deep links: - `returnUrl` - a URL for the handler application to navigate to upon completion of the `operation` indicated in the deep link - **MAY** be HTTP/S URL or another deep link - **MUST** be short enough to satisfy the 2,048-character limit for URLs :information_source: It is worth noting that all deep links should revolve around a process where private key signing is required. This theoretically allows the location of the secret manager to be in a separate, more secure place rather than placed say inside the app sending the originally triggering the deep link. ### Wallet Context #### Sending Transactions ##### ABNF Form ``` request = scheme_prefix "/" "wallet" "/" "send" [ "?" parameters ] scheme_prefix = protocol ":" version version = ( "v" 1*INTEGER ) protocol = "iota" / "shimmer" parameters = parameter *( "&" parameter ) parameter = key "=" value key = "recipient" / "amount" / "unit" / "expirationDate" / "expirationReturnAddress" / "nativeTokenId" / "nativeTokenAmount" / "storageDepositAmount" / "storageDepositReturnAddress" / "timelock" / "returnUrl" value = address / INTEGER / token_id / STRING address = ( address_hrp "1" 59*VCHAR ) address_hrp = "iota" / "atoi" / "smr" / "rms" token_id = ( "0x" 76*HEXDIG ) ``` The following parameters are **required** when building a `send` deep link: - `recipient`[^2] - an identifier of the party receiving funds - **MUST** be a Bech32 address[^3] - **MUST** be used *only* once - `amount` - the raw amount of tokens to transfer to the recipient - **MUST** be an integer - **MUST** be less than or equal to `MAX_BASE_TOKEN_SUPPLY` - **MUST** be used *only* once The following are optional parameters for `send` deep links: - `expirationDate` - a Unix time from which the UTXO "expires" - **MUST** be a Unix timestamp in the future - **MUST** be used *only* once - `expirationReturnAddress` - the return address that can be used to unlock a UTXO once it's "expired" - **MUST** be a valid Bech32 address - **MUST** immediately follow the `expirationDate` parameter - **MUST** be used *only* once - `nativeTokenId` - the identifier string indicating a native token to send - **MUST** be in hexadecimal format with `0x` prefix - **MUST NOT** contain duplicate values - `nativeTokenAmount` - the amount of native tokens to send - **MUST** immediately follow the `nativeTokenId` parameter - **MUST** be less than or equal to `MAX_NATIVE_TOKEN_SUPPLY` - `storageDepositAmount` - the amount of IOTA tokens to use in funding the storage deposit - **MUST** be used *only* once - `storageDepositReturnAddress` - the return address of the storage deposit - **MUST** be a valid Bech32 address - **MUST** immediately follow the `storageDepositAmount` parameter - **MUST** be used *only* once - `timelock` - a Unix time from which the UTXO can be consumed - **MUST** be a Unix timestamp in the future - **MUST** be used *only* once ##### Examples Normal transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=1337420 ``` Micro-transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=420&storageDepositAmount=210&storageDepositReturnAddress=iota1qrlgpsht03ekmghhex8v7y67a835uns8dtlxu807hj0v279c74kj76j6rev ``` Expiry transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=1337420&expirationDate=1664838806&expirationReturnAddress=iota1qqc4nvg4ufcj3dkmzmd4uc034fx8pkz2nxl820a28mnsmxkec6ntw0vklm7 ``` Timelocked transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=1337420&timelock=1664838806 ``` Native token transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&nativeTokenId=0x089f40f533fca039600e20b1485dfeff859e6e087889cd8a1dc5a29b3886a738d50100000000&nativeTokenAmount=1&storageDepositAmount=1024&storageDepositReturnAddress=iota1qqc4nvg4ufcj3dkmzmd4uc034fx8pkz2nxl820a28mnsmxkec6ntw0vklm7 ``` Bundled transaction: ``` iota:v1/wallet/send?recipient=iota1qrhacyfwlcnzkvzteumekfkrrwks98mpdm37cj4xx3drvmjvnep6xqgyzyx&amount=1337420&nativeTokenId=0x089f40f533fca039600e20b1485dfeff859e6e087889cd8a1dc5a29b3886a738d50100000000&nativeTokenAmount=1&storageDepositAmount=1024&storageDepositReturnAddress=iota1qqc4nvg4ufcj3dkmzmd4uc034fx8pkz2nxl820a28mnsmxkec6ntw0vklm7 ``` #### Claiming Transactions ##### ABNF Form ``` request = scheme_prefix "/" "wallet" "/" "claim" [ "?" parameters ] scheme_prefix = protocol ":" version version = ( "v" 1*INTEGER ) protocol = "iota" / "shimmer" parameters = parameter *( "&" parameter ) parameter = key "=" value key = "outputId" / "returnUrl" value = output_ids / STRING output_ids = output_id *( "," output_id ) output_id = ( "0x" 64*HEXDIG ) ``` The following parameters are **required** when building a `claim` deep link: - `outputId` - the identifier of the output(s) to be claimed - **MUST** be in hexadecimal format with `0x` prefix - **MUST** be separated by commas if more than one, e.g. `0x123abc,0xabc123` ##### Examples Claim output: ``` iota:v1/wallet/claim?outputIds=0x9e8e1a15c831441797912a86022f5a78fcb70e151e43fe84812d4c7f6eb79a7b ``` ### Foundry Context #### Minting :information_source: Support for minting native NFTs is unclear at the moment, but will be possible. ##### ABNF Form ``` request = scheme_prefix "/" "foundry" "/" "mint" [ "?" parameters ] scheme_prefix = protocol ":" version version = ( "v" 1*INTEGER ) protocol = "iota" / "shimmer" parameters = parameter *( "&" parameter ) parameter = key "=" value key = "aliasId" / "circulatingSupply" / "maximumSupply" / "foundryMetadata" / "returnUrl" value = alias_id / INTEGER / STRING alias_id = TODO ``` The following parameters are **required**: - `aliasId` - the identifier of the alias to use in unlocking the foundry output - **SHOULD** be BLAKE2b-256 hash of the Output ID that created it - `circulatingSupply` - the total number of native tokens that will be minted - **MUST** be less than or equal to the `maximumSupply` (defined below) - `maximumSupply` - the total number of native tokens that can be minted - **MUST** be less than or equal to `MAX_NATIVE_TOKEN_SUPPLY` The following parameter(s) are optional: - `foundryMetadata` - a string of metadata (exact format TBD) - **MAY** be simple string or encoded, stringified JSON - **MUST** be short enough to be within the 2,048-character limit for URLs ##### Examples Minting native tokens: ``` iota:v1/foundry/mint?aliasId=fe80c2eb7c736da2f7c98ecf135ee9e34e4e076afe6e1dfebc9ec578b8f56d2f&circulatingSupply=1337420&maximumSupply=4201337&foundryMetadata=%7B%22standard%22%3A%22IRC30%22%2C%22name%22%3A%22FooCoin%22%2C%22symbol%22%3A%22FOO%22%2C%22decimals%22%3A3%7D ``` Minting native NFTs: ``` iota:v1/foundry/mint?aliasId=fe80c2eb7c736da2f7c98ecf135ee9e34e4e076afe6e1dfebc9ec578b8f56d2f&foundryMetadata%7B%22standard%22%3A%22IRC27%22%2C%22type%22%3A%22iamge%2Fpng%22%2C%22version%22%3A%22v1.0%22%2C%22uri%22%3A%22https%3A%2F%2Fplacekitten.com%2F200%2F300%22%2C%22name%22%3A%22NFT%20Kitten%22%7D ``` #### Burning ##### ABNF Form ``` request = scheme_prefix "/" "foundry" "/" "burn" [ "?" parameters ] scheme_prefix = protocol ":" version version = ( "v" 1*INTEGER ) protocol = "iota" / "shimmer" parameters = parameter *( "&" parameter ) parameter = key "=" value key = "nativeTokenId" / "amount" / "nftId" / "returnUrl" value = token_id / INTEGER / nft_id / STRING token_id = ( "0x" 76*HEXDIG ) nft_id = ( "0x" 64*HEXDIG ) ``` - `nativeTokenId` - the identifier string indicating the native token to burn - **MUST** be in hexadecimal format with `0x` prefix - `amount` - the amount of native tokens to burn - **MUST** be an integer - **MUST** immediately follow the `nativeTokenId` parameter - **SHOULD** be less than or equal to the circulating supply - `nftId` - the identifier string indicating the native NFT to burn - **SHOULD** be the BLAKE2b-256 hash of the Output ID that created the NFT :stop_sign: As mentioned in [TIP-0018](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#consumed-outputs-2): > Other outputs in the ledger that are locked to the address of the NFT can only be unlocked by including the NFT itself in the transaction. **If the NFT is burned, such funds are locked forever**. It is strongly advised to always check and sweep what the NFT owns in the ledger before burning it. ##### Examples Burning native tokens: ``` iota:v1/foundry/burn?nativeTokenId=0x0861c93f772ad52d8f78f2d7464d557097471454779c9ce00498ee87da2f473b100100000000&amountToBurn=4201337 ``` Burning native NFTs: ``` iota:v1/foundry/burn?nftId=0x0861c93f772ad52d8f78f2d7464d557097471454779c9ce00498ee87da2f473b ``` ### Governance Context #### Voting ##### ABNF Form ``` request = scheme_prefix "/" "governance" "/" "vote" [ "?" parameters ] scheme_prefix = protocol ":" version version = ( "v" 1*INTEGER ) protocol = "iota" / "shimmer" parameters = parameter *( "&" parameter ) parameter = key "=" value key = "eventId" / "answers" / "amount" / "returnUrl" value = event_id / answers / INTEGER / STRING event_id = ( "0x" 64*HEXDIG ) answers = answer *( "," answer ) answer = INTEGER ``` The following parameters are **required** when building a `send` deep link: - `eventId` - an identifier of the event to vote for - **MUST** be in hexadecimal format - **MUST** be exactly 64 characters with the `0x` prefix - `answers` - a list of answers to cast the vote for - **MUST** be integers (separated by commas if more than one) - **SHOULD** be a valid answer value to the corresponding question - **SHOULD** contain the same number of answers as there are questions - `amount` - the raw amount of tokens to vote with - **MUST** be an integer - **MUST** be less than or equal to `MAX_BASE_TOKEN_SUPPLY` ##### Examples Normal vote: ``` iota:v1/governance/vote?eventId=9e8e1a15c831441797912a86022f5a78fcb70e151e43fe84812d4c7f6eb79a7b&answers=1&amount=1337420 ``` ### Security Concerns Due to the fact that the deep links operations typically involve private key signing, applications **MUST NOT** allow the operations to be carried out automatically ever; they **MUST** require manual confirmation by the user and **SHOULD** be easily inspectable as not to maliciously deceive the user in sending more than they intend or to an unintended recipient. Return URLs **SHOULD** be validated and if possible even white-listed to protect against attacks from sources where deep links are triggered. As mentioned above, these **SHOULD** also be easily viewable by the user to confirm that they understand where they'll be re-navigated to. ## Drawbacks - Due to the constraint where only one app can be registered to handle a particular deep link scheme, allowing support for functionality intended to be covered by multiple types of applications (e.g. wallets, dapps, dexes) is difficult. One option is too have contexts split at the scheme level, i.e. `iota-wallet`, `iota-foundry`, `iota-did`, `iota-governance`, `iota-node`, but this itself has some drawbacks, such as needing to duplicate every context for its `shimmer` equivalent and potentially having many contexts in the future. The fragmentation makes it somewhat burdensome to develop and maintain. ## Rationale and Alternatives - Although lacking in other URL schemes for cryptocurrencies like Bitcoin and Ethereum, integrating versioning into IOTA deep links makes sense as the protocol offers a suite of functionality just from Layer 1 alone, which while it has been maturing at an incredible pace, still has some more substantial upgrades to implement, which may contain some breaking changes. - The proposed design could be more abstracted in that instead of providing a base `send` URL, we can provide additional URLs for specific types of "sends", e.g. `sendTransaction`, `sendExpiryTransaction`, `sendTimelockTransaction`, etc. This is arguably more representative of a more standard RPC API. However, this particular abstraction layer could be placed in the parsing / building library as specific functions too, maintaining flexibility of the underlying deep link mechanism but giving developers an easy way to build and parse them. ## Unresolved Questions - **Should return URLs be supported?** While adding support for return URLs for applications to return to upon completion of the deep link operation would be somewhat straight-forward, is this something that should be implement in app-specific deep link schemes? - **How and when should we support Layer 2?** We will need to provide the ability to invoke smart contracts (both Layer 1 *and* Layer 2) with deep links. This could be done in a future tip, e.g. "Layer 2 Support in Deep Links", or just added to this one in a revised draft. - **What would potential support for Stronghold look like?** With the idea that deep links are intended for applications, what possible functionality could we offer a Stronghold application? What would be useful here? [^1]: We can also improve developer experience here by providing an incredibly simple library for building and parsing IOTA deep links, like [this one](https://github.com/iotaledger/iota-url-parser) (WIP). This especially makes sense along with this standard as our protocol contains lots of functionality, and putting the burden of domain knowledge onto the developers wanting to interact with the technology is **NOT** ideal. [^2]: It is worth noting that initially `recipient` can simply be a Bech32 address, however introducing a `recipientType` would open up the possibility of using INS-domains, Ed25519 addresses, etc. [^3]: There is an [`address-parser`](https://github.com/maxwellmattryan/stardust-tools/tree/develop/tools/address-parser) library that can help parse and validate Bech32 addresses.