Browser Extension

Sign-in

Prove a user controls an XRP Ledger address with CAIP-122 sign-in, without moving funds.

signIn() lets a user prove they control an address. It needs a connected site and the user's approval.

There is deliberately no signMessage: a signature over an arbitrary string could be replayed against other sites. Sign-in messages are bound to your site, the chain and a nonce.

interface SignInParams {
  statement?: string    // shown to the user; up to 512 characters
  nonce?: string        // up to 128 characters; Joey generates one if omitted
  resources?: string[]  // up to 16 URIs, shown and included in the message
}

type SignInResult = Caip122SignInResult | ChallengeSignInResult

interface Caip122SignInResult {
  address: string
  publicKey: string
  signature: string
  message: string
  mode?: 'caip122'
}

interface ChallengeSignInResult {
  address: string
  signedTx: string
  mode: 'challenge-v1'
}

Usage

The result's shape depends on the account the user chooses, so always check it with isChallengeSignIn:

import { isChallengeSignIn } from '@joeywallet/wallet-sdk'

const result = await joey.signIn({
  statement: 'Sign in to Example',
  nonce: nonceFromYourServer,
})

if (isChallengeSignIn(result)) {
  // Ledger hardware account: verify result.signedTx on your server
} else {
  // Software account: verify result.signature over result.message with result.publicKey
}

The CAIP-122 message

Joey builds the message itself. Lines are joined with \n:

<host> wants you to sign in with your XRPL account:
<address>

<statement>

URI: <origin>
Version: 1
Chain ID: xrpl:<networkId>
Nonce: <nonce>
Issued At: <ISO-8601 timestamp>
Resources:
- <uri>

The statement block appears only if you pass a statement, and the Resources block only if you pass resources.

Verifying on your server

  1. Parse message and check the host, URI, chain ID, nonce and issue time against what you expect.
  2. Check that publicKey derives address.
  3. Verify signature over the UTF-8 bytes of message, using publicKey. The signature covers the message bytes directly, so it can never be valid as a transaction signature.

Ledger challenge sign-in

Ledger devices can't sign arbitrary messages, so Joey asks a Ledger account to sign a challenge transaction instead (mode: 'challenge-v1'):

  • The transaction is a 1-drop Payment from the account to itself with Fee: '10' and Sequence: 0, so it can never be applied on-ledger.
  • Its memo contains the hex-encoded JSON {"wallet":"joey","challenge":"<nonce>"}.
  • signedTx is the JSON of the signed transaction. To verify: parse it, check the memo's challenge, verify the signature, and bind the session to the account that actually signed.

This is the same format the Joey mobile app returns for WalletConnect sign-in, so one server-side verifier can handle both.