Files
zeekayandHanzo Dev 8d7457cdf0 Lux ID is the only way in
Base v1.5.72 closed the door this depended on, and closed it deliberately:
"IAM IS THE ONLY AUTH. There is no second way in... _superusers is not an
exception: it reaches schema, settings, backups and logs, and one process
serves many orgs' Bases, so two independently-keyed doors to it would be
one more than can be reasoned about."

The sandbox login was the second door. It kept a bcrypt hash in its own
collection and minted a _superusers token, and until that release Base
honoured such a token as a fallback. After it, the route still answered
200 with a token and the very next request answered 401 — a login that
succeeds and authenticates nothing. Every authed route was refusing, and
the suite could not see it: the test harness never registers the org
plugin, so 141 tests passed green against a binary whose demo login was
broken.

It was also the custom auth this estate does not build. Gone: the route,
the credentials collection, the token minting, the demo password that was
a constant in a file every browser downloads, and the merged hook shape
that let one path stand in for the other. The SPA already had the PKCE
flow it needed.

What this costs: [email protected] must exist in Lux IAM under org lux, app
lux-bank. The demo cannot sign in until it does. That is an IAM seeding
task, not a code one, and it is the same identity convention every other
surface already follows.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-26 15:16:53 -07:00
..
2026-08-26 15:16:53 -07:00

@luxfi/bank

Typed client for the Lux banking API (/v1/bank) — accounts, transfers, payments, beneficiaries, cards, crypto, exchange, FX, and membership plans. Sandbox and production serve the identical contract (LP-3040); point the client at either.

import { Bank } from '@luxfi/bank'

const bank = new Bank({
  baseUrl: 'https://api.sandbox.lux.financial',
  token: () => sessionStorage.getItem('token'),
})

const plans = await bank.plans()
const { holdings } = await bank.wallet()
await bank.sendCrypto('ETH', 400_000, '0x1234…5678') // minor units, 6 dp

// Card issuance — branch only on the issuer's status/nextAction pair:
const { data } = await bank.cardAccount()
if (data.virtualAccount?.nextAction === 'complete_kyc') {
  const { url } = await bank.cardKYCURL() // sensitive — straight to the browser
  window.open(url)
}

Amounts are integers in minor units; every balance carries decimals (2 for most fiat, 0 for JPY, 6 for crypto). Errors throw BankError with status and the parsed body.

Docs: docs.lux.financial · Spec: LP-3040.