ERC-5169 Client script URI for tokens (Client Script URI for Token Contracts)

Final · Names & metadata · created 2022-05-03

A standard in which a token contract uses the scriptURI() function to report the download address of the official client script dedicated to that token. Wallets and apps can find the script the issuer specified and extend token functionality.

Why it is needed

Tokens can have many uses besides transfers. For example, operating a smart lock with an NFT, or using contract functions such as mint that a wallet does not support by default, requires a separate client script. But this script does sensitive things such as building transactions on the user's behalf, and without a way to officially link the token contract to the script, users have no way to verify that the script they are using is the genuine one made by the issuer. Storing the whole script inside the contract is possible, but every update requires the contract's signing key, which raises the risk of key exposure, and client scripts are usually much larger than contracts, so storage and update costs are also high. Building a separate centralized authenticated hosting service is a burden as well.

What to implement

In depth

Interface: scriptURI and setScriptURI

The standard consists of a single IERC5169 interface. scriptURI() MUST return currentScriptURI, the currently stored state, as a string[], and MAY be implemented as pure or view. setScriptURI(string[] memory newScriptURI) MUST update the internal state with currentScriptURI = newScriptURI, and MUST emit the ScriptUpdate event whenever an update occurs. A URI can be anything that follows RFC 3986, and the original gives IPFS multihashes, GitHub gists, and cloud storage links as examples. Examples of scripts include a miniapp tailored to a single token, TokenScript that lets users use features such as mint that wallets lack, extensions for hardware wallets such as Ledger, and JavaScript commands that operate a smart lock.

Owner verification rule

The implementation contract MUST store an address owner variable in its state and MUST set owner = msg.sender in the constructor. setScriptURI MUST first verify owner == msg.sender before running logic and changing state. In other words, only the contract owner can change the script addresses. The original explains that this management key (SCPrivKey) does not need to be a newly created key for this standard and that the existing management key used to manage already issued tokens can be reused. The test contract also implements this rule with OpenZeppelin's Ownable and the onlyOwner modifier.

Script authenticity obligation

A party that uses a script learned through scriptURI MUST verify one of the following. The script is at an immutable location, the URI itself contains the script's hash digest, or the party implements the Authenticity for Client Script EIP, a separate standard that guarantees authenticity through signatures instead of hashes. This is because a script can perform trusted operations such as building transactions for the user, so a plain address whose contents may change must not be trusted alone.

Why external storage rather than on-chain

Storing the script directly in the contract has three drawbacks. First, every update requires the contract's signing key, so the key is exposed more often. Second, updating requires a contract call, which becomes a cost barrier if updates are frequent. Third, client scripts are usually much larger than contracts, so storage fees are high. So frequently changing functionality is placed in an external resource, and the contract only tells where it is. The external resource can be centralized hosting such as the cloud, a personal server, or decentralized storage such as IPFS. Fully decentralized storage can be disadvantageous in speed, price, and capacity, so this standard returns multiple URIs as an array, allowing centralized, personal, and decentralized locations to be mixed. The script itself can point to other scripts or data sources to enable extensions such as lazy loading, and the integrity handling of such secondary data is left to the script format.

Reference implementation: office door NFT

The source uses the STL office door token as an example. An NFT is minted and sent to an employee, and the TokenScript linked through the contract's scriptURI() contains the procedure for opening the door. The script receives a random challenge string (for example 'Apples-5E3FA1') from the IoT device, shows it on screen, requests a personal signature (Sign Personal), and sends the signature back to the IoT device. The device opens the door if the address recovered from the signature holds that NFT. From the user's side, as soon as they receive the NFT they can use the authenticated feature directly in their wallet. The source states that this setup is used in practice at the Smart Token Labs office door and in several installations.

Lifecycle and compatibility

The lifecycle is simple. In the issuance phase, the issuer deploys a contract and tokens that implement this standard and registers the scriptURI with setScriptURI. In the update phase, the script is first stored at the new URI locations, and then setScriptURI is called again with the new scriptURI structure. The source states that this standard is backwards compatible with most existing token standards and names ERC-20, ERC-721, ERC-777 and ERC-1155 as widely used standards. The status of this standard is Final, meaning its content is settled and will no longer change.

Things to watch out for

Original ERC · ← ERCs