Skip to content

feat: add on-chain .skr resolution in sample wallet#282

Open
lopeselio wants to merge 6 commits into
magicblock-labs:mainfrom
lopeselio:feat/281-skr-name-resolution
Open

feat: add on-chain .skr resolution in sample wallet#282
lopeselio wants to merge 6 commits into
magicblock-labs:mainfrom
lopeselio:feat/281-skr-name-resolution

Conversation

@lopeselio
Copy link
Copy Markdown
Contributor

@lopeselio lopeselio commented Apr 22, 2026

With recent work in this repo focused on Solana Mobile Wallet Adapter support for Seeker, it makes sense to improve identity UX for the same audience by adding native .skr resolution in the SDK.

A large and growing number of Seeker users interact with apps built on Solana Unity SDK, including games like Cryptara Conquest. In these experiences, users should be able to transfer assets, complete purchases, and trade with other players using human-readable .skr ANS names (AllDomains), instead of manually copying and pasting wallet addresses.

This would significantly reduce friction, improve usability on mobile, and better align the SDK with how Seeker users actually interact on-chain.

Support .skr name resolution for transfer and receive flows without requiring a backend service. Resolve wrapped domains to the actual NFT holder and query mainnet for ANS lookups to improve Seeker mobile UX.

check video below ⬇️

Refs #281

Closes #281

⚠️ NOTE: This PR adds native on-chain .skr resolution in the sample wallet and does not depend on any backend resolver service.

Status Type ⚠️ Core Change Issue
Ready Feature Yes #281

Problem

The sample wallet currently expects raw base58 addresses for transfers and does not natively resolve .skr names.
This creates friction for Seeker/mobile users and fails common UX expectations for human-readable identity flows.

Additionally:

  • transfer flow cannot reliably use .skr out of the box
  • receive flow displays only wallet addresses, not .skr identity
  • backend resolver approaches add runtime/deployment dependency

Solution

Implemented native on-chain ANS resolution in sample wallet flows:

  • Added SkrAddressResolutionClient in sample scripts for on-chain .skr lookups
  • Transfer flow:
    • accepts .skr in recipient field
    • resolves .skr -> wallet before transaction submission
    • blocks submission with clear error if unresolved
  • Receive flow:
    • resolves connected wallet address -> .skr
    • shows .skr when available, otherwise falls back to pubkey
  • Wrapped-domain handling:
    • if name record owner is an NFT record PDA, resolves to the actual NFT holder wallet
  • Resolution network behavior:
    • uses Mainnet RPC for ANS resolution (where .skr lives)
    • leaves normal wallet/transaction cluster behavior unchanged

Before & After Screenshots

BEFORE:

  • transfer with .skr fails / unresolved behavior
  • receive screen shows only base58 pubkey

AFTER:

  • valid .skr resolves and transfer proceeds
  • invalid/nonexistent .skr shows Unable to resolve <domain> and aborts submission
  • receive screen displays .skr when available
PR_SKR.-.Large.540p.mov

0.001 SOL transfer from lopeselio.skr -> magellan.skr tx on devnet

Other changes (e.g. bug fixes, small refactors)

  • Refactored transfer screen flow to resolve recipient address before creating transfer instructions
  • Added recipient validation path for resolved/invalid values
  • Added sample services folder and Unity meta files for resolver service

Deploy Notes

No backend/service deployment required for .skr resolution in this feature.

New scripts:

  • Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs : native on-chain .skr resolution service for transfer and receive flows
    New dependencies:
  • none

Summary by CodeRabbit

  • New Features

    • Receive screen now shows resolved .skr domain names for wallet addresses (loads immediately, then updates when available).
    • Transfer screen accepts .skr domains as recipients and resolves them automatically.
    • Better handling for NFT transfers (ensures single-NFT transfers) and clearer amount handling for SOL/tokens.
  • Bug Fixes / UX

    • Async resolution no longer overwrites changed or hidden fields, avoiding stale display updates.

