Skip to content

Comments

feat: Implement Private Receive Functionality#5

Open
toruguera wants to merge 4 commits intomainfrom
feat/receive-integration-provider
Open

feat: Implement Private Receive Functionality#5
toruguera wants to merge 4 commits intomainfrom
feat/receive-integration-provider

Conversation

@toruguera
Copy link
Contributor

This PR implements the private receive flow, allowing users to generate receiving addresses (UTXOs) for receiving confidential transactions. The implementation follows the same patterns as the deposit flow and aligns with the sandbox reference implementation.

Overview

Users can now generate receiving addresses by specifying an amount they wish to receive. The system will:

  • Reserve 5 UTXOs (fixed for optimal privacy and QR code size)
  • Randomly partition the requested amount across the UTXOs
  • Generate CREATE operations for each UTXO
  • Return MLXDR-encoded operations that can be shared with senders

Features

  • Receive Form: Input screen for specifying the amount to receive

    • Displays account, channel, and asset information
    • Amount input with validation
    • "How it works" informational banner
    • Provider connection validation
  • Receive Confirmation: Display screen showing generated receiving addresses

    • Channel and expected amount information
    • MLXDR receiving address (with copy functionality)
    • QR code placeholder (ready for future implementation)
    • List of 5 transaction operations (UTXOs) with addresses and amounts
    • "Waiting for funds" informational message
  • Home Integration: Receive button in private view now navigates to the receive flow

Technical Changes

Backend

  • New handler: src/background/handlers/private/receive.ts

    • Validates wallet, channel, provider, and session
    • Reserves 5 UTXOs using UtxoBasedStellarAccount
    • Partitions amount randomly using partitionAmountRandom utility
    • Creates MoonlightOperation.create operations
    • Converts operations to MLXDR format
    • Returns UTXO list with base64-encoded public keys
  • New types: src/background/handlers/private/receive.types.ts

    • ReceiveRequest: network, channelId, providerId, accountId, amount
    • ReceiveResponse: operationsMLXDR, utxos array, metadata
  • Message system: Added MessageType.Receive to message routing

Frontend

  • New API service: src/popup/api/receive.ts

    • Wrapper for calling the receive background handler
  • New pages:

    • src/popup/pages/receive-page.tsx: Main receive form page
    • src/popup/pages/receive-confirmation-page.tsx: Confirmation and address display
  • New template: src/popup/templates/receive-form-template.tsx

    • Reusable form component for receive flow
  • State management: Added receive routes and state handling

    • Routes: receive, receive-confirmation
    • Actions: goReceive, goReceiveConfirmation, setReceiveFormData, setReceiveResult, clearReceiveData
  • Navigation: Integrated receive button in HomeTemplate private view

Implementation Details

  • Fixed UTXO count: Always uses 5 UTXOs (matching sandbox implementation)
  • Amount partitioning: Uses cryptographically secure random distribution via partitionAmountRandom
  • Error handling: Comprehensive error codes (LOCKED, NOT_FOUND, INSUFFICIENT_UTXOS, etc.)
  • Type safety: Full TypeScript typing throughout
  • Consistency: Follows same patterns as deposit flow for maintainability

Files Changed

New Files:

  • src/background/handlers/private/receive.ts
  • src/background/handlers/private/receive.types.ts
  • src/popup/api/receive.ts
  • src/popup/pages/receive-page.tsx
  • src/popup/pages/receive-confirmation-page.tsx
  • src/popup/templates/receive-form-template.tsx

Modified Files:

  • src/background/messages.ts - Added Receive message type
  • src/background/handler.ts - Registered receive handler
  • src/popup/hooks/state.tsx - Added receive routes and state
  • src/popup/app.tsx - Added receive route handling
  • src/popup/pages/home-page.tsx - Connected receive button
  • src/popup/templates/home-template.tsx - Added receive action prop
  • src/popup/pages/deposit-page.tsx - Fixed chain state response handling

Testing Notes

  • Verify receive flow with valid channel and provider connection
  • Test amount validation (minimum 5 stroops)
  • Confirm UTXO reservation and MLXDR generation
  • Verify error handling for locked wallet, missing channel/provider
  • Test navigation flow from home to receive to confirmation

