Skip to content

WalletConnect Pay Solana support

Latest

Choose a tag to compare

@ignaciosantise ignaciosantise released this 20 May 19:57
b4e7cb5

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.