Skip to main content

SOL Sign

This article explains how to complete Solana address confirmation and transaction signing on UKey Wallet hardware devices. The focus is on three things: using the device-supported derivation path, serializing the transaction into device-parsable rawTx, and completing signature backfilling and verification on the application side.

Table of contents

  1. How It Works
  2. Setup
  3. Init
  4. Best For
  5. Flow & State
  6. Demo
  7. Signature verification and troubleshooting

How It Works

The application sends the transaction to be signed to the device-side Solana App through the SDK entry. The device displays a summary of the account, transfer target, amount, fee, and related details based on the serialized rawTx, then returns the signature only after user confirmation. The application then writes the signature back to Transaction or VersionedTransaction and verifies the signer's public key with @solana/web3.js.

Please confirm the following boundaries before accessing:

  • The derived path must be a 4-segment fully hardened structure: m/44'/501'/account'/0', for instance m/44'/501'/3'/0'.
  • rawTx Just pass in the hexadecimal string, and the 0x prefix may be included or omitted.
  • If you have an Transaction or VersionedTransaction object, execute Buffer.from(tx.serialize()).toString('hex') first and then pass it to the SDK.
  • The application is responsible for constructing the transaction, setting up recentBlockhash, and serializing the transaction; the signature is only generated after confirmation by the device.
  • Versioned Transaction requires the firmware version to meet UKey Lite 24 / UKey Lite 25 / UKey Core 26 ≥ 1.1.0.

Setup

npm i @ukeyfe/hardware-core @ukeyfe/hardware-common-connect-sdk

Init

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";
await ukeySdk.init({ env: "webusb", fetchConfig: true, debug: false });
const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

Best For

Scenario 1: Read and confirm address

const path = "m/44'/501'/3'/0'";
const opResult = await ukeySdk.solGetAddress(connectId, deviceId, {
path,
showOnUKey: true,
});
// Example data: opResult.payload.address, publicKey?, path

Input Fields

  • path: Fully hardened Solana account path in the 44'/501'/account'/0' shape.
  • showOnUKey?: Turn this on when the user should verify the address on the device, such as first-time binding or before sensitive actions.

return

Promise<{ success; payload: { address; path; publicKey? } }>;

Scenario 2: Send a prepared transaction to the device for signing

const opResult = await ukeySdk.solSignTransaction(connectId, deviceId, {
path: "m/44'/501'/3'/0'",
rawTx,
keepSession: true,
});
// Example data: opResult.payload.signature

Input Fields

  • path: Solana account path that matches the address used to build the transaction.
  • rawTx: Serialized transaction as hex. The 0x prefix is accepted but not required; convert Transaction.serialize() output to hex before calling.
  • keepSession?: Keep the active session for smoother consecutive signatures.

return

Promise<{ success; payload: { signature } }>;

After getting the signature, attach it back to the transaction object and complete local verification before broadcasting.

Flow & State

  • The SDK method returns the final result through Promise; intermediate steps such as unlocking the device, opening the Solana App, confirming the transaction, etc. will notify the application display prompt through the UI_REQUEST event.
  • Please call the interfaces in sequence on the same device and do not initiate multiple signature requests concurrently.
  • When signing in batches or continuously, you can combine keepSession to make the interaction smoother.

Demo

Reference: transaction signature

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";

await ukeySdk.init({ env: "webusb", debug: false });

const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

const path = "m/44'/501'/3'/0'";
await ukeySdk.solGetAddress(connectId, deviceId, {
path,
showOnUKey: true,
});

// If you start with a Transaction / VersionedTransaction, convert it to hex first:
const rawTxFromObject = Buffer.from(transaction.serialize()).toString('hex');
// The demoRawTx hex below is already serialized and can be sent to the device as-is.
const demoRawTx =
"5f3b1d7a9c2e4f6081a3b5d7f90c2e4f6081a3b5d7f90c2e4f6081a3b5d7f90c2e4f6081a3b5d7f90c2e4f60";

const { success, payload } = await ukeySdk.solSignTransaction(
connectId,
deviceId,
{
path,
rawTx: demoRawTx,
keepSession: true,
},
);

if (success) {
console.info("Returned signature payload:", payload.signature);
}

Final Checks

CheckpointWhat to do
Path rulesKeep the path in fully hardened form as 44'/501'/account'/0'; other patterns may be rejected.
Transaction dataPrepare a valid recentBlockhash, a complete account list, and a stable instruction order first.
On-screen matchMake sure the account, destination, amount, and fee summary match the device display.
Signature recoveryAfter reapplying the signature with @solana/web3.js, confirm the signing public key matches the address from solGetAddress.
Payload sizeSplit or simplify the transaction if it is too large or includes instructions the device does not support yet.
User exit pathIf the user rejects or times out, let them restart the flow instead of replaying the same transaction in the background.

Continue with the API page: solSignTransaction.