Skip to content

enable sending to federated address#821

Open
leofelix077 wants to merge 28 commits into
mainfrom
lf-enable-send-federated-address
Open

enable sending to federated address#821
leofelix077 wants to merge 28 commits into
mainfrom
lf-enable-send-federated-address

Conversation

@leofelix077
Copy link
Copy Markdown
Collaborator

@leofelix077 leofelix077 commented Apr 15, 2026

Closes #807

tested on iOS Device - Build 1.13.25 (1776460485)


Untitled.mp4

Screenshot 2026-04-17 at 18 23 57 Screenshot 2026-04-17 at 18 24 00
Screenshot 2026-04-17 at 18 24 08 Screenshot 2026-04-17 at 18 24 12
Screenshot 2026-04-17 at 18 24 26 Screenshot 2026-04-17 at 18 24 32

Checklist

PR structure

  • This PR does not mix refactoring changes with feature changes (break it down into smaller PRs if not).
  • This PR has reasonably narrow scope (break it down into smaller PRs if not).
  • This PR includes relevant before and after screenshots/videos highlighting these changes.
  • I took the time to review my own PR.

Testing

  • These changes have been tested and confirmed to work as intended on Android.
  • These changes have been tested and confirmed to work as intended on iOS.
  • I have tried to break these changes while extensively testing them.
  • This PR adds tests for the new functionality or fixes.

Release

  • This is not a breaking change.
  • This PR updates existing JSDocs when applicable.
  • This PR adds JSDocs to new functionalities.
  • I've checked with the product team if we should add metrics to these changes.
  • I've shared relevant before and after screenshots/videos highlighting these changes with the design team and they've approved the changes.

@leofelix077 leofelix077 self-assigned this Apr 15, 2026
@leofelix077 leofelix077 added wip work in progress don't review yet Work in Progress / Draft PR / Code Review adjustments being worked on labels Apr 15, 2026
Copilot AI review requested due to automatic review settings April 15, 2026 19:12
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

Enables sending to Stellar federation addresses (user*domain) by preserving the original federation address for display while using the resolved G... public key for transaction building.

Changes:

  • Added federationAddress to the transaction settings store and propagated it through send flow screens for display and recents.
  • Updated send UI (amount/review/processing) to show federation address when available.
  • Added a new E2E flow to validate federation address send on mainnet and added input search debounce.

Reviewed changes

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

Show a summary per file
File Description
src/ducks/transactionSettings.ts Adds federationAddress state + setter to track original federation address separately from resolved recipient key.
src/components/screens/SendScreen/screens/TransactionProcessingScreen.tsx Uses federation address (when present) when saving “recent addresses” after a send completes.
src/components/screens/SendScreen/screens/TransactionAmountScreen.tsx Displays federation address in the recipient row when present.
src/components/screens/SendScreen/components/SendReviewBottomSheet.tsx Displays federation address + resolved/truncated G... key in the review sheet.
src/components/screens/SendScreen/SendSearchContacts.tsx Adds debounced search; attempts to store resolved address for transactions while keeping federation for display.
e2e/flows/transactions/SendFederatedAddress.yaml Adds an E2E flow covering federation resolution and a complete send.

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

* @param {string} address - The recipient address
* @param {string} address - The recipient address (resolved G... public key)
*/
saveRecipientAddress: (address) => set({ recipientAddress: address }),
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

saveRecipientAddress updates recipientAddress but leaves federationAddress untouched. This can lead to stale federation UI (and recent address saving) when other entry points set the recipient without also clearing federation (e.g., TransactionAmountScreen clears recipient on mount but not federation). Consider clearing federationAddress inside saveRecipientAddress (or providing a single setter that updates both) so state can’t become inconsistent.

Suggested change
saveRecipientAddress: (address) => set({ recipientAddress: address }),
saveRecipientAddress: (address) =>
set({ recipientAddress: address, federationAddress: "" }),

