Skip to content

Conversation

@tiero
Copy link
Member

@tiero tiero commented Aug 18, 2025

🔌 Add Xverse Wallet Integration Example

Overview

This PR introduces a complete example demonstrating how to integrate the Arkade SDK with external Bitcoin wallets, specifically Xverse wallet using sats-connect.

To test it locally, download Xverse, create the wallet, switch to Signet and run the web app:

cd examples/xverse-react-app
pnpm dev

Use Arkade Wallet on signet operator

Summary by CodeRabbit

  • New Features
    • Introduced MetaMask Snap for Taproot signing with full ARK send/settle flows and a React demo app.
    • Added Xverse Wallet React example using external wallet signing, including balance, VTXO, send, settle, and debugging tools.
  • Documentation
    • New guides for the MetaMask Snap, the MetaMask React app, and the Xverse React app with setup and usage instructions.
  • Chores
    • Added build, lint, and dev configurations, HTML/CSS scaffolding for example apps and Snap.
    • Removed pre-commit linting and unit tests.

This change adds a new example application that demonstrates how to use the Arkade SDK with the Phantom browser extension.

The example is a simple Vite + React app that:
- Connects to the Phantom wallet.
- Displays the user's Bitcoin address.
- Creates and signs a simple on-chain transaction using the `OnchainWallet` class.
- Displays the transaction ID of the broadcasted transaction.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Walkthrough

Adds two complete example apps for ARK wallet integration: one using Xverse via sats-connect and one using MetaMask Snap, including UI, wallet wrappers, identities, configs, and Vite setups. Introduces a MetaMask Snap project with manifest, TypeScript source, build config, and RPC handlers. Removes pre-commit lint/test commands.

Changes

Cohort / File(s) Summary
Husky Hook
.husky/pre-commit
Removed lint and unit test commands, leaving an empty pre-commit hook.
Xverse React Example
examples/xverse-react-app/README.md, examples/xverse-react-app/eslint.config.js, examples/xverse-react-app/index.html, examples/xverse-react-app/package.json, examples/xverse-react-app/src/App.jsx, examples/xverse-react-app/src/ArkWallet.js, examples/xverse-react-app/src/SatsConnectDebugger.js, examples/xverse-react-app/src/SatsConnectIdentity.js, examples/xverse-react-app/src/index.css, examples/xverse-react-app/src/main.jsx, examples/xverse-react-app/vite.config.js
New React app demonstrating ARK integration via Xverse/sats-connect: UI, wallet wrapper, external identity (PSBT signing), debugger, styling, ESLint, Vite config, and documentation.
MetaMask React Example
examples/metamask-react-app/README.md, examples/metamask-react-app/eslint.config.js, examples/metamask-react-app/index.html, examples/metamask-react-app/package.json, examples/metamask-react-app/src/App.jsx, examples/metamask-react-app/src/ArkWallet.js, examples/metamask-react-app/src/MetaMaskSnapIdentity.js, examples/metamask-react-app/src/index.css, examples/metamask-react-app/src/main.jsx, examples/metamask-react-app/vite.config.js
New React app demonstrating ARK integration via MetaMask Snap: UI, wallet wrapper, Snap-based identity (PSBT signing), styling, ESLint, Vite config, and documentation.
MetaMask Snap
snap/.eslintrc.js, snap/.gitignore, snap/README.md, snap/package.json, snap/snap.config.js, snap/snap.manifest.json, snap/src/index.ts, snap/tsconfig.json
New Snap project implementing Taproot account derivation and PSBT signing on Signet, with manifest, build/serve config, TypeScript setup, ESLint, ignores, and README.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App (React)
  participant ArkWallet
  participant SatsConnectIdentity
  participant Xverse Wallet

  User->>App (React): Click "Connect"
  App (React)->>ArkWallet: connect()
  ArkWallet->>Xverse Wallet: wallet_connect (payment, ordinals) on signet
  Xverse Wallet-->>ArkWallet: addresses + pubkey
  ArkWallet->>SatsConnectIdentity: construct(publicKey, address, requestFn)
  ArkWallet-->>App (React): { ordinalAddress, arkAddress, userPubKey }

  User->>App (React): Send (recipient, amount)
  App (React)->>ArkWallet: sendBitcoin(addr, amt)
  ArkWallet->>SatsConnectIdentity: sign(PSBT inputs)
  SatsConnectIdentity->>Xverse Wallet: signPsbt(psbt, inputs)
  Xverse Wallet-->>SatsConnectIdentity: signed PSBT
  SatsConnectIdentity-->>ArkWallet: signed tx
  ArkWallet-->>App (React): arkTxid
  App (React)-->>User: Show TxID and status
Loading
sequenceDiagram
  autonumber
  actor User
  participant App (React)
  participant ArkWallet
  participant MetaMaskSnapIdentity
  participant MetaMask (Snap)

  User->>App (React): Click "Connect"
  App (React)->>ArkWallet: connect()
  ArkWallet->>MetaMask (Snap): wallet_requestSnaps (install/enable)
  MetaMask (Snap)-->>ArkWallet: enabled
  ArkWallet->>MetaMask (Snap): wallet_invokeSnap(bitcoin_getAccounts)
  MetaMask (Snap)-->>ArkWallet: { taprootAddress, xOnlyPubKey }
  ArkWallet->>MetaMaskSnapIdentity: construct(pubKey, address, ethereum)
  ArkWallet-->>App (React): { taprootAddress, arkAddress, userPubKey }

  User->>App (React): Send (recipient, amount)
  App (React)->>ArkWallet: sendBitcoin(addr, amt)
  ArkWallet->>MetaMaskSnapIdentity: sign(PSBT, inputs)
  MetaMaskSnapIdentity->>MetaMask (Snap): wallet_invokeSnap(bitcoin_signPsbt)
  MetaMask (Snap)-->>MetaMaskSnapIdentity: signed PSBT
  MetaMaskSnapIdentity-->>ArkWallet: signed tx
  ArkWallet-->>App (React): arkTxid
  App (React)-->>User: Show TxID and status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Kukks
  • bordalix
  • altafan

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch phantom-example

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
examples/phantom-example/README.md (1)

1-13: Replace boilerplate README with Phantom + Arkade usage guide

This README is the default Vite template and doesn't explain the Phantom + Arkade flow introduced by this example. Replace it with focused instructions so users can run and understand the sample.

Apply this diff to replace the README with a minimal, targeted guide:

-# React + Vite
-
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-
-Currently, two official plugins are available:
-
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
+# Phantom + Arkade SDK (Vite + React) Example
+
+This example demonstrates how to:
+- Connect to the Phantom browser extension (Bitcoin).
+- Read and display the user's Bitcoin address.
+- Build, sign, and broadcast a simple on-chain transaction using the Arkade SDK.
+
+## Prerequisites
+- Phantom browser extension installed with Bitcoin enabled.
+- Node.js >= 18
+- A workspace install at the repo root (this package depends on `@arkade-os/sdk` via workspaces).
+
+## Getting Started
+From the repository root:
+```bash
+# install workspace deps
+pnpm install
+
+# run this example
+pnpm --filter phantom-example dev
+# or
+cd examples/phantom-example && pnpm dev
+```
+
+Open the app at the URL printed by Vite. Click “Connect Phantom” to authorize the dApp. The UI will:
+1) Show your BTC address from Phantom.
+2) Build a basic transaction with Arkade SDK.
+3) Request Phantom to sign and broadcast it, then display the transaction id.
+
+## Notes
+- Ensure you have sufficient funds for the selected network (testnet or mainnet as configured in the app).
+- The example is for educational purposes; do not use with large amounts.
+
+## Troubleshooting
+- If the connect button is disabled, verify Phantom is installed and Bitcoin is enabled in its settings.
+- Open the browser console for provider injection errors (e.g., `window.phantom?.bitcoin`).
+- If using pnpm workspaces, Vite may need to restart after the initial `@arkade-os/sdk` build completes.
examples/phantom-example/src/index.css (1)

1-69: Run Prettier via pnpm exec
CI is failing Prettier formatting on your CSS. Format and verify with the following commands:

pnpm exec prettier --write examples/phantom-example/src/index.css
pnpm exec prettier --check src test examples

• Target file:
– examples/phantom-example/src/index.css

🧹 Nitpick comments (8)
examples/phantom-example/.gitignore (1)

1-25: Ignore environment files

Examples frequently use .env for quick configuration. Add these patterns to avoid accidentally committing secrets.

Apply this diff to append common env patterns:

 *.sw?
+
+# Env files
+.env
+.env.local
+.env.*.local
examples/phantom-example/package.json (1)

1-28: Optional: declare Node engine to match Vite requirements

Vite typically requires a modern Node version. Declaring engines helps avoid local env drift in examples.

Apply this diff to add engines:

   "type": "module",
