Skip to content

Releases: reown-com/react-native-examples

WalletConnect Pay Solana support

20 May 19:57
b4e7cb5

Choose a tag to compare

This release captures the changes that extend the React Native CLI wallet's WalletConnect Pay reference implementation to support Solana payments end-to-end, alongside the existing EVM flows. It is intended as a documentation/reference checkpoint for other wallet teams adding Solana to their WCPay integration — this repo has no semver product stream and uses these release tags purely as integration milestones.

Scope is intentionally narrow: only the single PR listed under Included PRs below. Unrelated CI, dependabot, and other-wallet changes on main are not part of this release. See the attached patch for the exact diff.

Prerequisites. This release assumes your wallet already has:

  • A working WCPay action-chain executor for EVM — see the rn-cli-wallet-wcpay-usdt release if you have not implemented that yet.
  • A general Solana signing capability — an ed25519 keypair and a signTransaction function that takes an unsigned base64 transaction and returns a fully signed base64 transaction. The reference wallet uses SolanaLib (added in #500), but any equivalent signer works.

Everything below is purely the WCPay-flow integration on top of those two prerequisites.


What this enables

  • SOL-native payments via WCPay. When a Pay link's merchant supports Solana, the wallet now surfaces a SOL payment option in the picker, signs the backend-returned transaction with the user's Solana key, and forwards the signed blob to confirmPayment. The backend broadcasts; the wallet never touches a Solana RPC for this flow.
  • Mixed-namespace option lists. A single Pay link can return EVM and Solana options side-by-side. The wallet sends both eip155:…:0x… and solana:…:<pubkey> entries in accounts[], lets the user pick, and routes the chosen action chain to the correct signer at execution time.
  • Per-action wallet resolution. The Pay action executor no longer assumes one wallet type per payment. Each returned action is dispatched by its CAIP-2 namespace, so future namespaces (Sui, TON, etc.) can be added in the same handler without restructuring it.

Implementation requirements for other wallets

These are the wallet-integration contracts other implementers must follow to add Solana to WCPay. They are the public takeaway from this release. The EVM contracts from the previous release (rn-cli-wallet-wcpay-usdt) still apply — these are additive.

  1. Advertise Solana accounts in getPaymentOptions. Include the user's Solana account(s) in the accounts[] array as CAIP-10 strings — solana:<chainReference>:<base58Pubkey> — alongside any EVM entries. Without this, the backend cannot return SOL-denominated options even when the merchant supports them. Mainnet reference is solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.
  2. Resolve the signing wallet per action by CAIP-2 namespace — not once per payment. The wallet for action i is determined by actions[i].walletRpc.chainId.split(':')[0]. An eager EVM-wallet lookup at the top of approvePayment is incorrect: it will throw on Solana-only payments even when the Solana wallet is initialized, and it will pick the wrong key on mixed-namespace payments.
  3. Implement the solana_signTransaction method. Sign the transaction with the user's Solana key and append the signed transaction blob, base64-encoded, to the signatures[] array. This is the same array used for EVM tx hashes and typed-data signatures — keep ordering: index i of signatures corresponds to action i.
  4. Do NOT submit the bs58 detached signature. The Solana backend expects the fully signed transaction, base64-encoded, and broadcasts it server-side. Submitting the bs58 signature from nacl.sign.detached will fail with a validation error. Most Solana signer libraries return both — be explicit about which one you forward.
  5. Do NOT implement solana_signAndSendTransaction for the Pay flow. It is intentionally out of scope: the backend broadcasts. If your wallet's generic session_request handler implements it for non-Pay requests, that is fine, but the Pay action executor should not call it.
  6. solana_signTransaction params are array-wrapped. The Pay backend sends params as a single-element array wrapping the payload — [{ transaction }] — matching the WC-style array invariant used by EVM methods. The transaction field is the unsigned tx, base64-encoded.
  7. Defensively validate the signer at action-execution time. Even though a Solana action will only be returned when your wallet advertised a Solana account in accounts[], the wallet's signer state may have changed between option fetch and action execution (e.g. the user cleared keys, switched profiles). Guard the action handler against a missing signer and surface an error rather than silently skipping the action — the backend will reject a short signatures[] array regardless.
  8. Reject unknown wallet-RPC methods. Same rule as the EVM contract: throw rather than skip. Partial result arrays will be rejected.

Reference implementation (all in PR #501):

  • Per-action namespace dispatch: PaymentStore.approvePayment — see the switch (namespace) block that resolves either evmWallet or solanaWallet per action.
  • solana_signTransaction case: same file, the SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION branch — note signatures.push(signedTransaction), the base64 blob, not the bs58 signature.
  • Account advertisement: usePairing.handlePaymentLinkaccounts[] is concatenated from EVM and Solana chain maps.

End-to-end flow

  1. User → Wallet: paste a Pay link.
  2. Wallet → Pay backend: getPaymentOptions({ accounts: ["eip155:…:0x…", "solana:…:<pubkey>"] }).
  3. Pay backend → Wallet: options list, including SOL-denominated options.
  4. User → Wallet: tap Pay on the SOL option.
  5. Wallet → Pay backend: getRequiredPaymentActions({ paymentId, optionId }).
  6. Pay backend → Wallet: [{ chainId: "solana:…", method: "solana_signTransaction", params: [{ transaction }] }].
  7. Wallet → Solana signer: signTransaction({ transaction }).
  8. Solana signer → Wallet: { transaction: <signedB64>, signature: <bs58> }.
  9. Wallet → Pay backend: confirmPayment({ signatures: [<signedB64>] }) — forwarding the base64 signed transaction, not the bs58 signature.
  10. Pay backend → Wallet: succeeded (backend broadcasts server-side).

Validate your integration

Run your wallet through these flows to confirm Solana support implements the contracts above correctly:

  • SOL-only Pay link: merchant supports only Solana; SOL option appears in the picker; solana_signTransaction action signs and confirmPayment returns succeeded. Inspect the signatures[] payload and confirm it is the base64 signed-transaction blob, not the bs58 signature.
  • Mixed EVM+SOL Pay link: both EVM and SOL options appear; picking SOL completes via the Solana signer; picking the EVM option still completes via the EVM action chain. Both should work in the same wallet session without restarting.
  • EVM regression: existing Permit2 / USDT / native-token flows from the rn-cli-wallet-wcpay-usdt release continue to work end-to-end. The per-action namespace dispatch must not regress EVM behavior.
  • No Solana account → no SOL option: if the wallet has not generated/imported a Solana key, it must not advertise a solana:… entry in accounts[]. A merchant that supports only Solana should result in the Pay backend returning no compatible options (or the wallet's "no options" empty state), never a SOL option the user cannot complete.
  • Param parsing: confirm your wallet correctly unwraps the array-wrapped params = [{ transaction }] shape sent by the Pay backend and feeds the inner transaction string into your signer. See the array-wrapped Solana case in PaymentStore.test.ts.

Included PRs

This release contains exactly this one merged PR and nothing else. The patch is attached to this release for verification.

  • #501 — feat(rn_cli_wallet): add Solana support in WalletConnect Pay (merge b4e7cb57). usePairing advertises Solana accounts in getPaymentOptions, PaymentStore.approvePayment resolves the wallet per action by CAIP-2 namespace, and adds the solana_signTransaction case which unwraps the backend's array-wrapped [{ transaction }] params and forwards the base64 signed blob (not the bs58 signature) to confirmPayment. Three new unit tests cover the Solana action path. Android versionCode bumped 65 → 66.

Tagged at b4e7cb57 on origin/main.

WalletConnect Pay USDT support

19 May 14:20
c791039

Choose a tag to compare

This release captures the changes that make the React Native CLI wallet a working reference implementation for WalletConnect Pay with ERC-20 / USDT support, end-to-end. It is intended as a documentation/reference checkpoint for other wallet teams integrating WCPay — this repo has no prior GitHub releases and does not follow a semver product stream.

Scope is intentionally narrow: only the three PRs listed under Included PRs below. Unrelated CI, dependabot, and other-wallet changes on main between those PRs are not part of this release. See the attached patches for the exact diff.


What this enables

  • First-time USDT (and other ERC-20) payments on chains that require Permit2 (e.g. Polygon). The wallet now executes the WCPay-returned two-action flow — an eth_sendTransaction (Permit2 approval) followed by an eth_signTypedData_v4 (Permit2 typed-data signature) — and forwards the per-action results to confirmPayment in order.
  • Repeat USDT payments with no approval step. After the one-time Permit2 setup, WCPay returns a single typed-data action; the wallet signs and submits without an on-chain tx.
  • Native-token payments (e.g. ETH on Base) now complete reliably. The wallet broadcasts the action's eth_sendTransaction, waits for confirmation, and includes the resulting transaction hash in the signatures array passed to confirmPayment. Prior to this release, native flows posted an empty signatures array and the backend rejected confirmation with 400: Validation error: Next result not present.
  • Per-option fee estimates and one-time-setup UX so users can compare options and understand why USDT requires an extra step the first time.

Implementation requirements for other wallets

These are the wallet-integration contracts other implementers must follow. They are the public takeaway from this release.

  1. Call pay.getRequiredPaymentActions({ paymentId, optionId }) exactly once, at the moment the user confirms payment — not during option preview, not when fee estimates are computed, not on selection change. The call is committal: once it returns, the wallet must execute every action and call confirmPayment. There is no "preview" or "go back" path after this point. Surface fee estimates and option previews from your own price/fee pipeline (the reference wallet uses PaymentTransactionUtil), not from this RPC. Do not assume one action per option — the returned array can have one or more entries.
  2. Execute every returned action, in order. The action array is ordered; you must execute index 0, then 1, etc. Do not reorder, skip, or batch.
  3. For eth_sendTransaction actions: broadcast the tx, wait for confirmation, then append the transaction hash to a signatures array. (This is the rule #495 enforces. It applies to native-token payments and to Permit2 approval txs.)
  4. For eth_signTypedData, eth_signTypedData_v3, eth_signTypedData_v4 actions: sign the typed data and append the returned signature to the same signatures array.
  5. Maintain the invariant signatures.length === paymentActions.length when calling pay.confirmPayment({ paymentId, optionId, signatures }). Ordering must match the action ordering returned by WCPay — index i in signatures corresponds to action i.
  6. Reject unknown wallet-RPC methods. Throw rather than silently skipping; partial result arrays will be rejected by the backend.

Reference implementation: PaymentStore.approvePayment — this handler runs when the user presses PAY. getRequiredPaymentActions is called once at line 519, and only inside this handler — never during option preview. The loop at lines 529–667 is the canonical action-chain executor; the signatures.push(tx.hash) at line 613 is the rule from #495.


Recommended UX

These are guidance, not protocol requirements. The reference wallet implements all of them.

  • Preload per-option fee estimates before the user picks an option, so options can be compared with realistic gas. See PaymentTransactionUtil and the fee-estimate pipeline added in #480.
  • Show one-time-setup messaging when the action chain includes an approval. The reference wallet uses shouldShowSetupLoader (PaymentUtil.ts) — true when actions.length > 1 and an approval action is present — and surfaces "Setting up TOKEN for one-time setup… Future TOKEN payments will be instant."
  • Explain gas separately when ERC-20 payments carry a native-token gas cost. The reference wallet ships a gas explainer view.
  • Persist the last-paid token unit for faster repeat payments — auto-select it next time the user pays, but only when the saved unit is present in the new payment's available options. If the saved unit isn't offered (different merchant, different chains, expired token, etc.), fall back to manual option selection. The reference wallet does this via findPreferredOption(options, lastPaidTokenUnit) returning null when there's no match.
  • Guard against stale action fetches (e.g. user changes option mid-fetch) and locally-expired payments (drop the request before signing). The reference wallet implements both.

Validate your integration

Run your wallet through these flows to confirm it implements the contracts above correctly:

  • First-time ERC-20 payment requiring Permit2: approval eth_sendTransaction succeeds and is confirmed, followed by an eth_signTypedData_v4 signature; confirmPayment receives [txHash, signature] and returns succeeded.
  • Repeat ERC-20 payment (Permit2 already in place): a single eth_signTypedData_v4; confirmPayment receives [signature] and returns succeeded. No approval prompt is shown.
  • Native-token payment: a single eth_sendTransaction; the broadcast tx hash is included in signatures; confirmPayment receives [txHash] and returns succeeded. (Regression test for #495.)
  • Rapid option switching: repeatedly tapping different options does not leak stale fee or action data into the currently selected option (stale-fetch guard).
  • Locally-expired payment: if expiresAt has passed before the user taps "Approve", the wallet sets an expired result and does not prompt for signatures.

Included PRs

This release contains exactly these three merged PRs and nothing else. Per-PR and combined patches are attached to this release for verification.


Tagged at c791039e on origin/main.