Usage of the walletAPIServer in Ledger Live
The react hook useWalletAPIServer is used within Ledger Live (opens in a new tab) to create a walletAPIServer
It is used in ledger-live-common here
ledger-live-common/src/wallet-api/react.ts
In this file, a walletAPIServer
is created, and exposed through another hook (also called
useWalletAPIServer
)
This (LLC hook) is in turn used in both LLD (opens in a new tab) and LLM (opens in a new tab)
useWebview()
useWebView() gets called
Both LLD and LLM, when creating live apps, use the hook useWebView
,
with the main arguments being:
- manifest
- customHandlers
- webviewRef
const WalletAPIWebview => {
({ manifest customHandlers} ) => {
const { webviewRef } = useWebviewState();
useWebView({ manifest, customHandlers}, webviewRef);
return <webview />
}
};
Prepares transport
Will allow communication with the webview.
const webviewHook = {
return {
postMessage: (message) => {
webviewRef.current.contentWindow?.postMessage(message);
},
};
};
Prepares uiHooks
They will allow walletHandlers to trigger ui actions
more on uiHooks
uiHooks are created in useWebView and sent to LLC useWalletAPIServer
uiHook
is an object that maps a method like "account.request"
to an action in LLD / LLM
here's a list of actions it triggers in LLD:
- opening a drawer to select an account, start an exchange process
- opening modals to sign transactions, messages, start an exchange process, connect a device
- store data / update account data
- display toaster
LLC useWalletAPIServer() gets called
The goal of LLC's useWalletAPIServer
is simply to call the walletAPIServer hook (also called useWalletAPIServer
)
Before doing so it:
-
Extracts permissions From the manifest
-
Converts accounts sent from useWebView (coming from redux store) to a format readable by walletAPIServer
-
Fetches and filters currencies (coming from
libs/ledger-live-common/src/currencies/helpers.ts
listCurrencies
) -
Converts filtered currencies to a format readable by wallet-api-server. More on that format here
-
Creates a transport
function useTransport(postMessage: (message: string) => void | undefined): Transport {
return useMemo(() => {
return {
onMessage: undefined,
send: postMessage, // will allow WALLETAPISERVER -> LIVEAPP communication (via postMessage)
};
}, [postMessage]);
}
const transport = useTransport(webviewHook.postMessage);
walletAPIServer gets instantiated
via the react hook useWalletAPIServer
post-instantiation transport setup
walletAPIServer sends back its onMessage
callback, the webview will use
it to send message to it.
const { onMessage } = useWalletAPIServer({
manifest,
accounts,
config,
webviewHook,
uiHook,
customHandlers,
});
const handleMessage = useCallback(
(event: Electron.IpcMessageEvent) => {
if (event.channel === "webviewToParent") {
onMessage(event.args[0]);
}
},
[onMessage]
);
useEffect(() => {
webviewRef.current.addEventListener("ipc-message", handleMessage);
}, [handleMessage, onLoad]);
post-instantiation setup of wallet handlers
server.setHandler is called to setup Ledger Live Wallet/UI/Store callbacks.
Simplified example of setHandler() call
server.setHandler("account.request", async ({ accounts$, currencies$ }) => {
const currencies = await firstValueFrom(currencies$);
return new Promise((resolve, reject) => {
let currencyList = currencyList = allCurrenciesAndTokens.filter(({ id }) => currencyIds.includes(id));
uiAccountRequest({
accounts$,
currencies: currencyList,
onSuccess: (account: AccountLike, parentAccount: Account | undefined) => {
resolve(accountToWalletAPIAccount(account, parentAccount));
},
onCancel: () => {
reject(new Error("Canceled by user"));
},
});
});
});
}, [manifest, server, tracking, uiAccountRequest]);
those handlers are saved in the property WalletAPIServer.walletHandlers
To recap:
WalletAPIServer.walletHandlers
-> callbacks defined in LLC, trigger modals / drawers / update redux store, etc.WalletAPIServer.requestHandlers
-> internalHandlers + customHandlers, can callwalletHandlers
inside of them