Mysten Incubation
Reference

CLI Signer API

HTTP endpoints the standalone server exposes for signing with the sui CLI keystore

HTTP API for signing transactions using the local sui CLI keystore. Private keys never leave the sui binary — only transaction bytes are sent for signing.

This API is served by the dev wallet's standalone server (pnpm dlx @mysten-incubation/dev-wallet serve). The endpoints are implemented as middleware that can be mounted on any HTTP server.

Endpoints

GET /api/v1/accounts

List all accounts available in the Sui CLI keystore.

Request:

GET /api/v1/accounts HTTP/1.1
Authorization: Bearer <token>

Response (200):

The accounts array is the raw, verbatim output of sui keytool list --json — the server runs that command and returns its parsed JSON unchanged. The exact field shape is defined by the sui CLI, not by the dev wallet, and may change between CLI versions. A representative entry looks like:

{
	"accounts": [
		{
			"suiAddress": "0x1234567890abcdef...",
			"publicBase64Key": "AO3a1234...",
			"keyScheme": "ed25519",
			"alias": "my-account"
		}
	]
}

Because this is a passthrough, treat the field names and types as owned by sui keytool list --json. Run that command locally to see the precise schema for your installed CLI version.

POST /api/v1/sign-transaction

Sign BCS-serialized TransactionData using the sui CLI. Calls sui keytool sign under the hood.

Request:

POST /api/v1/sign-transaction HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json

{
  "address": "0x1234567890abcdef...",
  "txBytes": "oEJ..."
}
FieldTypeValidationDescription
addressstringValid Sui address (isValidSuiAddress)Sui address to sign with
txBytesstringNon-empty base64, max ~1M charsBCS-serialized transaction data

Response (200):

{
	"suiSignature": "AKjTrX9...",
	"digest": "7f9a2c1b..."
}
FieldTypeDescription
suiSignaturestringSui signature (includes scheme flag)
digeststringTransaction digest

Personal message signing is not supportedsui keytool sign only accepts TransactionData.

Error responses

All errors return JSON with an error field:

{ "error": "description of what went wrong" }

The status codes are not uniform across endpoints — 404 and 413 only apply to POST /api/v1/sign-transaction, and a failed account listing returns 500, not 404:

StatusEndpointWhen
400sign-transactionInvalid JSON body, bad address format, or invalid txBytes
401bothMissing or invalid bearer token (token middleware runs on /api/*)
403bothHost header is not localhost/127.0.0.1/[::1] (DNS-rebinding guard)
404sign-transactionAddress not found in the keystore
413sign-transactionRequest body exceeds 2 MB (based on content-length)
500accountssui CLI not found, or sui keytool list failed
500sign-transactionsui keytool sign failed for a reason other than a missing key

Authentication

The API uses a token-in-URL approach (same pattern as Jupyter notebooks):

  1. On server start, a 256-bit random token is generated
  2. The token is printed to the terminal as part of the URL: http://localhost:5174/?token=<token>
  3. Opening that URL stores the token in localStorage
  4. All API requests require the token in the Authorization: Bearer <token> header
  5. Token comparison uses timingSafeEqual() to prevent timing attacks

Client integration

The RemoteCliAdapter from @mysten-incubation/dev-wallet/adapters implements the browser-side client for this API. The token is read from the URL printed at server start and persisted to localStorage. See Remote CLI adapter for usage.

import { RemoteCliAdapter } from '@mysten-incubation/dev-wallet/adapters';

const adapter = new RemoteCliAdapter({
	serverOrigin: 'http://localhost:5174',
	token: '<token>',
});
await adapter.initialize();

// List accounts available on the server (not yet imported)
const available = await adapter.listAvailableAccounts();

// Import a specific account for use in the wallet
await adapter.importAccount({ address: '0x...' });

On this page