{"url":"https://api.github.com/repos/dashpay/platform/pulls/3954","id":3915705678,"node_id":"PR_kwDOGUlHz87pZO1O","html_url":"https://github.com/dashpay/platform/pull/3954","diff_url":"https://github.com/dashpay/platform/pull/3954.diff","patch_url":"https://github.com/dashpay/platform/pull/3954.patch","issue_url":"https://api.github.com/repos/dashpay/platform/issues/3954","commits_url":"https://api.github.com/repos/dashpay/platform/pulls/3954/commits","review_comments_url":"https://api.github.com/repos/dashpay/platform/pulls/3954/comments","review_comment_url":"https://api.github.com/repos/dashpay/platform/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dashpay/platform/issues/3954/comments","statuses_url":"https://api.github.com/repos/dashpay/platform/statuses/c1d86d96e1774ae1b67bdb1eb3b586a38a85b5f9","number":3954,"state":"open","locked":false,"title":"feat(platform-wallet)!: shared ThreadRegistry for coordinator lifecycle + shutdown UAF/data-loss fixes","user":{"login":"Claudius-Maginificent","id":8431764,"node_id":"MDQ6VXNlcjg0MzE3NjQ=","avatar_url":"https://avatars.githubusercontent.com/u/8431764?v=4","gravatar_id":"","url":"https://api.github.com/users/Claudius-Maginificent","html_url":"https://github.com/Claudius-Maginificent","followers_url":"https://api.github.com/users/Claudius-Maginificent/followers","following_url":"https://api.github.com/users/Claudius-Maginificent/following{/other_user}","gists_url":"https://api.github.com/users/Claudius-Maginificent/gists{/gist_id}","starred_url":"https://api.github.com/users/Claudius-Maginificent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Claudius-Maginificent/subscriptions","organizations_url":"https://api.github.com/users/Claudius-Maginificent/orgs","repos_url":"https://api.github.com/users/Claudius-Maginificent/repos","events_url":"https://api.github.com/users/Claudius-Maginificent/events{/privacy}","received_events_url":"https://api.github.com/users/Claudius-Maginificent/received_events","type":"User","user_view_type":"public","site_admin":false},"body":"## Human-generated TL;DR\r\n\r\ndash-evo-tool crashes on shutdown, and mismanages spv sync start/stop, because some start/stop coordination between tokio and OS threads is not working well. \r\n\r\n## Issue being fixed or feature implemented\r\n\r\nThe four background sync coordinators (platform-address/BLAST, per-identity\r\ntoken state, DashPay, shielded) each run a `!Send` sync loop on a detached OS\r\nthread. At manager teardown / FFI `destroy` **nothing joined those threads**:\r\na loop mid-pass could keep calling `persister.store(...)` or fire host\r\ncompletion callbacks through a context the host frees the instant `destroy`\r\nreturns — a use-after-free. Cancellation + join were also racy: a `stop()` →\r\nquick `start()` could leave a newer loop uncancellable, and a `start()` racing\r\n`shutdown()` could leave a live, never-cancelled loop the registry then joins\r\nwith a 30s timeout while it keeps firing callbacks.\r\n\r\n## What was done?\r\n\r\nMerged onto current `v4.1-dev`; the new FFI error code below takes slot `22`\r\n(slot `21` is already used by `ErrorAddressNonceMismatch`, merged via #4047).\r\n\r\n**`dash-async`: new shared `ThreadRegistry<K>` lifecycle engine** (`registry.rs`).\r\nTracks background OS-thread / tokio-task workers, owns their spawn +\r\ncancellation + join, and reports teardown as a `ShutdownReport`. Surface used\r\nby the wallet: `start_thread` (registry spawns, owns cancellation, and joins\r\n— one slot lock covers check → spawn → install, no gap to race), weight-ordered\r\n`shutdown` → `ShutdownReport`, `quiesce`, per-key clearing latch\r\n(`hold_clearing`/`is_clearing`), and the teardown latch `is_closing`. Orphan\r\nreap keeps a still-draining prior generation UAF-safe (parked, never\r\ndropped-and-detached). `WorkerConfig` gained a `stack_size` field (`None` =\r\nplatform default) so a caller with a deep-recursion loop body (DashPay's\r\nGroveDB proof descent — see below) can request a larger OS-thread stack\r\nwithout hand-rolling its own `Builder`. The `AtomicFlagGuard`/`RefcountedFlagGuard`\r\nhelper types originally drafted alongside the registry had zero callers\r\nanywhere in this diff or in the wallet, so they were removed rather than kept\r\nspeculatively.\r\n\r\n**Wallet: all four coordinators spawn through `registry.start_thread`, full stop.**\r\nEach coordinator's `start()` used to spawn its own `std::thread`, install a\r\nhand-rolled `LoopCancelGuard` (a per-coordinator, duplicated generation-guard),\r\nand hand the `JoinHandle` to the registry after the fact — two independently\r\nchecked gates instead of one. That's gone: `start()` now calls\r\n`registry.start_thread(key, cfg, |cancel| { ...loop... })` and the registry\r\nowns the whole lifecycle — the `is_closing()`/`is_clearing()` gate check, the\r\nspawn, and the cancellation-token install all happen under the same slot\r\nlock, so there's no window between \"checked it's safe\" and \"spawned anyway.\"\r\n`loop_cancel.rs` is deleted outright — zero remaining references repo-wide.\r\nSee **Why this instead of keeping `LoopCancelGuard`** below for the fuller\r\nrationale. `PlatformWalletManager::shutdown` quiesces each coordinator\r\n(drains any in-flight pass incl. its persister/callback fan-out), then\r\nhard-joins the loop threads via the registry, then drains the wallet-event\r\nadapter, returning a `ShutdownReport` keyed by `WalletWorker`.\r\n\r\n**FFI: `platform_wallet_manager_destroy` gates on the report.** Runs the full\r\nshutdown; on a non-clean report it retries `shutdown()` once (the re-quiesce\r\ncancels any loop that raced the first pass); if it still can't cleanly join\r\nevery coordinator it returns the new `ErrorShutdownIncomplete` code instead of\r\nonly logging. The Swift host mirror\r\n(`PlatformWalletResultCode.errorShutdownIncomplete` + typed\r\n`PlatformWalletError.shutdownIncomplete`) is included in this PR's diff.\r\n\r\nHardening fixes folded in: `shutdown` re-drains late-parked orphans so\r\n`all_clean()` can't false-pass on a straggler; the restart reap classifies a\r\njoined/dropped prior generation (surfacing a panicked prior) instead of\r\ndiscarding the join result; `coordinator_worker_config` uses\r\n`dash_async::DEFAULT_JOIN_BUDGET` directly.\r\n\r\n## Why this instead of keeping `LoopCancelGuard`?\r\n\r\nThe first version of this PR fixed the three races with **defense-in-depth**:\r\nkeep each coordinator's hand-rolled `LoopCancelGuard` for cancellation, add\r\nthe registry alongside it purely for join/status. That closes the races\r\n(verified — regression tests for all three), but it does it with two\r\nseparately-checked gates instead of one atomic operation, and it's four\r\nnear-identical copies of the same generation-guard logic. `ThreadRegistry`\r\nalready had a stronger primitive sitting unused: `start_thread()`, which\r\nperforms the shutdown-state check, the spawn, and the cancel-token install\r\nunder one slot lock. This revision migrates all four coordinators onto it and\r\ndeletes `loop_cancel.rs`.\r\n\r\n| | `LoopCancelGuard` (this PR's first version) | `start_thread()` (this revision) |\r\n|---|---|---|\r\n| Who spawns the thread | The coordinator, directly via `std::thread::Builder::spawn` | The registry, inside `start_thread`'s factory closure, under its own slot lock |\r\n| Cancellation ownership | One hand-rolled guard per coordinator — four separate copies of the same logic | One shared implementation — the registry's `SlotState` owns the token for every worker |\r\n| Check-then-spawn atomicity | Two separate checks (`is_closing`/`is_clearing`, then install) — a real, if narrow, gap between them | Structural — the check and the spawn happen under the identical lock, no gap to hit |\r\n| `stop()` + quick `start()` | Handled — this was `LoopCancelGuard`'s original reason to exist | Handled the same way, natively, by `SlotState`'s own generation bump |\r\n| Panic mid-loop | Not observed by the guard itself | `EpilogueGuard`'s `Drop` runs on every exit path including panic, and clears the running flag — closes a real gap: the old `clear_if_current` was fall-through and could leave `is_running() == true` after a panic |\r\n| Net result | Closes the UAF this PR exists to fix, via two coordinated checks | Same race window, closed by construction — nothing to hit, not just nothing observed hitting it |\r\n\r\nConcretely, this also **tightens RUST-003** (shielded-sync TOCTOU wipe/resurrect):\r\n`ShieldedSyncManager::start()` used to do its own check → install → re-check\r\ndance around `is_closing()`/`is_clearing()` before spawning; that's now a\r\nsingle `registry.start_thread(...)` call with no application-level recheck\r\nneeded, because the registry's own start-slot lock covers both latches\r\nalready.\r\n\r\nOne deliberate addition to make the move backward-compatible: DashPay's loop\r\nneeds an 8 MiB stack (GroveDB `verify_layer_proof_v1` recursion SIGBUSes\r\non-device on the default), previously set via a manual `Builder::stack_size`.\r\n`start_thread` spawned with the default, so `WorkerConfig` gained a\r\n`stack_size: Option<NonZeroUsize>` field (`None` for the other three\r\ncoordinators, honored only by `start_thread`) rather than silently dropping\r\nthe larger stack.\r\n\r\nFull visual comparison (spectrum diagram + row-by-row table):\r\nhttps://claude.ai/code/artifact/aa6e2bbc-6c34-4384-abdd-b9339909c6d0\r\n\r\n## How Has This Been Tested?\r\n\r\nScoped through the cached cargo wrapper (`-p dash-async -p platform-wallet -p\r\nplatform-wallet-ffi`, `--features platform-wallet/shielded`):\r\n- `cargo clippy --all-targets -- -D warnings` → clean (re-verified after the\r\n  `start_thread` migration, independently re-run).\r\n- `cargo nextest run` → passing, incl. `dash-async` 44 + 1 doctest and the\r\n  full `platform-wallet` suite with the `shielded` feature on.\r\n- `RUSTDOCFLAGS=\"-D warnings\" cargo doc -p dash-async --no-deps` → clean.\r\n- `cargo fmt --check` → clean.\r\n\r\nRegistry regression tests assert the intended contract (RED-provable):\r\n`is_closing_tracks_shutdown_latch`,\r\n`register_thread_after_shutdown_wedged_orphan_flips_all_clean` (a live\r\nstraggler flips `all_clean`), `register_thread_restart_reaps_panicked_prior`\r\n(a panicked prior is joined+classified, the restarting caller neither hangs\r\nnor inherits the panic), `start_thread_honors_custom_stack_size` (the new\r\n`WorkerConfig::stack_size` path), alongside the existing quiesce/shutdown/\r\ngeneration-guard/orphan-reap suite.\r\n\r\nEach coordinator's stale-loop regression test was rewritten to drive real\r\n`start()`/`stop()` on live OS-thread loops through the registry instead of\r\npoking the (now-deleted) guard directly — e.g.\r\n`stop_then_quick_start_keeps_new_loop_cancellable` — and a full\r\n`shutdown().await` at the end of each asserts `report.all_clean()`.\r\n\r\nThe Swift mirror addition (`PlatformWalletResult.swift`) has not been\r\ncompiled on this branch — no Swift toolchain in this environment. It was\r\nhand-verified against the file's established pattern (exhaustive switch\r\ncoverage, brace balance, cbindgen-generated constant naming). **Note:** this\r\nPR's Swift CI check is currently red for an unrelated, pre-existing reason —\r\nsee PR comments below.\r\n\r\n## Breaking Changes\r\n\r\n- `PlatformWalletManager::shutdown(&self)` return type: `()` → `ShutdownReport<WalletWorker>`.\r\n- The four coordinator constructors gain a leading\r\n  `registry: Arc<ThreadRegistry<WalletWorker>>` parameter:\r\n  `IdentitySyncManager::new`, `PlatformAddressSyncManager::new`,\r\n  `DashPaySyncManager::new`, `ShieldedSyncManager::new`.\r\n- `dash_async::WorkerConfig` (public struct) gains a `stack_size: Option<NonZeroUsize>`\r\n  field — a full struct-literal construction outside this diff needs\r\n  `..WorkerConfig::default()` or an explicit value.\r\n- New FFI result code `PlatformWalletFFIResultCode::ErrorShutdownIncomplete = 22`;\r\n  `platform_wallet_manager_destroy` can now return a non-`Success` result when a\r\n  coordinator thread can't be cleanly joined even after a retry (previously it\r\n  always returned `Success`).\r\n\r\n## Checklist:\r\n- [x] I have performed a self-review of my own code\r\n- [x] I have commented my code, particularly in hard-to-understand areas\r\n- [x] I have added or updated relevant unit/integration/functional/e2e tests\r\n- [x] I have added \"!\" to the title and described breaking changes in the corresponding section if my code contains any\r\n- [x] I have made corresponding changes to the documentation if needed\r\n\r\n<sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>\r\n\r\n\r\n<!-- This is an auto-generated comment: release notes by coderabbit.ai -->\r\n\r\n## Summary by CodeRabbit\r\n\r\n* **New Features**\r\n  * Added coordinated lifecycle management for wallet background synchronization tasks.\r\n  * Added shutdown status reporting, including detection of incomplete shutdowns.\r\n  * Added Swift and FFI error handling for incomplete shutdowns.\r\n\r\n* **Bug Fixes**\r\n  * Improved worker cancellation and restart behavior.\r\n  * Prevented synchronization tasks from restarting during shielded-wallet clearing.\r\n  * Added retry handling when shutdown does not complete cleanly.\r\n\r\n<!-- end of auto-generated comment: release notes by coderabbit.ai -->","labels":[],"milestone":{"id":16167250,"node_id":"MI_kwDOGUlHz84A9rFS","url":"https://api.github.com/repos/dashpay/platform/milestones/38","html_url":"https://github.com/dashpay/platform/milestone/38","labels_url":"https://api.github.com/repos/dashpay/platform/milestones/38/labels","number":38,"state":"open","title":"v4.1.0","description":"","creator":{"login":"shumkov","id":24296,"node_id":"MDQ6VXNlcjI0Mjk2","avatar_url":"https://avatars.githubusercontent.com/u/24296?v=4","gravatar_id":"","url":"https://api.github.com/users/shumkov","html_url":"https://github.com/shumkov","followers_url":"https://api.github.com/users/shumkov/followers","following_url":"https://api.github.com/users/shumkov/following{/other_user}","gists_url":"https://api.github.com/users/shumkov/gists{/gist_id}","starred_url":"https://api.github.com/users/shumkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shumkov/subscriptions","organizations_url":"https://api.github.com/users/shumkov/orgs","repos_url":"https://api.github.com/users/shumkov/repos","events_url":"https://api.github.com/users/shumkov/events{/privacy}","received_events_url":"https://api.github.com/users/shumkov/received_events","type":"User","user_view_type":"public","site_admin":false},"open_issues":43,"closed_issues":85,"created_at":"2026-06-01T14:04:15Z","updated_at":"2026-07-18T06:38:34Z","closed_at":null,"due_on":null},"active_lock_reason":null,"created_at":"2026-06-22T22:18:41Z","updated_at":"2026-07-17T08:26:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"672dcbb0795464114606bd163aeb2034e469c62c","assignee":null,"assignees":[],"requested_reviewers":[{"login":"shumkov","id":24296,"node_id":"MDQ6VXNlcjI0Mjk2","avatar_url":"https://avatars.githubusercontent.com/u/24296?v=4","gravatar_id":"","url":"https://api.github.com/users/shumkov","html_url":"https://github.com/shumkov","followers_url":"https://api.github.com/users/shumkov/followers","following_url":"https://api.github.com/users/shumkov/following{/other_user}","gists_url":"https://api.github.com/users/shumkov/gists{/gist_id}","starred_url":"https://api.github.com/users/shumkov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shumkov/subscriptions","organizations_url":"https://api.github.com/users/shumkov/orgs","repos_url":"https://api.github.com/users/shumkov/repos","events_url":"https://api.github.com/users/shumkov/events{/privacy}","received_events_url":"https://api.github.com/users/shumkov/received_events","type":"User","user_view_type":"public","site_admin":false},{"login":"QuantumExplorer","id":11468583,"node_id":"MDQ6VXNlcjExNDY4NTgz","avatar_url":"https://avatars.githubusercontent.com/u/11468583?v=4","gravatar_id":"","url":"https://api.github.com/users/QuantumExplorer","html_url":"https://github.com/QuantumExplorer","followers_url":"https://api.github.com/users/QuantumExplorer/followers","following_url":"https://api.github.com/users/QuantumExplorer/following{/other_user}","gists_url":"https://api.github.com/users/QuantumExplorer/gists{/gist_id}","starred_url":"https://api.github.com/users/QuantumExplorer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QuantumExplorer/subscriptions","organizations_url":"https://api.github.com/users/QuantumExplorer/orgs","repos_url":"https://api.github.com/users/QuantumExplorer/repos","events_url":"https://api.github.com/users/QuantumExplorer/events{/privacy}","received_events_url":"https://api.github.com/users/QuantumExplorer/received_events","type":"User","user_view_type":"public","site_admin":false},{"login":"llbartekll","id":25840476,"node_id":"MDQ6VXNlcjI1ODQwNDc2","avatar_url":"https://avatars.githubusercontent.com/u/25840476?v=4","gravatar_id":"","url":"https://api.github.com/users/llbartekll","html_url":"https://github.com/llbartekll","followers_url":"https://api.github.com/users/llbartekll/followers","following_url":"https://api.github.com/users/llbartekll/following{/other_user}","gists_url":"https://api.github.com/users/llbartekll/gists{/gist_id}","starred_url":"https://api.github.com/users/llbartekll/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/llbartekll/subscriptions","organizations_url":"https://api.github.com/users/llbartekll/orgs","repos_url":"https://api.github.com/users/llbartekll/repos","events_url":"https://api.github.com/users/llbartekll/events{/privacy}","received_events_url":"https://api.github.com/users/llbartekll/received_events","type":"User","user_view_type":"public","site_admin":false},{"login":"ZocoLini","id":100384750,"node_id":"U_kgDOBfu_7g","avatar_url":"https://avatars.githubusercontent.com/u/100384750?v=4","gravatar_id":"","url":"https://api.github.com/users/ZocoLini","html_url":"https://github.com/ZocoLini","followers_url":"https://api.github.com/users/ZocoLini/followers","following_url":"https://api.github.com/users/ZocoLini/following{/other_user}","gists_url":"https://api.github.com/users/ZocoLini/gists{/gist_id}","starred_url":"https://api.github.com/users/ZocoLini/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ZocoLini/subscriptions","organizations_url":"https://api.github.com/users/ZocoLini/orgs","repos_url":"https://api.github.com/users/ZocoLini/repos","events_url":"https://api.github.com/users/ZocoLini/events{/privacy}","received_events_url":"https://api.github.com/users/ZocoLini/received_events","type":"User","user_view_type":"public","site_admin":false}],"requested_teams":[],"head":{"label":"dashpay:feat/platform-wallet-shutdown-join","ref":"feat/platform-wallet-shutdown-join","repo":{"id":424232911,"node_id":"R_kgDOGUlHzw","name":"platform","full_name":"dashpay/platform","owner":{"login":"dashpay","id":11511719,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTExNzE5","avatar_url":"https://avatars.githubusercontent.com/u/11511719?v=4","gravatar_id":"","url":"https://api.github.com/users/dashpay","html_url":"https://github.com/dashpay","followers_url":"https://api.github.com/users/dashpay/followers","following_url":"https://api.github.com/users/dashpay/following{/other_user}","gists_url":"https://api.github.com/users/dashpay/gists{/gist_id}","starred_url":"https://api.github.com/users/dashpay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dashpay/subscriptions","organizations_url":"https://api.github.com/users/dashpay/orgs","repos_url":"https://api.github.com/users/dashpay/repos","events_url":"https://api.github.com/users/dashpay/events{/privacy}","received_events_url":"https://api.github.com/users/dashpay/received_events","type":"Organization","user_view_type":"public","site_admin":false},"private":false,"fork":false,"archived":false,"disabled":false,"description":"Structured data storage blockchain with secondary indices and identities.","homepage":"https://docs.dash.org/projects/platform/en/stable/docs/intro/what-is-dash-platform.html","language":"Rust","visibility":"public","is_template":false,"topics":["blockchain","crypto","cryptocurrency","dapp","dapps","dash","decentralization","p2p"],"forks_count":56,"forks":56,"stargazers_count":93,"watchers_count":93,"watchers":93,"open_issues_count":211,"open_issues":211,"size":726383,"default_branch":"v4.1-dev","has_issues":true,"has_projects":true,"has_wiki":true,"has_pages":true,"has_downloads":false,"has_discussions":true,"has_pull_requests":true,"pushed_at":"2026-07-18T06:38:03Z","created_at":"2021-11-03T13:23:54Z","updated_at":"2026-07-17T11:58:29Z","allow_forking":true,"web_commit_signoff_required":false,"pull_request_creation_policy":"all","license":{"key":"mit","name":"MIT License","url":"https://api.github.com/licenses/mit","spdx_id":"MIT","node_id":"MDc6TGljZW5zZTEz"},"url":"https://api.github.com/repos/dashpay/platform","html_url":"https://github.com/dashpay/platform","archive_url":"https://api.github.com/repos/dashpay/platform/{archive_format}{/ref}","assignees_url":"https://api.github.com/repos/dashpay/platform/assignees{/user}","blobs_url":"https://api.github.com/repos/dashpay/platform/git/blobs{/sha}","branches_url":"https://api.github.com/repos/dashpay/platform/branches{/branch}","collaborators_url":"https://api.github.com/repos/dashpay/platform/collaborators{/collaborator}","comments_url":"https://api.github.com/repos/dashpay/platform/comments{/number}","commits_url":"https://api.github.com/repos/dashpay/platform/commits{/sha}","compare_url":"https://api.github.com/repos/dashpay/platform/compare/{base}...{head}","contents_url":"https://api.github.com/repos/dashpay/platform/contents/{+path}","contributors_url":"https://api.github.com/repos/dashpay/platform/contributors","deployments_url":"https://api.github.com/repos/dashpay/platform/deployments","downloads_url":"https://api.github.com/repos/dashpay/platform/downloads","events_url":"https://api.github.com/repos/dashpay/platform/events","forks_url":"https://api.github.com/repos/dashpay/platform/forks","git_commits_url":"https://api.github.com/repos/dashpay/platform/git/commits{/sha}","git_refs_url":"https://api.github.com/repos/dashpay/platform/git/refs{/sha}","git_tags_url":"https://api.github.com/repos/dashpay/platform/git/tags{/sha}","git_url":"git://github.com/dashpay/platform.git","issue_comment_url":"https://api.github.com/repos/dashpay/platform/issues/comments{/number}","issue_events_url":"https://api.github.com/repos/dashpay/platform/issues/events{/number}","issues_url":"https://api.github.com/repos/dashpay/platform/issues{/number}","keys_url":"https://api.github.com/repos/dashpay/platform/keys{/key_id}","labels_url":"https://api.github.com/repos/dashpay/platform/labels{/name}","languages_url":"https://api.github.com/repos/dashpay/platform/languages","merges_url":"https://api.github.com/repos/dashpay/platform/merges","milestones_url":"https://api.github.com/repos/dashpay/platform/milestones{/number}","notifications_url":"https://api.github.com/repos/dashpay/platform/notifications{?since,all,participating}","pulls_url":"https://api.github.com/repos/dashpay/platform/pulls{/number}","releases_url":"https://api.github.com/repos/dashpay/platform/releases{/id}","ssh_url":"git@github.com:dashpay/platform.git","stargazers_url":"https://api.github.com/repos/dashpay/platform/stargazers","statuses_url":"https://api.github.com/repos/dashpay/platform/statuses/{sha}","subscribers_url":"https://api.github.com/repos/dashpay/platform/subscribers","subscription_url":"https://api.github.com/repos/dashpay/platform/subscription","tags_url":"https://api.github.com/repos/dashpay/platform/tags","teams_url":"https://api.github.com/repos/dashpay/platform/teams","trees_url":"https://api.github.com/repos/dashpay/platform/git/trees{/sha}","clone_url":"https://github.com/dashpay/platform.git","mirror_url":null,"hooks_url":"https://api.github.com/repos/dashpay/platform/hooks","svn_url":"https://github.com/dashpay/platform"},"sha":"c1d86d96e1774ae1b67bdb1eb3b586a38a85b5f9","user":{"login":"dashpay","id":11511719,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTExNzE5","avatar_url":"https://avatars.githubusercontent.com/u/11511719?v=4","gravatar_id":"","url":"https://api.github.com/users/dashpay","html_url":"https://github.com/dashpay","followers_url":"https://api.github.com/users/dashpay/followers","following_url":"https://api.github.com/users/dashpay/following{/other_user}","gists_url":"https://api.github.com/users/dashpay/gists{/gist_id}","starred_url":"https://api.github.com/users/dashpay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dashpay/subscriptions","organizations_url":"https://api.github.com/users/dashpay/orgs","repos_url":"https://api.github.com/users/dashpay/repos","events_url":"https://api.github.com/users/dashpay/events{/privacy}","received_events_url":"https://api.github.com/users/dashpay/received_events","type":"Organization","user_view_type":"public","site_admin":false}},"base":{"label":"dashpay:v4.1-dev","ref":"v4.1-dev","repo":{"id":424232911,"node_id":"R_kgDOGUlHzw","name":"platform","full_name":"dashpay/platform","owner":{"login":"dashpay","id":11511719,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTExNzE5","avatar_url":"https://avatars.githubusercontent.com/u/11511719?v=4","gravatar_id":"","url":"https://api.github.com/users/dashpay","html_url":"https://github.com/dashpay","followers_url":"https://api.github.com/users/dashpay/followers","following_url":"https://api.github.com/users/dashpay/following{/other_user}","gists_url":"https://api.github.com/users/dashpay/gists{/gist_id}","starred_url":"https://api.github.com/users/dashpay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dashpay/subscriptions","organizations_url":"https://api.github.com/users/dashpay/orgs","repos_url":"https://api.github.com/users/dashpay/repos","events_url":"https://api.github.com/users/dashpay/events{/privacy}","received_events_url":"https://api.github.com/users/dashpay/received_events","type":"Organization","user_view_type":"public","site_admin":false},"private":false,"fork":false,"archived":false,"disabled":false,"description":"Structured data storage blockchain with secondary indices and identities.","homepage":"https://docs.dash.org/projects/platform/en/stable/docs/intro/what-is-dash-platform.html","language":"Rust","visibility":"public","is_template":false,"topics":["blockchain","crypto","cryptocurrency","dapp","dapps","dash","decentralization","p2p"],"forks_count":56,"forks":56,"stargazers_count":93,"watchers_count":93,"watchers":93,"open_issues_count":211,"open_issues":211,"size":726383,"default_branch":"v4.1-dev","has_issues":true,"has_projects":true,"has_wiki":true,"has_pages":true,"has_downloads":false,"has_discussions":true,"has_pull_requests":true,"pushed_at":"2026-07-18T06:38:03Z","created_at":"2021-11-03T13:23:54Z","updated_at":"2026-07-17T11:58:29Z","allow_forking":true,"web_commit_signoff_required":false,"pull_request_creation_policy":"all","license":{"key":"mit","name":"MIT License","url":"https://api.github.com/licenses/mit","spdx_id":"MIT","node_id":"MDc6TGljZW5zZTEz"},"url":"https://api.github.com/repos/dashpay/platform","html_url":"https://github.com/dashpay/platform","archive_url":"https://api.github.com/repos/dashpay/platform/{archive_format}{/ref}","assignees_url":"https://api.github.com/repos/dashpay/platform/assignees{/user}","blobs_url":"https://api.github.com/repos/dashpay/platform/git/blobs{/sha}","branches_url":"https://api.github.com/repos/dashpay/platform/branches{/branch}","collaborators_url":"https://api.github.com/repos/dashpay/platform/collaborators{/collaborator}","comments_url":"https://api.github.com/repos/dashpay/platform/comments{/number}","commits_url":"https://api.github.com/repos/dashpay/platform/commits{/sha}","compare_url":"https://api.github.com/repos/dashpay/platform/compare/{base}...{head}","contents_url":"https://api.github.com/repos/dashpay/platform/contents/{+path}","contributors_url":"https://api.github.com/repos/dashpay/platform/contributors","deployments_url":"https://api.github.com/repos/dashpay/platform/deployments","downloads_url":"https://api.github.com/repos/dashpay/platform/downloads","events_url":"https://api.github.com/repos/dashpay/platform/events","forks_url":"https://api.github.com/repos/dashpay/platform/forks","git_commits_url":"https://api.github.com/repos/dashpay/platform/git/commits{/sha}","git_refs_url":"https://api.github.com/repos/dashpay/platform/git/refs{/sha}","git_tags_url":"https://api.github.com/repos/dashpay/platform/git/tags{/sha}","git_url":"git://github.com/dashpay/platform.git","issue_comment_url":"https://api.github.com/repos/dashpay/platform/issues/comments{/number}","issue_events_url":"https://api.github.com/repos/dashpay/platform/issues/events{/number}","issues_url":"https://api.github.com/repos/dashpay/platform/issues{/number}","keys_url":"https://api.github.com/repos/dashpay/platform/keys{/key_id}","labels_url":"https://api.github.com/repos/dashpay/platform/labels{/name}","languages_url":"https://api.github.com/repos/dashpay/platform/languages","merges_url":"https://api.github.com/repos/dashpay/platform/merges","milestones_url":"https://api.github.com/repos/dashpay/platform/milestones{/number}","notifications_url":"https://api.github.com/repos/dashpay/platform/notifications{?since,all,participating}","pulls_url":"https://api.github.com/repos/dashpay/platform/pulls{/number}","releases_url":"https://api.github.com/repos/dashpay/platform/releases{/id}","ssh_url":"git@github.com:dashpay/platform.git","stargazers_url":"https://api.github.com/repos/dashpay/platform/stargazers","statuses_url":"https://api.github.com/repos/dashpay/platform/statuses/{sha}","subscribers_url":"https://api.github.com/repos/dashpay/platform/subscribers","subscription_url":"https://api.github.com/repos/dashpay/platform/subscription","tags_url":"https://api.github.com/repos/dashpay/platform/tags","teams_url":"https://api.github.com/repos/dashpay/platform/teams","trees_url":"https://api.github.com/repos/dashpay/platform/git/trees{/sha}","clone_url":"https://github.com/dashpay/platform.git","mirror_url":null,"hooks_url":"https://api.github.com/repos/dashpay/platform/hooks","svn_url":"https://github.com/dashpay/platform"},"sha":"9b5aee8c71e07c4b1f28444c3512fe3c01debe87","user":{"login":"dashpay","id":11511719,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTExNzE5","avatar_url":"https://avatars.githubusercontent.com/u/11511719?v=4","gravatar_id":"","url":"https://api.github.com/users/dashpay","html_url":"https://github.com/dashpay","followers_url":"https://api.github.com/users/dashpay/followers","following_url":"https://api.github.com/users/dashpay/following{/other_user}","gists_url":"https://api.github.com/users/dashpay/gists{/gist_id}","starred_url":"https://api.github.com/users/dashpay/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dashpay/subscriptions","organizations_url":"https://api.github.com/users/dashpay/orgs","repos_url":"https://api.github.com/users/dashpay/repos","events_url":"https://api.github.com/users/dashpay/events{/privacy}","received_events_url":"https://api.github.com/users/dashpay/received_events","type":"Organization","user_view_type":"public","site_admin":false}},"_links":{"comments":{"href":"https://api.github.com/repos/dashpay/platform/issues/3954/comments"},"commits":{"href":"https://api.github.com/repos/dashpay/platform/pulls/3954/commits"},"statuses":{"href":"https://api.github.com/repos/dashpay/platform/statuses/c1d86d96e1774ae1b67bdb1eb3b586a38a85b5f9"},"html":{"href":"https://github.com/dashpay/platform/pull/3954"},"issue":{"href":"https://api.github.com/repos/dashpay/platform/issues/3954"},"review_comments":{"href":"https://api.github.com/repos/dashpay/platform/pulls/3954/comments"},"review_comment":{"href":"https://api.github.com/repos/dashpay/platform/pulls/comments{/number}"},"self":{"href":"https://api.github.com/repos/dashpay/platform/pulls/3954"}},"author_association":"COLLABORATOR","auto_merge":null,"draft":false,"merged":false,"mergeable":true,"rebaseable":false,"mergeable_state":"clean","merged_by":null,"comments":17,"review_comments":156,"maintainer_can_modify":false,"commits":6,"additions":3149,"deletions":345,"changed_files":14}