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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read -r f; echo \"$f\" | grep -qE '\\.rs$' && rustfmt --edition 2021 \"$f\"; } 2>/dev/null || true",
"timeout": 30,
"statusMessage": "Formatting Rust..."
}
]
}
]
}
}
11 changes: 11 additions & 0 deletions .claude/skills/build/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: build
description: Build platform bindings using build.sh. Pass a target as argument (ios, android, python, all). Use when you need to generate or test platform-specific bindings.
disable-model-invocation: true
---

Run `./build.sh $ARGUMENTS` from the project root.

If no arguments provided, ask the user which target to build (ios, android, python, all).

For release builds, remind the user to use `-r` with a version bump flag (`--patch`, `--minor`, or `--major`).
11 changes: 11 additions & 0 deletions .claude/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: verify
description: Run clippy and tests to verify the codebase compiles cleanly and all tests pass. Use after making changes or before committing.
---

Run the following checks in sequence, stopping on first failure:

1. `cargo clippy -- -D warnings` — ensure no lint warnings
2. `cargo test` — run all tests

Report results concisely. If clippy or tests fail, show the relevant errors and suggest fixes.
55 changes: 55 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# GitHub Copilot Code Review Instructions

When performing a code review, respond in English.

## Architecture & Patterns

When performing a code review, ensure new public types are properly exported via `src/lib.rs` for UniFFI binding generation.

When performing a code review, verify that modules follow the established structure: `mod.rs`, `types.rs`, `errors.rs`, `implementation.rs`, and optional `tests.rs`.

When performing a code review, check that UniFFI-exported types follow existing patterns (derive macros, enum representations, error types).

## Error Handling & Safety

When performing a code review, flag any use of `unwrap()` or `expect()` in non-test code and suggest proper error propagation with `?` or `Result`.

When performing a code review, ensure error types implement proper `Display` and `Error` traits and are exported for UniFFI.

When performing a code review, flag any `unsafe` blocks and verify they are necessary and well-documented.

## Code Quality & Readability

When performing a code review, ensure `cargo clippy` warnings are addressed — the project treats clippy warnings as errors.

When performing a code review, verify that `cargo fmt` formatting is applied consistently.

When performing a code review, focus on readability and avoid deeply nested match arms, replacing with early returns or helper functions where possible.

When performing a code review, ensure unused code is removed after refactoring.

When performing a code review, verify that existing utilities and helper functions are reused rather than creating duplicates.

## Dependencies & Platform

When performing a code review, verify that platform-specific dependencies use correct `#[cfg(target_os)]` guards (especially Trezor: BLE-only on iOS, USB+BLE elsewhere).

When performing a code review, check that new dependencies are justified and don't introduce unnecessary bloat to the FFI binary.

## Testing

When performing a code review, suggest tests for new functionality covering the most important cases.

When performing a code review, verify that tests use the established patterns (test modules in `tests.rs`, `#[cfg(test)]` gating).

## Bitcoin & Lightning Specific

When performing a code review, verify that Bitcoin/Lightning operations use proper types from the `bitcoin` and `bdk` crates.

When performing a code review, verify that proper Bitcoin and Lightning technical terms are used when naming code components.

## Build & Version

When performing a code review, check that version changes are synchronized across `Cargo.toml`, `Package.swift`, and `bindings/android/gradle.properties`.

When performing a code review, verify that changes to public API types don't break existing UniFFI bindings without updating the binding generation.
15 changes: 15 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Closes | Fixes | Resolves #ISSUE_ID -->
<!-- Brief summary of the PR changes, linking to the related resources (issue/design/bug/etc) if applicable. -->

### Description

<!-- Extended summary of the changes, can be a list. -->

### Preview

<!-- Insert relevant screenshot / recording -->

### QA Notes

<!-- Add testing instructions for the PR reviewer to validate the changes. -->
<!-- List the tests you ran, including regression tests if applicable. -->
55 changes: 55 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Claude Code Review

on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
claude-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1

- name: Minimize old Claude comments
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO="${{ github.repository }}"
PR_NUMBER="${{ github.event.pull_request.number }}"

# Minimize issue comments from claude[bot]
gh api "repos/$REPO/issues/$PR_NUMBER/comments" --jq '.[] | select(.user.login == "claude[bot]") | .node_id' | while read -r node_id; do
if [ -n "$node_id" ]; then
echo "Minimizing comment: $node_id"
gh api graphql -f query='
mutation($id: ID!) {
minimizeComment(input: {subjectId: $id, classifier: OUTDATED}) {
minimizedComment { isMinimized }
}
}' -f id="$node_id" || true
fi
done

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
claude_args: |
--allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:*),Bash(git log:*),Bash(git diff:*),Bash(git blame:*),Read,Glob,Grep"
45 changes: 45 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association))
runs-on: ubuntu-latest
permissions:
contents: write # Allow creating branches/commits
pull-requests: write # Allow pushing to PR branches
issues: write # Allow updating issue comments
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0 # Full history for git operations

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target/
.idea/
.DS_Store
.ai
49 changes: 49 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Rust FFI library (`bitkitcore`) providing Bitcoin & Lightning functionality with UniFFI-generated bindings for iOS (Swift), Android (Kotlin), and Python.

