@chipi-stack/starknet-connector
v0.5.0
Published
Connect with Chipi: a starknet-react / get-starknet connector and Wallet Standard wallet for the Chipi hosted smart-account wallet (passkey, gasless). Works next to injected wallets like Bramble, Ready and Braavos. No browser extension, no WalletConnect.
Readme
@chipi-stack/starknet-connector
"Connect with Chipi" for Starknet dApps. A starknet-react / get-starknet connector that opens the user's Chipi wallet (passkey, gasless) in a popup. No browser extension, no WalletConnect, no whitelisting. It works next to injected wallets such as Bramble, Ready and Braavos.
npm install @chipi-stack/starknet-connectorShips ESM and CommonJS. Versions before 0.3.0 were ESM-only and failed under CommonJS
resolvers (plain Node, tsx, ts-node, Jest) with ERR_PACKAGE_PATH_NOT_EXPORTED.
Use it with starknet-react
Offer Chipi next to Bramble, Ready and Braavos:
import type { ReactNode } from "react";
import {
StarknetConfig,
InjectedConnector,
publicProvider,
ready,
braavos,
} from "@starknet-react/core";
import { mainnet } from "@starknet-react/chains";
import { chipi } from "@chipi-stack/starknet-connector";
const connectors = [
chipi(), // "Connect with Chipi"
new InjectedConnector({ options: { id: "bramble", name: "Bramble" } }),
ready(), // Ready (formerly Argent X)
braavos(),
];
export function Providers({ children }: { children: ReactNode }) {
return (
<StarknetConfig chains={[mainnet]} provider={publicProvider()} connectors={connectors}>
{children}
</StarknetConfig>
);
}Your useConnect / useAccount flow now offers all four. With Chipi, account.execute(...)
and account.signMessage(...) go to the user's Chipi wallet: gasless, signed with their
passkey.
Notes for @starknet-react/core 5:
- List every wallet yourself.
useInjectedConnectorsonly picks up objects with the legacy keys (provider,isConnected,enable,isPreauthorized). Neitherwindow.starknet_chipinorwindow.starknet_bramblehas all of them, so it finds neither. injected({ id: "bramble" })also works, but in 5.0.3injected()takes onlyid. The label is then the wallet's own name once Bramble has injected, and"bramble"before that.argent()is an alias ofready(). Useready().InjectedConnectorlooks upwindow.starknet_brambleeach time it is used, so Bramble's late injection is fine.autoConnectruns once on mount, though: if Bramble has not injected by then, the last Bramble session is not restored.
Use it with get-starknet v4
get-starknet v4 (@starknet-io/get-starknet-core) lists every window.starknet* object that
has id, name, version, icon, request, on and off. Creating a Chipi connector puts
window.starknet_chipi on the page, so both wallets show up:
import { getStarknet } from "@starknet-io/get-starknet-core";
import { chipi } from "@chipi-stack/starknet-connector";
declare const connectButton: HTMLButtonElement;
declare function pickedWalletId(): "chipi" | "bramble"; // from your wallet picker
// Puts window.starknet_chipi on the page.
chipi();
// Bramble injects window.starknet_bramble after page load, then fires
// "starknet#initialized". Scan after that, or after a short timeout.
function waitForInjectedWallets(timeoutMs = 3000): Promise<void> {
return new Promise((resolve) => {
if ("starknet_bramble" in window) return resolve();
const done = () => {
clearTimeout(timer);
window.removeEventListener("starknet#initialized", done);
resolve();
};
const timer = setTimeout(done, timeoutMs);
window.addEventListener("starknet#initialized", done);
});
}
await waitForInjectedWallets();
const gsc = getStarknet();
const wallets = await gsc.getAvailableWallets();
// Both are listed: wallets.map((w) => w.id) includes "chipi" and "bramble".
// Prompts only from a click: Chipi opens its popup, Bramble its side panel.
connectButton.onclick = async () => {
const wallet = wallets.find((w) => w.id === pickedWalletId());
if (wallet) await gsc.enable(wallet);
};getAuthorizedWallets() and getLastConnectedWallet() send wallet_getPermissions to every
wallet at page load. Chipi answers that locally with [] and does not open its popup (see
Passive calls). There is no silent reconnect: the user clicks connect again
on each page load.
Bramble's guide notes that get-starknet v3 also requires a provider key, which Bramble lacks.
Chipi's object lacks it too.
Wallet Standard (get-starknet v5 and v6)
@starknet-io/get-starknet-discovery (5.0.0 and 6.0.6 ship the same code) builds its list in
createStore():
- Wallet Standard wallets register through
wallet-standard:register-wallet, at any time. window.starknet*objects are scanned once, whencreateStore()runs, and wrapped as injected wallets. Entries are de-duplicated by name, and a Wallet Standard entry wins.
Bramble has no Wallet Standard registration, so get-starknet v5/v6 lists it only if
window.starknet_bramble exists when createStore() runs. Register Chipi as a Wallet Standard
wallet so the order does not matter:
import { registerChipiStandardWallet } from "@chipi-stack/starknet-connector";
const unregister = registerChipiStandardWallet({
walletUrl: "https://wallet.chipipay.com", // default
});
// later: unregister();It dispatches wallet-standard:register-wallet and answers wallet-standard:app-ready, with no
extra dependency. It exposes starknet:walletApi, standard:connect, standard:disconnect and
standard:events on starknet:0x534e5f4d41494e (mainnet), backed by the same popup.
connect({ silent: true }) never opens the popup. Its default name is "Chipi"; the
connector's is "Connect with Chipi". If window.starknet_chipi also exists when the store is
created, the store lists two Chipi entries: pass register: false to the connector, or give both
the same name. createChipiStandardWallet(options) builds the wallet
without registering it.
Works next to Bramble
- No id collision. Chipi is
window.starknet_chipiwith id"chipi". Bramble iswindow.starknet_bramblewith id"bramble". get-starknet v4 and starknet-react key wallets by id, so both are listed. - Neither writes
window.starknet. - When each object exists.
window.starknet_chipiexists only after aChipiConnectoris created (unlessregister: false).disconnect()removes it until the nextconnect().window.starknet_brambleappears after page load, followed by astarknet#initializedevent. - A wallet scan never opens Chipi. Since 0.5.0, the calls scanners send at page load are answered without opening the popup (see Passive calls).
- Bramble's own rules apply to Bramble. Prompts only from a click, at most 5 pending prompts
per site, strict params (an extra key fails with
114), a typed-datachainIdequal to its active network, nowallet_disconnect(114), one account per site. Signatures are[r, s]for OpenZeppelin and Braavos accounts and["1", "0", pub, r, s]for Argent accounts: verify them with the account'sis_valid_signature, not by length. starknet-react'sInjectedConnector.disconnect()does not sendwallet_disconnect. See Bramble rules to follow.
How it works
your dApp Chipi hosted wallet (wallet.chipipay.com)
───────── ──────────────────────────────────────────
ChipiConnector.connect()
└─ opens a popup ───────────► /connect (the user's authenticated wallet)
└─ postMessage ◄──────────► approval UI (passkey + gasless paymaster)
account.execute(calls)
└─ wallet_addInvokeTransaction ─► user approves ─► sponsored tx ─► tx hash- The connector only carries messages. It opens the hosted wallet in a popup and forwards
get-starknet
wallet_*calls overpostMessage. It holds no keys and signs nothing. - Each request is posted right away and matched to its answer by id. Several can be in flight at once; the connector neither queues nor rejects them.
account()wraps the channel in a starknet.jsWalletAccount, so the dApp gets a standardAccountInterface.- There is no silent reconnect.
ready()istrueonly afterconnect()succeeded on this page. Callconnect()from a click: it opens a popup. - Chipi is mainnet-only.
chainId()always returnsSN_MAIN.
Passive calls
While no wallet window is open (before connect(), or after the user closed it), the connector
answers these calls itself and does not open the popup. Once the window is open, they go to the
wallet.
| Call | Answer with no wallet window open |
| ------------------------------------------------- | --------------------------------- |
| wallet_getPermissions | [] |
| wallet_requestAccounts with silent_mode: true | [] |
| wallet_requestChainId | "0x534e5f4d41494e" (SN_MAIN) |
wallet_requestAccounts without silent_mode: true opens the popup, so send it from a click.
Options
import { chipi } from "@chipi-stack/starknet-connector";
chipi({
walletUrl: "https://wallet.chipipay.com", // hosted wallet base URL (default)
id: "chipi", // connector id (default); the window key stays starknet_chipi
name: "Connect with Chipi", // display name (default)
icon: "data:image/png;base64,...", // default: a 48x48 PNG data URI
register: true, // put window.starknet_chipi on the page (default; no-op in SSR)
transport: {
windowName: "chipi-wallet", // default: a new random name per open
windowFeatures: "popup=yes,width=420,height=680", // default
openTimeoutMs: 180_000, // wait for the wallet page to load (default)
requestTimeoutMs: 120_000, // per request; 0 disables (default)
},
});chipi(options) is the same as new ChipiConnector(options).
Errors
The connector rejects with an Error. Check err.code first, then err.message.
| code or message | When |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
| code "UNSUPPORTED_CHAIN" | connect({ chainIdHint }) with a chain other than SN_MAIN. No popup opens. |
| code "popup_blocked" | The browser blocked the popup: connect() did not run from a click. |
| message "wallet_open_timeout" | The wallet page did not load within transport.openTimeoutMs (180 s). The popup closes. |
| code "REQUEST_TIMEOUT" | No answer within transport.requestTimeoutMs (120 s). |
| code "POPUP_CLOSED" | A request after the user closed the wallet window. Call connect() again from a click. |
| code 4001, message "User closed the wallet" | The user closed the wallet window while a request was waiting. |
| code 4001, message "disconnected" | disconnect() ran while a request, or the wallet page load, was waiting. |
| message "chipi_no_account" | The wallet returned no account on connect. |
| message "chipi_not_connected" | account() before connect(). |
| message "chipi_connector_requires_browser" | The popup was requested without window (SSR). |
| numeric code from the wallet (113 … 163) | The wallet's own error, passed through with its message (see below). |
Wallets built on createChipiWindowObject from @chipi-stack/chipi-react answer 113 (the
user refused), 114 (no calls, or typed data with no chainId or one for another chain), 115 (account
already deployed), 116 (deployment data not available) and 163 (unsupported method,
including wallet_disconnect and wallet_switchStarknetChain, or another failure).
Events
- The wallet's
accountsChanged/networkChangedbecome starknet-reactchangeevents ({ account, chainId }). An empty account list emitsdisconnect. disconnect()closes the popup and removeswindow.starknet_chipi. The nextconnect()puts it back.
Compatibility
@starknet-react/core>= 3 (peer), tested against 5.0.3.starknet>= 8.1.2 (peer).WalletAccount.connectis the same in 8 and 9.- Starknet mainnet only.
- Popup only. The in-app-browser ("parent") mode is in the transport but not exposed.
MIT · docs.chipipay.com · example
