Skip to content
LogoLogo

Wallets & sessions

Your wallet owns your trading account. Two optional signers let you trade without a wallet popup on every order: 1-click trading (a key held in your browser) and trading passkeys. Both can only trade, and Flint pays the network fee for the trades they sign.

SignerCan doExpiresWho pays feesSet up by
Connected wallet (owner)EverythingNeverYouConnecting
1-click tradingPlace orders and close positions4h, 12h, or 7dFlintOwner wallet, once
Trading passkeyPlace orders and close positions90 daysFlintOwner wallet, once

Pick which one signs your trades under Configuration → Trading → Sign trades with: Connected wallet, 1-click trading, or Passkey {address}.

Wallet

Connect any Wallet Standard wallet from the header (Connect → Select wallet). The wallet that creates a trading account is its owner, and only the owner can:

  • deposit into a new asset;
  • withdraw, lend, or borrow, unless it has granted a delegate those rights (see below);
  • add or remove 1-click trading and passkeys.

Owner-signed transactions are never sponsored; you pay fees and rent in SOL. There's no email or passkey login. The app always starts from a connected wallet.

Delegates

1-click trading and passkeys are both built on on-chain delegates. A delegate is an extra key the owner authorises on one trading account, with a set of permissions and an optional expiry. The program checks both on every instruction the delegate signs.

ScopeAllows
TRADEplacePerpOrder, modifyPerpOrder, cancelAllPerpOrders, setAccountLeverageCap (and opening, releasing and updating a maker quoting slot, for makers)
WITHDRAWwithdraw
BORROW_LENDlend, redeemLend, borrow, repayBorrow
QUOTEMaker fast-quoting instructions

The owner bypasses scope and expiry. Flash loans and the liquidator and backstopper roles can't be delegated at all. Delegates carry no size or notional limits; scope and expiry are the whole policy.

Revoking (revokeDelegateTraderAccount, owner-signed) empties the delegate's scope. The account stays on-chain so its replay nonce can't be reset.

From code, an owner grants an Ed25519 delegate with createDelegateTraderAccount (ops: a list of DelegateOp, and expiryUnixSeconds, where 0 means never). An empty list is rejected. AccountService.ListDelegates lists an account's delegates with their credential kind (ED25519 or PASSKEY), scope bits, expiry, and whether they're active. ListAccounts({ authority }) returns accounts where a key is owner or delegate.

1-click trading

A signing key generated in your browser and authorised to trade for one account.

Enable it:

  1. Configuration → Trading → 1-click trading.
  2. Pick a Session duration: 4h, 12h, or 7d.
  3. Press Enable 1-click trading and approve the one transaction in your wallet.

The panel then shows Ready for 1-click trades and Expires {date}. Choose it under Sign trades with, and orders and closes go out without a wallet popup. The ticket shows "1-click signing · Flint sponsors trade fees". Order confirmations are skipped when signing this way.

What it is: an Ed25519 key created as non-extractable in your browser's storage (IndexedDB) and tied to this browser, wallet, trading account, and network. "The local signing key cannot be exported." On-chain it's a delegate with TRADE scope only, and the expiry you picked is enforced by the program.

What it can't do: withdraw, deposit, lend or borrow, settle PnL, create accounts, or manage delegates. Those still go through your wallet.

Remove it: Remove delegate, confirmed by your wallet. Only the owner can enable or remove 1-click trading. A wallet that's only a delegate of an account can't set it up.

Trading passkeys

A passkey (Face ID, Touch ID, Windows Hello, a security key, or a synced passkey) registered as a trade-only delegate. Each order is approved with a biometric or PIN prompt instead of a wallet popup.

Add one:

  1. Configuration → Trading → Trading passkeys.
  2. Press Add passkey and complete your device's passkey prompt.
  3. Approve the registration transaction in your wallet.

"New passkeys can trade for 90 days. Deposits, withdrawals, and passkey management require your wallet." Remove access revokes a passkey, signed by your wallet. A passkey is bound to the site that created it. One registered elsewhere shows "Registered for another app or site" and can't be used here.

