KaspireBack home

Kaspire Extension provider v1 · Kaspa Mainnet

Connect to Kaspire Extension.

Use the injected window.kaspire provider for direct, origin-bound browser connections. No WalletConnect project, pairing URI, QR code, relay, or mobile handoff is required.

Add extension
01 / Architecture

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 Extension

Detect window.kaspire, call the provider, and receive origin-scoped approval.

Kaspire Mobile

Create WalletConnect v2 sessions and use the verified Kaspire App Link or desktop QR flow.

Do not use WalletConnect for the extension

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.

02 / Quick start

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.

03 / Connections

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.
Addresses are opaque account identifiers

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.

04 / Provider API

Supported methods

requestAccounts

Prompts the user and grants this origin access to the selected address.

getAccounts

Returns the connected address or an empty array without prompting.

getNetwork

Returns mainnet in the current store build.

getPublicKey

Returns the selected signing wallet's x-only public key.

getBalance

Returns verified KAS, UTXO, asset and activity snapshot data.

getUtxoEntries

Returns live UTXOs for transaction builders.

signMessage

Creates a reviewed KIP-5 personal-message signature.

sendKaspa

Builds, reviews, signs and broadcasts a native KAS payment.

sendKRC20

Performs the reviewed KRC-20 commit/reveal flow.

transferKRC721

Transfers an exactly identified NFT owned by the selected wallet.

transferKNS

Transfers an exactly identified KNS asset owned by the selected wallet.

sendKCC20

Transfers verified legacy KCC20 or KRON-native covenant tokens.

signPskt

Reviews SafeJSON and signs only the explicitly selected inputs.

signPolicyTransaction

Uses the stricter native KasCoven create/heartbeat policy.

pushTx

Broadcasts a complete signed SafeJSON transaction deliberately supplied by the dApp.

disconnect

Revokes 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"
  }
});
05 / Assets

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.
06 / Transaction builders

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.

Signing is not broadcasting

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.

07 / Errors

Handle rejection and locked state explicitly

4001User rejected the connection, signature or transaction
4100Origin not connected, wallet locked, unavailable or watch-only
4200Unsupported 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 request
try {
  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.

08 / Security

Production checklist

  • Offer separate “Kaspire Mobile” and “Kaspire Extension” buttons.
  • Call requestAccounts only 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 accountsChanged and disconnect.
  • Do not treat a resolved promise as an on-chain confirmation unless the method documents broadcast.
  • Do not automatically call pushTx after 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.

09 / Testing

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.