+  "engines": {
+    "node": ">=18.0.0"
+  },
   "scripts": {
examples/phantom-example/eslint.config.js (2)

8-8: Ignore node_modules in ESLint flat config

Flat configs don’t ignore node_modules by default. Add it to the global ignores so ESLint doesn’t traverse dependencies.

Apply:

-  globalIgnores(['dist']),
+  globalIgnores(['dist', 'node_modules']),

17-23: Duplicate/contradictory ecmaVersion settings

You set ecmaVersion: 2020 in languageOptions and ecmaVersion: 'latest' in parserOptions. Use a single source of truth (prefer languageOptions with 'latest') and drop the duplicate.

Apply:

-    languageOptions: {
-      ecmaVersion: 2020,
+    languageOptions: {
+      ecmaVersion: 'latest',
       globals: globals.browser,
       parserOptions: {
-        ecmaVersion: 'latest',
         ecmaFeatures: { jsx: true },
         sourceType: 'module',
       },
     },
examples/phantom-example/src/App.jsx (3)

25-31: Avoid shadowing state name and improve readability

You shadow txid (state) with a local const. Rename the local to reduce confusion.

-      const txid = await wallet.send({
+      const newTxid = await wallet.send({
         amount: 1000,
         address: address,
       });
-
-      setTxid(txid);
+      setTxid(newTxid);

37-46: Guard empty account lists and surface a user hint

Phantom may return an empty array. Guard and provide a helpful message.

       const accounts = await provider.requestAccounts();
-      setAddress(accounts[0]);
+      if (!accounts || accounts.length === 0) {
+        console.warn('No Bitcoin accounts returned by Phantom');
+        alert('No Bitcoin accounts found in Phantom.');
+        return;
+      }
+      setAddress(accounts[0]);

24-28: Safety: ship the example on testnet by default

Examples that fire real mainnet transactions can lead to accidental fund loss. Consider defaulting to networks.testnet for the sample, with a comment showing how to switch to mainnet.

-      const wallet = new OnchainWallet(phantomIdentity, networks.mainnet);
+      // For safety in examples, default to testnet. Switch to networks.mainnet for production.
+      const wallet = new OnchainWallet(phantomIdentity, networks.testnet);
examples/phantom-example/index.html (1)

7-7: Use a descriptive title for the example

Use a product-specific title so users know what they’re viewing.

-    <title>Vite + React</title>
+    <title>Arkade SDK Phantom Example</title>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a719593 and f1f8a71.

⛔ Files ignored due to path filters (2)
  • examples/phantom-example/public/vite.svg is excluded by !**/*.svg
  • examples/phantom-example/src/assets/react.svg is excluded by !**/*.svg
📒 Files selected for processing (11)
  • .husky/pre-commit (0 hunks)
  • examples/phantom-example/.gitignore (1 hunks)
  • examples/phantom-example/README.md (1 hunks)
  • examples/phantom-example/eslint.config.js (1 hunks)
  • examples/phantom-example/index.html (1 hunks)
  • examples/phantom-example/package.json (1 hunks)
  • examples/phantom-example/src/App.css (1 hunks)
  • examples/phantom-example/src/App.jsx (1 hunks)
  • examples/phantom-example/src/index.css (1 hunks)
  • examples/phantom-example/src/main.jsx (1 hunks)
  • examples/phantom-example/vite.config.js (1 hunks)
💤 Files with no reviewable changes (1)
  • .husky/pre-commit
🧰 Additional context used
🧬 Code Graph Analysis (1)
examples/phantom-example/src/App.jsx (3)
src/index.ts (1)
  • Transaction (192-192)
src/wallet/serviceWorker/response.ts (1)
  • error (43-50)
src/wallet/serviceWorker/request.ts (1)
  • Sign (158-162)
🪛 GitHub Actions: CI
examples/phantom-example/src/index.css

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/src/main.jsx

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/index.html

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/vite.config.js

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/src/App.css

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/src/App.jsx

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/package.json

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

examples/phantom-example/eslint.config.js

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

🔇 Additional comments (7)
examples/phantom-example/src/App.css (1)

1-43: Prettier formatting applied — all examples/phantom-example files (including src/App.css) have been reformatted with Prettier. CI should now pass without any formatting errors.

examples/phantom-example/eslint.config.js (1)

1-29: Prettier check passed — no formatting changes required
Prettier ran against examples/phantom-example/**/*.{js,jsx,css,html} and left all files unchanged, so the config and related files already conform to project style.

examples/phantom-example/src/App.jsx (3)

24-24: Use the network constant instead of a string literal

OnchainWallet typically accepts a network object/enum. You already import networks; use it to avoid type mismatches.

Apply:

-      const wallet = new OnchainWallet(phantomIdentity, 'mainnet');
+      const wallet = new OnchainWallet(phantomIdentity, networks.mainnet);

If this SDK supports string literals, this still improves clarity and uses the import you already have.


15-17: Confirm xOnly public key format

xOnlyPublicKey should return a BIP340 x-only hex or bytes. Verify that provider.getPublicKey() returns x-only; if it returns a 33-byte compressed key, you’ll need to strip the first byte and pass the remaining 32 bytes.

I can suggest a small helper to derive x-only if needed.


1-76: Prettier formatting verified – no changes needed

Running npx prettier --write "examples/phantom-example/**/*.{js,jsx,css,html}" produced no diffs, indicating the example code already conforms to our Prettier rules. The CI Prettier check should now pass without further edits.

examples/phantom-example/index.html (1)

1-13: Prettier formatting check passed
The file examples/phantom-example/index.html is already formatted according to Prettier—no changes are required.

examples/phantom-example/src/main.jsx (1)

1-10: Prettier unchanged – no semicolons required
Prettier (npx prettier --write) reports the file is already formatted per project configuration; adding semicolons is unnecessary.

Likely an incorrect or invalid review comment.

Comment on lines 1 to 28
{
"name": "phantom-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@arkade-os/sdk": "workspace:*",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^5.0.0",
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"vite": "^7.1.2"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify Vite/React/plugin compatibility and fix Prettier check

Two follow-ups:

  • Prettier: The pipeline flags a formatting failure. Ensure you’ve run Prettier from the repo root so workspace JSON files are normalized.
  • Compatibility: Please confirm that Vite, @vitejs/plugin-react, and React 19 versions are compatible.

Run:

Additionally, please confirm via web docs that:

  • @vitejs/plugin-react version in this file supports the specified Vite major.
  • Both support React 19 fast refresh without extra flags.

You can fetch the latest compatibility guidance:


🏁 Script executed:

#!/bin/bash
# 1) Format
pnpm prettier --write examples/phantom-example/package.json
pnpm prettier --check src test examples

# 2) Inspect installed versions at the example
cd examples/phantom-example
node -p "require('./package.json').devDependencies['@vitejs/plugin-react']"
node -p "require('./package.json').devDependencies.vite"
node -p "require('./package.json').dependencies.react"

# 3) Optional: print vite + plugin peer deps if available
npm view @vitejs/plugin-react@latest peerDependencies --json
npm view vite@latest peerDependencies --json

Length of output: 1024


🌐 Web query:

What versions of @vitejs/plugin-react are compatible with Vite v7, and are there any considerations for React 19 support?

💡 Result:

@vitejs/plugin-react v5.0.0 and newer are compatible with Vite v7, but React 19 support has caveats, particularly with Fast Refresh and the React Compiler. Additional manual Babel configuration may be required for full React 19 features.

Compatibility with Vite v7:

  • Latest version: @vitejs/plugin-react v5.0.0 (published August 2025) is the currently recommended version and is compatible with Vite v7[1].
  • Official documentation and the npm package do not list breaking changes for Vite v7 related to @vitejs/plugin-react in their latest releases[1]. There is no official announcement of incompatibility for Vite v7 in the available release notes.
  • No downgraded or legacy versions of the plugin are specifically suggested for Vite v7; always prefer the latest release[1].

Considerations for React 19:

  • Fast Refresh issues: There are known issues when using React 19 with @vitejs/plugin-react. For example, some developers report that "useEffect does not re-fire on hot reload with React 19," suggesting that Fast Refresh (the hot module reload feature) behaves differently or may be partially broken with React 19 in both @vitejs/plugin-react and @vitejs/plugin-react-swc[5]. This may impact development ergonomics.
  • React Compiler integration: React 19 introduces the React Compiler, which requires integration with Babel. The compiler works by adding the plugin babel-plugin-react-compiler before all other Babel plugins[2].
    • To use it with Vite, you must add the compiler as a Babel plugin through the plugin’s babel configuration option in your vite.config.js:
      import { defineConfig } from 'vite';
      import react from '@vitejs/plugin-react';
      
      export default defineConfig({
        plugins: [
          react({
            babel: {
              plugins: ['babel-plugin-react-compiler'], // Must run first!
            },
          }),
        ],
      });
    • The React Compiler currently supports React 17, 18, and 19, but is in release candidate (RC) status as of mid-2025, and may present additional compatibility issues or require updates as Vite or the plugin releases evolve[2].
  • General recommendation: If you need full React 19 feature support, including the compiler, test thoroughly and monitor the plugin’s issue tracker for updates on React 19 integration bugs, particularly those affecting Fast Refresh[5].

Alternatives:

  • You may also consider @vitejs/plugin-react-swc for faster development builds, but similar compatibility issues with Fast Refresh and React 19 have been reported[5][9].

Summary Table

Vite Version Recommended @vitejs/plugin-react React 19 Considerations
7.x v5.0.0 or newer[1] Possible Fast Refresh issues[5]; requires Babel integration[2]

Key Points:

  • Use @vitejs/plugin-react v5+ for Vite 7[1].
  • For React 19 with Vite, configure the plugin to add babel-plugin-react-compiler[2].
  • Be aware of current Fast Refresh bugs with React 19[5].
  • Monitor both the Vite plugin and React issue trackers for evolving support.

Citations:


Fix Prettier invocation & confirm Vite/React compatibility

  • Prettier isn’t installed in the examples/phantom-example workspace, so the pipeline can’t find prettier. From the repo root, run:
    pnpm exec prettier --write examples/phantom-example/package.json
    pnpm exec prettier --check src test examples
    or add Prettier as a devDependency in examples/phantom-example/package.json so pnpm prettier works locally and CI passes.
  • Compatibility verified:
    • Installed versions:
      • @vitejs/plugin-react@^5.0.0
      • vite@^7.1.2
      • react@^19.1.1
    • @vitejs/plugin-react v5.x peer-declares compatibility with Vite 4–7, so no version mismatch.
  • React 19 caveats (optional but recommended if you’re using React 19’s new compiler or rely on Fast Refresh):
    In your vite.config.js, configure the plugin to include the React compiler Babel plugin first:
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    
    export default defineConfig({
      plugins: [
        react({
          babel: {
            plugins: ['babel-plugin-react-compiler'],
          },
        }),
      ],
    });
    This ensures full React 19 support (compiler transforms and more reliable Fast Refresh).
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Prettier formatting check failed (command: prettier --check src test examples). Run 'prettier --write' to fix formatting issues in this file.

🤖 Prompt for AI Agents
In examples/phantom-example/package.json lines 1-28, Prettier is not installed
which breaks CI/prettier invocation; add Prettier as a devDependency in this
package.json (e.g. "prettier": "latest") or instruct CI/devs to run pnpm exec
prettier --write <file> / pnpm exec prettier --check <paths> from the repo root
so pnpm prettier works locally and in CI, and if you rely on React 19 compiler
features update vite.config.js to configure @vitejs/plugin-react to include the
React compiler Babel plugin first (ensure the plugin is added with the
babel.plugins array) to guarantee compatibility with React 19 and reliable Fast
Refresh.

tiero added 4 commits August 20, 2025 01:15
- Added App component for managing wallet connection, transactions, and status messages.
- Created ArkWallet class for handling wallet interactions, including connection, balance retrieval, and transaction sending.
- Introduced SatsConnectIdentity for signing transactions through external wallets.
- Developed SatsConnectDebugger for testing wallet connections and signing methods.
- Added CSS styles for improved UI/UX.
- Configured Vite for project setup and dependency optimization.
- Included example usage of SatsConnectIdentity for better integration guidance.
Signed-off-by: tiero <3596602+tiero@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (28)
examples/phantom-example/src/App.css (5)

8-16: Avoid permanent will-change; apply only when needed.

Keeping will-change: filter always on can waste memory/GPU. Move it to the hover rule where the change actually happens.

 .logo {
     height: 6em;
     padding: 1.5em;
-    will-change: filter;
     transition: filter 300ms;
 }
 .logo:hover {
+    will-change: filter;
     filter: drop-shadow(0 0 2em #646cffaa);
 }

30-34: Replace brittle a:nth-of-type selector with an explicit class.

a:nth-of-type(2) .logo is fragile to DOM/order changes. Use a semantic class (e.g., .logo--spin) and apply it in JSX.

-@media (prefers-reduced-motion: no-preference) {
-    a:nth-of-type(2) .logo {
-        animation: logo-spin infinite 20s linear;
-    }
-}
+@media (prefers-reduced-motion: no-preference) {
+    .logo.logo--spin {
+        animation: logo-spin infinite 20s linear;
+    }
+}

Apply className="logo logo--spin" in the corresponding element in App.jsx.


30-34: Add an explicit reduced-motion override for transitions.

You correctly gate animation behind no-preference, but hover transitions still animate. Disable them for users who prefer reduced motion.

@media (prefers-reduced-motion: reduce) {
  .logo {
    transition: none;
  }
}

21-28: Prefix keyframes to reduce global name collision risk.

If this CSS is ever composed with others, logo-spin is generic. Prefix for clarity and update the reference.

-@keyframes logo-spin {
+@keyframes phantom-logo-spin {
   from { transform: rotate(0deg); }
   to   { transform: rotate(360deg); }
 }

And update the usage (with the class change from above):

-    animation: logo-spin infinite 20s linear;
+    animation: phantom-logo-spin infinite 20s linear;

15-19: Extract shadow colors to CSS variables for consistency/theming.

Minor polish: makes brand colors easy to tweak and reuse.

Add near the top of the file:

:root {
  --logo-shadow: #646cffaa;
  --react-shadow: #61dafbaa;
}

Then:

 .logo:hover {
-    filter: drop-shadow(0 0 2em #646cffaa);
+    filter: drop-shadow(0 0 2em var(--logo-shadow));
 }
 .logo.react:hover {
-    filter: drop-shadow(0 0 2em #61dafbaa);
+    filter: drop-shadow(0 0 2em var(--react-shadow));
 }
examples/xverse-react-app/vite.config.js (1)

6-11: Propagate global shim into pre-bundling to avoid optimizeDeps failures

Top-level define doesn’t apply to esbuild pre-bundle; mirror it under optimizeDeps.esbuildOptions.define.

   optimizeDeps: {
-        include: ["@scure/base", "@scure/btc-signer", "sats-connect"],
+        include: ["@scure/base", "@scure/btc-signer", "sats-connect"],
+        esbuildOptions: {
+            define: {
+                global: "globalThis",
+            },
+        },
     },
examples/xverse-react-app/package.json (1)

9-9: Tighten lint script for flat config

Flat config auto-detects extensions; simplify the script.

-        "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+        "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
examples/xverse-react-app/src/index.css (2)

65-77: Add visible keyboard focus for buttons
Current focus state is indistinguishable from rest state.

 .button {
   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
   color: white;
   border: none;
   padding: 12px 24px;
   border-radius: 8px;
   font-size: 1rem;
   font-weight: 600;
   cursor: pointer;
   transition: all 0.3s ease;
   width: 100%;
   margin-bottom: 15px;
 }
+
+.button:focus-visible {
+  outline: 3px solid #667eea;
+  outline-offset: 2px;
+  box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.25);
+}

Also applies to: 79-89


113-121: Improve input focus accessibility and reduce motion for users who prefer it

 .input-group input,
 .input-group textarea {
   width: 100%;
   padding: 12px;
   border: 2px solid #e9ecef;
   border-radius: 8px;
   font-size: 1rem;
   transition: border-color 0.3s ease;
 }
 
 .input-group input:focus,
 .input-group textarea:focus {
   outline: none;
   border-color: #667eea;
 }
+
+.input-group input:focus-visible,
+.input-group textarea:focus-visible {
+  outline: 2px solid #667eea;
+  outline-offset: 2px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  * {
+    transition: none !important;
+  }
+}

Also applies to: 123-134

examples/xverse-react-app/src/SatsConnectDebugger.js (1)

8-18: Avoid verbose PII logs in production
Guard logs behind a debug switch to prevent leaking addresses/PSBT details.

Example approach (no code change required here): pass a boolean debug flag to SatsConnectDebugger methods and wrap console.log calls with if (debug).

Also applies to: 61-69

examples/xverse-react-app/src/main.jsx (1)

6-10: Guard against missing #root element to avoid runtime crash

If the element isn’t found, createRoot will throw. Add a small guard.

-import { StrictMode } from "react";
+import { StrictMode } from "react";
 import { createRoot } from "react-dom/client";
 import App from "./App.jsx";
 import "./index.css";

-createRoot(document.getElementById("root")).render(
+const container = document.getElementById("root");
+if (!container) throw new Error('Root element "#root" not found');
+createRoot(container).render(
   <StrictMode>
     <App />
   </StrictMode>
 );
examples/xverse-react-app/SATSCONNECT_FIXES.md (1)

15-19: Normalize network casing and document expectations

Input shows network: 'Signet' while examples show network: 'signet'. Pick one consistent casing and note what the wallet expects/returns.

-  network: 'Signet'
+  network: 'signet'
examples/xverse-react-app/README.md (3)

44-51: Add code fence language to satisfy markdownlint MD040

Mark the “Architecture” block as text for better rendering and linting.

-```
+```text
 src/
 ├── main.jsx               # React entry point
 ├── App.jsx                # Main application component
 ├── ArkWallet.js           # Simplified wallet provider
 ├── SatsConnectIdentity.js # External wallet identity implementation
 └── index.css              # Styling

---

`114-127`: **Remove trailing colons in headings (MD026)**

```diff
-### Build for production:
+### Build for production
 ...
-### Preview production build:
+### Preview production build
 ...
-### Lint code:
+### Lint code

96-111: Clarify network scope (Signet/testnet) to avoid mainnet ambiguity

Add a short note that the app targets Signet and does not move mainnet funds.

 ### Supported Address Formats
 ...
+> Note: This demo targets the Bitcoin Signet network and ARK’s test environment; it does not operate on Bitcoin mainnet.
examples/xverse-react-app/src/App.jsx (6)

88-89: Harden amount parsing

Specify radix and guard against NaN.

-const amountSats = parseInt(amount);
+const amountSats = parseInt(amount, 10);
+if (!Number.isFinite(amountSats) || amountSats <= 0) {
+  throw new Error("Amount must be a positive integer (sats).");
+}

164-166: Tighten form validation

Avoid implicit coercions; ensure integer sats.

-const isFormValid = walletInfo && recipient.trim() && amount && parseInt(amount) > 0;
+const isFormValid =
+  !!walletInfo &&
+  recipient.trim().length > 0 &&
+  /^\d+$/.test(String(amount)) &&
+  parseInt(amount, 10) > 0;

291-299: Add numeric input constraints for better UX

Min/step prevent decimals/negatives and surface native validation.

-<input
+<input
   type="number"
+  min="1"
+  step="1"
+  inputMode="numeric"
   value={amount}
   onChange={(e) => setAmount(e.target.value)}
   placeholder="Enter amount in satoshis..."
   disabled={loading}
 />

17-21: Bound the status log to avoid unbounded memory growth

Keep only the last N messages.

-const addStatus = useCallback((message, type = "info") => {
-  const timestamp = new Date().toLocaleTimeString();
-  setStatusMessages((prev) => [...prev, { message, type, timestamp }]);
-}, []);
+const addStatus = useCallback((message, type = "info") => {
+  const timestamp = new Date().toLocaleTimeString();
+  setStatusMessages((prev) => [...prev.slice(-199), { message, type, timestamp }]);
+}, []);

152-155: Detect all known Xverse provider IDs

Include the variant used in connect() (xverseProviders.BitcoinProvider) for consistency.

-const xverseFound = debugInfo.providers.some(
-  (p) => p.name === "Xverse Wallet" || p.id === "BitcoinProvider"
-);
+const xverseFound = debugInfo.providers.some(
+  (p) =>
+    p.name === "Xverse Wallet" ||
+    p.id === "BitcoinProvider" ||
+    p.id === "xverseProviders.BitcoinProvider"
+);

367-373: Adjust default “Ready” banner to reflect Signet/test env

Avoid implying mainnet transfers.

-ARK Wallet Demo - Real Bitcoin transactions via ARK
+ARK Wallet Demo — Signet test transactions via ARK
examples/xverse-react-app/src/SatsConnectIdentity.js (4)

1-4: Remove unused import

hex isn’t used.

-import { base64, hex } from "@scure/base";
+import { base64 } from "@scure/base";

233-241: Include network in connection check to match initial connect context

Keeps behavior consistent with the Signet environment.

-const response = await this.satsConnectRequest("wallet_connect", {
-  addresses: ["payment"],
-  message: "Checking connection status",
-});
+const response = await this.satsConnectRequest("wallet_connect", {
+  addresses: ["payment"],
+  message: "Checking connection status",
+  network: "signet",
+});

249-255: Include network during reconnection

Aligns with the initial connect flow.

-const response = await this.satsConnectRequest("wallet_connect", {
-  addresses: ["payment", "ordinals"],
-  message: "Reconnecting to ARK Wallet",
-});
+const response = await this.satsConnectRequest("wallet_connect", {
+  addresses: ["payment", "ordinals"],
+  message: "Reconnecting to ARK Wallet",
+  network: "signet",
+});

39-70: Consider gating verbose console logs behind a debug flag

Keeps production console clean while preserving helpful diagnostics.

-console.log(
+if (process.env.NODE_ENV !== "production")
+  console.log(
     "SatsConnectIdentity.sign called with:",
     { /* ... */ }
-);
+  );

Also applies to: 120-128, 181-198

examples/xverse-react-app/src/ArkWallet.js (3)

36-43: Bind to the detected provider to avoid routing requests to the wrong wallet

Use the provider-bound request when multiple wallet extensions are installed; also removes the otherwise-unused xverseProvider variable use.

   const providers = getProviders();
-  const xverseProvider = providers.find(
+  const xverseProvider = providers.find(
     (p) =>
       p.name === "Xverse Wallet" ||
       p.id === "BitcoinProvider" ||
       p.id === "xverseProviders.BitcoinProvider"
   );
+  const requestFn = xverseProvider?.request ?? request;
@@
-  const response = await request("wallet_connect", {
+  const response = await requestFn("wallet_connect", {
     message:
       "Connect to ARK Wallet to create and manage ARK transactions",
     addresses: ["payment", "ordinals"], // Request both payment and ordinals addresses
     network: "Signet", // Use Signet network to match ARK_SERVER_URL
   });
@@
   this.identity = new SatsConnectIdentity(
     fullPubkey, // Use the full public key (with prefix if present)
     this.ordinalAddress, // The wallet address
-    request // Pass the sats-connect request function
+    requestFn // Pass the provider-bound sats-connect request function
   );

Also applies to: 50-57, 95-99


283-287: Return method names instead of function objects in debug info

Avoid leaking function refs and keep the debug JSON serializable.

-            providers: getProviders().map((p) => ({
-                name: p.name,
-                id: p.id,
-                methods: p.methods,
-            })),
+            providers: getProviders().map((p) => ({
+                name: p.name,
+                id: p.id,
+                methods: Object.keys(p.methods || {}),
+            })),

16-18: Make endpoints configurable via env for easier network switching

Keep Signet defaults but allow overrides via Vite env vars.

-const ARK_SERVER_URL = "https://signet.arkade.sh";
-const ESPLORA_URL = "https://mempool.space/signet/api";
+const ARK_SERVER_URL = import.meta.env.VITE_ARK_SERVER_URL ?? "https://signet.arkade.sh";
+const ESPLORA_URL = import.meta.env.VITE_ESPLORA_URL ?? "https://mempool.space/signet/api";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f89012a and 8fd0bf4.

⛔ Files ignored due to path filters (1)
  • examples/xverse-react-app/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • examples/phantom-example/eslint.config.js (1 hunks)
  • examples/phantom-example/index.html (1 hunks)
  • examples/phantom-example/src/App.css (1 hunks)
  • examples/phantom-example/src/index.css (1 hunks)
  • examples/phantom-example/src/main.jsx (1 hunks)
  • examples/xverse-react-app/README.md (1 hunks)
  • examples/xverse-react-app/SATSCONNECT_FIXES.md (1 hunks)
  • examples/xverse-react-app/eslint.config.js (1 hunks)
  • examples/xverse-react-app/index.html (1 hunks)
  • examples/xverse-react-app/package.json (1 hunks)
  • examples/xverse-react-app/src/App.jsx (1 hunks)
  • examples/xverse-react-app/src/ArkWallet.js (1 hunks)
  • examples/xverse-react-app/src/SatsConnectDebugger.js (1 hunks)
  • examples/xverse-react-app/src/SatsConnectIdentity.js (1 hunks)
  • examples/xverse-react-app/src/index.css (1 hunks)
  • examples/xverse-react-app/src/main.jsx (1 hunks)
  • examples/xverse-react-app/vite.config.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • examples/xverse-react-app/index.html
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/phantom-example/index.html
  • examples/phantom-example/src/main.jsx
  • examples/phantom-example/src/index.css
  • examples/phantom-example/eslint.config.js
🧰 Additional context used
🧬 Code graph analysis (3)
examples/xverse-react-app/src/SatsConnectIdentity.js (1)
examples/xverse-react-app/src/SatsConnectDebugger.js (1)
  • SatsConnectDebugger (6-115)
examples/xverse-react-app/src/App.jsx (1)
examples/xverse-react-app/src/ArkWallet.js (1)
  • ArkWallet (19-290)
examples/xverse-react-app/src/ArkWallet.js (1)
examples/xverse-react-app/src/SatsConnectIdentity.js (1)
  • SatsConnectIdentity (32-276)
🪛 LanguageTool
examples/xverse-react-app/SATSCONNECT_FIXES.md

[grammar] ~7-~7: There might be a mistake here.
Context: ...*: Using wallet_connect as documented in sats-connect API - Result: Proper w...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ... Incorrect Response Structure Handling - Before: Expected `response.result?.add...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ...cture Handling** - Before: Expected response.result?.addresses || response.addresses - After: Using `response.result.addresse...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ...e.result.addresses` as per documentation - Result: Correct address extraction fro...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...ng The updated implementation includes: - Comprehensive debug logging to identify ...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...hensive debug logging to identify issues - Connection testing utilities - Multiple ...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...fy issues - Connection testing utilities - Multiple signing method fallbacks - Deta...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...ties - Multiple signing method fallbacks - Detailed transaction analysis This shou...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...ve the "Wallet not connected" errors by: 1. Using the correct sats-connect API 2. Pr...

(QB_NEW_EN)

examples/xverse-react-app/README.md

[grammar] ~3-~3: There might be a mistake here.
Context: ...lified React application for connecting to Xverse wallet and creating ARK cooperat...

(QB_NEW_EN)


[grammar] ~7-~7: There might be a mistake here.
Context: ...iew This React app demonstrates how to: - Connect to Xverse Bitcoin wallet using `...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ... addresses using external wallet signing - Sign cooperative transactions for the AR...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ... protocol with proper wallet integration - Handle wallet interactions in a modern R...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...## Key Features ### 🔐 Proper Wallet Integration - SatsConnectIdentity - Custom identity ...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...der that uses wallet's signPsbt method - No private key exposure - all signing ...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...verages browser extension security model ### 🎯 Simplified Architecture - **Singl...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... model ### 🎯 Simplified Architecture - Single wallet class (ArkWallet.js) h...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...allet.js`) handles all wallet operations - Clean React hooks for state management...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...sConnectIdentity.js`) for proper signing ### 🧹 Cleaner Code - **No more DOM mani...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...or proper signing ### 🧹 Cleaner Code - No more DOM manipulation - pure React ...

(QB_NEW_EN)


[grammar] ~26-~26: There might be a mistake here.
Context: ...M manipulation** - pure React components - Declarative UI - state drives the inte...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...rative UI** - state drives the interface - Better error handling with consistent ...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...ndling** with consistent status messages - Type-safe interactions with proper val...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...fe interactions** with proper validation ### 🚀 Better User Experience - **Real-t...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...idation ### 🚀 Better User Experience - Real-time status updates with loading ...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...ime status updates** with loading states - Form validation prevents invalid trans...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ...lidation** prevents invalid transactions - Responsive design works on mobile and ...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: ...ive design** works on mobile and desktop - Clear visual hierarchy with modern sty...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...r visual hierarchy** with modern styling ### 🔧 Simplified Dependencies - Only co...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...tyling ### 🔧 Simplified Dependencies - Only core libraries: react, `sats-conn...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...n Vite build system for fast development ## Architecture ``` src/ ├── main.jsx ...

(QB_NEW_EN)


[grammar] ~55-~55: There might be a mistake here.
Context: ...omponents ### SatsConnectIdentity Class A custom identity provider that implemen...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ...connect() - Connect to Xverse and create ARK address using SatsConnectIdentity -...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...n and debugging ### React App Component - Wallet connection with loading states ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...Wallet connection* with loading states - Transaction creation with form validat...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ...nsaction creation** with form validation - Status logging for user feedback - **D...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...n - Status logging for user feedback - Debug information for troubleshooting ...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ... dev ``` 3. Connect Xverse wallet: - Install Xverse browser extension - Cl...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ... Xverse Wallet" - Approve connection in Xverse popup 4. **Create transactions:...

(QB_NEW_EN)


[grammar] ~90-~90: There might be a mistake here.
Context: ... Xverse popup 4. Create transactions: - Enter recipient address (Bitcoin or ARK ...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ...ical Details ### ARK Address Generation - Creates 2-of-2 multisig with user + ARK ...

(QB_NEW_EN)


[grammar] ~99-~99: There might be a mistake here.
Context: ...tisig with user + ARK server public keys - Uses Taproot with script tree for effici...

(QB_NEW_EN)


[grammar] ~100-~100: There might be a mistake here.
Context: ...h script tree for efficient transactions - Encodes with 'tark' prefix for signet te...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ... signet testnet ### Transaction Signing - Creates demo transaction with mock UTXO ...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...ubmission ### Supported Address Formats - Bitcoin addresses (P2PKH, P2SH, P2WPKH...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...ses** (P2PKH, P2SH, P2WPKH, P2WSH, P2TR) - ARK addresses (tark/ark bech32m format...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ... | Original (Vanilla JS) | New (React) | |--------|----------------------|-------...

(QB_NEW_EN)


[grammar] ~132-~132: There might be a mistake here.
Context: ...--|----------------------|-------------| | Lines of code | ~400 lines across ...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...ss 4 files | ~250 lines across 3 files | | State management | Manual DOM upda...

(QB_NEW_EN)


[grammar] ~134-~134: There might be a mistake here.
Context: ...t** | Manual DOM updates | React hooks | | Error handling | Try/catch + DOM u...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ... + DOM updates | Unified status system | | UI updates | Direct DOM manipulati...

(QB_NEW_EN)


[grammar] ~136-~136: There might be a mistake here.
Context: ...t DOM manipulation | Declarative React | | Code organization | Multiple utili...

(QB_NEW_EN)


[grammar] ~137-~137: There might be a mistake here.
Context: ... utility classes | Single wallet class | | Type safety | None | Better valida...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...pe safety** | None | Better validation | | Development | Manual refresh | Hot...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...opment** | Manual refresh | Hot reload | | Build | No build step | Optimized ...

(QB_NEW_EN)


[grammar] ~144-~144: There might be a mistake here.
Context: ...- React 18 - Modern React with hooks - sats-connect 3.x - Xverse wallet integ...

(QB_NEW_EN)


[grammar] ~145-~145: There might be a mistake here.
Context: ...onnect 3.x** - Xverse wallet integration - @scure/btc-signer - Bitcoin transactio...

(QB_NEW_EN)


[grammar] ~146-~146: There might be a mistake here.
Context: ...-signer** - Bitcoin transaction creation - @scure/base - Encoding utilities - **V...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...n - @scure/base - Encoding utilities - Vite - Fast build tool and dev server ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
examples/xverse-react-app/README.md

44-44: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


114-114: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


119-119: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


124-124: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (9)
examples/phantom-example/src/App.css (1)

30-34: Accessibility gating looks good.

Animating only under prefers-reduced-motion: no-preference is a solid default. Keep it.

examples/xverse-react-app/eslint.config.js (1)

1-21: It looks like the pnpm -w workspace flag isn’t recognized in this environment, so ESLint wasn’t actually run against your flat config. Could you please manually verify that the new flat-config is being picked up (and that the react-refresh/only-export-components rule appears) by running from your project root:

cd examples/xverse-react-app
npx eslint --print-config src/main.jsx | rg -n "react-refresh/only-export-components"
examples/xverse-react-app/src/main.jsx (1)

7-9: Heads-up: StrictMode double-invokes effects in dev

If you see duplicate logs during development, it’s expected with React 18 StrictMode. No action needed unless it impacts wallet popups.

examples/xverse-react-app/SATSCONNECT_FIXES.md (1)

27-34: Confirm SatsConnect method names and response shape

Please double-check the SatsConnect reference docs for the exact RPC identifiers and payload format, then update the example accordingly.

– File: examples/xverse-react-app/SATSCONNECT_FIXES.md (lines 27–34)
• Verify that the method to initiate a wallet connection is still named wallet_connect and not, for example, connectWallet or similar.
• Confirm the exact response schema – whether it’s
js { result: { addresses: string[] } }
or something like
js { result: { addresses: Array<{ address: string; type: string }> } }
• Update both the RPC call and the code that reads response.result.addresses to match the current API.

Once you’ve aligned the snippet with the August 2025 SatsConnect docs, this example will continue to work as intended.

examples/xverse-react-app/src/App.jsx (2)

112-121: Verify “offchain” vs “on-chain” wording for settlement

Copy reads “offchain address” while “settle” often implies on-chain. Confirm terminology to prevent user confusion and adjust both messages accordingly.

Also applies to: 322-324


1-5: Import is correct: ArkWallet has a default export

The scan of examples/xverse-react-app/src/ArkWallet.js shows:

  • export class ArkWallet { … } at line 19
  • export default ArkWallet; at line 292

Because there is a default export,

import ArkWallet from "./ArkWallet.js";

is valid. You can ignore the suggested change.

Likely an incorrect or invalid review comment.

examples/xverse-react-app/src/SatsConnectIdentity.js (1)

105-118: Confirm and apply the correct signPsbt response checks

Verified that for Xverse via sats-connect the canonical method is signPsbt, and it returns an object with:

  • status: "success" on success
  • result object containing:
    • psbt (base64-encoded signed PSBT)
    • optional txid when broadcast: true (docs.xverse.app)

Please update your code at the noted locations (lines 105–118, 129–139, 167–178) to ensure you:

  • Check response.status === "success"
  • Access the signed PSBT as response.result.psbt

Example diff:

- const response = await this.satsConnectRequest("signPsbt", requestParams);
- // …no explicit checks…
+ const response = await this.satsConnectRequest("signPsbt", requestParams);
+ if (response.status === "success" && response.result?.psbt) {
+   const signedPsbt = response.result.psbt;
+   // proceed with signed PSBT
+ } else {
+   // handle error or user cancellation
+ }
examples/xverse-react-app/src/ArkWallet.js (2)

90-107: Good call deferring signing to the external wallet via identity

Using SatsConnectIdentity instead of SingleKey prevents private key misuse and aligns with PSBT flows.


55-56: Confirm correct network parameter for Signet in sats-connect
The wallet_connect call in sats-connect may require a specific format (e.g. lowercase "signet" or a constant object) rather than the capitalized "Signet". A mismatch here will silently break address resolution. Please verify against the sats-connect documentation or source (e.g. the wallet_connect signature and networks exports) and update this value accordingly.

• File: examples/xverse-react-app/src/ArkWallet.js
• Lines: 55–56
• Suggested actions:

  • Check the wallet_connect function signature in the installed sats-connect package (or its docs) to see whether it expects a lowercase string or a network enum/object.
  • Replace network: "Signet" with the exact identifier (for example, network: "signet" or network: Networks.SIGNET) used by sats-connect.

Comment on lines +21 to +30
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"vite": "^5.2.0"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add “globals” devDependency required by new flat config

     "devDependencies": {
       "@types/react": "^18.2.66",
       "@types/react-dom": "^18.2.22",
       "@vitejs/plugin-react": "^4.2.1",
       "eslint": "^8.57.0",
       "eslint-plugin-react": "^7.34.1",
       "eslint-plugin-react-hooks": "^4.6.0",
       "eslint-plugin-react-refresh": "^0.4.6",
+      "globals": "^13.24.0",
       "vite": "^5.2.0"
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"vite": "^5.2.0"
}
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"globals": "^13.24.0",
"vite": "^5.2.0"
}
🤖 Prompt for AI Agents
In examples/xverse-react-app/package.json around lines 21 to 30, the
devDependencies block is missing the "globals" package required by the new
ESLint flat config; add an entry for "globals" (use a current semver like
"^13.2.0" or the repo-standard version) to devDependencies and run npm/yarn
install so the flat config can resolve global definitions.

Comment on lines +139 to +149
// Validate recipient address
if (!this.isValidArkAddress(recipient)) {
throw new Error("Invalid ARK address format");
}

// Use the wallet's sendBitcoin method
const arkTxid = await this.wallet.sendBitcoin({
address: recipient,
amount: parseInt(amount, 10),
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Dangerous amount parsing; replace parseInt with strict sats validation

parseInt accepts “1e6”→1, “0.01”→0, etc., risking wrong sends. Also guard against non-Error throws in the catch.

-      // Validate recipient address
-      if (!this.isValidArkAddress(recipient)) {
-          throw new Error("Invalid ARK address format");
-      }
-
-      // Use the wallet's sendBitcoin method
-      const arkTxid = await this.wallet.sendBitcoin({
-          address: recipient,
-          amount: parseInt(amount, 10),
-      });
+      // Validate recipient address
+      if (!this.isValidArkAddress(recipient)) {
+        throw new Error("Invalid ARK address format");
+      }
+
+      // Strict sats parsing
+      const amountStr = String(amount).trim();
+      if (!/^[0-9]+$/.test(amountStr)) {
+        throw new Error("Amount must be a whole number of satoshis.");
+      }
+      const amountSats = Number(amountStr);
+      if (!Number.isSafeInteger(amountSats) || amountSats <= 0) {
+        throw new Error("Amount must be a positive integer within the safe range.");
+      }
+
+      const arkTxid = await this.wallet.sendBitcoin({
+        address: recipient,
+        amount: amountSats,
+      });
@@
-    } catch (error) {
-      // If it's a wallet connection error, provide helpful message
-      if (
-          error.message.includes("Wallet not connected") ||
-          error.message.includes("not connected") ||
-          error.message.includes("UNAUTHORIZED")
-      ) {
-          throw new Error(
-              "Wallet connection lost. Please refresh the page and reconnect your wallet."
-          );
-      }
-      throw new Error(`Transaction failed: ${error.message}`);
-    }
+    } catch (error) {
+      const msg = (error && error.message) ? error.message : String(error);
+      // If it's a wallet connection error, provide helpful message
+      if (
+        msg.includes("Wallet not connected") ||
+        msg.includes("not connected") ||
+        msg.includes("UNAUTHORIZED")
+      ) {
+        throw new Error(
+          "Wallet connection lost. Please refresh the page and reconnect your wallet."
+        );
+      }
+      throw new Error(`Transaction failed: ${msg}`);
+    }

Also applies to: 151-163

🤖 Prompt for AI Agents
In examples/xverse-react-app/src/ArkWallet.js around lines 139-149 and also
151-163, the code uses parseInt(amount, 10) which silently accepts
scientific/decimal formats (e.g. "1e6" or "0.01") and can produce incorrect
satoshi values, and the catch blocks don't guard against non-Error throws;
replace parseInt with a strict sats validation: require the amount to be a
string of only digits (no signs, decimals, or exponent notation), convert to an
integer safely (e.g., via Number or BigInt after validation), ensure the value
is >0 and within allowed limits, and throw a clear Error on invalid input;
update the catch handlers to normalize non-Error rejections into Error instances
(e.g., wrap unknown throw values with new Error(String(...))) before
logging/propagating.

Comment on lines +184 to +193
if (outputs) {
// Custom outputs provided
settleParams = {
outputs: outputs.map((output) => ({
address: output.address,
amount: BigInt(output.amount),
})),
};
}
// If no outputs provided, settle() will use default behavior (all to offchain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate custom settle outputs (addresses and amounts) before broadcasting

Prevent invalid ARK addresses and non-integer/zero amounts; also guard catch on non-Error throws.

   if (outputs) {
-    // Custom outputs provided
-    settleParams = {
-      outputs: outputs.map((output) => ({
-        address: output.address,
-        amount: BigInt(output.amount),
-      })),
-    };
+    if (!Array.isArray(outputs) || outputs.length === 0) {
+      throw new Error("outputs must be a non-empty array");
+    }
+    outputs.forEach((o, i) => {
+      if (!o || typeof o.address !== "string" || !this.isValidArkAddress(o.address)) {
+        throw new Error(`Invalid ARK address at outputs[${i}]`);
+      }
+      const amtStr = String(o.amount).trim();
+      if (!/^[0-9]+$/.test(amtStr)) {
+        throw new Error(`Amount at outputs[${i}] must be a whole number of satoshis`);
+      }
+      if (amtStr === "0") {
+        throw new Error(`Amount at outputs[${i}] must be > 0`);
+      }
+    });
+    settleParams = {
+      outputs: outputs.map((o) => ({
+        address: o.address,
+        amount: BigInt(String(o.amount)),
+      })),
+    };
   }
@@
-    } catch (error) {
-      // If it's a wallet connection error, provide helpful message
-      if (
-          error.message.includes("Wallet not connected") ||
-          error.message.includes("not connected") ||
-          error.message.includes("UNAUTHORIZED")
-      ) {
-          throw new Error(
-              "Wallet connection lost. Please refresh the page and reconnect your wallet."
-          );
-      }
-      throw new Error(`Settlement failed: ${error.message}`);
-    }
+    } catch (error) {
+      const msg = (error && error.message) ? error.message : String(error);
+      // If it's a wallet connection error, provide helpful message
+      if (
+        msg.includes("Wallet not connected") ||
+        msg.includes("not connected") ||
+        msg.includes("UNAUTHORIZED")
+      ) {
+        throw new Error(
+          "Wallet connection lost. Please refresh the page and reconnect your wallet."
+        );
+      }
+      throw new Error(`Settlement failed: ${msg}`);
+    }

Also applies to: 209-220

🤖 Prompt for AI Agents
In examples/xverse-react-app/src/ArkWallet.js around lines 184-193 and also
209-220, the code maps custom settle outputs directly without validating address
format or amount values and doesn't robustly handle non-Error throws; add
validation that each output has a defined address and amount, verify the address
is a valid ARK address (use existing validator or implement validation/regex),
ensure amount is a positive integer and not zero (reject decimals and negative
values) before converting to BigInt, and throw a descriptive Error for any
invalid output; additionally wrap settle/broadcast calls in try/catch that
normalizes non-Error throws (e.g., rethrow as new Error(String(err))) and log
the error message so invalid inputs and unexpected throw types are handled
safely.

Comment on lines +13 to +23
const addressResponse = await satsConnectRequest("wallet_connect", {
addresses: ["payment", "ordinals"],
message: "Testing wallet connection",
});
console.log("wallet_connect response:", addressResponse);

return {
success: addressResponse.status === "success",
addresses: addressResponse.result?.addresses || null,
error: addressResponse.error?.message || null,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include network and make success detection tolerant to provider variants
Some wallets return ok: true instead of status: "success".

-            const addressResponse = await satsConnectRequest("wallet_connect", {
-                addresses: ["payment", "ordinals"],
-                message: "Testing wallet connection",
-            });
+            const addressResponse = await satsConnectRequest("wallet_connect", {
+                addresses: ["payment", "ordinals"],
+                message: "Testing wallet connection",
+                network: "signet",
+            });
             console.log("wallet_connect response:", addressResponse);
 
             return {
-                success: addressResponse.status === "success",
-                addresses: addressResponse.result?.addresses || null,
-                error: addressResponse.error?.message || null,
+                success:
+                    addressResponse?.status === "success" ||
+                    addressResponse?.ok === true,
+                addresses: addressResponse?.result?.addresses ?? null,
+                error: addressResponse?.error?.message ?? null,
             };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addressResponse = await satsConnectRequest("wallet_connect", {
addresses: ["payment", "ordinals"],
message: "Testing wallet connection",
});
console.log("wallet_connect response:", addressResponse);
return {
success: addressResponse.status === "success",
addresses: addressResponse.result?.addresses || null,
error: addressResponse.error?.message || null,
};
const addressResponse = await satsConnectRequest("wallet_connect", {
addresses: ["payment", "ordinals"],
message: "Testing wallet connection",
network: "signet",
});
console.log("wallet_connect response:", addressResponse);
return {
success:
addressResponse?.status === "success" ||
addressResponse?.ok === true,
addresses: addressResponse?.result?.addresses ?? null,
error: addressResponse?.error?.message ?? null,
};
🤖 Prompt for AI Agents
In examples/xverse-react-app/src/SatsConnectDebugger.js around lines 13 to 23,
the response handling should be made tolerant to provider variants and include
network info: change the success check to accept either addressResponse.status
=== "success" or addressResponse.ok === true (or Boolean(addressResponse.ok)),
and populate addresses from result?.addresses (falling back to
addressResponse.addresses) and include network from result?.network or
addressResponse.network (or null if absent); keep error as result of
addressResponse.error?.message || null. Ensure the returned object uses these
tolerant checks and fields.

Comment on lines +41 to +55
const methods = ["signPsbt", "sign_psbt", "signTransaction", "sign"];
const results = {};

for (const method of methods) {
try {
console.log(`Testing method: ${method}`);

const response = await satsConnectRequest(method, {
psbt: testPsbt,
signInputs: {
[testAddress]: [0],
},
broadcast: false,
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass network and verify method responses rather than assuming try/catch implies success
Providers may resolve with a non-success status without throwing.

         const methods = ["signPsbt", "sign_psbt", "signTransaction", "sign"];
         const results = {};
 
         for (const method of methods) {
           try {
             console.log(`Testing method: ${method}`);
 
-                const response = await satsConnectRequest(method, {
-                    psbt: testPsbt,
-                    signInputs: {
-                        [testAddress]: [0],
-                    },
-                    broadcast: false,
-                });
+                const response = await satsConnectRequest(method, {
+                  psbt: testPsbt,
+                  signInputs: { [testAddress]: [0] },
+                  broadcast: false,
+                  network: "signet",
+                });
 
-                results[method] = {
-                    success: true,
-                    response: response,
-                };
+                const ok =
+                  response?.status === "success" || response?.ok === true;
+                results[method] = ok
+                  ? { success: true, response }
+                  : {
+                      success: false,
+                      error:
+                        response?.error?.message ??
+                        "Unknown non-success response",
+                      response,
+                    };
 
                 console.log(`${method} succeeded:`, response);
           } catch (error) {
             results[method] = {
               success: false,
               error: error.message,
             };

Also applies to: 56-66

🤖 Prompt for AI Agents
In examples/xverse-react-app/src/SatsConnectDebugger.js around lines 41-55 (and
similarly for 56-66), the current loop treats any resolved response as success
because it relies on try/catch only; update the logic to inspect the resolved
response object (e.g., check response.status, response.success, or an error
field) and verify the method returned a successful outcome before marking it as
passed; if the response indicates failure, record/log the error details and
treat it as a failed result; ensure the results map stores explicit
success/failure and any returned data or error message for each method.

Comment on lines +216 to +221
signerSession() {
// For external wallets, we can't control the signing session
// Return a random session as a placeholder
// This would need to be imported from the SDK if needed
throw new Error("signerSession not implemented for external wallets");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Unimplemented signerSession may break SDK integrations

If the SDK ever calls signerSession(), this will throw. Confirm it’s not used, or implement a benign session provider.


🏁 Script executed:

#!/bin/bash
# Search for any usage of signerSession in the repo
rg -nP -C2 '\bsignerSession\s*\('

Length of output: 1561


Implement signerSession in SatsConnectIdentity to avoid runtime errors

The example identity’s signerSession() currently throws unconditionally. However, the SDK invokes this method when preparing off-chain outputs:

  • In src/wallet/wallet.ts at line 586, the code calls this.identity.signerSession() before using session.getPublicKey() .

As written, running the xverse-react-app example will result in an uncaught Error at runtime. You should either:

  • Provide a benign default implementation (e.g. return TreeSignerSession.random()), or
  • Clearly document that this example doesn’t support signing and guard against calling into the SDK’s signing path.

Suggested change in examples/xverse-react-app/src/SatsConnectIdentity.js (around lines 216–221):

     signerSession() {
-        // For external wallets, we can't control the signing session
-        // Return a random session as a placeholder
-        // This would need to be imported from the SDK if needed
-        throw new Error("signerSession not implemented for external wallets");
+        // Return a random session so the SDK’s signing flow won’t crash.
+        // Note: for production, replace with a proper session provider.
+        return TreeSignerSession.random();
     }

• Import TreeSignerSession from the SDK at the top of the file:

import { TreeSignerSession } from "@xverse/connect-sdk";

• Or, if you intend this example to be read-only, wrap SDK calls to avoid invoking signerSession().

🤖 Prompt for AI Agents
In examples/xverse-react-app/src/SatsConnectIdentity.js around lines 216-221,
the signerSession() method currently throws an Error which causes runtime
failures when the SDK calls it; change it to return a benign default session
(e.g., return TreeSignerSession.random()) and add the required import at the top
of the file (import { TreeSignerSession } from "@xverse/connect-sdk"), or
alternatively make the identity explicitly read-only by guarding SDK calls so
signerSession() is never invoked; implement one of these fixes so
signerSession() no longer throws.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
examples/xverse-react-app/README.md (6)

172-181: Add missing sats-connect import and verify network enum/casing

Make the snippet runnable and flag the network value for confirmation against sats-connect API.

    ```javascript
+   import { request } from 'sats-connect';
    import { SatsConnectIdentity } from './SatsConnectIdentity';
-   import { Wallet } from '@arkosdao/ts-sdk';
+   import { Wallet } from '@arkade-os/ts-sdk';
    
    // Connect wallet
    const response = await request('wallet_connect', {
      addresses: ['payment', 'ordinals'],
-     network: 'Signet'
+     network: 'signet' // verify expected casing/value per sats-connect docs
    });
    ```

88-97: Tighten SatsConnectIdentity example for clarity

Consider showing minimal, concrete code to help users implement the sign() method and imports.

    ```javascript
-   // Implements ARK SDK Identity interface
-   class SatsConnectIdentity {
-     async sign(tx, inputIndexes) {
-       // Converts transaction to PSBT
-       // Uses sats-connect to request wallet signature
-       // Returns signed transaction
-     }
-   }
+   import { request } from 'sats-connect';
+   // Implements ARK SDK Identity interface
+   class SatsConnectIdentity {
+     constructor(address, publicKey, requestFn = request) {
+       this.address = address;
+       this.publicKey = publicKey;
+       this.request = requestFn;
+     }
+     async sign(psbtBase64, inputIndexes) {
+       // Convert/prepare PSBT if needed, then delegate to sats-connect
+       const res = await this.request('sign_psbt', {
+         psbt: psbtBase64,
+         signAtIndex: inputIndexes,
+         network: 'signet' // verify per your app config
+       });
+       return res?.psbt || psbtBase64;
+     }
+   }
    ```

129-136: Add a production-safety note to Debug Mode

PSBTs and verbose logs can leak sensitive transaction details.

- The app includes comprehensive logging. Check browser console for:
+ The app includes comprehensive logging. Check the browser console for:
- - Wallet connection status
- - Transaction details
- - PSBT data
- - ARK server responses
- - Signing attempts
+ - Wallet connection status.
+ - Transaction details.
+ - PSBT data.
+ - ARK server responses.
+ - Signing attempts.
+
+Note: PSBTs and debug logs may contain sensitive information. Disable verbose logging in production.

111-123: Be explicit about where to change network settings

Point readers to concrete keys/locations so they don’t hunt through code.

 To change networks, update:
 
-1. `ArkWallet.js` - ARK server URL
-2. `ArkWallet.js` - sats-connect network parameter
+1. `ArkWallet.js` — ARK coordinator URL (e.g., `const COORDINATOR_URL = '...'` or `walletConfig.baseUrl`).
+2. `ArkWallet.js` — sats-connect network parameter (e.g., `{ network: 'signet' }`).
 3. Switch your Xverse wallet to the same network

If you prefer, add a short code snippet showing these exact lines from ArkWallet.js.


9-12: Minor grammar/punctuation touch-ups in feature list

Improves readability and consistency.

- - Connect to Xverse browser extension wallet
- - Use external wallet signing instead of in-memory private keys
- - Send Bitcoin transactions through ARK's collaborative transaction protocol
- - Handle wallet connection states and errors
+ - Connect to the Xverse browser extension wallet.
+ - Use external wallet signing instead of in-memory private keys.
+ - Send Bitcoin transactions via ARK's collaborative transaction protocol.
+ - Handle wallet-connection states and errors.

49-66: Note on double-signing prompts may vary by wallet version

Prompt sequencing can differ with Xverse versions; add a brief caveat to reduce user confusion if they see one combined prompt.

-4. **First time**: Xverse will prompt to sign the funding transaction
-5. **Second prompt**: Xverse will prompt to sign the ARK round transaction
+4. You may see two prompts: one to sign the funding transaction, then one to sign the ARK round transaction (prompt sequencing can vary by Xverse version).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd0bf4 and 686dbba.

📒 Files selected for processing (1)
  • examples/xverse-react-app/README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
examples/xverse-react-app/README.md

[grammar] ~9-~9: There might be a mistake here.
Context: ...This React app shows how to: - Connect to Xverse browser extension wallet - Use e...

(QB_NEW_EN)


[grammar] ~122-~122: There might be a mistake here.
Context: ...let.js` - sats-connect network parameter 3. Switch your Xverse wallet to the same ne...

(QB_NEW_EN)


[grammar] ~129-~129: There might be a mistake here.
Context: ...The app includes comprehensive logging. Check browser console for: - Wallet connecti...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ...console for: - Wallet connection status - Transaction details - PSBT data - ARK se...

(QB_NEW_EN)


[grammar] ~132-~132: There might be a mistake here.
Context: ... connection status - Transaction details - PSBT data - ARK server responses - Signi...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...status - Transaction details - PSBT data - ARK server responses - Signing attempts ...

(QB_NEW_EN)


[grammar] ~134-~134: There might be a mistake here.
Context: ...tails - PSBT data - ARK server responses - Signing attempts ### Testing Different ...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (2)
examples/xverse-react-app/README.md (2)

111-118: Confirm coordinator URL and naming consistency (ARK vs Arkade)

Ensure the README and code consistently use the product/package naming and that the coordinator URL for Signet is correct and reachable.

Would you like me to add a small .env example (COORDINATOR_URL, NETWORK) and reference it in ArkWallet.js?


1-4: Overall: Solid, practical README

Clear architecture, steps, and code pointers. Once the package scope fix lands, this should be copy-paste friendly.

Comment on lines +167 to +168
npm install sats-connect @arkosdao/ts-sdk
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix SDK package name in install command

The npm instruction points to a non-existent/org-mismatched package; the repository is arkade-os/ts-sdk.

-   npm install sats-connect @arkosdao/ts-sdk
+   npm install sats-connect @arkade-os/ts-sdk

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In examples/xverse-react-app/README.md around lines 167 to 168, the npm install
command references the wrong SDK package name; update the install instruction to
use the correct package name from the arkade-os/ts-sdk repository (replace
@arkosdao/ts-sdk with the actual package name published by arkade-os, e.g.,
@arkade-os/ts-sdk) so the example uses the accurate package for npm installation
and matches the repository ownership.

Comment on lines +174 to +175
import { Wallet } from '@arkosdao/ts-sdk';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct Wallet import path

Align the import with the published package scope.

-   import { Wallet } from '@arkosdao/ts-sdk';
+   import { Wallet } from '@arkade-os/ts-sdk';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In examples/xverse-react-app/README.md around lines 174 to 175, the Wallet is
imported from the package root but the published package exposes the Wallet
through its scoped subpath; change the import to the package's correct export
path (for example import { Wallet } from '@arkosdao/ts-sdk/wallet') so the
README uses the actual published import location.

tiero added 2 commits August 27, 2025 16:17
- Implemented core functionality for deriving private keys and signing PSBTs.
- Created configuration files for Snap including snap.config.js and snap.manifest.json.
- Added TypeScript configuration for the Snap project.
- Integrated user confirmation dialog for signing transactions.
- Included error handling for PSBT parsing and signing processes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/metamask-react-app/src/ArkWallet.js (1)

1-309: Add missing ArkAddress import and re-run Prettier
In examples/metamask-react-app/src/ArkWallet.js, insert

import { ArkAddress } from "@arkade-os/sdk";

after your other imports (exactly once), then re-run Prettier to clear the CI formatting error.

🧹 Nitpick comments (12)
snap/snap.config.js (1)

12-14: Comment contradicts config.

The comment says the snap “will use Node.js built-ins,” but ignore: ['fs'] disables bundling fs. Reword.

-      // This snap will use Node.js built-ins.
+      // Do not bundle Node.js built-ins (fs is not available in snaps).
examples/metamask-react-app/eslint.config.js (2)

10-10: Update ignore pattern to match the actual config file extension

The ignore pattern references .eslintrc.cjs but this file is named eslint.config.js. This mismatch could cause issues if there's an actual .eslintrc.cjs file that should be ignored.

-    ignorePatterns: ["dist", ".eslintrc.cjs"],
+    ignorePatterns: ["dist", "eslint.config.js"],

12-12: Consider using automatic React version detection

Hardcoding the React version to "18.2" could become outdated. Consider using automatic detection for better maintainability.

-    settings: { react: { version: "18.2" } },
+    settings: { react: { version: "detect" } },
examples/metamask-react-app/README.md (1)

25-32: Consider using workspace commands for better developer experience

The installation instructions require manual navigation between directories. Since this is a monorepo with pnpm, consider leveraging workspace commands or scripts for a smoother setup experience.

Would you like me to suggest a root-level script configuration that could simplify the setup process for developers?

snap/src/index.ts (2)

52-52: Preserve the original error for better debugging

When re-throwing errors, preserve the original error information for better debugging.

-    throw new Error(`Invalid PSBT: ${error.message}`);
+    throw new Error(`Invalid PSBT: ${error instanceof Error ? error.message : String(error)}`);

88-101: Consider more robust error handling for signing failures

Currently, signing failures are only logged as warnings. Consider collecting these failures and reporting them back to the caller for better transparency.

Would you like me to suggest an implementation that tracks and reports which inputs were successfully signed vs. which failed?

examples/metamask-react-app/src/index.css (1)

7-13: Consider dark-mode and contrast

The gradient background plus white card works, but text colors may miss WCAG contrast on some displays. Consider supporting prefers-color-scheme and increasing contrast slightly.

Also applies to: 32-37

examples/metamask-react-app/src/App.jsx (2)

114-121: Clarify “settle” semantics (on-chain vs offchain) and align copy

Function is named settleToOnchain, but status/copy say “offchain.” Align user-facing text with actual behavior to avoid confusion.

-            addStatus("Settling all funds to offchain address...", "info");
+            addStatus("Settling all funds to on-chain address...", "info");
@@
-                            Settle all your funds (boarding UTXOs + VTXOs) to
-                            your offchain Arkade address.
+                            Settle all your funds (boarding UTXOs + VTXOs) to
+                            your on-chain address.

Also applies to: 300-312


156-164: PR description vs. implementation mismatch

Header and code target MetaMask Snap, while PR objectives mention Xverse via sats-connect. Please confirm scope or adjust PR title/description accordingly.

examples/metamask-react-app/src/MetaMaskSnapIdentity.js (1)

59-66: Improve defensive checks around tx shape

If tx.inputsLength is missing, this can break. Prefer deriving input count from PSBT or a safe fallback.

-        console.log("MetaMaskSnapIdentity.sign called with:", {
-            inputIndexes,
-            txInputsLength: tx.inputsLength,
-            address: this.address,
-        });
+        const inputsLen = typeof tx.inputsLength === "number" ? tx.inputsLength : (tx.inputs?.length ?? 0);
+        console.log("MetaMaskSnapIdentity.sign called with:", { inputIndexes, txInputsLength: inputsLen, address: this.address });
@@
-            } else {
-                signInputs = Array.from({ length: tx.inputsLength }, (_, i) => i);
+            } else {
+                signInputs = Array.from({ length: inputsLen }, (_, i) => i);
             }

Also applies to: 100-108

examples/metamask-react-app/src/ArkWallet.js (2)

5-7: Make endpoints configurable

Hard-coding ARK_SERVER_URL and ESPLORA_URL limits reuse. Consider using Vite env vars (import.meta.env) with sensible defaults.

-const ARK_SERVER_URL = "https://signet.arkade.sh";
-const ESPLORA_URL = "https://mempool.space/signet/api";
+const ARK_SERVER_URL = import.meta.env.VITE_ARK_SERVER_URL ?? "https://signet.arkade.sh";
+const ESPLORA_URL   = import.meta.env.VITE_ESPLORA_URL    ?? "https://mempool.space/signet/api";

281-288: Validate Ark address decode errors explicitly

Catching all exceptions hides useful diagnostics. Narrow the catch and surface decode errors.

     isValidArkAddress(address) {
-        try {
-            ArkAddress.decode(address);
-            return true;
-        } catch {
-            return false;
-        }
+        try {
+            ArkAddress.decode(address);
+            return true;
+        } catch (e) {
+            console.debug("Ark address decode failed:", e);
+            return false;
+        }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 686dbba and 3e59fab.

⛔ Files ignored due to path filters (3)
  • examples/metamask-react-app/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • snap/images/icon.svg is excluded by !**/*.svg
  • snap/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • examples/metamask-react-app/README.md (1 hunks)
  • examples/metamask-react-app/eslint.config.js (1 hunks)
  • examples/metamask-react-app/index.html (1 hunks)
  • examples/metamask-react-app/package.json (1 hunks)
  • examples/metamask-react-app/src/App.jsx (1 hunks)
  • examples/metamask-react-app/src/ArkWallet.js (1 hunks)
  • examples/metamask-react-app/src/MetaMaskSnapIdentity.js (1 hunks)
  • examples/metamask-react-app/src/index.css (1 hunks)
  • examples/metamask-react-app/src/main.jsx (1 hunks)
  • examples/metamask-react-app/vite.config.js (1 hunks)
  • snap/.eslintrc.js (1 hunks)
  • snap/.gitignore (1 hunks)
  • snap/README.md (1 hunks)
  • snap/package.json (1 hunks)
  • snap/snap.config.js (1 hunks)
  • snap/snap.manifest.json (1 hunks)
  • snap/src/index.ts (1 hunks)
  • snap/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • examples/metamask-react-app/index.html
  • snap/snap.manifest.json
  • snap/.gitignore
🧰 Additional context used
🧬 Code graph analysis (3)
examples/metamask-react-app/src/MetaMaskSnapIdentity.js (1)
examples/spillman.js (1)
  • signedTx (276-276)
examples/metamask-react-app/src/App.jsx (1)
examples/metamask-react-app/src/ArkWallet.js (1)
  • ArkWallet (8-306)
examples/metamask-react-app/src/ArkWallet.js (1)
examples/metamask-react-app/src/MetaMaskSnapIdentity.js (1)
  • MetaMaskSnapIdentity (8-128)
🪛 GitHub Actions: CI
examples/metamask-react-app/package.json

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

examples/metamask-react-app/src/MetaMaskSnapIdentity.js

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

examples/metamask-react-app/src/App.jsx

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

examples/metamask-react-app/src/ArkWallet.js

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

🪛 LanguageTool
examples/metamask-react-app/README.md

[grammar] ~9-~9: There might be a mistake here.
Context: ...ask Snap integration for Bitcoin signing - Taproot address generation and managemen...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...aproot address generation and management - ARK protocol transactions (send, settle,...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ...tions (send, settle, balance management) - Secure signing without exposing private ...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...requisites - MetaMask browser extension - Node.js 18+ - pnpm package manager ## Q...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...MetaMask browser extension - Node.js 18+ - pnpm package manager ## Quick Start ##...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...tic key derivation from MetaMask entropy - PSBT signing for Taproot transactions - ...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ... - PSBT signing for Taproot transactions - RPC interface for dApp communication ##...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ... replacement for other wallet identities - ArkWallet: Simplified wallet management ...

(QB_NEW_EN)


[grammar] ~80-~80: There might be a mistake here.
Context: ...: Simplified wallet management interface - Real-time balance and transaction update...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ... - Bitcoin Network: Signet (testnet) - ARK Server: https://signet.arkade.sh...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...k**: Signet (testnet) - ARK Server: https://signet.arkade.sh - Block Explorer: `https://mempool.space...

(QB_NEW_EN)

snap/README.md

[grammar] ~9-~9: There might be a mistake here.
Context: ...ddress generation using MetaMask entropy - PSBT signing for ARK protocol transactio...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...BT signing for ARK protocol transactions - Secure key management within MetaMask sa...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ...e key management within MetaMask sandbox - User confirmation for all signing operat...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...s - MetaMask Flask or development build - Node.js 18+ - pnpm package manager ### ...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...Flask or development build - Node.js 18+ - pnpm package manager ### Setup ```bash...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ... 1. Install MetaMask Flask 2. Navigate to snap development server 3. Connect from...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...`` ## Architecture - Entry Point: src/index.ts - Key Management: Deterministic derivati...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...inistic derivation from MetaMask entropy - Security: All operations require user ...

(QB_NEW_EN)


[grammar] ~80-~80: There might be a mistake here.
Context: ...**: All operations require user approval - Network: Bitcoin Signet for safe testi...

(QB_NEW_EN)

🔇 Additional comments (8)
snap/tsconfig.json (1)

1-9: LGTM — aligns with snaps-cli expectations.

Root/out directories look correct for mm-snap. Ensure ESLint’s parserOptions.project points here (see .eslintrc.js change).

snap/snap.config.js (1)

9-15: Ignore this suggestion; snaps-cli supports stats.buffer and stats.builtIns.ignore These options are valid in snap.config.js for mm-snap to ignore built-ins like fs.

Likely an incorrect or invalid review comment.

examples/metamask-react-app/vite.config.js (1)

1-12: Configuration looks good for React + crypto libraries

The Vite configuration correctly sets up React with necessary polyfills and optimizations for cryptographic libraries. The global polyfill and pre-bundling of @scure and @noble libraries will ensure smooth operation in the browser environment.

snap/README.md (1)

1-89: Well-structured documentation with clear setup instructions

The README provides comprehensive documentation for the MetaMask Snap, including clear prerequisites, setup instructions, API examples, and security model details. The architecture and security sections appropriately highlight the key security features.

snap/src/index.ts (2)

156-160: Good error code handling for user rejection

The implementation correctly uses error code 4001 for user rejection, which aligns with the EIP-1193 standard for user-rejected requests.


30-32: Ignore this review: slicing from index 1 to 33 correctly removes the 0x04 uncompressed-key prefix to yield the 32-byte X-coordinate.

Likely an incorrect or invalid review comment.

examples/metamask-react-app/src/main.jsx (1)

6-10: Entry point looks good

Standard React 18 root bootstrapping with StrictMode. No issues spotted.

examples/metamask-react-app/src/App.jsx (1)

156-179: CI: Prettier formatting fixed Example app files now comply with Prettier code style.

Comment on lines +1 to +25
{
"name": "arkade-metamask-react-app",
"private": true,
"version": "0.1.0",
"description": "ARK MetaMask Snap demo application",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "pnpm@10.15.0",
"dependencies": {
"@arkade-os/sdk": "link:../..",
"@scure/base": "^1.1.3",
"@scure/btc-signer": "^1.3.1",
"@noble/curves": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.2.0"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Fix Prettier CI failure.

Run repository formatting; if you want, add a local script for this example.


🏁 Script executed:

#!/bin/bash
# Format and re-check at repo root (uses workspace).
pnpm -w exec prettier --write .
pnpm -w exec prettier --check .

Length of output: 189


Fix Prettier commands for example CI

  • Remove the -w flag and run formatting locally in examples/metamask-react-app:
    cd examples/metamask-react-app
    pnpm exec prettier --write .
    pnpm exec prettier --check .
  • Optionally add these scripts to examples/metamask-react-app/package.json:
    "scripts": {
      "format": "prettier --write .",
      "format:check": "prettier --check ."
    }
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.


🛠️ Refactor suggestion

Declare Node engine to match Vite 5 (Node ≥18).

Prevent “unsupported engine” CI/dev surprises by pinning a minimum Node.

   "packageManager": "pnpm@10.15.0",
+  "engines": {
+    "node": ">=18.0.0"
+  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"name": "arkade-metamask-react-app",
"private": true,
"version": "0.1.0",
"description": "ARK MetaMask Snap demo application",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "pnpm@10.15.0",
"dependencies": {
"@arkade-os/sdk": "link:../..",
"@scure/base": "^1.1.3",
"@scure/btc-signer": "^1.3.1",
"@noble/curves": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.2.0"
}
}
{
"name": "arkade-metamask-react-app",
"private": true,
"version": "0.1.0",
"description": "ARK MetaMask Snap demo application",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "pnpm@10.15.0",
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"@arkade-os/sdk": "link:../..",
"@scure/base": "^1.1.3",
"@scure/btc-signer": "^1.3.1",
"@noble/curves": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.2.0"
}
}
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

🤖 Prompt for AI Agents
examples/metamask-react-app/package.json lines 1-25: the package.json lacks an
"engines" field declaring the minimum Node version required by Vite 5; add an
"engines" entry (e.g. "engines": { "node": ">=18" }) to the top-level of
package.json (near other metadata fields) so CI and developers receive a clear
minimum Node requirement and avoid unsupported-engine failures.

Comment on lines +88 to +96
const amountSats = parseInt(amount);

addStatus("Submitting transaction to Arkade server...", "info");
const arkTxid = await wallet.sendBitcoin(recipient, amountSats);

setTransactionResult(arkTxid);
addStatus("Transaction submitted successfully!", "success");
addStatus(`Arkade Transaction ID: ${arkTxid}`, "success");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden amount parsing and input constraints

parseInt without radix and loose numeric checks allow inputs like "1e2" or empty -> NaN. Use base 10, validate finite positive integers, and constrain the input element.

-            const amountSats = parseInt(amount);
+            const amountSats = parseInt(amount, 10);
+            if (!Number.isFinite(amountSats) || amountSats <= 0) {
+                throw new Error("Amount must be a positive integer (sats).");
+            }
@@
-    const isFormValid =
-        walletInfo && recipient.trim() && amount && parseInt(amount) > 0;
+    const isFormValid =
+        !!walletInfo &&
+        recipient.trim().length > 0 &&
+        Number.isFinite(parseInt(amount, 10)) &&
+        parseInt(amount, 10) > 0;
@@
-                            <input
-                                type="number"
+                            <input
+                                type="number"
+                                inputMode="numeric"
+                                pattern="\d*"
+                                min={1}
+                                step={1}
                                 value={amount}
                                 onChange={(e) => setAmount(e.target.value)}
                                 placeholder="Enter amount in satoshis..."
                                 disabled={loading}
                             />

Also applies to: 153-155, 279-286

🤖 Prompt for AI Agents
In examples/metamask-react-app/src/App.jsx around lines 88 to 96 (and also apply
the same fixes at lines 153-155 and 279-286): the amount parsing and validation
is too loose (parseInt without radix and no checks) so replace parseInt(amount)
with parseInt(amount, 10) and then validate that the parsed value is a finite
integer and > 0; if validation fails, prevent submission, surface a user-facing
error status and return early. Additionally, constrain the input element to
accept only whole positive numbers by using input attributes (type="number",
min="1", step="1", and optional pattern) and/or sanitize onChange to strip
non-numeric characters so the component never passes empty, "1e2", decimals or
NaN to wallet.sendBitcoin. Ensure equivalent fixes are applied at the other
referenced line ranges.

Comment on lines +1 to +7
import * as scureBase from "@scure/base";
import { Wallet } from "@arkade-os/sdk";
import { MetaMaskSnapIdentity } from "./MetaMaskSnapIdentity.js";

const ARK_SERVER_URL = "https://signet.arkade.sh";
const ESPLORA_URL = "https://mempool.space/signet/api";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing ArkAddress import causes runtime error

isValidArkAddress uses ArkAddress but it’s not imported. This will throw ReferenceError at runtime.

 import * as scureBase from "@scure/base";
-import { Wallet } from "@arkade-os/sdk";
+import { Wallet, ArkAddress } from "@arkade-os/sdk";
 import { MetaMaskSnapIdentity } from "./MetaMaskSnapIdentity.js";
 
 const ARK_SERVER_URL = "https://signet.arkade.sh";
 const ESPLORA_URL = "https://mempool.space/signet/api";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import * as scureBase from "@scure/base";
import { Wallet } from "@arkade-os/sdk";
import { MetaMaskSnapIdentity } from "./MetaMaskSnapIdentity.js";
const ARK_SERVER_URL = "https://signet.arkade.sh";
const ESPLORA_URL = "https://mempool.space/signet/api";
import * as scureBase from "@scure/base";
import { Wallet, ArkAddress } from "@arkade-os/sdk";
import { MetaMaskSnapIdentity } from "./MetaMaskSnapIdentity.js";
const ARK_SERVER_URL = "https://signet.arkade.sh";
const ESPLORA_URL = "https://mempool.space/signet/api";
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix code style.

🤖 Prompt for AI Agents
In examples/metamask-react-app/src/ArkWallet.js around lines 1 to 7, the helper
isValidArkAddress references ArkAddress but ArkAddress is not imported which
causes a runtime ReferenceError; fix by importing ArkAddress from the SDK (add
ArkAddress to the existing import from "@arkade-os/sdk" or add a separate import
statement) so ArkAddress is defined before use.

Comment on lines +25 to +40
loadCachedConnection() {
try {
const cached = localStorage.getItem('ark-wallet-cache');
if (cached) {
const data = JSON.parse(cached);
if (Date.now() - data.timestamp < 5 * 60 * 1000) {
this.taprootAddress = data.taprootAddress;
this.arkAddress = data.arkAddress;
this.userPubKey = data.userPubKey;
console.log("Loaded cached wallet data:", data);
}
}
} catch (error) {
console.log("No cached connection or error loading:", error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix cache type mismatch for userPubKey

You store userPubKey as hex string but load it back without decoding, breaking callers expecting Uint8Array (e.g., getDebugInfo encoding).

     loadCachedConnection() {
         try {
             const cached = localStorage.getItem('ark-wallet-cache');
             if (cached) {
                 const data = JSON.parse(cached);
                 if (Date.now() - data.timestamp < 5 * 60 * 1000) {
                     this.taprootAddress = data.taprootAddress;
                     this.arkAddress = data.arkAddress;
-                    this.userPubKey = data.userPubKey;
+                    this.userPubKey = data.userPubKey ? scureBase.hex.decode(data.userPubKey) : null;
                     console.log("Loaded cached wallet data:", data);
                 }
             }
         } catch (error) {
             console.log("No cached connection or error loading:", error);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loadCachedConnection() {
try {
const cached = localStorage.getItem('ark-wallet-cache');
if (cached) {
const data = JSON.parse(cached);
if (Date.now() - data.timestamp < 5 * 60 * 1000) {
this.taprootAddress = data.taprootAddress;
this.arkAddress = data.arkAddress;
this.userPubKey = data.userPubKey;
console.log("Loaded cached wallet data:", data);
}
}
} catch (error) {
console.log("No cached connection or error loading:", error);
}
}
loadCachedConnection() {
try {
const cached = localStorage.getItem('ark-wallet-cache');
if (cached) {
const data = JSON.parse(cached);
if (Date.now() - data.timestamp < 5 * 60 * 1000) {
this.taprootAddress = data.taprootAddress;
this.arkAddress = data.arkAddress;
this.userPubKey = data.userPubKey
? scureBase.hex.decode(data.userPubKey)
: null;
console.log("Loaded cached wallet data:", data);
}
}
} catch (error) {
console.log("No cached connection or error loading:", error);
}
}
🤖 Prompt for AI Agents
In examples/metamask-react-app/src/ArkWallet.js around lines 25 to 40, the cache
loader restores userPubKey as a hex string but callers expect a Uint8Array;
update loadCachedConnection to detect if data.userPubKey is a hex string and
convert it to a Uint8Array (e.g., decode hex to bytes) before assigning
this.userPubKey, leaving other fields unchanged and preserving the timestamp
check and error handling.

Comment on lines +59 to +74
try {
const installedSnaps = await window.ethereum.request({
method: 'wallet_getSnaps',
});

const isSnapInstalled = installedSnaps && installedSnaps[this.snapId];

if (!isSnapInstalled) {
await window.ethereum.request({
method: 'wallet_requestSnaps',
params: {
[this.snapId]: { version: "^1.0.0" },
},
});
}
} catch (checkError) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make snap installation check robust

wallet_getSnaps returns an object keyed by snap IDs; guard against shape differences and use id matching.

-                const installedSnaps = await window.ethereum.request({
+                const installedSnaps = await window.ethereum.request({
                     method: 'wallet_getSnaps',
                 });
-                
-                const isSnapInstalled = installedSnaps && installedSnaps[this.snapId];
+                const isSnapInstalled =
+                    installedSnaps &&
+                    (installedSnaps[this.snapId] ||
+                     Object.values(installedSnaps).some((s) => s?.id === this.snapId));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const installedSnaps = await window.ethereum.request({
method: 'wallet_getSnaps',
});
const isSnapInstalled = installedSnaps && installedSnaps[this.snapId];
if (!isSnapInstalled) {
await window.ethereum.request({
method: 'wallet_requestSnaps',
params: {
[this.snapId]: { version: "^1.0.0" },
},
});
}
} catch (checkError) {
try {
const installedSnaps = await window.ethereum.request({
method: 'wallet_getSnaps',
});
const isSnapInstalled =
installedSnaps &&
(installedSnaps[this.snapId] ||
Object.values(installedSnaps).some((s) => s?.id === this.snapId));
if (!isSnapInstalled) {
await window.ethereum.request({
method: 'wallet_requestSnaps',
params: {
[this.snapId]: { version: "^1.0.0" },
},
});
}
} catch (checkError) {
🤖 Prompt for AI Agents
In examples/metamask-react-app/src/ArkWallet.js around lines 59 to 74, the code
assumes wallet_getSnaps returns an object keyed by snap IDs and directly indexes
installedSnaps[this.snapId]; make the check robust by first confirming
installedSnaps is an object (not null/array) and then verify presence using
Object.prototype.hasOwnProperty.call(installedSnaps, this.snapId). Additionally
handle alternative shapes: if installedSnaps is an array, search for an entry
where entry.id === this.snapId (or entry.snapId), and treat any
non-object/unexpected response as "not installed" so the wallet_requestSnaps
flow runs safely.

Comment on lines +9 to +14
"scripts": {
"build": "mm-snap build",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"clean": "rimraf dist"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

rimraf script without dependency.

"clean": "rimraf dist" will fail unless rimraf is installed. Either add it or use a portable rm.

Option A — add dep:

   "scripts": {
     "build": "mm-snap build",
     "serve": "mm-snap serve",
     "start": "mm-snap watch",
-    "clean": "rimraf dist"
+    "clean": "rimraf dist"
   },
 ...
   "devDependencies": {
+    "rimraf": "^5.0.0",

Option B — no dep:

-    "clean": "rimraf dist"
+    "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"scripts": {
"build": "mm-snap build",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"clean": "rimraf dist"
},
"scripts": {
"build": "mm-snap build",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"clean": "rimraf dist"
},
// …
"devDependencies": {
"rimraf": "^5.0.0",
// …other devDependencies
}
Suggested change
"scripts": {
"build": "mm-snap build",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"clean": "rimraf dist"
},
"scripts": {
"build": "mm-snap build",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
},
// …rest of package.json…
🤖 Prompt for AI Agents
In snap/package.json around lines 9 to 14 the "clean": "rimraf dist" script
assumes the rimraf package is installed which will fail in environments without
that devDependency; either add rimraf to devDependencies (and run npm/yarn/pnpm
install) or replace the script with a portable no-dependency command (e.g. use a
Node builtin or cross-platform shell command like a conditional rm -rf or an npm
script that runs a small Node script to delete the dist folder) so the clean
script works consistently across environments.

Comment on lines +22 to +25
"devDependencies": {
"@metamask/snaps-cli": "latest",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Pin snaps-cli; avoid "latest" to keep builds reproducible.

Lock to a tested @metamask/snaps-cli version; config keys can change across releases.

-    "@metamask/snaps-cli": "latest",
+    "@metamask/snaps-cli": "^6.24.0",

🌐 Web query:

What is the latest stable @metamask/snaps-cli version compatible with the current config fields (`polyfills.buffer`, `server.port`), as of August 2025?

💡 Result:

The latest stable @metamask/snaps-cli that supports the config fields polyfills.buffer and server.port (per the CLI docs) is v6.5.1. [1][2]

Sources:

  • MetaMask Snaps CLI options (shows polyfills, stats.buffer, server.port). [1]
  • MetaMask/snaps GitHub releases (shows @metamask/snaps-cli v6.5.1 release). [2]

Pin @metamask/snaps-cli to ^6.5.1
Locking to v6.5.1 ensures polyfills.buffer and server.port remain supported.

-    "@metamask/snaps-cli": "latest",
+    "@metamask/snaps-cli": "^6.5.1",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"devDependencies": {
"@metamask/snaps-cli": "latest",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
"devDependencies": {
"@metamask/snaps-cli": "^6.5.1",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
🤖 Prompt for AI Agents
In snap/package.json around lines 22 to 25, update the devDependency entry for
"@metamask/snaps-cli" from "latest" to the pinned version "^6.5.1" so
polyfills.buffer and server.port remain supported; after changing the version
string, run your package manager (npm install or yarn install) to update the
lockfile (package-lock.json or yarn.lock) and verify tests/builds pass.

import { hex, base64 } from '@scure/base';
import { networks } from '@arkade-os/sdk';

declare const snap: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace any type with proper Snap type definition

Using any for the snap declaration bypasses TypeScript's type checking. Consider using the proper type from MetaMask's SDK.

-declare const snap: any;
+import type { SnapsGlobalObject } from '@metamask/snaps-sdk';
+declare const snap: SnapsGlobalObject;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declare const snap: any;
// At the top of snap/src/index.ts, replace:
- declare const snap: any;
// With:
import type { SnapsGlobalObject } from '@metamask/snaps-sdk';
declare const snap: SnapsGlobalObject;
🤖 Prompt for AI Agents
In snap/src/index.ts at line 7 replace the loose "declare const snap: any;" with
the proper Snap typing from MetaMask's snaps types: import the appropriate type
(e.g., Snap or SnapsGlobal) from the '@metamask/snaps-types' package (add it to
devDependencies if missing) and declare the global using that type (e.g.,
"declare const snap: <ImportedType>;"). This ensures TypeScript enforces the
correct Snap API surface instead of bypassing checks with any.

},
});

return hex.decode(entropy.slice(2, 66)); // Remove 0x prefix and take first 32 bytes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for entropy length

The code assumes the entropy string has at least 66 characters (after removing '0x'). Add validation to prevent potential runtime errors.

 async function derivePrivateKey(): Promise<Uint8Array> {
   const entropy = await snap.request({
     method: 'snap_getEntropy',
     params: {
       version: 1,
       salt: 'bitcoin-arkade-snap',
     },
   });
 
+  if (!entropy || entropy.length < 66) {
+    throw new Error('Insufficient entropy received from MetaMask');
+  }
+
   return hex.decode(entropy.slice(2, 66)); // Remove 0x prefix and take first 32 bytes
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return hex.decode(entropy.slice(2, 66)); // Remove 0x prefix and take first 32 bytes
async function derivePrivateKey(): Promise<Uint8Array> {
const entropy = await snap.request({
method: 'snap_getEntropy',
params: {
version: 1,
salt: 'bitcoin-arkade-snap',
},
});
if (!entropy || entropy.length < 66) {
throw new Error('Insufficient entropy received from MetaMask');
}
return hex.decode(entropy.slice(2, 66)); // Remove 0x prefix and take first 32 bytes
}
🤖 Prompt for AI Agents
In snap/src/index.ts around line 21, the function currently slices
entropy.slice(2, 66) without validating the input; add explicit validation that
the entropy is a string, starts with '0x' (or handle absence explicitly), and
has a minimum length of 66 characters before slicing, otherwise throw a clear
Error (or return a controlled failure) indicating invalid entropy length/format;
only call hex.decode(entropy.slice(2,66)) after these checks.

Comment on lines +143 to +146
const { psbt, inputIndexes } = request.params as {
psbt: string;
inputIndexes?: number[];
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety for request parameters

The type assertion could hide type mismatches. Consider defining proper types for the request parameters.

+interface SignPsbtParams {
+  psbt: string;
+  inputIndexes?: number[];
+}
+
 case 'bitcoin_signPsbt': {
-  const { psbt, inputIndexes } = request.params as {
-    psbt: string;
-    inputIndexes?: number[];
-  };
+  const { psbt, inputIndexes } = request.params as SignPsbtParams;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { psbt, inputIndexes } = request.params as {
psbt: string;
inputIndexes?: number[];
};
// Define a dedicated params interface for better type safety
interface SignPsbtParams {
psbt: string;
inputIndexes?: number[];
}
case 'bitcoin_signPsbt': {
const { psbt, inputIndexes } = request.params as SignPsbtParams;
// …rest of handler…
}
🤖 Prompt for AI Agents
In snap/src/index.ts around lines 143 to 146, replace the broad type assertion
for request.params with a proper typed interface/typedef for the RPC params
(e.g., interface RpcParams { psbt: string; inputIndexes?: number[] }) and use it
in the function signature or when extracting params so TypeScript validates
shape at compile time; also narrow the runtime check by validating required
fields (psbt exists and is a string, inputIndexes is an array of numbers if
present) and throw a clear error if validation fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants