Build on Liquid Connect

One protocol connects Liquid wallets to sites and services. Keys stay in the wallet, every approval happens there, and SideSwap runs the server in between.

Make your wallet a Liquid Connect wallet

One integration gives your users every Liquid Connect site, from trading to payments and contract flows, with keys that never leave your wallet and approvals that always happen on your screens.

  1. 1

    An identity, not an account

    Your wallet logs in with a challenge signed by a key derived from material it already holds. The public key is the wallet's identity, with nothing to register and nothing new to back up.

  2. 2

    Activation before pairing

    Joining Liquid Connect shares the wallet's descriptor with the Connect server and never with sites, so it is its own moment of consent. Contact the server only once the user activates, and show them their Liquid Connect ID then.

  3. 3

    Catch the link

    Register the liquidconnect scheme and scan QR codes. A link carries a single use request ID. Your wallet claims it and shows the site that asks. A link that arrives before activation leads into activation first and then on to the pairing.

  4. 4

    Verify, then sign

    Requests to sign arrive as PSETs. Your wallet is the only verifier. It decodes, shows and signs with its own keys. The SDK turns a PSET into something to show, and says plainly what it cannot read.

use lc_wallet_core::key::{Network, WalletKey};
use lc_wallet_core::transport::{WalletConnect, WalletConnectConfig, WalletEvent};
use lc_wallet_core::{approval, wire};

// The wallet's Connect identity, from key material it already holds.
let key = WalletKey::new(master_blinding_key, Network::Liquid);

let (wallet, mut events) = WalletConnect::spawn(WalletConnectConfig {
    url: wire::MAINNET_URL.to_owned(),
    descriptor: wallet_descriptor, // shared with the Connect server, never with sites
    key,
    install_id,                    // InstallId::random(), persisted once
});

// A scanned QR or a tapped link claims the waiting request.
wallet.open_link(&scanned_text)?;

while let Some(event) = events.recv().await {
    match event {
        WalletEvent::LoginRequested(req) => {
            // Show req.domain, and on approval
            wallet.accept_login(&req.request_id);
        }
        WalletEvent::SignRequested(req) => {
            let summary = approval::summarize_pset(&req.pset, Network::Liquid)?;
            // Show the summary, check it with your own decode, sign with
            // your own signer, then hand back the result
            wallet.accept_sign(&req.request_id, &signed_pset);
        }
        _ => {}
    }
}

Reconnecting, keepalive, the challenge login and the life of each request are handled for you. The state machine does no networking of its own, so a wallet with its own networking can drive it directly.

A Rust core with Kotlin and Swift bindings. Write to [email protected] for access while it is prepared for public release.

Optional, a reachable identity for your users

The identity module lets a user attach a verified email address or phone number to their wallet, so people who already know them can find and pay them. Matching is on hashes and discoverability is opt in. No contact detail is stored in plain text.

Connect

Free

Sessions, approvals and signing

Email identity

Free

A verified email address that people who have it can pay

Phone identity

0.5 USDt

A verified number, paid from the connected wallet itself. A wallet holding only USDt can pay, as the network fee goes through SideSwap's payjoin.

What the SDK will never do

Hold keys, sign transactions or approve anything. Signing belongs to your wallet alone. The Connect server relays requests and cannot spend.

Wallet connectivity without custody

Your site or app gets a live view of the wallets that approve it, limited to its own session, and can ask them to sign. That is all it gets. Keys stay in the wallet and the user can end the connection at any time.

One WebSocket and JSON frames, no SDK needed. sideswap.io is itself an integration.

  1. 1

    Connect as your domain

    Open a WebSocket to the Connect server and log in as your domain. It is what the user's wallet shows when you ask for anything. In production you prove you own it.

  2. 2

    Ask to connect

    StartLogin returns a single use, short lived request ID. Show it as a QR code or a link. The user approves in their wallet.

  3. 3

    Receive the session

    Approval creates a session and the server streams snapshots of the wallet's coins, blinding data included, so you can build valid confidential PSETs without looking anything up. You never see the descriptor.

  4. 4

    Ask for a signature

    Build the PSET and send StartSign with the session. The wallet checks it, the user approves, the signed PSET comes back and you broadcast it.

→ {"Req":{"id":1,"req":{"StartLogin":{"client_data":null}}}}
← {"Resp":{"id":1,"resp":{"StartLogin":{"login_request":{"request_id":"…","expires_at":…}}}}}
   … the user approves in their wallet …
← {"Notif":{"notif":{"SessionCreated":{"session":{…}}}}}
← {"Notif":{"notif":{"WalletUpdated":{"wallet":{"wallet_id":"…","utxos":[…]}}}}}
→ {"Req":{"id":2,"req":{"StartSign":{"session_id":"…","pset":"…","ttl":300000}}}}
← {"Notif":{"notif":{"SignRequestUpdated":{"sign_request":{"status":{"Succeed":{"pset":"…"}}}}}}}

Requests are single use and time limited, so a request the user never approves simply expires. The Connect server only relays. It never inspects your PSET and cannot sign anything.

What you get, and what you never get

  • Live snapshots of the coins of each wallet that approved you, for its session
  • A signing channel that ends at the user's own device
  • QR and app link hand off to phone and desktop wallets
  • The wallet descriptor, keys, or history beyond your session
  • A signature without the user approving your request, by name
  • Anything after the user ends the session

More than payments

Connections and PSET signing are the base. On testnet your service can also use richer requests, which reach production as the network rolls forward.

Clear signed messages

Ask the wallet to sign a digest with a readable description of what it allows. The wallet rebuilds the digest from the typed fields before anyone approves, so what the user reads is what gets signed.

Payment requests

Send the recipient, asset, amount and a memo, and the wallet builds, approves, signs and broadcasts the payment from its own coins.

Venue account links

Bind a wallet to an account at your service in one scan, with guards against a link being steered elsewhere.

The connect button

One script tag gives your site the whole flow of connecting a SideSwap wallet, the same on every Liquid Connect site. A phone opens the wallet directly and comes back by itself, a desktop chooses between a QR for the phone and a wallet on the same computer, and the wait survives the trip to the app. Your backend keeps talking to Liquid Connect as it does, and the button only owns the part people see.

<script src="https://sideswap.io/connect/lc-connect.js"></script>
<div id="lc"></div>
<script>
  LiquidConnect.mount("#lc", {
    start: "/api/connect/start",   // POST
    status: "/api/connect/status", // GET ?request_id=
    onConnected(answer) {
      // answer is what your status route returned
    },
  });
</script>

What your backend answers

RouteYour backend answersWhat the button does
POST start{ request_id, deep_link, expires_at }Shows the QR or opens the wallet for this single use request
GET status{ status: "pending" } or { status: "WaitUser" }Keeps waiting. The wallet has the request, or soon will
GET status{ status: "approved", … }Closes the modal and calls onConnected with this answer
GET statusanything elseShows a failure with a retry, unless your onStatus says to keep waiting

Rules it keeps

  • On a phone the wallet is opened directly and told to hand the person back to your page once they approve.
  • The QR always carries the plain link, because the phone that scans it is a different device.
  • It keeps asking for 90 seconds past the request's expiry, so an approval given in time is still picked up.
  • Nothing in it holds a secret. Your backend issues whatever session it likes in its answer.

Networks and domain proof

One protocol on two networks. Start on testnet, where any domain can connect. In production a site is identified by its domain, so production asks for proof that you own it.

NetworkWallets connect toSites connect toWho can connect
Testnetwss://api-testnet.sideswap.io/wallet-connectwss://api-testnet.sideswap.io/server-connectAny domain, to build and test
Productionwss://api.sideswap.io/wallet-connectwss://api.sideswap.io/server-connectSites that prove their domain

A DNS TXT record at _liquidconnect.<your-domain> or a file at https://<your-domain>/.well-known/liquid-connect/rp.json

The proof is one record for the domain your site logs in as, either a DNS TXT record or a file at a fixed path on that domain. The user's wallet shows your domain on every request, and the proof is what makes that name worth trusting. Write to [email protected] for the record's contents while the scheme settles.

Talk to us about integrating at [email protected].