In this guide, we’ll create a simple hook for signing messages with MWA, and then build a MessageModifyingSigner
so you can easily use it with the kit SDK.
Create a hook for signing messages
- To sign a message, we need the user’s wallet address and the message payload. First, we
establish and authorize an MWA session. Then, we call the
signMessages method to sign the payloads(messages).
services/mwa/useSignMessage.ts
type Input = Readonly<{
addresses: readonly string[];
payloads: readonly Uint8Array[];
}>;
type Output = Readonly<{
signed_payloads: readonly Uint8Array[];
auth_token: string;
}>;
type AuthorizeInput = Readonly<{
chain: `solana:${string}`;
identity: Readonly<{
name?: string;
uri?: `https://${string}`;
icon?: string;
}>;
auth_token?: string;
}>;
export function useSignMessage(authInput: AuthorizeInput):
(input: Input) => Promise<Output> {
return useCallback(async (input: Input): Promise<Output> => {
const promise = transact(async (wallet: KitMobileWallet): Promise<Output> => {
const authResult = await wallet.authorize(authInput);
const signedMessages = await wallet.signMessages({
addresses: [...input.addresses],
payloads: [...input.payloads],
});
return {
signed_payloads: signedMessages,
auth_token: authResult.auth_token,
};
});
return promise;
}, [authInput]);
}
If you take a look at the above snippet, you’ll notice that input for authorization is passed as a parameter to the hook, which is quite
different from the previous connect hooks that we created. This is because in a general dApp we perform signing actions after the user has connected
their wallet.
So I feel like it is kinda redundant to pass them again and again. It would be better if we could eventually
remove the need to pass authorization input to the signing hook at all, but for now this will do.
- Add an option to abort the operation : this is a small inspiration from kit
to run abortable sign message operations.
services/mwa/useSignMessage.ts
import { getAbortablePromise } from '@solana/promises';
export function useSignMessage(authInput: AuthorizeInput):
(input: Input) => Promise<Output> {
return useCallback(async (input: Input): Promise<Output> => {
(input: Input, abortSignal?: AbortSignal) => Promise<Output> {
return useCallback(async (input: Input, abortSignal?: AbortSignal): Promise<Output> => {
const promise = transact(async (wallet: KitMobileWallet): Promise<Output> => {
const authResult = await wallet.authorize(authInput);
const signedMessages = await wallet.signMessages({
addresses: [...input.addresses],
payloads: [...input.payloads],
});
return {
signed_payloads: signedMessages,
auth_token: authResult.auth_token,
};
});
return promise;
return getAbortablePromise(promise, abortSignal);
}, [authInput]);
}
AbortSignal is a web API for aborting asynchronous operations (DOM request).
Here we are using getAbortablePromise utility from @solana/promises to automatically stop the operation when the abort signal is triggered.
AbortSignal is not natively supported by RN. So you will have to manually define the abort signal and abort functions in order to use this functionality.
Create the Message Signer
We can leverage MessagePartialSigner and MessageModifyingSigner abstractions
from kit to create signers that can sign messages. But to make them compatible with MWA we need to transform input from the signer method,
So let’s use MessageModifyingSigner.
services/mwa/MessageModifyingSigner.ts
import { Address, MessageModifyingSigner, MessageModifyingSignerConfig, SignableMessage, SignatureBytes } from "@solana/kit";
import { useSignMessage } from "./useSignMessage";
type AuthorizeInput = Readonly<{
chain: `solana:${string}`;
identity: Readonly<{
name?: string;
uri?: `https://${string}`;
icon?: string;
}>;
auth_token?: string;
}>;
export function useMessageSigner(authInput: AuthorizeInput, signerAddress: Address): MessageModifyingSigner {
const signMessage = useSignMessage(authInput)
return {
address: signerAddress,
modifyAndSignMessages: async (messages: readonly SignableMessage[], config?: MessageModifyingSignerConfig) => {
const { abortSignal } = config ?? {};
abortSignal?.throwIfAborted();
const addresses = [signerAddress];
const payloads = messages.map((message) => message.content);
}
}
}
Above we have created a function useMessageSigner that accepts AuthorizeInput, Address and returns a MessageModifyingSigner instance.
Now we need to transform the messages into the input format required by our useSignMessage hook by extracting the addresses and payloads.
Once this is done we can proceed in executing the sign message operation.
To create abortable operations, we pass the Abort signal in the config parameter of modifyAndSignMessages method. This method will check the
status of the abort signal and execute our sign messages hook.
services/mwa/MessageModifyingSigner.ts
export function useMessageSigner(authInput: AuthorizeInput, signerAddress: Address): MessageModifyingSigner {
const signMessage = useSignMessage(authInput)
return {
address: signerAddress,
modifyAndSignMessages: async (messages: readonly SignableMessage[], config?: MessageModifyingSignerConfig) => {
const { abortSignal } = config ?? {};
abortSignal?.throwIfAborted();
const addresses = [signerAddress];
const payloads = messages.map((message) => message.content);
const result = await signMessage({
addresses: addresses,
payloads,
}, abortSignal);
return messages.map((message, index) => ({
content: message.content,
signatures: {
[signerAddress]: result.signed_payloads[index] as SignatureBytes
} as Readonly<Record<Address, SignatureBytes>>
})) as readonly SignableMessage[];
}
}
}
The signMessage method from MWA modifies the output structure of the signed messages.
So we need to transform it back to the format expected by the MessageModifyingSigner.