How a passkey order is checked on-chain:

  • The program only ever gives a passkey TRADE scope and refuses to widen it, so a passkey can never withdraw or borrow.
  • The device signs a challenge built from the order itself: the program, every account in the instruction (including the fee payer and each maker), and the order's arguments. Nothing about the order can change after you approve it.
  • Each approval carries the delegate's next nonce and a valid-until slot at most 150 slots ahead, so it can be used once and only for about a minute. Reusing one fails with PasskeyNonceMismatch: "This passkey was used for another trade. Review and retry your order."
  • User verification is required (PasskeyUserNotVerified without it), and the origin and relying-party id must match the ones registered.
  • The signature is checked by Solana's secp256r1 precompile, which must be the instruction immediately before the order.

Passkey errors are codes 30–45; see Errors.

Passkey orders from code

@superis-labs/flint-margin-user-client builds both instructions:

import {
  buildRegisterPasskeyDelegateInstruction,
  passkeyRegistrationFromWebAuthn,
  preparePasskeyPerpOrder,
} from "@superis-labs/flint-margin-user-client";
 
// Once, owner-signed: register the credential as a TRADE delegate.
const registerIx = buildRegisterPasskeyDelegateInstruction({
  owner, traderId, registration, programAddress,
  expiryUnixSeconds, // absolute unix seconds; 0n never expires
});
 
// Per order: get the challenge, run navigator.credentials.get with it,
// then complete into [secp256r1 verify, placePerpOrder].
const prepared = preparePasskeyPerpOrder({ /* market accounts, order, nonce, validUntilSlot, … */ });
const [verifyIx, orderIx] = prepared.complete({ authenticatorData, signature, clientDataJson });

preparePasskeyPerpOrder builds immediate-or-cancel orders only and accepts at most two maker accounts. The two instructions must stay adjacent and top-level in the transaction. On-chain, a passkey can also authorise modifyPerpOrder, cancelAllPerpOrders, and setAccountLeverageCap, but the SDK has no helper for those yet.

Transaction sponsorship

Trades signed by 1-click trading or a passkey are sponsored: Flint's sponsor key pays the network fee and any rent the order needs. Your wallet needs no SOL for them. Everything signed by your wallet (account creation, deposits, withdrawals, settlement, adding or removing delegates) is wallet-paid.

If sponsorship is unavailable, the app doesn't fall back to wallet payment; it shows "Transaction sponsorship is unavailable on the selected endpoint." Switch Sign trades with to Connected wallet to keep trading and pay your own fees.

Sponsorship rules

SponsorshipService co-signs only as fee payer. The server checks every transaction before signing:

RuleDetail
ProgramsOnly the margin program, Compute Budget, and secp256r1. Each secp256r1 instruction must sit right before a margin instruction.
InstructionsplacePerpOrder or revokePasskeyDelegate, on an existing trading account. The transaction may also include syncLinearPerpCache. Modifying or cancelling orders and setting a leverage cap are not sponsored.
SignaturesAt most 3, counting the sponsor, any delegate wallet, and passkey verifications. All but the sponsor's must already be present and valid.
Priority feeAt most 100,000 lamports.
SimulationMust succeed before the sponsor signs.
RateCurrently one sponsored transaction per 10 seconds per trading-account owner (RESOURCE_EXHAUSTED past that).

Using it from code

  1. GetSponsorshipPubkey returns the sponsor's address (UNAVAILABLE if the endpoint has no sponsor).
  2. Build the transaction with that address as fee payer (account 0), and leave its signature slot empty.
  3. Sign with your delegate (or attach the passkey instructions).
  4. Send the serialized transaction to SponsorTransaction. It returns the signature after confirmation.

Policy failures return INVALID_ARGUMENT with the reason.

Security summary

ThreatOwner wallet1-click keyPasskey
Withdraw fundsYesNoNo
TradeYesUntil expiry or removalUntil expiry or removal; each order needs user verification
Grant itself more rights—NoNo (program refuses)
Replay a signed orderNormal Solana rulesNormal Solana rulesNo: one nonce, ~150-slot window
Stolen with the deviceDepends on the walletUsable from that browser profileNeeds the device's biometric or PIN

Account data isn't private: anyone can read any trading account's balances, positions, and fills by trader id.