Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/noir-wallet-zcash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@swapkit/wallet-extensions": minor
"@swapkit/wallets": minor
---

Add Noir Wallet connector for Zcash (`connectNoirWallet`). Noir Wallet is a shielded-first Zcash browser extension; the connector delegates balance and transaction building to the extension and supports deposit-address swap routes (e.g. NEAR Intents). OP_RETURN memo routes are rejected with `wallet_noir_wallet_memo_not_supported`. `NOIR_WALLET` and the `wallet_noir_wallet_*` error codes (80101-80103) register through the `@swapkit/helpers` 5.1.0 extensible registries.
17 changes: 17 additions & 0 deletions .changeset/swapkit-sdk-36el3mv6gxftc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@swapkit/sdk": patch
"@swapkit/wallet-extensions": patch
"@swapkit/wallet-hardware": patch
"@swapkit/wallet-mobile": patch
"@swapkit/wallets": patch
---

Update SwapKit SDK dependencies:

- @swapkit/core: 5.0.2 → 5.0.3
- @swapkit/helpers: 5.0.2 → 5.1.0
- @swapkit/plugins: 5.0.0 → 5.0.3
- @swapkit/server: 5.0.0 → 5.1.1
- @swapkit/toolboxes: 5.1.1 → 5.1.2
- @swapkit/wallet-core: 5.0.0 → 5.0.3
- @swapkit/wallet-keystore: 5.0.0 → 5.0.3
5 changes: 5 additions & 0 deletions .changeset/tonconnect-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@swapkit/wallets": patch
---