Support .skr name resolution for transfer and receive flows without requiring a backend service. Resolve wrapped domains to the actual NFT holder and query mainnet for ANS lookups to improve Seeker mobile UX.

Refs magicblock-labs#281
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d2d0d51-f46f-466c-b67b-c39acdc4d4bc

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef33bb and a4c3102.

📒 Files selected for processing (3)
  • Runtime/codebase/SkrAddressResolutionClient.cs
  • Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs
  • Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs

Walkthrough

Adds native on-chain .skr name resolution: new static resolver client for forward and reverse lookups on Mainnet, and UI integrations in the sample wallet's Receive and Transfer screens to resolve/display .skr names and validate recipient inputs before transfers.

Changes

Cohort / File(s) Summary
Screen UI Integration
Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs, Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs
ReceiveScreen: store/display raw public key immediately, start async reverse lookup, cancel stale updates via request-version and UI state checks. TransferScreen: convert transfer flow to async, resolve .skr recipient names, add explicit recipient validation (CheckRecipientAddress), separate NFT checks (CheckNftInput), safer amount parsing (CheckInput with InvariantCulture), and track/reset _ownedTokenAmount.
Core Resolution Client
Runtime/codebase/SkrAddressResolutionClient.cs
New static SkrAddressResolutionClient exposing ResolveDomainToAddress and ResolveAddressToDomain. Implements on-chain forward and reverse resolution using PDA derivation, mainnet RPC fetches, expiry/ownership checks, wrapped-owner (NFT) resolution, memcmp-filtered program account queries for reverse lookup, bidirectional verification, caches (hit/miss TTL) and in-flight task coalescing.
Unity Metadata
Runtime/codebase/SkrAddressResolutionClient.cs.meta
Added Unity .meta sidecar with assigned GUID and default MonoImporter settings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add on-chain .skr resolution in sample wallet' directly and concisely describes the main change: implementing on-chain .skr name resolution in the sample wallet.
Description check ✅ Passed The PR description is comprehensive, covering problem, solution, deployment notes, and includes a demo video. It follows the template structure with status/type/issue table, problem/solution sections, and deploy notes, though before/after screenshots are referenced rather than embedded.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #281: native on-chain .skr resolution for transfer flow (resolves .skr input before submission with error handling), receive flow (reverse lookup and display), wrapped-domain handling, Mainnet RPC usage, and no backend dependencies.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #281 objectives: SkrAddressResolutionClient implements on-chain resolution, TransferScreen/ReceiveScreen refactorings support the transfer/receive flows, and meta files support the new resolver service.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@lopeselio
Copy link
Copy Markdown
Contributor Author

@Kuldotha requesting your review!

@lopeselio
Copy link
Copy Markdown
Contributor Author