Future Enhancements

  • Implement QR code generation for MLXDR address
  • Add support for configurable UTXO count (entropy levels)
  • Add copy-to-clipboard for individual UTXO addresses
  • Implement address sharing functionality

Copy link

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 implements a comprehensive private receive functionality for the Moonlight wallet, allowing users to generate receiving addresses (UTXOs) for confidential transactions. The implementation follows established patterns from the deposit flow and includes both backend handlers and frontend UI components.

Changes:

  • Added private receive backend handler with UTXO reservation and random amount partitioning
  • Implemented deposit backend handler with bundle submission to privacy providers
  • Created receive and deposit frontend flows with form and confirmation pages
  • Added cryptographically secure random partitioning utility for privacy enhancement

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/background/handlers/private/receive.ts New handler for generating receive addresses with 5 UTXOs
src/background/handlers/private/receive.types.ts Type definitions for receive requests and responses
src/background/handlers/private/deposit.ts New handler for depositing funds to private channels
src/background/handlers/private/deposit.types.ts Type definitions for deposit requests and responses
src/background/utils/random-partition.ts Cryptographic random partitioning utility for privacy
src/background/services/privacy-provider-client.ts Enhanced token validation and bundle submission
src/background/messages.ts Added Receive and Deposit message types
src/background/handler.ts Registered new handlers
src/popup/pages/receive-page.tsx Receive form page component
src/popup/pages/receive-confirmation-page.tsx Receive confirmation and address display page
src/popup/pages/deposit-page.tsx Deposit form page component
src/popup/pages/deposit-review-page.tsx Deposit review page component
src/popup/templates/receive-form-template.tsx Reusable receive form template
src/popup/templates/deposit-form-template.tsx Reusable deposit form template
src/popup/templates/deposit-review-template.tsx Deposit review template
src/popup/templates/home-template.tsx Integrated receive and deposit buttons in private view
src/popup/api/receive.ts API wrapper for receive handler
src/popup/api/deposit.ts API wrapper for deposit handler
src/popup/hooks/state.tsx Added receive and deposit state management
src/popup/app.tsx Added routing for new pages
deno.json Removed blank lines between script groups
deno.lock Updated dependency versions

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

</Text>
</div>
<Text className="text-sm font-medium whitespace-nowrap">
{parseFloat(utxo.amount) / 10_000_000} XLM
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Manual division by 10_000_000 is used here to convert stroops to XLM, but the codebase uses the toDecimals utility function from @colibri/core for this purpose elsewhere (see home-template.tsx). Consider using toDecimals(BigInt(utxo.amount), 7) instead for consistency and to avoid potential floating-point precision issues.

