KrayWallet

Developers

window.krayWallet

The Chrome extension injects a provider API for approved origins. The mobile PWA does not expose this API — it is a standalone wallet client.

Listen for krayWalletReady, then call requestAccounts() from a user gesture. On page load use getAccounts() (silent) — never auto-popup requestAccounts() on every refresh.

Availability

Public API

requestAccounts()

Connect with popup (recommended for Connect button).

const result = await window.krayWallet.requestAccounts();
// { address, ... }

getAccounts()

Silent session restore. Returns [] if revoked or locked out.

const accounts = await window.krayWallet.getAccounts();

connect()

Silent connect when already unlocked and approved.

getPublicKey()

x-only public key (hex). Prefer getAccounts() for the bc1p… address.

getExtensionInfo()

// { signature, version, build, isKrayWallet: true }

getBalance() · getInscriptions(offset?, limit?) · getRunes() · getFullWalletData()

Read public wallet inventory for the connected account.

signMessage(message) use with caution

BIP-340 Schnorr sign (see Signature format below). May auto-sign non-sensitive messages while unlocked. Sensitive prefixes always force a popup.

signMessageWithConfirmation(message) recommended

Always opens the confirmation UI so the user sees exactly what they sign. Prefer this for payments, admin actions, and L2 auth. Both methods produce identical signatures.

const { signature, address } = await window.krayWallet.signMessageWithConfirmation(message);
// signature: 64-byte Schnorr, hex (128 chars)
// address:   bc1p… taproot address of the signer

Signature format (canonical)

Everything a verifier needs. This is the exact contract the extension, the mobile app, and every kray.space backend share:

Common integrator mistake: do not verify against a key extracted from the bc1p… address — those 32 bytes are the tweaked output key (BIP-341), while the wallet signs with the internal key, so that check always fails. Verify against the pubkey the wallet returns, then bind it to the address.

// Node verifier — npm i @bitcoinerlab/secp256k1 bitcoinjs-lib
import * as ecc from "@bitcoinerlab/secp256k1";
import * as bitcoin from "bitcoinjs-lib";
import { createHash } from "crypto";
bitcoin.initEccLib(ecc);

export function verifyKrayWalletMessage(message, sigHex, pubkeyHex, address) {
    const pub = Buffer.from(pubkeyHex, "hex");
    const x = pub.length === 33 ? pub.subarray(1, 33) : pub.subarray(0, 32);
    const derived = bitcoin.payments.p2tr({
        internalPubkey: x, network: bitcoin.networks.bitcoin,
    }).address;
    if (derived !== address) return false;              // anti-spoof binding
    const hash = createHash("sha256").update(message).digest();
    const sig = Buffer.from(sigHex, "hex");
    return sig.length === 64 && ecc.verifySchnorr(hash, x, sig);
}

External wallets (Unisat, Xverse, …) authenticating against kray.space backends use a separate BIP-322 (base64) path — that path never applies to window.krayWallet.signMessage.

Chat session

activateChatSession(), encryptChatMessage(msg, theirPubKey), decryptChatMessage(enc, theirPubKey), isChatSessionActive(), clearChatSession().

getNetwork() / getActiveNetwork()

Network helpers (livenet, mainnet / kray-l2).

Partner / advanced

PSBT signing, pushTx, marketplace helpers (createOffer, buyNow, buyAtomicSwap, …) exist in the extension for ecosystem partners. Documented access: partners@kray.space.

L2 from third-party apps

L2 transfers typically use kray.space REST plus a wallet signature — not a separate send method on the provider. Full platform reference: kray.space/developers.

Canonical message formats