Skip to content

feat(mcp): unified send routing with auto-detect destination#808

Open
lklimek wants to merge 10 commits intov1.0-devfrom
feat/unified-send-routing
Open

feat(mcp): unified send routing with auto-detect destination#808
lklimek wants to merge 10 commits intov1.0-devfrom
feat/unified-send-routing

Conversation

@lklimek
Copy link
Copy Markdown
Contributor

@lklimek lklimek commented Mar 31, 2026

Summary

  • Extract send routing logic from UI into shared src/model/send_routing.rs — a pure, synchronous resolve_send() function that auto-detects destination address type and returns the correct BackendTask variant
  • Refactor send_screen.rs — replace 14 individual send_*_to_* methods with a single validate_and_send() call to resolve_send() (net -914 lines)
  • Add wallet_funds_send MCP tool — unified send tool that accepts any address type, with source parameter (core/platform/shielded/identity, defaults to "core"). All 14 routing paths supported with proper fee context.
  • Remove 12 individual send toolscore_funds_send, 5 identity credit tools, 5 shielded tools replaced by the single unified tool (-1337 lines)

Motivation

Send routing logic (the 4x4 source x destination matrix) lived exclusively in the UI WalletSendScreen. MCP tools couldn't reuse it — each route had its own dedicated tool with duplicated parameter handling. Any new consumer (CLI, automation) would have to reimplement 14 routing paths.

Now both UI and MCP share resolve_send() as the single source of truth, and MCP callers use one tool instead of twelve.

Removed MCP Tools (replaced by wallet_funds_send)

Old Tool Route New Equivalent
core_funds_send core-core wallet_funds_send (source=core)
identity_credits_topup core-identity wallet_funds_send (source=core)
identity_credits_topup_from_platform platform-identity wallet_funds_send (source=platform)
identity_credits_transfer identity-identity wallet_funds_send (source=identity)
identity_credits_withdraw identity-core wallet_funds_send (source=identity)
identity_credits_to_address identity-platform wallet_funds_send (source=identity)
shielded_shield_from_core core-shielded wallet_funds_send (source=core)
shielded_shield_from_platform platform-shielded wallet_funds_send (source=platform)
shielded_transfer shielded-shielded wallet_funds_send (source=shielded)
shielded_unshield shielded-platform wallet_funds_send (source=shielded)
shielded_withdraw shielded-core wallet_funds_send (source=shielded)

Test plan

  • 38 unit tests in send_routing.rs covering all 14 supported routes, 2 unsupported routes, error cases, balance checks, platform allocation boundary conditions
  • cargo clippy --all-features --all-targets -- -D warnings clean
  • cargo test --all-features --workspace all pass
  • Manual: verify GUI send screen still works for core-core, core-platform, platform-platform, platform-core paths
  • Manual: verify wallet_funds_send MCP tool for core-core, platform-platform paths

🤖 Co-authored by Claudius the Magnificent AI Agent

lklimek and others added 6 commits March 31, 2026 18:04
Extract routing logic from WalletSendScreen into a pure synchronous
resolve_send() function in src/model/send_routing.rs. Covers all 14
supported source/destination combinations plus 2 unsupported routes
with user-friendly error messages. Includes platform address allocation
helpers and 36 unit tests covering happy paths, error cases, balance
checks, self-send prevention, and overflow safety.

Also adds test-only constructors for Wallet and QualifiedIdentity
to support unit testing across modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New MCP tool that uses the send_routing module to route funds from any
source (core, platform, shielded, identity) to any destination address
type. Accepts amount in duffs and converts to credits internally for
non-Core sources. Routes that require FeeContext (platform-to-platform,
platform-to-identity, platform-to-shielded, platform-to-core) pass None
and rely on the routing module's fallback behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ting

Replace the 14 individual send_*_to_* methods in WalletSendScreen with a
single call to resolve_send() from the send_routing module. The new
validate_and_send() converts UI-layer SourceSelection to SendSource,
builds FeeContext for platform routes, resolves destination identities,
and delegates routing entirely to the pure resolve_send() function.

Changes:
- send_screen.rs: Remove 14 send handler methods (~900 lines), remove
  duplicated allocation/fee functions (now in send_routing.rs), add
  build_send_source(), resolve_destination_identity(), build_fee_context()