Copilot uses AI. Check for mistakes.
return {
type: MessageType.Receive,
ok: false,
error: { code: "INVALID_AMOUNT", message: "Invalid deposit amount" },
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The error message says "Invalid deposit amount" but this is the receive handler, not deposit. The message should say "Invalid receive amount" for consistency and to avoid confusion.

Suggested change
error: { code: "INVALID_AMOUNT", message: "Invalid deposit amount" },
error: { code: "INVALID_AMOUNT", message: "Invalid receive amount" },

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +128
// Handle leftover from integer division
const remainderExtra = remaining - assignedExtra;
if (remainderExtra > 0n) {
// Distribute the leftover randomly among parts
const remainderCount = Number(remainderExtra);
const indices = new Set<number>();

// Generate unique random indices to receive +1
while (indices.size < remainderCount && indices.size < partsCount) {
const idx = Number(
randomIntInRangeBigInt(0n, BigInt(partsCount - 1)),
);
indices.add(idx);
}

// Add +1 to the selected indices
for (const idx of indices) {
amounts[idx] += 1n;
}
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The code only handles the case when remainderExtra > 0n but doesn't handle the case when it might be negative (which could theoretically happen due to rounding in integer division). While the mathematical logic suggests this shouldn't occur, defensive programming would suggest either adding an assertion that remainderExtra >= 0n or handling the negative case to prevent potential silent bugs.

Copilot uses AI. Check for mistakes.
sumWeights += weight;
}

// Distribuir o remaining proporcionalmente aos pesos
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Comment is in Portuguese instead of English. The comment should be translated to maintain consistency with the rest of the codebase, which uses English for all comments and documentation.

Suggested change
// Distribuir o remaining proporcionalmente aos pesos
// Distribute the remaining amount proportionally to the weights

Copilot uses AI. Check for mistakes.
>
{copiedIndex === index
? <IconInfoCircle className="h-4 w-4 text-green-400" />
: <IconExternalLink className="h-4 w-4" />}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The icon used for the copy button is misleading. IconExternalLink suggests opening an external link, but the button actually copies the address to clipboard. Consider using IconCopy instead (which is already imported) to match the user's expectation and the MLXDR copy button pattern above.

Copilot uses AI. Check for mistakes.
// Handle leftover from integer division
const remainderExtra = remaining - assignedExtra;
if (remainderExtra > 0n) {
// Distribute the leftover randomly among parts
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Converting a potentially large bigint value to Number on line 113 could cause issues if remainderExtra exceeds Number.MAX_SAFE_INTEGER (2^53 - 1). While this is unlikely in practice given the typical transaction amounts, it would be safer to add a check or use a different approach. Consider adding a guard: if (remainderExtra > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("Remainder too large to distribute") before the conversion.

Suggested change
// Distribute the leftover randomly among parts
// Distribute the leftover randomly among parts
if (remainderExtra > BigInt(Number.MAX_SAFE_INTEGER)) {
throw new Error("Remainder too large to distribute");
}

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +202
const canStartDeposit = Boolean(selectedPrivateChannel?.id) &&
Boolean(selectedPrivateChannel?.selectedProviderId) &&
Boolean(props.isConnected) &&
Boolean(props.onStartDeposit);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The variable canStartDeposit is misleadingly named as it's used to enable/disable both the Receive and Ramp (deposit) buttons, not just deposit. Consider renaming it to something more generic like canStartPrivateAction or canUsePrivateFeatures to better reflect its actual usage across multiple private operations.

Suggested change
const canStartDeposit = Boolean(selectedPrivateChannel?.id) &&
Boolean(selectedPrivateChannel?.selectedProviderId) &&
Boolean(props.isConnected) &&
Boolean(props.onStartDeposit);
const canUsePrivateFeatures = Boolean(selectedPrivateChannel?.id) &&
Boolean(selectedPrivateChannel?.selectedProviderId) &&
Boolean(props.isConnected) &&
Boolean(props.onStartDeposit);
// Legacy alias: used to gate multiple private actions (e.g. Receive and Ramp)
const canStartDeposit = canUsePrivateFeatures;

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +62
useMemo(() => {
if (!formData) return;
getPrivateChannels({ network })
.then((res) => {
if (res.ok) {
setPrivateChannels({
channels: res.channels,
selectedChannelId: res.selectedChannelId,
});
}
})
.catch((err) => {
console.error("Failed to load private channels", err);
});
}, [network, formData]);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

useMemo is being misused here for side effects. The useMemo hook is intended for memoizing computed values, not for executing side effects like API calls. This should be changed to useEffect instead. Using useMemo for side effects can lead to unpredictable behavior since React may choose to discard memoized values at any time.

Copilot uses AI. Check for mistakes.
provider={selectedProvider}
amount={amount}
setAmount={setAmount}
maxAmount="1,000"
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The hardcoded maxAmount="1,000" appears arbitrary and doesn't seem to be based on any actual validation or constraint. Consider either removing this prop if there's no real maximum limit for receiving, or implementing proper validation based on actual system constraints. The comma formatting is also inconsistent with how amounts are typically represented in the codebase.

Suggested change
maxAmount="1,000"

Copilot uses AI. Check for mistakes.
Comment on lines 7 to 9
"pre-commit": "deno run --allow-run --allow-read src/scripts/pre-commit.ts",
"prepare": "deno run --allow-read --allow-write src/scripts/install-hooks.ts",

"fmt": "deno fmt",
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Blank lines were removed between script groups. While this doesn't affect functionality, it reduces readability by removing visual separation between different groups of scripts (build scripts, git hooks, and formatting/linting tools).

Copilot uses AI. Check for mistakes.
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.

1 participant