Integrate into applications
The easiest way to integrate TokenScript into your application is using the TokenScript viewer. Integrating in this way allows you to stay up to date with new TokenScript features without deploying new versions of your wallet.
In this integration guide, we will guide you how to support TokenScript (opens in a new tab) and EIP-5169 (opens in a new tab) as a wallet provider like JoyId (opens in a new tab) and Alphawallet (opens in a new tab). You can also embed TokenScript viewer into any webpage, such as NFT marketplaces & portfolio apps.
Checking for TokenScripts
Before showing the TokenScript in an iframe or webview within your wallet, you must first check that the chain+contract has a TokenScript associated with it. The launchpad server provides a public API that you can query to find TokenScripts:
Example in Typescript:
async function getScriptUri(chain: string, contractAddr: string) {
// i.e. https://store-backend.smartlayer.network/tokenscript/0xD5cA946AC1c1F24Eb26dae9e1A53ba6a02bd97Fe/chain/137/script-uri
const res = await fetch(`https://store-backend.smartlayer.network/tokenscript/${contractAddr.toLowerCase()}/chain/${chain}/script-uri`);
const data = await res.json();
if (!data.scriptURI)
return null;
if (data.scriptURI.erc5169?.length)
return <string>data.scriptURI.erc5169[0];
if (data.scriptURI.offchain?.length)
return <string>data.scriptURI.offchain[0];
return null;
}
Integrate into a web application wallet or other webpage
Web integration uses an iframe to embed a token details screen with TokenScript support. We will use a NextJS/wagmi and ethers.js example, but the idea is the same for any other tech stack.
The token details view
TokenScript viewer provides a custom view that displays an NFT details screen and the actions for the corresponding TokenScript. Here is an example: https://viewer.tokenscript.org/?viewType=sts-token&chain=137&contract=0xD5cA946AC1c1F24Eb26dae9e1A53ba6a02bd97Fe&tokenId=3803829543 (opens in a new tab)
If you require a different UI, please reach out to us. We would be happy to create a design which better reflects your wallets current UI.
Step 1: Open the iFrame
Check if the NFT contract implements TokenScript using the API above, if yes,
then you can use the first value of scriptURI
or offchain
to load TokenScript Viewer iframe.
offchain
are scripts linked using the launchpad API which is an alternative for contract that do not implement the scriptURI (ERC-5169) standard.
With Next.js/wagmi:
export default function AppPageContent({
params,
}: {
params: {
chainId: string
contract: string
tokenId: string
}
}) {
const chainId = parseInt(params.chainId, 10)
const { address } = useAccount()
// read contract to get scriptURI content, need to check if it's a valid tokenscript url
const { scriptURI } = useGetTokenScriptURI(params.contract, chainId)
const iframeRef = useRef<HTMLIFrameElement>(null)
const dAppUrl = addQueriesToUrl(`https://viewer.tokenscript.org/`, {
chainId: params.chainId,
contract: params.contract,
tokenId: params.tokenId,
viewType: "sts-token",
chain: chainId.toString(),
tokenscriptUrl: encodeURIComponent(scriptURI),
})
useIframePostMessage(iframeRef, dAppUrl)
return (
<iframe
key={`${chainId}-${params.contract}-${params.tokenId}-${address}`}
ref={iframeRef}
src={dAppUrl}
/>
)
}
function addQueriesToUrl(
url: string,
params: { [key: string]: string }
): string {
const result = new URL(url)
Object.entries(params).forEach(([key, value]) => {
result.searchParams.append(key, value)
})
return result.toString()
}
Note: For ERC-20 tokens, simply omit the tokenId parameter.
Step 2: implement TokenScript RPC requirements
The iframe will proxy any RPC requests through postMessage to the parent window where your wallet app runs. To implement message & transaction signing, you must listen and process these requests.
With Next.js/wagmi:
import { RefObject, useEffect } from "react"
import { useWalletClient } from "wagmi"
export const useIframePostMessage = (
iframeRef: RefObject<HTMLIFrameElement>,
targetOrigin: string
) => {
const { data: walletClient } = useWalletClient()
useEffect(() => {
function sendResponse(
messageData: MessageEvent["data"],
response: any,
error?: any
) {
const data = messageData
if (error) {
data.error = error
} else {
data.result = response
}
iframeRef.current?.contentWindow?.postMessage(data, targetOrigin)
}
const handleMessage = async (event: MessageEvent) => {
if (!walletClient) {
return
}
try {
switch (event.data.method) {
case "eth_accounts":
case "eth_requestAccounts": {
const data = await walletClient.request({
method: event.data.method,
})
sendResponse(event.data, data)
break
}
case "eth_chainId":
case "net_version":
case "eth_blockNumber":
case "eth_estimateGas":
case "eth_sendTransaction":
case "eth_getTransactionByHash":
case "eth_getTransactionReceipt":
case "eth_getTransactionCount":
case "personal_sign":
case "eth_signTypedData":
case "wallet_switchEthereumChain": {
const data = await walletClient.request({
method: event.data.method,
params: event.data.params,
})
sendResponse(event.data, data)
break
}
default:
sendResponse(event.data, null, {
code: -1,
message:
"RPC Method " + event.data.method + " is not implemented",
})
break
}
} catch (e: any) {
const innerError = e.walk()
if (innerError) e = innerError
sendResponse(event.data, null, {
code: e.data?.code ?? e.code,
message: e.message + (e.data?.message ? " " + e.data?.message : ""),
})
}
}
window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
}, [iframeRef, targetOrigin, walletClient])
}
With ethers.js:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/5.7.2/ethers.umd.min.js" integrity="sha512-FDcVY+g7vc5CXANbrTSg1K5qLyriCsGDYCE02Li1tXEYdNQPvLPHNE+rT2Mjei8N7fZbe0WLhw27j2SrGRpdMg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body style="margin: 0;">
<div style="max-width: 600px; width: 100%; margin: 0 auto; position: relative; height: 100dvh;">
<iframe id="frame" src="https://viewer.tokenscript.org/?viewType=sts-token&chain=137&contract=0xD5cA946AC1c1F24Eb26dae9e1A53ba6a02bd97Fe&tokenId=3803829543"style="border: 0; width: 100%; height: 100%;"></iframe>
</div>
<script>
const BASE_URL = "https://viewer.tokenscript.org";
// Metamask provider
const provider = new ethers.providers.Web3Provider(window.ethereum);
const iframe = document.getElementById("frame");
window.addEventListener("message", async (message) => {
if (message.origin !== BASE_URL)
return;
if (message.data.jsonrpc !== "2.0")
return;
console.log("[IFRAME_RPC] request received: ", message);
try {
switch (message.data.method) {
case "eth_accounts":
case "eth_requestAccounts":
await window.ethereum.enable();
const accounts = await provider.listAccounts();
sendResponse(message.data, accounts);
break;
case "eth_chainId":
case "net_version":
case "eth_blockNumber":
case "eth_estimateGas":
case "eth_sendTransaction":
case "eth_getTransactionByHash":
case "eth_getTransactionReceipt":
case "eth_getTransactionCount":
case "personal_sign":
case "eth_signTypedData":
case "wallet_switchEthereumChain":
const result = await provider.send(message.data.method, message.data.params);
sendResponse(message.data, result);
break;
default:
sendResponse(message.data, null, {code: -1, message: "RPC Method " + message.data.method + " is not implemented"});
}
} catch (e){
console.error(e);
sendResponse(message.data, null, {
code: e.code,
message: e.message
});
}
});
function sendResponse(messageData, response, error){
const data = messageData;
if (response){
data.result = response;
} else {
data.error = error;
}
iframe.contentWindow.postMessage(data, BASE_URL);
}
</script>
</body>
</html>
As you can see, the code required is minimal. It simply forwards the request to your own RPC provider and returns the result back to the iframe.
Integrate into a native wallet application
The process of native integration is similar to web application integration, but instead of opening an iframe we need to use a webview for the corresponding platform and provide an injected RPC provider.
Due to the number of different webview implementations we haven't provided detailed implementation instructions, but rather high-level tech requirements.
Open the webview
Check if the NFT contract implements TokenScript using the API above, if yes,
then you can use the first value of scriptURI
to load TokenScript Viewer in a webview.
The webview should load the following URL, replacing the tokens with the correct values for the chain, contract, etc.
https://viewer.tokenscript.org/?viewType=alphawallet&chain=${chainId}&contract=${contractAddress}&tokenId=${tokenId}&tokenscriptUrl=${scriptUri}
Note: The "alphawallet" view will automatically connect to the injected RPC provider.
Provide an injected RPC provider
An injected ethereum RPC provider is required for wallet interaction and is usually implemented through a Javascript interface provided to the webview by your application. Most wallets will provide a "Dapp browser", and will therefore already have the components required to provide injected RPC to a webview. If this is the case for your app, then good news! It should be a straightforward process to reuse these components to show TokenScript viewer.
If you don't currently have these components, here are a few links that will help you add injected RPC for your chosen platform.
- Android: https://developer.samsung.com/blockchain/platform/programming-guide/cucumber-webview.html (opens in a new tab)
- iOS: https://github.com/web3swift-team/web3swift (opens in a new tab)
- Flutter: https://pub.dev/packages/flutter_injected_web3 (opens in a new tab)
Note: The RPC provider should support wallet_switchEthereumChain & wallet_addEthereumChain as well as all standard RPC methods.
Conclusion
That's it, you are now ready to use TokenScript and EIP-5169 in your wallet using TokenScript viewer! If you would like to customise the UI TokenScript viewer provides to fit in better with your wallet design, please contact us.