## Build

```bash
cargo build # Rust library
./build.sh <ios|android|python|all> # Platform bindings
./build.sh -r --patch <target> # Bump version + build (--minor, --major)
```

## Test

```bash
cargo test # All tests
cargo test modules::<module> # Single module (scanner, lnurl, onchain, activity, blocktank, trezor, pubky)
```

## Lint & Format

```bash
cargo clippy # Lint
cargo fmt # Format Rust
```

Android bindings use ktlint via Gradle plugin (`org.jlleitschuh.gradle.ktlint`), excluding generated code.

## Architecture

- `src/lib.rs` — UniFFI exports and module re-exports
- `src/modules/` — Core modules: scanner, lnurl, onchain, activity, blocktank, trezor, pubky
- `bindings/` — Platform-specific binding outputs (ios/, android/, python/)
- `build.sh`, `build_ios.sh`, `build_android.sh`, `build_python.sh` — Build scripts

## Key Constraints

- **Version sync**: Version must match across `Cargo.toml`, `Package.swift`, and `bindings/android/gradle.properties`. Use `build.sh -r` to bump all three.
- **UniFFI**: Public types exposed to bindings are declared in `src/lib.rs`. Follow existing UniFFI patterns when adding new types.
- **Platform-specific deps**: Trezor uses Bluetooth-only on iOS, USB+Bluetooth on other platforms (see `Cargo.toml` target-specific dependencies).
- **Android build**: `build_android.sh` temporarily modifies `Cargo.toml` crate-type and removes `example/main.rs` during build — don't run concurrent builds.

## Conventions

- Branch naming: `feat/*`, `fix/*`, `chore/*`
1 change: 1 addition & 0 deletions CLAUDE.md
23 changes: 17 additions & 6 deletions example/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

use bitkitcore::*;

fn handle_decode_result(result: Result<Scanner, DecodingError>) {
match result {
Ok(Scanner::Lightning { invoice: ln_invoice }) => {
Ok(Scanner::Lightning {
invoice: ln_invoice,
}) => {
println!("Successfully decoded Lightning invoice:");
println!("Payment hash: {:?}", ln_invoice.payment_hash);
println!("Amount: {} sats", ln_invoice.amount_satoshis);
Expand All @@ -19,7 +20,9 @@ fn handle_decode_result(result: Result<Scanner, DecodingError>) {
}
}

Ok(Scanner::OnChain { invoice: btc_invoice }) => {
Ok(Scanner::OnChain {
invoice: btc_invoice,
}) => {
println!("\nSuccessfully decoded on-chain invoice:");
println!("Address: {}", btc_invoice.address);
println!("Amount Sats: {}", btc_invoice.amount_satoshis);
Expand Down Expand Up @@ -98,7 +101,14 @@ fn handle_decode_result(result: Result<Scanner, DecodingError>) {
println!("\nSuccessfully decoded Node Connection:");
println!("URL: {}", url);
println!("Network: {}", network);
println!("Type: {}", if url.contains("onion") { "Tor" } else { "Clearnet" });
println!(
"Type: {}",
if url.contains("onion") {
"Tor"
} else {
"Clearnet"
}
);
}

Ok(Scanner::Gift { code, amount }) => {
Expand Down Expand Up @@ -128,15 +138,16 @@ async fn main() {
let legacy_address = "199Grz1BcL5KffikSAtbgngAPgYZZRa3cs";
let random_string = "random_string";
let tor_node_id = "72413cc3e96168cb4320f992bfa483865133dc28d@3phi2gcmu3nsbvux53hixrxjgyg3u6vd6kqy3yq6rlrvudqrjsxir6id.onion:9735";
let node_id = "039b8b4dd1d88c2c5db374290cda397a8f5d79f312d6ea5d5bfdfc7c6ff363eae3@34.65.111.104:9735";
let node_id =
"039b8b4dd1d88c2c5db374290cda397a8f5d79f312d6ea5d5bfdfc7c6ff363eae3@34.65.111.104:9735";
let gift_code = "bitkit://gift-ABC123XYZ-50000";
let invalid_gift_code = "bitkit://gift-TEST-notanumber";

// Test gift code parsing
println!("\n=== Testing Gift Code Parsing ===");
println!("Decoding: {}", gift_code);
handle_decode_result(Scanner::decode(gift_code.to_string()).await);

// Test with invalid amount
println!("\nDecoding invalid gift code: {}", invalid_gift_code);
handle_decode_result(Scanner::decode(invalid_gift_code.to_string()).await);
Expand Down
Loading
Loading