# Mesh Connect — llms-full.txt Complete condensed reference for AI coding assistants. Covers all Mesh Guides across all five SDK platforms (Web, iOS, Android, React Native, Flutter). Following the llms.txt standard (https://llmstxt.org). **Sandbox base URL**: `https://sandbox-integration-api.meshconnect.com` **Production base URL**: `https://integration-api.meshconnect.com` **Auth headers**: `X-Client-Id: ` and `X-Client-Secret: ` on every server-side request. --- ## Prepare to build Get a Mesh dashboard account at [dashboard.meshconnect.com](https://dashboard.meshconnect.com). Create a sandbox API key under Account > API keys. Add allowed domains under Account > API keys > Access (include localhost:3000 for local dev). **SDK installs**: Web: `npm install @meshconnect/web-link-sdk` | iOS: Swift Package Manager or CocoaPods -> `https://github.com/FrontFin/mesh-ios-sdk` | Android: `implementation 'com.meshconnect:link:'` in `build.gradle` | React Native: `npm install @meshconnect/react-native-link-sdk react-native-webview` | Flutter: `mesh_sdk_flutter` in `pubspec.yaml` --- ## 15-Minute Quickstart 5-step end-to-end integration using the Web SDK: (1) `npm install @meshconnect/web-link-sdk` (2) `POST /api/v1/linktoken` from your server (3) pass `linkToken` to client (4) call `createLink({...callbacks})` then `connection.openLink(linkToken)` (5) handle callbacks. **Sandbox test credentials** (all accounts use same password/OTP): Password `Pass123`, OTP `123456`. Username `Mesh` = full portfolio for general testing. **Key networkIds**: Solana `0291810a-5947-424d-9a59-e88bb33e999d` | Ethereum `e3c7fdd8-b1fc-4e51-85ae-bb276e075611` | Base `aa883b03-120d-477c-a588-37c2afd3ca71` **Minimal Link Token request (deposit)**: `POST https://sandbox-integration-api.meshconnect.com/api/v1/linktoken` with headers `X-Client-Id` and `X-Client-Secret`, body `{ "userId": "user-123", "transferOptions": { "transferType": "deposit", "toAddresses": [{ "networkId": "0291810a...", "symbol": "USDC", "address": "YOUR_ADDRESS" }] } }` **Minimal Web SDK init**: `import { createLink } from '@meshconnect/web-link-sdk'` then `const connection = createLink({ onIntegrationConnected, onTransferFinished, onExit })` then `connection.openLink(linkToken)` --- ## Fetch a Link Token **Endpoint**: `POST /api/v1/linktoken`. Tokens are 10 minutes, single-use. Fetch on button click, not on page load. Request a new one each session. **Required field**: `userId` — unique, non-PII string, max 300 chars. **`transferOptions` parameters**: `transferType` (`"deposit"` | `"payment"` | `"onramp"`, omit for withdrawal/verify) | `toAddresses[]` array of `{ networkId, symbol, address }` — all three required for deposit/payment | `amount` required for `payment` type | `isInclusiveFeeEnabled` boolean — fees deducted from `amount` if true | `generatePayLink` boolean | `amountInFiat` fiat display value | `addressTag` memo/tag for XRP, XLM, etc. | `minAmount` / `minAmountInFiat` minimum transfer size | `transactionId` your internal transaction ID **`verifyWalletOptions`**: `verificationMethods: ["signedMessage"]` | `message` the message to sign | `networkId` OR `networkType` (not both) | `addresses[]` list to verify **Other top-level fields**: `restrictMultipleAccounts` boolean | `subClientId` for B2B/PSP multi-merchant setups | `integrationId` to pre-select a provider **Canonical deposit request**: `POST /api/v1/linktoken` body `{ "userId": "UNIQUE_USER_ID", "restrictMultipleAccounts": true, "transferOptions": { "transferType": "deposit", "toAddresses": [{ "networkId": "", "symbol": "USDC", "address": "YOUR_SOL_ADDRESS" }, { "networkId": "", "symbol": "USDC", "address": "YOUR_ETH_ADDRESS" }, { "networkId": "", "symbol": "USDC", "address": "YOUR_BASE_ADDRESS" }] } }` --- ## Launch the Mesh SDK **Web SDK** — `createLink({ renderType, theme, language, displayFiatCurrency, accessTokens, onIntegrationConnected, onTransferFinished, onExit, onEvent })`. Params: `renderType` `'overlay'` or `'embedded'` | `theme` `'system'`/`'dark'`/`'light'` | `language` `'system'` or BCP 47 code | `displayFiatCurrency` ISO 4217, default `'USD'` | `accessTokens` MMT return-user array. Launch: `connection.openLink(linkToken)` (overlay) or `connection.openLink(linkToken, 'custom-iframe-id')` (embedded). Close: `connection.closeLink()`. **Rendering options**: Overlay = modal popup. Embedded = iframe in your container (min 450px, recommended 665px; pass `renderType: 'embedded'` and a custom iframe ID). Paylink = set `generatePayLink: true` in Link Token -> response includes `paymentLink` URL, no SDK install needed, short-lived single-use. **iOS (Swift)**: Create `LinkSettings(accessTokens:, language: "system", displayFiatCurrency: "USD", theme: .system)`. Create `LinkConfiguration(linkToken:, settings:, onIntegrationConnected:, onTransferFinished:, onEvent:, onExit:)`. Call `configuration.createHandler()` -> on `.success(let handler)`: set `linkHandler = handler` then `handler.present(in: viewController)`. In `onExit`: call `linkHandler?.showExitAlert()` if `showAlert == true`, else `linkHandler?.closeLink()`. **Android (Kotlin)**: Register `registerForActivityResult(LaunchLink()) { result -> when(result) { is LinkSuccess -> handle result.payloads; is LinkExit -> handle result.errorMessage } }`. Create `LinkConfiguration(token = linkToken, theme = LinkTheme.SYSTEM, language = "system", displayFiatCurrency = "USD", accessTokens = accessTokens)`. Launch: `linkLauncher.launch(configuration)`. **React Native**: `` **Flutter**: `await MeshSdk.show(context, configuration: MeshConfiguration(linkToken:, language: 'system', displayFiatCurrency: 'USD', theme: ThemeMode.system, integrationAccessTokens: accessTokens, onEvent:, onError:, onSuccess:, onIntegrationConnected:, onTransferFinished:))` --- ## Use Mesh's Callback Functions **`onIntegrationConnected`** — fires when user connects an exchange or wallet. Store `payload.accessToken.accountTokens[0].tokenId` and `brokerType` server-side for MMT return-user flows. Full payload: `{ accessToken: { accountTokens: [{ tokenId, brokerType, ... }] }, brokerType, brokerName, ... }` **`onTransferFinished`** — fires when transfer request acknowledged by provider. Important: NOT on-chain confirmation — use webhooks for business logic. Use only for UI updates (show success screen). Payload: `{ status, transferId, txHash, symbol, amount, amountInFiat, totalAmountInFiat, fromAddress, toAddress, refundAddress, networkId, networkName }` **`onExit`** — fires whenever Link closes. Check `page` field: `'transferExecutedPage'` = flow completed. Call `connection.closeLink()` here if using embedded mode. **`onEvent`** — fires for granular user interactions. Useful for analytics. Payload: `{ type: '', payload: { ... } }` **Web**: In `onIntegrationConnected`: extract `payload.accessToken.accountTokens[0]` -> `{ tokenId, brokerType }` -> save to server. In `onTransferFinished`: update UI from `payload.status`. In `onExit`: call `connection.closeLink()`. In `onEvent`: `analytics.track(event.type, event.payload)`. **iOS (Swift)**: Pass closures to `LinkConfiguration`. In `onIntegrationConnected`: pattern-match `if case .accessToken(let p) = linkPayload` -> extract `p.accountTokens.first?.tokenId`. **Android (Kotlin)**: Results come via registered `LaunchLink()` activity result: `is LinkSuccess -> result.payloads` contains `IntegrationConnected` / `TransferFinished` payloads. `is LinkExit -> result.errorMessage`. --- ## Supercharge Return Users (MMT — Mesh Managed Tokens) Skip re-authentication for returning users by passing a stored `tokenId` back to the SDK. **Step 1 — Capture on first connection**: In `onIntegrationConnected`, extract `const { tokenId, brokerType } = payload.accessToken.accountTokens[0]` and store server-side keyed to userId. **Step 2 — Build accessTokens array**: `[{ accessToken: '', brokerType: '', brokerName: '', accountId: '', accountName: '' }]` — note: put the tokenId in the `accessToken` field. **Step 3 — Pass to SDK on return visits**: Web: `createLink({ accessTokens: accessTokens, ...callbacks })` | iOS: `LinkSettings(accessTokens: accessTokens, language: "system", displayFiatCurrency: "USD", theme: .system)` | Android: `LinkConfiguration(token = linkToken, accessTokens = accessTokens, theme = LinkTheme.SYSTEM, language = "system", displayFiatCurrency = "USD")` | React Native: `` | Flutter: `MeshConfiguration(integrationAccessTokens: accessTokens, language: 'system', displayFiatCurrency: 'USD', theme: ThemeMode.system, linkToken: linkToken, ...)` **Token lifecycle**: Same user + same integration -> always same `tokenId`. Revoked tokens permanently disabled (new connection required). Binance: tokens expire but `tokenId` never changes — no DB update needed. Supported integrations: Coinbase, Binance, Uphold. Not available in sandbox. --- ## Polish the Experience **Entry point UX**: Label button "Direct deposit" or "Pay with crypto". Lead with exchange logos/icons. Offer a dedicated return-user button ("Pay with Coinbase in 1 tap"). **Session lifecycle**: Preload Link session on user login (before they click). Refresh Link Token every 50 minutes (sessions expire at 60 min). Refresh triggers: address change, theme change, connectivity restore, modal close. **Closing Link**: `closeLinkRequested()` = user-initiated dismiss, shows Mesh confirmation dialog if mid-flow, then fires `onExit` — call this when user clicks your X button. `closeLink()` = programmatic force-close, no confirmation, no `onExit` — use inside `onExit` handler to tear down iframe. Do NOT call `closeLink()` inside `onTransferFinished` — user is still on the success screen. **Embedded iframe**: Keep in DOM between sessions (hide/show with CSS — don't destroy/recreate). Min width 450px, recommended 665px. In modals: `min(665px, calc(100dvh - Xpx))` where X = modal chrome height (~100–160px). **iOS plist — `LSApplicationQueriesSchemes` (wallet deep links)**: bitcoin, bitkeep, bitcoincom, bnc, cbwallet, dfw, exodus, ledgerlive, metamask, okx, phantom, rabby, rainbow, robinhood-wallet, tpoutside, tronlinkoutside, trust, uniswap, zengo **Link customization**: Dashboard > Account > Link Configuration > Interface (fonts, colors, loading animations, logo). --- ## Transfer Status Webhooks **Setup**: Register callback URI at Dashboard > Account > API keys > Webhooks. Secret shown once — store securely. Separate sandbox and production URIs. Whitelist Mesh IP: `20.22.113.37` **Signature verification**: Header `X-Mesh-Signature-256`. Compute `crypto.createHmac('sha256', WEBHOOK_SECRET).update(rawBody).digest('base64')` over **raw request body bytes** (not parsed JSON) and compare to the header value. Critical: never parse/re-serialize the body before computing the HMAC. Use `express.raw({ type: '*/*' })` NOT `express.json()` on the webhook route. Respond with 200 in under 200ms, then process asynchronously. **Webhook handler pattern**: Route with `express.raw({ type: '*/*' })` -> read `req.body` as raw Buffer -> compute HMAC-SHA256 Base64 digest -> compare to `req.headers['x-mesh-signature-256']` -> if mismatch return 400 -> else `res.sendStatus(200)` -> then `processAsync(JSON.parse(rawBody))` **Delivery semantics**: At-least-once delivery — deduplicate on `EventId` (stable across retries; `Id` changes per delivery). `pending` webhook not guaranteed in sandbox. **Transfer payload fields**: `EventId` (idempotency key) | `TransferId` | `TransactionId` (your ID from Link Token) | `TransferStatus`: `pending` | `succeeded` | `failed` | `TxHash` | `Token` | `Chain` | `SourceAmount` | `DestinationAmount` | `DestinationAddress` | `SourceAddress` | `RefundAddress` | `RefundType` | `TransferSteps[]` for bridge/multi-step transfers (each has `StepOrder`, `Chain`, `Token`, `TxHash`, `SourceAddress`) **Flow**: `pending` -> `succeeded` | `failed`. Use **`succeeded`** to credit users / release inventory, never `onTransferFinished`. --- ## Prepare for Go-Live 1. **Production webhook URI**: Dashboard > Account > API keys > Webhooks > Production (separate from sandbox — sandbox webhook URIs don't carry over). 2. **Update to latest SDK version**: Check GitHub before go-live. Web: `mesh-web-sdk` | iOS: `mesh-ios-sdk` | Android: `mesh-android-sdk` | React Native: `mesh-react-native-sdk` | Flutter: `mesh-flutter-sdk` 3. **Production API key**: Dashboard > Account > API keys > API keys > Production. Permissions: Read-only or Read & Write (required for transfers). Prerequisites: 2FA enabled + Mesh business verification complete. 4. **Production base URL**: `https://integration-api.meshconnect.com` (Sandbox: `https://sandbox-integration-api.meshconnect.com`) 5. **Content Security Policy**: `frame-src`: add `*.meshconnect.com`. Tron only — `connect-src`: add `https://tron.twnodes.com`, `https://trx.mytokenpocket.vip`, `https://api.trongrid.io` --- ## Add Mesh Onramp Integrations Let users buy crypto with fiat and send it directly to your address. **Link Token for onramp**: Set `transferOptions.transferType: "onramp"`, `isInclusiveFeeEnabled: true`, `amountInFiat: `, and `toAddresses: [{ networkId, symbol, address }]`. **Optional: pre-select a provider** by passing `integrationId` in Link Token body (get integrationId from `GET /api/v1/transfers/managed/integrations`). **Optional: Quote API** — `POST /api/v1/transfers/managed/quote` with body `{ amountInFiat, fiatCurrency, symbol, networkId, toAddress, brokerType }`. Response includes `minAmountFiat` (show to users before amount entry), `isEligible` (check before displaying a provider), and fee breakdown. **Exchange icons**: Use `logoUrl` from the integrations endpoint. --- ## Add Mesh to Withdrawal Flow Enable users to withdraw from your platform back to their exchange/wallet. **Webhook `refundAddress`**: In your `transfer.succeeded` webhook, save `RefundAddress` + `Chain` + `Token` + `SourceAccountProvider` per userId. This is the address to send funds back to. **Retrieve user deposit address** — `POST /api/v1/transfers/managed/address/list` with body `{ type: "", authToken: "", symbol: "USDC", networks: [{ networkId: "..." }] }`. Response includes address per network, `networkName`, `logoUrl`, `memo` (critical for XRP, XLM — always display to user). **Withdrawal Link Token**: Minimal body is just `{ "userId": "UNIQUE_USER_ID", "restrictMultipleAccounts": true }` (no `transferOptions` needed). Add `integrationId` to pre-select provider. Set `restrictMultipleAccounts: false` for address book / multi-account use cases. --- ## Verify Self-Hosted Wallets Prove a user owns a specific wallet address via message signing. **Link Token for wallet verification**: Include `verifyWalletOptions: { verificationMethods: ["signedMessage"], message: "Sign this message...", networkId: "" }`. Use `networkId` OR `networkType`, never both. Can be combined with a deposit/payment Link Token. **`walletMessageSigned` event payload** (via `onEvent`): `signedMessageHash` (store immediately — Mesh does not retain it) | `message` | `address` | `timeStamp` | `isVerified` **`verifyWalletRejected`**: fires if user declines to sign. **Testing**: MetaMask on Sepolia or Phantom on Solana Devnet. --- ## How It All Fits Together Architecture: 3-party model — your server <-> Mesh API, your client <-> Mesh SDK (Link), Mesh -> your webhook endpoint. **4-step integration flow**: (1) Server -> Mesh: `POST /api/v1/linktoken` -> returns `linkToken`, configures full user session. (2) Client -> SDK: pass `linkToken` to `createLink()` / `LinkConfiguration` / `MeshConfiguration`, call `openLink()`, Mesh handles all UX. (3) SDK -> Client callbacks: `onIntegrationConnected` save `tokenId` for MMT | `onTransferFinished` update UI | `onExit` check `page` field | `onEvent` analytics. (4) Mesh -> Server webhooks: `pending` -> `succeeded` or `failed`, use `succeeded` to credit users, always verify HMAC-SHA256 signature. **Critical**: `onTransferFinished` fires when the provider acknowledges the request, not on-chain confirmation. Always use the `transfer.succeeded` webhook for final confirmation. **Return users (MMT)**: Store `tokenId` from `onIntegrationConnected`. Pass as `accessTokens` on next session -> user skips re-auth entirely. **Platforms**: Web, iOS, Android, React Native, Flutter — all use the same Link Token; initialization pattern differs per SDK. --- ## Concepts Glossary **Link**: Mesh's hosted UX (iframe / native view) for connecting accounts, initiating transfers, verifying wallets. Available on 5 SDKs: Web, iOS, Android, React Native, Flutter. **Link Token**: Short-lived (10 min), single-use token requested server-side via `POST /api/v1/linktoken`. Configures the entire user session. Every session starts with one. **Integrations**: Exchanges (Coinbase, Binance, etc.) and wallets (MetaMask, Phantom, etc.) that Mesh connects to. Mesh handles all auth flows. **Mesh Managed Tokens (MMT)**: Mesh stores and refreshes OAuth tokens on your behalf. You store a stable `tokenId`. User skips re-auth on return visits. `tokenId` never changes even if underlying access token rotates. **SmartFunding**: Auto-selects best source asset for a transfer based on available balance, network compatibility, fees, and speed. **refundAddress**: Address funds can be returned to. For exchange transfers, this is a user deposit address within the exchange (not the originating on-chain address, which is an omnibus hot wallet). Returned in webhooks and `GET /api/v1/transfers/managed/mesh`. **Sub-clients**: Child Mesh accounts for B2B/PSP use cases. Each gets independent branding, API credentials, and compliance settings. **Webhooks**: Server-to-server event notifications from Mesh. Use `EventId` as idempotency key (stable across retries; `Id` changes per delivery). At-least-once delivery. Register at Dashboard > Account > API keys > Webhooks. **Transfer types**: `deposit` user sends to your address | `payment` deposit with fixed `amount` | `onramp` user buys with fiat | `withdrawal` retrieve user deposit addresses (no `transferType` in Link Token) | `verify` wallet ownership via `verifyWalletOptions` | types can be combined --- ## Multi-Language Support Configure the `language` parameter to display Link in the user's preferred language. Set in SDK initialization on all 5 platforms. Use `'system'` to auto-detect device/browser locale, or a BCP 47 code (e.g. `'en-US'`, `'fr'`, `'zh-CN'`). **Fallback chain**: unknown region -> language default (e.g. `fr-FR` for any French variant) -> `en-US`. Unsupported language -> `en-US`. **Currently live**: en-US, zh-CN, fi-FI, fr-FR, de-DE, hi-IN, id-ID, ja-JP, ms-MY, pl-PL, pt-PT, ru-RU, es-US, th-TH, tr-TR, uk-UA, uz-UZ, vi-VN **Backlog (not yet live)**: ar-EG, zh-HK, zh-TW, cs-CZ, da-DK, nl-NL, en-AU, en-GB, en-IN, fr-CA, el-GR, he-IL, hu-HU, it-IT, ko-KR, no-NO, pt-BR, sk-SK, es-ES, sv-SE --- ## Fiat Currency Support Configure the `displayFiatCurrency` parameter to show fiat amounts in the user's preferred currency throughout Link. Set in SDK initialization on all 5 platforms. Use ISO 4217 currency codes. Defaults to `'USD'` if not specified. **Currently live**: USD (US Dollar) | EUR (Euro) | GBP (British Pound) **Backlog (not yet live)**: CNY, JPY, CHF, CAD, AUD, KRW, HKD, INR, BRL, MXN, SGD, TRY, SAR, AED, ZAR, THB Affects all fiat-equivalent display values throughout Link. Does not affect which currencies users can transact in — only the display conversion. `displayFiatCurrency` and `language` are set in the same SDK initialization call. --- ## Mesh SDK Events Complete reference of all SDK events fired via the `onEvent` callback during a Link session. **Core events**: `pageLoaded` (Link launched, no payload) | `integrationConnected` (user connected account; payload: `integrationType`, `integrationName`, `accessToken` containing `tokenId`) | `transferPreviewed` (user viewed preview; payload: `symbol`, `amount`, `amountInFiat`, `toAddress`, `fees` with `institutionTransferFee`/`estimatedNetworkGasFee`/`customClientFee`, `previewId`) | `transferInitiated` (user clicked Proceed; same payload as `transferPreviewed`) | `transferCompleted` (user reached Success page, triggers `onTransferFinished`; use `onTransferFinished` for business logic, this event for analytics only) | `transferNoEligibleAssets` (no matching assets; payload: `arrayOfTokensHeld` each with `symbol`, `amount`, `amountInFiat`, `ineligibilityReason`) | `close` (user dismissed Link; payload: `page`; if `page === 'transferExecutedPage'` -> full flow completed) **Wallet verification events**: `walletMessageSigned` (payload: `signedMessageHash`, `message`, `address`, `timeStamp`, `isVerified`) | `verifyWalletRejected` (no payload) | `verifyDonePage` (no payload) **Other notable events**: `integrationSelected` | `transferDeclined` | `connectionDeclined` | `transferExecutionError` | `linkTransferQRGenerated` | `executeFundingStep` | `done` (read-only or verify flow complete; `page` = `verifyDonePage` or `integrationConnectedPage`) | `methodSelected` | `transferAssetSelected` | `transferNetworkSelected` | `transferAmountEntered` | `fundingOptionsViewed` | `fundingOptionsUpdated` | `gasIncreaseWarning` --- ## Sandbox & Testnets Full production replica for testing. Uses simulated exchange accounts and real blockchain testnets. No real assets move. **Sandbox base URL**: `https://sandbox-integration-api.meshconnect.com` **CEX test accounts** (all use password `Pass123`, MFA/OTP `123456`): - `Mesh`: full portfolio (BTC, ETH, BNB, USDC, USDT, SOL, XRP+), $10M — general testing - `Mesh2`: empty portfolio, $0 — empty state / zero balance testing - `Mesh3`: no crypto, $10M cash only — buy/onramp testing - `Mesh4`: full portfolio, $100M — large transaction testing - `MeshBTC` / `MeshUSDC` / `MeshUSDT` / `MeshETH` / `MeshSOL`: single-asset portfolios for asset-specific flows **Wallet testnets**: MetaMask: Sepolia (`SEPOLIAETH`, stablecoins: PYUSD, USDG) and Base Sepolia (`SEPOLIAETH`, stablecoins: USDC, EURC). Rainbow: Sepolia (`SEPOLIAETH`, stablecoins: PYUSD, USDG). Phantom: Solana Devnet (`DEVNETSOL`, stablecoins: PYUSD, USDG) — enable via Settings > Developer Settings > Testnet mode. **Get testnet tokens**: [sepoliafaucet.com](https://sepoliafaucet.com) (MetaMask) | [sepolia-faucet.pk910.de](https://sepolia-faucet.pk910.de) (Rainbow) | [faucet.solana.com](https://faucet.solana.com) (Phantom) | or ask your Mesh representative. **Sandbox limitations**: Only Coinbase + legacy Binance available (other production exchanges not replicated). MMT not supported in sandbox. `pending` webhook not guaranteed — transfers may go directly to `succeeded` or `failed`. Only MetaMask, Rainbow, and Phantom for wallet flows. --- ## Managing Sub-Clients Register and manage sub-clients (child Mesh accounts) for B2B/PSP contexts where you serve multiple downstream merchants. **When to use**: Only for platforms that embed Mesh in products for other businesses (PSPs, payment platforms). Single-product direct-to-consumer apps do not need sub-clients. **Register via Dashboard**: Account > Link Configuration > Clients > Add a client. Fields: Business Legal Name, Display Name, Callback URL(s), icon upload. **Register via API**: `POST` to Account Management API at `https://admin-api.meshconnect.com`. Requires separate Account Management API key (Dashboard: Account > API keys > Account management API keys) and IP allowlisting. **Apply to user session**: Pass `subClientId` in Link Token request body alongside `userId` and `transferOptions`. Result: Link displays sub-client branding instead of top-level account branding. Omitting or using an incorrect `subClientId` silently falls back to top-level branding — no error thrown. **Testing**: Register sandbox sub-clients separately from production. Use sandbox base URL for sandbox sessions. --- ## Manual Deposits Mesh's QR code / copy-paste fallback deposit flow. Enabled by default for all deposit sessions. Supports SmartFunding and bridging. **How it works**: User shown QR code + destination address + token/network + memo/tag if applicable. User initiates transfer from their own wallet or exchange. Mesh monitors blockchain. On detection -> `status: succeeded` webhook (no `pending` event first). **Attribution logic**: Transfer attributed when correct token + network + address received, within 15 minutes of user viewing the QR code (under 30 min for BTC), and no concurrent connected-account deposit for that session. **Appears in two scenarios**: (1) Primary option on landing page alongside "Connect account" (configurable). (2) Automatic fallback when direct connection fails or is unavailable. **Not appropriate for**: Non-unique deposit addresses (unless using memo/tag), source-of-funds compliance requirements, exact payment amount requirements, or clients that must restrict to direct-only flows (contact Mesh to disable). --- ## Intelligent Catalog Filtering Mesh automatically filters which integrations appear in Link during session initialization. No client action required — filtering is automatic. **5 filter types**: 1. **Asset & Network Compatibility** (all clients): Only integrations supporting at least one `toAddresses` asset/network pair are shown. 2. **VASP ID Requirement** (Travel Rule — custodial platforms): If your business is a VASP, Mesh lacks your VASP ID, and user IP is in an affected country (EU countries + NZ, SG, KR) -> impacted exchanges hidden. 3. **Wallet Ownership Requirement** (Travel Rule — self-custody): If destination is a self-custody wallet, user IP in affected country, and transfer > 1000 EUR (or any amount in SG, HK, PH, KR) -> impacted exchanges hidden. 4. **Exchange Geography Restrictions** (all clients): Provider-level IP restrictions automatically applied. Examples: Binance hidden for US/Canada/Netherlands IPs; Robinhood shown only for US IPs. 5. **Use Case Restrictions** (varies): Some providers restrict transfers based on platform type and geography. Applied automatically. **Action**: Contact your Mesh account manager to validate coverage for specific asset/network combinations or user regions, or to provide your VASP ID. --- ## Common Errors **SDK domain not authorized** (CORS error or silent auth failure on load): Domain not on allowlist. Fix: Dashboard > Account > API keys > Access -> add all domains including `localhost:3000` and staging URLs. Takes effect within a few minutes. **SDK iframe blocked by CSP** (blank grey box in embed area): CSP `frame-src` doesn't permit Mesh's iframe. Fix: add `*.meshconnect.com` to `frame-src`. **Tron transfers blocked by CSP**: Add `https://tron.twnodes.com`, `https://trx.mytokenpocket.vip`, `https://api.trongrid.io` to `connect-src`. **Ad-blocker interfering with OAuth** (spinner, OAuth never resolves): uBlock Origin, AdBlock Plus, or Brave blocking browser storage / OAuth redirects. Fix: user disables ad-blocker for your domain. Not officially supported. **Link Token expired or already used** (error on `openLink()`): Tokens are 10 min, single-use. Fetching on page load means it can expire before the user clicks. Fix: fetch a new token on button click. Never cache or reuse tokens. **Empty catalog / symbol not supported** (empty integration list or `toAddresses` validation error): ERC-20 contract address (e.g. `0xA0b8...`) passed as `symbol` instead of ticker (e.g. `USDC`). Fix: use ticker symbol. Key networkIds: Ethereum `e3c7fdd8-b1fc-4e51-85ae-bb276e075611`, Solana `0291810a-5947-424d-9a59-e88bb33e999d`, Base `aa883b03-120d-477c-a588-37c2afd3ca71`. Include multiple networkId/symbol pairs to maximize coverage. **No eligible assets after connection** (`transferNoEligibleAssets` event fires): User's connected account holds no assets matching your `toAddresses`. Fix: broaden `toAddresses` to cover more asset/network combinations. Handle `transferNoEligibleAssets` and use `arrayOfTokensHeld` (each entry has `symbol`, `amount`, `amountInFiat`, `ineligibilityReason`) to show a helpful message. Sandbox: use `Mesh2` account to test empty-state UI. **Webhook signature validation fails** (every event rejected with signature mismatch): Computing HMAC over parsed/re-serialized JSON instead of raw bytes, or `express.json()` middleware on webhook route destroys raw bytes, or using `.digest('hex')` instead of `.digest('base64')`. Fix: scope `express.raw({ type: '*/*' })` to the webhook route only, compute HMAC over `req.body` as a raw Buffer, Base64-encode the digest, compare to `X-Mesh-Signature-256` header. **Duplicate webhook deliveries** (same event processed multiple times): Mesh delivers at-least-once. Non-2xx or slow responses trigger retries. Fix: use `EventId` as idempotency key (`EventId` is constant across retries; `Id` changes per delivery). Return 200 immediately, process async. **OAuth blank screen or raw code in mobile WebView** (OAuth flow never completes in in-app browser): iOS `WKWebView` / Android `WebView` block `window.open()` by default. Fix: iOS -> implement `WKUIDelegate.webView(_:createWebViewWith:...)`. Android -> override `WebChromeClient.onCreateWindow()`. React Native -> use `react-native-inappbrowser-reborn` or `Linking.openURL`. Or ask Mesh about redirect-based OAuth (deep link / custom URL scheme). **Transfer stays pending or finalizes as failed**: CEX processing delay is normal (up to 24h for Coinbase/Binance). `pending` fires at exchange initiation; `succeeded` fires on-chain. Check `minAmount` / `minAmountInFiat` in `toAddresses` to prevent below-minimum transfers. Verify destination address format is valid for the specified network. User 2FA timeout may cause the exchange to drop the transfer. Sandbox: `pending` webhook not guaranteed. For stuck production transfers: contact Mesh with the `TransferId` from the webhook payload.