In the browser, the SDK talks either to your backend (proxyTransport, see Partner backend) or straight to the hub’s gateway with your public project id (projectTransport), and the user signs in their wallet. Everything else is the same client as on a server.
Never put a secret API key in a web page: any script there can read it, and whoever holds it acts as your organization. The hub’s gateway does not take a key from a browser anyway (its CORS pre-flight never allows x-api-key), and hubTransport refuses to start in a page. A page uses one of the two transports below.

Two ways to reach the hub

Through your backend: proxyTransport

proxyTransport sends no hub credential: only your own authentication (headers), which your backend’s resolveUser reads. Use credentials: "include" when your backend authenticates by cookie on another origin. From an https page, the proxy’s URL must be https too, or on this computer (http://localhost): the transport refuses, before any call, an address the browser would block as mixed content. It waits longer than your proxy does: 15 s on reads, 30 s on quotes, 35 s on submissions.

With a project id: projectTransport

  • List your pages’ origins on the project in the hub dashboard: the exact scheme, host and port of each page, and for development, for example, http://localhost:3000. While the list is empty, any origin is accepted; once it is set, a page on an unlisted origin gets EPX0010 (403).
  • Every route but the order history is open (PROJECT_ROUTES): the catalog, the quotes, order submission, both status views, the chain facts and the refund routes. GET /orders would show every visitor all of your orders: a call to it throws an InteropError (invalid-input) before any request.
  • Keep the ids of the orders the page submits (the submitted event). The order history is not open to a project id, so list those ids yourself.
  • Tracking and refunds work as on a server. The chain facts, the open flow’s lifecycle view and the refund routes are open to a project id, so executeQuote follows an order to its end, the filled event included, and the page can refund an order itself (History and refunds).
  • It names the visitor. The hub requires X-Infinity-Subject on the quotes, order submission and GET /order/{orderId}/status, so the transport sends one random id per browser and project there: visitor- and a UUID, kept in localStorage (for the life of the transport when storage is unavailable). Each visitor’s quotes and statuses stay apart, and a reload keeps the same id. A subject on the transport, the client or a call sends yours instead. The project id is public, so anyone can send any subject: it labels a visitor and proves nothing. Scope your signed-in users from your server (Authentication — your users).
  • The transport sends x-project-id, Content-Type on a POST, the subject where the hub requires it and the standard Accept, nothing more: the gateway sets the rest. It refuses a secret key in projectId, with a message that says so.
  • The gateway limits each visitor IP, plus a pool shared by every visitor of your project; a 429 is a HubError whose retryAfterMs says how long to wait. Its timeouts are 25 s on quotes, 12 s on reads and 15 s on submissions, a little longer than the gateway’s own.

The user’s wallets

window.ethereum belongs to whichever extension loaded last: with MetaMask and Phantom both installed, it may not be the wallet your user wants. List the installed wallets instead, each of which announces itself (EIP-6963), and pass the provider of the one they pick:
For Solana, any Wallet Standard wallet (Phantom, Solflare, Backpack…):
See Wallets for every adapter and option.

A bridge screen, step by step

1

Pickers from the catalog

await client.getCatalog() returns a CatalogIndex: chains, their assets, which assets can be a source (canBeSource), which are native (isNativeSource), and the token-in chains (acceptsTokenIn). It is cached for 5 minutes, and readable before your user signs in: through your backend’s proxy, or with your project id.
2

Estimates while the user types (optional)

client.previewQuotes(request) returns non-binding offers per provider, and nothing to sign. An empty offers array is a normal answer: no provider takes this route or amount right now.
3

A quote to review

client.getQuote(request) returns a Quote. Show the user:
  • what they pay: quote.payAmount (a ceiling when quote.payAmountIsCeiling, on the token-in lane);
  • what the recipient gets: quote.amountOut, a floor (a fill usually pays more, as the order is an auction);
  • the time left: quoteTimeLeftMs(quote), a client-clock countdown from when this quote was first received;
  • the checks: await client.vetQuote(quote), one row per check, all of which must pass.
The funding account and the recipient must be wallets the user connected; through your backend, the proxy refuses others.
4

Execute

The wallet prompts once or twice, depending on the lane: an approval, then a transaction or a signature. If less than 15 s is left on the quote, the SDK fetches a fresh one first, and continues only if it is not worse (same lane, no lower output, no higher cost). A worse one throws ReviewRequiredError with both quotes: show the new one and ask again.
5

Show the outcome

A filled event means the chain shows the recipient paid, usually seconds after submission. result.outcome is success once the order has settled. See Executing orders for every event.

History and refunds

Through your backend, client.listAllOrders() pages through the user’s orders (your backend scopes them to the user, by its records or by the user’s subject), and client.summarizeOrder(entry) turns each into a row with readable sides and a phase. A row whose order was never filled offers a refund once the order’s deadline has passed: see Refunds. With a project id, the history needs your backend, but refunds do not: show the orders this browser submitted, from the ids it kept, follow each with trackOrder, and refund one from the page with refundEvmOrder or refundSolanaOrder.

A complete example

The SDK repository’s examples/bridge-demo is a bridge page built this way; a hosted copy runs at demo.infinimesh.net. It has two views, switched from its header (and remembered; ?face=dev or ?face=simple in a link picks one). Both share one connection, the wallets and the browser’s order list:
  • Simple, a swap page with the essentials in the c8ntinuum look: the two tokens and the amount, a quote that refreshes on its own, one button that says what to do next, the route as the order moves (signed, submitted, filled), and your orders with their state and a refund button. It connects with your project id.
  • Dev, the walk-through: a Connection card for your project id (App ID, straight to the gateway) or your proxy server (examples/proxy-server), the step-by-step timeline, the check table and the raw wire calls.
Both offer test-network dev wallets for trying every lane without a browser extension, and list the installed browser wallets by name.