- send_routing.rs: Add seed_hash to SendSource::CoreWallet (fixes the
  zeroed placeholder), make allocation types and fee functions pub for
  UI rendering code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…llet_funds_send

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ion boundary tests

- Replace developer-facing "programming error" message with user-friendly text
- Add MAX_PLATFORM_INPUTS boundary test (TC-SR-035)
- Add multi-input within limit test (TC-SR-037)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@lklimek has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 38 seconds before requesting another review.

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 0 minutes and 38 seconds.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e438fecf-f896-45df-8556-4ca3276c24d6

📥 Commits

Reviewing files that changed from the base of the PR and between 9acacca and 673fb7d.

📒 Files selected for processing (13)
  • docs/MCP.md
  • docs/ai-design/2026-03-31-unified-send-routing/requirements.md
  • src/mcp/resolve.rs
  • src/mcp/server.rs
  • src/mcp/tools/identity.rs
  • src/mcp/tools/mod.rs
  • src/mcp/tools/shielded.rs
  • src/mcp/tools/wallet.rs
  • src/model/mod.rs
  • src/model/qualified_identity/mod.rs
  • src/model/send_routing.rs
  • src/model/wallet/mod.rs
  • src/ui/wallets/send_screen.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/unified-send-routing

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR centralizes “send routing” (mapping source+destination combinations to the correct BackendTask) into a new model-layer module so both the GUI send screen and MCP tooling can reuse the same routing logic.

Changes:

  • Added src/model/send_routing.rs with resolve_send() plus platform allocation/fee helpers and unit tests.
  • Refactored WalletSendScreen to replace many per-route send handlers with a single validate_and_send() that delegates to resolve_send().
  • Added a new MCP tool wallet_funds_send, registered it on the server, and documented it.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/model/send_routing.rs Introduces shared routing API (SendSource/SendRequest/resolve_send) and shared platform allocation + fee estimation helpers with tests.
src/ui/wallets/send_screen.rs Migrates UI send dispatch to the shared routing layer and reuses shared allocation/fee helpers.
src/mcp/tools/wallet.rs Adds wallet_funds_send tool that builds a SendRequest and dispatches tasks based on resolve_send().
src/mcp/server.rs Registers wallet_funds_send on the MCP service.
docs/MCP.md Adds the new MCP tool to the tool list.
src/model/wallet/mod.rs Adds Wallet::new_test_wallet() helper (tests only) for routing unit tests.
src/model/qualified_identity/mod.rs Adds QualifiedIdentity::new_test_identity() helper (tests only) for routing unit tests.
src/model/mod.rs Exposes the new send_routing module.
docs/ai-design/2026-03-31-unified-send-routing/requirements.md Adds a design/requirements doc for the unified routing initiative.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +599 to 636
// For Core wallet sources, amount is in duffs; otherwise it's already in credits
let amount = match &source {
SendSource::CoreWallet { .. } => self
.amount
.as_ref()
.ok_or_else(|| "Amount is required".to_string())?
.dash_to_duffs()?,
_ => amount_obj.value(),
};

self.mark_sending();
// Resolve identity for identity-destination routes
let resolved_identity = self.resolve_destination_identity(&destination)?;

Ok(AppAction::BackendTask(BackendTask::CoreTask(
CoreTask::SendWalletPayment {
wallet,
request: WalletPaymentRequest {
recipients: vec![recipient],
subtract_fee_from_amount: self.subtract_fee,
memo: None,
override_fee: None,
},
},
)))
}

