Signing Bytes
You can sign arbitrary bytes with Wallet Provider in a React-based web application. This action is useful for verifying account ownership without having to post a transaction to the chain, and is commonly used as a form of simple user authentication.
Tip
Not using React? Use the wallet-controller instead.
The Wallet Provider comes with a useConnectedWallet
hook, which lets you trigger actions from a XPLA Vault that’s connected to the web page. The connectedWallet
object includes a .signBytes()
method, which prompts the user to sign the data and then returns an object of type SignBytesResult
. The returned SignBytesResult
object contains the address of the signer and the signed data.
The verifyBytes
function then compares the original TEST_BYTES
against the signature and public key pairing returned by the SignBytesResult
. If verifyBytes
returns true
, then the account is owned by the connected wallet. Likewise, if verifyBytes
returns false
, then the account is not owned by the connected wallet. In this way, the owner of the associated wallet is verified without having to produce an on-chain action or pay gas fees.
Tip
You can see how the verifyBytes
function works here.
catch
statement notify the user whether the signing was successful: import {
SignBytesFailed,
SignBytesResult,
Timeout,
useConnectedWallet,
UserDenied,
verifyBytes,
} from '@xpla/wallet-provider';
import React, { useCallback, useState } from 'react';
const TEST_BYTES = Buffer.from('hello'); // resolves to <Buffer 68 65 6c 6c 6f>
export function SignBytesSample() {
const [txResult, setTxResult] = useState<SignBytesResult | null>(null);
const [txError, setTxError] = useState<string | null>(null);
const [verifyResult, setVerifyResult] = useState<string | null>(null);
const connectedWallet = useConnectedWallet();
const signBytes = useCallback(async () => {
if (!connectedWallet) {
return;
}
try {
const signedBytes: SignBytesResult = await connectedWallet.signBytes(TEST_BYTES);
setTxResult(signedBytes);
setTxError(null);
const result = verifyBytes(TEST_BYTES, signedBytes.result);
setVerifyResult(result ? 'Verified' : 'Verification failed');
} catch (error) {
setTxResult(null);
setVerifyResult(null);
if (error instanceof UserDenied) {
setTxError('User Denied');
} else if (error instanceof Timeout) {
setTxError('Timeout');
} else if (error instanceof SignBytesFailed) {
setTxError('Sign Bytes Failed');
} else {
setTxError(
'Unknown Error: ' +
(error instanceof Error ? error.message : String(error)),
);
}
}
}, [connectedWallet]);
return (
<div>
<h1>Sign Bytes Sample</h1>
{connectedWallet?.availableSignBytes &&
!txResult &&
!txError &&
!verifyResult && (
<button onClick={() => signBytes()}>
Sign bytes with {connectedWallet.walletAddress}
</button>
)}
{txResult && <pre>{JSON.stringify(txResult, null, 2)}</pre>}
{txError && <pre>{txError}</pre>}
{verifyResult && <pre>{verifyResult}</pre>}
{!connectedWallet && <p>Wallet not connected!</p>}
{connectedWallet && !connectedWallet.availableSignBytes && (
<p>This connection does not support signBytes()</p>
)}
</div>
);
}