To add light client functionality to your extension, follow this step-by-step guide. This guide explains how to integrate smoldot and the light client extension helpers into your browser extension.
- Manifest V3: This is required in both Chrome and Firefox because we need to use service workers to run smoldot. For more details, you can refer to the Chrome Manifest V3 documentation and the Firefox Manifest V3 documentation.
- Ensure the following permissions are enabled in your manifest:
["notifications", "storage", "tabs", "alarms"]
.
First install these three packages: @substrate/light-client-extension-helpers
,@substrate/smoldot-discovery
, @substrate/smoldot-discovery-connector
and @substrate/connect-known-chains
. We will use these packages to implement a provider for the @substrate/smoldot-discovery
package.
pnpm i @substrate/light-client-extension-helpers @substrate/connect-known-chains @substrate/smoldot-discovery
Start by adding the light client background extension helper to your background script. This must be a service worker script using Manifest V3. When your extension launches, it will immediately connect to smoldot
.
Manifest V3 Configuration:
// manifest-v3-chrome.json
"background": {
"service_worker": "background/background.js",
"type": "module"
},
"permissions": ["notifications", "storage", "tabs", "alarms"],
"web_accessible_resources": [
{
"resources": ["path-to-your/inpage.js"],
"matches": ["<all_urls>"],
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
},
Background Script:
import {
polkadot,
ksmcc3,
westend2,
paseo,
} from "@substrate/connect-known-chains"
import { register } from "@substrate/light-client-extension-helpers/background"
import { start } from "@substrate/light-client-extension-helpers/smoldot"
const { lightClientPageHelper, addOnAddChainByUserListener } = register({
smoldotClient: start({ maxLogLevel: 4 }),
getWellKnownChainSpecs: async () => [polkadot, ksmcc3, westend2, paseo],
})
Then, invoke the register function in your content script and inject your in-page into the DOM. See the content script for detailed implementation.
Content Script:
import { register } from "@substrate/light-client-extension-helpers/content-script"
const CHANNEL_ID = "substrate-wallet-template"
try {
const s = document.createElement("script")
s.type = "module"
s.src = chrome.runtime.getURL("inpage/inpage.js")
s.onload = () => s.remove()
;(document.head || document.documentElement).appendChild(s)
} catch (error) {
console.error("error injecting inpage/inpage.js", error)
}
register(CHANNEL_ID)
const port = chrome.runtime.connect({ name: "substrate-wallet-template" })
port.onMessage.addListener((msg) =>
window.postMessage({ origin: "substrate-wallet-template/extension", msg }),
)
window.addEventListener("message", ({ data }) => {
if (data.origin !== "substrate-wallet-template/web") return
port.postMessage(data.msg)
})
Finally, in the in-page script injected by the content script, expose your provider using the @substrate/discovery
protocol in conjunction with the @substrate/smoldot-discovery
package. Refer to the in-page script for full implementation details.
In-Page Script:
import { getLightClientProvider } from "@substrate/light-client-extension-helpers/web-page"
import {
make as makeSmoldotDiscoveryConnector,
SmoldotExtensionProviderDetail,
} from "@substrate/smoldot-discovery-connector"
const CHANNEL_ID = "substrate-wallet-template"
const PROVIDER_INFO = {
uuid: crypto.randomUUID(),
name: "Substrate Connect Wallet Template",
icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
rdns: "io.github.paritytech.SubstrateConnectWalletTemplate",
}
const lightClientProviderPromise = getLightClientProvider(CHANNEL_ID)
// #region Smoldot Discovery Provider
{
const provider = lightClientProvider.then(makeSmoldotDiscoveryConnector)
const detail: SmoldotExtensionProviderDetail = Object.freeze({
info: PROVIDER_INFO,
kind: "smoldot-v1",
provider,
})
window.addEventListener(
"substrateDiscovery:requestProvider",
({ detail: { onProvider } }) => onProvider(detail),
)
window.dispatchEvent(
new CustomEvent("substrateDiscovery:announceProvider", {
detail,
}),
)
}
// #endregion
To verify your integration, build your extension and load it into your browser. Then, run the @substrate/smoldot example.
If you see your extension in the list, you have completed this integration.
The next part of this tutorial will extend the steps of the prior integration. It adds support for the @substrate/connect-discovery package. We will use Polkadot JS (PJS) extension as a baseline in this example.
pnpm i @polkadot-api/pjs-signer @polkadot-api/utils @substrate/connect-discovery
Add the following code below to the relevant section of your inpage.js
file to implement createTx
and getAccounts
. Replace "polkadot-js" with the name of your PJS-compatible extension.
import { connectInjectedExtension } from "@polkadot-api/pjs-signer"
import { toHex, fromHex } from "@polkadot-api/utils"
import { createTx } from "@substrate/light-client-extension-helpers/tx-helper" // 👈 create-tx import
import { Unstable } from "@substrate/connect-discovery"
import {
getLightClientProvider,
LightClientProvider,
} from "@substrate/light-client-extension-helpers/web-page"
// #region Connect Discovery Provider
{
const provider = lightClientProviderPromise.then(
(lightClientProvider): Unstable.Provider => ({
...lightClientProvider,
async createTx(chainId: string, from: string, callData: string) {
const chains = Object.values(lightClientProvider.getChains())
const chain = chains.find(({ genesisHash }) => genesisHash === chainId)
if (!chain) {
throw new Error("unknown chain")
}
const injectedExt = await connectInjectedExtension("polkadot-js")
const account = injectedExt
.getAccounts()
.find((account) => toHex(account.polkadotSigner.publicKey) === from)
if (!account) {
throw new Error("no account")
}
const signer = account.polkadotSigner
const tx = await createTx(chain.connect)({
callData: fromHex(callData),
signer,
})
return toHex(tx)
},
async getAccounts(_chainId: string) {
const injectedExt = await connectInjectedExtension("polkadot-js")
const accounts = injectedExt.getAccounts()
return accounts
},
}),
)
const detail: Unstable.SubstrateConnectProviderDetail = Object.freeze({
info: PROVIDER_INFO,
kind: "substrate-connect-unstable",
provider,
})
window.addEventListener(
"substrateDiscovery:requestProvider",
({ detail: { onProvider } }) => onProvider(detail),
)
window.dispatchEvent(
new CustomEvent("substrateDiscovery:announceProvider", {
detail,
}),
)
}
// #endregion
Once your extension is properly set up, you can test it using the light client dapp example.
-
Connect Account:
-
Verify Extension in Providers List:
-
Access Extension Options:
-
Check Smoldot Logs:
If you successfully complete these steps, your extension setup is finalized.