Copilot uses AI. Check for mistakes.
Comment thread src/ducks/transactionSettings.ts Outdated
Comment on lines 104 to 107
} else if (transactionHash) {
setStatus(TransactionStatus.SENT);
addRecentAddress(recipientAddress);
addRecentAddress(federationAddress || recipientAddress);
} else if (isContractAddress && !isSubmitting) {
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Saving federationAddress into recents will break re-sending from the Recent list: loadRecentAddresses persists only the raw string, and selecting a recent federated address currently bypasses searchAddress/federation resolution, so the transaction flow can end up with an unresolved user*domain in recipientAddress. Consider storing the resolved G... address in recents (optionally alongside the federation address for display), or ensure selecting a recent federated address triggers resolution before continuing.

Copilot uses AI. Check for mistakes.
Comment on lines 106 to +112
(text: string) => {
setAddress(text);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
searchAddress(text);

if (searchDebounceRef.current) {
clearTimeout(searchDebounceRef.current);
}

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

With the new debounce, the send recipient store state isn’t cleared until the delayed searchAddress(text) runs. That means searchError / isValidDestination from a previous search can remain visible for ~300ms while the user is typing. Consider clearing the relevant send-recipient state immediately on input change (e.g., add an action like clearSearchState() / setSearching(true) and call it here) so the UI reflects the new input right away.

Copilot uses AI. Check for mistakes.
Comment thread src/components/screens/SendScreen/SendSearchContacts.tsx Outdated
Comment thread src/components/screens/SendScreen/screens/TransactionAmountScreen.tsx Outdated
@leofelix077 leofelix077 added enhancement New feature or request and removed wip work in progress don't review yet Work in Progress / Draft PR / Code Review adjustments being worked on labels Apr 17, 2026
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

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.


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

Comment thread src/ducks/sendRecipient.ts Outdated
Comment on lines +305 to +308
searchError: null,
isValidDestination: false,
isDestinationFunded: null,
isSearching: true,
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

clearSearchState sets isSearching: true and leaves destinationAddress/federationAddress untouched. If the pending debounced searchAddress call is cancelled (e.g., on unmount), the store can be left permanently “searching” with stale destination data. Consider resetting isSearching to false here (or introducing a separate debouncing flag in the component) and clearing destinationAddress/federationAddress to keep the store internally consistent.

Suggested change
searchError: null,
isValidDestination: false,
isDestinationFunded: null,
isSearching: true,
destinationAddress: "",
federationAddress: "",
searchError: null,
isValidDestination: false,
isDestinationFunded: null,
isSearching: false,

Copilot uses AI. Check for mistakes.
Comment on lines 77 to 81
searchAddress: mockSearchAddress,
addRecentAddress: mockAddRecentAddress,
setDestinationAddress: mockSetDestinationAddress,
clearSearchState: jest.fn(),
resetSendRecipient: mockReset,
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The SendSearchContacts tests’ useSendRecipientStore mock only adds clearSearchState, but the component now also reads isSearching, isValidDestination, and isDestinationFunded from the store. The mock should provide realistic defaults for these fields to avoid the component taking different render paths in tests vs production. Additionally, the module mock for helpers/stellar should export isFederationAddress now that the component imports/uses it; otherwise any test that triggers handleContactPress will throw. Finally, since search is now debounced via setTimeout, assertions that searchAddress was called may need fake timers / advancing timers to avoid flakiness.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/android-e2e.yml Outdated
Comment thread .github/workflows/ios-e2e.yml Outdated
Comment thread src/ducks/sendRecipient.ts Outdated
Comment thread __tests__/components/screens/SendScreen/SendSearchContacts.test.tsx Outdated
Comment thread src/ducks/sendRecipient.ts
Comment thread src/ducks/sendRecipient.ts
Comment thread e2e/flows/transactions/SendFederatedAddress.yaml
Comment thread src/components/screens/SendScreen/SendSearchContacts.tsx
Comment thread src/components/screens/SendScreen/SendSearchContacts.tsx
…/freighter-mobile into lf-enable-send-federated-address
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

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.


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

Comment thread src/services/transactionService.ts Outdated
Comment thread src/components/screens/SendScreen/SendSearchContacts.tsx
Comment on lines 294 to +307
{searchResults.length > 0 ? (
<SearchSuggestionsList
suggestions={searchResults}
onContactPress={handleContactPress}
onContactPress={(contactAddress, name) => {
handleContactPress(contactAddress, name);
}}
/>
) : (
recentAddresses.length > 0 && (
<RecentContactsList
transactions={recentAddresses}
onContactPress={handleContactPress}
onContactPress={(contactAddress, name) => {
handleContactPress(contactAddress, name);
}}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

handleContactPress is async, but it’s invoked from the list callbacks without awaiting/voiding the returned promise. With @typescript-eslint/no-floating-promises enabled elsewhere in this file, this is likely to trigger linting and can also hide unhandled rejections. Prefer onContactPress={(a, n) => void handleContactPress(a, n)} (or make the wrapper async and await).

Copilot uses AI. Check for mistakes.
@leofelix077
Copy link
Copy Markdown
Collaborator Author

@CassioMG I opened a separate issue for the extension for the memo type inference bug listed on the analysis stellar/freighter#2717 / https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0002.md

Comment thread src/helpers/stellar.ts Fixed
Comment thread src/services/transactionService.ts Outdated
Comment thread src/components/screens/SendScreen/SendSearchContacts.tsx Outdated
Comment thread src/ducks/sendRecipient.ts Outdated
ContactType was defined and set throughout the code but never consumed
for business logic - only checked in test assertions. Removed the enum,
the type property from Contact interface, and all assignments.

Per code review feedback: property was write-only with no actual usage.
Resolved conflicts in:
- SendReviewBottomSheet.tsx: combined imports (added computeTotalFeeXlm, isSorobanTransaction from soroban helpers)
- TransactionAmountScreen.tsx: combined imports (added isSorobanTransaction)
- TransactionTokenScreen.tsx: kept ContactRow for recipient display (part of federated address feature)

All conflicts resolved to preserve federated address functionality while
integrating main's fee breakdown and Soroban transaction improvements.
Comment thread src/services/transactionService.ts Outdated
Comment on lines +401 to +421
if (memoType === "id" && /^\d+$/.test(memo)) {
// Memo.id validates numeric range (0..2^64-1); wrap to avoid crash on out-of-range values
try {
transactionBuilder.addMemo(Memo.id(memo));
} catch {
transactionBuilder.addMemo(Memo.text(memo));
}
} else if (memoType === "hash") {
try {
const hashBytes = Buffer.from(memo, "base64");
if (hashBytes.length === 32) {
transactionBuilder.addMemo(Memo.hash(hashBytes));
} else {
transactionBuilder.addMemo(Memo.text(memo));
}
} catch {
transactionBuilder.addMemo(Memo.text(memo));
}
} else {
transactionBuilder.addMemo(Memo.text(memo));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Silent memo-type downgrade

When Memo.id(memo) validation fails (non-numeric, > 2^64−1), the catch falls through to Memo.text(memo) at line 406. Same shape for memo_type:"hash" with invalid base64 (lines 414, 417) and memo_type:"return" (bare else at line 420).

For a federation handle that points to a custodial/exchange address with memo_type:"id", the receiving exchange routes deposits by memo type. A silent downgrade to text means the transaction succeeds on-chain but the funds aren't routed to the user's sub-account — they land in the omnibus address, and recovery is manual support / KYC / possibly weeks. I'd rather have the wallet fail-loud than rewrite the memo type.

Sketch (not prescriptive): logger.warn("buildPaymentTransaction", "Federation memo failed validation", { memoType, error }) to capture a Sentry breadcrumb, then surface an Alert — something like "We couldn't process the memo returned by the federation server. The recipient may not credit this transfer correctly. Please verify the address or contact the recipient." — and abort the send.

For reference, the extension's parallel PR stellar/freighter#2744 ships a federationMemo.ts helper that validates id / hash / text against SEP-0002, throws on failure, and surfaces a generic user-facing error while Sentry-capturing the diagnostic detail. Not saying we should port it wholesale — just one already-designed pattern in case it fits.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable sending to federated address

4 participants