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
- Chrome / Brave / Edge with KrayWallet extension installed.
- Unknown origins require a one-time Connection Request approval.
kray.spacesubdomains andlocalhostare auto-approved.
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:
- Algorithm: BIP-340 Schnorr over
SHA256(UTF-8 message)— a Kray-specific digest. It is not BIP-322, not the legacy “Bitcoin Signed Message” format, and not ECDSA. - Signature: 64 bytes, always returned as a 128-char hex string (never base64).
- Pubkey: 32-byte x-only hex — the BIP86 internal (untweaked) key at
m/86'/0'/0'/0/0, returned bygetPublicKey()and alongside signatures. Required for verification. - Address binding: derive
p2tr({ internalPubkey })from the provided pubkey and compare it with the claimedbc1p…address.
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
- Marketplace cancel:
KRAY:cancel-listing:<address>:<order_id>:<nonce>:<timestamp> - L2: structured JSON or
from:to:amount:noncepatterns as documented on kray.space