fn send_core_to_platform(&mut self, seed_hash: WalletSeedHash) -> Result<AppAction, String> {
let amount_duffs = self
.amount
.as_ref()
.ok_or_else(|| "Amount is required".to_string())?
.dash_to_duffs()?;
if amount_duffs == 0 {
return Err("Amount must be greater than 0".to_string());
}
// Build fee context for Platform source routes
let fee_context = self.build_fee_context(&source, &destination);

// Extract validated platform address
let destination = self
.validated_destination
.as_ref()
.and_then(|v| v.as_platform().copied())
.ok_or_else(|| "Invalid platform address".to_string())?;
let request = SendRequest {
source,
destination,
amount,
resolved_identity,
fee_context,
};

// Check balance; fees will be subtracted from amount
let required = amount_duffs;
let balance = self.get_core_balance();
if required > balance {
return Err(format!(
"Insufficient balance. Need {} (including fee) but have {}",
Self::format_dash(required),
Self::format_dash(balance)
));
match send_routing::resolve_send(request) {
Ok(SendResult::Single(task)) => {
self.mark_sending();
Ok(AppAction::BackendTask(task))
}
Ok(SendResult::Sequential(tasks)) => {
self.mark_sending();
Ok(AppAction::BackendTasks(
tasks,
crate::app::BackendTasksExecutionMode::Sequential,
))
}
Err(e) => Err(e.to_string()),
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

validate_and_send() no longer applies the UI's subtract_fee option for Core→Core sends. The checkbox (and the auto-enable on Max) still updates self.subtract_fee, but resolve_send() always builds WalletPaymentRequest with subtract_fee_from_amount: false, so Max-on-core is likely to fail and the recipient amount semantics change. Consider threading subtract_fee_from_amount through SendRequest (or similar) and using it when constructing WalletPaymentRequest for Core wallet routes.

Copilot uses AI. Check for mistakes.
.validated_destination
.clone()
.ok_or_else(|| {
"Invalid destination address. Use a Dash address (X.../y...) or Platform address (dash1.../tdash1...)"
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The invalid-destination error message only mentions Core (X/y...) and Platform (dash1/tdash1) addresses, but the screen now supports Shielded (dash1z/tdash1z) and Identity destinations too. This message will be misleading for users entering those address types (or making minor typos). Update the message to include all supported destination kinds.

Suggested change
"Invalid destination address. Use a Dash address (X.../y...) or Platform address (dash1.../tdash1...)"
"Invalid destination. Use a Dash Core address (X.../y...), Platform address (dash1.../tdash1...), Shielded address (dash1z.../tdash1z...), or Identity destination."

Copilot uses AI. Check for mistakes.
Comment thread src/model/send_routing.rs
Comment on lines +421 to +425
ValidatedAddress::Identity { .. },
) => {
check_balance(balance_duffs, amount, true)?;

let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Core→Identity routing uses resolved_identity without verifying it matches the destination identity ID (the destination pattern ignores the id). A mismatched resolved_identity could top up the wrong identity if a caller passes the wrong object. Capture the destination id in the match and validate qi.identity.id() == id (or similar), returning a routing error on mismatch.

Suggested change
ValidatedAddress::Identity { .. },
) => {
check_balance(balance_duffs, amount, true)?;
let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;
ValidatedAddress::Identity { id, .. },
) => {
check_balance(balance_duffs, amount, true)?;
let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;
// Ensure the resolved identity matches the destination identity ID
if qi.identity.id() != id {
return Err(SendRoutingError::IdentityNotResolved);
}

Copilot uses AI. Check for mistakes.
Comment thread src/model/send_routing.rs
Comment on lines +587 to +593
ValidatedAddress::Identity { .. },
) => {
let total_balance: u64 = addresses.iter().map(|(_, _, b)| *b).sum();
check_balance(total_balance, amount, false)?;

let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Platform→Identity routing also uses resolved_identity directly (destination match ignores the id), so a caller could accidentally fund the wrong identity if resolved_identity doesn’t correspond to the destination. Capture the destination id in the match arm and validate it matches the QualifiedIdentity before constructing TopUpIdentityFromPlatformAddresses.

Suggested change
ValidatedAddress::Identity { .. },
) => {
let total_balance: u64 = addresses.iter().map(|(_, _, b)| *b).sum();
check_balance(total_balance, amount, false)?;
let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;
ValidatedAddress::Identity { id, .. },
) => {
let total_balance: u64 = addresses.iter().map(|(_, _, b)| *b).sum();
check_balance(total_balance, amount, false)?;
let qi = resolved_identity.ok_or(SendRoutingError::IdentityNotResolved)?;
if qi.id() != &id {
return Err(SendRoutingError::IdentityNotResolved);
}

Copilot uses AI. Check for mistakes.
Comment thread src/mcp/tools/wallet.rs
Comment on lines +467 to +476
#[derive(Serialize, schemars::JsonSchema)]
pub struct WalletFundsSendOutput {
/// Which route was taken (e.g. "core->core", "platform->shielded")
route: String,
/// Amount sent (in the source's native unit)
amount: u64,
/// Transaction ID if available (Core-to-Core sends)
txid: Option<String>,
/// Number of sequential tasks dispatched (>1 for multi-address Platform-to-Shielded)
tasks_dispatched: u32,
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

WalletFundsSendOutput.amount is documented as “in the source's native unit”, but the tool always returns param.amount_duffs even when the source is non-Core (where the routed amount is credits). Either rename the output field to amount_duffs (and document it as duffs), or return the actual amount passed into SendRequest (credits for non-Core) and/or include both units to avoid ambiguous logging.

Copilot uses AI. Check for mistakes.
Comment thread docs/MCP.md
Comment on lines 74 to 77
| `platform_addresses_list` | `wallet_id`, `network`? | `det-cli platform-addresses-list` | Fetch platform address balances (credits and nonces) |
| `core_funds_send` | `wallet_id`, `address`, `amount_duffs`, `network` | `det-cli core-funds-send` | Send DASH from a wallet to an address (amount in duffs) |
| `wallet_funds_send` | `wallet_id`, `address`, `amount_duffs`, `network`, `source`?, `identity_id`? | `det-cli wallet-funds-send` | Unified send: any source (core/platform/shielded/identity) to any destination address type |
| `platform_withdrawals_get` | `status`?, `network`? | `det-cli platform-withdrawals-get` | Query Platform withdrawal documents (`"queued"` or `"completed"`) |
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The wallet_funds_send table entry claims “any source … to any destination address type”, but current implementation has important limitations (e.g., Platform-source routes that need fee context error out, and Core/Platform→Shielded operations ignore the provided shielded destination because they shield into the wallet’s own pool). Update this row (or add a nearby note) to reflect supported routes/limitations so MCP users don’t assume funds will go to an arbitrary shielded address.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +72
### US-4: Amount unit clarity
**As** any caller, **I want** the routing function to accept a single canonical unit and handle conversion internally, **so that** I do not need to know whether the destination expects duffs or credits.

**Acceptance criteria**:
- The routing function accepts amount in **duffs** (the L1 base unit, universally understood).
- When the resolved route requires credits (platform transfers, identity top-ups), the function converts using `CREDITS_PER_DUFF` (currently 1000).
- The MCP tool documents that `amount_duffs` is always in duffs regardless of destination type.

## 5. Function Signature (Conceptual)

```
resolve_send(
source: SendSource,
destination: ValidatedAddress,
amount_duffs: u64,
context: &SendContext, // wallet refs, network, fee config
) -> Result<BackendTask, SendRoutingError>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

US-4 / function signature in this requirements doc says resolve_send() accepts amount_duffs and converts internally, but the implemented SendRequest.amount is documented/used as “native unit” (duffs for Core, credits otherwise) and callers (UI/MCP) perform conversion before calling. Either update this doc to match the implemented API, or adjust resolve_send() to take a canonical duffs amount and handle conversion internally as specified here.

Copilot uses AI. Check for mistakes.
Comment thread src/model/send_routing.rs
Comment on lines +360 to +368
Ok(SendResult::Single(BackendTask::CoreTask(
CoreTask::SendWalletPayment {
wallet,
request: WalletPaymentRequest {
recipients: vec![recipient],
subtract_fee_from_amount: false,
memo: None,
override_fee: None,
},
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Core→Core routing always builds WalletPaymentRequest with subtract_fee_from_amount: false. Since the UI and other callers may need “subtract fee” semantics (especially for “send max”), this should be configurable via SendRequest (or a dedicated field for Core payment options) rather than hard-coded.

Copilot uses AI. Check for mistakes.
lklimek and others added 2 commits April 1, 2026 09:27
…s_send

Platform-to-Platform, Platform-to-Identity, Platform-to-Core, and
Platform-to-Shielded routes require a FeeContext for proper fee
estimation and platform address allocation. Without it, resolve_send()
returns MissingFeeContext errors for all Platform source routes.

Adds build_fee_context() mirroring the UI's SendScreen::build_fee_context()
logic to construct the correct FeeContext variant based on source/destination
combination.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t_funds_send

Remove 12 single-route send tools now superseded by wallet_funds_send:
- core_funds_send (Core->Core)
- identity_credits_topup (Core->Identity)
- identity_credits_topup_from_platform (Platform->Identity)
- identity_credits_transfer (Identity->Identity)
- identity_credits_withdraw (Identity->Core)
- identity_credits_to_address (Identity->Platform)
- shielded_shield_from_core (Core->Shielded)
- shielded_shield_from_platform (Platform->Shielded)
- shielded_transfer (Shielded->Shielded)
- shielded_unshield (Shielded->Platform)
- shielded_withdraw (Shielded->Core)

Removes identity.rs and shielded.rs tool modules entirely (no remaining
tools), cleans up server.rs registrations, and updates docs/MCP.md.

The validate_address helper is now #[cfg(test)] since only tests use it;
validate_credits is removed (no tests referenced it).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lklimek lklimek changed the title feat(model): unified send routing with auto-detect destination feat(mcp): unified send routing with auto-detect destination Apr 1, 2026
@lklimek lklimek marked this pull request as ready for review April 1, 2026 11:09
@thepastaclaw
Copy link
Copy Markdown
Collaborator

thepastaclaw commented Apr 1, 2026

✅ Review complete (commit 673fb7d)

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

PR #808 replaces 14 individual MCP send tools with a unified wallet_funds_send tool backed by a pure resolve_send() routing function. The routing logic is sound with 38 unit tests and clean typed errors. Two issues are blocking: the PR violates its own backward-compatibility requirement (US-3) by removing core_funds_send, and CLI docs reference deleted tools. Three design gaps are significant: Core/Platform→Shielded routes silently discard the user-provided destination address, sequential task dispatch can partially complete without reporting which steps succeeded, and all routing errors are flattened to a single MCP error code.

Reviewed commit: 69f9606

🔴 3 blocking | 🟡 4 suggestion(s) | 💬 1 nitpick(s)

8 additional findings

🔴 blocking: PR violates its own backward-compatibility requirement (US-3)

src/mcp/tools/wallet.rs (line 1)

🔴 blocking: CLI.md references removed tools

docs/CLI.md (line 1)

🔴 blocking: Core→Shielded and Platform→Shielded silently discard the destination address

src/mcp/tools/wallet.rs (line 1)

🟡 suggestion: Sequential dispatch reports no partial progress on failure

src/mcp/tools/wallet.rs (line 1)

🟡 suggestion: All SendRoutingError variants mapped to McpToolError::InvalidParam

src/mcp/tools/wallet.rs (line 1)

🟡 suggestion: Core→Core hardcodes subtract_fee_from_amount: false — no way to send full balance

src/mcp/tools/wallet.rs (line 1)

🟡 suggestion: build_fee_context logic duplicated between MCP tool and UI

src/mcp/tools/wallet.rs (line 1)

💬 nitpick: platform_shortfall_error always returns TooManyInputs regardless of cause

src/mcp/tools/wallet.rs (line 1)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/mcp/tools/wallet.rs`:
- [BLOCKING] line 1: PR violates its own backward-compatibility requirement (US-3)
- [BLOCKING] line 1: Core→Shielded and Platform→Shielded silently discard the destination address
- [SUGGESTION] line 1: Sequential dispatch reports no partial progress on failure
- [SUGGESTION] line 1: All SendRoutingError variants mapped to McpToolError::InvalidParam
- [SUGGESTION] line 1: Core→Core hardcodes subtract_fee_from_amount: false — no way to send full balance
- [SUGGESTION] line 1: build_fee_context logic duplicated between MCP tool and UI

In `docs/CLI.md`:
- [BLOCKING] line 1: CLI.md references removed tools

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

This push is a merge of v1.0-dev into the feature branch (pulls in #767 and #809). No new feature code — just branch synchronization. All prior review findings from the initial review (backward-compat break, shielded address discard, error mapping, subtract_fee_from_amount) remain unaddressed and still apply.

Reviewed commit: 673fb7d

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants