diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..6acdd6a --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1,5 @@ +root: ./ + +structure: + readme: README.md + summary: SUMMARY.md diff --git a/README.md b/README.md index de5df88..2e6616d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,56 @@ -# freighter-developer-docs -Freighter wallet developer documentation +# Welcome to Freighter + +Freighter is a non-custodial wallet for the Stellar network, available as a browser extension and a mobile app. Non-custodial means your users hold their own keys — your dapp never sees or stores private keys. This guide walks you through integrating Freighter so your users can connect their wallets, sign transactions, and interact with Soroban smart contracts. + +## What You Can Build + +With Freighter, your dapp can connect a user's wallet with a single call — no signup form, no password, just a familiar wallet popup and an immediate public key. From there you can read their active network, hand off transactions for signing, and submit to the network. + +For dapps that integrate with smart contracts, Freighter also handles authorization entry signing for smart contract calls and arbitrary message signing for account verification. + +## How It Works + +The integration model is simple: your dapp talks to the wallet, and the wallet talks to the user. + +For **desktop browsers**, you integrate with the Freighter extension through a lightweight JavaScript library. It injects itself into the page, and your dapp calls methods like "is the wallet connected?", "what's the user's address?", and "please sign this transaction." + +For **mobile**, the integration works over WalletConnect v2. When a user opens your dapp in a mobile browser, WalletConnect presents a modal where they select Freighter as their wallet and approve the connection. On desktop, the same flow can display a QR code for the user to scan. Either way, a secure relay session is established and signing requests flow through the same pattern — your dapp proposes, the user reviews and approves in the wallet. + +Both paths produce the same output: signed transactions you can submit to the Stellar network. So your backend and submission logic stay the same regardless of whether your user connected from a laptop or a phone. + +If you want to support multiple Stellar wallets — not just Freighter — take a look at [Stellar Wallets Kit](https://stellarwalletskit.dev/). It provides a unified interface across Stellar wallets, including browser extensions and WalletConnect-based mobile wallets, so your users can connect with whichever wallet they prefer. + +> **Note:** Stellar Wallets Kit's WalletConnect module currently only exposes `stellar_signXDR` and `stellar_signAndSubmitXDR`. If your dapp needs `stellar_signMessage` or `stellar_signAuthEntry` against Freighter Mobile, integrate WalletConnect directly for now (tracked in [stellar/freighter-mobile#815](https://github.com/stellar/freighter-mobile/issues/815)). + +## Why Freighter + +**Built and maintained by SDF.** Freighter is developed by the Stellar Development Foundation, the same team behind the core Stellar protocol and SDKs. + +**Complete Stellar support.** Transaction signing, Soroban authorization entry signing with contract invocation inspection, SEP-53 message signing, contract token management, and Blockaid transaction scanning. + +**Desktop and mobile.** Browser extension for desktop, WalletConnect for mobile. Both paths produce the same signed output, so your dapp supports both with minimal branching. + +## Get Started + +**Desktop** — integrate with the Freighter browser extension using `@stellar/freighter-api`. See the [Extension Guide](extension/installation.md). + +**Mobile** — connect to Freighter Mobile over WalletConnect v2. See the [Mobile Guide](mobile/README.md). + +**Both** — if your dapp needs to support desktop and mobile users, use both integration paths. Both produce the same output (signed XDR), so your submission logic stays the same regardless of how the user connected. + +## Quick links + +| I want to... | Extension | Mobile | +| ------------------------------------- | -------------------------------------------------- | ---------------------------------------------------- | +| Install / set up | [Installation](extension/installation.md) | [Installation](mobile/installation.md) | +| Connect to Freighter | [Connecting](extension/connecting.md) | [Connecting](mobile/connecting.md) | +| Sign a transaction | [Signing](extension/signing.md) | [Signing](mobile/signing.md) | +| Add a token | [Token Management](extension/token-management.md) | — | + +## Resources + +- [GitHub — Freighter Extension](https://github.com/stellar/freighter) +- [GitHub — Freighter Mobile](https://github.com/stellar/freighter-mobile) +- [Chrome Extension Store](https://chromewebstore.google.com/detail/freighter/bcacfldlkkdogcmkkibnjlakofdplcbk) +- [Stellar Developer Docs](https://developers.stellar.org) +- [WalletConnect v2 Docs](https://docs.walletconnect.network/app-sdk/overview) diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..083da4b --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,24 @@ +# Table of contents + +* [Introduction](README.md) + +## Extension (freighter-api) + +* [Installation](extension/installation.md) +* [Overview](extension/README.md) +* [Connecting](extension/connecting.md) +* [Reading Data](extension/reading-data.md) +* [Signing](extension/signing.md) +* [Token Management](extension/token-management.md) +* [Watching Changes](extension/watching-changes.md) + +## Mobile (WalletConnect) + +* [Installation](mobile/installation.md) +* [Overview](mobile/README.md) +* [Connecting](mobile/connecting.md) +* [Signing](mobile/signing.md) + +## Integrations + +* [Third-Party Integrations](integrations/README.md) diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..e4edbc6 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,27 @@ +# Extension Integration + +Integrate Freighter into your web application using `@stellar/freighter-api`. This library lets you send and receive data from a user's Freighter extension. + +`@stellar/freighter-api` adheres to the [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) standard for wallet interfaces on Stellar, and also offers additional methods beyond the spec such as `getNetworkDetails`, `addToken`, and `WatchWalletChanges`. + +## Error Type + +All methods return an optional `error` field of type `FreighterApiError`: + +```typescript +interface FreighterApiError { + code: number; + message: string; + ext?: string[]; +} +``` + +## API Reference + +| Category | Description | +| --- | --- | +| [Connecting](connecting.md) | Detect Freighter, check permissions, request access | +| [Reading Data](reading-data.md) | Get the user's address and network configuration | +| [Signing](signing.md) | Sign transactions, auth entries, and messages | +| [Token Management](token-management.md) | Add contract tokens to the user's wallet | +| [Watching Changes](watching-changes.md) | Monitor wallet state changes in real time | diff --git a/extension/connecting.md b/extension/connecting.md new file mode 100644 index 0000000..fa69097 --- /dev/null +++ b/extension/connecting.md @@ -0,0 +1,101 @@ +# Connecting to Freighter + +Detect whether a user has Freighter installed, check if your app is authorized, and request access to the user's public key. + +## Detecting Freighter + +### `isConnected()` + +Check if the user has Freighter installed in their browser. + +**Returns:** `Promise<{ isConnected: boolean } & { error?: FreighterApiError }>` + +```typescript +import { isConnected } from "@stellar/freighter-api"; + +const result = await isConnected(); + +if (result.isConnected) { + console.log("Freighter is installed"); +} +``` + +{% hint style="info" %} +`isConnected()` checks whether the Freighter extension is installed. Note that it returns `false` for any user without the extension — not just mobile users. To detect Freighter Mobile's in-app browser specifically, check for `window.stellar?.platform === "mobile"`. +{% endhint %} + +## Checking Authorization + +### `isAllowed()` + +Check if the user has previously authorized your app to receive data from Freighter. + +**Returns:** `Promise<{ isAllowed: boolean } & { error?: FreighterApiError }>` + +```typescript +import { isAllowed } from "@stellar/freighter-api"; + +const result = await isAllowed(); + +if (result.isAllowed) { + console.log("App is on the Allow List"); +} +``` + +## Requesting Authorization + +### `setAllowed()` + +Prompt the user to authorize your app and add it to Freighter's **Allow List**. Once approved, the extension can provide user data without additional prompts. + +**Returns:** `Promise<{ isAllowed: boolean } & { error?: FreighterApiError }>` + +```typescript +import { setAllowed } from "@stellar/freighter-api"; + +const result = await setAllowed(); + +if (result.isAllowed) { + console.log("App added to Allow List"); +} +``` + +{% hint style="info" %} +If the user has already authorized your app, `setAllowed()` resolves immediately with `{ isAllowed: true }`. +{% endhint %} + +## Requesting Access + +### `requestAccess()` + +Prompt the user to add your dapp to Freighter's Allow List and return their public key in one call. This is the recommended way to connect — it combines authorization and data retrieval in a single step. + +**Returns:** `Promise<{ address: string } & { error?: FreighterApiError }>` + +If the user has previously authorized your app, the public key is returned immediately without a popup. + +```typescript +import { isConnected, requestAccess } from "@stellar/freighter-api"; + +// Always check isConnected first +const connectResult = await isConnected(); +if (!connectResult.isConnected) { + console.error("Freighter is not installed"); + return; +} + +const accessResult = await requestAccess(); +if (accessResult.error) { + console.error("Access denied:", accessResult.error.message); +} else { + console.log("Public key:", accessResult.address); +} +``` + +{% hint style="warning" %} +Always check `isConnected()` before calling `requestAccess()` to ensure the extension is available. +{% endhint %} + +## Next steps + +Once connected, you can [read the user's address and network](reading-data.md) or [sign transactions](signing.md). diff --git a/extension/installation.md b/extension/installation.md new file mode 100644 index 0000000..d31d61f --- /dev/null +++ b/extension/installation.md @@ -0,0 +1,85 @@ +# Installation + +Get up and running with Freighter by installing the browser extension and choosing the right integration method for your app. + +## Prerequisites + +1. Install the [Freighter browser extension](https://chromewebstore.google.com/detail/freighter/bcacfldlkkdogcmkkibnjlakofdplcbk) from the Chrome Web Store. +2. Choose an integration method below based on your project setup. + +## Choose Your Integration + +### npm / yarn + +Use this when building with a bundler (Webpack, Vite, etc.) in a React, Next.js, or other modern JS framework. + +**Ideal for:** + +- React, Vue, Svelte, or Angular applications +- Server-side rendered apps (Next.js, Nuxt) +- Any project using a JavaScript bundler + +```bash +npm install @stellar/freighter-api +``` + +```bash +yarn add @stellar/freighter-api +``` + +Then import in your code. You can import the entire library: + +```javascript +import freighterApi from "@stellar/freighter-api"; +``` + +Or import only what you need: + +```javascript +import { + isConnected, + getAddress, + signAuthEntry, + signTransaction, + signMessage, + addToken, +} from "@stellar/freighter-api"; +``` + +> See the [Extension Integration](../extension/) for the full API reference. + +### CDN (script tag) + +Use this for plain HTML pages or projects without a build step. The library is loaded directly in the browser. + +**Ideal for:** + +- Static HTML sites +- Prototypes and quick experiments +- Projects without a bundler + +Add to your ``: + +```html + +``` + +This always loads the latest version automatically. To pin a specific version: + +```html + +``` + +Then access the API via `window.freighterApi`: + +```javascript +const { address } = await window.freighterApi.requestAccess(); +``` + +## Next steps + +| I want to... | Go to | +| ------------------------------------- | -------------------------------------------------------- | +| Connect my app to Freighter | [Connecting](../extension/connecting.md) | +| Sign a transaction | [Signing](../extension/signing.md) | +| Add a token | [Token Management](../extension/token-management.md) | diff --git a/extension/reading-data.md b/extension/reading-data.md new file mode 100644 index 0000000..e322cc3 --- /dev/null +++ b/extension/reading-data.md @@ -0,0 +1,71 @@ +# Reading Data + +Retrieve the user's public key and network configuration from Freighter. + +{% hint style="info" %} +These methods require that the user has previously authorized your app via [`requestAccess()`](connecting.md#requesting-access) or [`setAllowed()`](connecting.md#requesting-authorization). +{% endhint %} + +## Getting the User's Address + +### `getAddress()` + +A lightweight way to retrieve the user's public key. Unlike `requestAccess()`, this will **not** prompt the user — it returns an empty string if the app isn't authorized or Freighter isn't connected. + +**Returns:** `Promise<{ address: string } & { error?: FreighterApiError }>` + +```typescript +import { getAddress } from "@stellar/freighter-api"; + +const { address, error } = await getAddress(); + +if (error) { + console.error(error.message); +} else { + console.log("Public key:", address); +} +``` + +## Getting the Network + +### `getNetwork()` + +Get the name and passphrase of the network the user has selected in Freighter. + +**Returns:** `Promise<{ network: string; networkPassphrase: string } & { error?: FreighterApiError }>` + +Possible `network` values: `PUBLIC`, `TESTNET`, `FUTURENET`, or `STANDALONE` (custom networks). + +```typescript +import { getNetwork } from "@stellar/freighter-api"; + +const { network, networkPassphrase, error } = await getNetwork(); + +if (!error) { + console.log("Network:", network); // e.g., "TESTNET" + console.log("Passphrase:", networkPassphrase); +} +``` + +### `getNetworkDetails()` + +Get comprehensive network configuration including the Horizon URL, passphrase, and Soroban RPC URL. + +**Returns:** `Promise<{ network: string; networkUrl: string; networkPassphrase: string; sorobanRpcUrl?: string } & { error?: FreighterApiError }>` + +```typescript +import { getNetworkDetails } from "@stellar/freighter-api"; + +const details = await getNetworkDetails(); + +if (!details.error) { + console.log("Network:", details.network); // e.g., "TESTNET" + console.log("Horizon:", details.networkUrl); // e.g., "https://horizon-testnet.stellar.org" + console.log("Passphrase:", details.networkPassphrase); + console.log("Soroban RPC:", details.sorobanRpcUrl); // e.g., "https://soroban-testnet.stellar.org" +} +``` + +{% hint style="info" %} +Use `getNetworkDetails()` instead of `getNetwork()` when working with Soroban smart contracts or when you need specific network endpoints. +{% endhint %} diff --git a/extension/signing.md b/extension/signing.md new file mode 100644 index 0000000..d1376fe --- /dev/null +++ b/extension/signing.md @@ -0,0 +1,168 @@ +# Signing + +Sign transactions, authorization entries, and arbitrary messages using the user's Freighter wallet. + +## Signing a Transaction + +### `signTransaction()` + +Pass a transaction XDR string to Freighter for the user to review and sign. + +``` +signTransaction(xdr: string, opts?: { + network?: string, + networkPassphrase?: string, + address?: string +}) -> Promise<{ signedTxXdr: string; signerAddress: string } & { error?: FreighterApiError }> +``` + +The user will review the transaction details before signing. + +**Parameters** + +| Parameter | Type | Description | +| --- | --- | --- | +| `xdr` | `string` | **Required.** Base64-encoded transaction XDR. | +| `opts.network` | `string` | Network name (maps to `Networks` enum in `@stellar/stellar-sdk`). | +| `opts.networkPassphrase` | `string` | Custom passphrase. Ignored if `network` is also provided. | +| `opts.address` | `string` | Request a specific account's signature. Freighter will switch to that account if available. | + +{% hint style="info" %} +Passing `network` or `networkPassphrase` lets Freighter warn the user if their wallet is configured to the wrong network. +{% endhint %} + +**Example** + +```typescript +import { signTransaction } from "@stellar/freighter-api"; + +const { signedTxXdr, signerAddress, error } = await signTransaction(xdr, { + network: "TESTNET", + address: "G...", // optional: request a specific account +}); + +if (error) { + console.error("Signing failed:", error.message); +} else { + console.log("Signed by:", signerAddress); + console.log("Signed XDR:", signedTxXdr); +} +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| User rejected | `"The user rejected this request."` | +| Extension not installed | `"The wallet encountered an internal error"` | + +## Signing an Auth Entry + +### `signAuthEntry()` + +Sign an [authorization entry preimage](https://github.com/stellar/js-stellar-base/blob/a9567e5843760bfb6a8b786592046aee4c9d38b2/types/next.d.ts#L6895) and receive the signed hash back as a base64 string. Used for [Soroban contract authorization](https://developers.stellar.org/docs/smart-contracts/guides/auth/authorization) flows. + +``` +signAuthEntry(entryXdr: string, opts: { + address: string +}) -> Promise<{ signedAuthEntry: string | null; signerAddress: string } & { error?: FreighterApiError }> +``` + +See the [`authorizeEntry` helper](https://github.com/stellar/js-stellar-base/blob/e3d6fc3351e7d242b374c7c6057668366364a279/src/auth.js#L97) in `js-stellar-base` for how signed auth entries are used, or the [Soroban development documentation](https://developers.stellar.org/docs/smart-contracts) for wallet-side patterns. + +**Example** + +```typescript +import { signAuthEntry } from "@stellar/freighter-api"; + +const { signedAuthEntry, signerAddress, error } = await signAuthEntry(entryXdr, { + address: "G...", +}); + +if (error) { + console.error("Auth entry signing failed:", error.message); +} else { + console.log("Signed auth entry:", signedAuthEntry); +} +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| User rejected | `"The user rejected this request."` | + +## Signing a Message + +### `signMessage()` + +Sign an arbitrary string and receive a base64-encoded Ed25519 signature. Follows [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md). + +``` +signMessage(message: string, opts: { + address: string +}) -> Promise<{ signedMessage: string | null; signerAddress: string } & { error?: FreighterApiError }> +``` + +**Example** + +```typescript +import { signMessage } from "@stellar/freighter-api"; + +const { signedMessage, signerAddress, error } = await signMessage( + "Verify account ownership", + { address: "G..." }, +); + +if (error) { + console.error("Message signing failed:", error.message); +} else { + console.log("Signature:", signedMessage); +} +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| User rejected | `"The user rejected this request."` | + +## Full Example: Connect, Sign, and Submit + +A complete flow from connection to Horizon submission: + +```typescript +import { isConnected, requestAccess, signTransaction } from "@stellar/freighter-api"; +import { Server, TransactionBuilder } from "@stellar/stellar-sdk"; + +// 1. Check Freighter is installed +const connectResult = await isConnected(); +if (!connectResult.isConnected) { + throw new Error("Freighter not found"); +} + +// 2. Request the user's public key +const accessResult = await requestAccess(); +if (accessResult.error) { + throw new Error(accessResult.error.message); +} + +// 3. Sign the transaction +const xdr = "AAAAAgAAAAA..."; // your assembled transaction XDR +const signResult = await signTransaction(xdr, { + network: "TESTNET", + address: accessResult.address, +}); +if (signResult.error) { + throw new Error(signResult.error.message); +} + +// 4. Submit to Horizon +const server = new Server("https://horizon-testnet.stellar.org"); +const tx = TransactionBuilder.fromXDR( + signResult.signedTxXdr, + "Test SDF Network ; September 2015", +); +const response = await server.submitTransaction(tx); +console.log("Transaction submitted:", response.hash); +``` diff --git a/extension/token-management.md b/extension/token-management.md new file mode 100644 index 0000000..a3a8bde --- /dev/null +++ b/extension/token-management.md @@ -0,0 +1,51 @@ +# Token Management + +Unlike Stellar assets, contract tokens a user owns are not automatically displayed in Freighter — they must be explicitly added. This method lets your dapp prompt users to add a contract token directly, without the user having to find and add it in Freighter manually. + +{% hint style="info" %} +In a future release, Freighter will auto-detect token transfers and display them automatically. Once that ships, `addToken()` will become a no-op. +{% endhint %} + +## Adding a Token + +### `addToken()` + +Trigger an "add token" flow that displays token details in a modal for the user to review and approve. + +``` +addToken({ contractId: string, networkPassphrase?: string }) + -> Promise<{ contractId: string } & { error?: FreighterApiError }> +``` + +When called, Freighter loads the token's **symbol**, **name**, **decimals**, and **balance** from the contract and displays them for the user to verify before adding. + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| `contractId` | `string` | **Required.** The contract token ID (`C...` address). | +| `networkPassphrase` | `string` | Defaults to Mainnet passphrase if omitted. | + +### Example + +```typescript +import { isConnected, addToken } from "@stellar/freighter-api"; + +const { isConnected: connected } = await isConnected(); +if (!connected) return; + +const result = await addToken({ + contractId: "CC...ABCD", + networkPassphrase: "Test SDF Network ; September 2015", // optional +}); + +if (result.error) { + console.error(result.error.message); +} else { + console.log(`Token added: ${result.contractId}`); +} +``` + +{% hint style="info" %} +After the user approves, Freighter will automatically track the token's balance and display it alongside other account balances. +{% endhint %} diff --git a/extension/watching-changes.md b/extension/watching-changes.md new file mode 100644 index 0000000..536f2e2 --- /dev/null +++ b/extension/watching-changes.md @@ -0,0 +1,46 @@ +# Watching Wallet Changes + +Monitor the user's Freighter wallet for address and network changes in real time. + +## WatchWalletChanges + +### `new WatchWalletChanges(timeout?: number)` + +Creates a watcher that polls Freighter for changes at a configurable interval. + +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| `timeout` | `number` | `3000` | Polling interval in milliseconds. | + +### `watch(callback)` + +Start polling. The callback fires only when something has changed (address, network, or passphrase). + +``` +watch(callback: ({ address: string; network: string; networkPassphrase: string }) => void) +``` + +### `stop()` + +Stop polling for changes. + +### Example + +```typescript +import { WatchWalletChanges } from "@stellar/freighter-api"; + +const watcher = new WatchWalletChanges(1000); // poll every second + +watcher.watch((changes) => { + console.log("Address:", changes.address); + console.log("Network:", changes.network); + console.log("Passphrase:", changes.networkPassphrase); +}); + +// Stop after 30 seconds +setTimeout(() => watcher.stop(), 30000); +``` + +{% hint style="info" %} +The callback only fires when a value actually changes — it won't emit duplicate events. +{% endhint %} diff --git a/index.html b/index.html new file mode 100644 index 0000000..77cc2c6 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + Freighter Documentation + + + + +
+ + + + + + + diff --git a/integrations/README.md b/integrations/README.md new file mode 100644 index 0000000..51d6450 --- /dev/null +++ b/integrations/README.md @@ -0,0 +1,15 @@ +# Third-Party Integrations + +Freighter connects to third-party services to provide a better user experience. As an open source project, we document these integrations so other wallet developers can adopt similar patterns. + +## Multi-Wallet Support + +If your dapp needs to support multiple Stellar wallets beyond Freighter, [Stellar Wallets Kit](https://stellarwalletskit.dev/) provides a unified interface for connecting to any Stellar wallet. It handles wallet detection, connection, and signing through a single API, so your users can choose whichever wallet they prefer. + +## Available Integrations + +| Integration | Description | +| --- | --- | +| [Stellar Wallets Kit](https://stellarwalletskit.dev/) | Unified multi-wallet interface for Stellar dapps | +| [Soroswap](https://github.com/stellar/freighter/blob/master/extension/INTEGRATING_SOROSWAP.MD) | DEX integration for token swaps | +| [Hardware Wallets](https://github.com/stellar/freighter/blob/master/extension/INTEGRATING_HARDWARE_WALLET.MD) | Ledger and other hardware wallet support | diff --git a/mobile/README.md b/mobile/README.md new file mode 100644 index 0000000..b9325f8 --- /dev/null +++ b/mobile/README.md @@ -0,0 +1,56 @@ +# Mobile Integration + +Connect your dapp to Freighter Mobile using the [WalletConnect v2](https://docs.walletconnect.network/app-sdk/overview) protocol. All methods follow the standard JSON-RPC 2.0 request/response format over a WalletConnect session. + +## Supported Chains + +| Network | Chain ID | +| --- | --- | +| Mainnet (PUBLIC) | `stellar:pubnet` | +| Testnet | `stellar:testnet` | + +## API Reference + +| Method | Description | +| --- | --- | +| [`stellar_signXDR`](signing.md#stellar_signxdr) | Sign a transaction and return the signed XDR | +| [`stellar_signAndSubmitXDR`](signing.md#stellar_signandsubmitxdr) | Sign and submit a transaction to Horizon | +| [`stellar_signMessage`](signing.md#stellar_signmessage) | Sign an arbitrary UTF-8 message ([SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md)) | +| [`stellar_signAuthEntry`](signing.md#stellar_signauthentry) | Sign a Soroban authorization entry preimage (as defined in [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md)) | + +## Error Handling + +All error responses use JSON-RPC 2.0 format with code `5000`: + +```json +{ + "id": 1234567890, + "jsonrpc": "2.0", + "error": { + "code": 5000, + "message": "User rejected the request" + } +} +``` + +{% hint style="warning" %} +Freighter Mobile validates that the `chainId` in the request matches the wallet's active network. If the user is on the wrong network, the request is rejected. Always specify `chainId` as a separate argument to `provider.request()`, not inside `params`. +{% endhint %} + +## Security + +Freighter Mobile uses **Blockaid** scanning to protect users: + +| Scan type | When it runs | +| --- | --- | +| **Site scan** | Once during WalletConnect session connection | +| **Transaction scan** | For `stellar_signXDR` and `stellar_signAndSubmitXDR` requests | + +`stellar_signMessage` and `stellar_signAuthEntry` do not trigger additional scans — the site was already scanned during connection. + +## See also + +- [WalletConnect v2 Docs](https://docs.walletconnect.network/app-sdk/overview) +- [Stellar Smart Contracts Auth](https://developers.stellar.org/docs/smart-contracts/guides/auth/authorization) +- [SEP-53: Message Signing](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) +- [SEP-43: Auth Entry Signing](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) diff --git a/mobile/connecting.md b/mobile/connecting.md new file mode 100644 index 0000000..bf9dcac --- /dev/null +++ b/mobile/connecting.md @@ -0,0 +1,86 @@ +# Connecting to Freighter Mobile + +Establish a WalletConnect session with Freighter Mobile, handle session lifecycle events, and disconnect. + +Assumes `provider` and `modal` are initialized as shown in [Installation](installation.md). + +## Creating a Session + +Call `modal.open()` to show the wallet selection modal, then `provider.connect()` to start the pairing. The modal displays a QR code and a list of wallets — when the user selects Freighter or scans the QR code, the connection is established automatically. `connect()` resolves once the wallet approves. + +```typescript +// Open the modal (shows a spinner, then the QR code and wallet list) +modal.open(); + +// Connect — resolves when the wallet approves +const session = await provider.connect({ + namespaces: { + stellar: { + methods: [ + "stellar_signXDR", + "stellar_signAndSubmitXDR", + "stellar_signMessage", + "stellar_signAuthEntry", + ], + chains: ["stellar:pubnet"], + events: ["accountsChanged"], + }, + }, +}); + +if (!session) { + throw new Error("Connection failed"); +} + +modal.close(); +console.log("Connected! Session topic:", session.topic); +``` + +The connected session is also accessible at any time via `provider.session`. + +Accounts are stored in `session.namespaces.stellar.accounts` as `"stellar:pubnet:G..."` strings — split on `:` to extract the public key: + +```typescript +const publicKey = session.namespaces.stellar.accounts[0].split(":")[2]; +``` + +{% hint style="info" %} +Use `namespaces` for methods your dapp needs to function. Use `optionalNamespaces` for methods that enhance the experience but aren't essential. + +**Note:** `UniversalProvider` treats `namespaces` as optional at the protocol level on the first connection. After the wallet approves, the approved namespaces are persisted and become required on subsequent connections. To be safe, always verify the approved methods after connecting: + +```typescript +const methods = session.namespaces.stellar?.methods || []; +if (!methods.includes("stellar_signXDR")) { + throw new Error("Wallet does not support required methods"); +} +``` +{% endhint %} + +## Handling Events + +Listen for session lifecycle events to keep your UI in sync: + +```typescript +// Session was disconnected by the wallet +provider.on("session_delete", ({ topic }) => { + console.log("Session deleted:", topic); +}); + +// Session expired +provider.on("session_expire", ({ topic }) => { + console.log("Session expired:", topic); +}); +``` + +## Disconnecting + +When the user wants to disconnect: + +```typescript +await provider.disconnect(); +``` + +## Next steps + +Once connected, you can [sign transactions, messages, and auth entries](signing.md). diff --git a/mobile/installation.md b/mobile/installation.md new file mode 100644 index 0000000..80e8b48 --- /dev/null +++ b/mobile/installation.md @@ -0,0 +1,65 @@ +# Installation + +Get up and running with WalletConnect so your dapp can connect to Freighter Mobile. + +## Prerequisites + +1. Sign up at [WalletConnect Dashboard](https://dashboard.walletconnect.com/) and create a project to get your **Project ID**. +2. Choose an installation method below. + +## npm / yarn + +```bash +npm install @walletconnect/universal-provider @reown/appkit +``` + +```bash +yarn add @walletconnect/universal-provider @reown/appkit +``` + +Then initialize the provider and modal: + +```typescript +import { UniversalProvider } from "@walletconnect/universal-provider"; +import { createAppKit } from "@reown/appkit/core"; +import { mainnet } from "@reown/appkit/networks"; + +const projectId = "YOUR_PROJECT_ID"; + +// Provider — handles the WalletConnect protocol +const provider = await UniversalProvider.init({ + projectId, + metadata: { + name: "My Stellar Dapp", + description: "A dapp that integrates with Freighter Mobile", + url: "https://my-dapp.com", + icons: ["https://my-dapp.com/icon.png"], + }, +}); + +// Modal — displays the QR code and wallet list. +// AppKit requires at least one network. Stellar is not built-in, +// so we pass a placeholder. With manualWCControl the modal won't +// use it for chain switching. +const modal = createAppKit({ + projectId, + networks: [mainnet], + universalProvider: provider, + manualWCControl: true, +}); +``` + +## Next steps + +| I want to... | Go to | +| ------------------------------------- | ---------------------------------------------- | +| Connect to Freighter Mobile | [Connecting](connecting.md) | +| Sign a transaction | [Signing](signing.md) | + +## Alternatives + +[Stellar Wallets Kit](https://stellarwalletskit.dev/) provides a multi-wallet interface that includes WalletConnect support, but currently only supports `stellar_signXDR` and `stellar_signAndSubmitXDR`. To use all four Freighter Mobile methods — including `stellar_signMessage` and `stellar_signAuthEntry` — implement the WalletConnect integration directly as shown in this guide. + +{% hint style="info" %} +Older examples may use `@walletconnect/sign-client` directly. That approach still works, but `@walletconnect/universal-provider` is the actively documented package. For more details, see the [UniversalProvider docs](https://docs.reown.com/advanced/providers/universal) and the [AppKit modal migration guide](https://docs.reown.com/appkit/upgrade/wcm). +{% endhint %} diff --git a/mobile/signing.md b/mobile/signing.md new file mode 100644 index 0000000..e0013e6 --- /dev/null +++ b/mobile/signing.md @@ -0,0 +1,247 @@ +# Signing + +Sign transactions, messages, and Soroban authorization entries via WalletConnect. + +Assumes `provider` and `modal` are initialized as shown in [Installation](installation.md). + +{% hint style="info" %} +The WalletConnect RPC methods use different response field names than the extension API. For example, `stellar_signXDR` returns `signedXDR` while the extension's `signTransaction()` returns `signedTxXdr`. See the response tables below for each method's exact fields. +{% endhint %} + +## Signing a Transaction + +### `stellar_signXDR` + +Sign a transaction and return the signed XDR. The transaction is **not** submitted to the network — your dapp is responsible for submission. + +**Parameters** + +| Parameter | Type | Description | +| --- | --- | --- | +| `xdr` | `string` | **Required.** Base64-encoded `TransactionEnvelope` or `FeeBumpTransactionEnvelope` XDR. | + +**Response** + +| Field | Type | Description | +| --- | --- | --- | +| `signedXDR` | `string` | Base64-encoded signed transaction XDR. | + +```typescript +const result = await provider.request( + { + method: "stellar_signXDR", + params: { xdr: "AAAAAgAAAAA..." }, + }, + "stellar:pubnet", +); + +console.log("Signed XDR:", result.signedXDR); +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| Chain not supported | `"Unsupported chain: "` | +| Wrong active network | `"Please switch to and try again"` | +| XDR parse failure | `"Failed to sign transaction"` | +| User rejected | `"User rejected the request"` | + +--- + +### `stellar_signAndSubmitXDR` + +Sign a transaction **and** submit it to Horizon in one step. + +**Parameters** + +| Parameter | Type | Description | +| --- | --- | --- | +| `xdr` | `string` | **Required.** Base64-encoded `TransactionEnvelope` or `FeeBumpTransactionEnvelope` XDR. | + +**Response** + +| Field | Type | Description | +| --- | --- | --- | +| `status` | `string` | `"success"` when the transaction is signed and submitted. | + +```typescript +const result = await provider.request( + { + method: "stellar_signAndSubmitXDR", + params: { xdr: "AAAAAgAAAAA..." }, + }, + "stellar:pubnet", +); + +console.log("Status:", result.status); // "success" +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| Chain not supported | `"Unsupported chain: "` | +| Wrong active network | `"Please switch to and try again"` | +| XDR parse / sign failure | `"Failed to sign transaction"` | +| Horizon submission failure | `"Failed to submit transaction"` | +| User rejected | `"User rejected the request"` | + +{% hint style="info" %} +Use **`stellar_signXDR`** when you need to inspect or modify the signed transaction before submission. Use **`stellar_signAndSubmitXDR`** for a simpler flow where Freighter handles everything. +{% endhint %} + +--- + +## Signing a Message + +### `stellar_signMessage` + +Sign an arbitrary UTF-8 text message with the wallet's active key, following [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md). + +**Parameters** + +| Parameter | Type | Constraints | Description | +| --- | --- | --- | --- | +| `message` | `string` | Non-empty, max 1024 UTF-8 bytes | The plaintext message to sign. | + +**Response** + +| Field | Type | Description | +| --- | --- | --- | +| `signature` | `string` | Base64-encoded Ed25519 signature (per SEP-53). | + +```typescript +const result = await provider.request( + { + method: "stellar_signMessage", + params: { message: "Please sign to verify account ownership" }, + }, + "stellar:pubnet", +); + +console.log("Signature:", result.signature); +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| Missing or non-string message | `"Invalid message"` | +| Empty message | `"Cannot sign empty message"` | +| Message exceeds 1 KB | `"Message too long (max 1KB)"` | +| Signing failed | `"Failed to sign message"` | +| User rejected | `"User rejected the request"` | + +--- + +## Signing an Auth Entry + +### `stellar_signAuthEntry` + +Sign a Soroban authorization entry preimage for multi-auth and custom-account smart contract workflows. Follows [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md). + +Your dapp constructs the `HashIdPreimage` (network ID + nonce + expiry + invocation), and Freighter hashes it with SHA-256 and signs the digest. + +{% hint style="warning" %} +Freighter Mobile performs a **Blockaid site scan** during the initial WalletConnect session connection — no additional scan runs at auth-entry signing time. Review the contract address, function name, and subinvocations displayed in the UI before confirming. +{% endhint %} + +**Parameters** + +| Parameter | Type | Constraints | Description | +| --- | --- | --- | --- | +| `entryXdr` | `string` | Non-empty, non-whitespace | Base64-encoded `HashIdPreimage` XDR (envelope type `envelopeTypeSorobanAuthorization`). Build from the `SorobanAuthorizationEntry` returned by contract simulation. | + +**Response** + +| Field | Description | +| --- | --- | +| `signedAuthEntry` | Base64-encoded 64-byte Ed25519 signature over `SHA-256(preimage)`. | +| `signerAddress` | Stellar public key (G-address) of the account that produced the signature. | + +```typescript +const result = await provider.request( + { + method: "stellar_signAuthEntry", + params: { entryXdr: "AAAAAQ..." }, + }, + "stellar:pubnet", +); + +console.log("Signature:", result.signedAuthEntry); +console.log("Signer:", result.signerAddress); +``` + +**Errors** + +| Condition | Error message | +| --- | --- | +| Missing, non-string, or whitespace-only `entryXdr` | `"Invalid authorization entry"` | +| XDR parse failure | `"Failed to process auth entry"` | +| `networkId` doesn't match active network | `"Authorization entry is for a different network"` | +| User rejected | `"User rejected the request"` | + +## Full Example: Connect, Sign, and Submit + +A complete flow from connection to transaction signing. The AppKit modal displays both a QR code and a list of wallets. When a user opens your dapp in Freighter Mobile's in-app browser, they select Freighter from the wallet list to deep-link into the connection approval — no QR code scanning needed. From an external browser, the user scans the QR code with their phone instead. + +```typescript +import { UniversalProvider } from "@walletconnect/universal-provider"; +import { createAppKit } from "@reown/appkit/core"; +import { mainnet } from "@reown/appkit/networks"; + +const projectId = "YOUR_PROJECT_ID"; + +// 1. Initialize provider and modal +const provider = await UniversalProvider.init({ + projectId, + metadata: { + name: "My Stellar Dapp", + description: "A dapp that integrates with Freighter Mobile", + url: "https://my-dapp.com", + icons: ["https://my-dapp.com/icon.png"], + }, +}); + +// AppKit requires at least one network. Stellar is not built-in, +// so we pass a placeholder. With manualWCControl the modal won't +// use it for chain switching. +const modal = createAppKit({ + projectId, + networks: [mainnet], + universalProvider: provider, + manualWCControl: true, +}); + +// 2. Open the modal and connect +modal.open(); + +const session = await provider.connect({ + namespaces: { + stellar: { + methods: ["stellar_signXDR", "stellar_signAndSubmitXDR", "stellar_signMessage", "stellar_signAuthEntry"], + chains: ["stellar:pubnet"], + events: ["accountsChanged"], + }, + }, +}); + +if (!session) { + throw new Error("Connection failed"); +} + +modal.close(); + +// 3. Sign a transaction +const xdr = "AAAAAgAAAAA..."; // your assembled transaction XDR +const result = await provider.request( + { + method: "stellar_signXDR", + params: { xdr }, + }, + "stellar:pubnet", +); + +console.log("Signed XDR:", result.signedXDR); +```