In this feature, i have tried to resolve it directly on-chain in Unity (C#), with no backend.

  • Domain -> wallet (.skr -> pubkey)

    • Derive ANS PDAs for origin/TLD/domain account.
    • Read name record account on Mainnet RPC.
    • Parse owner from account data.
    • If owner is wrapped-domain nft_record PDA, resolve further to actual NFT holder wallet.
    • Use that final wallet as transfer destination.
  • Wallet -> domain (pubkey -> .skr)

    • Query ANS program accounts with memcmp filters (owner, .skr parent).
    • Derive reverse-lookup account(s).
    • Parse and return label + .skr for receive display.

So compared to alternatives:

  • Not local backend
  • Not hosted backend
  • Not base58-only UX
  • It’s native, on-chain, mobile-friendly resolution inside the SDK sample flow.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Samples`~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs:
- Around line 48-57: ResolveWalletDisplayName currently does a fire-and-forget
reverse lookup and directly sets publicKey_txt.text, risking exceptions or stale
UI updates; change it to capture the walletAddress at invocation (e.g., local
var requestedAddress = walletAddress), wrap the await
SkrAddressResolutionClient.ResolveAddressToDomain(...) in try/catch to
swallow/log errors, and only assign publicKey_txt.text = domain if domain is
non-empty AND requestedAddress still matches the currently displayed wallet (the
same value the UI is showing) so the pubkey fallback remains intact and stale
results are not applied.

In `@Samples`~/Solana Wallet/Scripts/example/screens/TransferScreen.cs:
- Around line 43-58: ResolveRecipientAddress() should be validated for emptiness
once before branching and amount/funds validation (CheckInput) should be applied
to every transfer path; update the control flow around ResolveRecipientAddress,
TransferNft, TransferSol, and TransferToken so that you return early if
recipientAddress is null/empty, then call CheckInput(recipientAddress) (or the
appropriate amount/funds validator) for every branch (including the NFT branch)
before invoking TransferNft/TransferSol/TransferToken.

In `@Samples`~/Solana
Wallet/Scripts/example/services/SkrAddressResolutionClient.cs:
- Around line 44-55: Both public methods ResolveDomainToAddress and
ResolveAddressToDomain should swallow RPC/network failures and return null so
callers can preserve fallback UX: keep the existing early null-for-empty-input
checks, but wrap the calls to ResolveDomainToAddressOnChain(domain) and
ResolveAddressToDomainOnChain(address) in try/catch, log or ignore the
exception, and return null on any caught exception instead of letting it
propagate.
- Around line 296-307: The TrySplitDomain method currently accepts multi-label
domains (e.g., "foo.bar.skr") and returns label="bar" which can incorrectly
resolve "bar.skr"; change TrySplitDomain to only accept exactly two-dot parts
(parts.Length must equal 2) and require the tld to be "skr" (case-insensitive)
before setting label and tld; return false for any other length or non-".skr"
TLD to prevent resolving unintended multi-label domains and ensure label/tld
remain non-empty.
- Around line 133-165: The reverse lookup loop currently returns any label it
finds without verifying ownership or expiry; update the loop (after
ExtractUtf8Label and before returning) to mirror the forward-resolution checks:
call ResolveWrappedDomainOwner (or the equivalent forward-resolution helper) for
the candidate nameAccount.Key to obtain the resolved owner and expiresAt, verify
the resolved owner matches the requested owner (handling wrapped/NFT owner PDAs)
and that expiresAt is in the future, and only then return the label; if
verification fails, continue the loop. If scanning all children is too
expensive, use the canonical ANS reverse index but still perform the same
ResolveWrappedDomainOwner and expiry checks on each candidate before returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fec77a6b-0efa-445d-a18d-2382123e4a56

📥 Commits

Reviewing files that changed from the base of the PR and between 74a2179 and 4306c53.

📒 Files selected for processing (5)
  • Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs
  • Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs
  • Samples~/Solana Wallet/Scripts/example/services.meta
  • Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs
  • Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs.meta

Comment thread Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs Outdated
Comment thread Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs Outdated
Comment thread Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs Outdated
Comment thread Runtime/codebase/SkrAddressResolutionClient.cs
Comment thread Runtime/codebase/SkrAddressResolutionClient.cs
Address CodeRabbit findings by guarding async receive UI updates, validating recipient/amount flow across transfer paths, and tightening resolver behavior for failures, strict .skr parsing, and reverse lookup ownership/expiry checks.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs (1)

149-153: ⚠️ Potential issue | 🟠 Major

Avoid a Mainnet-wide .skr scan for receive-screen reverse lookup.

This filter only bounds by the .skr parent, so ResolveAddressToDomainOnChain() can fetch every .skr child account and then perform per-account RPC reads until it finds a match. On mobile/public Mainnet RPC, wallets without an early match can hit timeouts, rate limits, or very slow receive-screen loads. Prefer a bounded on-chain reverse index/candidate lookup, then keep the ownership/expiry verification already added below.

Please verify reverse lookup latency and RPC request volume against a Mainnet wallet with no .skr and one with a late-match .skr; expected behavior should stay bounded and not depend on the total number of .skr domains.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Samples`~/Solana
Wallet/Scripts/example/services/SkrAddressResolutionClient.cs around lines 149 -
153, ResolveAddressToDomainOnChain currently calls
NameResolutionRpcClient.GetProgramAccountsAsync with only a parent filter
(AnsProgramId and tldParentAccount) which can return all .skr children and cause
unbounded RPC work; change the query to a bounded reverse-indexed candidate
lookup by adding an additional MemCmp filter keyed to the recipient address (or
its hashed/indexed prefix) so the RPC returns only candidates for that specific
wallet before doing per-account reads, then keep the existing ownership and
expiry checks afterwards; update the GetProgramAccountsAsync call and the
memCmpList construction used by ResolveAddressToDomainOnChain (and any helper
that builds filters for AnsProgramId/tldParentAccount) to include the
address-specific filter so mainnet reverse-lookups are bounded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Samples`~/Solana Wallet/Scripts/example/screens/TransferScreen.cs:
- Around line 52-60: CheckInput currently uses direct parses that throw and
doesn't reject zero/negative values; update CheckInput to use a safe parse
(e.g., decimal.TryParse or double.TryParse with appropriate
NumberStyles/CultureInfo) for the amount field, return false and set errorTxt
when parsing fails, and explicitly verify parsedAmount > 0 (reject
zero/negative) before allowing TransferSol or TransferToken to run; keep the
conditional flow around _transferTokenAccount/TransferSol/TransferToken but rely
on the new CheckInput behavior so invalid or non-numeric amounts never reach
TransferSol or TransferToken.

In `@Samples`~/Solana
Wallet/Scripts/example/services/SkrAddressResolutionClient.cs:
- Around line 194-198: The code returns a reverse-extracted label without
ensuring that label + SuffixSkr forward-resolves to the same name account that
was verified; update the logic in SkrAddressResolutionClient (around
ExtractUtf8Label and where nameAccount is defined) to construct the candidate
name (e.g., $"{label}.{SuffixSkr}"), derive or lookup its nameAccount/public
key, perform the same owner/account verification used earlier, and only return
the label if the derived/forward-resolved nameAccount matches the original
verified nameAccount; otherwise continue/ignore the reverse record.

---

Duplicate comments:
In `@Samples`~/Solana
Wallet/Scripts/example/services/SkrAddressResolutionClient.cs:
- Around line 149-153: ResolveAddressToDomainOnChain currently calls
NameResolutionRpcClient.GetProgramAccountsAsync with only a parent filter
(AnsProgramId and tldParentAccount) which can return all .skr children and cause
unbounded RPC work; change the query to a bounded reverse-indexed candidate
lookup by adding an additional MemCmp filter keyed to the recipient address (or
its hashed/indexed prefix) so the RPC returns only candidates for that specific
wallet before doing per-account reads, then keep the existing ownership and
expiry checks afterwards; update the GetProgramAccountsAsync call and the
memCmpList construction used by ResolveAddressToDomainOnChain (and any helper
that builds filters for AnsProgramId/tldParentAccount) to include the
address-specific filter so mainnet reverse-lookups are bounded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f80a7ed7-d4ee-47e0-b997-5830232754d2

📥 Commits

Reviewing files that changed from the base of the PR and between 4306c53 and b540cb6.

📒 Files selected for processing (3)
  • Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs
  • Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs
  • Samples~/Solana Wallet/Scripts/example/services/SkrAddressResolutionClient.cs

Comment thread Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs Outdated
Comment thread Runtime/codebase/SkrAddressResolutionClient.cs
Address remaining CodeRabbit feedback by preventing invalid transfer amounts from reaching send paths and hardening reverse .skr lookup with owner-bounded filtering plus forward-name consistency validation.
@GabrielePicco GabrielePicco requested a review from Kuldotha April 23, 2026 12:30
@lopeselio
Copy link
Copy Markdown
Contributor Author

lopeselio commented Apr 25, 2026

@Kuldotha hey, i tightened many tests after working on the coderabbit checks. requesting your review

@@ -0,0 +1,351 @@
using System;
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.

This file shouldn't be under samples, otherwise people can only use it when having the samples in their project. Or is this entire file meant to be a sample setup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Kuldotha great callout. agreed this should be reusable outside samples. i’ll move the resolver into runtime so apps can use it without importing sample assets, and keep the sample side as a thin consumer. i’ll push an update in this pr.

Expose SkrAddressResolutionClient under Solana.Unity.SDK so apps can resolve
.skr without importing sample assets. Sample wallet screens now reference the
runtime type; sample-only services folder removed per review feedback.
@lopeselio
Copy link
Copy Markdown
Contributor Author

@Kuldotha i've done the necessary changes. waiting for your feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs (1)

107-111: 🧹 Nitpick | 🔵 Trivial

Clipboard copies raw public key, not displayed domain.

After reverse lookup, publicKey_txt.text shows the .skr domain, but CopyPublicKeyToClipboard always copies the raw public key. This is arguably correct (public keys are more universally accepted), but could confuse users who expect to copy what they see.

Consider either:

  1. Keeping current behavior with a toast like "Public Key copied" (already done)
  2. Adding a separate "Copy domain" option if needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Samples`~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs around lines
107 - 111, The CopyPublicKeyToClipboard method currently always copies the raw
key via Clipboard.Copy(Web3.Instance.WalletBase.Account.PublicKey.ToString()),
which differs from the displayed value in publicKey_txt.text when a .skr domain
is shown; update CopyPublicKeyToClipboard so it copies publicKey_txt.text if it
contains a displayed domain (e.g., endsWith ".skr" or is non-empty and not the
raw key), otherwise fall back to the raw public key, and update the toast text
to a neutral "Copied to clipboard" or include which form was copied; reference
CopyPublicKeyToClipboard, publicKey_txt, Clipboard.Copy, and
Web3.Instance.WalletBase.Account.PublicKey.ToString() when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Runtime/codebase/SkrAddressResolutionClient.cs`:
- Around line 41-42: The NameResolutionRpcClient field is hardcoded to
"https://api.mainnet-beta.solana.com", limiting configurability; change it from
a private static readonly initialization to a configurable static property or an
initialization method so callers can set a custom RPC endpoint before use.
Replace the current private static readonly IRpcClient NameResolutionRpcClient =
ClientFactory.GetClient(...) with either a private static IRpcClient
_nameResolutionRpcClient plus a public static property NameResolutionRpcClient {
get; set; } (or a public static InitializeNameResolutionClient(string url) that
calls ClientFactory.GetClient(url)), ensure existing usages read the
property/field, and add null/validation checks so attempting to use it without
initialization throws a clear exception.
- Around line 110-111: The code currently extracts slices like "var ownerBytes =
rawData.Skip(OwnerOffset).Take(32).ToArray();" which allocates intermediate
enumerables and arrays; replace these with a lower-allocation approach (e.g.,
use Array.Copy(rawData, OwnerOffset, ownerBytes, 0, 32) with a pre-sized
ownerBytes = new byte[32] or use Span<byte> ownerSpan =
rawData.AsSpan(OwnerOffset, 32)) to avoid the Skip/Take/ToArray chain; make the
same change for the other slices referenced in the file (the extractions at the
locations using offsets on rawData on lines analogous to 182, 213, 276, and
296), ensuring you preserve the All(b => b == 0) check by operating on the Span
or the copied fixed-size array.
- Around line 49-56: The catch block in the address resolution method swallows
all exceptions and returns null, which hampers debugging; update the method(s)
that call ResolveDomainToAddressOnChain(domain) to log the caught Exception
before returning null—preferably by injecting an
ILogger<SkrAddressResolutionClient> (e.g. a private readonly _logger) into the
SkrAddressResolutionClient class and replacing the empty catch with catch
(Exception ex) { _logger?.LogDebug(ex, "ResolveDomainToAddressOnChain failed for
domain '{Domain}'", domain); return null; } so you preserve the null-return
behavior while recording exception details for troubleshooting.
- Around line 222-229: The code assumes Data[0] is base64 when calling
Convert.FromBase64String on encodedData from
NameResolutionRpcClient.GetAccountInfoAsync; instead, check the RPC response's
Data array for an encoding indicator (typically Data[1]) and only call
Convert.FromBase64String when that encoding equals "base64" (or handle other
encodings appropriately), otherwise decode using the correct scheme or return an
empty byte[]/throw a clear exception; update the logic around
response?.Result?.Value?.Data, encodedData, and the Convert.FromBase64String
call to perform this defensive encoding check.
- Around line 149-156: The Owner memcmp filter (using OwnerOffset = 40) passed
into NameResolutionRpcClient.GetProgramAccountsAsync is excluding wrapped
domains because wrapped name records store the NFT PDA as the owner; remove the
owner memcmp from the filters list so queries use only the tldParentAccount
filter (AnsProgramId, memCmpList without the owner filter), then after fetching
accounts validate ownership per-account by calling
ResolveWrappedDomainOwner/your existing ownership-check logic to determine the
true wallet owner. Ensure references to OwnerOffset and the owner memcmp are
removed so wrapped domain records are returned and processed.

In `@Samples`~/Solana Wallet/Scripts/example/screens/TransferScreen.cs:
- Around line 188-192: The CheckInput code is fragile because it reparses
ownedAmountTxt.text; instead store the raw token balance when you populate the
UI (introduce a field like _ownedTokenAmount alongside the existing
_ownedSolAmount) and set it at the same time you set ownedAmountTxt.text, then
update CheckInput to use _ownedTokenAmount rather than parsing
ownedAmountTxt.text; update any places that relied on parsing to use the new
_ownedTokenAmount and remove the ulong.TryParse call around ownedAmountTxt.text.

---

Outside diff comments:
In `@Samples`~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs:
- Around line 107-111: The CopyPublicKeyToClipboard method currently always
copies the raw key via
Clipboard.Copy(Web3.Instance.WalletBase.Account.PublicKey.ToString()), which
differs from the displayed value in publicKey_txt.text when a .skr domain is
shown; update CopyPublicKeyToClipboard so it copies publicKey_txt.text if it
contains a displayed domain (e.g., endsWith ".skr" or is non-empty and not the
raw key), otherwise fall back to the raw public key, and update the toast text
to a neutral "Copied to clipboard" or include which form was copied; reference
CopyPublicKeyToClipboard, publicKey_txt, Clipboard.Copy, and
Web3.Instance.WalletBase.Account.PublicKey.ToString() when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98d428ef-3976-477c-bb68-f42c33f2ebf1

📥 Commits

Reviewing files that changed from the base of the PR and between b540cb6 and 5ef33bb.

📒 Files selected for processing (4)
  • Runtime/codebase/SkrAddressResolutionClient.cs
  • Runtime/codebase/SkrAddressResolutionClient.cs.meta
  • Samples~/Solana Wallet/Scripts/example/screens/ReceiveScreen.cs
  • Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs

Comment on lines +41 to +42
private static readonly IRpcClient NameResolutionRpcClient =
ClientFactory.GetClient("https://api.mainnet-beta.solana.com");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded mainnet RPC endpoint limits flexibility.

The static NameResolutionRpcClient uses a hardcoded mainnet URL. This prevents users from configuring custom RPC endpoints (e.g., for rate limits, private nodes, or testing). Consider making this configurable via a static property or initialization method.

♻️ Suggested approach
-        private static readonly IRpcClient NameResolutionRpcClient =
-            ClientFactory.GetClient("https://api.mainnet-beta.solana.com");
+        private static IRpcClient _nameResolutionRpcClient;
+        private static IRpcClient NameResolutionRpcClient => 
+            _nameResolutionRpcClient ??= ClientFactory.GetClient(DefaultMainnetRpcUrl);
+
+        private const string DefaultMainnetRpcUrl = "https://api.mainnet-beta.solana.com";
+
+        /// <summary>
+        /// Configure a custom RPC client for name resolution queries.
+        /// </summary>
+        public static void SetRpcClient(IRpcClient client)
+        {
+            _nameResolutionRpcClient = client;
+        }
📝 Committable suggestion

‼️ IMPORTANT
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.

Suggested change
private static readonly IRpcClient NameResolutionRpcClient =
ClientFactory.GetClient("https://api.mainnet-beta.solana.com");
private static IRpcClient _nameResolutionRpcClient;
private static IRpcClient NameResolutionRpcClient =>
_nameResolutionRpcClient ??= ClientFactory.GetClient(DefaultMainnetRpcUrl);
private const string DefaultMainnetRpcUrl = "https://api.mainnet-beta.solana.com";
/// <summary>
/// Configure a custom RPC client for name resolution queries.
/// </summary>
public static void SetRpcClient(IRpcClient client)
{
_nameResolutionRpcClient = client;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/SkrAddressResolutionClient.cs` around lines 41 - 42, The
NameResolutionRpcClient field is hardcoded to
"https://api.mainnet-beta.solana.com", limiting configurability; change it from
a private static readonly initialization to a configurable static property or an
initialization method so callers can set a custom RPC endpoint before use.
Replace the current private static readonly IRpcClient NameResolutionRpcClient =
ClientFactory.GetClient(...) with either a private static IRpcClient
_nameResolutionRpcClient plus a public static property NameResolutionRpcClient {
get; set; } (or a public static InitializeNameResolutionClient(string url) that
calls ClientFactory.GetClient(url)), ensure existing usages read the
property/field, and add null/validation checks so attempting to use it without
initialization throws a clear exception.

Comment on lines +49 to +56
try
{
return await ResolveDomainToAddressOnChain(domain);
}
catch (Exception)
{
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Silent exception swallowing hinders debugging.

Both public methods catch all exceptions and return null without any logging. While graceful failure is appropriate for resolution APIs, consider adding optional debug logging to aid troubleshooting connectivity or parsing issues.

♻️ Optional: Add conditional debug logging
             try
             {
                 return await ResolveDomainToAddressOnChain(domain);
             }
             catch (Exception)
             {
+#if UNITY_EDITOR
+                UnityEngine.Debug.LogWarning($"[SKR] Forward resolution failed for '{domain}': {ex.Message}");
+#endif
                 return null;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/SkrAddressResolutionClient.cs` around lines 49 - 56, The
catch block in the address resolution method swallows all exceptions and returns
null, which hampers debugging; update the method(s) that call
ResolveDomainToAddressOnChain(domain) to log the caught Exception before
returning null—preferably by injecting an ILogger<SkrAddressResolutionClient>
(e.g. a private readonly _logger) into the SkrAddressResolutionClient class and
replacing the empty catch with catch (Exception ex) { _logger?.LogDebug(ex,
"ResolveDomainToAddressOnChain failed for domain '{Domain}'", domain); return
null; } so you preserve the null-return behavior while recording exception
details for troubleshooting.

Comment on lines +110 to +111
var ownerBytes = rawData.Skip(OwnerOffset).Take(32).ToArray();
if (ownerBytes.All(b => b == 0))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Reduce allocations in byte extraction.

Skip().Take().ToArray() creates intermediate enumerators and allocates a new array. For a method that may be called frequently, consider using Array.Copy or span-based slicing.

♻️ Lower-allocation alternative
-            var ownerBytes = rawData.Skip(OwnerOffset).Take(32).ToArray();
+            var ownerBytes = new byte[PublicKeyLength];
+            Array.Copy(rawData, OwnerOffset, ownerBytes, 0, PublicKeyLength);

Apply similar changes to lines 182, 213, 276, and 296.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/SkrAddressResolutionClient.cs` around lines 110 - 111, The
code currently extracts slices like "var ownerBytes =
rawData.Skip(OwnerOffset).Take(32).ToArray();" which allocates intermediate
enumerables and arrays; replace these with a lower-allocation approach (e.g.,
use Array.Copy(rawData, OwnerOffset, ownerBytes, 0, 32) with a pre-sized
ownerBytes = new byte[32] or use Span<byte> ownerSpan =
rawData.AsSpan(OwnerOffset, 32)) to avoid the Skip/Take/ToArray chain; make the
same change for the other slices referenced in the file (the extractions at the
locations using offsets on rawData on lines analogous to 182, 213, 276, and
296), ensuring you preserve the All(b => b == 0) check by operating on the Span
or the copied fixed-size array.

Comment thread Runtime/codebase/SkrAddressResolutionClient.cs
Comment thread Runtime/codebase/SkrAddressResolutionClient.cs
Comment thread Samples~/Solana Wallet/Scripts/example/screens/TransferScreen.cs Outdated
Address follow-up review feedback by allowing wrapped-domain candidates in reverse lookups, adding defensive account-data encoding checks, and using stored raw token balance values instead of parsing UI text.
@lopeselio
Copy link
Copy Markdown
Contributor Author

  1. _ownedTokenAmount stores the raw numeric token balance from the token account, so validation uses a reliable number instead of reparsing UI text.

before:

  • validation compared amountToken against ownedAmountTxt.text
  • if UI text ever changes format (1,000, 1000 USDC, localized separators), parse can fail or misread

now:

  • when loading token data, we parse once from tokenAccount...Amount into _ownedTokenAmount
  • CheckInput compares transfer amount directly against _ownedTokenAmount
  • UI display can change freely without breaking transfer validation

so it’s mainly a correctness/safety fix for token transfers and addresses the CodeRabbit concern.

  1. removed owner memcmp filter in reverse lookup query, so wrapped domains are not filtered out before owner resolution
    added defensive account-data encoding check in GetRawAccountData before base64 decode

@Kuldotha
Copy link
Copy Markdown
Contributor

Kuldotha commented Apr 29, 2026

@lopeselio I'm testing the system but it looks like the callback isn't called relyably. Also, does it make sense to cache reverse lookups so we don't have to do another reverse lookup? Better to limit the amount of getprogramaccounts calls.

Edit, it looks like the callback is returned properly but the result isn't consistently filled, looks like a race condition (result is good when debugging but empty at runtime)

Improve receive-flow reliability by deduplicating in-flight reverse lookups, adding short-lived reverse lookup caching, and guarding UI updates against stale async completions.
@lopeselio
Copy link
Copy Markdown
Contributor Author

lopeselio commented Apr 29, 2026

@lopeselio I'm testing the system but it looks like the callback isn't called relyably. Also, does it make sense to cache reverse lookups so we don't have to do another reverse lookup? Better to limit the amount of getprogramaccounts calls.

Edit, it looks like the callback is returned properly but the result isn't consistently filled, looks like a race condition (result is good when debugging but empty at runtime)

thanks for testing and for the detailed feedback, i pushed updates to address both points.

  • added reverse lookup caching (short ttl for misses, longer ttl for hits) to reduce repeated lookups
  • added in-flight deduping so concurrent requests for the same wallet share one lookup
  • added stale-request guards on receive screen so late async results don’t overwrite current ui state
  • also kept wrapped-domain ownership resolution and reverse verification checks intact

this should reduce getprogramaccounts pressure and avoid the race behavior where result appears empty at runtime.

cc: @Kuldotha

@lopeselio
Copy link
Copy Markdown
Contributor Author

@Kuldotha please let me know if we can add helpful information about the feature in the project readme as well.

@lopeselio
Copy link
Copy Markdown
Contributor Author

Hey @Kuldotha , just checking back here to see if we have any updates, thanks.

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.

Feature: Add Native On-Chain .skr Name Resolution (Transfer + Receive) for Seeker Users

2 participants