`TON_CONNECT` now registers through the `@swapkit/helpers` 5.1.0 extensible `WalletOption` registry (swapkit/sdk#346) instead of the interim outside-the-enum literal shim. No behavior change — the runtime value stays `"TON_CONNECT"`.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SwapKit Wallets

Wallet connectors for the [SwapKit SDK](https://github.com/swapkit/sdk):
browser extensions, hardware devices, and mobile/protocol wallets, exposed
through a single `loadWallet` entry point and typed `SKWallets` registry.

## Packages

| Package | Contents |
| --- | --- |
| [`@swapkit/wallets`](packages/wallets) | aggregator: `loadWallet`, `SKWallets`/`SKWalletsSupportedChains` types, and connectors without their own package (keystore, tonconnect, xaman, radix, passkeys, …) |
| [`@swapkit/wallet-extensions`](packages/wallet-extensions) | injected browser-extension providers (MetaMask/EVM, Keplr, Vultisig, Ctrl, Phantom, Petra, Noir Wallet, …) |
| [`@swapkit/wallet-hardware`](packages/wallet-hardware) | Ledger, Trezor, KeepKey |
| [`@swapkit/wallet-mobile`](packages/wallet-mobile) | mobile wallet support |
| [`@swapkit/sdk`](packages/sdk) | batteries-included bundle re-exporting core, plugins, toolboxes, and wallets |

Each connector is a lazy-loaded subpath export — apps pull only the wallets
they use.

## Development

Requires [bun](https://bun.sh) — use the version pinned in
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) to keep `bun.lock`
reproducible.

```bash
bun install
bun run build:ci # build all packages + .d.ts generation
bun run type-check # tsc across packages
bun test # bun test across packages
bun run lint # biome check --fix
bun run playground:vite-lite:start # browser playground
```

## Adding a wallet

See [docs/adding-a-wallet.md](docs/adding-a-wallet.md) — covers the connector
module, registering the `WalletOption` and error codes through the
`@swapkit/helpers` extensible registries (no helpers release required), the
`loadWallet`/`SKWallets` wiring, and the gotchas.

## Releases

Versioning goes through [changesets](https://github.com/changesets/changesets):
every user-facing change ships a `.changeset/*.md` entry, and `@swapkit/*`
dependency bumps additionally need the generated changeset from
`bun generate:dep-changeset` (CI enforces it). Merges to `develop` roll up
into a "Version Packages" PR; merging that publishes to npm.
78 changes: 39 additions & 39 deletions bun.lock

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions docs/adding-a-wallet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Adding a wallet connector

How to add a new wallet to this repo, including registering its `WalletOption`
and error codes through the extensible registries that shipped with
`@swapkit/helpers` 5.1.0 ([swapkit/sdk#346]). No `@swapkit/helpers` release is
needed to add a wallet.

Worked examples in this repo:

- `packages/wallet-extensions/src/noir-wallet/` — injected browser-extension
wallet with custom error codes (added via fork PR #111 + #144)
- `packages/wallets/src/tonconnect/` — connector living directly in
`@swapkit/wallets`, registry registration only

## Where things live

| Package | Contents |
| --- | --- |
| `@swapkit/wallet-extensions` | injected browser-extension providers (`window.*`) |
| `@swapkit/wallet-hardware` | Ledger, Trezor, KeepKey |
| `@swapkit/wallets` | aggregator: `loadWallet`, `SKWallets` types, plus a few connectors that need no separate package (tonconnect, xaman, radix, …) |

The `WalletOption` union, `SwapKitError` keys, and the registries themselves
are defined upstream in `@swapkit/helpers` (SDK monorepo).

## 1. The connector module

Create `packages/wallet-extensions/src/<wallet>/index.ts` built around
`createWallet` from `@swapkit/wallet-core`:

- `connect` receives `{ addChain, walletType }` and returns the
`connect<Name>` function. For each supported chain, spread the chain's
toolbox into `addChain({ ...toolbox, address, chain, walletType, ... })` and
override what the wallet handles itself (`transfer`, `getBalance`,
`signMessage`, `signAndBroadcastTransaction`, …).
- `directSigningSupport` maps chains where the wallet can sign an
API-prebuilt transaction (`route.tx`). With `{ [chain]: true }`, core routes
swaps to the generic SwapKit plugin, which decodes `route.tx` and calls your
`signAndBroadcastTransaction` override. With `{}`, swaps go through the
provider plugin, which calls high-level methods like `transfer`. Wallets
that only expose a "send" RPC can still support direct signing by
translating the decoded transaction back into a send — see
`extractUtxoTransferIntent` in
`packages/wallet-extensions/src/helpers/utxoTransferIntent.ts` (Vultisig,
Ctrl, KeepKey BEX).
- If the wallet cannot serve a method the toolbox spread exposes, override it
with a clear `SwapKitError` throw instead of letting the toolbox default
fail deep inside signing (see `unsupportedUtxoSignTransaction`).

## 2. The register module

Create `packages/wallet-extensions/src/<wallet>/register.ts`. The type-level
declaration and the runtime registration must sit side by side — one without
the other compiles to a trap (see [Gotchas](#gotchas)):

```ts
import { registerErrorCodes, registerWalletOption } from "@swapkit/helpers";

declare module "@swapkit/helpers" {
interface WalletOptionRegistry {
MY_WALLET: "MY_WALLET";
}
interface SwapKitErrorRegistry {
wallet_my_wallet_not_found: 80201;
}
}

registerWalletOption("MY_WALLET", "MY_WALLET");
registerErrorCodes({
wallet_my_wallet_not_found: 80201,
});
```

Rules:

- The `declare module` must target `"@swapkit/helpers"` — that package
declares the registries — even when consumers import `WalletOption` from
`@swapkit/core`.
- Error codes must use the **80000–89999 extension range**. It is reserved by
convention, not enforced at runtime; first-party codes live outside it and
collisions throw only when two keys claim the same number. Grep this repo's
registers for the next free block (noir-wallet holds 80101–80103).
- Registration is idempotent for identical values and throws
`helpers_invalid_params` on conflicting re-registration, so the module may
safely load through multiple import paths.
- Registered options are appended to `SKConfig`'s default wallet list
automatically (and survive `SKConfig.reinitialize()`).

Make the connector's `index.ts` import it first:

```ts
// Registers WalletOption.MY_WALLET and the wallet_my_wallet_* error codes
// before anything below reads them.
import "./register";
```

## 3. Package exports

Add **two** subpath exports to `packages/wallet-extensions/package.json`,
mirroring the neighbors' shape:

```jsonc
"./my-wallet": { "bun": "./src/my-wallet/index.ts", ... },
"./my-wallet/register": { "bun": "./src/my-wallet/register.ts", ... },
```

The separate `register` subpath exists so `loadWallet` (and any app that only
references `WalletOption.MY_WALLET` — wallet pickers, `walletType`
comparisons) can import a ~20-line side-effect module instead of statically
pulling the whole connector, which would defeat the lazy loading in the match
arms. Apps that import the connector subpath directly need nothing extra —
`index.ts` imports `./register` itself.

Also add the new register module to `packages/wallets/src/register.ts` — the
roll-up that `@swapkit/wallets/register` exposes to apps. An app that puts a
registered option in a module-scope wallet list (as the SwapKit UI does in its
wallet dialog) evaluates `WalletOption.MY_WALLET` before any connector loads;
its entry module must `import "@swapkit/wallets/register"` first or the list
silently contains `undefined`.

## 4. Wiring `@swapkit/wallets`

Three touch points:

- **`src/utils.ts`** — side-effect import at the top (ordering matters: the
match reads `WalletOption.MY_WALLET` at call time, before any connector has
loaded), plus a lazy match arm:

```ts
import "@swapkit/wallet-extensions/my-wallet/register";
// ...
.with(WalletOption.MY_WALLET, async () => (await import("@swapkit/wallet-extensions/my-wallet")).myWallet)
```

- **`src/types.ts`** — `SKWallets` and `SKWalletsSupportedChains` entries,
keyed `[WalletOption.MY_WALLET]:` (computed keys in type position work
because the const's properties are distinct string literals).

## 5. Tests

Alongside the usual connector tests, always assert the registered value:

```ts
test("registers MY_WALLET in the extensible WalletOption registry", () => {
expect(WalletOption.MY_WALLET).toBe("MY_WALLET");
});
```

Without this, a missing registration makes `walletType` assertions pass
vacuously: `expect(wallet.walletType).toBe(WalletOption.MY_WALLET)` is
`undefined === undefined`. This exact false-green happened in the original
Noir Wallet PR.

## 6. Changeset

Add a `.changeset/*.md` entry (`minor` for a new connector). If the change
also bumps `@swapkit/*` dependency versions, run `bun generate:dep-changeset`
and commit the generated file — CI's `check` workflow fails without it.

## Gotchas

- **Type/runtime skew.** The module augmentation is visible program-wide the
moment the `.d.ts` is in the compilation; the runtime key exists only after
`register.ts` executes. Code that reads `WalletOption.MY_WALLET` before
registration gets `undefined` — and a ts-pattern `.with(undefined, …)` arm
then matches *any* unregistered option. Keep the side-effect import above
the code that reads the key.
- **Tree-shaking.** Neither package sets `"sideEffects"` in package.json, so
bundlers keep the register imports. If `"sideEffects": false` is ever
added, the `register.ts` files must be listed as exceptions or registration
silently disappears from production bundles.
- **One `@swapkit/helpers` copy, exactly.** Published SDK packages pin exact
helpers versions. Bumping helpers alone forks the lockfile into nested
copies — two helpers instances at runtime means registrations land in one
copy while `@swapkit/core` reads the other, with no error. Bump the whole
`@swapkit` release train together, reinstall clean with the CI-pinned bun
(see `.github/workflows/ci.yml`), and verify:

```bash
grep -o '@swapkit/helpers@[0-9][^"]*' bun.lock | sort | uniq -c
```

One `5.x` entry is correct. (A legacy `4.x` copy nested under the old
`@swapkit/ui` devDep is expected and inert.)

[swapkit/sdk#346]: https://github.com/swapkit/sdk/pull/346
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"@cosmjs/proto-signing": "0.37.0",
"@cosmjs/stargate": "0.37.0",
"@scure/bip32": "2.2.0",
"@swapkit/core": "^5.0.2",
"@swapkit/helpers": "^5.0.2",
"@swapkit/plugins": "^5.0.0",
"@swapkit/server": "^5.0.0",
"@swapkit/toolboxes": "^5.1.1",
"@swapkit/wallet-core": "^5.0.0",
"@swapkit/wallet-keystore": "^5.0.0",
"@swapkit/core": "^5.0.3",
"@swapkit/helpers": "^5.1.0",
"@swapkit/plugins": "^5.0.3",
"@swapkit/server": "^5.1.1",
"@swapkit/toolboxes": "^5.1.2",
"@swapkit/wallet-core": "^5.0.3",
"@swapkit/wallet-keystore": "^5.0.3",
"@types/bun": "1.3.13",
"@types/node": "25.6.0",
"ledger-bitcoin": "0.3.0",
Expand Down
14 changes: 7 additions & 7 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"author": "swapkit-dev",
"dependencies": {
"@stricahq/typhonjs": "~3.0.1",
"@swapkit/core": "^5.0.2",
"@swapkit/helpers": "^5.0.2",
"@swapkit/plugins": "^5.0.0",
"@swapkit/server": "^5.0.0",
"@swapkit/toolboxes": "^5.1.1",
"@swapkit/wallet-core": "^5.0.0",
"@swapkit/wallet-keystore": "^5.0.0",
"@swapkit/core": "^5.0.3",
"@swapkit/helpers": "^5.1.0",
"@swapkit/plugins": "^5.0.3",
"@swapkit/server": "^5.1.1",
"@swapkit/toolboxes": "^5.1.2",
"@swapkit/wallet-core": "^5.0.3",
"@swapkit/wallet-keystore": "^5.0.3",
"@swapkit/wallets": "workspace:*",
"cosmjs-types": "0.10.1"
},
Expand Down
18 changes: 15 additions & 3 deletions packages/wallet-extensions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"@near-js/transactions": "~2.5.0",
"@scure/base": "~2.2.0",
"@solana/web3.js": "~1.98.4",
"@swapkit/helpers": "^5.0.2",
"@swapkit/toolboxes": "^5.1.1",
"@swapkit/helpers": "^5.1.0",
"@swapkit/toolboxes": "^5.1.2",
"@swapkit/utxo-signer": "^3.0.0",
"@swapkit/wallet-core": "^5.0.0",
"@swapkit/wallet-core": "^5.0.3",
"@wallet-standard/app": "^1.1.1",
"cosmjs-types": "0.10.1",
"ethers": "^6.14.0",
Expand Down Expand Up @@ -82,6 +82,18 @@
"require": "./dist/src/keplr/index.cjs",
"types": "./dist/types/keplr/index.d.ts"
},
"./noir-wallet": {
"bun": "./src/noir-wallet/index.ts",
"default": "./dist/src/noir-wallet/index.js",
"require": "./dist/src/noir-wallet/index.cjs",
"types": "./dist/types/noir-wallet/index.d.ts"
},
"./noir-wallet/register": {
"bun": "./src/noir-wallet/register.ts",
"default": "./dist/src/noir-wallet/register.js",
"require": "./dist/src/noir-wallet/register.cjs",
"types": "./dist/types/noir-wallet/register.d.ts"
},
"./okx": {
"bun": "./src/okx/index.ts",
"default": "./dist/src/okx/index.js",
Expand Down
Loading
Loading