Extension and Mobile are separate transports
Choose the Extension provider when the dApp and wallet run in the same desktop browser. Choose Kaspire Mobile when an Android wallet connects to a mobile or desktop website through an encrypted WalletConnect session. A dApp may offer both buttons.
Kaspire ExtensionDetect window.kaspire, call the provider, and receive origin-scoped approval.
Kaspire MobileCreate WalletConnect v2 sessions and use the verified Kaspire App Link or desktop QR flow.
The browser extension has its own provider. Do not generate awc: URI, show a QR code, or redirect to the mobile download page when window.kaspire?.isKaspire is available.
Detect the provider and request an account
Kaspire injects the provider at document start. Check the property immediately and also listen for kaspire#initialized so asynchronous page bundles work consistently.
function detectKaspire(timeoutMs = 3000) {
if (window.kaspire?.isKaspire) return Promise.resolve(window.kaspire);
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error("Kaspire Extension was not detected.")),
timeoutMs
);
window.addEventListener("kaspire#initialized", () => {
clearTimeout(timeout);
resolve(window.kaspire);
}, { once: true });
});
}
const kaspire = await detectKaspire();type KaspireError = Error & { code?: number };
interface KaspireProvider {
readonly isKaspire: true;
readonly version: string;
request<T = unknown>(input: {
method: string;
params?: unknown;
}): Promise<T>;
on(event: "accountsChanged" | "networkChanged" | "disconnect",
listener: (data: unknown) => void): this;
removeListener(event: string, listener: (data: unknown) => void): this;
}const accounts = await kaspire.request<string[]>({
method: "requestAccounts"
});
// The user reviews the requesting origin in an extension-owned window.
const address = accounts[0]; // full kaspa:q... address
if (!address) throw new Error("No Kaspire account was approved.");Call requestAccounts only after the user clicks a visible “Connect Kaspire Extension” button. Kaspire opens an extension-owned approval window showing the exact requesting origin and selected public address.
Restore permission and follow wallet changes
Permissions are scoped to the exact origin. Subdomains, ports, HTTP and HTTPS origins are distinct. getAccountsnever opens an approval window and returns an empty array when the current origin is disconnected.
const accounts = await kaspire.request<string[]>({
method: "getAccounts"
});
if (accounts.length === 0) {
// This origin is not connected. Show a Connect Kaspire button.
}
const network = await kaspire.request<string>({ method: "getNetwork" });
// Kaspire Extension 0.3.20 is Mainnet-only: network === "mainnet"const onAccounts = (accounts) => {
selectedAddress = accounts[0] ?? null;
renderWalletState();
};
kaspire.on("accountsChanged", onAccounts);
kaspire.on("disconnect", () => {
selectedAddress = null;
renderDisconnectedState();
});
// When your component unmounts:
kaspire.removeListener("accountsChanged", onAccounts);await kaspire.request({ method: "disconnect" });
// The extension removes the permission for this exact origin and emits
// accountsChanged([]) plus disconnect to the page.The selected address may belong to a BIP-44 account or an address-index subwallet. Never infer a derivation path. Always use the returned full kaspa:q... address and replace cached UI state after accountsChanged.
Supported methods
requestAccountsPrompts the user and grants this origin access to the selected address.
getAccountsReturns the connected address or an empty array without prompting.
getNetworkReturns mainnet in the current store build.
getPublicKeyReturns the selected signing wallet's x-only public key.
getBalanceReturns verified KAS, UTXO, asset and activity snapshot data.
getUtxoEntriesReturns live UTXOs for transaction builders.
signMessageCreates a reviewed KIP-5 personal-message signature.
sendKaspaBuilds, reviews, signs and broadcasts a native KAS payment.
sendKRC20Performs the reviewed KRC-20 commit/reveal flow.
transferKRC721Transfers an exactly identified NFT owned by the selected wallet.
transferKNSTransfers an exactly identified KNS asset owned by the selected wallet.
sendKCC20Transfers verified legacy KCC20 or KRON-native covenant tokens.
signPsktReviews SafeJSON and signs only the explicitly selected inputs.
signPolicyTransactionUses the stricter native KasCoven create/heartbeat policy.
pushTxBroadcasts a complete signed SafeJSON transaction deliberately supplied by the dApp.
disconnectRevokes the current origin's connection.
Read wallet state
const publicKey = await kaspire.request<string>({
method: "getPublicKey"
});
const snapshot = await kaspire.request({ method: "getBalance" });
// {
// balanceSompi, balanceKas, utxoCount, utxos,
// assets: { tokens, domains, krc721, kcc20, transactions },
// transactions
// }
const utxos = await kaspire.request({ method: "getUtxoEntries" });Treat snapshot metadata as display data. Before signing, Kaspire independently reloads and validates the data required by the requested operation.
KIP-5 personal signatures
const result = await kaspire.request<{
address: string;
signature: string;
}>({
method: "signMessage",
params: {
address,
message: [
"Sign in to Example dApp",
"Domain: " + location.host,
"Address: " + address,
"Nonce: " + serverNonce,
"Issued At: " + new Date().toISOString()
].join("\n")
}
});Generate login nonces server-side, bind them to the domain and account, expire them quickly, verify the signature server-side, and consume each nonce exactly once.
Native KAS
const transactionId = await kaspire.request<string>({
method: "sendKaspa",
params: {
from: address,
to: "kaspa:q...",
amountSompi: "100000000"
}
});Use exact asset identifiers and raw units
Asset methods verify that the selected wallet owns enough of the requested asset. They open extension-owned reviews and never expose seed material to the dApp. Recipient values must be full Mainnet addresses; resolve a KNS recipient in your dApp before submitting the request.
KRC-20
const result = await kaspire.request({
method: "sendKRC20",
params: {
from: address,
to: "kaspa:q...",
ticker: "KASBTC",
amount: "100000000"
}
});
// {
// kind: "krc20", commitTransactionId, revealTransactionId,
// commitFeeSompi, revealFeeSompi
// }KRC-721 and KNS
const nft = await kaspire.request({
method: "transferKRC721",
params: {
from: address,
to: "kaspa:q...",
ticker: "COLLECTION",
tokenId: "the exact owned token ID"
}
});
const name = await kaspire.request({
method: "transferKNS",
params: {
from: address,
to: "kaspa:q...",
assetId: "64 lowercase hexadecimal characters followed by i0"
}
});Legacy KCC20 and KRON
const transactionId = await kaspire.request<string>({
method: "sendKCC20",
params: {
from: address,
to: "kaspa:q...",
covenantId: "64 lowercase hexadecimal characters",
amount: "100000000"
}
});
// The same method supports verified legacy KCC20 and KRON-native tokens.
// Kaspire discovers the standard from the covenant ID; never trust a ticker
// supplied by the dApp as the asset identity.- Use base-10 integer strings representing raw token units.
- Use uppercase tickers where a ticker is required.
- Use the covenant ID—not a ticker—as KCC20/KRON identity.
- Do not infer completion until the provider promise resolves.
- KRC transfers require commit and reveal approvals and may remain pending while the commit confirms.
Generic PSKT, policies and broadcast
const signedTxJson = await kaspire.request<string>({
method: "signPskt",
params: {
sender: address,
txJsonString: draft.txJson,
options: {
signInputs: [
{ index: 0, sighashType: 1 } // SIGHASH_ALL
]
}
}
});
// Kaspire signs only the reviewed input set. Your dApp may combine/finalize
// the PSKT and deliberately request broadcast afterwards.signPskt is dApp-independent and is the correct method for marketplaces, listings, purchases, KRC-721/KNS transaction builders and general covenant flows. Kaspire parses every embedded UTXO, rejects duplicate outpoints and inconsistent fields, calculates wallet effects and fees, shows every output, binds the review hash, and signs only the selected inputs.
signPskt returns signed SafeJSON but does not broadcast it. This allows multisigner and marketplace flows to combine signatures. Call pushTx only when your protocol has a complete transaction and the user deliberately initiated submission.
const signed = await kaspire.request({
method: "signPolicyTransaction",
params: {
sender: address,
txJsonString: draft.txJson,
signInputIndexes: [0, 1],
redeemScript: draft.redeemScript
}
});
// { signedTxJson, profile, reviewHash }
const transactionId = await kaspire.request<string>({
method: "pushTx",
params: signed.signedTxJson
});Use signPolicyTransaction only for the recognized KasCoven vault create and DMS heartbeat profiles. Other dApps should use generic signPskt rather than requesting a custom Kaspire-specific integration.
Handle rejection and locked state explicitly
4001User rejected the connection, signature or transaction4100Origin not connected, wallet locked, unavailable or watch-only4200Unsupported method, network or asset operation-32602Malformed parameters, wrong account or invalid asset identifier-32000Signing, verification or broadcast failed safely-32603The extension background service could not complete the requesttry {
await kaspire.request({ method: "sendKaspa", params });
} catch (error) {
const failure = error as KaspireError;
if (failure.code === 4001) {
showStatus("Request rejected by the user.");
} else if (failure.code === 4100) {
showStatus("Connect or unlock Kaspire first.");
} else {
showStatus(failure.message);
}
}Never retry a signature or payment automatically. Keep a visible pending state while approval is open, clear it on rejection, and require a new user action for every retry. Provider requests may remain open for multi-step confirmation; use a six-minute UI timeout rather than a short HTTP-style timeout.
Production checklist
- Offer separate “Kaspire Mobile” and “Kaspire Extension” buttons.
- Call
requestAccountsonly after a deliberate click. - Never ask users to paste recovery phrases or private keys into a dApp.
- Never request a signature whose exact meaning is hidden from the user.
- Use server-generated, expiring, one-time nonces for authentication.
- Represent KAS and token amounts as base-10 raw integer strings.
- Validate addresses and asset identifiers before opening the wallet.
- Bind application state to the returned account and current origin.
- React to
accountsChangedanddisconnect. - Do not treat a resolved promise as an on-chain confirmation unless the method documents broadcast.
- Do not automatically call
pushTxafter a generic PSKT signature unless that is the user-visible flow.
Kaspire guarantees that its extension-owned review is bound to the locally reconstructed transaction. It cannot certify a dApp's marketplace price, royalty model, contract intent or business rules. The user must still verify the requesting domain and displayed transaction.
Test connection, approval and recovery paths
- Extension missing, installed but locked, and watch-only wallet
- Connection approval and rejection for each production origin
- Wallet switching, account changes and explicit disconnect
- Malformed address, amount, covenant ID, NFT ID and KNS asset ID
- Insufficient KAS, token balance, fragmented UTXOs and indexer failure
- Rejected KRC commit, delayed commit confirmation and rejected/resumed reveal
- PSKT duplicate outpoints, wrong sender, mutable sighash warnings and partial signatures
- Background service-worker restart while a wallet session is unlocked
- Broadcast rejection and mismatching transaction IDs
Start with a low-value Mainnet wallet. The current public extension is Mainnet-only even though reserved testnet plumbing remains in the source for future releases.

