[{"id":"8629208093","type":"IssueCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/k-nuth/py-native/issues/7","repository_url":"https://api.github.com/repos/k-nuth/py-native","labels_url":"https://api.github.com/repos/k-nuth/py-native/issues/7/labels{/name}","comments_url":"https://api.github.com/repos/k-nuth/py-native/issues/7/comments","events_url":"https://api.github.com/repos/k-nuth/py-native/issues/7/events","html_url":"https://github.com/k-nuth/py-native/pull/7","id":4301935114,"node_id":"PR_kwDODiJdjc7UQ4r7","number":7,"title":"tests: cover the classes newly generator-driven in #6","user":{"login":"fpelliccioni","id":273683,"node_id":"MDQ6VXNlcjI3MzY4Mw==","avatar_url":"https://avatars.githubusercontent.com/u/273683?v=4","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","html_url":"https://github.com/fpelliccioni","followers_url":"https://api.github.com/users/fpelliccioni/followers","following_url":"https://api.github.com/users/fpelliccioni/following{/other_user}","gists_url":"https://api.github.com/users/fpelliccioni/gists{/gist_id}","starred_url":"https://api.github.com/users/fpelliccioni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpelliccioni/subscriptions","organizations_url":"https://api.github.com/users/fpelliccioni/orgs","repos_url":"https://api.github.com/users/fpelliccioni/repos","events_url":"https://api.github.com/users/fpelliccioni/events{/privacy}","received_events_url":"https://api.github.com/users/fpelliccioni/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"open","locked":false,"assignees":[],"milestone":null,"comments":1,"created_at":"2026-04-21T10:23:13Z","updated_at":"2026-04-21T10:23:22Z","closed_at":null,"assignee":null,"type":null,"active_lock_reason":null,"draft":false,"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/7","html_url":"https://github.com/k-nuth/py-native/pull/7","diff_url":"https://github.com/k-nuth/py-native/pull/7.diff","patch_url":"https://github.com/k-nuth/py-native/pull/7.patch","merged_at":null},"body":"## Summary\n\nFills the test gap left open by #6 — twelve new pytest files, **75 new cases** (suite 89 → 164), covering the ~20 chain and wallet classes that the 0.80.0 sync regenerated as generator-driven bindings. Existing coverage under PR #5 already handled `block` / `header` / `point` / `output_point` / `output` / `input` / `script` / `transaction` and their lists; this PR picks up everything the regen added on top.\n\n> ⚠️ **Stacked on #6** (`feature/sync-kth-0.80` branch). Opened against `master` for visibility; will be rebased onto `master` once #6 merges.\n\n## Classes covered\n\n### Chain\n\n- `test_chain_utxo.py` (9) — `utxo` + `utxo_list`: construct_default, height / amount / point round-trips, copy, equals; list lifecycle.\n- `test_chain_operation.py` (9) — `operation` + `operation_list`: construct_default, construct_from_code, from_data → to_data round-trip, copy, equals; static opcode probes (`is_push`, `is_counted`, `opcode_from_positive` ↔ `_to_positive`); list lifecycle.\n- `test_chain_history_compact.py` (2) — list lifecycle + symbol-presence smoke on element accessors. `history_compact` has no public constructor on either the C-API or py-native side, so element-level unit tests aren't practical without a running chain.\n- `test_chain_get_blocks.py` (8) — covers both `get_blocks` and `get_headers`: construct_default, stop_hash round-trip, copy / equals, `to_data` ↔ `construct_from_data` wire-level round-trip, reset.\n- `test_chain_prefilled_transaction.py` (7) — construct_default, index / transaction round-trips, copy, equals; list push_back / nth.\n- `test_chain_double_spend_proof.py` (6) — DSP construct_default, copy / equals, hash is 32 bytes, `serialized_size` matches `to_data` length, reset; element-level `spender` accessors covered by a symbol-presence smoke test (no public ctor from Python).\n- `test_chain_token_data.py` (7) — each `make_*` factory (`make_fungible`, `make_non_fungible`, `make_both`), NFT accessors (`has_nft`, `get_nft_capability`, `get_nft_commitment`, `is_mutable_nft`, `is_immutable_nft`), copy, equals, `to_data` ↔ `construct_from_data` round-trip.\n- `test_chain_payment_address_list.py` (3) — list lifecycle using real `wallet_payment_address_construct_from_address` elements, with a round-trip through `wallet_payment_address_encoded_legacy` to verify `nth` returns a usable handle.\n\n### Wallet\n\n- `test_wallet_ec_public.py` (6) — construct_default invalid, base16 / compressed-point round-trips, copy / equals, `to_uncompressed` is 65 bytes with `0x04` sentinel, `to_payment_address` derives a valid address.\n- `test_wallet_ec_private.py` (5) — construct_default invalid, secret round-trip, WIF `encoded` ↔ `construct_from_wif_version` round-trip, copy / equals.\n- `test_wallet_hd_private.py` (8) — BIP32 test-vector-1 seed construction, secret / chain_code sizes, encoded ↔ `construct_from_encoded_prefixes` round-trip, `derive_private` child has distinct secret, `to_public` cross-check on chain_code; plus HdPublic default-invalid, `derive_public`, copy / equals.\n- `test_wallet_wallet_data.py` (5) — `create(password, passphrase)` exposure, encrypted_seed is non-empty bytes, xpub is a valid HdPublic, copy preserves encrypted_seed, `mnemonics` call doesn't raise.\n\n## Intentional scope\n\nNo end-to-end validation against real block data — these are smoke + structural round-trip tests meant to catch regen drift (missing symbols, wrong arities, segfaults on common call patterns, handle-lifecycle breakages). Deeper fidelity tests against known test vectors can land in later PRs as the bindings see actual consumer use.\n\n## Test plan\n\n- [x] Full local suite: **164 passed in ~2s** against the locally-built .so from #6.\n- [ ] CI wheels job stays green (blocked on #6's kth/0.80.0 being resolvable from the public remote).","reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/issues/7/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/k-nuth/py-native/issues/7/timeline","performed_via_github_app":null,"state_reason":null},"comment":{"url":"https://api.github.com/repos/k-nuth/py-native/issues/comments/4287774017","html_url":"https://github.com/k-nuth/py-native/pull/7#issuecomment-4287774017","issue_url":"https://api.github.com/repos/k-nuth/py-native/issues/7","id":4287774017,"node_id":"IC_kwDODiJdjc7_kj1B","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"created_at":"2026-04-21T10:23:21Z","updated_at":"2026-04-21T10:23:21Z","body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->\n\n> [!WARNING]\n> ## Rate limit exceeded\n> \n> `@fpelliccioni` has exceeded the limit for the number of commits that can be reviewed per hour. Please wait **1 minutes and 32 seconds** before requesting another review.\n> \n> Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in **1 minutes and 32 seconds**.\n> \n> <details>\n> <summary>⌛ How to resolve this issue?</summary>\n> \n> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.\n> \n> We recommend that you space out your commits to avoid hitting the rate limit.\n> \n> </details>\n> \n> \n> <details>\n> <summary>🚦 How do rate limits work?</summary>\n> \n> CodeRabbit enforces hourly rate limits for each developer per organization.\n> \n> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.\n> \n> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.\n> \n> </details>\n> \n> <details>\n> <summary>ℹ️ Review info</summary>\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: defaults\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `4f737ce3-36d7-4832-8fa2-4fd8881226a0`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 54a59bb17c7600eb146c2e31c2b6be1c316eac72 and 3771e927e72723a805440f0bde5a33b064b5a412.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (86)</summary>\n> \n> * `conanfile.py`\n> * `include/kth/py-native/binary.h`\n> * `include/kth/py-native/capsule_names.h`\n> * `include/kth/py-native/chain/compact_block.h`\n> * `include/kth/py-native/chain/double_spend_proof.h`\n> * `include/kth/py-native/chain/double_spend_proof_spender.h`\n> * `include/kth/py-native/chain/get_blocks.h`\n> * `include/kth/py-native/chain/get_headers.h`\n> * `include/kth/py-native/chain/history_compact.h`\n> * `include/kth/py-native/chain/history_compact_list.h`\n> * `include/kth/py-native/chain/merkle_block.h`\n> * `include/kth/py-native/chain/operation.h`\n> * `include/kth/py-native/chain/operation_list.h`\n> * `include/kth/py-native/chain/prefilled_transaction.h`\n> * `include/kth/py-native/chain/prefilled_transaction_list.h`\n> * `include/kth/py-native/chain/script.h`\n> * `include/kth/py-native/chain/stealth_compact.h`\n> * `include/kth/py-native/chain/stealth_compact_list.h`\n> * `include/kth/py-native/chain/token_data.h`\n> * `include/kth/py-native/chain/transaction.h`\n> * `include/kth/py-native/chain/utxo.h`\n> * `include/kth/py-native/chain/utxo_list.h`\n> * `include/kth/py-native/wallet/ec_private.h`\n> * `include/kth/py-native/wallet/ec_public.h`\n> * `include/kth/py-native/wallet/hd_private.h`\n> * `include/kth/py-native/wallet/hd_public.h`\n> * `include/kth/py-native/wallet/payment_address.h`\n> * `include/kth/py-native/wallet/payment_address_list.h`\n> * `include/kth/py-native/wallet/wallet_data.h`\n> * `kth_native.pyi`\n> * `scripts/build-nix.sh`\n> * `setup.py`\n> * `src/binary.cpp`\n> * `src/chain/block.cpp`\n> * `src/chain/block_list.cpp`\n> * `src/chain/chain.cpp`\n> * `src/chain/compact_block.cpp`\n> * `src/chain/double_spend_proof.cpp`\n> * `src/chain/double_spend_proof_spender.cpp`\n> * `src/chain/get_blocks.cpp`\n> * `src/chain/get_headers.cpp`\n> * `src/chain/header.cpp`\n> * `src/chain/history.cpp`\n> * `src/chain/history_compact.cpp`\n> * `src/chain/history_compact_list.cpp`\n> * `src/chain/input.cpp`\n> * `src/chain/input_list.cpp`\n> * `src/chain/merkle_block.cpp`\n> * `src/chain/operation.cpp`\n> * `src/chain/operation_list.cpp`\n> * `src/chain/output.cpp`\n> * `src/chain/output_list.cpp`\n> * `src/chain/output_point.cpp`\n> * `src/chain/output_point_list.cpp`\n> * `src/chain/point.cpp`\n> * `src/chain/point_list.cpp`\n> * `src/chain/prefilled_transaction.cpp`\n> * `src/chain/prefilled_transaction_list.cpp`\n> * `src/chain/script.cpp`\n> * `src/chain/stealth_compact.cpp`\n> * `src/chain/stealth_compact_list.cpp`\n> * `src/chain/token_data.cpp`\n> * `src/chain/transaction.cpp`\n> * `src/chain/transaction_list.cpp`\n> * `src/chain/utxo.cpp`\n> * `src/chain/utxo_list.cpp`\n> * `src/module.c`\n> * `src/wallet/ec_private.cpp`\n> * `src/wallet/ec_public.cpp`\n> * `src/wallet/hd_private.cpp`\n> * `src/wallet/hd_public.cpp`\n> * `src/wallet/payment_address.cpp`\n> * `src/wallet/payment_address_list.cpp`\n> * `src/wallet/wallet_data.cpp`\n> * `tests/test_chain_double_spend_proof.py`\n> * `tests/test_chain_get_blocks.py`\n> * `tests/test_chain_history_compact.py`\n> * `tests/test_chain_operation.py`\n> * `tests/test_chain_payment_address_list.py`\n> * `tests/test_chain_prefilled_transaction.py`\n> * `tests/test_chain_token_data.py`\n> * `tests/test_chain_utxo.py`\n> * `tests/test_wallet_ec_private.py`\n> * `tests/test_wallet_ec_public.py`\n> * `tests/test_wallet_hd_private.py`\n> * `tests/test_wallet_wallet_data.py`\n> \n> </details>\n> \n> </details>\n\n<!-- end of auto-generated comment: rate limited by coderabbit.ai -->\n\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `feature/sync-kth-0.80-tests`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=k-nuth/py-native&utm_content=7)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->","reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/issues/comments/4287774017/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":{"id":347564,"client_id":"Iv1.6aaafe4fe882736b","slug":"coderabbitai","node_id":"A_kwHOB96YWc4ABU2s","owner":{"login":"coderabbitai","id":132028505,"node_id":"O_kgDOB96YWQ","avatar_url":"https://avatars.githubusercontent.com/u/132028505?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai","html_url":"https://github.com/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai/followers","following_url":"https://api.github.com/users/coderabbitai/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai/orgs","repos_url":"https://api.github.com/users/coderabbitai/repos","events_url":"https://api.github.com/users/coderabbitai/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai/received_events","type":"Organization","user_view_type":"public","site_admin":false},"name":"coderabbitai","description":"# Transforming Code Reviews with AI\r\n\r\n## Features\r\n\r\n**Automated Reviews**: Continuous reviews of the pull requests including incremental commits. \r\n\r\n**Summarization**: Generates high-level summary and a technical walkthrough of the PR changes. \r\n\r\n**Line-by-line review**: Provides line-by-line suggestions committable with one click.\r\n\r\n**Codebase verification**:  Verifies the impact on the overall codebase and identifies missing changes.\r\n\r\n**Insights into your code**:  Ask any questions on your codebase within the pull request \r\n\r\n**Chat about your code** : Chat with the bot around your code. The more you chat, the smarter it gets.\r\n\r\n**Issue Validation**:  Validates the PR against the linked issues and identifies other related issues \r\n\r\n\r\n\r\n","external_url":"https://coderabbit.ai?utm_source=cr_app&utm_medium=github","html_url":"https://github.com/apps/coderabbitai","created_at":"2023-06-14T15:47:27Z","updated_at":"2026-03-14T07:58:44Z","permissions":{"actions":"read","checks":"write","contents":"write","discussions":"read","issues":"write","members":"read","metadata":"read","pull_requests":"write","statuses":"write"},"events":["issues","issue_comment","label","organization","pull_request","pull_request_review","pull_request_review_comment","pull_request_review_thread","release","repository"]}}},"public":true,"created_at":"2026-04-21T10:23:21Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8629202929","type":"PullRequestEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"opened","number":7,"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/7","id":3561196283,"number":7,"head":{"ref":"feature/sync-kth-0.80-tests","sha":"3771e927e72723a805440f0bde5a33b064b5a412","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T10:23:13Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10847510507","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33139309557,"ref":"refs/heads/master","head":"0bc3c89f34007b4fcfff6dfe218d75be156e9bd5","before":"48a0c1b13ad6b55c01be03f038a2e400c49d0ddb"},"public":true,"created_at":"2026-04-21T10:08:22Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10847497940","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33139296984,"ref":"refs/heads/master","head":"48a0c1b13ad6b55c01be03f038a2e400c49d0ddb","before":"2e03451983afe2de5e2d4fd2f7c4bfc9da2e4384"},"public":true,"created_at":"2026-04-21T10:08:03Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10847492407","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33139291394,"ref":"refs/heads/master","head":"2e03451983afe2de5e2d4fd2f7c4bfc9da2e4384","before":"edefe152aa1b4a7409dd06701c55c66dc9cff520"},"public":true,"created_at":"2026-04-21T10:07:55Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8628569469","type":"ReleaseEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":1007599554,"name":"k-nuth/kth","url":"https://api.github.com/repos/k-nuth/kth"},"payload":{"action":"published","release":{"url":"https://api.github.com/repos/k-nuth/kth/releases/311643589","assets_url":"https://api.github.com/repos/k-nuth/kth/releases/311643589/assets","upload_url":"https://uploads.github.com/repos/k-nuth/kth/releases/311643589/assets{?name,label}","html_url":"https://github.com/k-nuth/kth/releases/tag/v0.80.0","id":311643589,"author":{"login":"fpelliccioni","id":273683,"node_id":"MDQ6VXNlcjI3MzY4Mw==","avatar_url":"https://avatars.githubusercontent.com/u/273683?v=4","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","html_url":"https://github.com/fpelliccioni","followers_url":"https://api.github.com/users/fpelliccioni/followers","following_url":"https://api.github.com/users/fpelliccioni/following{/other_user}","gists_url":"https://api.github.com/users/fpelliccioni/gists{/gist_id}","starred_url":"https://api.github.com/users/fpelliccioni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpelliccioni/subscriptions","organizations_url":"https://api.github.com/users/fpelliccioni/orgs","repos_url":"https://api.github.com/users/fpelliccioni/repos","events_url":"https://api.github.com/users/fpelliccioni/events{/privacy}","received_events_url":"https://api.github.com/users/fpelliccioni/received_events","type":"User","user_view_type":"public","site_admin":false},"node_id":"RE_kwDOPA6_ws4Sk03F","tag_name":"v0.80.0","target_commitish":"master","name":"v0.80.0","draft":false,"immutable":false,"prerelease":false,"created_at":"2026-04-21T10:05:25Z","updated_at":"2026-04-21T10:05:28Z","published_at":"2026-04-21T10:05:28Z","assets":[],"tarball_url":"https://api.github.com/repos/k-nuth/kth/tarball/v0.80.0","zipball_url":"https://api.github.com/repos/k-nuth/kth/zipball/v0.80.0","body":"## What's Changed\n* refactor(c-api): unify and harden binary bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/232\n* refactor(c-api): unify and harden chain/get_blocks bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/234\n* refactor(c-api): unify and harden chain/get_headers bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/236\n* refactor(c-api): unify and harden chain/{merkle,compact}_block bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/237\n* chore(c-api): remove dead raw_output and transaction_functions scaffolding by @fpelliccioni in https://github.com/k-nuth/kth/pull/238\n* chore(c-api): refresh generated headers with richer lifetime docs by @fpelliccioni in https://github.com/k-nuth/kth/pull/239\n* refactor: replace cashaddr global prefix with constexpr network mapping by @fpelliccioni in https://github.com/k-nuth/kth/pull/240\n* refactor(c-api): unify and harden wallet/payment_address bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/241\n* refactor(c-api): unify and harden ec_public, ec_private, and operation bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/245\n* refactor: move BIP-specific wallet classes from infrastructure to domain by @fpelliccioni in https://github.com/k-nuth/kth/pull/249\n* refactor(c-api): unify and harden wallet/hd_public and hd_private bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/250\n* ci: cancel redundant workflow runs on PR updates by @fpelliccioni in https://github.com/k-nuth/kth/pull/252\n* refactor(c-api): unify and harden history_compact and stealth_compact bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/253\n* refactor(c-api): unify and harden chain/double_spend_proof bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/254\n* refactor(c-api): harden constructors and modernize wallet/ec_public by @fpelliccioni in https://github.com/k-nuth/kth/pull/255\n* refactor(c-api): collapse factory pattern via make_leaked helpers by @fpelliccioni in https://github.com/k-nuth/kth/pull/258\n* refactor(c-api): nullable contract for empty-as-failure list factories by @fpelliccioni in https://github.com/k-nuth/kth/pull/259\n* refactor(c-api): unify and harden chain/token_data bindings + cleanup by @fpelliccioni in https://github.com/k-nuth/kth/pull/260\n* refactor(c-api): migrate wallet_data binding; rename wallet::create by @fpelliccioni in https://github.com/k-nuth/kth/pull/261\n* refactor(c-api): migrate utxo binding; drop flattened token accessors by @fpelliccioni in https://github.com/k-nuth/kth/pull/263\n* refactor(c-api): replace per-type opaque-handle converters with cpp_ref<T> by @fpelliccioni in https://github.com/k-nuth/kth/pull/264\n* refactor(c-api): migrate vm::program binding; forward rvalue-ref params properly by @fpelliccioni in https://github.com/k-nuth/kth/pull/265\n* refactor(c-api): cleanup helpers + file-local cpp_t alias by @fpelliccioni in https://github.com/k-nuth/kth/pull/267\n* refactor(c-api): regenerate 5 standalone enum headers via unified generator by @fpelliccioni in https://github.com/k-nuth/kth/pull/268\n* refactor(c-api): move opcode helpers to opcode.h; regen sighash_algorithm.h via generator by @fpelliccioni in https://github.com/k-nuth/kth/pull/269\n* refactor(c-api): extract primitives.h enums into their own headers by @fpelliccioni in https://github.com/k-nuth/kth/pull/270\n* refactor(domain): reshape interpreter run and debug around op_result + debug_snapshot by @fpelliccioni in https://github.com/k-nuth/kth/pull/271\n* test(blockchain): run/debug_run parity checks in vmlimits suite by @fpelliccioni in https://github.com/k-nuth/kth/pull/273\n* refactor(domain): replace set_jump_register address lookup with PC by @fpelliccioni in https://github.com/k-nuth/kth/pull/274\n* feat(c-api): interpreter + debug_snapshot bindings by @fpelliccioni in https://github.com/k-nuth/kth/pull/275\n* refactor(c-api): split primitives.h into handles.h + callbacks.h by @fpelliccioni in https://github.com/k-nuth/kth/pull/276\n* refactor(domain): move script_pattern to domain + rename pay_* to pay_to_* by @fpelliccioni in https://github.com/k-nuth/kth/pull/277\n* feat(domain): add pay_to_script (P2S) pattern for BCH 2026-May leibniz by @fpelliccioni in https://github.com/k-nuth/kth/pull/278\n* chore(c-api): drop dead macro / wrapper comments by @fpelliccioni in https://github.com/k-nuth/kth/pull/279\n* refactor(c-api): sync binding surface with current library policy by @fpelliccioni in https://github.com/k-nuth/kth/pull/280\n* fix(c-api): migrate test to output_pattern_simple after rename by @fpelliccioni in https://github.com/k-nuth/kth/pull/281\n* release: 0.80.0 by @fpelliccioni in https://github.com/k-nuth/kth/pull/282\n\n\n**Full Changelog**: https://github.com/k-nuth/kth/compare/v0.79.0...v0.80.0","mentions_count":1,"mentions":[{"avatar_url":"https://avatars.githubusercontent.com/u/273683?v=4","login":"fpelliccioni","profile_name":"Fernando Pelliccioni","profile_url":"https://github.com/fpelliccioni","avatar_user_actor":true}],"short_description_html":"<h2>What's Changed</h2>\n<ul>\n<li>refactor(c-api): unify and harden binary bindings by <a class=\"user-mention notranslate\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/fpelliccioni/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"https://github.com/fpelliccioni\">@fpelliccioni</a> in <a class=\"issue-link js-issue-link\" data-error-text=\"Failed to load title\" data-id=\"4244529774\" data-permission-text=\"Title is private\" data-url=\"https://github.com/k-nuth/kth/issues/232\" data-hovercard-type=\"pull_request\" data-hovercard-url=\"/k-nuth/kth/pull/232/hovercard\" href=\"https://github.com/k-nuth/kth/pull/232\">#232</a>\n</li>\n<li>refactor(c-api): unify and harden chain/get_blocks bindings by <a class=\"user-mention notranslate\" data-hovercard-type=\"user\" data-hovercard-url=\"/users/fpelliccioni/hovercard\" data-octo-click=\"hovercard-link-click\" data-octo-dimensions=\"link_type:self\" href=\"https://github.com/fpelliccioni\">@fpelliccioni</a> in <a class=\"issue-link js-issue-link\" data-error-text=\"Failed to load title\" data-id=\"4245069903\" data-permission-text=\"Title is private\" data-url=\"https://github.com/k-nuth/kth/issues/234\" data-hovercard-type=\"pull_request\" data-hovercard-url=\"/k-nuth/kth/pull/234/hovercard\" href=\"https://github.com/k-nuth/kth/pull/234\">#234</a>\n</li>\n<li>refactor(c-api): unif…</li>\n</ul>","is_short_description_html_truncated":true}},"public":true,"created_at":"2026-04-21T10:05:28Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8628558206","type":"PullRequestEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":1007599554,"name":"k-nuth/kth","url":"https://api.github.com/repos/k-nuth/kth"},"payload":{"action":"merged","number":282,"pull_request":{"url":"https://api.github.com/repos/k-nuth/kth/pulls/282","id":3560960411,"number":282,"head":{"ref":"release/0.80.0","sha":"f5c288a700b7f213ab23de005a5291f118fc2066","repo":{"id":1007599554,"url":"https://api.github.com/repos/k-nuth/kth","name":"kth"}},"base":{"ref":"master","sha":"c9ce7119eb088f231fa760428ecdb446984716bd","repo":{"id":1007599554,"url":"https://api.github.com/repos/k-nuth/kth","name":"kth"}}}},"public":true,"created_at":"2026-04-21T10:05:10Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10847131206","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33138930414,"ref":"refs/heads/master","head":"edefe152aa1b4a7409dd06701c55c66dc9cff520","before":"09e8c97408f194aaae0a80c70041111ce1c0f23d"},"public":true,"created_at":"2026-04-21T09:59:36Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627901785","type":"IssueCommentEvent","actor":{"id":22429695,"login":"codecov[bot]","display_login":"codecov","gravatar_id":"","url":"https://api.github.com/users/codecov[bot]","avatar_url":"https://avatars.githubusercontent.com/u/22429695?"},"repo":{"id":1007599554,"name":"k-nuth/kth","url":"https://api.github.com/repos/k-nuth/kth"},"payload":{"action":"created","issue":{"url":"https://api.github.com/repos/k-nuth/kth/issues/282","repository_url":"https://api.github.com/repos/k-nuth/kth","labels_url":"https://api.github.com/repos/k-nuth/kth/issues/282/labels{/name}","comments_url":"https://api.github.com/repos/k-nuth/kth/issues/282/comments","events_url":"https://api.github.com/repos/k-nuth/kth/issues/282/events","html_url":"https://github.com/k-nuth/kth/pull/282","id":4301657159,"node_id":"PR_kwDOPA6_ws7UP_Gb","number":282,"title":"release: 0.80.0","user":{"login":"fpelliccioni","id":273683,"node_id":"MDQ6VXNlcjI3MzY4Mw==","avatar_url":"https://avatars.githubusercontent.com/u/273683?v=4","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","html_url":"https://github.com/fpelliccioni","followers_url":"https://api.github.com/users/fpelliccioni/followers","following_url":"https://api.github.com/users/fpelliccioni/following{/other_user}","gists_url":"https://api.github.com/users/fpelliccioni/gists{/gist_id}","starred_url":"https://api.github.com/users/fpelliccioni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fpelliccioni/subscriptions","organizations_url":"https://api.github.com/users/fpelliccioni/orgs","repos_url":"https://api.github.com/users/fpelliccioni/repos","events_url":"https://api.github.com/users/fpelliccioni/events{/privacy}","received_events_url":"https://api.github.com/users/fpelliccioni/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"closed","locked":false,"assignees":[],"milestone":null,"comments":2,"created_at":"2026-04-21T09:33:14Z","updated_at":"2026-04-21T10:05:53Z","closed_at":"2026-04-21T10:05:10Z","assignee":null,"type":null,"active_lock_reason":null,"draft":false,"pull_request":{"url":"https://api.github.com/repos/k-nuth/kth/pulls/282","html_url":"https://github.com/k-nuth/kth/pull/282","diff_url":"https://github.com/k-nuth/kth/pull/282.diff","patch_url":"https://github.com/k-nuth/kth/pull/282.patch","merged_at":"2026-04-21T10:05:10Z"},"body":"release: 0.80.0\n\n<!-- CURSOR_SUMMARY -->\n---\n\n> [!NOTE]\n> **Low Risk**\n> Low risk release housekeeping: documentation version bumps and dependency lockfile updates; no runtime code changes.\n> \n> **Overview**\n> Updates release artifacts for `0.80.0` by bumping all module install instructions in READMEs from `0.79.0` to `0.80.0`.\n> \n> Adds a new `build-create/conan.lock` for reproducible native builds and refreshes `conan-wasm.lock` to a newer `emsdk/5.0.3` revision.\n> \n> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f5c288a700b7f213ab23de005a5291f118fc2066. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>\n<!-- /CURSOR_SUMMARY -->","reactions":{"url":"https://api.github.com/repos/k-nuth/kth/issues/282/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/k-nuth/kth/issues/282/timeline","performed_via_github_app":null,"state_reason":null},"comment":{"url":"https://api.github.com/repos/k-nuth/kth/issues/comments/4287536313","html_url":"https://github.com/k-nuth/kth/pull/282#issuecomment-4287536313","issue_url":"https://api.github.com/repos/k-nuth/kth/issues/282","id":4287536313,"node_id":"IC_kwDOPA6_ws7_jpy5","user":{"login":"codecov[bot]","id":22429695,"node_id":"MDM6Qm90MjI0Mjk2OTU=","avatar_url":"https://avatars.githubusercontent.com/in/254?v=4","gravatar_id":"","url":"https://api.github.com/users/codecov%5Bbot%5D","html_url":"https://github.com/apps/codecov","followers_url":"https://api.github.com/users/codecov%5Bbot%5D/followers","following_url":"https://api.github.com/users/codecov%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/codecov%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/codecov%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codecov%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/codecov%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/codecov%5Bbot%5D/repos","events_url":"https://api.github.com/users/codecov%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/codecov%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"created_at":"2026-04-21T09:47:06Z","updated_at":"2026-04-21T10:05:53Z","body":"## [Codecov](https://app.codecov.io/gh/k-nuth/kth/pull/282?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=k-nuth) Report\n:white_check_mark: All modified and coverable lines are covered by tests.\n:white_check_mark: Project coverage is 48.96%. Comparing base ([`c9ce711`](https://app.codecov.io/gh/k-nuth/kth/commit/c9ce7119eb088f231fa760428ecdb446984716bd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=k-nuth)) to head ([`f5c288a`](https://app.codecov.io/gh/k-nuth/kth/commit/f5c288a700b7f213ab23de005a5291f118fc2066?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=k-nuth)).\n:warning: Report is 2 commits behind head on master.\n\n<details><summary>Additional details and impacted files</summary>\n\n\n\n```diff\n@@           Coverage Diff           @@\n##           master     #282   +/-   ##\n=======================================\n  Coverage   48.96%   48.96%           \n=======================================\n  Files         325      325           \n  Lines       16207    16207           \n  Branches     5911     5911           \n=======================================\n  Hits         7936     7936           \n  Misses       5970     5970           \n  Partials     2301     2301           \n```\n</details>\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/k-nuth/kth/pull/282?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=k-nuth).   \n:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=k-nuth).\n<details><summary> :rocket: New features to boost your workflow: </summary>\n\n- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.\n</details>","reactions":{"url":"https://api.github.com/repos/k-nuth/kth/issues/comments/4287536313/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":{"id":254,"client_id":"Iv1.9d88ef6ecc903626","slug":"codecov","node_id":"MDM6QXBwMjU0","owner":{"login":"codecov","id":8226205,"node_id":"MDEyOk9yZ2FuaXphdGlvbjgyMjYyMDU=","avatar_url":"https://avatars.githubusercontent.com/u/8226205?v=4","gravatar_id":"","url":"https://api.github.com/users/codecov","html_url":"https://github.com/codecov","followers_url":"https://api.github.com/users/codecov/followers","following_url":"https://api.github.com/users/codecov/following{/other_user}","gists_url":"https://api.github.com/users/codecov/gists{/gist_id}","starred_url":"https://api.github.com/users/codecov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codecov/subscriptions","organizations_url":"https://api.github.com/users/codecov/orgs","repos_url":"https://api.github.com/users/codecov/repos","events_url":"https://api.github.com/users/codecov/events{/privacy}","received_events_url":"https://api.github.com/users/codecov/received_events","type":"Organization","user_view_type":"public","site_admin":false},"name":"Codecov","description":"Codecov provides highly integrated tools to group, merge, archive and compare coverage reports. Whether your team is comparing changes in a pull request or reviewing a single commit, Codecov will improve the code review workflow and quality.\r\n\r\n## Code coverage done right.®\r\n\r\n1. Upload coverage reports from your CI builds.\r\n2. Codecov merges all builds and languages into one beautiful coherent report.\r\n3. Get commit statuses, pull request comments and coverage overlay via our browser extension.\r\n\r\nWhen Codecov merges your uploads it keeps track of the CI provider (inc. build details) and user specified context, e.g. `#unittest` ~ `#smoketest` or `#oldcode` ~ `#newcode`. You can track the `#unittest` coverage independently of other groups. [Learn more here](\r\nhttp://docs.codecov.io/docs/flags)\r\n\r\nThrough **Codecov's Browser Extension** reports overlay directly in GitHub UI to assist in code review in [Chrome](https://chrome.google.com/webstore/detail/codecov/gedikamndpbemklijjkncpnolildpbgo) or Firefox (https://addons.mozilla.org/en-US/firefox/addon/codecov/)\r\n\r\n*Highly detailed* **pull request comments** and *customizable* **commit statuses** will improve your team's workflow and code coverage incrementally.\r\n\r\n**File backed configuration** all through the `codecov.yml`. \r\n\r\n## FAQ\r\n- Do you **merge multiple uploads** to the same commit? **Yes**\r\n- Do you **support multiple languages** in the same project? **Yes**\r\n- Can you **group coverage reports** by project and/or test type? **Yes**\r\n- How does **pricing** work? Only paid users can view reports and post statuses/comments. ","external_url":"https://codecov.io","html_url":"https://github.com/apps/codecov","created_at":"2016-09-25T14:18:27Z","updated_at":"2025-03-20T19:12:59Z","permissions":{"administration":"read","checks":"write","contents":"read","emails":"read","issues":"read","members":"read","metadata":"read","pull_requests":"write","statuses":"write"},"events":["delete","fork","member","membership","organization","public","pull_request","push","release","repository","status","team_add"]}}},"public":true,"created_at":"2026-04-21T09:47:06Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10846618601","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33138417916,"ref":"refs/heads/master","head":"09e8c97408f194aaae0a80c70041111ce1c0f23d","before":"5c3e6f4f62fee41a3ffce0132e8a679f779930a9"},"public":true,"created_at":"2026-04-21T09:46:26Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10846496225","type":"CreateEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"ref":"feature/sync-kth-0.80-tests","ref_type":"branch","full_ref":"refs/heads/feature/sync-kth-0.80-tests","master_branch":"master","description":null,"pusher_type":"user"},"public":true,"created_at":"2026-04-21T09:43:26Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10846391138","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33138190457,"ref":"refs/heads/master","head":"5c3e6f4f62fee41a3ffce0132e8a679f779930a9","before":"91d2d801aa49225e5205bad94b5a1cb9d0e1bd74"},"public":true,"created_at":"2026-04-21T09:40:45Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"10846205878","type":"PushEvent","actor":{"id":273683,"login":"fpelliccioni","display_login":"fpelliccioni","gravatar_id":"","url":"https://api.github.com/users/fpelliccioni","avatar_url":"https://avatars.githubusercontent.com/u/273683?"},"repo":{"id":617118407,"name":"k-nuth/conan-packages","url":"https://api.github.com/repos/k-nuth/conan-packages"},"payload":{"repository_id":617118407,"push_id":33138005229,"ref":"refs/heads/master","head":"91d2d801aa49225e5205bad94b5a1cb9d0e1bd74","before":"a39e4f4a2441dc5bb56fe59038305f66eb99e6fc"},"public":true,"created_at":"2026-04-21T09:35:58Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627493882","type":"PullRequestReviewEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"review":{"id":4146628261,"node_id":"PRR_kwDODiJdjc73KIal","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"**Actionable comments posted: 15**\n\n> [!CAUTION]\n> Some comments are outside the diff and can’t be posted inline due to platform limitations.\n> \n> \n> \n> <details>\n> <summary>⚠️ Outside diff range comments (2)</summary><blockquote>\n> \n> <details>\n> <summary>src/chain/chain.cpp (2)</summary><blockquote>\n> \n> `90-99`: _⚠️ Potential issue_ | _🟠 Major_\n> \n> **Missing `PyGILState_Ensure`/`PyGILState_Release` in several fetch handlers.**\n> \n> `chain_fetch_merkle_block_handler` (L90), `chain_fetch_history_handler` (L250), `chain_stealth_fetch_handler` (L336), `chain_fetch_block_height_fetch_handler` (L296), `chain_fetch_transaction_position_handler` (L452), `chain_organize_handler` (L485), `chain_validate_tx_handler` (L539), `chain_fetch_compact_block_handler` (L571), and `chain_fetch_spend_handler` (L628) all call `to_py_obj`, `Py_BuildValue`, `PyObject_CallObject`, `Py_DECREF`, etc. without first acquiring the GIL — unlike their siblings (`chain_fetch_block_handler` L22, `chain_fetch_last_height_handler` L212, `chain_fetch_transaction_handler` L371, and both subscribe handlers) that correctly wrap the body in `PyGILState_Ensure()`/`PyGILState_Release()`.\n> \n> These callbacks are invoked from kth worker threads, so touching CPython state without the GIL is undefined behavior (silent refcount corruption, segfaults under load). The PR only retyped the first argument of these handlers to `*_mut_t`, but the GIL gap is now easier to spot while you're in here. Worth fixing in this PR or immediately after.\n> \n> <details>\n> <summary>🔒 Proposed fix (apply to each affected handler)</summary>\n> \n> ```diff\n>  void chain_fetch_merkle_block_handler(kth_chain_t chain, void* ctx, kth_error_code_t error, kth_merkle_block_mut_t merkle, kth_size_t h) {\n> +    PyGILState_STATE gstate = PyGILState_Ensure();\n> +\n>      PyObject* py_callback = (PyObject*)ctx;\n>      PyObject* py_merkle = to_py_obj(merkle);\n> \n>      PyObject* arglist = Py_BuildValue(\"(iOK)\", error, py_merkle, h);\n>      PyObject_CallObject(py_callback, arglist);\n>      Py_DECREF(arglist);\n>      Py_XDECREF(py_callback);  // Dispose of the call\n> +\n> +    PyGILState_Release(gstate);\n>  }\n> ```\n> </details>\n> \n> <details>\n> <summary>🤖 Prompt for AI Agents</summary>\n> \n> ```\n> Verify each finding against the current code and only fix it if needed.\n> \n> In `@src/chain/chain.cpp` around lines 90 - 99, Each listed callback\n> (chain_fetch_merkle_block_handler, chain_fetch_history_handler,\n> chain_stealth_fetch_handler, chain_fetch_block_height_fetch_handler,\n> chain_fetch_transaction_position_handler, chain_organize_handler,\n> chain_validate_tx_handler, chain_fetch_compact_block_handler,\n> chain_fetch_spend_handler) is touching CPython API without holding the GIL; wrap\n> the Python-API section of each handler with PyGILState_Ensure() at the start of\n> the region that calls to_py_obj / Py_BuildValue / PyObject_CallObject /\n> Py_DECREF / Py_XDECREF and use PyGILState_Release() at the end so all\n> reference-counted operations execute while the GIL is held, ensuring the GIL\n> acquire/release surrounds creation of py_merkle/py_* objects, the\n> PyObject_CallObject invocation, and subsequent DECREFs.\n> ```\n> \n> </details>\n> \n> ---\n> \n> `283-290`: _⚠️ Potential issue_ | _🟠 Major_\n> \n> **Resolve the payment address leak at L285–287.**\n> \n> `kth_wallet_payment_address_construct_from_address` returns a mutable handle that is never destructed—the `payment_address_destruct(pa)` call is commented out at L287 and `chain_fetch_history_handler` does not clean it up. Since `pa` is a raw C handle (not wrapped in a PyCapsule), Python garbage collection will not manage it.\n> \n> If `kth_chain_async_history` takes ownership of `pa`, destruct it in `chain_fetch_history_handler` after the handler completes (not inline). If it makes a copy, destruct immediately after the call. Uncomment the inline destruct only in the latter case to avoid double-free.\n> \n> <details>\n> <summary>🤖 Prompt for AI Agents</summary>\n> \n> ```\n> Verify each finding against the current code and only fix it if needed.\n> \n> In `@src/chain/chain.cpp` around lines 283 - 290,\n> kth_wallet_payment_address_construct_from_address returns a mutable handle (pa)\n> that must be freed to avoid a leak; inspect kth_chain_async_history to determine\n> ownership: if kth_chain_async_history makes a copy of the payment address,\n> uncomment and call payment_address_destruct(pa) immediately after the\n> kth_chain_async_history call in the function that constructs pa; if\n> kth_chain_async_history takes ownership, ensure chain_fetch_history_handler\n> calls payment_address_destruct(pa) after it finishes using pa (i.e., free pa at\n> the end of chain_fetch_history_handler) and do not free it inline to avoid\n> double-free. Include the exact symbol names\n> (kth_wallet_payment_address_construct_from_address, kth_chain_async_history,\n> chain_fetch_history_handler, payment_address_destruct) when editing so reviewers\n> can find the changes.\n> ```\n> \n> </details>\n> \n> </blockquote></details>\n> \n> </blockquote></details>\n\n<details>\n<summary>🧹 Nitpick comments (8)</summary><blockquote>\n\n<details>\n<summary>src/wallet/wallet_data.cpp (1)</summary><blockquote>\n\n`49-89`: **Address borrowed-reference lifetime semantics across ~50+ accessor methods returning non-owning capsules.**\n\nThis pattern appears throughout the bindings: accessor methods like `mnemonics`, `xpub`, and many others (`payment_address_list_nth`, `transaction_inputs`, `script_operations`, etc.) all return a capsule with `NULL` destructor — a non-owning view into a parent object. If Python code retains one of these capsules past the parent's destruction (explicit or via GC), subsequent use dereferences freed memory.\n\nWhile the pattern is consistent and the parent-lifetime dependency is implicit in the current design, decide now how to formalize it:\n\n1. **Preferred:** Make returned capsules hold a strong reference to the parent using `PyCapsule_SetContext` + a wrapper destructor that `Py_DECREF`s the parent. This ensures the parent stays alive as long as any borrowed capsule is referenced.\n2. **Alternative:** Document (in `.pyi` / Python wrapper) that all `*_nth`, `*_operations`, `*_inputs`, `*_outputs`, and accessor methods return borrowed views whose lifetime is bound to the parent, with a code example showing correct usage.\n\nNot blocking, but worth deciding and implementing consistently as the pattern spans ~50+ methods across wallet, chain, and core modules.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/wallet_data.cpp` around lines 49 - 89, Accessor methods (e.g.,\nkth_py_native_wallet_wallet_data_mnemonics,\nkth_py_native_wallet_wallet_data_xpub and the many other *_nth / *_operations /\n*_inputs accessors) currently return non-owning PyCapsule objects with NULL\ndestructors, risking dangling references when the parent is freed; change them\nto set the parent capsule as the capsule context and provide a small destructor\nthat Py_DECREFs that parent so the parent stays alive while the borrowed capsule\nexists: after creating the result capsule with PyCapsule_New, call\nPyCapsule_SetContext(result_capsule, (void*)py_self) and register a destructor\nfunction that calls Py_XDECREF((PyObject*)PyCapsule_GetContext(capsule)); update\nall similar accessors to use this pattern (replace NULL destructor and add\nSetContext+destructor) for consistent strong-reference semantics across wallet,\nchain, and core bindings.\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/wallet/payment_address_list.cpp (1)</summary><blockquote>\n\n`61-72`: **Add bounds validation to prevent opaque `ValueError` when accessing out-of-range indices.**\n\n`kth_wallet_payment_address_list_nth` appears to return NULL for out-of-range indices, causing `PyCapsule_New(NULL, ...)` to raise `ValueError: \"PyCapsule_New called with null pointer\"` — safe but unclear to callers. Use `kth_wallet_payment_address_list_count` to validate the index before calling `nth`, or return an explicit `IndexError` on NULL. The same issue affects `assign_at` and `erase`.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/payment_address_list.cpp` around lines 61 - 72, The current\nkth_py_native_wallet_payment_address_list_nth calls\nkth_wallet_payment_address_list_nth and blindly wraps the result in\nPyCapsule_New which throws an opaque ValueError when NULL; fix by checking the\nindex against kth_wallet_payment_address_list_count(list_handle) and if index is\nout of range set a Python IndexError (PyErr_SetString(PyExc_IndexError, \"...\"))\nand return NULL instead of calling nth; apply the same pattern to the related\nfunctions that call kth_wallet_payment_address_list_assign_at and\nkth_wallet_payment_address_list_erase (validate index with\nkth_wallet_payment_address_list_count and raise IndexError rather than letting\nPyCapsule_New or underlying NULLs propagate).\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/chain/utxo.cpp (1)</summary><blockquote>\n\n`84-95`: **Document that the returned `output_point` capsule is a non-owning view.**\n\n`kth_py_native_chain_utxo_point` wraps the raw pointer returned by `kth_chain_utxo_point` in a capsule with a `NULL` destructor. The Python side therefore holds a borrowed reference whose validity is tied to the parent UTXO capsule — if the parent is explicitly `destruct`ed (capsule retagged to `\"kth.destroyed\"`) or GC'd while the point capsule is still in use, subsequent reads via the point capsule would dereference freed memory, and a user calling any `output_point_destruct`-style API on it would cause a double-free.\n\nTwo things worth confirming/doing:\n- Confirm `kth_chain_utxo_point` indeed returns a pointer into the UTXO (not a fresh owned copy). If it’s owned, the capsule should get `kth_py_native_chain_output_point_capsule_dtor` instead of `NULL`.\n- If borrowed is intentional, a short comment near the call site (and a note in `kth_native.pyi`) would help future callers avoid the footgun.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/utxo.cpp` around lines 84 - 95, kth_py_native_chain_utxo_point\ncurrently wraps the pointer returned from kth_chain_utxo_point into a PyCapsule\nwith a NULL destructor, but we must confirm ownership and document behavior:\nverify whether kth_chain_utxo_point returns a pointer into the UTXO (borrowed)\nor an owned copy; if it returns an owned copy, replace the NULL destructor with\nkth_py_native_chain_output_point_capsule_dtor so the capsule frees the memory;\nif it is borrowed, add a brief inline comment next to\nkth_py_native_chain_utxo_point and update kth_native.pyi to state that the\nreturned capsule (KTH_PY_CAPSULE_CHAIN_OUTPUT_POINT) is a non-owning view tied\nto the lifetime of the parent UTXO capsule to avoid double-free/use-after-free\nhazards.\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/chain/prefilled_transaction_list.cpp (1)</summary><blockquote>\n\n`61-101`: **Consider validating `index` against `count()` before dispatching to the C-API.**\n\n`nth`, `assign_at`, and `erase` forward `index` directly into the native list. If the C-API doesn’t range-check, an out-of-bounds `nth` returns `NULL` and `PyCapsule_New` then raises a confusing `ValueError: PyCapsule_New called with null pointer` rather than a clear `IndexError`; `assign_at`/`erase` may touch invalid memory. A short guard using `kth_chain_prefilled_transaction_list_count` would give predictable Python semantics regardless of the C-API contract.\n\n\nPlease confirm whether `kth_chain_prefilled_transaction_list_{nth,assign_at,erase}` in kth 0.80.0 perform bounds checking internally (and what they return / whether they abort on OOB).\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/prefilled_transaction_list.cpp` around lines 61 - 101, The three\nwrappers (kth_py_native_chain_prefilled_transaction_list_nth,\nkth_py_native_chain_prefilled_transaction_list_assign_at,\nkth_py_native_chain_prefilled_transaction_list_erase) must validate the incoming\nindex against kth_chain_prefilled_transaction_list_count(list_handle) before\ncalling the C API: check index < count (after converting count to a compatible\ntype), and if out of range raise a Python IndexError and return NULL for nth (do\nnot call kth_chain_prefilled_transaction_list_nth or PyCapsule_New) and return\nNULL for assign_at/erase (do not call\nkth_chain_prefilled_transaction_list_assign_at/erase) to avoid null-capsule or\nmemory corruption; perform the kth_size_t cast only after the bounds check.\nEnsure these checks reference the existing symbols\nkth_chain_prefilled_transaction_list_count,\nkth_chain_prefilled_transaction_list_nth,\nkth_chain_prefilled_transaction_list_assign_at, and\nkth_chain_prefilled_transaction_list_erase.\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/module.c (2)</summary><blockquote>\n\n`889-1046`: **Consider a local macro for `PyModule_AddFunctions` boilerplate.**\n\nThe AUTO-GENERATED REGISTER block repeats the same 4-line `if (PyModule_AddFunctions(...) < 0) { Py_DECREF(module); return NULL; }` pattern ~40 times. A small macro (emitted once by the generator or added here) would cut ~120 lines and make future regenerations diff-friendlier.\n\n<details>\n<summary>♻️ Example</summary>\n\n```c\n`#define` KTH_REGISTER_METHODS(table)                               \\\n    do {                                                          \\\n        if (PyModule_AddFunctions(module, (table)) < 0) {         \\\n            Py_DECREF(module);                                    \\\n            return NULL;                                          \\\n        }                                                         \\\n    } while (0)\n\n// ...\nKTH_REGISTER_METHODS(kth_py_native_chain_block_methods);\nKTH_REGISTER_METHODS(kth_py_native_chain_block_list_methods);\n// ...\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/module.c` around lines 889 - 1046, The repeated pattern using\nPyModule_AddFunctions(module, ...) should be refactored to a small local macro\nto reduce boilerplate and make regenerations cleaner; add a macro (e.g.\nKTH_REGISTER_METHODS) that takes a method table symbol and performs the\nif-check, Py_DECREF(module) and return NULL on failure, then replace each\nrepeated 4-line block with single calls like\nKTH_REGISTER_METHODS(kth_py_native_chain_block_methods) referencing the existing\nmodule variable and the various kth_py_native_*_methods symbols; ensure the\nmacro is defined near the AUTO-GENERATED REGISTER block so the generator can\nreuse it.\n```\n\n</details>\n\n---\n\n`35-75`: **Drop duplicate includes now that the AUTO-GENERATED block is authoritative.**\n\nLines 61, 70, 71, 72, 73, 74, 75 re-include headers that are already pulled in by the AUTO-GENERATED INCLUDES block above (`merkle_block.h`, `compact_block.h`, `payment_address.h`, `block_list.h`, `transaction_list.h`, `stealth_compact.h`, `stealth_compact_list.h`). Header guards make this harmless at compile time, but it hides whether the generator or the hand-maintained list is the source of truth and will drift further as more types are generator-owned.\n\n<details>\n<summary>♻️ Suggested cleanup</summary>\n\n```diff\n // ── AUTO-GENERATED INCLUDES END ───────────────────────────────────────\n-#include <kth/py-native/chain/merkle_block.h>\n `#include` <kth/py-native/chain/word_list.h>\n-#include <kth/py-native/chain/transaction.h>\n-#include <kth/py-native/chain/output.h>\n-#include <kth/py-native/chain/output_list.h>\n-#include <kth/py-native/chain/input.h>\n-#include <kth/py-native/chain/input_list.h>\n-#include <kth/py-native/chain/script.h>\n-#include <kth/py-native/chain/output_point.h>\n-#include <kth/py-native/chain/compact_block.h>\n-#include <kth/py-native/wallet/payment_address.h>\n-#include <kth/py-native/chain/block_list.h>\n-#include <kth/py-native/chain/transaction_list.h>\n-#include <kth/py-native/chain/stealth_compact.h>\n-#include <kth/py-native/chain/stealth_compact_list.h>\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/module.c` around lines 35 - 75, Remove the duplicate manual includes that\nfollow the AUTO-GENERATED INCLUDES block so the generator remains authoritative;\nspecifically delete the second occurrences of merkle_block.h, compact_block.h,\npayment_address.h, block_list.h, transaction_list.h, stealth_compact.h and\nstealth_compact_list.h (the includes after the \"// ── AUTO-GENERATED INCLUDES\nEND ───────────────────────────────────────\" marker) and keep only the\nauto-generated include block intact.\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/wallet/hd_public.cpp (1)</summary><blockquote>\n\n`44-72`: **Replace the magic `82` with `KTH_HD_KEY_SIZE`.**\n\nThe literal `82` appears four times (L44, L49, L67, L72) and represents the HD key size that is already named `KTH_HD_KEY_SIZE` in the same file (used at L233 for `to_hd_key`). Using the constant keeps the two bindings consistent and protects against a future upstream resize.\n\n\n<details>\n<summary>♻️ Proposed change</summary>\n\n```diff\n-    if (public_key_size != (Py_ssize_t)82) {\n-        PyErr_Format(PyExc_ValueError, \"expected %d-byte public_key, got %zd\", (int)82, public_key_size);\n+    if (public_key_size != (Py_ssize_t)KTH_HD_KEY_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte public_key, got %zd\", (int)KTH_HD_KEY_SIZE, public_key_size);\n         return NULL;\n     }\n     kth_hd_key_t public_key;\n-    memcpy(public_key.data, public_key_buf, (size_t)82);\n+    memcpy(public_key.data, public_key_buf, (size_t)KTH_HD_KEY_SIZE);\n```\n\n(apply to both `construct_from_public_key` and `construct_from_public_key_prefix`)\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/hd_public.cpp` around lines 44 - 72, Replace the four occurrences\nof the literal 82 in kth_py_native_wallet_hd_public_construct_from_public_key\nand kth_py_native_wallet_hd_public_construct_from_public_key_prefix with the\nexisting constant KTH_HD_KEY_SIZE; update the PyErr_Format checks, the memcpy\nlength cast, and any (int) or (size_t) casts that currently reference 82 so they\nuse KTH_HD_KEY_SIZE to ensure consistency (e.g., the error message width, the\npublic_key_size comparison, and the memcpy length).\n```\n\n</details>\n\n</blockquote></details>\n<details>\n<summary>src/chain/operation.cpp (1)</summary><blockquote>\n\n`163-179`: **Minor: simplify the `NULL` branch in `to_string`.**\n\nThe comma-expression `(Py_INCREF(Py_None), Py_None)` works but is unidiomatic; `Py_RETURN_NONE` or an explicit early return reads better and avoids interleaving the incref with the ternary. Purely cosmetic, no functional impact.\n\n\n\n<details>\n<summary>♻️ Suggested cleanup</summary>\n\n```diff\n-    auto const result = kth_chain_operation_to_string(self_handle, (kth_script_flags_t)active_flags);\n-    PyObject* py_result = (result == NULL)\n-        ? (Py_INCREF(Py_None), Py_None)\n-        : Py_BuildValue(\"s\", result);\n-    if (result != NULL) kth_core_destruct_string(result);\n-    return py_result;\n+    auto const result = kth_chain_operation_to_string(self_handle, (kth_script_flags_t)active_flags);\n+    if (result == NULL) Py_RETURN_NONE;\n+    PyObject* py_result = Py_BuildValue(\"s\", result);\n+    kth_core_destruct_string(result);\n+    return py_result;\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/operation.cpp` around lines 163 - 179, Replace the unidiomatic\ncomma-expression ternary in kth_py_native_chain_operation_to_string with an\nexplicit None return: when kth_chain_operation_to_string returns NULL use\nPy_RETURN_NONE (which handles the incref) and return early; otherwise build\npy_result with Py_BuildValue(\"s\", result) and then call\nkth_core_destruct_string(result) before returning py_result. This simplifies the\ncontrol flow and avoids interleaving Py_INCREF with the ternary expression.\n```\n\n</details>\n\n</blockquote></details>\n\n</blockquote></details>\n\n<details>\n<summary>🤖 Prompt for all review comments with AI agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nInline comments:\nIn `@include/kth/py-native/wallet/payment_address_list.h`:\n- Around line 5-29: The include-guard macro in this header is incorrect: replace\nthe existing macro name KTH_PY_NATIVE_CHAIN_PAYMENT_ADDRESS_LIST_H_ (and its\nmatching `#ifndef/`#define/#endif) with\nKTH_PY_NATIVE_WALLET_PAYMENT_ADDRESS_LIST_H_ so the guard reflects the wallet\nsubdirectory; update all three occurrences (the `#ifndef`, `#define`, and the\ntrailing comment on the `#endif`) and verify the symbol names referenced in this\nfile (e.g., kth_py_native_wallet_payment_address_list_construct_default,\nkth_py_native_wallet_payment_address_list_methods) remain unchanged; if this\nfile was generated, also audit the v2 generator template to prevent regressions.\n\nIn `@kth_native.pyi`:\n- Around line 1418-1426: Update the return type annotations of the three factory\nfunctions so they reference the declared type token alias TokenDataT: change\nchain_token_data_make_fungible, chain_token_data_make_non_fungible, and\nchain_token_data_make_both to return \"TokenDataT\" (instead of the nonexistent\n\"TokenData\") so the stub aligns with the declared TokenDataT at/around the\nTokenDataT declaration.\n- Around line 1362-1373: The stub uses an undefined type name\nDoubleSpendProofSpender in the signatures for\nchain_double_spend_proof_construct, chain_double_spend_proof_spender1,\nchain_double_spend_proof_set_spender1, chain_double_spend_proof_spender2 and\nchain_double_spend_proof_set_spender2; update those signatures to reference the\nexisting Spender type (e.g., replace DoubleSpendProofSpender with Spender in\nchain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1:\n\"DoubleSpendProofSpender\", spender2: \"DoubleSpendProofSpender\") and the\ngetter/setter signatures) so the annotations match the defined class name.\n\nIn `@src/chain/double_spend_proof.cpp`:\n- Around line 204-226: The to_data function\nkth_py_native_chain_double_spend_proof_to_data (and the sibling serialized_size\nwrapper) currently parse version as Py_ssize_t with format \"n\" and validate\nnon-negativity; change both to parse version as unsigned int using format \"I\"\n(i.e. replace Py_ssize_t version with unsigned int version and format \"n\"->\"I\"),\nremove the negative-value check branches, and cast the parsed value to uint32_t\nwhen passing to kth_chain_double_spend_proof_to_data /\nkth_chain_double_spend_proof_serialized_size so the parameter types match the\nother chain message wrappers and avoid 64->32-bit truncation.\n\nIn `@src/chain/history_compact.cpp`:\n- Around line 26-37: kth_py_native_chain_history_compact_copy allocates a native\nobject with kth_chain_history_compact_copy then creates a PyCapsule with\nPyCapsule_New; if PyCapsule_New fails the native result is leaked — fix by\nchecking the return of PyCapsule_New and if it returns NULL immediately free the\nnative object (call the native destructor, e.g.\nkth_chain_history_compact_destruct or the appropriate free function) before\nreturning NULL; ensure similar guarding is applied to other construct_*/copy\nwrappers that follow the same pattern and reference\nkth_py_native_chain_history_compact_capsule_dtor for consistent cleanup.\n- Around line 72-83: The returned point capsule from\nkth_py_native_chain_history_compact_point holds a borrowed pointer into the\nparent history_compact and currently has no ownership tie, allowing the parent\n(kth_history_compact_const_t / KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT) to be GC'd\nand cause a use-after-free; fix by making the child capsule keep a strong\nreference to the parent capsule before returning (e.g., call\nPyCapsule_SetContext on the new KTH_PY_CAPSULE_CHAIN_POINT capsule to store a\nnew reference to the original py_self, or otherwise return a tuple (py_self,\ncapsule) or Python wrapper that retains py_self) so\nkth_chain_history_compact_point results remain valid while the child is alive.\nEnsure you apply the same pattern to other functions using borrowed views (e.g.,\nprefilled_transaction_transaction, *_list_nth) and do not change the parent's\nNULL destructor unless you also manage proper refcounting.\n\nIn `@src/chain/operation.cpp`:\n- Around line 26-96: Each constructor/copy\n(kth_py_native_chain_operation_construct_default,\nkth_py_native_chain_operation_construct_from_data,\nkth_py_native_chain_operation_construct_from_uncoded_minimal,\nkth_py_native_chain_operation_construct_from_code,\nkth_py_native_chain_operation_copy) currently returns a newly-allocated native\nhandle wrapped by PyCapsule_New but does not free the native handle if\nPyCapsule_New itself fails; update each function so that after creating the\nnative pointer (e.g., result or out) you attempt PyCapsule_New and if it returns\nNULL call the corresponding destructor (kth_chain_operation_destruct) on the\nnative handle before returning NULL to avoid leaks, and ensure you only call the\ndestructor on successfully-allocated handles (for construct_from_data use the\nout variable).\n- Around line 265-274: The function\nkth_py_native_chain_operation_opcode_to_positive (and the sibling functions\nopcode_to_positive, is_push, is_payload, is_counted, is_version, is_numeric,\nis_positive, is_reserved, is_disabled, is_conditional, is_relaxed_push,\nconstruct_from_code) currently parse the incoming code with format \"K\" and cast\nto kth_opcode_t, which silently truncates values ≥256; fix by making the parsing\nstrict to an 8-bit value—either change PyArg_ParseTupleAndKeywords format to \"B\"\n(unsigned char) like opcode_from_positive uses, or keep \"K\" but add an explicit\nrange check (0 <= code <= 255) before casting and return a Python exception on\nout-of-range input; apply the same change to all listed functions to ensure\nconsistent 0–255 validation before casting to kth_opcode_t.\n\nIn `@src/chain/prefilled_transaction.cpp`:\n- Around line 26-83: The four functions\n(kth_py_native_chain_prefilled_transaction_construct_default,\nkth_py_native_chain_prefilled_transaction_construct_from_data,\nkth_py_native_chain_prefilled_transaction_construct,\nkth_py_native_chain_prefilled_transaction_copy) can leak the native\nkth_prefilled_transaction when PyCapsule_New fails; add a small helper (e.g.,\nwrap_or_destruct) that accepts the allocated kth_prefilled_transaction_mut_t,\ncalls PyCapsule_New(..., KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION,\nkth_py_native_chain_prefilled_transaction_capsule_dtor) and if PyCapsule_New\nreturns NULL immediately calls the native destructor\n(kth_chain_prefilled_transaction_destruct or the appropriate kth_*_destruct for\nprefilled_transaction) to free the handle and then returns NULL; replace the\ndirect returns of PyCapsule_New in each of the four functions with calls to this\nhelper (use 'out' in construct_from_data and 'result' in the others).\n\nIn `@src/chain/stealth_compact_list.cpp`:\n- Around line 61-72: Check the list bounds and avoid calling PyCapsule_New with\na null pointer: call kth_chain_stealth_compact_list_count(list_handle) before\nnth and if index >= count set an IndexError via\nPyErr_SetString(PyExc_IndexError, \"index out of range\") and return NULL; after\ncalling kth_chain_stealth_compact_list_nth(list_handle, (kth_size_t)index) check\nif result == NULL and if so set a descriptive IndexError (or appropriate error)\nand return NULL; only call PyCapsule_New((void*)result,\nKTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT, NULL) when result is non-NULL. Apply the\nsame guard pattern to the analogous history_compact_list_nth binding.\n\nIn `@src/chain/token_data.cpp`:\n- Around line 112-184: The three factory functions\nkth_py_native_chain_token_data_make_fungible,\nkth_py_native_chain_token_data_make_non_fungible, and\nkth_py_native_chain_token_data_make_both create native kth_token_data_mut_t\nhandles but call PyCapsule_New with a NULL destructor; update each PyCapsule_New\ncall to pass the existing destructor function\nkth_py_native_chain_token_data_capsule_dtor so the native handle is freed when\nthe capsule is GC'd (replace the final NULL argument with\nkth_py_native_chain_token_data_capsule_dtor in each of the three functions).\n\nIn `@src/wallet/hd_private.cpp`:\n- Around line 332-339: The function kth_py_native_wallet_hd_private_lineage\ncurrently serializes the whole kth_hd_lineage_t via Py_BuildValue(\"y#\", &result,\nsizeof(...)) which can leak padding; instead unpack the kth_hd_lineage_t\nreturned by kth_wallet_hd_private_lineage into its individual fields (e.g.,\ndepth, parent_fingerprint, child_number, version/prefix fields — whatever\nmembers exist on kth_hd_lineage_t) and return a Python tuple or dict built with\nPy_BuildValue or PyDict_New/PyDict_SetItemString (matching the pattern used by\nchain_code() and point()); update kth_py_native_wallet_hd_public_lineage (the\nanalogous function in hd_public.cpp) the same way and remove the raw struct\nbinary serialization.\n\nIn `@src/wallet/hd_public.cpp`:\n- Around line 210-216: kth_py_native_wallet_hd_public_lineage currently returns\nthe raw kth_hd_lineage_t bytes; change it to extract the struct fields from the\nkth_wallet_hd_public_lineage result and build a Python tuple (depth,\nparent_fingerprint, child_number) instead of using\nPy_BuildValue(\"y#\",&result,...). Specifically, call\nkth_wallet_hd_public_lineage(self_handle) to get the kth_hd_lineage_t value,\nread its fields explicitly (e.g., depth as an integer, parent_fingerprint as a\nfixed-width byte sequence, child_number as an integer or fixed-width sequence\nconsistent with chain_code/point/to_hd_key conventions), and return a PyTuple\ncontaining those converted Python types; apply the same pattern for the\nanalogous hd_private_lineage binding to avoid exposing raw struct layout and\nplatform-dependent padding/endianness.\n\nIn `@src/wallet/payment_address_list.cpp`:\n- Around line 103-112: The exposed Python C API method names in\nkth_py_native_wallet_payment_address_list_methods use the wrong\n\"chain_payment_address_list_*\" prefix; update all seven entries to use the\n\"wallet_payment_address_list_*\" prefix to match the wallet capsule\n(KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST) and the C functions\n(kth_wallet_payment_address_list_*), then update the generated type stub\nkth_native.pyi to reflect the renamed methods; finally fix the generator\ntemplate (likely generate_capi_v2.py) so it emits \"wallet_\" for wallet module\nlists to prevent this regression.\n\nIn `@src/wallet/payment_address.cpp`:\n- Around line 374-429: The three functions\nkth_py_native_wallet_payment_address_extract,\nkth_py_native_wallet_payment_address_extract_input, and\nkth_py_native_wallet_payment_address_extract_output create\nkth_payment_address_list objects but call PyCapsule_New with a NULL destructor\ncausing a leak; update their PyCapsule_New calls to pass\nkth_py_native_wallet_payment_address_list_capsule_dtor as the destructor (same\nchange also needed for the similar PyCapsule_New calls in\nkth_py_native_chain_output and kth_py_native_chain_input in output.cpp and\ninput.cpp) so the list is freed on capsule GC.\n\n---\n\nOutside diff comments:\nIn `@src/chain/chain.cpp`:\n- Around line 90-99: Each listed callback (chain_fetch_merkle_block_handler,\nchain_fetch_history_handler, chain_stealth_fetch_handler,\nchain_fetch_block_height_fetch_handler,\nchain_fetch_transaction_position_handler, chain_organize_handler,\nchain_validate_tx_handler, chain_fetch_compact_block_handler,\nchain_fetch_spend_handler) is touching CPython API without holding the GIL; wrap\nthe Python-API section of each handler with PyGILState_Ensure() at the start of\nthe region that calls to_py_obj / Py_BuildValue / PyObject_CallObject /\nPy_DECREF / Py_XDECREF and use PyGILState_Release() at the end so all\nreference-counted operations execute while the GIL is held, ensuring the GIL\nacquire/release surrounds creation of py_merkle/py_* objects, the\nPyObject_CallObject invocation, and subsequent DECREFs.\n- Around line 283-290: kth_wallet_payment_address_construct_from_address returns\na mutable handle (pa) that must be freed to avoid a leak; inspect\nkth_chain_async_history to determine ownership: if kth_chain_async_history makes\na copy of the payment address, uncomment and call payment_address_destruct(pa)\nimmediately after the kth_chain_async_history call in the function that\nconstructs pa; if kth_chain_async_history takes ownership, ensure\nchain_fetch_history_handler calls payment_address_destruct(pa) after it finishes\nusing pa (i.e., free pa at the end of chain_fetch_history_handler) and do not\nfree it inline to avoid double-free. Include the exact symbol names\n(kth_wallet_payment_address_construct_from_address, kth_chain_async_history,\nchain_fetch_history_handler, payment_address_destruct) when editing so reviewers\ncan find the changes.\n\n---\n\nNitpick comments:\nIn `@src/chain/operation.cpp`:\n- Around line 163-179: Replace the unidiomatic comma-expression ternary in\nkth_py_native_chain_operation_to_string with an explicit None return: when\nkth_chain_operation_to_string returns NULL use Py_RETURN_NONE (which handles the\nincref) and return early; otherwise build py_result with Py_BuildValue(\"s\",\nresult) and then call kth_core_destruct_string(result) before returning\npy_result. This simplifies the control flow and avoids interleaving Py_INCREF\nwith the ternary expression.\n\nIn `@src/chain/prefilled_transaction_list.cpp`:\n- Around line 61-101: The three wrappers\n(kth_py_native_chain_prefilled_transaction_list_nth,\nkth_py_native_chain_prefilled_transaction_list_assign_at,\nkth_py_native_chain_prefilled_transaction_list_erase) must validate the incoming\nindex against kth_chain_prefilled_transaction_list_count(list_handle) before\ncalling the C API: check index < count (after converting count to a compatible\ntype), and if out of range raise a Python IndexError and return NULL for nth (do\nnot call kth_chain_prefilled_transaction_list_nth or PyCapsule_New) and return\nNULL for assign_at/erase (do not call\nkth_chain_prefilled_transaction_list_assign_at/erase) to avoid null-capsule or\nmemory corruption; perform the kth_size_t cast only after the bounds check.\nEnsure these checks reference the existing symbols\nkth_chain_prefilled_transaction_list_count,\nkth_chain_prefilled_transaction_list_nth,\nkth_chain_prefilled_transaction_list_assign_at, and\nkth_chain_prefilled_transaction_list_erase.\n\nIn `@src/chain/utxo.cpp`:\n- Around line 84-95: kth_py_native_chain_utxo_point currently wraps the pointer\nreturned from kth_chain_utxo_point into a PyCapsule with a NULL destructor, but\nwe must confirm ownership and document behavior: verify whether\nkth_chain_utxo_point returns a pointer into the UTXO (borrowed) or an owned\ncopy; if it returns an owned copy, replace the NULL destructor with\nkth_py_native_chain_output_point_capsule_dtor so the capsule frees the memory;\nif it is borrowed, add a brief inline comment next to\nkth_py_native_chain_utxo_point and update kth_native.pyi to state that the\nreturned capsule (KTH_PY_CAPSULE_CHAIN_OUTPUT_POINT) is a non-owning view tied\nto the lifetime of the parent UTXO capsule to avoid double-free/use-after-free\nhazards.\n\nIn `@src/module.c`:\n- Around line 889-1046: The repeated pattern using PyModule_AddFunctions(module,\n...) should be refactored to a small local macro to reduce boilerplate and make\nregenerations cleaner; add a macro (e.g. KTH_REGISTER_METHODS) that takes a\nmethod table symbol and performs the if-check, Py_DECREF(module) and return NULL\non failure, then replace each repeated 4-line block with single calls like\nKTH_REGISTER_METHODS(kth_py_native_chain_block_methods) referencing the existing\nmodule variable and the various kth_py_native_*_methods symbols; ensure the\nmacro is defined near the AUTO-GENERATED REGISTER block so the generator can\nreuse it.\n- Around line 35-75: Remove the duplicate manual includes that follow the\nAUTO-GENERATED INCLUDES block so the generator remains authoritative;\nspecifically delete the second occurrences of merkle_block.h, compact_block.h,\npayment_address.h, block_list.h, transaction_list.h, stealth_compact.h and\nstealth_compact_list.h (the includes after the \"// ── AUTO-GENERATED INCLUDES\nEND ───────────────────────────────────────\" marker) and keep only the\nauto-generated include block intact.\n\nIn `@src/wallet/hd_public.cpp`:\n- Around line 44-72: Replace the four occurrences of the literal 82 in\nkth_py_native_wallet_hd_public_construct_from_public_key and\nkth_py_native_wallet_hd_public_construct_from_public_key_prefix with the\nexisting constant KTH_HD_KEY_SIZE; update the PyErr_Format checks, the memcpy\nlength cast, and any (int) or (size_t) casts that currently reference 82 so they\nuse KTH_HD_KEY_SIZE to ensure consistency (e.g., the error message width, the\npublic_key_size comparison, and the memcpy length).\n\nIn `@src/wallet/payment_address_list.cpp`:\n- Around line 61-72: The current kth_py_native_wallet_payment_address_list_nth\ncalls kth_wallet_payment_address_list_nth and blindly wraps the result in\nPyCapsule_New which throws an opaque ValueError when NULL; fix by checking the\nindex against kth_wallet_payment_address_list_count(list_handle) and if index is\nout of range set a Python IndexError (PyErr_SetString(PyExc_IndexError, \"...\"))\nand return NULL instead of calling nth; apply the same pattern to the related\nfunctions that call kth_wallet_payment_address_list_assign_at and\nkth_wallet_payment_address_list_erase (validate index with\nkth_wallet_payment_address_list_count and raise IndexError rather than letting\nPyCapsule_New or underlying NULLs propagate).\n\nIn `@src/wallet/wallet_data.cpp`:\n- Around line 49-89: Accessor methods (e.g.,\nkth_py_native_wallet_wallet_data_mnemonics,\nkth_py_native_wallet_wallet_data_xpub and the many other *_nth / *_operations /\n*_inputs accessors) currently return non-owning PyCapsule objects with NULL\ndestructors, risking dangling references when the parent is freed; change them\nto set the parent capsule as the capsule context and provide a small destructor\nthat Py_DECREFs that parent so the parent stays alive while the borrowed capsule\nexists: after creating the result capsule with PyCapsule_New, call\nPyCapsule_SetContext(result_capsule, (void*)py_self) and register a destructor\nfunction that calls Py_XDECREF((PyObject*)PyCapsule_GetContext(capsule)); update\nall similar accessors to use this pattern (replace NULL destructor and add\nSetContext+destructor) for consistent strong-reference semantics across wallet,\nchain, and core bindings.\n```\n\n</details>\n\n<details>\n<summary>🪄 Autofix (Beta)</summary>\n\nFix all unresolved CodeRabbit comments on this PR:\n\n- [ ] <!-- {\"checkboxId\": \"4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9\"} --> Push a commit to this branch (recommended)\n- [ ] <!-- {\"checkboxId\": \"ff5b1114-7d8c-49e6-8ac1-43f82af23a33\"} --> Create a new PR with the fixes\n\n</details>\n\n---\n\n<details>\n<summary>ℹ️ Review info</summary>\n\n<details>\n<summary>⚙️ Run configuration</summary>\n\n**Configuration used**: defaults\n\n**Review profile**: CHILL\n\n**Plan**: Pro\n\n**Run ID**: `33d4c641-5096-4fc1-b2c7-1866eb426c43`\n\n</details>\n\n<details>\n<summary>📥 Commits</summary>\n\nReviewing files that changed from the base of the PR and between 54a59bb17c7600eb146c2e31c2b6be1c316eac72 and d07e5b50f1bc525ffff336f6ca5f89e3a177525d.\n\n</details>\n\n<details>\n<summary>📒 Files selected for processing (74)</summary>\n\n* `conanfile.py`\n* `include/kth/py-native/binary.h`\n* `include/kth/py-native/capsule_names.h`\n* `include/kth/py-native/chain/compact_block.h`\n* `include/kth/py-native/chain/double_spend_proof.h`\n* `include/kth/py-native/chain/double_spend_proof_spender.h`\n* `include/kth/py-native/chain/get_blocks.h`\n* `include/kth/py-native/chain/get_headers.h`\n* `include/kth/py-native/chain/history_compact.h`\n* `include/kth/py-native/chain/history_compact_list.h`\n* `include/kth/py-native/chain/merkle_block.h`\n* `include/kth/py-native/chain/operation.h`\n* `include/kth/py-native/chain/operation_list.h`\n* `include/kth/py-native/chain/prefilled_transaction.h`\n* `include/kth/py-native/chain/prefilled_transaction_list.h`\n* `include/kth/py-native/chain/script.h`\n* `include/kth/py-native/chain/stealth_compact.h`\n* `include/kth/py-native/chain/stealth_compact_list.h`\n* `include/kth/py-native/chain/token_data.h`\n* `include/kth/py-native/chain/transaction.h`\n* `include/kth/py-native/chain/utxo.h`\n* `include/kth/py-native/chain/utxo_list.h`\n* `include/kth/py-native/wallet/ec_private.h`\n* `include/kth/py-native/wallet/ec_public.h`\n* `include/kth/py-native/wallet/hd_private.h`\n* `include/kth/py-native/wallet/hd_public.h`\n* `include/kth/py-native/wallet/payment_address.h`\n* `include/kth/py-native/wallet/payment_address_list.h`\n* `include/kth/py-native/wallet/wallet_data.h`\n* `kth_native.pyi`\n* `scripts/build-nix.sh`\n* `setup.py`\n* `src/binary.cpp`\n* `src/chain/block.cpp`\n* `src/chain/block_list.cpp`\n* `src/chain/chain.cpp`\n* `src/chain/compact_block.cpp`\n* `src/chain/double_spend_proof.cpp`\n* `src/chain/double_spend_proof_spender.cpp`\n* `src/chain/get_blocks.cpp`\n* `src/chain/get_headers.cpp`\n* `src/chain/header.cpp`\n* `src/chain/history.cpp`\n* `src/chain/history_compact.cpp`\n* `src/chain/history_compact_list.cpp`\n* `src/chain/input.cpp`\n* `src/chain/input_list.cpp`\n* `src/chain/merkle_block.cpp`\n* `src/chain/operation.cpp`\n* `src/chain/operation_list.cpp`\n* `src/chain/output.cpp`\n* `src/chain/output_list.cpp`\n* `src/chain/output_point.cpp`\n* `src/chain/output_point_list.cpp`\n* `src/chain/point.cpp`\n* `src/chain/point_list.cpp`\n* `src/chain/prefilled_transaction.cpp`\n* `src/chain/prefilled_transaction_list.cpp`\n* `src/chain/script.cpp`\n* `src/chain/stealth_compact.cpp`\n* `src/chain/stealth_compact_list.cpp`\n* `src/chain/token_data.cpp`\n* `src/chain/transaction.cpp`\n* `src/chain/transaction_list.cpp`\n* `src/chain/utxo.cpp`\n* `src/chain/utxo_list.cpp`\n* `src/module.c`\n* `src/wallet/ec_private.cpp`\n* `src/wallet/ec_public.cpp`\n* `src/wallet/hd_private.cpp`\n* `src/wallet/hd_public.cpp`\n* `src/wallet/payment_address.cpp`\n* `src/wallet/payment_address_list.cpp`\n* `src/wallet/wallet_data.cpp`\n\n</details>\n\n</details>\n\n<!-- This is an auto-generated comment by CodeRabbit for review status -->","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","state":"commented","html_url":"https://github.com/k-nuth/py-native/pull/6#pullrequestreview-4146628261","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"html":{"href":"https://github.com/k-nuth/py-native/pull/6#pullrequestreview-4146628261"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"submitted_at":"2026-04-21T09:35:53Z","updated_at":"2026-04-21T09:35:53Z"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}},"action":"created"},"public":true,"created_at":"2026-04-21T09:35:58Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490806","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461902","pull_request_review_id":4146628261,"id":3116461902,"node_id":"PRRC_kwDODiJdjc65wW9O","diff_hunk":"@@ -1,85 +1,464 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n #include <kth/py-native/wallet/payment_address.h>\n \n+#include <string.h>\n #include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n #include <kth/py-native/utils.h>\n \n #ifdef __cplusplus\n extern \"C\" {\n #endif\n \n-PyObject* kth_py_native_wallet_payment_address_destruct(PyObject* self, PyObject* args){\n-    PyObject* py_payment_address;\n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_payment_address)) {\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_wallet_payment_address_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS)) return;\n+    kth_payment_address_mut_t handle = (kth_payment_address_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (handle != NULL) kth_wallet_payment_address_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_wallet_payment_address_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n-    kth_payment_address_t payment_address = (kth_payment_address_t)get_ptr(py_payment_address);\n-    kth_wallet_payment_address_destruct(payment_address);\n-    Py_RETURN_NONE;\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_decoded(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"decoded\", NULL};\n+    char const* decoded_buf = NULL;\n+    Py_ssize_t decoded_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &decoded_buf, &decoded_size)) {\n+        return NULL;\n+    }\n+    if (decoded_size != (Py_ssize_t)25) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte decoded, got %zd\", (int)25, decoded_size);\n+        return NULL;\n+    }\n+    kth_payment_t decoded;\n+    memcpy(decoded.hash, decoded_buf, (size_t)25);\n+    auto const result = kth_wallet_payment_address_construct_from_decoded(decoded);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n }\n \n-PyObject* kth_py_native_wallet_payment_address_encoded_legacy(PyObject* self, PyObject* args){\n-    PyObject* py_payment_address;\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_ec_private(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_secret = py_arg0;\n+    kth_ec_private_const_t secret_handle = (kth_ec_private_const_t)PyCapsule_GetPointer(py_secret, KTH_PY_CAPSULE_WALLET_EC_PRIVATE);\n+    if (secret_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_construct_from_ec_private(secret_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_payment_address)) {\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_address(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"address\", NULL};\n+    char const* address = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"s\", kwlist, &address)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_payment_address_construct_from_address(address);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    kth_payment_address_t payment_address = (kth_payment_address_t)get_ptr(py_payment_address);\n-    char* res = kth_wallet_payment_address_encoded_legacy(payment_address);\n-    return Py_BuildValue(\"s\", res);\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_address_net(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"address\", (char*)\"net\", NULL};\n+    char const* address = NULL;\n+    unsigned long long net = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"sK\", kwlist, &address, &net)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_payment_address_construct_from_address_net(address, (kth_network_t)net);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n }\n \n-#if defined(KTH_CURRENCY_BCH)\n-PyObject* kth_py_native_wallet_payment_address_encoded_cashaddr(PyObject* self, PyObject* args){\n-    PyObject* py_payment_address;\n-    int py_token_aware;\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_short_hash_version(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"short_hash\", (char*)\"version\", NULL};\n+    char const* short_hash_buf = NULL;\n+    Py_ssize_t short_hash_size = 0;\n+    unsigned char version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#B\", kwlist, &short_hash_buf, &short_hash_size, &version)) {\n+        return NULL;\n+    }\n+    if (short_hash_size != (Py_ssize_t)KTH_BITCOIN_SHORT_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte short_hash, got %zd\", (int)KTH_BITCOIN_SHORT_HASH_SIZE, short_hash_size);\n+        return NULL;\n+    }\n+    kth_shorthash_t short_hash;\n+    memcpy(short_hash.hash, short_hash_buf, (size_t)KTH_BITCOIN_SHORT_HASH_SIZE);\n+    auto const result = kth_wallet_payment_address_construct_from_short_hash_version(short_hash, (uint8_t)version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    if ( ! PyArg_ParseTuple(args, \"Op\", &py_payment_address, &py_token_aware)) {\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_hash_version(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"hash\", (char*)\"version\", NULL};\n+    char const* hash_buf = NULL;\n+    Py_ssize_t hash_size = 0;\n+    unsigned char version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#B\", kwlist, &hash_buf, &hash_size, &version)) {\n+        return NULL;\n+    }\n+    if (hash_size != (Py_ssize_t)KTH_BITCOIN_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte hash, got %zd\", (int)KTH_BITCOIN_HASH_SIZE, hash_size);\n+        return NULL;\n+    }\n+    kth_hash_t hash;\n+    memcpy(hash.hash, hash_buf, (size_t)KTH_BITCOIN_HASH_SIZE);\n+    auto const result = kth_wallet_payment_address_construct_from_hash_version(hash, (uint8_t)version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    kth_payment_address_t payment_address = (kth_payment_address_t)get_ptr(py_payment_address);\n-    char* res = kth_wallet_payment_address_encoded_cashaddr(payment_address, (kth_bool_t)py_token_aware);\n-    return Py_BuildValue(\"s\", res);\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_ec_public_version(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"point\", (char*)\"version\", NULL};\n+    PyObject* py_point = NULL;\n+    unsigned char version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OB\", kwlist, &py_point, &version)) {\n+        return NULL;\n+    }\n+    kth_ec_public_const_t point_handle = (kth_ec_public_const_t)PyCapsule_GetPointer(py_point, KTH_PY_CAPSULE_WALLET_EC_PUBLIC);\n+    if (point_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_construct_from_ec_public_version(point_handle, (uint8_t)version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n }\n \n-PyObject* kth_py_native_wallet_payment_address_encoded_token(PyObject* self, PyObject* args){\n-    PyObject* py_payment_address;\n+PyObject*\n+kth_py_native_wallet_payment_address_construct_from_script_version(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"script\", (char*)\"version\", NULL};\n+    PyObject* py_script = NULL;\n+    unsigned char version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OB\", kwlist, &py_script, &version)) {\n+        return NULL;\n+    }\n+    kth_script_const_t script_handle = (kth_script_const_t)PyCapsule_GetPointer(py_script, KTH_PY_CAPSULE_CHAIN_SCRIPT);\n+    if (script_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_construct_from_script_version(script_handle, (uint8_t)version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_payment_address)) {\n+PyObject*\n+kth_py_native_wallet_payment_address_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-    kth_payment_address_t payment_address = (kth_payment_address_t)get_ptr(py_payment_address);\n-    char* res = kth_wallet_payment_address_encoded_token(payment_address);\n-    return Py_BuildValue(\"s\", res);\n+PyObject*\n+kth_py_native_wallet_payment_address_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_mut_t self_handle = (kth_payment_address_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    kth_wallet_payment_address_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n }\n-#endif // KTH_CURRENCY_BCH\n \n+PyObject*\n+kth_py_native_wallet_payment_address_cashaddr_prefix_for(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"net\", NULL};\n+    unsigned long long net = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &net)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_payment_address_cashaddr_prefix_for((kth_network_t)net);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_from_pay_public_key_hash_script(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"script\", (char*)\"version\", NULL};\n+    PyObject* py_script = NULL;\n+    unsigned char version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OB\", kwlist, &py_script, &version)) {\n+        return NULL;\n+    }\n+    kth_script_const_t script_handle = (kth_script_const_t)PyCapsule_GetPointer(py_script, KTH_PY_CAPSULE_CHAIN_SCRIPT);\n+    if (script_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_from_pay_public_key_hash_script(script_handle, (uint8_t)version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, kth_py_native_wallet_payment_address_capsule_dtor);\n+}\n \n-PyObject* kth_py_native_wallet_payment_address_version(PyObject* self, PyObject* args){\n-    PyObject* py_payment_address;\n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_payment_address)) {\n+PyObject*\n+kth_py_native_wallet_payment_address_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n         return NULL;\n     }\n-    kth_payment_address_t payment_address = (kth_payment_address_t)get_ptr(py_payment_address);\n-    uint8_t res = kth_wallet_payment_address_version(payment_address);\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    kth_payment_address_const_t other_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n \n-    return Py_BuildValue(\"i\", res);\n+PyObject*\n+kth_py_native_wallet_payment_address_less(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    kth_payment_address_const_t x_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (x_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_less(self_handle, x_handle);\n+    return PyBool_FromLong((long)result);\n }\n \n-PyObject* kth_py_native_wallet_payment_address_construct_from_string(PyObject* self, PyObject* args){\n-    char* py_string;\n-    Py_ssize_t py_size;\n+PyObject*\n+kth_py_native_wallet_payment_address_valid(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_valid(self_handle);\n+    return PyBool_FromLong((long)result);\n+}\n \n-    if ( ! PyArg_ParseTuple(args, \"s#\", &py_string, &py_size)) {\n+PyObject*\n+kth_py_native_wallet_payment_address_encoded_legacy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_encoded_legacy(self_handle);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_encoded_cashaddr(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"token_aware\", NULL};\n+    PyObject* py_self = NULL;\n+    int token_aware = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"Op\", kwlist, &py_self, &token_aware)) {\n         return NULL;\n     }\n-    kth_payment_address_t res = kth_wallet_payment_address_construct_from_string(py_string);\n-    return to_py_obj(res);\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_encoded_cashaddr(self_handle, (kth_bool_t)token_aware);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_encoded_token(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_encoded_token(self_handle);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n }\n \n+PyObject*\n+kth_py_native_wallet_payment_address_version(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_version(self_handle);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_hash_span(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    kth_size_t out_size = 0;\n+    auto const result = kth_wallet_payment_address_hash_span(self_handle, &out_size);\n+    if (result == NULL) {\n+        Py_RETURN_NONE;\n+    }\n+    return Py_BuildValue(\"y#\", result, (Py_ssize_t)out_size);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_hash20(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_hash20(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_SHORT_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_hash32(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_hash32(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_to_payment(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_payment_address_const_t self_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_to_payment(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_PAYMENT_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_extract(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"script\", (char*)\"p2kh_version\", (char*)\"p2sh_version\", NULL};\n+    PyObject* py_script = NULL;\n+    unsigned char p2kh_version = 0;\n+    unsigned char p2sh_version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OBB\", kwlist, &py_script, &p2kh_version, &p2sh_version)) {\n+        return NULL;\n+    }\n+    kth_script_const_t script_handle = (kth_script_const_t)PyCapsule_GetPointer(py_script, KTH_PY_CAPSULE_CHAIN_SCRIPT);\n+    if (script_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_extract(script_handle, (uint8_t)p2kh_version, (uint8_t)p2sh_version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL list returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_extract_input(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"script\", (char*)\"p2kh_version\", (char*)\"p2sh_version\", NULL};\n+    PyObject* py_script = NULL;\n+    unsigned char p2kh_version = 0;\n+    unsigned char p2sh_version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OBB\", kwlist, &py_script, &p2kh_version, &p2sh_version)) {\n+        return NULL;\n+    }\n+    kth_script_const_t script_handle = (kth_script_const_t)PyCapsule_GetPointer(py_script, KTH_PY_CAPSULE_CHAIN_SCRIPT);\n+    if (script_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_extract_input(script_handle, (uint8_t)p2kh_version, (uint8_t)p2sh_version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL list returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_payment_address_extract_output(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"script\", (char*)\"p2kh_version\", (char*)\"p2sh_version\", NULL};\n+    PyObject* py_script = NULL;\n+    unsigned char p2kh_version = 0;\n+    unsigned char p2sh_version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OBB\", kwlist, &py_script, &p2kh_version, &p2sh_version)) {\n+        return NULL;\n+    }\n+    kth_script_const_t script_handle = (kth_script_const_t)PyCapsule_GetPointer(py_script, KTH_PY_CAPSULE_CHAIN_SCRIPT);\n+    if (script_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_extract_output(script_handle, (uint8_t)p2kh_version, (uint8_t)p2sh_version);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL list returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST, NULL);\n+}","path":"src/wallet/payment_address.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Confirm the underlying C API ownership of these extract functions, and that a\n# payment_address_list destructor exists to wire up here.\nrg -nP -C3 'kth_wallet_payment_address_extract(_input|_output)?\\b'\nrg -nP -C2 'kth_wallet_payment_address_list_destruct|KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 8468\n\n---\n\n\n\n</details>\n\n**Memory leak: `extract*` factories and related functions omit the destructor for list capsules.**\n\nThe three `extract`, `extract_input`, and `extract_output` functions in `payment_address.cpp` (lines 390, 409, 428) create new `kth_payment_address_list` objects but wrap them with `PyCapsule_New(..., KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST, NULL)`. The null destructor means the underlying list is never freed on garbage collection.\n\nA proper destructor—`kth_py_native_wallet_payment_address_list_capsule_dtor`—already exists and is correctly used elsewhere (e.g., in `payment_address_list.cpp`). These three functions should register it instead of NULL.\n\nThe same bug also appears in:\n- `src/chain/output.cpp:181`\n- `src/chain/input.cpp:137`\n\nUse `kth_py_native_wallet_payment_address_list_capsule_dtor` as the destructor for all `PyCapsule_New` calls that wrap a newly created list object.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/payment_address.cpp` around lines 374 - 429, The three functions\nkth_py_native_wallet_payment_address_extract,\nkth_py_native_wallet_payment_address_extract_input, and\nkth_py_native_wallet_payment_address_extract_output create\nkth_payment_address_list objects but call PyCapsule_New with a NULL destructor\ncausing a leak; update their PyCapsule_New calls to pass\nkth_py_native_wallet_payment_address_list_capsule_dtor as the destructor (same\nchange also needed for the similar PyCapsule_New calls in\nkth_py_native_chain_output and kth_py_native_chain_input in output.cpp and\ninput.cpp) so the list is freed on capsule GC.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:267d471b-dbc3-464a-af77-a02060aace43 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461902","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461902"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461902"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461902/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":468,"position":468,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490720","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461890","pull_request_review_id":4146628261,"id":3116461890,"node_id":"PRRC_kwDODiJdjc65wW9C","diff_hunk":"@@ -0,0 +1,116 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/wallet/payment_address_list.h>\n+\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor for this list type — GC-driven release.\n+void kth_py_native_wallet_payment_address_list_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST)) return;\n+    kth_payment_address_list_mut_t handle = (kth_payment_address_list_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (handle != NULL) kth_wallet_payment_address_list_destruct(handle);\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_wallet_payment_address_list_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST, kth_py_native_wallet_payment_address_list_capsule_dtor);\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_push_back(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"elem\", NULL};\n+    PyObject* py_list = NULL;\n+    PyObject* py_elem = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_list, &py_elem)) {\n+        return NULL;\n+    }\n+    kth_payment_address_list_mut_t list_handle = (kth_payment_address_list_mut_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_payment_address_const_t elem_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_elem, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (elem_handle == NULL) return NULL;\n+    kth_wallet_payment_address_list_push_back(list_handle, elem_handle);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_destruct(PyObject* self, PyObject* py_arg0) {\n+    kth_payment_address_list_mut_t list_handle = (kth_payment_address_list_mut_t)PyCapsule_GetPointer(py_arg0, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_wallet_payment_address_list_destruct(list_handle);\n+    PyCapsule_SetName(py_arg0, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_count(PyObject* self, PyObject* py_arg0) {\n+    kth_payment_address_list_const_t list_handle = (kth_payment_address_list_const_t)PyCapsule_GetPointer(py_arg0, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_list_count(list_handle);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_nth(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"index\", NULL};\n+    PyObject* py_list = NULL;\n+    unsigned long long index = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OK\", kwlist, &py_list, &index)) {\n+        return NULL;\n+    }\n+    kth_payment_address_list_const_t list_handle = (kth_payment_address_list_const_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    auto const result = kth_wallet_payment_address_list_nth(list_handle, (kth_size_t)index);\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS, NULL);\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_assign_at(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"index\", (char*)\"elem\", NULL};\n+    PyObject* py_list = NULL;\n+    unsigned long long index = 0;\n+    PyObject* py_elem = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OKO\", kwlist, &py_list, &index, &py_elem)) {\n+        return NULL;\n+    }\n+    kth_payment_address_list_mut_t list_handle = (kth_payment_address_list_mut_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_payment_address_const_t elem_handle = (kth_payment_address_const_t)PyCapsule_GetPointer(py_elem, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS);\n+    if (elem_handle == NULL) return NULL;\n+    kth_wallet_payment_address_list_assign_at(list_handle, (kth_size_t)index, elem_handle);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject* kth_py_native_wallet_payment_address_list_erase(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"index\", NULL};\n+    PyObject* py_list = NULL;\n+    unsigned long long index = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OK\", kwlist, &py_list, &index)) {\n+        return NULL;\n+    }\n+    kth_payment_address_list_mut_t list_handle = (kth_payment_address_list_mut_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_wallet_payment_address_list_erase(list_handle, (kth_size_t)index);\n+    Py_RETURN_NONE;\n+}\n+\n+PyMethodDef kth_py_native_wallet_payment_address_list_methods[] = {\n+    {\"chain_payment_address_list_construct_default\", (PyCFunction)kth_py_native_wallet_payment_address_list_construct_default, METH_NOARGS, NULL},\n+    {\"chain_payment_address_list_push_back\", (PyCFunction)kth_py_native_wallet_payment_address_list_push_back, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"chain_payment_address_list_destruct\", (PyCFunction)kth_py_native_wallet_payment_address_list_destruct, METH_O, NULL},\n+    {\"chain_payment_address_list_count\", (PyCFunction)kth_py_native_wallet_payment_address_list_count, METH_O, NULL},\n+    {\"chain_payment_address_list_nth\", (PyCFunction)kth_py_native_wallet_payment_address_list_nth, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"chain_payment_address_list_assign_at\", (PyCFunction)kth_py_native_wallet_payment_address_list_assign_at, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"chain_payment_address_list_erase\", (PyCFunction)kth_py_native_wallet_payment_address_list_erase, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {NULL, NULL, 0, NULL}  // sentinel\n+};","path":"src/wallet/payment_address_list.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🔴 Critical_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Confirm how consumers reference these list symbols.\nrg -n --type=py -C2 'payment_address_list'\nrg -n -C2 'chain_payment_address_list|wallet_payment_address_list' -g '!**/build/**'\n# Cross-check that other wallet_* list bindings use the wallet_ prefix in their PyMethodDef ml_name.\nrg -nP -C1 '\"(wallet|chain)_[a-z_]+_list_(construct_default|push_back|count|nth|assign_at|erase|destruct)\"' src/\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 27575\n\n---\n\n\n\n</details>\n\n**Method names use `chain_` prefix for a wallet type — breaks module API contract.**\n\nLines 104-110 expose functions with `chain_payment_address_list_*` prefix, but this is the wallet module (file location `src/wallet/`, capsule `KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST`, C functions `kth_wallet_payment_address_list_*`). The generated `.pyi` file (kth_native.pyi:1106-1112) also uses the same incorrect prefix. Compare all chain lists (e.g., `src/chain/operation_list.cpp`) which correctly use `chain_*` prefix — this is a code-generator template bug that affected both the C bindings and type stubs.\n\n<details>\n<summary>🐛 Proposed fix</summary>\n\nRename all seven method names from `chain_payment_address_list_*` to `wallet_payment_address_list_*` and update kth_native.pyi accordingly. This generator bug must be corrected in the template (likely `generate_capi_v2.py`) to prevent recurrence.\n\n```diff\n PyMethodDef kth_py_native_wallet_payment_address_list_methods[] = {\n-    {\"chain_payment_address_list_construct_default\", (PyCFunction)kth_py_native_wallet_payment_address_list_construct_default, METH_NOARGS, NULL},\n-    {\"chain_payment_address_list_push_back\", (PyCFunction)kth_py_native_wallet_payment_address_list_push_back, METH_VARARGS | METH_KEYWORDS, NULL},\n-    {\"chain_payment_address_list_destruct\", (PyCFunction)kth_py_native_wallet_payment_address_list_destruct, METH_O, NULL},\n-    {\"chain_payment_address_list_count\", (PyCFunction)kth_py_native_wallet_payment_address_list_count, METH_O, NULL},\n-    {\"chain_payment_address_list_nth\", (PyCFunction)kth_py_native_wallet_payment_address_list_nth, METH_VARARGS | METH_KEYWORDS, NULL},\n-    {\"chain_payment_address_list_assign_at\", (PyCFunction)kth_py_native_wallet_payment_address_list_assign_at, METH_VARARGS | METH_KEYWORDS, NULL},\n-    {\"chain_payment_address_list_erase\", (PyCFunction)kth_py_native_wallet_payment_address_list_erase, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"wallet_payment_address_list_construct_default\", (PyCFunction)kth_py_native_wallet_payment_address_list_construct_default, METH_NOARGS, NULL},\n+    {\"wallet_payment_address_list_push_back\", (PyCFunction)kth_py_native_wallet_payment_address_list_push_back, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"wallet_payment_address_list_destruct\", (PyCFunction)kth_py_native_wallet_payment_address_list_destruct, METH_O, NULL},\n+    {\"wallet_payment_address_list_count\", (PyCFunction)kth_py_native_wallet_payment_address_list_count, METH_O, NULL},\n+    {\"wallet_payment_address_list_nth\", (PyCFunction)kth_py_native_wallet_payment_address_list_nth, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"wallet_payment_address_list_assign_at\", (PyCFunction)kth_py_native_wallet_payment_address_list_assign_at, METH_VARARGS | METH_KEYWORDS, NULL},\n+    {\"wallet_payment_address_list_erase\", (PyCFunction)kth_py_native_wallet_payment_address_list_erase, METH_VARARGS | METH_KEYWORDS, NULL},\n     {NULL, NULL, 0, NULL}  // sentinel\n };\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/payment_address_list.cpp` around lines 103 - 112, The exposed\nPython C API method names in kth_py_native_wallet_payment_address_list_methods\nuse the wrong \"chain_payment_address_list_*\" prefix; update all seven entries to\nuse the \"wallet_payment_address_list_*\" prefix to match the wallet capsule\n(KTH_PY_CAPSULE_WALLET_PAYMENT_ADDRESS_LIST) and the C functions\n(kth_wallet_payment_address_list_*), then update the generated type stub\nkth_native.pyi to reflect the renamed methods; finally fix the generator\ntemplate (likely generate_capi_v2.py) so it emits \"wallet_\" for wallet module\nlists to prevent this regression.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:b8f3e64b-8740-4ffc-b5a4-055ba3e206c1 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461890","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461890"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461890"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461890/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":112,"position":112,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490665","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461880","pull_request_review_id":4146628261,"id":3116461880,"node_id":"PRRC_kwDODiJdjc65wW84","diff_hunk":"@@ -0,0 +1,277 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/wallet/hd_public.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_wallet_hd_public_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_WALLET_HD_PUBLIC)) return;\n+    kth_hd_public_mut_t handle = (kth_hd_public_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (handle != NULL) kth_wallet_hd_public_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_wallet_hd_public_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_construct_from_public_key(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"public_key\", NULL};\n+    char const* public_key_buf = NULL;\n+    Py_ssize_t public_key_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &public_key_buf, &public_key_size)) {\n+        return NULL;\n+    }\n+    if (public_key_size != (Py_ssize_t)82) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte public_key, got %zd\", (int)82, public_key_size);\n+        return NULL;\n+    }\n+    kth_hd_key_t public_key;\n+    memcpy(public_key.data, public_key_buf, (size_t)82);\n+    auto const result = kth_wallet_hd_public_construct_from_public_key(public_key);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_construct_from_public_key_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"public_key\", (char*)\"prefix\", NULL};\n+    char const* public_key_buf = NULL;\n+    Py_ssize_t public_key_size = 0;\n+    unsigned int prefix = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#I\", kwlist, &public_key_buf, &public_key_size, &prefix)) {\n+        return NULL;\n+    }\n+    if (public_key_size != (Py_ssize_t)82) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte public_key, got %zd\", (int)82, public_key_size);\n+        return NULL;\n+    }\n+    kth_hd_key_t public_key;\n+    memcpy(public_key.data, public_key_buf, (size_t)82);\n+    auto const result = kth_wallet_hd_public_construct_from_public_key_prefix(public_key, (uint32_t)prefix);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_construct_from_encoded(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"encoded\", NULL};\n+    char const* encoded = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"s\", kwlist, &encoded)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_public_construct_from_encoded(encoded);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_construct_from_encoded_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"encoded\", (char*)\"prefix\", NULL};\n+    char const* encoded = NULL;\n+    unsigned int prefix = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"sI\", kwlist, &encoded, &prefix)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_public_construct_from_encoded_prefix(encoded, (uint32_t)prefix);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_mut_t self_handle = (kth_hd_public_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    kth_wallet_hd_public_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_to_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"prefixes\", NULL};\n+    unsigned long long prefixes = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &prefixes)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_public_to_prefix((uint64_t)prefixes);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_less(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    kth_hd_public_const_t x_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (x_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_less(self_handle, x_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n+        return NULL;\n+    }\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    kth_hd_public_const_t other_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_valid(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_valid(self_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_encoded(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_encoded(self_handle);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_chain_code(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_chain_code(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_public_lineage(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_public_const_t self_handle = (kth_hd_public_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PUBLIC);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_public_lineage(self_handle);\n+    return Py_BuildValue(\"y#\", &result, (Py_ssize_t)sizeof(kth_hd_lineage_t));\n+}","path":"src/wallet/hd_public.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Inspect kth_hd_lineage_t definition and whether the C-API offers a canonical accessor.\nfd -t f -e h -e hpp | xargs rg -nP -C5 '\\bkth_hd_lineage_t\\b|\\bkth_wallet_hd_public_lineage\\b' 2>/dev/null | head -120\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n# Explore repository structure\ngit ls-files | head -30\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1059\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search for the file being reviewed\nfind . -name \"hd_public.cpp\" -o -name \"hd_public.hpp\" 2>/dev/null\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 86\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search more broadly for kth_hd_lineage_t and related functions\nrg -i \"hd_lineage|hd_public_lineage\" --type cpp --type h -l 2>/dev/null | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 151\n\n---\n\n🏁 Script executed:\n\n```shell\n# Check the actual file around the function in question\ncat -n src/wallet/hd_public.cpp | sed -n '200,230p'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1840\n\n---\n\n🏁 Script executed:\n\n```shell\n# Check the header file for wallet hd_public\ncat -n include/kth/py-native/wallet/hd_public.h\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2456\n\n---\n\n🏁 Script executed:\n\n```shell\n# Look for to_hd_key implementation\ncat -n src/wallet/hd_public.cpp | sed -n '227,250p'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1392\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search for where kth_hd_lineage_t might be defined/imported\nrg -i \"kth_hd_lineage_t|typedef.*lineage\" --type cpp --type h 2>/dev/null\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 268\n\n---\n\n🏁 Script executed:\n\n```shell\n# Look for external C-API dependencies or includes at the top of hd_public.cpp\nhead -50 src/wallet/hd_public.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2170\n\n---\n\n🏁 Script executed:\n\n```shell\n# Check dependencies in conanfile.py\ncat conanfile.py | head -80\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2966\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search for any accessor patterns for lineage fields\nrg -i \"depth|parent_fingerprint|child_number\" --type cpp --type h 2>/dev/null | head -30\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search for any comments or documentation about lineage structure\nrg -i \"lineage|struct.*layout|abi|platform\" src/wallet/hd_public.cpp -A2 -B2\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1011\n\n---\n\n🏁 Script executed:\n\n```shell\n# Check if there's a private version (hd_private) with same pattern\ncat -n src/wallet/hd_private.cpp | sed -n '210,225p'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 969\n\n---\n\n🏁 Script executed:\n\n```shell\n# Find the lineage function in hd_private.cpp\ncat -n src/wallet/hd_private.cpp | grep -A10 \"hd_private_lineage\"\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1379\n\n---\n\n🏁 Script executed:\n\n```shell\n# Look for any wrapper or helper functions that might work with lineage fields\nrg \"kth_wallet_hd.*lineage|kth_hd_lineage\" src/ include/ 2>/dev/null\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 454\n\n---\n\n🏁 Script executed:\n\n```shell\n# Search the kth dependency information for clues about lineage structure\nfind kth 2>/dev/null | grep -i \"lineage\\|hd_\" | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n\n\n</details>\n\n**Return `lineage` as a structured tuple instead of raw struct bytes.**\n\nThe current implementation exposes the raw in-memory layout of `kth_hd_lineage_t` directly to Python via `Py_BuildValue(\"y#\", &result, sizeof(kth_hd_lineage_t))`. This differs from analogous functions (`chain_code`, `point`, `to_hd_key`), which return fixed-width byte sequences from individual struct fields using documented constants—not `sizeof()`.\n\nRaw struct serialization is fragile across platforms due to:\n- Implementation-defined padding and alignment between fields\n- Endianness effects on multi-byte integers (if depth, parent fingerprint, or child number are present as such)\n- Silent ABI drift when the C-API adds or reorders fields\n\nEither extract and return lineage fields as a structured Python representation (e.g., a tuple of `(depth, parent_fingerprint, child_number)` via explicit field access), or serialize through a documented canonical form provided by the C-API. The same issue exists in `hd_private_lineage`.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/hd_public.cpp` around lines 210 - 216,\nkth_py_native_wallet_hd_public_lineage currently returns the raw\nkth_hd_lineage_t bytes; change it to extract the struct fields from the\nkth_wallet_hd_public_lineage result and build a Python tuple (depth,\nparent_fingerprint, child_number) instead of using\nPy_BuildValue(\"y#\",&result,...). Specifically, call\nkth_wallet_hd_public_lineage(self_handle) to get the kth_hd_lineage_t value,\nread its fields explicitly (e.g., depth as an integer, parent_fingerprint as a\nfixed-width byte sequence, child_number as an integer or fixed-width sequence\nconsistent with chain_code/point/to_hd_key conventions), and return a PyTuple\ncontaining those converted Python types; apply the same pattern for the\nanalogous hd_private_lineage binding to avoid exposing raw struct layout and\nplatform-dependent padding/endianness.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:4af7457e-39a5-4a9c-ae21-427ab81b9de1 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461880","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461880"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461880"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461880/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":216,"position":216,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490575","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461871","pull_request_review_id":4146628261,"id":3116461871,"node_id":"PRRC_kwDODiJdjc65wW8v","diff_hunk":"@@ -0,0 +1,379 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/wallet/hd_private.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_wallet_hd_private_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_WALLET_HD_PRIVATE)) return;\n+    kth_hd_private_mut_t handle = (kth_hd_private_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (handle != NULL) kth_wallet_hd_private_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_wallet_hd_private_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_seed_prefixes(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"seed\", (char*)\"prefixes\", NULL};\n+    char const* seed_buf = NULL;\n+    Py_ssize_t seed_size = 0;\n+    unsigned long long prefixes = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#K\", kwlist, &seed_buf, &seed_size, &prefixes)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_private_construct_from_seed_prefixes((uint8_t const*)seed_buf, (kth_size_t)seed_size, (uint64_t)prefixes);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_private_key(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"private_key\", NULL};\n+    char const* private_key_buf = NULL;\n+    Py_ssize_t private_key_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &private_key_buf, &private_key_size)) {\n+        return NULL;\n+    }\n+    if (private_key_size != (Py_ssize_t)82) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte private_key, got %zd\", (int)82, private_key_size);\n+        return NULL;\n+    }\n+    kth_hd_key_t private_key;\n+    memcpy(private_key.data, private_key_buf, (size_t)82);\n+    auto const result = kth_wallet_hd_private_construct_from_private_key(private_key);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_private_key_prefixes(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"private_key\", (char*)\"prefixes\", NULL};\n+    char const* private_key_buf = NULL;\n+    Py_ssize_t private_key_size = 0;\n+    unsigned long long prefixes = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#K\", kwlist, &private_key_buf, &private_key_size, &prefixes)) {\n+        return NULL;\n+    }\n+    if (private_key_size != (Py_ssize_t)82) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte private_key, got %zd\", (int)82, private_key_size);\n+        return NULL;\n+    }\n+    kth_hd_key_t private_key;\n+    memcpy(private_key.data, private_key_buf, (size_t)82);\n+    auto const result = kth_wallet_hd_private_construct_from_private_key_prefixes(private_key, (uint64_t)prefixes);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_private_key_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"private_key\", (char*)\"prefix\", NULL};\n+    char const* private_key_buf = NULL;\n+    Py_ssize_t private_key_size = 0;\n+    unsigned int prefix = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#I\", kwlist, &private_key_buf, &private_key_size, &prefix)) {\n+        return NULL;\n+    }\n+    if (private_key_size != (Py_ssize_t)82) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte private_key, got %zd\", (int)82, private_key_size);\n+        return NULL;\n+    }\n+    kth_hd_key_t private_key;\n+    memcpy(private_key.data, private_key_buf, (size_t)82);\n+    auto const result = kth_wallet_hd_private_construct_from_private_key_prefix(private_key, (uint32_t)prefix);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_encoded(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"encoded\", NULL};\n+    char const* encoded = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"s\", kwlist, &encoded)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_private_construct_from_encoded(encoded);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_encoded_prefixes(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"encoded\", (char*)\"prefixes\", NULL};\n+    char const* encoded = NULL;\n+    unsigned long long prefixes = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"sK\", kwlist, &encoded, &prefixes)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_private_construct_from_encoded_prefixes(encoded, (uint64_t)prefixes);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_construct_from_encoded_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"encoded\", (char*)\"prefix\", NULL};\n+    char const* encoded = NULL;\n+    unsigned int prefix = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"sI\", kwlist, &encoded, &prefix)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_private_construct_from_encoded_prefix(encoded, (uint32_t)prefix);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_mut_t self_handle = (kth_hd_private_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    kth_wallet_hd_private_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_to_prefix(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"prefixes\", NULL};\n+    unsigned long long prefixes = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &prefixes)) {\n+        return NULL;\n+    }\n+    auto const result = kth_wallet_hd_private_to_prefix((uint64_t)prefixes);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_less(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    kth_hd_private_const_t x_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (x_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_less(self_handle, x_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n+        return NULL;\n+    }\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    kth_hd_private_const_t other_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_encoded(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_encoded(self_handle);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_secret(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_secret(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_to_hd_key(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_to_hd_key(self_handle);\n+    return Py_BuildValue(\"y#\", result.data, (Py_ssize_t)KTH_HD_KEY_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_to_public(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_to_public(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_derive_private(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"index\", NULL};\n+    PyObject* py_self = NULL;\n+    unsigned int index = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OI\", kwlist, &py_self, &index)) {\n+        return NULL;\n+    }\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_derive_private(self_handle, (uint32_t)index);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PRIVATE, kth_py_native_wallet_hd_private_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_derive_public(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"index\", NULL};\n+    PyObject* py_self = NULL;\n+    unsigned int index = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OI\", kwlist, &py_self, &index)) {\n+        return NULL;\n+    }\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_derive_public(self_handle, (uint32_t)index);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_WALLET_HD_PUBLIC, kth_py_native_wallet_hd_public_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_valid(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_valid(self_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_chain_code(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_chain_code(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_wallet_hd_private_lineage(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_hd_private_const_t self_handle = (kth_hd_private_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_WALLET_HD_PRIVATE);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_wallet_hd_private_lineage(self_handle);\n+    return Py_BuildValue(\"y#\", &result, (Py_ssize_t)sizeof(kth_hd_lineage_t));\n+}","path":"src/wallet/hd_private.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Confirm kth_hd_lineage_t layout and see whether other bindings ever pack struct bytes the same way.\nrg -nP -C3 'kth_hd_lineage_t'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1135\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Find the kth_hd_lineage_t struct definition\nrg -nP 'typedef.*kth_hd_lineage_t|struct.*kth_hd_lineage' --type=c --type=h\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Look for other Py_BuildValue patterns serializing struct bytes (y# format)\nrg -n 'Py_BuildValue.*\"y#\"' --type=c --type=h\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 187\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Check if there are custom serialization/deserialization functions for lineage\nrg -nP 'lineage|kth_hd_lineage' --type=h | head -50\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 311\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Look for kth_hd_lineage struct in header files and external includes\nfind . -name \"*.h\" -o -name \"*.hpp\" | xargs rg -l 'kth_hd_lineage' 2>/dev/null | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Check what else uses Py_BuildValue and its patterns in the codebase\nrg -n 'Py_BuildValue' src/wallet/ --type=c | head -40\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Look at hd_public.cpp to see how other return values are constructed (for comparison)\nsed -n '190,230p' src/wallet/hd_public.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2063\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Look at hd_private.cpp for similar patterns with other return types\nsed -n '300,360p' src/wallet/hd_private.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 4017\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Search for generator-related comments or patterns\nrg -n 'generator|GENERATOR' src/wallet/hd_private.cpp src/wallet/hd_public.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Check if there's external documentation or comments about struct serialization\nhead -50 src/wallet/hd_private.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2264\n\n---\n\n\n\n</details>\n\n**Serializing raw `kth_hd_lineage_t` struct with `sizeof()` risks exposing padding bytes.**\n\n`Py_BuildValue(\"y#\", &result, (Py_ssize_t)sizeof(kth_hd_lineage_t))` serializes the entire C struct in-memory layout, including implementation-defined padding that may leak indeterminate stack/register contents. This pattern also forces Python callers to parse an ABI-specific binary layout they cannot portably reason about.\n\nThis same pattern appears in both `hd_public.cpp` and `hd_private.cpp`, suggesting intentional design rather than oversight. However, other methods in the same file handle similar cases differently — `chain_code()` and `point()` extract and return specific fields with fixed, known sizes (e.g., `result.hash` with `KTH_BITCOIN_HASH_SIZE`), avoiding the struct serialization problem.\n\nConsider returning a tuple or dict of the individual `kth_hd_lineage_t` fields (depth, parent, child number, prefixes, etc.) instead, following the pattern used elsewhere in the module.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/wallet/hd_private.cpp` around lines 332 - 339, The function\nkth_py_native_wallet_hd_private_lineage currently serializes the whole\nkth_hd_lineage_t via Py_BuildValue(\"y#\", &result, sizeof(...)) which can leak\npadding; instead unpack the kth_hd_lineage_t returned by\nkth_wallet_hd_private_lineage into its individual fields (e.g., depth,\nparent_fingerprint, child_number, version/prefix fields — whatever members exist\non kth_hd_lineage_t) and return a Python tuple or dict built with Py_BuildValue\nor PyDict_New/PyDict_SetItemString (matching the pattern used by chain_code()\nand point()); update kth_py_native_wallet_hd_public_lineage (the analogous\nfunction in hd_public.cpp) the same way and remove the raw struct binary\nserialization.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:267d471b-dbc3-464a-af77-a02060aace43 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461871","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461871"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461871"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461871/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":339,"position":339,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490497","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461862","pull_request_review_id":4146628261,"id":3116461862,"node_id":"PRRC_kwDODiJdjc65wW8m","diff_hunk":"@@ -0,0 +1,345 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/token_data.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_token_data_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA)) return;\n+    kth_token_data_mut_t handle = (kth_token_data_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (handle != NULL) kth_chain_token_data_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_construct_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"reader\", NULL};\n+    char const* reader_buf = NULL;\n+    Py_ssize_t reader_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &reader_buf, &reader_size)) {\n+        return NULL;\n+    }\n+    kth_token_data_mut_t out = NULL;\n+    kth_error_code_t result = kth_chain_token_construct_from_data((uint8_t const*)reader_buf, (kth_size_t)reader_size, &out);\n+    if (result != kth_ec_success) {\n+        PyErr_Format(PyExc_RuntimeError, \"kth error code %d\", (int)result);\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)out, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, kth_py_native_chain_token_data_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_token_data_const_t self_handle = (kth_token_data_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_token_data_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, kth_py_native_chain_token_data_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_token_data_mut_t self_handle = (kth_token_data_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (self_handle == NULL) return NULL;\n+    kth_chain_token_data_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_id(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_token_data_const_t self_handle = (kth_token_data_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_token_data_id(self_handle);\n+    return Py_BuildValue(\"y#\", result.hash, (Py_ssize_t)KTH_BITCOIN_HASH_SIZE);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_set_id(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"value\", NULL};\n+    PyObject* py_self = NULL;\n+    char const* value_buf = NULL;\n+    Py_ssize_t value_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"Oy#\", kwlist, &py_self, &value_buf, &value_size)) {\n+        return NULL;\n+    }\n+    kth_token_data_mut_t self_handle = (kth_token_data_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (self_handle == NULL) return NULL;\n+    if (value_size != (Py_ssize_t)KTH_BITCOIN_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte value, got %zd\", (int)KTH_BITCOIN_HASH_SIZE, value_size);\n+        return NULL;\n+    }\n+    kth_hash_t value;\n+    memcpy(value.hash, value_buf, (size_t)KTH_BITCOIN_HASH_SIZE);\n+    kth_chain_token_data_set_id(self_handle, value);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n+        return NULL;\n+    }\n+    kth_token_data_const_t self_handle = (kth_token_data_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (self_handle == NULL) return NULL;\n+    kth_token_data_const_t other_handle = (kth_token_data_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_chain_token_data_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_make_fungible(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"id\", (char*)\"amount\", NULL};\n+    char const* id_buf = NULL;\n+    Py_ssize_t id_size = 0;\n+    unsigned long long amount = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#K\", kwlist, &id_buf, &id_size, &amount)) {\n+        return NULL;\n+    }\n+    if (id_size != (Py_ssize_t)KTH_BITCOIN_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte id, got %zd\", (int)KTH_BITCOIN_HASH_SIZE, id_size);\n+        return NULL;\n+    }\n+    kth_hash_t id;\n+    memcpy(id.hash, id_buf, (size_t)KTH_BITCOIN_HASH_SIZE);\n+    auto const result = kth_chain_token_make_fungible(id, (uint64_t)amount);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_make_non_fungible(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"id\", (char*)\"capability\", (char*)\"commitment\", NULL};\n+    char const* id_buf = NULL;\n+    Py_ssize_t id_size = 0;\n+    unsigned long long capability = 0;\n+    char const* commitment_buf = NULL;\n+    Py_ssize_t commitment_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#Ky#\", kwlist, &id_buf, &id_size, &capability, &commitment_buf, &commitment_size)) {\n+        return NULL;\n+    }\n+    if (id_size != (Py_ssize_t)KTH_BITCOIN_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte id, got %zd\", (int)KTH_BITCOIN_HASH_SIZE, id_size);\n+        return NULL;\n+    }\n+    kth_hash_t id;\n+    memcpy(id.hash, id_buf, (size_t)KTH_BITCOIN_HASH_SIZE);\n+    auto const result = kth_chain_token_make_non_fungible(id, (kth_token_capability_t)capability, (uint8_t const*)commitment_buf, (kth_size_t)commitment_size);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_chain_token_data_make_both(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"id\", (char*)\"amount\", (char*)\"capability\", (char*)\"commitment\", NULL};\n+    char const* id_buf = NULL;\n+    Py_ssize_t id_size = 0;\n+    unsigned long long amount = 0;\n+    unsigned long long capability = 0;\n+    char const* commitment_buf = NULL;\n+    Py_ssize_t commitment_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#KKy#\", kwlist, &id_buf, &id_size, &amount, &capability, &commitment_buf, &commitment_size)) {\n+        return NULL;\n+    }\n+    if (id_size != (Py_ssize_t)KTH_BITCOIN_HASH_SIZE) {\n+        PyErr_Format(PyExc_ValueError, \"expected %d-byte id, got %zd\", (int)KTH_BITCOIN_HASH_SIZE, id_size);\n+        return NULL;\n+    }\n+    kth_hash_t id;\n+    memcpy(id.hash, id_buf, (size_t)KTH_BITCOIN_HASH_SIZE);\n+    auto const result = kth_chain_token_make_both(id, (uint64_t)amount, (kth_token_capability_t)capability, (uint8_t const*)commitment_buf, (kth_size_t)commitment_size);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+}","path":"src/chain/token_data.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🔴 Critical_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Find all PyCapsule_New call sites that use a NULL destructor so the author can\n# audit whether each is intentional (borrowed ref) or a leaked owning handle.\nrg -nP --type=cpp -C2 'PyCapsule_New\\s*\\([^;]*,\\s*NULL\\s*\\)\\s*;'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 17185\n\n---\n\n🏁 Script executed:\n\n```shell\ncat -n src/chain/token_data.cpp | head -250\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 13108\n\n---\n\n\n\n</details>\n\n**Memory leak: `make_fungible` / `make_non_fungible` / `make_both` register capsules with a `NULL` destructor.**\n\nEach of these three factories returns a newly-allocated `kth_token_data_mut_t` handle (confirmed by the `result == NULL` error check), but the capsule is created with `NULL` as the destructor. When Python GC collects the capsule, `kth_chain_token_data_destruct` is never invoked, leaking the native handle. This is inconsistent with `construct_from_data` (line 40) and `copy` (line 53) in the same file, both of which correctly wire `kth_py_native_chain_token_data_capsule_dtor`.\n\n<details>\n<summary>🐛 Proposed fix</summary>\n\n```diff\n@@ -129,13 +129,13 @@\n         return NULL;\n     }\n-    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, kth_py_native_chain_token_data_capsule_dtor);\n }\n \n PyObject*\n kth_py_native_chain_token_data_make_non_fungible(PyObject* self, PyObject* args, PyObject* kwds) {\n     ...\n-    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, kth_py_native_chain_token_data_capsule_dtor);\n }\n \n PyObject*\n kth_py_native_chain_token_data_make_both(PyObject* self, PyObject* args, PyObject* kwds) {\n     ...\n-    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, NULL);\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_TOKEN_DATA, kth_py_native_chain_token_data_capsule_dtor);\n }\n```\n</details>\n\nThis pattern affects many other generated bindings; a generator-side fix is preferable to hand-patching individual instances.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/token_data.cpp` around lines 112 - 184, The three factory functions\nkth_py_native_chain_token_data_make_fungible,\nkth_py_native_chain_token_data_make_non_fungible, and\nkth_py_native_chain_token_data_make_both create native kth_token_data_mut_t\nhandles but call PyCapsule_New with a NULL destructor; update each PyCapsule_New\ncall to pass the existing destructor function\nkth_py_native_chain_token_data_capsule_dtor so the native handle is freed when\nthe capsule is GC'd (replace the final NULL argument with\nkth_py_native_chain_token_data_capsule_dtor in each of the three functions).\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:267d471b-dbc3-464a-af77-a02060aace43 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461862","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461862"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461862"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461862/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":184,"position":184,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490445","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461858","pull_request_review_id":4146628261,"id":3116461858,"node_id":"PRRC_kwDODiJdjc65wW8i","diff_hunk":"@@ -1,72 +1,116 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n #include <kth/py-native/chain/stealth_compact_list.h>\n+\n #include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n #include <kth/py-native/utils.h>\n \n #ifdef __cplusplus\n extern \"C\" {\n #endif\n \n-\n-/*\n-PyObject* kth_py_native_chain_stealth_compact_list_construct_default(PyObject* self, PyObject* args){\n-    kth_stealth_compact_list_t res = (kth_stealth_compact_list_t)chain_stealth_compact_list_construct_default();\n-    return to_py_obj(res);\n+// PyCapsule destructor for this list type — GC-driven release.\n+void kth_py_native_chain_stealth_compact_list_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST)) return;\n+    kth_stealth_compact_list_mut_t handle = (kth_stealth_compact_list_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n+    if (handle != NULL) kth_chain_stealth_compact_list_destruct(handle);\n }\n \n-\n-PyObject* kth_py_native_chain_stealth_compact_list_push_back(PyObject* self, PyObject* args){\n-    PyObject* py_stealth_compact_list;\n-    PyObject* py_stealth_compact;\n-\n-    if ( ! PyArg_ParseTuple(args, \"OO\", &py_stealth_compact_list, &py_stealth_compact)) {\n+PyObject* kth_py_native_chain_stealth_compact_list_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_chain_stealth_compact_list_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n-\n-    kth_stealth_compact_list_t list = (kth_stealth_compact_list_t)get_ptr(py_stealth_compact_list);\n-    kth_stealth_compact_t stealth_compact = (list)get_ptr(py_stealth_compact);\n-    kth_chain_stealth_compact_list_push_back(kth_stealth_compact_list, stealth_compact);\n-    Py_RETURN_NONE;\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST, kth_py_native_chain_stealth_compact_list_capsule_dtor);\n }\n-*/\n-\n-PyObject* kth_py_native_chain_stealth_compact_list_destruct(PyObject* self, PyObject* args){\n-   PyObject* py_stealth_compact_list;\n \n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_stealth_compact_list)) {\n+PyObject* kth_py_native_chain_stealth_compact_list_push_back(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"elem\", NULL};\n+    PyObject* py_list = NULL;\n+    PyObject* py_elem = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_list, &py_elem)) {\n         return NULL;\n     }\n-\n-    kth_stealth_compact_list_t list = (kth_stealth_compact_list_t)get_ptr(py_stealth_compact_list);\n-    kth_chain_stealth_compact_list_destruct(list);\n+    kth_stealth_compact_list_mut_t list_handle = (kth_stealth_compact_list_mut_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_stealth_compact_const_t elem_handle = (kth_stealth_compact_const_t)PyCapsule_GetPointer(py_elem, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT);\n+    if (elem_handle == NULL) return NULL;\n+    kth_chain_stealth_compact_list_push_back(list_handle, elem_handle);\n     Py_RETURN_NONE;\n }\n \n+PyObject* kth_py_native_chain_stealth_compact_list_destruct(PyObject* self, PyObject* py_arg0) {\n+    kth_stealth_compact_list_mut_t list_handle = (kth_stealth_compact_list_mut_t)PyCapsule_GetPointer(py_arg0, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n+    if (list_handle == NULL) return NULL;\n+    kth_chain_stealth_compact_list_destruct(list_handle);\n+    PyCapsule_SetName(py_arg0, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n \n-PyObject* kth_py_native_chain_stealth_compact_list_count(PyObject* self, PyObject* args){\n-    PyObject* py_stealth_compact_list;\n+PyObject* kth_py_native_chain_stealth_compact_list_count(PyObject* self, PyObject* py_arg0) {\n+    kth_stealth_compact_list_const_t list_handle = (kth_stealth_compact_list_const_t)PyCapsule_GetPointer(py_arg0, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n+    if (list_handle == NULL) return NULL;\n+    auto const result = kth_chain_stealth_compact_list_count(list_handle);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}\n \n-    if ( ! PyArg_ParseTuple(args, \"O\", &py_stealth_compact_list)) {\n+PyObject* kth_py_native_chain_stealth_compact_list_nth(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"list\", (char*)\"index\", NULL};\n+    PyObject* py_list = NULL;\n+    unsigned long long index = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OK\", kwlist, &py_list, &index)) {\n         return NULL;\n     }\n-\n-    kth_stealth_compact_list_t list = (kth_stealth_compact_list_t)get_ptr(py_stealth_compact_list);\n-    uint64_t res = kth_chain_stealth_compact_list_count(list);\n-    return Py_BuildValue(\"K\", res);\n+    kth_stealth_compact_list_const_t list_handle = (kth_stealth_compact_list_const_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n+    if (list_handle == NULL) return NULL;\n+    auto const result = kth_chain_stealth_compact_list_nth(list_handle, (kth_size_t)index);\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT, NULL);\n }","path":"src/chain/stealth_compact_list.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n**`nth` can call `PyCapsule_New(NULL, ...)` and has no bounds check.**\n\nIf the native `kth_chain_stealth_compact_list_nth` returns `NULL` for any reason (currently nothing here guarantees non-null — unlike `history_compact.cpp::point()` which explicitly guards), `PyCapsule_New` will raise a confusing `ValueError: PyCapsule_New called with null pointer` instead of a meaningful `IndexError`. Additionally, nothing validates `index` against `count` before calling into native code, so an out-of-range index is pushed straight to the C API (UB depends on native implementation). Same gap exists in `history_compact_list.cpp` and likely other new `*_list_nth` bindings.\n\n<details>\n<summary>🛡️ Proposed guard</summary>\n\n```diff\n     kth_stealth_compact_list_const_t list_handle = (kth_stealth_compact_list_const_t)PyCapsule_GetPointer(py_list, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT_LIST);\n     if (list_handle == NULL) return NULL;\n+    kth_size_t const count = kth_chain_stealth_compact_list_count(list_handle);\n+    if ((kth_size_t)index >= count) {\n+        PyErr_SetString(PyExc_IndexError, \"stealth_compact_list index out of range\");\n+        return NULL;\n+    }\n     auto const result = kth_chain_stealth_compact_list_nth(list_handle, (kth_size_t)index);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL element returned\");\n+        return NULL;\n+    }\n     return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT, NULL);\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/stealth_compact_list.cpp` around lines 61 - 72, Check the list\nbounds and avoid calling PyCapsule_New with a null pointer: call\nkth_chain_stealth_compact_list_count(list_handle) before nth and if index >=\ncount set an IndexError via PyErr_SetString(PyExc_IndexError, \"index out of\nrange\") and return NULL; after calling\nkth_chain_stealth_compact_list_nth(list_handle, (kth_size_t)index) check if\nresult == NULL and if so set a descriptive IndexError (or appropriate error) and\nreturn NULL; only call PyCapsule_New((void*)result,\nKTH_PY_CAPSULE_CHAIN_STEALTH_COMPACT, NULL) when result is non-NULL. Apply the\nsame guard pattern to the analogous history_compact_list_nth binding.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:82d52b92-3a3e-40d5-9ae9-5f882202169f -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461858","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461858"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461858"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461858/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":103,"position":103,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490376","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461854","pull_request_review_id":4146628261,"id":3116461854,"node_id":"PRRC_kwDODiJdjc65wW8e","diff_hunk":"@@ -0,0 +1,236 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/prefilled_transaction.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_prefilled_transaction_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION)) return;\n+    kth_prefilled_transaction_mut_t handle = (kth_prefilled_transaction_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION);\n+    if (handle != NULL) kth_chain_prefilled_transaction_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_prefilled_transaction_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_chain_prefilled_transaction_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION, kth_py_native_chain_prefilled_transaction_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_prefilled_transaction_construct_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"reader\", (char*)\"version\", NULL};\n+    char const* reader_buf = NULL;\n+    Py_ssize_t reader_size = 0;\n+    unsigned int version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#I\", kwlist, &reader_buf, &reader_size, &version)) {\n+        return NULL;\n+    }\n+    kth_prefilled_transaction_mut_t out = NULL;\n+    kth_error_code_t result = kth_chain_prefilled_transaction_construct_from_data((uint8_t const*)reader_buf, (kth_size_t)reader_size, (uint32_t)version, &out);\n+    if (result != kth_ec_success) {\n+        PyErr_Format(PyExc_RuntimeError, \"kth error code %d\", (int)result);\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)out, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION, kth_py_native_chain_prefilled_transaction_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_prefilled_transaction_construct(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"index\", (char*)\"tx\", NULL};\n+    unsigned long long index = 0;\n+    PyObject* py_tx = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"KO\", kwlist, &index, &py_tx)) {\n+        return NULL;\n+    }\n+    kth_transaction_const_t tx_handle = (kth_transaction_const_t)PyCapsule_GetPointer(py_tx, KTH_PY_CAPSULE_CHAIN_TRANSACTION);\n+    if (tx_handle == NULL) return NULL;\n+    auto const result = kth_chain_prefilled_transaction_construct((uint64_t)index, tx_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION, kth_py_native_chain_prefilled_transaction_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_prefilled_transaction_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_prefilled_transaction_const_t self_handle = (kth_prefilled_transaction_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_prefilled_transaction_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION, kth_py_native_chain_prefilled_transaction_capsule_dtor);\n+}","path":"src/chain/prefilled_transaction.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n**All owned-handle capsule-wrap sites leak on `PyCapsule_New` failure.**\n\n`construct_default` (line 33), `construct_from_data` (line 51), `construct` (line 69), and `copy` (line 82) each successfully allocate a native `kth_prefilled_transaction` and then pass it to `PyCapsule_New` without guarding the capsule allocation. If `PyCapsule_New` returns `NULL` (e.g., `MemoryError` under pressure), the native handle is leaked. This is the same systemic pattern flagged on `src/chain/history_compact.cpp`; since `prefilled_transaction` has four such sites, it's worth a single local helper.\n\n<details>\n<summary>🛡️ Proposed helper + call-site fix</summary>\n\n```diff\n+static PyObject*\n+wrap_or_destruct(kth_prefilled_transaction_mut_t handle) {\n+    PyObject* cap = PyCapsule_New((void*)handle, KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION, kth_py_native_chain_prefilled_transaction_capsule_dtor);\n+    if (cap == NULL) {\n+        kth_chain_prefilled_transaction_destruct(handle);\n+    }\n+    return cap;\n+}\n```\n\nAnd replace each `return PyCapsule_New(...);` at lines 33, 51, 69, 82 with `return wrap_or_destruct(result);` (or `out` for `construct_from_data`).\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/prefilled_transaction.cpp` around lines 26 - 83, The four functions\n(kth_py_native_chain_prefilled_transaction_construct_default,\nkth_py_native_chain_prefilled_transaction_construct_from_data,\nkth_py_native_chain_prefilled_transaction_construct,\nkth_py_native_chain_prefilled_transaction_copy) can leak the native\nkth_prefilled_transaction when PyCapsule_New fails; add a small helper (e.g.,\nwrap_or_destruct) that accepts the allocated kth_prefilled_transaction_mut_t,\ncalls PyCapsule_New(..., KTH_PY_CAPSULE_CHAIN_PREFILLED_TRANSACTION,\nkth_py_native_chain_prefilled_transaction_capsule_dtor) and if PyCapsule_New\nreturns NULL immediately calls the native destructor\n(kth_chain_prefilled_transaction_destruct or the appropriate kth_*_destruct for\nprefilled_transaction) to free the handle and then returns NULL; replace the\ndirect returns of PyCapsule_New in each of the four functions with calls to this\nhelper (use 'out' in construct_from_data and 'result' in the others).\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:82d52b92-3a3e-40d5-9ae9-5f882202169f -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461854","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461854"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461854"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461854/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":83,"position":83,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490295","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461848","pull_request_review_id":4146628261,"id":3116461848,"node_id":"PRRC_kwDODiJdjc65wW8Y","diff_hunk":"@@ -0,0 +1,536 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/operation.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_operation_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_OPERATION)) return;\n+    kth_operation_mut_t handle = (kth_operation_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (handle != NULL) kth_chain_operation_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_chain_operation_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"reader\", NULL};\n+    char const* reader_buf = NULL;\n+    Py_ssize_t reader_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &reader_buf, &reader_size)) {\n+        return NULL;\n+    }\n+    kth_operation_mut_t out = NULL;\n+    kth_error_code_t result = kth_chain_operation_construct_from_data((uint8_t const*)reader_buf, (kth_size_t)reader_size, &out);\n+    if (result != kth_ec_success) {\n+        PyErr_Format(PyExc_RuntimeError, \"kth error code %d\", (int)result);\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)out, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_uncoded_minimal(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"uncoded\", (char*)\"minimal\", NULL};\n+    char const* uncoded_buf = NULL;\n+    Py_ssize_t uncoded_size = 0;\n+    int minimal = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#p\", kwlist, &uncoded_buf, &uncoded_size, &minimal)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_construct_from_uncoded_minimal((uint8_t const*)uncoded_buf, (kth_size_t)uncoded_size, (kth_bool_t)minimal);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_code(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"code\", NULL};\n+    unsigned long long code = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &code)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_construct_from_code((kth_opcode_t)code);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_mut_t self_handle = (kth_operation_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    kth_chain_operation_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n+        return NULL;\n+    }\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    kth_operation_const_t other_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_from_string(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"mnemonic\", NULL};\n+    PyObject* py_self = NULL;\n+    char const* mnemonic = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"Os\", kwlist, &py_self, &mnemonic)) {\n+        return NULL;\n+    }\n+    kth_operation_mut_t self_handle = (kth_operation_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_from_string(self_handle, mnemonic);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_is_valid(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_is_valid(self_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_to_data(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    kth_size_t out_size = 0;\n+    auto const result = kth_chain_operation_to_data(self_handle, &out_size);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: serialization failed\");\n+        return NULL;\n+    }\n+    PyObject* py_result = Py_BuildValue(\"y#\", result, (Py_ssize_t)out_size);\n+    kth_core_destruct_array(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_to_string(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"active_flags\", NULL};\n+    PyObject* py_self = NULL;\n+    unsigned long long active_flags = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OK\", kwlist, &py_self, &active_flags)) {\n+        return NULL;\n+    }\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_to_string(self_handle, (kth_script_flags_t)active_flags);\n+    PyObject* py_result = (result == NULL)\n+        ? (Py_INCREF(Py_None), Py_None)\n+        : Py_BuildValue(\"s\", result);\n+    if (result != NULL) kth_core_destruct_string(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_serialized_size(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_serialized_size(self_handle);\n+    return PyLong_FromSize_t((size_t)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_code(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_code(self_handle);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_data(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    kth_size_t out_size = 0;\n+    auto const result = kth_chain_operation_data(self_handle, &out_size);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: serialization failed\");\n+        return NULL;\n+    }\n+    PyObject* py_result = Py_BuildValue(\"y#\", result, (Py_ssize_t)out_size);\n+    kth_core_destruct_array(result);\n+    return py_result;\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_opcode_from_size(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"size\", NULL};\n+    Py_ssize_t size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"n\", kwlist, &size)) {\n+        return NULL;\n+    }\n+    if (size < 0) {\n+        PyErr_Format(PyExc_ValueError, \"size must be non-negative, got %zd\", size);\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_opcode_from_size((kth_size_t)size);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_minimal_opcode_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"data\", NULL};\n+    char const* data_buf = NULL;\n+    Py_ssize_t data_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &data_buf, &data_size)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_minimal_opcode_from_data((uint8_t const*)data_buf, (kth_size_t)data_size);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_nominal_opcode_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"data\", NULL};\n+    char const* data_buf = NULL;\n+    Py_ssize_t data_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &data_buf, &data_size)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_nominal_opcode_from_data((uint8_t const*)data_buf, (kth_size_t)data_size);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_opcode_from_positive(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"value\", NULL};\n+    unsigned char value = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"B\", kwlist, &value)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_opcode_from_positive((uint8_t)value);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_opcode_to_positive(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"code\", NULL};\n+    unsigned long long code = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &code)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_opcode_to_positive((kth_opcode_t)code);\n+    return PyLong_FromUnsignedLongLong((unsigned long long)result);\n+}","path":"src/chain/operation.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Confirm the width of kth_opcode_t in the installed kth C-API.\nrg -nP -C2 '\\bkth_opcode_t\\b' -g '*.h' -g '*.hpp'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\ncd /tmp && find . -type f -name \"operation.cpp\" 2>/dev/null | head -5\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\ngit ls-files | grep -E '(operation\\.(cpp|h)|opcode)' | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 123\n\n---\n\n🏁 Script executed:\n\n```shell\nwc -l src/chain/operation.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 87\n\n---\n\n🏁 Script executed:\n\n```shell\nsed -n '250,280p' src/chain/operation.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1349\n\n---\n\n🏁 Script executed:\n\n```shell\nsed -n '1,50p' src/chain/operation.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2205\n\n---\n\n🏁 Script executed:\n\n```shell\nrg -n 'PyArg_ParseTupleAndKeywords.*\"K\"' src/chain/operation.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 916\n\n---\n\n🏁 Script executed:\n\n```shell\nrg -n 'kth_opcode_t' src/chain/operation.cpp | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1078\n\n---\n\n🏁 Script executed:\n\n```shell\ncat include/kth/py-native/chain/operation.h\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 4352\n\n---\n\n🏁 Script executed:\n\n```shell\nrg 'typedef|using.*kth_opcode_t'\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 684\n\n---\n\n🏁 Script executed:\n\n```shell\ngrep -r 'kth_opcode_t' include/\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\nrg -n 'construct_from_code|opcode_from_positive' src/chain/operation.cpp | head -10\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 741\n\n---\n\n🏁 Script executed:\n\n```shell\nsed -n '65,85p' src/chain/operation.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 839\n\n---\n\n🏁 Script executed:\n\n```shell\npython3 << 'EOF'\n# Check the inconsistency: opcode_from_positive vs others\nfunctions_with_K = [\n    'construct_from_code', 'opcode_to_positive', 'is_push', 'is_payload',\n    'is_counted', 'is_version', 'is_numeric', 'is_positive', 'is_reserved',\n    'is_disabled', 'is_conditional', 'is_relaxed_push'\n]\nprint(f\"Functions using 'K' format (unsigned long long): {len(functions_with_K)}\")\nprint(functions_with_K)\nprint(\"\\nOnly opcode_from_positive uses 'B' format (unsigned char)\")\nprint(\"This creates an asymmetry where input validation differs\")\nEOF\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 422\n\n---\n\n🏁 Script executed:\n\n```shell\ngrep -r 'opcode' src/chain/operation.cpp | grep -i 'uint8\\|unsigned char\\|byte' | head -5\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 379\n\n---\n\n🏁 Script executed:\n\n```shell\nrg -i 'bitcoin.*opcode|opcode.*bitcoin' src/\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\ncat include/kth/py-native/utils.h 2>/dev/null | head -50\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 587\n\n---\n\n\n\n</details>\n\n**Input validation mismatch: inconsistent opcode format across related functions causes silent truncation.**\n\n`opcode_to_positive`, `is_push`, `is_payload`, `is_counted`, `is_version`, `is_numeric`, `is_positive`, `is_reserved`, `is_disabled`, `is_conditional`, `is_relaxed_push`, and `construct_from_code` all parse `code` with format `\"K\"` (unsigned long long) and cast to `kth_opcode_t`. Since opcodes are 8-bit values (as confirmed by `opcode_from_positive` casting to `uint8_t` on line 261), any input ≥ 256 silently truncates—producing misleading results (e.g., `is_push(0x100)` returning the same as `is_push(0x00)`). \n\nAlign input validation by either switching the format string to `\"B\"` (unsigned char) like `opcode_from_positive` uses, or add a single validation check before the cast to enforce the 0–255 range.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/operation.cpp` around lines 265 - 274, The function\nkth_py_native_chain_operation_opcode_to_positive (and the sibling functions\nopcode_to_positive, is_push, is_payload, is_counted, is_version, is_numeric,\nis_positive, is_reserved, is_disabled, is_conditional, is_relaxed_push,\nconstruct_from_code) currently parse the incoming code with format \"K\" and cast\nto kth_opcode_t, which silently truncates values ≥256; fix by making the parsing\nstrict to an 8-bit value—either change PyArg_ParseTupleAndKeywords format to \"B\"\n(unsigned char) like opcode_from_positive uses, or keep \"K\" but add an explicit\nrange check (0 <= code <= 255) before casting and return a Python exception on\nout-of-range input; apply the same change to all listed functions to ensure\nconsistent 0–255 validation before casting to kth_opcode_t.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:ca8f9755-c564-415d-8750-fb87f37a382d -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:54Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461848","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461848"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461848"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461848/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":274,"position":274,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490244","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461842","pull_request_review_id":4146628261,"id":3116461842,"node_id":"PRRC_kwDODiJdjc65wW8S","diff_hunk":"@@ -0,0 +1,536 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/operation.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_operation_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_OPERATION)) return;\n+    kth_operation_mut_t handle = (kth_operation_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (handle != NULL) kth_chain_operation_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_chain_operation_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"reader\", NULL};\n+    char const* reader_buf = NULL;\n+    Py_ssize_t reader_size = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#\", kwlist, &reader_buf, &reader_size)) {\n+        return NULL;\n+    }\n+    kth_operation_mut_t out = NULL;\n+    kth_error_code_t result = kth_chain_operation_construct_from_data((uint8_t const*)reader_buf, (kth_size_t)reader_size, &out);\n+    if (result != kth_ec_success) {\n+        PyErr_Format(PyExc_RuntimeError, \"kth error code %d\", (int)result);\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)out, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_uncoded_minimal(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"uncoded\", (char*)\"minimal\", NULL};\n+    char const* uncoded_buf = NULL;\n+    Py_ssize_t uncoded_size = 0;\n+    int minimal = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#p\", kwlist, &uncoded_buf, &uncoded_size, &minimal)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_construct_from_uncoded_minimal((uint8_t const*)uncoded_buf, (kth_size_t)uncoded_size, (kth_bool_t)minimal);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_construct_from_code(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"code\", NULL};\n+    unsigned long long code = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"K\", kwlist, &code)) {\n+        return NULL;\n+    }\n+    auto const result = kth_chain_operation_construct_from_code((kth_opcode_t)code);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_operation_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_operation_const_t self_handle = (kth_operation_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_OPERATION);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_operation_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+}","path":"src/chain/operation.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n**Native handle leaks if `PyCapsule_New` fails.**\n\nIn every constructor (`construct_default`, `construct_from_data`, `construct_from_uncoded_minimal`, `construct_from_code`) and in `copy`, on the last line we hand `result` to `PyCapsule_New(...)` and return directly. If `PyCapsule_New` returns `NULL` (e.g. on OOM), the freshly-allocated `kth_operation_*_t` is never destructed and leaks. Rare in practice, but worth fixing since the same pattern is repeated across many of the new generator-emitted wrappers in this PR.\n\n\n\n<details>\n<summary>♻️ Suggested pattern</summary>\n\n```diff\n-    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+    PyObject* capsule = PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OPERATION, kth_py_native_chain_operation_capsule_dtor);\n+    if (capsule == NULL) {\n+        kth_chain_operation_destruct(result);\n+        return NULL;\n+    }\n+    return capsule;\n```\n\nIf this pattern is generator-emitted, fixing it once in the template will cover all ~38 new bindings in this PR.\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/operation.cpp` around lines 26 - 96, Each constructor/copy\n(kth_py_native_chain_operation_construct_default,\nkth_py_native_chain_operation_construct_from_data,\nkth_py_native_chain_operation_construct_from_uncoded_minimal,\nkth_py_native_chain_operation_construct_from_code,\nkth_py_native_chain_operation_copy) currently returns a newly-allocated native\nhandle wrapped by PyCapsule_New but does not free the native handle if\nPyCapsule_New itself fails; update each function so that after creating the\nnative pointer (e.g., result or out) you attempt PyCapsule_New and if it returns\nNULL call the corresponding destructor (kth_chain_operation_destruct) on the\nnative handle before returning NULL to avoid leaks, and ensure you only call the\ndestructor on successfully-allocated handles (for construct_from_data use the\nout variable).\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:ca8f9755-c564-415d-8750-fb87f37a382d -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461842","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461842"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461842"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461842/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":96,"position":96,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490105","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461839","pull_request_review_id":4146628261,"id":3116461839,"node_id":"PRRC_kwDODiJdjc65wW8P","diff_hunk":"@@ -0,0 +1,163 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/history_compact.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_history_compact_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT)) return;\n+    kth_history_compact_mut_t handle = (kth_history_compact_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (handle != NULL) kth_chain_history_compact_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_history_compact_const_t self_handle = (kth_history_compact_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_history_compact_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT, kth_py_native_chain_history_compact_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_history_compact_mut_t self_handle = (kth_history_compact_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    kth_chain_history_compact_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_kind(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_history_compact_const_t self_handle = (kth_history_compact_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_history_compact_kind(self_handle);\n+    return PyLong_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_set_kind(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"value\", NULL};\n+    PyObject* py_self = NULL;\n+    unsigned long long value = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OK\", kwlist, &py_self, &value)) {\n+        return NULL;\n+    }\n+    kth_history_compact_mut_t self_handle = (kth_history_compact_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    kth_chain_history_compact_set_kind(self_handle, (kth_point_kind_t)value);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_point(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_history_compact_const_t self_handle = (kth_history_compact_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_history_compact_point(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_POINT, NULL);\n+}","path":"src/chain/history_compact.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n**Borrowed-handle capsule has no lifetime tie to parent.**\n\n`point()` returns a view into the parent `history_compact` (correctly capsule-wrapped with a `NULL` destructor so it isn't freed), but nothing prevents the parent capsule from being GC'd or explicitly destructed while the returned point capsule is still live — the next access is a use-after-free. This is a repeating pattern across the PR (e.g., `prefilled_transaction_transaction`, `*_list_nth`). Consider storing a strong reference to the parent on the child capsule (e.g., via `PyCapsule_SetContext` or by returning a tuple `(parent, capsule)` / a Python wrapper that holds the parent), or document the contract clearly on the Python-side API.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/history_compact.cpp` around lines 72 - 83, The returned point\ncapsule from kth_py_native_chain_history_compact_point holds a borrowed pointer\ninto the parent history_compact and currently has no ownership tie, allowing the\nparent (kth_history_compact_const_t / KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT) to\nbe GC'd and cause a use-after-free; fix by making the child capsule keep a\nstrong reference to the parent capsule before returning (e.g., call\nPyCapsule_SetContext on the new KTH_PY_CAPSULE_CHAIN_POINT capsule to store a\nnew reference to the original py_self, or otherwise return a tuple (py_self,\ncapsule) or Python wrapper that retains py_self) so\nkth_chain_history_compact_point results remain valid while the child is alive.\nEnsure you apply the same pattern to other functions using borrowed views (e.g.,\nprefilled_transaction_transaction, *_list_nth) and do not change the parent's\nNULL destructor unless you also manage proper refcounting.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:82d52b92-3a3e-40d5-9ae9-5f882202169f -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461839","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461839"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461839"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461839/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":83,"position":83,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627490033","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461831","pull_request_review_id":4146628261,"id":3116461831,"node_id":"PRRC_kwDODiJdjc65wW8H","diff_hunk":"@@ -0,0 +1,163 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/history_compact.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_history_compact_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT)) return;\n+    kth_history_compact_mut_t handle = (kth_history_compact_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (handle != NULL) kth_chain_history_compact_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_history_compact_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_history_compact_const_t self_handle = (kth_history_compact_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_history_compact_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT, kth_py_native_chain_history_compact_capsule_dtor);\n+}","path":"src/chain/history_compact.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟡 Minor_\n\n**Native handle leaks if `PyCapsule_New` fails.**\n\n`kth_chain_history_compact_copy` succeeds and allocates a fresh native object, but if `PyCapsule_New` then fails (e.g., `MemoryError`) the function returns `NULL` and `result` is never destructed — classic allocation-on-success-then-wrap-fail leak. The same shape appears in every capsule-wrapping path on the PR (construct_*, copy). Worth fixing once and propagating.\n\n<details>\n<summary>🛡️ Proposed guard</summary>\n\n```diff\n     auto const result = kth_chain_history_compact_copy(self_handle);\n     if (result == NULL) {\n         PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n         return NULL;\n     }\n-    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT, kth_py_native_chain_history_compact_capsule_dtor);\n+    PyObject* cap = PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_HISTORY_COMPACT, kth_py_native_chain_history_compact_capsule_dtor);\n+    if (cap == NULL) {\n+        kth_chain_history_compact_destruct(result);\n+        return NULL;\n+    }\n+    return cap;\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/history_compact.cpp` around lines 26 - 37,\nkth_py_native_chain_history_compact_copy allocates a native object with\nkth_chain_history_compact_copy then creates a PyCapsule with PyCapsule_New; if\nPyCapsule_New fails the native result is leaked — fix by checking the return of\nPyCapsule_New and if it returns NULL immediately free the native object (call\nthe native destructor, e.g. kth_chain_history_compact_destruct or the\nappropriate free function) before returning NULL; ensure similar guarding is\napplied to other construct_*/copy wrappers that follow the same pattern and\nreference kth_py_native_chain_history_compact_capsule_dtor for consistent\ncleanup.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:82d52b92-3a3e-40d5-9ae9-5f882202169f -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:50Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461831","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461831"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461831"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461831/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":37,"position":37,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:50Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627489973","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461825","pull_request_review_id":4146628261,"id":3116461825,"node_id":"PRRC_kwDODiJdjc65wW8B","diff_hunk":"@@ -0,0 +1,296 @@\n+// Copyright (c) 2016-present Knuth Project developers.\n+// Distributed under the MIT software license, see the accompanying\n+// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n+\n+#include <kth/py-native/chain/double_spend_proof.h>\n+\n+#include <string.h>\n+#include <kth/capi.h>\n+#include <kth/py-native/capsule_names.h>\n+#include <kth/py-native/utils.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+// PyCapsule destructor — released by GC when the capsule is\n+// collected. Explicit `destruct` calls set the capsule name to\n+// \"kth.destroyed\", so PyCapsule_IsValid returns false and this\n+// destructor becomes a no-op (no double-free).\n+void kth_py_native_chain_double_spend_proof_capsule_dtor(PyObject* capsule) {\n+    if ( ! PyCapsule_IsValid(capsule, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF)) return;\n+    kth_double_spend_proof_mut_t handle = (kth_double_spend_proof_mut_t)PyCapsule_GetPointer(capsule, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (handle != NULL) kth_chain_double_spend_proof_destruct(handle);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_construct_default(PyObject* self, PyObject* Py_UNUSED(args)) {\n+    auto const result = kth_chain_double_spend_proof_construct_default();\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF, kth_py_native_chain_double_spend_proof_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_construct_from_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"reader\", (char*)\"version\", NULL};\n+    char const* reader_buf = NULL;\n+    Py_ssize_t reader_size = 0;\n+    unsigned int version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"y#I\", kwlist, &reader_buf, &reader_size, &version)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_mut_t out = NULL;\n+    kth_error_code_t result = kth_chain_double_spend_proof_construct_from_data((uint8_t const*)reader_buf, (kth_size_t)reader_size, (uint32_t)version, &out);\n+    if (result != kth_ec_success) {\n+        PyErr_Format(PyExc_RuntimeError, \"kth error code %d\", (int)result);\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)out, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF, kth_py_native_chain_double_spend_proof_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_construct(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"out_point\", (char*)\"spender1\", (char*)\"spender2\", NULL};\n+    PyObject* py_out_point = NULL;\n+    PyObject* py_spender1 = NULL;\n+    PyObject* py_spender2 = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OOO\", kwlist, &py_out_point, &py_spender1, &py_spender2)) {\n+        return NULL;\n+    }\n+    kth_output_point_const_t out_point_handle = (kth_output_point_const_t)PyCapsule_GetPointer(py_out_point, KTH_PY_CAPSULE_CHAIN_OUTPUT_POINT);\n+    if (out_point_handle == NULL) return NULL;\n+    kth_double_spend_proof_spender_const_t spender1_handle = (kth_double_spend_proof_spender_const_t)PyCapsule_GetPointer(py_spender1, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER);\n+    if (spender1_handle == NULL) return NULL;\n+    kth_double_spend_proof_spender_const_t spender2_handle = (kth_double_spend_proof_spender_const_t)PyCapsule_GetPointer(py_spender2, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER);\n+    if (spender2_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_construct(out_point_handle, spender1_handle, spender2_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF, kth_py_native_chain_double_spend_proof_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_copy(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_copy(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_MemoryError, \"kth: allocation failed\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF, kth_py_native_chain_double_spend_proof_capsule_dtor);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_destruct(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_double_spend_proof_mut_t self_handle = (kth_double_spend_proof_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    kth_chain_double_spend_proof_destruct(self_handle);\n+    PyCapsule_SetName(py_self, \"kth.destroyed\");\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_equals(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"other\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_other = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_other)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    kth_double_spend_proof_const_t other_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_other, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (other_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_equals(self_handle, other_handle);\n+    return PyBool_FromLong((long)result);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_out_point(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_out_point(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_OUTPUT_POINT, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_set_out_point(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_mut_t self_handle = (kth_double_spend_proof_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    kth_output_point_const_t x_handle = (kth_output_point_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_CHAIN_OUTPUT_POINT);\n+    if (x_handle == NULL) return NULL;\n+    kth_chain_double_spend_proof_set_out_point(self_handle, x_handle);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_spender1(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_spender1(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_set_spender1(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_mut_t self_handle = (kth_double_spend_proof_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    kth_double_spend_proof_spender_const_t x_handle = (kth_double_spend_proof_spender_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER);\n+    if (x_handle == NULL) return NULL;\n+    kth_chain_double_spend_proof_set_spender1(self_handle, x_handle);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_spender2(PyObject* self, PyObject* py_arg0) {\n+    PyObject* py_self = py_arg0;\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    auto const result = kth_chain_double_spend_proof_spender2(self_handle);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: NULL handle returned\");\n+        return NULL;\n+    }\n+    return PyCapsule_New((void*)result, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER, NULL);\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_set_spender2(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"x\", NULL};\n+    PyObject* py_self = NULL;\n+    PyObject* py_x = NULL;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"OO\", kwlist, &py_self, &py_x)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_mut_t self_handle = (kth_double_spend_proof_mut_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    kth_double_spend_proof_spender_const_t x_handle = (kth_double_spend_proof_spender_const_t)PyCapsule_GetPointer(py_x, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF_SPENDER);\n+    if (x_handle == NULL) return NULL;\n+    kth_chain_double_spend_proof_set_spender2(self_handle, x_handle);\n+    Py_RETURN_NONE;\n+}\n+\n+PyObject*\n+kth_py_native_chain_double_spend_proof_to_data(PyObject* self, PyObject* args, PyObject* kwds) {\n+    static char* kwlist[] = {(char*)\"self\", (char*)\"version\", NULL};\n+    PyObject* py_self = NULL;\n+    Py_ssize_t version = 0;\n+    if ( ! PyArg_ParseTupleAndKeywords(args, kwds, \"On\", kwlist, &py_self, &version)) {\n+        return NULL;\n+    }\n+    kth_double_spend_proof_const_t self_handle = (kth_double_spend_proof_const_t)PyCapsule_GetPointer(py_self, KTH_PY_CAPSULE_CHAIN_DOUBLE_SPEND_PROOF);\n+    if (self_handle == NULL) return NULL;\n+    if (version < 0) {\n+        PyErr_Format(PyExc_ValueError, \"version must be non-negative, got %zd\", version);\n+        return NULL;\n+    }\n+    kth_size_t out_size = 0;\n+    auto const result = kth_chain_double_spend_proof_to_data(self_handle, (kth_size_t)version, &out_size);\n+    if (result == NULL) {\n+        PyErr_SetString(PyExc_RuntimeError, \"kth: serialization failed\");\n+        return NULL;\n+    }\n+    PyObject* py_result = Py_BuildValue(\"y#\", result, (Py_ssize_t)out_size);\n+    kth_core_destruct_array(result);\n+    return py_result;\n+}","path":"src/chain/double_spend_proof.cpp","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Compare C-API signatures for get_blocks vs double_spend_proof to_data / serialized_size.\nfd -t f -e h -e hpp | xargs rg -nP -C2 \\\n  '\\bkth_chain_(get_blocks|double_spend_proof)_(to_data|serialized_size)\\b' 2>/dev/null | head -80\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 42\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Explore repository structure to find C-API headers and get_blocks.cpp for comparison\necho \"=== Repository structure ===\"\nls -la\necho \"\"\necho \"=== Finding C-API related files ===\"\nfd -t f -name \"*get_blocks*\" | head -20\necho \"\"\nfd -t f -name \"*double_spend_proof*\" | head -20\necho \"\"\necho \"=== Checking for header files ===\"\nfd -t f \\( -name \"*.h\" -o -name \"*.hpp\" \\) | head -30\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 2066\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Fix fd syntax and explore src directory\necho \"=== Contents of src directory ===\"\nfind src -type f | head -40\necho \"\"\necho \"=== Looking for get_blocks.cpp and double_spend_proof.cpp ===\"\nfind src -name \"get_blocks.cpp\" -o -name \"double_spend_proof.cpp\"\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1247\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Read get_blocks.cpp to see version parameter handling\necho \"=== get_blocks.cpp L175-195 ===\"\nsed -n '175,195p' src/chain/get_blocks.cpp\necho \"\"\necho \"=== double_spend_proof.cpp L204-226 (to_data) ===\"\nsed -n '204,226p' src/chain/double_spend_proof.cpp\necho \"\"\necho \"=== double_spend_proof.cpp L246-262 (serialized_size) ===\"\nsed -n '246,262p' src/chain/double_spend_proof.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 3026\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Search for C-API function declarations or type definitions\necho \"=== Search for kth_chain_double_spend_proof_to_data signature ===\"\nrg 'kth_chain_double_spend_proof_to_data' src/ -A2 -B2\necho \"\"\necho \"=== Search for kth_chain_get_blocks_to_data signature ===\"\nrg 'kth_chain_get_blocks_to_data' src/ -A2 -B2\necho \"\"\necho \"=== Search for kth_size_t definition ===\"\nrg 'kth_size_t' src/ | head -10\necho \"\"\necho \"=== Check if there are C-API headers in include/ ===\"\nfind include/ -type f\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 4586\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Read the header files to see function declarations\necho \"=== include/kth/py-native/chain/double_spend_proof.h ===\"\ncat include/kth/py-native/chain/double_spend_proof.h\necho \"\"\necho \"=== include/kth/py-native/chain/get_blocks.h ===\"\ncat include/kth/py-native/chain/get_blocks.h\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 4231\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Search for other functions using version parameter to see the pattern\necho \"=== Search for other version parameters in codebase ===\"\nrg 'ParseTupleAndKeywords.*\".*n.*version' src/ -B2 -A8\necho \"\"\necho \"=== Search for other unsigned int version parameters ===\"\nrg 'ParseTupleAndKeywords.*\".*I.*version' src/ -B2 -A8\necho \"\"\necho \"=== Search for version cast patterns ===\"\nrg '\\(uint32_t\\)version|\\(kth_size_t\\)version' src/ | head -20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 28007\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Verify the double_spend_proof construct_from_data to confirm inconsistency\necho \"=== double_spend_proof.cpp construct_from_data ===\"\nsed -n '150,170p' src/chain/double_spend_proof.cpp\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 1227\n\n---\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Find the construct_from_data function for double_spend_proof\necho \"=== Searching for double_spend_proof construct_from_data ===\"\nrg -n 'kth_py_native_chain_double_spend_proof_construct_from_data' src/chain/double_spend_proof.cpp -A20\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 3591\n\n---\n\n\n\n</details>\n\n**Fix `version` parameter type inconsistency in `to_data` and `serialized_size` methods.**\n\nBoth `to_data` (L204–226) and `serialized_size` (L246–262) parse `version` as `Py_ssize_t` with format `\"n\"` and cast to `kth_size_t`, while the same file's `construct_from_data` (L37–52) uses `unsigned int` with format `\"I\"` and casts to `uint32_t`. All other chain message types (get_blocks, merkle_block, compact_block, prefilled_transaction, get_headers, double_spend_proof_spender) consistently use `unsigned int` + `\"I\"` + `uint32_t`, which is the standard for bitcoin protocol versions.\n\nThe current implementation:\n- Creates an internal inconsistency within double_spend_proof.cpp itself\n- Makes the non-negativity validation (L216, L253) unreachable for unsigned types\n- On 64-bit platforms, `kth_size_t` (typically `uintptr_t`/`size_t`, 64-bit) differs from `uint32_t` (32-bit), risking silent truncation if large values are passed\n\nChange format from `\"n\"` to `\"I\"`, `Py_ssize_t version` to `unsigned int version`, remove the non-negativity branch, and cast to `uint32_t` instead of `kth_size_t` to align with the established pattern.\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@src/chain/double_spend_proof.cpp` around lines 204 - 226, The to_data\nfunction kth_py_native_chain_double_spend_proof_to_data (and the sibling\nserialized_size wrapper) currently parse version as Py_ssize_t with format \"n\"\nand validate non-negativity; change both to parse version as unsigned int using\nformat \"I\" (i.e. replace Py_ssize_t version with unsigned int version and format\n\"n\"->\"I\"), remove the negative-value check branches, and cast the parsed value\nto uint32_t when passing to kth_chain_double_spend_proof_to_data /\nkth_chain_double_spend_proof_serialized_size so the parameter types match the\nother chain message wrappers and avoid 64->32-bit truncation.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:nectarine:4af7457e-39a5-4a9c-ae21-427ab81b9de1 -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:49Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461825","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461825"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461825"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461825/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":226,"position":226,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:49Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627489867","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461813","pull_request_review_id":4146628261,"id":3116461813,"node_id":"PRRC_kwDODiJdjc65wW71","diff_hunk":"@@ -885,4 +890,602 @@ def chain_transaction_list_count(list: TransactionList) -> int: ...\n def chain_transaction_list_nth(list: TransactionList, index: int) -> Transaction: ...\n def chain_transaction_list_assign_at(list: TransactionList, index: int, elem: Transaction) -> None: ...\n def chain_transaction_list_erase(list: TransactionList, index: int) -> None: ...\n+\n+# ─── Binary (auto-generated, do not edit) ─────────────────────────\n+\n+class Binary:\n+    \"\"\"Opaque handle to a `kth::binary`. Constructed by\n+    `core_binary_construct_*` and released by\n+    `core_binary_destruct`.\"\"\"\n+    ...\n+\n+def core_binary_construct_default() -> Binary: ...\n+def core_binary_construct_from_bit_string(bit_string: str) -> Binary: ...\n+def core_binary_construct_from_size_number(size: int, number: int) -> Binary: ...\n+def core_binary_construct_from_size_blocks(size: int, blocks: bytes) -> Binary: ...\n+def core_binary_copy(self: Binary) -> Binary: ...\n+def core_binary_destruct(self: Binary) -> None: ...\n+def core_binary_blocks_size(bit_size: int) -> int: ...\n+def core_binary_is_base2(text: str) -> bool: ...\n+def core_binary_resize(self: Binary, size: int) -> None: ...\n+def core_binary_at(self: Binary, index: int) -> bool: ...\n+def core_binary_blocks(self: Binary) -> bytes: ...\n+def core_binary_encoded(self: Binary) -> str | None: ...\n+def core_binary_size(self: Binary) -> int: ...\n+def core_binary_append(self: Binary, post: \"Binary\") -> None: ...\n+def core_binary_prepend(self: Binary, prior: \"Binary\") -> None: ...\n+def core_binary_shift_left(self: Binary, distance: int) -> None: ...\n+def core_binary_shift_right(self: Binary, distance: int) -> None: ...\n+def core_binary_substring(self: Binary, start: int, length: int) -> \"Binary\" | None: ...\n+def core_binary_is_prefix_of_span(self: Binary, field: bytes) -> bool: ...\n+def core_binary_is_prefix_of_uint32(self: Binary, field: int) -> bool: ...\n+def core_binary_is_prefix_of_binary(self: Binary, field: \"Binary\") -> bool: ...\n+def core_binary_equals(self: Binary, other: Binary) -> bool: ...\n+def core_binary_less(self: Binary, x: \"Binary\") -> bool: ...\n+\n+# ─── GetBlocks (auto-generated, do not edit) ─────────────────────────\n+\n+class GetBlocks:\n+    \"\"\"Opaque handle to a `kth::domain::message::get_blocks`. Constructed by\n+    `chain_get_blocks_construct_*` and released by\n+    `chain_get_blocks_destruct`.\"\"\"\n+    ...\n+\n+def chain_get_blocks_construct_default() -> GetBlocks: ...\n+def chain_get_blocks_construct_from_data(reader: bytes, version: int) -> GetBlocks: ...\n+def chain_get_blocks_construct(start: object, stop: bytes) -> GetBlocks: ...\n+def chain_get_blocks_copy(self: GetBlocks) -> GetBlocks: ...\n+def chain_get_blocks_destruct(self: GetBlocks) -> None: ...\n+def chain_get_blocks_equals(self: GetBlocks, other: GetBlocks) -> bool: ...\n+def chain_get_blocks_start_hashes(self: GetBlocks) -> object: ...\n+def chain_get_blocks_set_start_hashes(self: GetBlocks, value: object) -> None: ...\n+def chain_get_blocks_stop_hash(self: GetBlocks) -> bytes: ...\n+def chain_get_blocks_set_stop_hash(self: GetBlocks, value: bytes) -> None: ...\n+def chain_get_blocks_to_data(self: GetBlocks, version: int) -> bytes: ...\n+def chain_get_blocks_is_valid(self: GetBlocks) -> bool: ...\n+def chain_get_blocks_reset(self: GetBlocks) -> None: ...\n+def chain_get_blocks_serialized_size(self: GetBlocks, version: int) -> int: ...\n+\n+# ─── GetHeaders (auto-generated, do not edit) ─────────────────────────\n+\n+class GetHeaders:\n+    \"\"\"Opaque handle to a `kth::domain::message::get_headers`. Constructed by\n+    `chain_get_headers_construct_*` and released by\n+    `chain_get_headers_destruct`.\"\"\"\n+    ...\n+\n+def chain_get_headers_construct_default() -> GetHeaders: ...\n+def chain_get_headers_construct_from_data(reader: bytes, version: int) -> GetHeaders: ...\n+def chain_get_headers_construct(start: object, stop: bytes) -> GetHeaders: ...\n+def chain_get_headers_copy(self: GetHeaders) -> GetHeaders: ...\n+def chain_get_headers_destruct(self: GetHeaders) -> None: ...\n+def chain_get_headers_equals(self: GetHeaders, other: GetHeaders) -> bool: ...\n+def chain_get_headers_start_hashes(self: GetHeaders) -> object: ...\n+def chain_get_headers_set_start_hashes(self: GetHeaders, value: object) -> None: ...\n+def chain_get_headers_stop_hash(self: GetHeaders) -> bytes: ...\n+def chain_get_headers_set_stop_hash(self: GetHeaders, value: bytes) -> None: ...\n+def chain_get_headers_to_data(self: GetHeaders, version: int) -> bytes: ...\n+def chain_get_headers_is_valid(self: GetHeaders) -> bool: ...\n+def chain_get_headers_reset(self: GetHeaders) -> None: ...\n+def chain_get_headers_serialized_size(self: GetHeaders, version: int) -> int: ...\n+\n+# ─── MerkleBlock (auto-generated, do not edit) ─────────────────────────\n+\n+class MerkleBlock:\n+    \"\"\"Opaque handle to a `kth::domain::message::merkle_block`. Constructed by\n+    `chain_merkle_block_construct_*` and released by\n+    `chain_merkle_block_destruct`.\"\"\"\n+    ...\n+\n+def chain_merkle_block_construct_default() -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_data(reader: bytes, version: int) -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_header_total_transactions_hashes_flags(header: \"Header\", total_transactions: int, hashes: object, flags: bytes) -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_block(block: \"Block\") -> MerkleBlock: ...\n+def chain_merkle_block_copy(self: MerkleBlock) -> MerkleBlock: ...\n+def chain_merkle_block_destruct(self: MerkleBlock) -> None: ...\n+def chain_merkle_block_equals(self: MerkleBlock, other: MerkleBlock) -> bool: ...\n+def chain_merkle_block_header(self: MerkleBlock) -> \"Header\" | None: ...\n+def chain_merkle_block_set_header(self: MerkleBlock, value: \"Header\") -> None: ...\n+def chain_merkle_block_total_transactions(self: MerkleBlock) -> int: ...\n+def chain_merkle_block_set_total_transactions(self: MerkleBlock, value: int) -> None: ...\n+def chain_merkle_block_hashes(self: MerkleBlock) -> object: ...\n+def chain_merkle_block_set_hashes(self: MerkleBlock, value: object) -> None: ...\n+def chain_merkle_block_flags(self: MerkleBlock) -> bytes: ...\n+def chain_merkle_block_set_flags(self: MerkleBlock, value: bytes) -> None: ...\n+def chain_merkle_block_to_data(self: MerkleBlock, version: int) -> bytes: ...\n+def chain_merkle_block_is_valid(self: MerkleBlock) -> bool: ...\n+def chain_merkle_block_reset(self: MerkleBlock) -> None: ...\n+def chain_merkle_block_serialized_size(self: MerkleBlock, version: int) -> int: ...\n+\n+# ─── PrefilledTransaction (auto-generated, do not edit) ─────────────────────────\n+\n+class PrefilledTransaction:\n+    \"\"\"Opaque handle to a `kth::domain::message::prefilled_transaction`. Constructed by\n+    `chain_prefilled_transaction_construct_*` and released by\n+    `chain_prefilled_transaction_destruct`.\"\"\"\n+    ...\n+\n+def chain_prefilled_transaction_construct_default() -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_construct_from_data(reader: bytes, version: int) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_construct(index: int, tx: \"Transaction\") -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_copy(self: PrefilledTransaction) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_destruct(self: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_equals(self: PrefilledTransaction, other: PrefilledTransaction) -> bool: ...\n+def chain_prefilled_transaction_index(self: PrefilledTransaction) -> int: ...\n+def chain_prefilled_transaction_set_index(self: PrefilledTransaction, value: int) -> None: ...\n+def chain_prefilled_transaction_transaction(self: PrefilledTransaction) -> \"Transaction\" | None: ...\n+def chain_prefilled_transaction_set_transaction(self: PrefilledTransaction, tx: \"Transaction\") -> None: ...\n+def chain_prefilled_transaction_to_data(self: PrefilledTransaction, version: int) -> bytes: ...\n+def chain_prefilled_transaction_is_valid(self: PrefilledTransaction) -> bool: ...\n+def chain_prefilled_transaction_reset(self: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_serialized_size(self: PrefilledTransaction, version: int) -> int: ...\n+\n+# ─── PrefilledTransactionList (auto-generated, do not edit) ─────────────────────────\n+\n+class PrefilledTransactionList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::prefilled_transaction>`.\"\"\"\n+    ...\n+\n+def chain_prefilled_transaction_list_construct_default() -> PrefilledTransactionList: ...\n+def chain_prefilled_transaction_list_push_back(list: PrefilledTransactionList, elem: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_list_destruct(list: PrefilledTransactionList) -> None: ...\n+def chain_prefilled_transaction_list_count(list: PrefilledTransactionList) -> int: ...\n+def chain_prefilled_transaction_list_nth(list: PrefilledTransactionList, index: int) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_list_assign_at(list: PrefilledTransactionList, index: int, elem: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_list_erase(list: PrefilledTransactionList, index: int) -> None: ...\n+\n+# ─── CompactBlock (auto-generated, do not edit) ─────────────────────────\n+\n+class CompactBlock:\n+    \"\"\"Opaque handle to a `kth::domain::message::compact_block`. Constructed by\n+    `chain_compact_block_construct_*` and released by\n+    `chain_compact_block_destruct`.\"\"\"\n+    ...\n+\n+def chain_compact_block_construct_default() -> CompactBlock: ...\n+def chain_compact_block_construct_from_data(reader: bytes, version: int) -> CompactBlock: ...\n+def chain_compact_block_construct(header: \"Header\", nonce: int, short_ids: object, transactions: object) -> CompactBlock: ...\n+def chain_compact_block_copy(self: CompactBlock) -> CompactBlock: ...\n+def chain_compact_block_destruct(self: CompactBlock) -> None: ...\n+def chain_compact_block_equals(self: CompactBlock, other: CompactBlock) -> bool: ...\n+def chain_compact_block_header(self: CompactBlock) -> \"Header\" | None: ...\n+def chain_compact_block_set_header(self: CompactBlock, value: \"Header\") -> None: ...\n+def chain_compact_block_nonce(self: CompactBlock) -> int: ...\n+def chain_compact_block_set_nonce(self: CompactBlock, value: int) -> None: ...\n+def chain_compact_block_short_ids(self: CompactBlock) -> object: ...\n+def chain_compact_block_set_short_ids(self: CompactBlock, value: object) -> None: ...\n+def chain_compact_block_transactions(self: CompactBlock) -> object: ...\n+def chain_compact_block_set_transactions(self: CompactBlock, value: object) -> None: ...\n+def chain_compact_block_to_data(self: CompactBlock, version: int) -> bytes: ...\n+def chain_compact_block_is_valid(self: CompactBlock) -> bool: ...\n+def chain_compact_block_reset(self: CompactBlock) -> None: ...\n+def chain_compact_block_serialized_size(self: CompactBlock, version: int) -> int: ...\n+\n+# ─── PaymentAddress (auto-generated, do not edit) ─────────────────────────\n+\n+class PaymentAddress:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::payment_address`. Constructed by\n+    `wallet_payment_address_construct_*` and released by\n+    `wallet_payment_address_destruct`.\"\"\"\n+    ...\n+\n+def wallet_payment_address_construct_default() -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_decoded(decoded: bytes) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_ec_private(secret: \"EcPrivate\") -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_address(address: str) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_address_net(address: str, net: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_short_hash_version(short_hash: bytes, version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_hash_version(hash: bytes, version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_ec_public_version(point: \"EcPublic\", version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_script_version(script: \"Script\", version: int) -> PaymentAddress: ...\n+def wallet_payment_address_copy(self: PaymentAddress) -> PaymentAddress: ...\n+def wallet_payment_address_destruct(self: PaymentAddress) -> None: ...\n+def wallet_payment_address_cashaddr_prefix_for(net: int) -> str | None: ...\n+def wallet_payment_address_from_pay_public_key_hash_script(script: \"Script\", version: int) -> \"PaymentAddress\" | None: ...\n+def wallet_payment_address_equals(self: PaymentAddress, other: PaymentAddress) -> bool: ...\n+def wallet_payment_address_less(self: PaymentAddress, x: \"PaymentAddress\") -> bool: ...\n+def wallet_payment_address_valid(self: PaymentAddress) -> bool: ...\n+def wallet_payment_address_encoded_legacy(self: PaymentAddress) -> str | None: ...\n+def wallet_payment_address_encoded_cashaddr(self: PaymentAddress, token_aware: bool) -> str | None: ...\n+def wallet_payment_address_encoded_token(self: PaymentAddress) -> str | None: ...\n+def wallet_payment_address_version(self: PaymentAddress) -> int: ...\n+def wallet_payment_address_hash_span(self: PaymentAddress) -> object: ...\n+def wallet_payment_address_hash20(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_hash32(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_to_payment(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_extract(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+def wallet_payment_address_extract_input(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+def wallet_payment_address_extract_output(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+\n+# ─── PaymentAddressList (auto-generated, do not edit) ─────────────────────────\n+\n+class PaymentAddressList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::payment_address>`.\"\"\"\n+    ...\n+\n+def chain_payment_address_list_construct_default() -> PaymentAddressList: ...\n+def chain_payment_address_list_push_back(list: PaymentAddressList, elem: PaymentAddress) -> None: ...\n+def chain_payment_address_list_destruct(list: PaymentAddressList) -> None: ...\n+def chain_payment_address_list_count(list: PaymentAddressList) -> int: ...\n+def chain_payment_address_list_nth(list: PaymentAddressList, index: int) -> PaymentAddress: ...\n+def chain_payment_address_list_assign_at(list: PaymentAddressList, index: int, elem: PaymentAddress) -> None: ...\n+def chain_payment_address_list_erase(list: PaymentAddressList, index: int) -> None: ...\n+\n+# ─── EcPublic (auto-generated, do not edit) ─────────────────────────\n+\n+class EcPublic:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::ec_public`. Constructed by\n+    `wallet_ec_public_construct_*` and released by\n+    `wallet_ec_public_destruct`.\"\"\"\n+    ...\n+\n+def wallet_ec_public_construct_default() -> EcPublic: ...\n+def wallet_ec_public_construct_from_ec_private(secret: \"EcPrivate\") -> EcPublic: ...\n+def wallet_ec_public_construct_from_decoded(decoded: bytes) -> EcPublic: ...\n+def wallet_ec_public_construct_from_base16(base16: str) -> EcPublic: ...\n+def wallet_ec_public_construct_from_compressed_point_compress(compressed_point: bytes, compress: bool) -> EcPublic: ...\n+def wallet_ec_public_construct_from_uncompressed_point_compress(uncompressed_point: bytes, compress: bool) -> EcPublic: ...\n+def wallet_ec_public_copy(self: EcPublic) -> EcPublic: ...\n+def wallet_ec_public_destruct(self: EcPublic) -> None: ...\n+def wallet_ec_public_equals(self: EcPublic, other: EcPublic) -> bool: ...\n+def wallet_ec_public_less(self: EcPublic, x: \"EcPublic\") -> bool: ...\n+def wallet_ec_public_valid(self: EcPublic) -> bool: ...\n+def wallet_ec_public_encoded(self: EcPublic) -> str | None: ...\n+def wallet_ec_public_point(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_compressed(self: EcPublic) -> bool: ...\n+def wallet_ec_public_to_data(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_to_uncompressed(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_to_payment_address(self: EcPublic, version: int) -> \"PaymentAddress\" | None: ...\n+\n+# ─── EcPrivate (auto-generated, do not edit) ─────────────────────────\n+\n+class EcPrivate:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::ec_private`. Constructed by\n+    `wallet_ec_private_construct_*` and released by\n+    `wallet_ec_private_destruct`.\"\"\"\n+    ...\n+\n+def wallet_ec_private_construct_default() -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_version(wif: str, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_compressed_version(wif_compressed: bytes, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_uncompressed_version(wif_uncompressed: bytes, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_secret_version_compress(secret: bytes, version: int, compress: bool) -> EcPrivate: ...\n+def wallet_ec_private_copy(self: EcPrivate) -> EcPrivate: ...\n+def wallet_ec_private_destruct(self: EcPrivate) -> None: ...\n+def wallet_ec_private_to_address_prefix(version: int) -> int: ...\n+def wallet_ec_private_to_wif_prefix(version: int) -> int: ...\n+def wallet_ec_private_to_version(address: int, wif: int) -> int: ...\n+def wallet_ec_private_equals(self: EcPrivate, other: EcPrivate) -> bool: ...\n+def wallet_ec_private_less(self: EcPrivate, x: \"EcPrivate\") -> bool: ...\n+def wallet_ec_private_valid(self: EcPrivate) -> bool: ...\n+def wallet_ec_private_encoded(self: EcPrivate) -> str | None: ...\n+def wallet_ec_private_secret(self: EcPrivate) -> bytes: ...\n+def wallet_ec_private_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_payment_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_wif_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_compressed(self: EcPrivate) -> bool: ...\n+def wallet_ec_private_to_public(self: EcPrivate) -> \"EcPublic\" | None: ...\n+def wallet_ec_private_to_payment_address(self: EcPrivate) -> \"PaymentAddress\" | None: ...\n+\n+# ─── Operation (auto-generated, do not edit) ─────────────────────────\n+\n+class Operation:\n+    \"\"\"Opaque handle to a `kth::domain::machine::operation`. Constructed by\n+    `chain_operation_construct_*` and released by\n+    `chain_operation_destruct`.\"\"\"\n+    ...\n+\n+def chain_operation_construct_default() -> Operation: ...\n+def chain_operation_construct_from_data(reader: bytes) -> Operation: ...\n+def chain_operation_construct_from_uncoded_minimal(uncoded: bytes, minimal: bool) -> Operation: ...\n+def chain_operation_construct_from_code(code: int) -> Operation: ...\n+def chain_operation_copy(self: Operation) -> Operation: ...\n+def chain_operation_destruct(self: Operation) -> None: ...\n+def chain_operation_equals(self: Operation, other: Operation) -> bool: ...\n+def chain_operation_from_string(self: Operation, mnemonic: str) -> bool: ...\n+def chain_operation_is_valid(self: Operation) -> bool: ...\n+def chain_operation_to_data(self: Operation) -> bytes: ...\n+def chain_operation_to_string(self: Operation, active_flags: int) -> str | None: ...\n+def chain_operation_serialized_size(self: Operation) -> int: ...\n+def chain_operation_code(self: Operation) -> int: ...\n+def chain_operation_data(self: Operation) -> bytes: ...\n+def chain_operation_opcode_from_size(size: int) -> int: ...\n+def chain_operation_minimal_opcode_from_data(data: bytes) -> int: ...\n+def chain_operation_nominal_opcode_from_data(data: bytes) -> int: ...\n+def chain_operation_opcode_from_positive(value: int) -> int: ...\n+def chain_operation_opcode_to_positive(code: int) -> int: ...\n+def chain_operation_is_push(code: int) -> bool: ...\n+def chain_operation_is_payload(code: int) -> bool: ...\n+def chain_operation_is_counted(code: int) -> bool: ...\n+def chain_operation_is_version(code: int) -> bool: ...\n+def chain_operation_is_numeric(code: int) -> bool: ...\n+def chain_operation_is_positive(code: int) -> bool: ...\n+def chain_operation_is_reserved(code: int) -> bool: ...\n+def chain_operation_is_disabled(code: int, active_flags: int) -> bool: ...\n+def chain_operation_is_conditional(code: int) -> bool: ...\n+def chain_operation_is_relaxed_push(code: int) -> bool: ...\n+def chain_operation_is_push_simple(self: Operation) -> bool: ...\n+def chain_operation_is_counted_simple(self: Operation) -> bool: ...\n+def chain_operation_is_version_simple(self: Operation) -> bool: ...\n+def chain_operation_is_positive_simple(self: Operation) -> bool: ...\n+def chain_operation_is_disabled_simple(self: Operation, active_flags: int) -> bool: ...\n+def chain_operation_is_conditional_simple(self: Operation) -> bool: ...\n+def chain_operation_is_relaxed_push_simple(self: Operation) -> bool: ...\n+def chain_operation_is_oversized(self: Operation, max_size: int) -> bool: ...\n+def chain_operation_is_minimal_push(self: Operation) -> bool: ...\n+def chain_operation_is_nominal_push(self: Operation) -> bool: ...\n+\n+# ─── OperationList (auto-generated, do not edit) ─────────────────────────\n+\n+class OperationList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::operation>`.\"\"\"\n+    ...\n+\n+def chain_operation_list_construct_default() -> OperationList: ...\n+def chain_operation_list_push_back(list: OperationList, elem: Operation) -> None: ...\n+def chain_operation_list_destruct(list: OperationList) -> None: ...\n+def chain_operation_list_count(list: OperationList) -> int: ...\n+def chain_operation_list_nth(list: OperationList, index: int) -> Operation: ...\n+def chain_operation_list_assign_at(list: OperationList, index: int, elem: Operation) -> None: ...\n+def chain_operation_list_erase(list: OperationList, index: int) -> None: ...\n+\n+# ─── HdPublic (auto-generated, do not edit) ─────────────────────────\n+\n+class HdPublic:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::hd_public`. Constructed by\n+    `wallet_hd_public_construct_*` and released by\n+    `wallet_hd_public_destruct`.\"\"\"\n+    ...\n+\n+def wallet_hd_public_construct_default() -> HdPublic: ...\n+def wallet_hd_public_construct_from_public_key(public_key: bytes) -> HdPublic: ...\n+def wallet_hd_public_construct_from_public_key_prefix(public_key: bytes, prefix: int) -> HdPublic: ...\n+def wallet_hd_public_construct_from_encoded(encoded: str) -> HdPublic: ...\n+def wallet_hd_public_construct_from_encoded_prefix(encoded: str, prefix: int) -> HdPublic: ...\n+def wallet_hd_public_copy(self: HdPublic) -> HdPublic: ...\n+def wallet_hd_public_destruct(self: HdPublic) -> None: ...\n+def wallet_hd_public_to_prefix(prefixes: int) -> int: ...\n+def wallet_hd_public_less(self: HdPublic, x: \"HdPublic\") -> bool: ...\n+def wallet_hd_public_equals(self: HdPublic, other: HdPublic) -> bool: ...\n+def wallet_hd_public_valid(self: HdPublic) -> bool: ...\n+def wallet_hd_public_encoded(self: HdPublic) -> str | None: ...\n+def wallet_hd_public_chain_code(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_lineage(self: HdPublic) -> object: ...\n+def wallet_hd_public_point(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_to_hd_key(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_derive_public(self: HdPublic, index: int) -> \"HdPublic\" | None: ...\n+\n+# ─── HdPrivate (auto-generated, do not edit) ─────────────────────────\n+\n+class HdPrivate:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::hd_private`. Constructed by\n+    `wallet_hd_private_construct_*` and released by\n+    `wallet_hd_private_destruct`.\"\"\"\n+    ...\n+\n+def wallet_hd_private_construct_default() -> HdPrivate: ...\n+def wallet_hd_private_construct_from_seed_prefixes(seed: bytes, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key(private_key: bytes) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key_prefixes(private_key: bytes, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key_prefix(private_key: bytes, prefix: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded(encoded: str) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded_prefixes(encoded: str, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded_prefix(encoded: str, prefix: int) -> HdPrivate: ...\n+def wallet_hd_private_copy(self: HdPrivate) -> HdPrivate: ...\n+def wallet_hd_private_destruct(self: HdPrivate) -> None: ...\n+def wallet_hd_private_to_prefix(prefixes: int) -> int: ...\n+def wallet_hd_private_less(self: HdPrivate, x: \"HdPrivate\") -> bool: ...\n+def wallet_hd_private_equals(self: HdPrivate, other: HdPrivate) -> bool: ...\n+def wallet_hd_private_encoded(self: HdPrivate) -> str | None: ...\n+def wallet_hd_private_secret(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_to_hd_key(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_to_public(self: HdPrivate) -> \"HdPublic\" | None: ...\n+def wallet_hd_private_derive_private(self: HdPrivate, index: int) -> \"HdPrivate\" | None: ...\n+def wallet_hd_private_derive_public(self: HdPrivate, index: int) -> \"HdPublic\" | None: ...\n+def wallet_hd_private_valid(self: HdPrivate) -> bool: ...\n+def wallet_hd_private_chain_code(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_lineage(self: HdPrivate) -> object: ...\n+def wallet_hd_private_point(self: HdPrivate) -> bytes: ...\n+\n+# ─── StealthCompact (auto-generated, do not edit) ─────────────────────────\n+\n+class StealthCompact:\n+    \"\"\"Opaque handle to a `kth::domain::chain::stealth_compact`. Constructed by\n+    `chain_stealth_compact_construct_*` and released by\n+    `chain_stealth_compact_destruct`.\"\"\"\n+    ...\n+\n+def chain_stealth_compact_copy(self: StealthCompact) -> StealthCompact: ...\n+def chain_stealth_compact_destruct(self: StealthCompact) -> None: ...\n+def chain_stealth_compact_ephemeral_public_key_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_ephemeral_public_key_hash(self: StealthCompact, value: bytes) -> None: ...\n+def chain_stealth_compact_public_key_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_public_key_hash(self: StealthCompact, value: bytes) -> None: ...\n+def chain_stealth_compact_transaction_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_transaction_hash(self: StealthCompact, value: bytes) -> None: ...\n+\n+# ─── StealthCompactList (auto-generated, do not edit) ─────────────────────────\n+\n+class StealthCompactList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::stealth_compact>`.\"\"\"\n+    ...\n+\n+def chain_stealth_compact_list_construct_default() -> StealthCompactList: ...\n+def chain_stealth_compact_list_push_back(list: StealthCompactList, elem: StealthCompact) -> None: ...\n+def chain_stealth_compact_list_destruct(list: StealthCompactList) -> None: ...\n+def chain_stealth_compact_list_count(list: StealthCompactList) -> int: ...\n+def chain_stealth_compact_list_nth(list: StealthCompactList, index: int) -> StealthCompact: ...\n+def chain_stealth_compact_list_assign_at(list: StealthCompactList, index: int, elem: StealthCompact) -> None: ...\n+def chain_stealth_compact_list_erase(list: StealthCompactList, index: int) -> None: ...\n+\n+# ─── HistoryCompact (auto-generated, do not edit) ─────────────────────────\n+\n+class HistoryCompact:\n+    \"\"\"Opaque handle to a `kth::domain::chain::history_compact`. Constructed by\n+    `chain_history_compact_construct_*` and released by\n+    `chain_history_compact_destruct`.\"\"\"\n+    ...\n+\n+def chain_history_compact_copy(self: HistoryCompact) -> HistoryCompact: ...\n+def chain_history_compact_destruct(self: HistoryCompact) -> None: ...\n+def chain_history_compact_kind(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_kind(self: HistoryCompact, value: int) -> None: ...\n+def chain_history_compact_point(self: HistoryCompact) -> \"Point\" | None: ...\n+def chain_history_compact_set_point(self: HistoryCompact, value: \"Point\") -> None: ...\n+def chain_history_compact_height(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_height(self: HistoryCompact, value: int) -> None: ...\n+def chain_history_compact_value_or_previous_checksum(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_value_or_previous_checksum(self: HistoryCompact, value: int) -> None: ...\n+\n+# ─── HistoryCompactList (auto-generated, do not edit) ─────────────────────────\n+\n+class HistoryCompactList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::history_compact>`.\"\"\"\n+    ...\n+\n+def chain_history_compact_list_construct_default() -> HistoryCompactList: ...\n+def chain_history_compact_list_push_back(list: HistoryCompactList, elem: HistoryCompact) -> None: ...\n+def chain_history_compact_list_destruct(list: HistoryCompactList) -> None: ...\n+def chain_history_compact_list_count(list: HistoryCompactList) -> int: ...\n+def chain_history_compact_list_nth(list: HistoryCompactList, index: int) -> HistoryCompact: ...\n+def chain_history_compact_list_assign_at(list: HistoryCompactList, index: int, elem: HistoryCompact) -> None: ...\n+def chain_history_compact_list_erase(list: HistoryCompactList, index: int) -> None: ...\n+\n+# ─── DoubleSpendProof (auto-generated, do not edit) ─────────────────────────\n+\n+class DoubleSpendProof:\n+    \"\"\"Opaque handle to a `kth::domain::message::double_spend_proof`. Constructed by\n+    `chain_double_spend_proof_construct_*` and released by\n+    `chain_double_spend_proof_destruct`.\"\"\"\n+    ...\n+\n+def chain_double_spend_proof_construct_default() -> DoubleSpendProof: ...\n+def chain_double_spend_proof_construct_from_data(reader: bytes, version: int) -> DoubleSpendProof: ...\n+def chain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1: \"DoubleSpendProofSpender\", spender2: \"DoubleSpendProofSpender\") -> DoubleSpendProof: ...\n+def chain_double_spend_proof_copy(self: DoubleSpendProof) -> DoubleSpendProof: ...\n+def chain_double_spend_proof_destruct(self: DoubleSpendProof) -> None: ...\n+def chain_double_spend_proof_equals(self: DoubleSpendProof, other: DoubleSpendProof) -> bool: ...\n+def chain_double_spend_proof_out_point(self: DoubleSpendProof) -> \"OutputPoint\" | None: ...\n+def chain_double_spend_proof_set_out_point(self: DoubleSpendProof, x: \"OutputPoint\") -> None: ...\n+def chain_double_spend_proof_spender1(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n+def chain_double_spend_proof_set_spender1(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...\n+def chain_double_spend_proof_spender2(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n+def chain_double_spend_proof_set_spender2(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...\n+def chain_double_spend_proof_to_data(self: DoubleSpendProof, version: int) -> bytes: ...\n+def chain_double_spend_proof_is_valid(self: DoubleSpendProof) -> bool: ...\n+def chain_double_spend_proof_reset(self: DoubleSpendProof) -> None: ...\n+def chain_double_spend_proof_serialized_size(self: DoubleSpendProof, version: int) -> int: ...\n+def chain_double_spend_proof_hash(self: DoubleSpendProof) -> bytes: ...\n+\n+# ─── Spender (auto-generated, do not edit) ─────────────────────────\n+\n+class Spender:\n+    \"\"\"Opaque handle to a `kth::domain::message::double_spend_proof::spender`. Constructed by\n+    `chain_double_spend_proof_spender_construct_*` and released by\n+    `chain_double_spend_proof_spender_destruct`.\"\"\"\n+    ...\n+\n+def chain_double_spend_proof_spender_construct_from_data(reader: bytes, version: int) -> Spender: ...\n+def chain_double_spend_proof_spender_copy(self: Spender) -> Spender: ...\n+def chain_double_spend_proof_spender_destruct(self: Spender) -> None: ...\n+def chain_double_spend_proof_spender_version(self: Spender) -> int: ...\n+def chain_double_spend_proof_spender_set_version(self: Spender, value: int) -> None: ...\n+def chain_double_spend_proof_spender_out_sequence(self: Spender) -> int: ...\n+def chain_double_spend_proof_spender_set_out_sequence(self: Spender, value: int) -> None: ...\n+def chain_double_spend_proof_spender_locktime(self: Spender) -> int: ...\n+def chain_double_spend_proof_spender_set_locktime(self: Spender, value: int) -> None: ...\n+def chain_double_spend_proof_spender_prev_outs_hash(self: Spender) -> bytes: ...\n+def chain_double_spend_proof_spender_set_prev_outs_hash(self: Spender, value: bytes) -> None: ...\n+def chain_double_spend_proof_spender_sequence_hash(self: Spender) -> bytes: ...\n+def chain_double_spend_proof_spender_set_sequence_hash(self: Spender, value: bytes) -> None: ...\n+def chain_double_spend_proof_spender_outputs_hash(self: Spender) -> bytes: ...\n+def chain_double_spend_proof_spender_set_outputs_hash(self: Spender, value: bytes) -> None: ...\n+def chain_double_spend_proof_spender_push_data(self: Spender) -> bytes: ...\n+def chain_double_spend_proof_spender_set_push_data(self: Spender, value: bytes) -> None: ...\n+def chain_double_spend_proof_spender_is_valid(self: Spender) -> bool: ...\n+def chain_double_spend_proof_spender_reset(self: Spender) -> None: ...\n+def chain_double_spend_proof_spender_equals(self: Spender, other: Spender) -> bool: ...\n+def chain_double_spend_proof_spender_serialized_size(self: Spender) -> int: ...\n+\n+# ─── TokenDataT (auto-generated, do not edit) ─────────────────────────\n+\n+class TokenDataT:\n+    \"\"\"Opaque handle to a `kth::domain::chain::token_data_t`. Constructed by\n+    `chain_token_data_construct_*` and released by\n+    `chain_token_data_destruct`.\"\"\"\n+    ...\n+\n+def chain_token_data_construct_from_data(reader: bytes) -> TokenDataT: ...\n+def chain_token_data_copy(self: TokenDataT) -> TokenDataT: ...\n+def chain_token_data_destruct(self: TokenDataT) -> None: ...\n+def chain_token_data_id(self: TokenDataT) -> bytes: ...\n+def chain_token_data_set_id(self: TokenDataT, value: bytes) -> None: ...\n+def chain_token_data_equals(self: TokenDataT, other: TokenDataT) -> bool: ...\n+def chain_token_data_make_fungible(id: bytes, amount: int) -> \"TokenData\" | None: ...\n+def chain_token_data_make_non_fungible(id: bytes, capability: int, commitment: bytes) -> \"TokenData\" | None: ...\n+def chain_token_data_make_both(id: bytes, amount: int, capability: int, commitment: bytes) -> \"TokenData\" | None: ...","path":"kth_native.pyi","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Verify unresolved token-data type references in the stub.\n# Expect: no \"TokenData\" return annotations unless a matching class is declared.\nrg -n '\"TokenData\"|class TokenData\\b|class TokenDataT\\b' kth_native.pyi\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 414\n\n---\n\n\n\n</details>\n\n**Change return type annotations from `TokenData` to `TokenDataT` in three factory functions.**\n\nThe stub file declares `TokenDataT` at line 1412, but `chain_token_data_make_fungible`, `chain_token_data_make_non_fungible`, and `chain_token_data_make_both` incorrectly return `\"TokenData\"` (lines 1424–1426), which is not declared anywhere. Update all three to return `\"TokenDataT\"` instead.\n\n<details>\n<summary>Fix</summary>\n\n```diff\n-def chain_token_data_make_fungible(id: bytes, amount: int) -> \"TokenData\" | None: ...\n-def chain_token_data_make_non_fungible(id: bytes, capability: int, commitment: bytes) -> \"TokenData\" | None: ...\n-def chain_token_data_make_both(id: bytes, amount: int, capability: int, commitment: bytes) -> \"TokenData\" | None: ...\n+def chain_token_data_make_fungible(id: bytes, amount: int) -> \"TokenDataT\" | None: ...\n+def chain_token_data_make_non_fungible(id: bytes, capability: int, commitment: bytes) -> \"TokenDataT\" | None: ...\n+def chain_token_data_make_both(id: bytes, amount: int, capability: int, commitment: bytes) -> \"TokenDataT\" | None: ...\n```\n</details>\n\n<!-- suggestion_start -->\n\n<details>\n<summary>📝 Committable suggestion</summary>\n\n> ‼️ **IMPORTANT**\n> 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.\n\n```suggestion\ndef chain_token_data_construct_from_data(reader: bytes) -> TokenDataT: ...\ndef chain_token_data_copy(self: TokenDataT) -> TokenDataT: ...\ndef chain_token_data_destruct(self: TokenDataT) -> None: ...\ndef chain_token_data_id(self: TokenDataT) -> bytes: ...\ndef chain_token_data_set_id(self: TokenDataT, value: bytes) -> None: ...\ndef chain_token_data_equals(self: TokenDataT, other: TokenDataT) -> bool: ...\ndef chain_token_data_make_fungible(id: bytes, amount: int) -> \"TokenDataT\" | None: ...\ndef chain_token_data_make_non_fungible(id: bytes, capability: int, commitment: bytes) -> \"TokenDataT\" | None: ...\ndef chain_token_data_make_both(id: bytes, amount: int, capability: int, commitment: bytes) -> \"TokenDataT\" | None: ...\n```\n\n</details>\n\n<!-- suggestion_end -->\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@kth_native.pyi` around lines 1418 - 1426, Update the return type annotations\nof the three factory functions so they reference the declared type token alias\nTokenDataT: change chain_token_data_make_fungible,\nchain_token_data_make_non_fungible, and chain_token_data_make_both to return\n\"TokenDataT\" (instead of the nonexistent \"TokenData\") so the stub aligns with\nthe declared TokenDataT at/around the TokenDataT declaration.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:ibis -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:49Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461813","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461813"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461813"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461813/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":584,"position":584,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:49Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}},{"id":"8627489805","type":"PullRequestReviewCommentEvent","actor":{"id":136622811,"login":"coderabbitai[bot]","display_login":"coderabbitai","gravatar_id":"","url":"https://api.github.com/users/coderabbitai[bot]","avatar_url":"https://avatars.githubusercontent.com/u/136622811?"},"repo":{"id":237133197,"name":"k-nuth/py-native","url":"https://api.github.com/repos/k-nuth/py-native"},"payload":{"action":"created","comment":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461807","pull_request_review_id":4146628261,"id":3116461807,"node_id":"PRRC_kwDODiJdjc65wW7v","diff_hunk":"@@ -885,4 +890,602 @@ def chain_transaction_list_count(list: TransactionList) -> int: ...\n def chain_transaction_list_nth(list: TransactionList, index: int) -> Transaction: ...\n def chain_transaction_list_assign_at(list: TransactionList, index: int, elem: Transaction) -> None: ...\n def chain_transaction_list_erase(list: TransactionList, index: int) -> None: ...\n+\n+# ─── Binary (auto-generated, do not edit) ─────────────────────────\n+\n+class Binary:\n+    \"\"\"Opaque handle to a `kth::binary`. Constructed by\n+    `core_binary_construct_*` and released by\n+    `core_binary_destruct`.\"\"\"\n+    ...\n+\n+def core_binary_construct_default() -> Binary: ...\n+def core_binary_construct_from_bit_string(bit_string: str) -> Binary: ...\n+def core_binary_construct_from_size_number(size: int, number: int) -> Binary: ...\n+def core_binary_construct_from_size_blocks(size: int, blocks: bytes) -> Binary: ...\n+def core_binary_copy(self: Binary) -> Binary: ...\n+def core_binary_destruct(self: Binary) -> None: ...\n+def core_binary_blocks_size(bit_size: int) -> int: ...\n+def core_binary_is_base2(text: str) -> bool: ...\n+def core_binary_resize(self: Binary, size: int) -> None: ...\n+def core_binary_at(self: Binary, index: int) -> bool: ...\n+def core_binary_blocks(self: Binary) -> bytes: ...\n+def core_binary_encoded(self: Binary) -> str | None: ...\n+def core_binary_size(self: Binary) -> int: ...\n+def core_binary_append(self: Binary, post: \"Binary\") -> None: ...\n+def core_binary_prepend(self: Binary, prior: \"Binary\") -> None: ...\n+def core_binary_shift_left(self: Binary, distance: int) -> None: ...\n+def core_binary_shift_right(self: Binary, distance: int) -> None: ...\n+def core_binary_substring(self: Binary, start: int, length: int) -> \"Binary\" | None: ...\n+def core_binary_is_prefix_of_span(self: Binary, field: bytes) -> bool: ...\n+def core_binary_is_prefix_of_uint32(self: Binary, field: int) -> bool: ...\n+def core_binary_is_prefix_of_binary(self: Binary, field: \"Binary\") -> bool: ...\n+def core_binary_equals(self: Binary, other: Binary) -> bool: ...\n+def core_binary_less(self: Binary, x: \"Binary\") -> bool: ...\n+\n+# ─── GetBlocks (auto-generated, do not edit) ─────────────────────────\n+\n+class GetBlocks:\n+    \"\"\"Opaque handle to a `kth::domain::message::get_blocks`. Constructed by\n+    `chain_get_blocks_construct_*` and released by\n+    `chain_get_blocks_destruct`.\"\"\"\n+    ...\n+\n+def chain_get_blocks_construct_default() -> GetBlocks: ...\n+def chain_get_blocks_construct_from_data(reader: bytes, version: int) -> GetBlocks: ...\n+def chain_get_blocks_construct(start: object, stop: bytes) -> GetBlocks: ...\n+def chain_get_blocks_copy(self: GetBlocks) -> GetBlocks: ...\n+def chain_get_blocks_destruct(self: GetBlocks) -> None: ...\n+def chain_get_blocks_equals(self: GetBlocks, other: GetBlocks) -> bool: ...\n+def chain_get_blocks_start_hashes(self: GetBlocks) -> object: ...\n+def chain_get_blocks_set_start_hashes(self: GetBlocks, value: object) -> None: ...\n+def chain_get_blocks_stop_hash(self: GetBlocks) -> bytes: ...\n+def chain_get_blocks_set_stop_hash(self: GetBlocks, value: bytes) -> None: ...\n+def chain_get_blocks_to_data(self: GetBlocks, version: int) -> bytes: ...\n+def chain_get_blocks_is_valid(self: GetBlocks) -> bool: ...\n+def chain_get_blocks_reset(self: GetBlocks) -> None: ...\n+def chain_get_blocks_serialized_size(self: GetBlocks, version: int) -> int: ...\n+\n+# ─── GetHeaders (auto-generated, do not edit) ─────────────────────────\n+\n+class GetHeaders:\n+    \"\"\"Opaque handle to a `kth::domain::message::get_headers`. Constructed by\n+    `chain_get_headers_construct_*` and released by\n+    `chain_get_headers_destruct`.\"\"\"\n+    ...\n+\n+def chain_get_headers_construct_default() -> GetHeaders: ...\n+def chain_get_headers_construct_from_data(reader: bytes, version: int) -> GetHeaders: ...\n+def chain_get_headers_construct(start: object, stop: bytes) -> GetHeaders: ...\n+def chain_get_headers_copy(self: GetHeaders) -> GetHeaders: ...\n+def chain_get_headers_destruct(self: GetHeaders) -> None: ...\n+def chain_get_headers_equals(self: GetHeaders, other: GetHeaders) -> bool: ...\n+def chain_get_headers_start_hashes(self: GetHeaders) -> object: ...\n+def chain_get_headers_set_start_hashes(self: GetHeaders, value: object) -> None: ...\n+def chain_get_headers_stop_hash(self: GetHeaders) -> bytes: ...\n+def chain_get_headers_set_stop_hash(self: GetHeaders, value: bytes) -> None: ...\n+def chain_get_headers_to_data(self: GetHeaders, version: int) -> bytes: ...\n+def chain_get_headers_is_valid(self: GetHeaders) -> bool: ...\n+def chain_get_headers_reset(self: GetHeaders) -> None: ...\n+def chain_get_headers_serialized_size(self: GetHeaders, version: int) -> int: ...\n+\n+# ─── MerkleBlock (auto-generated, do not edit) ─────────────────────────\n+\n+class MerkleBlock:\n+    \"\"\"Opaque handle to a `kth::domain::message::merkle_block`. Constructed by\n+    `chain_merkle_block_construct_*` and released by\n+    `chain_merkle_block_destruct`.\"\"\"\n+    ...\n+\n+def chain_merkle_block_construct_default() -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_data(reader: bytes, version: int) -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_header_total_transactions_hashes_flags(header: \"Header\", total_transactions: int, hashes: object, flags: bytes) -> MerkleBlock: ...\n+def chain_merkle_block_construct_from_block(block: \"Block\") -> MerkleBlock: ...\n+def chain_merkle_block_copy(self: MerkleBlock) -> MerkleBlock: ...\n+def chain_merkle_block_destruct(self: MerkleBlock) -> None: ...\n+def chain_merkle_block_equals(self: MerkleBlock, other: MerkleBlock) -> bool: ...\n+def chain_merkle_block_header(self: MerkleBlock) -> \"Header\" | None: ...\n+def chain_merkle_block_set_header(self: MerkleBlock, value: \"Header\") -> None: ...\n+def chain_merkle_block_total_transactions(self: MerkleBlock) -> int: ...\n+def chain_merkle_block_set_total_transactions(self: MerkleBlock, value: int) -> None: ...\n+def chain_merkle_block_hashes(self: MerkleBlock) -> object: ...\n+def chain_merkle_block_set_hashes(self: MerkleBlock, value: object) -> None: ...\n+def chain_merkle_block_flags(self: MerkleBlock) -> bytes: ...\n+def chain_merkle_block_set_flags(self: MerkleBlock, value: bytes) -> None: ...\n+def chain_merkle_block_to_data(self: MerkleBlock, version: int) -> bytes: ...\n+def chain_merkle_block_is_valid(self: MerkleBlock) -> bool: ...\n+def chain_merkle_block_reset(self: MerkleBlock) -> None: ...\n+def chain_merkle_block_serialized_size(self: MerkleBlock, version: int) -> int: ...\n+\n+# ─── PrefilledTransaction (auto-generated, do not edit) ─────────────────────────\n+\n+class PrefilledTransaction:\n+    \"\"\"Opaque handle to a `kth::domain::message::prefilled_transaction`. Constructed by\n+    `chain_prefilled_transaction_construct_*` and released by\n+    `chain_prefilled_transaction_destruct`.\"\"\"\n+    ...\n+\n+def chain_prefilled_transaction_construct_default() -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_construct_from_data(reader: bytes, version: int) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_construct(index: int, tx: \"Transaction\") -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_copy(self: PrefilledTransaction) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_destruct(self: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_equals(self: PrefilledTransaction, other: PrefilledTransaction) -> bool: ...\n+def chain_prefilled_transaction_index(self: PrefilledTransaction) -> int: ...\n+def chain_prefilled_transaction_set_index(self: PrefilledTransaction, value: int) -> None: ...\n+def chain_prefilled_transaction_transaction(self: PrefilledTransaction) -> \"Transaction\" | None: ...\n+def chain_prefilled_transaction_set_transaction(self: PrefilledTransaction, tx: \"Transaction\") -> None: ...\n+def chain_prefilled_transaction_to_data(self: PrefilledTransaction, version: int) -> bytes: ...\n+def chain_prefilled_transaction_is_valid(self: PrefilledTransaction) -> bool: ...\n+def chain_prefilled_transaction_reset(self: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_serialized_size(self: PrefilledTransaction, version: int) -> int: ...\n+\n+# ─── PrefilledTransactionList (auto-generated, do not edit) ─────────────────────────\n+\n+class PrefilledTransactionList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::prefilled_transaction>`.\"\"\"\n+    ...\n+\n+def chain_prefilled_transaction_list_construct_default() -> PrefilledTransactionList: ...\n+def chain_prefilled_transaction_list_push_back(list: PrefilledTransactionList, elem: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_list_destruct(list: PrefilledTransactionList) -> None: ...\n+def chain_prefilled_transaction_list_count(list: PrefilledTransactionList) -> int: ...\n+def chain_prefilled_transaction_list_nth(list: PrefilledTransactionList, index: int) -> PrefilledTransaction: ...\n+def chain_prefilled_transaction_list_assign_at(list: PrefilledTransactionList, index: int, elem: PrefilledTransaction) -> None: ...\n+def chain_prefilled_transaction_list_erase(list: PrefilledTransactionList, index: int) -> None: ...\n+\n+# ─── CompactBlock (auto-generated, do not edit) ─────────────────────────\n+\n+class CompactBlock:\n+    \"\"\"Opaque handle to a `kth::domain::message::compact_block`. Constructed by\n+    `chain_compact_block_construct_*` and released by\n+    `chain_compact_block_destruct`.\"\"\"\n+    ...\n+\n+def chain_compact_block_construct_default() -> CompactBlock: ...\n+def chain_compact_block_construct_from_data(reader: bytes, version: int) -> CompactBlock: ...\n+def chain_compact_block_construct(header: \"Header\", nonce: int, short_ids: object, transactions: object) -> CompactBlock: ...\n+def chain_compact_block_copy(self: CompactBlock) -> CompactBlock: ...\n+def chain_compact_block_destruct(self: CompactBlock) -> None: ...\n+def chain_compact_block_equals(self: CompactBlock, other: CompactBlock) -> bool: ...\n+def chain_compact_block_header(self: CompactBlock) -> \"Header\" | None: ...\n+def chain_compact_block_set_header(self: CompactBlock, value: \"Header\") -> None: ...\n+def chain_compact_block_nonce(self: CompactBlock) -> int: ...\n+def chain_compact_block_set_nonce(self: CompactBlock, value: int) -> None: ...\n+def chain_compact_block_short_ids(self: CompactBlock) -> object: ...\n+def chain_compact_block_set_short_ids(self: CompactBlock, value: object) -> None: ...\n+def chain_compact_block_transactions(self: CompactBlock) -> object: ...\n+def chain_compact_block_set_transactions(self: CompactBlock, value: object) -> None: ...\n+def chain_compact_block_to_data(self: CompactBlock, version: int) -> bytes: ...\n+def chain_compact_block_is_valid(self: CompactBlock) -> bool: ...\n+def chain_compact_block_reset(self: CompactBlock) -> None: ...\n+def chain_compact_block_serialized_size(self: CompactBlock, version: int) -> int: ...\n+\n+# ─── PaymentAddress (auto-generated, do not edit) ─────────────────────────\n+\n+class PaymentAddress:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::payment_address`. Constructed by\n+    `wallet_payment_address_construct_*` and released by\n+    `wallet_payment_address_destruct`.\"\"\"\n+    ...\n+\n+def wallet_payment_address_construct_default() -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_decoded(decoded: bytes) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_ec_private(secret: \"EcPrivate\") -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_address(address: str) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_address_net(address: str, net: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_short_hash_version(short_hash: bytes, version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_hash_version(hash: bytes, version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_ec_public_version(point: \"EcPublic\", version: int) -> PaymentAddress: ...\n+def wallet_payment_address_construct_from_script_version(script: \"Script\", version: int) -> PaymentAddress: ...\n+def wallet_payment_address_copy(self: PaymentAddress) -> PaymentAddress: ...\n+def wallet_payment_address_destruct(self: PaymentAddress) -> None: ...\n+def wallet_payment_address_cashaddr_prefix_for(net: int) -> str | None: ...\n+def wallet_payment_address_from_pay_public_key_hash_script(script: \"Script\", version: int) -> \"PaymentAddress\" | None: ...\n+def wallet_payment_address_equals(self: PaymentAddress, other: PaymentAddress) -> bool: ...\n+def wallet_payment_address_less(self: PaymentAddress, x: \"PaymentAddress\") -> bool: ...\n+def wallet_payment_address_valid(self: PaymentAddress) -> bool: ...\n+def wallet_payment_address_encoded_legacy(self: PaymentAddress) -> str | None: ...\n+def wallet_payment_address_encoded_cashaddr(self: PaymentAddress, token_aware: bool) -> str | None: ...\n+def wallet_payment_address_encoded_token(self: PaymentAddress) -> str | None: ...\n+def wallet_payment_address_version(self: PaymentAddress) -> int: ...\n+def wallet_payment_address_hash_span(self: PaymentAddress) -> object: ...\n+def wallet_payment_address_hash20(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_hash32(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_to_payment(self: PaymentAddress) -> bytes: ...\n+def wallet_payment_address_extract(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+def wallet_payment_address_extract_input(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+def wallet_payment_address_extract_output(script: \"Script\", p2kh_version: int, p2sh_version: int) -> object: ...\n+\n+# ─── PaymentAddressList (auto-generated, do not edit) ─────────────────────────\n+\n+class PaymentAddressList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::payment_address>`.\"\"\"\n+    ...\n+\n+def chain_payment_address_list_construct_default() -> PaymentAddressList: ...\n+def chain_payment_address_list_push_back(list: PaymentAddressList, elem: PaymentAddress) -> None: ...\n+def chain_payment_address_list_destruct(list: PaymentAddressList) -> None: ...\n+def chain_payment_address_list_count(list: PaymentAddressList) -> int: ...\n+def chain_payment_address_list_nth(list: PaymentAddressList, index: int) -> PaymentAddress: ...\n+def chain_payment_address_list_assign_at(list: PaymentAddressList, index: int, elem: PaymentAddress) -> None: ...\n+def chain_payment_address_list_erase(list: PaymentAddressList, index: int) -> None: ...\n+\n+# ─── EcPublic (auto-generated, do not edit) ─────────────────────────\n+\n+class EcPublic:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::ec_public`. Constructed by\n+    `wallet_ec_public_construct_*` and released by\n+    `wallet_ec_public_destruct`.\"\"\"\n+    ...\n+\n+def wallet_ec_public_construct_default() -> EcPublic: ...\n+def wallet_ec_public_construct_from_ec_private(secret: \"EcPrivate\") -> EcPublic: ...\n+def wallet_ec_public_construct_from_decoded(decoded: bytes) -> EcPublic: ...\n+def wallet_ec_public_construct_from_base16(base16: str) -> EcPublic: ...\n+def wallet_ec_public_construct_from_compressed_point_compress(compressed_point: bytes, compress: bool) -> EcPublic: ...\n+def wallet_ec_public_construct_from_uncompressed_point_compress(uncompressed_point: bytes, compress: bool) -> EcPublic: ...\n+def wallet_ec_public_copy(self: EcPublic) -> EcPublic: ...\n+def wallet_ec_public_destruct(self: EcPublic) -> None: ...\n+def wallet_ec_public_equals(self: EcPublic, other: EcPublic) -> bool: ...\n+def wallet_ec_public_less(self: EcPublic, x: \"EcPublic\") -> bool: ...\n+def wallet_ec_public_valid(self: EcPublic) -> bool: ...\n+def wallet_ec_public_encoded(self: EcPublic) -> str | None: ...\n+def wallet_ec_public_point(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_compressed(self: EcPublic) -> bool: ...\n+def wallet_ec_public_to_data(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_to_uncompressed(self: EcPublic) -> bytes: ...\n+def wallet_ec_public_to_payment_address(self: EcPublic, version: int) -> \"PaymentAddress\" | None: ...\n+\n+# ─── EcPrivate (auto-generated, do not edit) ─────────────────────────\n+\n+class EcPrivate:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::ec_private`. Constructed by\n+    `wallet_ec_private_construct_*` and released by\n+    `wallet_ec_private_destruct`.\"\"\"\n+    ...\n+\n+def wallet_ec_private_construct_default() -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_version(wif: str, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_compressed_version(wif_compressed: bytes, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_wif_uncompressed_version(wif_uncompressed: bytes, version: int) -> EcPrivate: ...\n+def wallet_ec_private_construct_from_secret_version_compress(secret: bytes, version: int, compress: bool) -> EcPrivate: ...\n+def wallet_ec_private_copy(self: EcPrivate) -> EcPrivate: ...\n+def wallet_ec_private_destruct(self: EcPrivate) -> None: ...\n+def wallet_ec_private_to_address_prefix(version: int) -> int: ...\n+def wallet_ec_private_to_wif_prefix(version: int) -> int: ...\n+def wallet_ec_private_to_version(address: int, wif: int) -> int: ...\n+def wallet_ec_private_equals(self: EcPrivate, other: EcPrivate) -> bool: ...\n+def wallet_ec_private_less(self: EcPrivate, x: \"EcPrivate\") -> bool: ...\n+def wallet_ec_private_valid(self: EcPrivate) -> bool: ...\n+def wallet_ec_private_encoded(self: EcPrivate) -> str | None: ...\n+def wallet_ec_private_secret(self: EcPrivate) -> bytes: ...\n+def wallet_ec_private_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_payment_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_wif_version(self: EcPrivate) -> int: ...\n+def wallet_ec_private_compressed(self: EcPrivate) -> bool: ...\n+def wallet_ec_private_to_public(self: EcPrivate) -> \"EcPublic\" | None: ...\n+def wallet_ec_private_to_payment_address(self: EcPrivate) -> \"PaymentAddress\" | None: ...\n+\n+# ─── Operation (auto-generated, do not edit) ─────────────────────────\n+\n+class Operation:\n+    \"\"\"Opaque handle to a `kth::domain::machine::operation`. Constructed by\n+    `chain_operation_construct_*` and released by\n+    `chain_operation_destruct`.\"\"\"\n+    ...\n+\n+def chain_operation_construct_default() -> Operation: ...\n+def chain_operation_construct_from_data(reader: bytes) -> Operation: ...\n+def chain_operation_construct_from_uncoded_minimal(uncoded: bytes, minimal: bool) -> Operation: ...\n+def chain_operation_construct_from_code(code: int) -> Operation: ...\n+def chain_operation_copy(self: Operation) -> Operation: ...\n+def chain_operation_destruct(self: Operation) -> None: ...\n+def chain_operation_equals(self: Operation, other: Operation) -> bool: ...\n+def chain_operation_from_string(self: Operation, mnemonic: str) -> bool: ...\n+def chain_operation_is_valid(self: Operation) -> bool: ...\n+def chain_operation_to_data(self: Operation) -> bytes: ...\n+def chain_operation_to_string(self: Operation, active_flags: int) -> str | None: ...\n+def chain_operation_serialized_size(self: Operation) -> int: ...\n+def chain_operation_code(self: Operation) -> int: ...\n+def chain_operation_data(self: Operation) -> bytes: ...\n+def chain_operation_opcode_from_size(size: int) -> int: ...\n+def chain_operation_minimal_opcode_from_data(data: bytes) -> int: ...\n+def chain_operation_nominal_opcode_from_data(data: bytes) -> int: ...\n+def chain_operation_opcode_from_positive(value: int) -> int: ...\n+def chain_operation_opcode_to_positive(code: int) -> int: ...\n+def chain_operation_is_push(code: int) -> bool: ...\n+def chain_operation_is_payload(code: int) -> bool: ...\n+def chain_operation_is_counted(code: int) -> bool: ...\n+def chain_operation_is_version(code: int) -> bool: ...\n+def chain_operation_is_numeric(code: int) -> bool: ...\n+def chain_operation_is_positive(code: int) -> bool: ...\n+def chain_operation_is_reserved(code: int) -> bool: ...\n+def chain_operation_is_disabled(code: int, active_flags: int) -> bool: ...\n+def chain_operation_is_conditional(code: int) -> bool: ...\n+def chain_operation_is_relaxed_push(code: int) -> bool: ...\n+def chain_operation_is_push_simple(self: Operation) -> bool: ...\n+def chain_operation_is_counted_simple(self: Operation) -> bool: ...\n+def chain_operation_is_version_simple(self: Operation) -> bool: ...\n+def chain_operation_is_positive_simple(self: Operation) -> bool: ...\n+def chain_operation_is_disabled_simple(self: Operation, active_flags: int) -> bool: ...\n+def chain_operation_is_conditional_simple(self: Operation) -> bool: ...\n+def chain_operation_is_relaxed_push_simple(self: Operation) -> bool: ...\n+def chain_operation_is_oversized(self: Operation, max_size: int) -> bool: ...\n+def chain_operation_is_minimal_push(self: Operation) -> bool: ...\n+def chain_operation_is_nominal_push(self: Operation) -> bool: ...\n+\n+# ─── OperationList (auto-generated, do not edit) ─────────────────────────\n+\n+class OperationList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::operation>`.\"\"\"\n+    ...\n+\n+def chain_operation_list_construct_default() -> OperationList: ...\n+def chain_operation_list_push_back(list: OperationList, elem: Operation) -> None: ...\n+def chain_operation_list_destruct(list: OperationList) -> None: ...\n+def chain_operation_list_count(list: OperationList) -> int: ...\n+def chain_operation_list_nth(list: OperationList, index: int) -> Operation: ...\n+def chain_operation_list_assign_at(list: OperationList, index: int, elem: Operation) -> None: ...\n+def chain_operation_list_erase(list: OperationList, index: int) -> None: ...\n+\n+# ─── HdPublic (auto-generated, do not edit) ─────────────────────────\n+\n+class HdPublic:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::hd_public`. Constructed by\n+    `wallet_hd_public_construct_*` and released by\n+    `wallet_hd_public_destruct`.\"\"\"\n+    ...\n+\n+def wallet_hd_public_construct_default() -> HdPublic: ...\n+def wallet_hd_public_construct_from_public_key(public_key: bytes) -> HdPublic: ...\n+def wallet_hd_public_construct_from_public_key_prefix(public_key: bytes, prefix: int) -> HdPublic: ...\n+def wallet_hd_public_construct_from_encoded(encoded: str) -> HdPublic: ...\n+def wallet_hd_public_construct_from_encoded_prefix(encoded: str, prefix: int) -> HdPublic: ...\n+def wallet_hd_public_copy(self: HdPublic) -> HdPublic: ...\n+def wallet_hd_public_destruct(self: HdPublic) -> None: ...\n+def wallet_hd_public_to_prefix(prefixes: int) -> int: ...\n+def wallet_hd_public_less(self: HdPublic, x: \"HdPublic\") -> bool: ...\n+def wallet_hd_public_equals(self: HdPublic, other: HdPublic) -> bool: ...\n+def wallet_hd_public_valid(self: HdPublic) -> bool: ...\n+def wallet_hd_public_encoded(self: HdPublic) -> str | None: ...\n+def wallet_hd_public_chain_code(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_lineage(self: HdPublic) -> object: ...\n+def wallet_hd_public_point(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_to_hd_key(self: HdPublic) -> bytes: ...\n+def wallet_hd_public_derive_public(self: HdPublic, index: int) -> \"HdPublic\" | None: ...\n+\n+# ─── HdPrivate (auto-generated, do not edit) ─────────────────────────\n+\n+class HdPrivate:\n+    \"\"\"Opaque handle to a `kth::domain::wallet::hd_private`. Constructed by\n+    `wallet_hd_private_construct_*` and released by\n+    `wallet_hd_private_destruct`.\"\"\"\n+    ...\n+\n+def wallet_hd_private_construct_default() -> HdPrivate: ...\n+def wallet_hd_private_construct_from_seed_prefixes(seed: bytes, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key(private_key: bytes) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key_prefixes(private_key: bytes, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_private_key_prefix(private_key: bytes, prefix: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded(encoded: str) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded_prefixes(encoded: str, prefixes: int) -> HdPrivate: ...\n+def wallet_hd_private_construct_from_encoded_prefix(encoded: str, prefix: int) -> HdPrivate: ...\n+def wallet_hd_private_copy(self: HdPrivate) -> HdPrivate: ...\n+def wallet_hd_private_destruct(self: HdPrivate) -> None: ...\n+def wallet_hd_private_to_prefix(prefixes: int) -> int: ...\n+def wallet_hd_private_less(self: HdPrivate, x: \"HdPrivate\") -> bool: ...\n+def wallet_hd_private_equals(self: HdPrivate, other: HdPrivate) -> bool: ...\n+def wallet_hd_private_encoded(self: HdPrivate) -> str | None: ...\n+def wallet_hd_private_secret(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_to_hd_key(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_to_public(self: HdPrivate) -> \"HdPublic\" | None: ...\n+def wallet_hd_private_derive_private(self: HdPrivate, index: int) -> \"HdPrivate\" | None: ...\n+def wallet_hd_private_derive_public(self: HdPrivate, index: int) -> \"HdPublic\" | None: ...\n+def wallet_hd_private_valid(self: HdPrivate) -> bool: ...\n+def wallet_hd_private_chain_code(self: HdPrivate) -> bytes: ...\n+def wallet_hd_private_lineage(self: HdPrivate) -> object: ...\n+def wallet_hd_private_point(self: HdPrivate) -> bytes: ...\n+\n+# ─── StealthCompact (auto-generated, do not edit) ─────────────────────────\n+\n+class StealthCompact:\n+    \"\"\"Opaque handle to a `kth::domain::chain::stealth_compact`. Constructed by\n+    `chain_stealth_compact_construct_*` and released by\n+    `chain_stealth_compact_destruct`.\"\"\"\n+    ...\n+\n+def chain_stealth_compact_copy(self: StealthCompact) -> StealthCompact: ...\n+def chain_stealth_compact_destruct(self: StealthCompact) -> None: ...\n+def chain_stealth_compact_ephemeral_public_key_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_ephemeral_public_key_hash(self: StealthCompact, value: bytes) -> None: ...\n+def chain_stealth_compact_public_key_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_public_key_hash(self: StealthCompact, value: bytes) -> None: ...\n+def chain_stealth_compact_transaction_hash(self: StealthCompact) -> bytes: ...\n+def chain_stealth_compact_set_transaction_hash(self: StealthCompact, value: bytes) -> None: ...\n+\n+# ─── StealthCompactList (auto-generated, do not edit) ─────────────────────────\n+\n+class StealthCompactList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::stealth_compact>`.\"\"\"\n+    ...\n+\n+def chain_stealth_compact_list_construct_default() -> StealthCompactList: ...\n+def chain_stealth_compact_list_push_back(list: StealthCompactList, elem: StealthCompact) -> None: ...\n+def chain_stealth_compact_list_destruct(list: StealthCompactList) -> None: ...\n+def chain_stealth_compact_list_count(list: StealthCompactList) -> int: ...\n+def chain_stealth_compact_list_nth(list: StealthCompactList, index: int) -> StealthCompact: ...\n+def chain_stealth_compact_list_assign_at(list: StealthCompactList, index: int, elem: StealthCompact) -> None: ...\n+def chain_stealth_compact_list_erase(list: StealthCompactList, index: int) -> None: ...\n+\n+# ─── HistoryCompact (auto-generated, do not edit) ─────────────────────────\n+\n+class HistoryCompact:\n+    \"\"\"Opaque handle to a `kth::domain::chain::history_compact`. Constructed by\n+    `chain_history_compact_construct_*` and released by\n+    `chain_history_compact_destruct`.\"\"\"\n+    ...\n+\n+def chain_history_compact_copy(self: HistoryCompact) -> HistoryCompact: ...\n+def chain_history_compact_destruct(self: HistoryCompact) -> None: ...\n+def chain_history_compact_kind(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_kind(self: HistoryCompact, value: int) -> None: ...\n+def chain_history_compact_point(self: HistoryCompact) -> \"Point\" | None: ...\n+def chain_history_compact_set_point(self: HistoryCompact, value: \"Point\") -> None: ...\n+def chain_history_compact_height(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_height(self: HistoryCompact, value: int) -> None: ...\n+def chain_history_compact_value_or_previous_checksum(self: HistoryCompact) -> int: ...\n+def chain_history_compact_set_value_or_previous_checksum(self: HistoryCompact, value: int) -> None: ...\n+\n+# ─── HistoryCompactList (auto-generated, do not edit) ─────────────────────────\n+\n+class HistoryCompactList:\n+    \"\"\"Opaque handle to a `std::vector<kth::domain::chain::history_compact>`.\"\"\"\n+    ...\n+\n+def chain_history_compact_list_construct_default() -> HistoryCompactList: ...\n+def chain_history_compact_list_push_back(list: HistoryCompactList, elem: HistoryCompact) -> None: ...\n+def chain_history_compact_list_destruct(list: HistoryCompactList) -> None: ...\n+def chain_history_compact_list_count(list: HistoryCompactList) -> int: ...\n+def chain_history_compact_list_nth(list: HistoryCompactList, index: int) -> HistoryCompact: ...\n+def chain_history_compact_list_assign_at(list: HistoryCompactList, index: int, elem: HistoryCompact) -> None: ...\n+def chain_history_compact_list_erase(list: HistoryCompactList, index: int) -> None: ...\n+\n+# ─── DoubleSpendProof (auto-generated, do not edit) ─────────────────────────\n+\n+class DoubleSpendProof:\n+    \"\"\"Opaque handle to a `kth::domain::message::double_spend_proof`. Constructed by\n+    `chain_double_spend_proof_construct_*` and released by\n+    `chain_double_spend_proof_destruct`.\"\"\"\n+    ...\n+\n+def chain_double_spend_proof_construct_default() -> DoubleSpendProof: ...\n+def chain_double_spend_proof_construct_from_data(reader: bytes, version: int) -> DoubleSpendProof: ...\n+def chain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1: \"DoubleSpendProofSpender\", spender2: \"DoubleSpendProofSpender\") -> DoubleSpendProof: ...\n+def chain_double_spend_proof_copy(self: DoubleSpendProof) -> DoubleSpendProof: ...\n+def chain_double_spend_proof_destruct(self: DoubleSpendProof) -> None: ...\n+def chain_double_spend_proof_equals(self: DoubleSpendProof, other: DoubleSpendProof) -> bool: ...\n+def chain_double_spend_proof_out_point(self: DoubleSpendProof) -> \"OutputPoint\" | None: ...\n+def chain_double_spend_proof_set_out_point(self: DoubleSpendProof, x: \"OutputPoint\") -> None: ...\n+def chain_double_spend_proof_spender1(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n+def chain_double_spend_proof_set_spender1(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...\n+def chain_double_spend_proof_spender2(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n+def chain_double_spend_proof_set_spender2(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...","path":"kth_native.pyi","commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","original_commit_id":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"body":"_⚠️ Potential issue_ | _🟠 Major_\n\n<details>\n<summary>🧩 Analysis chain</summary>\n\n🏁 Script executed:\n\n```shell\n#!/bin/bash\n# Verify unresolved spender type references in the stub.\n# Expect: no \"DoubleSpendProofSpender\" references unless a matching class is declared.\nrg -n '\"DoubleSpendProofSpender\"|class DoubleSpendProofSpender|class Spender' kth_native.pyi\n```\n\nRepository: k-nuth/py-native\n\nLength of output: 692\n\n---\n\n\n\n</details>\n\n**Replace undefined `DoubleSpendProofSpender` references with the defined `Spender` type.**\n\nThe stub references `DoubleSpendProofSpender` in four function signatures (lines 1364, 1370–1373), but this class is never defined. The actual class defined in the stub is named `Spender` (line 1382). This type mismatch will fail strict stub validation and break downstream type checking.\n\n<details>\n<summary>Fix</summary>\n\n```diff\n-def chain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1: \"DoubleSpendProofSpender\", spender2: \"DoubleSpendProofSpender\") -> DoubleSpendProof: ...\n+def chain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1: \"Spender\", spender2: \"Spender\") -> DoubleSpendProof: ...\n@@\n-def chain_double_spend_proof_spender1(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n-def chain_double_spend_proof_set_spender1(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...\n-def chain_double_spend_proof_spender2(self: DoubleSpendProof) -> \"DoubleSpendProofSpender\" | None: ...\n-def chain_double_spend_proof_set_spender2(self: DoubleSpendProof, x: \"DoubleSpendProofSpender\") -> None: ...\n+def chain_double_spend_proof_spender1(self: DoubleSpendProof) -> \"Spender\" | None: ...\n+def chain_double_spend_proof_set_spender1(self: DoubleSpendProof, x: \"Spender\") -> None: ...\n+def chain_double_spend_proof_spender2(self: DoubleSpendProof) -> \"Spender\" | None: ...\n+def chain_double_spend_proof_set_spender2(self: DoubleSpendProof, x: \"Spender\") -> None: ...\n```\n</details>\n\n<details>\n<summary>🤖 Prompt for AI Agents</summary>\n\n```\nVerify each finding against the current code and only fix it if needed.\n\nIn `@kth_native.pyi` around lines 1362 - 1373, The stub uses an undefined type\nname DoubleSpendProofSpender in the signatures for\nchain_double_spend_proof_construct, chain_double_spend_proof_spender1,\nchain_double_spend_proof_set_spender1, chain_double_spend_proof_spender2 and\nchain_double_spend_proof_set_spender2; update those signatures to reference the\nexisting Spender type (e.g., replace DoubleSpendProofSpender with Spender in\nchain_double_spend_proof_construct(out_point: \"OutputPoint\", spender1:\n\"DoubleSpendProofSpender\", spender2: \"DoubleSpendProofSpender\") and the\ngetter/setter signatures) so the annotations match the defined class name.\n```\n\n</details>\n\n<!-- fingerprinting:phantom:medusa:ibis -->\n\n<!-- This is an auto-generated comment by CodeRabbit -->","created_at":"2026-04-21T09:35:49Z","updated_at":"2026-04-21T09:35:53Z","html_url":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461807","pull_request_url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461807"},"html":{"href":"https://github.com/k-nuth/py-native/pull/6#discussion_r3116461807"},"pull_request":{"href":"https://api.github.com/repos/k-nuth/py-native/pulls/6"}},"reactions":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/comments/3116461807/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"original_position":531,"position":531,"subject_type":"line"},"pull_request":{"url":"https://api.github.com/repos/k-nuth/py-native/pulls/6","id":3560919978,"number":6,"head":{"ref":"feature/sync-kth-0.80","sha":"d07e5b50f1bc525ffff336f6ca5f89e3a177525d","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}},"base":{"ref":"master","sha":"54a59bb17c7600eb146c2e31c2b6be1c316eac72","repo":{"id":237133197,"url":"https://api.github.com/repos/k-nuth/py-native","name":"py-native"}}}},"public":true,"created_at":"2026-04-21T09:35:49Z","org":{"id":50923978,"login":"k-nuth","gravatar_id":"","url":"https://api.github.com/orgs/k-nuth","avatar_url":"https://avatars.githubusercontent.com/u/50923978?"}}]