Documentation: spare-wallets-kit

Documentation

spare-wallets-kit

Add Spare to an app that signs with Stellar Wallets Kit: before each signature, the user sees “Add US$0.50 for [foundation]?” and decides. Nothing is added without an explicit yes. Spare never holds funds.

Install

pnpm add spare-wallets-kit spare-sdk @creit.tech/stellar-wallets-kit @stellar/stellar-sdk

Quickstart

import { StellarWalletsKit } from "@creit.tech/stellar-wallets-kit";
import { Networks } from "@stellar/stellar-sdk";
import { HorizonAccountPort } from "spare-sdk";
import { withSpare } from "spare-wallets-kit";

const spare = withSpare(StellarWalletsKit, {
  foundation: { account: "G...", domain: "foundation.org" }, // listed in its stellar.toml
  foundationName: "Tellus Cooperative",
  mode: { amount: "0.50" }, // or { roundUp: true } for USDC purchases
  appId: "my-app", // <= 22 ASCII bytes
  maxAmount: "5",
  accounts: new HorizonAccountPort("https://horizon.stellar.org"),
  networkPassphrase: Networks.PUBLIC,
  locale: "en",
});

// Same signature as the kit: use it wherever you called kit.signTransaction.
const { signedTxXdr } = await spare.signTransaction(xdr, { address });

Need to know whether the hash changed? Use signWithDonation: it returns { signedTxXdr, donation, changed, outcome }.

Soroban follow-up

A Soroban transaction holds one operation, so the donation is a separate payment after your call succeeds. It needs a second signature, and the modal says so.

// A Soroban tx holds one operation: the donation is a separate payment after success.
const follow = await spare.offerFollowUp({ successful: true, sourceAccount: address, hash });
if (follow.status === "signed") await submit(follow.signedTxXdr); // submitting is your job

API

ExportWhat it does
withSpare(kit, options)Returns { signTransaction, signWithDonation, offerFollowUp, lastDonation }.
createConfirmModal({ locale?, theme? })Default confirmation UI (vanilla DOM, shadow root). Resolves true only on the primary button.

Options

OptionRequiredDescription
foundationyes{ account, domain }. Domain-verified through SEP-1 stellar.toml.
foundationNamenoName shown to the user. Defaults to the domain.
modeyes{ amount: "0.50" } or { roundUp: true } (USDC purchases only).
appIdyesTag for the spare:<appId> memo.
maxAmountyesYour cap per donation, in USDC.
accountsyesAccountPort from spare-sdk (for example HorizonAccountPort).
fetchnoCustom fetch for stellar.toml lookups.
networkPassphrasenoUsed to parse XDR when the call does not pass one. Without either, the transaction is signed unchanged.
confirmno(prompt: DonationPrompt) => Promise<boolean>. Replaces the built-in modal.
locale / themeno"en" | "es"; "light" | "dark" | "auto" for the built-in modal.
walletnoWallet id (or getter). Defaults to kit.selectedModule.productId. LEDGER shows the “check your Ledger” note.
followUpAmountnoSoroban follow-up amount. Defaults to the fixed mode.amount; required with round-up.

Results

signWithDonation returns { signedTxXdr, donation, changed, outcome, reason? }. outcome is one of donated, declined, not-offered, passthrough or error. donation is null when the original transaction was signed, and changed is true when the signed transaction is not the one you passed in.

offerFollowUp resolves to { status: "signed", signedTxXdr, donation } or { status: "skipped", reason }. Submitting is your job.

DonationPrompt and custom confirm

DonationPrompt is { amount, foundationName, foundationDomain, mode: "fixed" | "round-up", wallet: "classic" | "soroban" | "ledger", locale, secondSignature, purchaseTotal?, roundUpTotal? }. purchaseTotal and roundUpTotal come straight from spare-sdk's donation.purchase / donation.roundedTotal. Pass your own confirm to replace the modal; return true only on an explicit yes.

Pass-through rules

  • Only unsigned, non-fee-bump, classic, single-source-signer transactions are modified. Adding an operation changes the hash and the fee, so anything else (partly signed, multisig, extra signers, fee-bump, Soroban) is signed unchanged and no donation is offered.
  • A failed check in spare-sdk (unverified foundation, no trustline, low balance, over the cap) means no prompt and the original is signed.
  • The user declines: the original XDR is signed, byte for byte.
  • Any error inside Spare (bad XDR, lookup failure, confirm crashing) fails closed: the original is signed. Errors from the wallet itself, for example the user rejecting in the wallet, are not swallowed.

role="dialog", aria-modal, labelled title, focus trap, Esc skips, focus returns to the opener, prefers-reduced-motion respected. It shows “Domain-verified: <domain>” under the title: the foundation's stellar.toml lists its account, nothing more. Focus starts on the dialog, not on a button, the primary button ignores activation for the first 400 ms, and the rest of the page is inert while it is open. Styles live in a shadow root. Fonts (Instrument Sans, Newsreader) are not bundled: load them yourself or the system fallbacks apply.