@orangecheck/auth-client 2.33.4 → 2.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appearance.mjs +23 -0
- package/dist/appearance.mjs.map +1 -0
- package/dist/bond.mjs +20 -0
- package/dist/bond.mjs.map +1 -0
- package/dist/chunk-7ST4Q5KR.mjs +346 -0
- package/dist/chunk-7ST4Q5KR.mjs.map +1 -0
- package/dist/chunk-7UYPMTAV.mjs +652 -0
- package/dist/chunk-7UYPMTAV.mjs.map +1 -0
- package/dist/chunk-BYAF2P2L.mjs +253 -0
- package/dist/chunk-BYAF2P2L.mjs.map +1 -0
- package/dist/chunk-CYR7J4UY.mjs +118 -0
- package/dist/chunk-CYR7J4UY.mjs.map +1 -0
- package/dist/chunk-D3HXJ64H.mjs +914 -0
- package/dist/chunk-D3HXJ64H.mjs.map +1 -0
- package/dist/chunk-FDWEXEZL.mjs +490 -0
- package/dist/chunk-FDWEXEZL.mjs.map +1 -0
- package/dist/chunk-H2TXOCXH.mjs +23 -0
- package/dist/chunk-H2TXOCXH.mjs.map +1 -0
- package/dist/chunk-IQVBEYSW.mjs +1172 -0
- package/dist/chunk-IQVBEYSW.mjs.map +1 -0
- package/dist/chunk-TJJNTITX.mjs +187 -0
- package/dist/chunk-TJJNTITX.mjs.map +1 -0
- package/dist/chunk-VRLFZAGK.mjs +37 -0
- package/dist/chunk-VRLFZAGK.mjs.map +1 -0
- package/dist/chunk-XCZVSE7M.mjs +40 -0
- package/dist/chunk-XCZVSE7M.mjs.map +1 -0
- package/dist/components.mjs +19 -0
- package/dist/components.mjs.map +1 -0
- package/dist/index.mjs +112 -4001
- package/dist/index.mjs.map +1 -1
- package/dist/linked-identities.mjs +41 -0
- package/dist/linked-identities.mjs.map +1 -0
- package/dist/provider.mjs +17 -0
- package/dist/provider.mjs.map +1 -0
- package/dist/return-to.mjs +18 -0
- package/dist/return-to.mjs.map +1 -0
- package/dist/signin.mjs +21 -0
- package/dist/signin.mjs.map +1 -0
- package/dist/sudo.mjs +16 -0
- package/dist/sudo.mjs.map +1 -0
- package/dist/tab-session.mjs +29 -0
- package/dist/tab-session.mjs.map +1 -0
- package/dist/types.mjs +17 -0
- package/dist/types.mjs.map +1 -0
- package/dist/webauthn.mjs +15 -0
- package/dist/webauthn.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/index.d.mts +0 -303
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/linked-identities.tsx"],"sourcesContent":["'use client';\n\n/**\n * @orangecheck/auth-client · OcLinkedIdentities\n *\n * The shared linked-identities management surface for every `X.ochk.io`\n * consumer. A signed-in user sees their linked email + Bitcoin address,\n * can reveal/hide each raw value, unlink a non-primary identity, and\n * link a new one. Linking a Bitcoin address that's already on another\n * OC account offers a dual-proof transfer; the same is now true for\n * email (auth host F4). Consolidated (`merged_from`) accounts render in\n * a tombstone panel.\n *\n * This generalizes me.ochk.io's bespoke `LinkedIdentitiesPanel` so\n * linking is no longer a single-product, one-tab feature — any family\n * site can drop in `<OcLinkedIdentities />`. See AUTH-PLAN.md §5.\n *\n * All endpoints live on the auth host (`ochk.io`); calls are\n * cross-origin with `credentials: 'include'` and rely on the auth\n * host's family-CORS — the same posture `OcSignIn` uses. No local\n * proxy route is needed (AUTH-AUDIT.md F6: direct-to-host is the\n * intended design).\n *\n * Style: inline, CSS-variable-themed (same approach as `OcSignIn`), so\n * the component carries zero CSS dependency and themes to each site.\n * Every block carries a `data-oc-li-*` attribute so a site may\n * override via its own stylesheet.\n */\n\nimport * as React from 'react';\nimport {\n handleSudoRequired,\n isSudoAccountMismatch,\n SUDO_ACCOUNT_MISMATCH_MESSAGE,\n} from './sudo';\n\nimport {\n SIGNIN_BUTTON_RADIUS,\n SIGNIN_CASE,\n SIGNIN_FONT,\n signinRadius,\n signinSize,\n signinTracking,\n} from './appearance';\nimport { tabSessionHeader } from './tab-session';\n\nconst DEFAULT_AUTH_ORIGIN = 'https://ochk.io';\n\n/* --- types --- */\n\nexport interface OcLinkedIdentity {\n id: string;\n kind: 'email' | 'btc';\n /** Raw value · null when the identity is pending recovery. */\n value: string | null;\n verified_at: string | null;\n verification_method: 'email-otp' | 'bip322' | null;\n /** The signup-time identity for its kind · cannot be unlinked here. */\n is_primary: boolean;\n}\n\ninterface MergedFromEntry {\n did_oc: string;\n created_at: string;\n last_signed_in_at: string | null;\n}\n\ninterface IdentitiesResp {\n ok: boolean;\n account_id?: string;\n did_oc?: string | null;\n linked?: OcLinkedIdentity[];\n merged_from?: MergedFromEntry[];\n reason?: string;\n}\n\n/**\n * Fetch the signed-in user's linked identities from the auth host —\n * the same data `<OcLinkedIdentities>` renders, exposed as a plain\n * function for surfaces that need the list without the full\n * management UI (notably `<OcAccountMenu>`'s badge-identity promote\n * list). Returns `[]` when anonymous; throws on network / server\n * error so the caller can show a deferred state.\n */\nexport async function fetchOcLinkedIdentities(opts?: {\n authOrigin?: string;\n}): Promise<OcLinkedIdentity[]> {\n const authOrigin = opts?.authOrigin ?? DEFAULT_AUTH_ORIGIN;\n // Per-tab pinning: forward the tab's session JWT so the host resolves\n // the SAME account this tab displays (its chip reads /api/auth/me\n // tab-aware). Without this, a pinned tab's show-as list is the shared\n // cookie account's identities — a menu that disagrees with itself.\n // Unpinned tabs send no header → unchanged cookie-default behavior.\n const r = await fetch(`${authOrigin}/api/auth/identities`, {\n credentials: 'include',\n headers: { ...tabSessionHeader() },\n });\n if (r.status === 401) return [];\n if (!r.ok) throw new Error(`identities fetch failed: http_${r.status}`);\n const body = (await r.json()) as IdentitiesResp;\n return body.linked ?? [];\n}\n\nexport interface OcLinkedIdentitiesProps {\n /** Auth host origin. Defaults to `https://ochk.io`. */\n authOrigin?: string;\n /** Outer container className. */\n className?: string;\n /** Called after every successful link / unlink / transfer. */\n onChange?: () => void;\n /**\n * Show the \"portable attestation\" card that links out to attest.ochk.io.\n * Defaults to `true`. Pass `false` on a page that already hosts its own\n * attestation builder (ochk.io's dashboard), or the same action is offered\n * twice, one panel apart.\n */\n attestationLink?: boolean;\n}\n\n/* --- main --- */\n\nexport function OcLinkedIdentities({\n authOrigin = DEFAULT_AUTH_ORIGIN,\n className,\n onChange,\n attestationLink = true,\n}: OcLinkedIdentitiesProps): React.ReactElement {\n const [resp, setResp] = React.useState<IdentitiesResp | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n const [showEmailLink, setShowEmailLink] = React.useState(false);\n const [showBtcLink, setShowBtcLink] = React.useState(false);\n\n const refresh = React.useCallback(async () => {\n setError(null);\n try {\n const r = await fetch(`${authOrigin}/api/auth/identities`, {\n credentials: 'include',\n headers: { ...tabSessionHeader() },\n });\n if (!r.ok) {\n if (r.status === 401) {\n setError('not signed in');\n return;\n }\n throw new Error(`http_${r.status}`);\n }\n setResp((await r.json()) as IdentitiesResp);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'load failed');\n }\n }, [authOrigin]);\n\n React.useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const afterChange = React.useCallback(() => {\n setShowEmailLink(false);\n setShowBtcLink(false);\n void refresh();\n onChange?.();\n }, [refresh, onChange]);\n\n if (error) {\n return (\n <div className={className} data-oc-li=\"\" style={panelStyle('warn')} role=\"alert\">\n <SectionLabel tone=\"warn\">§ identities · load deferred</SectionLabel>\n <p style={bodyStyle}>\n Couldn't reach the auth host ({error}). Linking is temporarily\n unavailable; your signed-in identity still works as normal.\n </p>\n <button type=\"button\" onClick={() => void refresh()} style={ghostBtnStyle(false)}>\n retry\n </button>\n </div>\n );\n }\n\n if (!resp) {\n return (\n <div className={className} data-oc-li=\"\" style={panelStyle()}>\n <span style={hintStyle}>loading…</span>\n </div>\n );\n }\n\n const linked = resp.linked ?? [];\n const emailRows = linked.filter((i) => i.kind === 'email');\n const btcRows = linked.filter((i) => i.kind === 'btc');\n const hasEmail = emailRows.some((i) => i.value);\n const hasBtc = btcRows.some((i) => i.value);\n const mergedFrom = resp.merged_from ?? [];\n\n return (\n <div className={className} data-oc-li=\"\" style={{ display: 'grid', gap: 16 }}>\n <div style={panelStyle()}>\n <SectionLabel>§ what this is</SectionLabel>\n <p style={bodyStyle}>\n These are the ways you can sign in to this account. An app you sign in to\n sees one only if you grant it. Linking a second one is also your way back\n in: lose one, and the other still signs you in.\n </p>\n </div>\n\n {mergedFrom.length > 0 && <ConsolidationPanel entries={mergedFrom} />}\n\n <div style={panelStyle('plain')} data-oc-li-rows=\"\">\n <IdentityGroup\n kind=\"email\"\n title=\"email\"\n rows={emailRows}\n authOrigin={authOrigin}\n onChange={afterChange}\n addCta={hasEmail ? null : 'link an email'}\n onAdd={() => {\n setShowEmailLink((v) => !v);\n setShowBtcLink(false);\n }}\n />\n <div style={{ borderTop: `1px solid ${V.border}` }} />\n <IdentityGroup\n kind=\"btc\"\n title=\"bitcoin address\"\n rows={btcRows}\n authOrigin={authOrigin}\n onChange={afterChange}\n addCta={hasBtc ? null : 'link a Bitcoin address'}\n onAdd={() => {\n setShowBtcLink((v) => !v);\n setShowEmailLink(false);\n }}\n />\n </div>\n\n {showEmailLink && (\n <EmailLinkForm\n authOrigin={authOrigin}\n onDone={afterChange}\n onCancel={() => setShowEmailLink(false)}\n />\n )}\n {showBtcLink && (\n <BtcLinkForm\n authOrigin={authOrigin}\n didOc={resp.did_oc ?? null}\n onDone={afterChange}\n onCancel={() => setShowBtcLink(false)}\n />\n )}\n\n {hasBtc && attestationLink && <AttestationLink />}\n </div>\n );\n}\n\n/* --- identity group (rows for one kind, or an empty-state add CTA) --- */\n\nfunction IdentityGroup({\n kind,\n title,\n rows,\n authOrigin,\n onChange,\n addCta,\n onAdd,\n}: {\n kind: 'email' | 'btc';\n title: string;\n rows: OcLinkedIdentity[];\n authOrigin: string;\n onChange: () => void;\n addCta: string | null;\n onAdd: () => void;\n}): React.ReactElement {\n if (rows.length === 0) {\n return (\n <div style={rowStyle}>\n <div>\n <div style={rowLabelStyle}>§ {title}</div>\n <div style={{ ...bodyStyle, marginTop: 4 }}>none linked</div>\n </div>\n {addCta && (\n <button type=\"button\" onClick={onAdd} style={primaryChipStyle}>\n + {addCta}\n </button>\n )}\n </div>\n );\n }\n return (\n <>\n {rows.map((r) => (\n <IdentityRow\n key={r.id}\n row={r}\n kind={kind}\n authOrigin={authOrigin}\n onChange={onChange}\n />\n ))}\n </>\n );\n}\n\n/* --- single identity row --- */\n\nfunction IdentityRow({\n row,\n kind,\n authOrigin,\n onChange,\n}: {\n row: OcLinkedIdentity;\n kind: 'email' | 'btc';\n authOrigin: string;\n onChange: () => void;\n}): React.ReactElement {\n const [shown, setShown] = React.useState(false);\n const [confirming, setConfirming] = React.useState(false);\n const [busy, setBusy] = React.useState(false);\n const [err, setErr] = React.useState<string | null>(null);\n\n const masked = row.value ? mask(row.value, kind) : '— pending recovery —';\n const display = shown && row.value ? row.value : masked;\n\n async function unlink(): Promise<void> {\n if (row.is_primary) return;\n setBusy(true);\n setErr(null);\n try {\n const r = await fetch(\n `${authOrigin}/api/auth/identities?id=${encodeURIComponent(row.id)}`,\n { method: 'DELETE', credentials: 'include' }\n );\n if (!r.ok) {\n const j = (await r.json().catch(() => ({}))) as { reason?: string };\n throw new Error(j.reason ?? `http_${r.status}`);\n }\n onChange();\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'unlink failed');\n setBusy(false);\n setConfirming(false);\n }\n }\n\n return (\n <div style={rowStyle} data-oc-li-row={kind}>\n <div style={{ minWidth: 0, flex: 1 }}>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px 12px' }}>\n <span style={rowLabelStyle}>\n § {kind === 'email' ? 'email' : 'bitcoin address'}\n </span>\n {row.is_primary && (\n <span style={{ ...rowLabelStyle, color: V.primary }}>\n primary · signup-time\n </span>\n )}\n </div>\n <div\n style={{\n marginTop: 4,\n color: V.foreground,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(12),\n wordBreak: 'break-all',\n }}\n >\n {display}\n </div>\n {row.verified_at && (\n <div style={{ ...hintStyle, marginTop: 4 }}>\n verified · {new Date(row.verified_at).toUTCString()}\n </div>\n )}\n {err && <ErrorLine>{err}</ErrorLine>}\n </div>\n <div style={{ display: 'flex', flexShrink: 0, gap: 8, flexWrap: 'wrap' }}>\n {row.value && (\n <button\n type=\"button\"\n onClick={() => setShown((s) => !s)}\n style={ghostBtnStyle(false)}\n >\n {shown ? 'hide' : 'show'}\n </button>\n )}\n {!row.is_primary && !confirming && (\n <button\n type=\"button\"\n onClick={() => setConfirming(true)}\n style={dangerBtnStyle(false)}\n >\n unlink\n </button>\n )}\n {!row.is_primary && confirming && (\n <>\n <button\n type=\"button\"\n onClick={() => void unlink()}\n disabled={busy}\n style={dangerBtnStyle(busy)}\n >\n {busy ? 'unlinking…' : 'confirm unlink'}\n </button>\n <button\n type=\"button\"\n onClick={() => setConfirming(false)}\n disabled={busy}\n style={ghostBtnStyle(busy)}\n >\n cancel\n </button>\n </>\n )}\n </div>\n </div>\n );\n}\n\n/* --- consolidated-accounts tombstone panel --- */\n\nfunction ConsolidationPanel({ entries }: { entries: MergedFromEntry[] }): React.ReactElement {\n return (\n <div style={panelStyle('success')} data-oc-li-consolidated=\"\">\n <SectionLabel tone=\"success\">\n § consolidated accounts · {entries.length}\n </SectionLabel>\n <p style={bodyStyle}>\n {entries.length === 1 ? 'A previous OC account' : 'Previous OC accounts'} you\n signed up with, folded into this one under dual proof of control. Historical\n activity is unioned into this account automatically;{' '}\n {entries.length === 1 ? 'it' : 'they'} can no longer sign in.\n </p>\n <ul style={{ listStyle: 'none', margin: '12px 0 0', padding: 0 }}>\n {entries.map((e) => (\n <li\n key={e.did_oc}\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n gap: 8,\n padding: '8px 0',\n borderTop: `1px solid ${V.border}`,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(11),\n }}\n >\n <code style={{ color: V.foreground, wordBreak: 'break-all' }}>\n {e.did_oc}\n </code>\n <span style={{ ...hintStyle, marginLeft: 'auto' }}>\n created {new Date(e.created_at).toLocaleDateString()}\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/* --- portable-attestation pointer --- */\n\n/**\n * A thin pointer to the OC Attest builder. The portable, self-sovereign\n * form of identity linking is a standard OrangeCheck attestation — one\n * BIP-322 signature by the Bitcoin address binding it to its handles,\n * published to Nostr. That ceremony already lives at\n * `attest.ochk.io/create` (which pre-fills the address from the signed-in\n * session); this panel routes the user there rather than re-implementing\n * it. Shown once the account has a linked Bitcoin address to attest with.\n */\nfunction AttestationLink(): React.ReactElement {\n return (\n <div style={panelStyle()} data-oc-li-attest=\"\">\n <SectionLabel>§ portable attestation</SectionLabel>\n <p style={bodyStyle}>\n Make your identity links portable: publish a standard OrangeCheck\n attestation — one Bitcoin-wallet signature (BIP-322) binding your address\n to your handles (Nostr, GitHub, …). It is published to Nostr and verifies\n with zero trust in OrangeCheck, so it outlives any single service.\n </p>\n <a\n href=\"https://attest.ochk.io/create\"\n target=\"_blank\"\n rel=\"noreferrer\"\n style={{ ...primaryChipStyle, textDecoration: 'none', display: 'inline-block' }}\n data-oc-li-attest-link=\"\"\n >\n open the attestation builder ↗\n </a>\n </div>\n );\n}\n\n/* --- post-sign-in link prompt (used by OcSignIn) --- */\n\n/** Where the ways to sign in are managed: ochk.app, which replaces me.ochk.io's pages. */\nconst ACCOUNT_WAYS_URL = 'https://ochk.app/me/account#ways';\n\n/**\n * Shown by `<OcSignIn>` after a successful sign-in *when the user ticked\n * the \"also link my other identity\" checkbox on the sign-in form*. It runs\n * the complementary identity's link ceremony inline — BIP-322 for a\n * Bitcoin address, OTP for an email.\n *\n * Skipping is first-class: even though the user opted in at the form, they\n * may change their mind here — an explicit \"skip, I'll do this later\"\n * control exits cleanly and tells them where to link later. `onResolved`\n * (linked, or skipped) hands control back to OcSignIn's post-sign-in\n * navigation.\n */\nexport function LinkPromptStep({\n method,\n didOc,\n authOrigin,\n onResolved,\n}: {\n /** The complementary identity to link — the opposite of the sign-in method. */\n method: 'btc' | 'email';\n didOc: string;\n authOrigin: string;\n /** Called once the user has linked the identity, or skipped. */\n onResolved: () => void;\n}): React.ReactElement {\n const what = method === 'btc' ? 'a Bitcoin address' : 'an email';\n return (\n <div data-oc-linkprompt={method}>\n <div style={{ ...panelStyle('success'), marginBottom: 16 }}>\n <SectionLabel tone=\"success\">§ signed in</SectionLabel>\n <p style={{ ...bodyStyle, margin: 0 }}>\n You chose to also link {what}. Complete it below — or skip for now; it is\n entirely optional and changes nothing about the sign-in you just completed.\n </p>\n </div>\n {method === 'btc' ? (\n <BtcLinkForm\n authOrigin={authOrigin}\n didOc={didOc}\n onDone={onResolved}\n onCancel={onResolved}\n />\n ) : (\n <EmailLinkForm authOrigin={authOrigin} onDone={onResolved} onCancel={onResolved} />\n )}\n <div\n data-oc-linkprompt-skip=\"\"\n style={{ marginTop: 14, paddingTop: 14, borderTop: `1px solid ${V.border}` }}\n >\n <button type=\"button\" onClick={onResolved} style={ghostBtnStyle(false)}>\n skip — I'll do this later\n </button>\n <p style={{ ...hintStyle, marginTop: 8 }}>\n Changed your mind? Skipping continues you straight on — your sign-in is\n unaffected. You can link {what} anytime later from your account page,{' '}\n <a\n href={ACCOUNT_WAYS_URL}\n target=\"_blank\"\n rel=\"noreferrer\"\n style={{ color: 'inherit' }}\n data-oc-linkprompt-account=\"\"\n >\n ochk.app/me/account\n </a>\n .\n </p>\n </div>\n </div>\n );\n}\n\n/* --- email link form --- */\n\nfunction EmailLinkForm({\n authOrigin,\n onDone,\n onCancel,\n}: {\n authOrigin: string;\n onDone: () => void;\n onCancel: () => void;\n}): React.ReactElement {\n const [step, setStep] = React.useState<'email' | 'code'>('email');\n const [email, setEmail] = React.useState('');\n const [code, setCode] = React.useState('');\n const [token, setToken] = React.useState('');\n const [busy, setBusy] = React.useState(false);\n const [err, setErr] = React.useState<string | null>(null);\n // Set when the auth host reports the email is linked elsewhere — the\n // user can confirm a dual-proof transfer (session + verified OTP).\n const [transfer, setTransfer] = React.useState(false);\n\n async function start(): Promise<void> {\n setBusy(true);\n setErr(null);\n try {\n const r = await fetch(`${authOrigin}/api/auth/email-otp/start`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email.trim().toLowerCase() }),\n });\n const j = (await r.json()) as { ok?: boolean; token?: string; reason?: string };\n if (!r.ok || !j.token) throw new Error(j.reason ?? `http_${r.status}`);\n setToken(j.token);\n setStep('code');\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'could not send code');\n } finally {\n setBusy(false);\n }\n }\n\n async function verify(confirmTransfer: boolean): Promise<void> {\n setBusy(true);\n setErr(null);\n try {\n const r = await fetch(`${authOrigin}/api/auth/link/email`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: email.trim().toLowerCase(),\n code: code.trim(),\n token,\n ...(confirmTransfer ? { confirm_transfer: true } : {}),\n }),\n });\n const j = (await r.json()) as { ok?: boolean; reason?: string };\n if (r.status === 409 && j.reason === 'email_linked_elsewhere') {\n setTransfer(true);\n setBusy(false);\n return;\n }\n if (handleSudoRequired(j, { purpose: 'link an email' })) return;\n if (isSudoAccountMismatch(j)) {\n setErr(SUDO_ACCOUNT_MISMATCH_MESSAGE);\n setBusy(false);\n return;\n }\n if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);\n onDone();\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'link failed');\n setBusy(false);\n }\n }\n\n return (\n <div style={panelStyle('accent')} data-oc-li-form=\"email\">\n <SectionLabel>§ link an email</SectionLabel>\n {step === 'email' ? (\n <>\n <input\n type=\"email\"\n autoComplete=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n style={inputStyle}\n />\n {err && <ErrorLine>{err}</ErrorLine>}\n <FormButtons>\n <button\n type=\"button\"\n onClick={() => void start()}\n disabled={busy || email.trim().length < 5}\n style={primaryBtnStyle(busy || email.trim().length < 5)}\n >\n {busy ? 'sending…' : 'send code'}\n </button>\n <button type=\"button\" onClick={onCancel} style={ghostBtnStyle(busy)}>\n cancel\n </button>\n </FormButtons>\n </>\n ) : transfer ? (\n <TransferConfirm\n what=\"email\"\n busy={busy}\n onConfirm={() => void verify(true)}\n onCancel={onCancel}\n err={err}\n />\n ) : (\n <>\n <p style={bodyStyle}>Enter the 6-digit code sent to {email}.</p>\n <input\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n maxLength={6}\n value={code}\n onChange={(e) => setCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n placeholder=\"123456\"\n style={{ ...inputStyle, letterSpacing: '0.3em' }}\n />\n {err && <ErrorLine>{err}</ErrorLine>}\n <FormButtons>\n <button\n type=\"button\"\n onClick={() => void verify(false)}\n disabled={busy || code.length !== 6}\n style={primaryBtnStyle(busy || code.length !== 6)}\n >\n {busy ? 'linking…' : 'verify + link'}\n </button>\n <button\n type=\"button\"\n onClick={() => setStep('email')}\n disabled={busy}\n style={ghostBtnStyle(busy)}\n >\n back\n </button>\n </FormButtons>\n </>\n )}\n </div>\n );\n}\n\n/* --- btc link form --- */\n\nfunction BtcLinkForm({\n authOrigin,\n didOc,\n onDone,\n onCancel,\n}: {\n authOrigin: string;\n didOc: string | null;\n onDone: () => void;\n onCancel: () => void;\n}): React.ReactElement {\n const [step, setStep] = React.useState<'request' | 'sign'>('request');\n const [addr, setAddr] = React.useState('');\n const [challenge, setChallenge] = React.useState('');\n const [busy, setBusy] = React.useState(false);\n const [err, setErr] = React.useState<string | null>(null);\n // Holds the signature awaiting transfer confirmation when the\n // address is already linked to another account.\n const [pendingSig, setPendingSig] = React.useState<string | null>(null);\n\n async function getChallenge(): Promise<void> {\n if (!didOc) {\n setErr('session not loaded yet · refresh and retry');\n return;\n }\n setBusy(true);\n setErr(null);\n try {\n // /api/challenge is a public primitive — no session needed.\n // audience MUST be the caller's did_oc: the link endpoint\n // verifies expectedAudience=session.did_oc, so a signature\n // obtained here can't be replayed against another account.\n const r = await fetch(\n `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}` +\n `&purpose=link&audience=${encodeURIComponent(didOc)}`\n );\n const j = (await r.json()) as { message?: string; reason?: string };\n if (!r.ok || !j.message) throw new Error(j.reason ?? `http_${r.status}`);\n setChallenge(j.message);\n setStep('sign');\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'challenge failed');\n } finally {\n setBusy(false);\n }\n }\n\n async function postLink(sig: string, confirmTransfer: boolean): Promise<void> {\n setBusy(true);\n setErr(null);\n try {\n const r = await fetch(`${authOrigin}/api/auth/link/btc`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n message: challenge,\n signature: sig,\n ...(confirmTransfer ? { confirm_transfer: true } : {}),\n }),\n });\n const j = (await r.json()) as { ok?: boolean; reason?: string };\n if (r.status === 409 && j.reason === 'address_linked_elsewhere') {\n setPendingSig(sig);\n setBusy(false);\n return;\n }\n if (handleSudoRequired(j, { purpose: 'link a Bitcoin address' })) return;\n if (isSudoAccountMismatch(j)) {\n setErr(SUDO_ACCOUNT_MISMATCH_MESSAGE);\n setBusy(false);\n return;\n }\n if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);\n onDone();\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'link failed');\n setBusy(false);\n }\n }\n\n return (\n <div style={panelStyle('accent')} data-oc-li-form=\"btc\">\n <SectionLabel>§ link a Bitcoin address</SectionLabel>\n {step === 'request' ? (\n <>\n <p style={bodyStyle}>\n Enter a Bitcoin address you control. We generate a challenge and your\n wallet signs it (BIP-322).\n </p>\n <input\n type=\"text\"\n value={addr}\n onChange={(e) => setAddr(e.target.value.trim())}\n placeholder=\"bc1q…\"\n autoCapitalize=\"none\"\n spellCheck={false}\n style={{ ...inputStyle, fontFamily: SIGNIN_FONT }}\n />\n {err && <ErrorLine>{err}</ErrorLine>}\n <FormButtons>\n <button\n type=\"button\"\n onClick={() => void getChallenge()}\n disabled={busy || addr.length < 14}\n style={primaryBtnStyle(busy || addr.length < 14)}\n >\n {busy ? 'requesting…' : 'get challenge'}\n </button>\n <button type=\"button\" onClick={onCancel} style={ghostBtnStyle(busy)}>\n cancel\n </button>\n </FormButtons>\n </>\n ) : pendingSig ? (\n <TransferConfirm\n what=\"bitcoin address\"\n busy={busy}\n onConfirm={() => void postLink(pendingSig, true)}\n onCancel={onCancel}\n err={err}\n />\n ) : (\n <>\n <p style={bodyStyle}>\n Sign the challenge with the wallet that controls{' '}\n <span style={{ fontFamily: SIGNIN_FONT, wordBreak: 'break-all' }}>{addr}</span>.\n </p>\n <LazyWalletButton\n address={addr}\n message={challenge}\n showManual\n layout=\"list\"\n heading={null}\n onSigned={(sig: string) => void postLink(sig, false)}\n onError={(e: { message?: string }) =>\n setErr(e?.message ?? 'wallet rejected')\n }\n />\n {err && <ErrorLine>{err}</ErrorLine>}\n <FormButtons>\n <button\n type=\"button\"\n onClick={() => {\n setStep('request');\n setErr(null);\n }}\n disabled={busy}\n style={ghostBtnStyle(busy)}\n >\n back\n </button>\n </FormButtons>\n </>\n )}\n </div>\n );\n}\n\n/* --- transfer-under-dual-proof confirm --- */\n\nfunction TransferConfirm({\n what,\n busy,\n onConfirm,\n onCancel,\n err,\n}: {\n what: string;\n busy: boolean;\n onConfirm: () => void;\n onCancel: () => void;\n err: string | null;\n}): React.ReactElement {\n return (\n <>\n <p style={bodyStyle}>\n This {what} is already linked to another OrangeCheck account. You've just\n proven control of it — you can transfer the link to this account under dual proof\n of control. The other account loses this sign-in path; its history stays\n verifiable. Continue?\n </p>\n {err && <ErrorLine>{err}</ErrorLine>}\n <FormButtons>\n <button\n type=\"button\"\n onClick={onConfirm}\n disabled={busy}\n style={dangerBtnStyle(busy)}\n >\n {busy ? 'transferring…' : 'transfer link here'}\n </button>\n <button type=\"button\" onClick={onCancel} disabled={busy} style={ghostBtnStyle(busy)}>\n cancel\n </button>\n </FormButtons>\n </>\n );\n}\n\n/* --- lazy wallet button · wallet-adapter loads on demand, not with the page --- */\n\ninterface WalletButtonProps {\n address: string;\n message: string;\n showManual?: boolean;\n layout?: string;\n heading?: React.ReactNode;\n onSigned: (sig: string) => void;\n onError: (err: { message?: string }, walletId?: string) => void;\n}\n\nexport function LazyWalletButton(props: WalletButtonProps): React.ReactElement {\n const [Comp, setComp] = React.useState<React.ComponentType<WalletButtonProps> | null>(null);\n const [failed, setFailed] = React.useState(false);\n\n React.useEffect(() => {\n let alive = true;\n // A bundler resolves this specifier at build time, so wallet-adapter\n // is a real dependency; only the chunk is deferred.\n import('@orangecheck/wallet-adapter/react')\n .then((m) => {\n if (alive) {\n setComp(\n () => (m as { OcWalletButton: React.ComponentType<WalletButtonProps> })\n .OcWalletButton\n );\n }\n })\n .catch(() => {\n if (alive) setFailed(true);\n });\n return () => {\n alive = false;\n };\n }, []);\n\n if (failed) {\n return (\n <ErrorLine>\n could not load wallet support · refresh to try again\n </ErrorLine>\n );\n }\n if (!Comp) return <span style={hintStyle}>loading wallet options…</span>;\n return <Comp {...props} />;\n}\n\n/* --- helpers --- */\n\nfunction mask(value: string, kind: 'email' | 'btc'): string {\n if (kind === 'email') {\n const [user, host] = value.split('@');\n if (!user || !host) return '••••';\n const m =\n user.length <= 2\n ? '•'.repeat(user.length)\n : user[0] + '•'.repeat(user.length - 2) + user.slice(-1);\n return `${m}@${host}`;\n }\n if (value.length <= 12) return '•'.repeat(value.length);\n return `${value.slice(0, 6)}…${value.slice(-4)}`;\n}\n\n/* --- presentational atoms · also used by bond.tsx; not re-exported from index --- */\n\nexport function SectionLabel({\n children,\n tone = 'primary',\n}: {\n children: React.ReactNode;\n tone?: 'primary' | 'success' | 'warn';\n}): React.ReactElement {\n const color = tone === 'success' ? V.success : tone === 'warn' ? V.warn : V.primary;\n return (\n <div\n style={{\n color,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(10),\n letterSpacing: signinTracking('0.18em'),\n textTransform: SIGNIN_CASE,\n marginBottom: 8,\n }}\n >\n {children}\n </div>\n );\n}\n\nexport function ErrorLine({ children }: { children: React.ReactNode }): React.ReactElement {\n return (\n <p\n role=\"alert\"\n style={{\n margin: '8px 0 0',\n color: V.destructive,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(11),\n }}\n >\n {children}\n </p>\n );\n}\n\nexport function FormButtons({ children }: { children: React.ReactNode }): React.ReactElement {\n return (\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 12 }}>{children}</div>\n );\n}\n\n/* --- style tokens (CSS-variable-themed, same approach as OcSignIn) --- */\n\n\nexport const V = {\n primary: 'var(--primary, #f97316)',\n foreground: 'var(--foreground, #fafafa)',\n muted: 'var(--muted-foreground, #a1a1aa)',\n background: 'var(--background, #0a0a0a)',\n card: 'var(--card, #111113)',\n border: 'var(--border, #27272a)',\n destructive: 'var(--destructive, #ef4444)',\n success: 'var(--success, #22c55e)',\n warn: 'var(--warning, #eab308)',\n} as const;\n\nexport function panelStyle(tone: 'plain' | 'accent' | 'success' | 'warn' | 'default' = 'default'): React.CSSProperties {\n const base: React.CSSProperties = {\n background: V.card,\n border: `1px solid ${V.border}`,\n padding: '1.25rem',\n };\n if (tone === 'plain') return { ...base, padding: 0 };\n if (tone === 'accent') return { ...base, borderColor: V.primary, background: 'var(--card, #111113)' };\n if (tone === 'success') return { ...base, borderColor: V.success };\n if (tone === 'warn') return { ...base, borderColor: V.warn };\n return base;\n}\n\nexport const bodyStyle: React.CSSProperties = {\n color: V.muted,\n fontSize: 13,\n lineHeight: 1.55,\n margin: '0 0 12px',\n};\n\nexport const hintStyle: React.CSSProperties = {\n color: V.muted,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(10.5),\n opacity: 0.8,\n};\n\nconst rowStyle: React.CSSProperties = {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 12,\n padding: '1rem',\n};\n\nconst rowLabelStyle: React.CSSProperties = {\n color: V.foreground,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(10.5),\n letterSpacing: signinTracking('0.12em'),\n textTransform: SIGNIN_CASE,\n opacity: 0.8,\n};\n\nexport const inputStyle: React.CSSProperties = {\n width: '100%',\n padding: '0.55rem 0.75rem',\n background: V.background,\n color: V.foreground,\n border: `1px solid ${V.border}`,\n borderRadius: signinRadius(6),\n fontFamily: SIGNIN_FONT,\n // 16px — never lower · suppresses iOS Safari focus auto-zoom.\n fontSize: 16,\n outline: 'none',\n boxSizing: 'border-box',\n};\n\nfunction btnBase(disabled: boolean): React.CSSProperties {\n return {\n padding: '0.5rem 0.875rem',\n borderRadius: SIGNIN_BUTTON_RADIUS,\n fontFamily: SIGNIN_FONT,\n fontSize: signinSize(10.5),\n letterSpacing: signinTracking('0.12em'),\n textTransform: SIGNIN_CASE,\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.5 : 1,\n };\n}\n\nexport function primaryBtnStyle(disabled: boolean): React.CSSProperties {\n return {\n ...btnBase(disabled),\n background: V.primary,\n color: 'var(--primary-foreground, #0a0a0a)',\n border: `1px solid ${V.primary}`,\n fontWeight: 600,\n };\n}\n\nexport function ghostBtnStyle(disabled: boolean): React.CSSProperties {\n return {\n ...btnBase(disabled),\n background: V.background,\n color: V.foreground,\n border: `1px solid ${V.border}`,\n };\n}\n\nexport function dangerBtnStyle(disabled: boolean): React.CSSProperties {\n return {\n ...btnBase(disabled),\n background: 'transparent',\n color: V.destructive,\n border: `1px solid ${V.destructive}`,\n };\n}\n\nconst primaryChipStyle: React.CSSProperties = {\n ...btnBase(false),\n background: 'transparent',\n color: V.primary,\n border: `1px solid ${V.primary}`,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,YAAY,WAAW;AAyIP,SA4HR,UA5HQ,KACA,YADA;AAxHhB,IAAM,sBAAsB;AAsC5B,eAAsB,wBAAwB,MAEd;AAC5B,QAAM,aAAa,MAAM,cAAc;AAMvC,QAAM,IAAI,MAAM,MAAM,GAAG,UAAU,wBAAwB;AAAA,IACvD,aAAa;AAAA,IACb,SAAS,EAAE,GAAG,iBAAiB,EAAE;AAAA,EACrC,CAAC;AACD,MAAI,EAAE,WAAW,IAAK,QAAO,CAAC;AAC9B,MAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,iCAAiC,EAAE,MAAM,EAAE;AACtE,QAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,SAAO,KAAK,UAAU,CAAC;AAC3B;AAoBO,SAAS,mBAAmB;AAAA,EAC/B,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,kBAAkB;AACtB,GAAgD;AAC5C,QAAM,CAAC,MAAM,OAAO,IAAU,eAAgC,IAAI;AAClE,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAwB,IAAI;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAU,eAAS,KAAK;AAC9D,QAAM,CAAC,aAAa,cAAc,IAAU,eAAS,KAAK;AAE1D,QAAM,UAAgB,kBAAY,YAAY;AAC1C,aAAS,IAAI;AACb,QAAI;AACA,YAAM,IAAI,MAAM,MAAM,GAAG,UAAU,wBAAwB;AAAA,QACvD,aAAa;AAAA,QACb,SAAS,EAAE,GAAG,iBAAiB,EAAE;AAAA,MACrC,CAAC;AACD,UAAI,CAAC,EAAE,IAAI;AACP,YAAI,EAAE,WAAW,KAAK;AAClB,mBAAS,eAAe;AACxB;AAAA,QACJ;AACA,cAAM,IAAI,MAAM,QAAQ,EAAE,MAAM,EAAE;AAAA,MACtC;AACA,cAAS,MAAM,EAAE,KAAK,CAAoB;AAAA,IAC9C,SAAS,GAAG;AACR,eAAS,aAAa,QAAQ,EAAE,UAAU,aAAa;AAAA,IAC3D;AAAA,EACJ,GAAG,CAAC,UAAU,CAAC;AAEf,EAAM,gBAAU,MAAM;AAClB,SAAK,QAAQ;AAAA,EACjB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,cAAoB,kBAAY,MAAM;AACxC,qBAAiB,KAAK;AACtB,mBAAe,KAAK;AACpB,SAAK,QAAQ;AACb,eAAW;AAAA,EACf,GAAG,CAAC,SAAS,QAAQ,CAAC;AAEtB,MAAI,OAAO;AACP,WACI,qBAAC,SAAI,WAAsB,cAAW,IAAG,OAAO,WAAW,MAAM,GAAG,MAAK,SACrE;AAAA,0BAAC,gBAAa,MAAK,QAAO,gDAA4B;AAAA,MACtD,qBAAC,OAAE,OAAO,WAAW;AAAA;AAAA,QACmB;AAAA,QAAM;AAAA,SAE9C;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,KAAK,QAAQ,GAAG,OAAO,cAAc,KAAK,GAAG,mBAElF;AAAA,OACJ;AAAA,EAER;AAEA,MAAI,CAAC,MAAM;AACP,WACI,oBAAC,SAAI,WAAsB,cAAW,IAAG,OAAO,WAAW,GACvD,8BAAC,UAAK,OAAO,WAAW,2BAAQ,GACpC;AAAA,EAER;AAEA,QAAM,SAAS,KAAK,UAAU,CAAC;AAC/B,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,QAAM,UAAU,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK;AACrD,QAAM,WAAW,UAAU,KAAK,CAAC,MAAM,EAAE,KAAK;AAC9C,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,KAAK;AAC1C,QAAM,aAAa,KAAK,eAAe,CAAC;AAExC,SACI,qBAAC,SAAI,WAAsB,cAAW,IAAG,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,GACvE;AAAA,yBAAC,SAAI,OAAO,WAAW,GACnB;AAAA,0BAAC,gBAAa,+BAAc;AAAA,MAC5B,oBAAC,OAAE,OAAO,WAAW,iNAIrB;AAAA,OACJ;AAAA,IAEC,WAAW,SAAS,KAAK,oBAAC,sBAAmB,SAAS,YAAY;AAAA,IAEnE,qBAAC,SAAI,OAAO,WAAW,OAAO,GAAG,mBAAgB,IAC7C;AAAA;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,OAAM;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA,UAAU;AAAA,UACV,QAAQ,WAAW,OAAO;AAAA,UAC1B,OAAO,MAAM;AACT,6BAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,2BAAe,KAAK;AAAA,UACxB;AAAA;AAAA,MACJ;AAAA,MACA,oBAAC,SAAI,OAAO,EAAE,WAAW,aAAa,EAAE,MAAM,GAAG,GAAG;AAAA,MACpD;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,OAAM;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA,UAAU;AAAA,UACV,QAAQ,SAAS,OAAO;AAAA,UACxB,OAAO,MAAM;AACT,2BAAe,CAAC,MAAM,CAAC,CAAC;AACxB,6BAAiB,KAAK;AAAA,UAC1B;AAAA;AAAA,MACJ;AAAA,OACJ;AAAA,IAEC,iBACG;AAAA,MAAC;AAAA;AAAA,QACG;AAAA,QACA,QAAQ;AAAA,QACR,UAAU,MAAM,iBAAiB,KAAK;AAAA;AAAA,IAC1C;AAAA,IAEH,eACG;AAAA,MAAC;AAAA;AAAA,QACG;AAAA,QACA,OAAO,KAAK,UAAU;AAAA,QACtB,QAAQ;AAAA,QACR,UAAU,MAAM,eAAe,KAAK;AAAA;AAAA,IACxC;AAAA,IAGH,UAAU,mBAAmB,oBAAC,mBAAgB;AAAA,KACnD;AAER;AAIA,SAAS,cAAc;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAQuB;AACnB,MAAI,KAAK,WAAW,GAAG;AACnB,WACI,qBAAC,SAAI,OAAO,UACR;AAAA,2BAAC,SACG;AAAA,6BAAC,SAAI,OAAO,eAAe;AAAA;AAAA,UAAG;AAAA,WAAM;AAAA,QACpC,oBAAC,SAAI,OAAO,EAAE,GAAG,WAAW,WAAW,EAAE,GAAG,yBAAW;AAAA,SAC3D;AAAA,MACC,UACG,qBAAC,YAAO,MAAK,UAAS,SAAS,OAAO,OAAO,kBAAkB;AAAA;AAAA,QACxD;AAAA,SACP;AAAA,OAER;AAAA,EAER;AACA,SACI,gCACK,eAAK,IAAI,CAAC,MACP;AAAA,IAAC;AAAA;AAAA,MAEG,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IAJK,EAAE;AAAA,EAKX,CACH,GACL;AAER;AAIA,SAAS,YAAY;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAKuB;AACnB,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,KAAK;AAC9C,QAAM,CAAC,YAAY,aAAa,IAAU,eAAS,KAAK;AACxD,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,KAAK;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAU,eAAwB,IAAI;AAExD,QAAM,SAAS,IAAI,QAAQ,KAAK,IAAI,OAAO,IAAI,IAAI;AACnD,QAAM,UAAU,SAAS,IAAI,QAAQ,IAAI,QAAQ;AAEjD,iBAAe,SAAwB;AACnC,QAAI,IAAI,WAAY;AACpB,YAAQ,IAAI;AACZ,WAAO,IAAI;AACX,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ,GAAG,UAAU,2BAA2B,mBAAmB,IAAI,EAAE,CAAC;AAAA,QAClE,EAAE,QAAQ,UAAU,aAAa,UAAU;AAAA,MAC/C;AACA,UAAI,CAAC,EAAE,IAAI;AACP,cAAM,IAAK,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC1C,cAAM,IAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE;AAAA,MAClD;AACA,eAAS;AAAA,IACb,SAAS,GAAG;AACR,aAAO,aAAa,QAAQ,EAAE,UAAU,eAAe;AACvD,cAAQ,KAAK;AACb,oBAAc,KAAK;AAAA,IACvB;AAAA,EACJ;AAEA,SACI,qBAAC,SAAI,OAAO,UAAU,kBAAgB,MAClC;AAAA,yBAAC,SAAI,OAAO,EAAE,UAAU,GAAG,MAAM,EAAE,GAC/B;AAAA,2BAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,UAAU,QAAQ,KAAK,WAAW,GAC7D;AAAA,6BAAC,UAAK,OAAO,eAAe;AAAA;AAAA,UACrB,SAAS,UAAU,UAAU;AAAA,WACpC;AAAA,QACC,IAAI,cACD,oBAAC,UAAK,OAAO,EAAE,GAAG,eAAe,OAAO,EAAE,QAAQ,GAAG,sCAErD;AAAA,SAER;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACG,OAAO;AAAA,YACH,WAAW;AAAA,YACX,OAAO,EAAE;AAAA,YACT,YAAY;AAAA,YACZ,UAAU,WAAW,EAAE;AAAA,YACvB,WAAW;AAAA,UACf;AAAA,UAEC;AAAA;AAAA,MACL;AAAA,MACC,IAAI,eACD,qBAAC,SAAI,OAAO,EAAE,GAAG,WAAW,WAAW,EAAE,GAAG;AAAA;AAAA,QAC5B,IAAI,KAAK,IAAI,WAAW,EAAE,YAAY;AAAA,SACtD;AAAA,MAEH,OAAO,oBAAC,aAAW,eAAI;AAAA,OAC5B;AAAA,IACA,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,GAAG,KAAK,GAAG,UAAU,OAAO,GAClE;AAAA,UAAI,SACD;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,SAAS,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;AAAA,UACjC,OAAO,cAAc,KAAK;AAAA,UAEzB,kBAAQ,SAAS;AAAA;AAAA,MACtB;AAAA,MAEH,CAAC,IAAI,cAAc,CAAC,cACjB;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,IAAI;AAAA,UACjC,OAAO,eAAe,KAAK;AAAA,UAC9B;AAAA;AAAA,MAED;AAAA,MAEH,CAAC,IAAI,cAAc,cAChB,iCACI;AAAA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,KAAK,OAAO;AAAA,YAC3B,UAAU;AAAA,YACV,OAAO,eAAe,IAAI;AAAA,YAEzB,iBAAO,oBAAe;AAAA;AAAA,QAC3B;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,KAAK;AAAA,YAClC,UAAU;AAAA,YACV,OAAO,cAAc,IAAI;AAAA,YAC5B;AAAA;AAAA,QAED;AAAA,SACJ;AAAA,OAER;AAAA,KACJ;AAER;AAIA,SAAS,mBAAmB,EAAE,QAAQ,GAAuD;AACzF,SACI,qBAAC,SAAI,OAAO,WAAW,SAAS,GAAG,2BAAwB,IACvD;AAAA,yBAAC,gBAAa,MAAK,WAAU;AAAA;AAAA,MACE,QAAQ;AAAA,OACvC;AAAA,IACA,qBAAC,OAAE,OAAO,WACL;AAAA,cAAQ,WAAW,IAAI,0BAA0B;AAAA,MAAuB;AAAA,MAEpB;AAAA,MACpD,QAAQ,WAAW,IAAI,OAAO;AAAA,MAAO;AAAA,OAC1C;AAAA,IACA,oBAAC,QAAG,OAAO,EAAE,WAAW,QAAQ,QAAQ,YAAY,SAAS,EAAE,GAC1D,kBAAQ,IAAI,CAAC,MACV;AAAA,MAAC;AAAA;AAAA,QAEG,OAAO;AAAA,UACH,SAAS;AAAA,UACT,UAAU;AAAA,UACV,KAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAW,aAAa,EAAE,MAAM;AAAA,UAChC,YAAY;AAAA,UACZ,UAAU,WAAW,EAAE;AAAA,QAC3B;AAAA,QAEA;AAAA,8BAAC,UAAK,OAAO,EAAE,OAAO,EAAE,YAAY,WAAW,YAAY,GACtD,YAAE,QACP;AAAA,UACA,qBAAC,UAAK,OAAO,EAAE,GAAG,WAAW,YAAY,OAAO,GAAG;AAAA;AAAA,YACtC,IAAI,KAAK,EAAE,UAAU,EAAE,mBAAmB;AAAA,aACvD;AAAA;AAAA;AAAA,MAhBK,EAAE;AAAA,IAiBX,CACH,GACL;AAAA,KACJ;AAER;AAaA,SAAS,kBAAsC;AAC3C,SACI,qBAAC,SAAI,OAAO,WAAW,GAAG,qBAAkB,IACxC;AAAA,wBAAC,gBAAa,uCAAsB;AAAA,IACpC,oBAAC,OAAE,OAAO,WAAW,gTAKrB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACG,MAAK;AAAA,QACL,QAAO;AAAA,QACP,KAAI;AAAA,QACJ,OAAO,EAAE,GAAG,kBAAkB,gBAAgB,QAAQ,SAAS,eAAe;AAAA,QAC9E,0BAAuB;AAAA,QAC1B;AAAA;AAAA,IAED;AAAA,KACJ;AAER;AAKA,IAAM,mBAAmB;AAclB,SAAS,eAAe;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAOuB;AACnB,QAAM,OAAO,WAAW,QAAQ,sBAAsB;AACtD,SACI,qBAAC,SAAI,sBAAoB,QACrB;AAAA,yBAAC,SAAI,OAAO,EAAE,GAAG,WAAW,SAAS,GAAG,cAAc,GAAG,GACrD;AAAA,0BAAC,gBAAa,MAAK,WAAU,4BAAW;AAAA,MACxC,qBAAC,OAAE,OAAO,EAAE,GAAG,WAAW,QAAQ,EAAE,GAAG;AAAA;AAAA,QACX;AAAA,QAAK;AAAA,SAEjC;AAAA,OACJ;AAAA,IACC,WAAW,QACR;AAAA,MAAC;AAAA;AAAA,QACG;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,UAAU;AAAA;AAAA,IACd,IAEA,oBAAC,iBAAc,YAAwB,QAAQ,YAAY,UAAU,YAAY;AAAA,IAErF;AAAA,MAAC;AAAA;AAAA,QACG,2BAAwB;AAAA,QACxB,OAAO,EAAE,WAAW,IAAI,YAAY,IAAI,WAAW,aAAa,EAAE,MAAM,GAAG;AAAA,QAE3E;AAAA,8BAAC,YAAO,MAAK,UAAS,SAAS,YAAY,OAAO,cAAc,KAAK,GAAG,4CAExE;AAAA,UACA,qBAAC,OAAE,OAAO,EAAE,GAAG,WAAW,WAAW,EAAE,GAAG;AAAA;AAAA,YAEZ;AAAA,YAAK;AAAA,YAAuC;AAAA,YACtE;AAAA,cAAC;AAAA;AAAA,gBACG,MAAM;AAAA,gBACN,QAAO;AAAA,gBACP,KAAI;AAAA,gBACJ,OAAO,EAAE,OAAO,UAAU;AAAA,gBAC1B,8BAA2B;AAAA,gBAC9B;AAAA;AAAA,YAED;AAAA,YAAI;AAAA,aAER;AAAA;AAAA;AAAA,IACJ;AAAA,KACJ;AAER;AAIA,SAAS,cAAc;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACJ,GAIuB;AACnB,QAAM,CAAC,MAAM,OAAO,IAAU,eAA2B,OAAO;AAChE,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,EAAE;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,EAAE;AACzC,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,EAAE;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,KAAK;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAU,eAAwB,IAAI;AAGxD,QAAM,CAAC,UAAU,WAAW,IAAU,eAAS,KAAK;AAEpD,iBAAe,QAAuB;AAClC,YAAQ,IAAI;AACZ,WAAO,IAAI;AACX,QAAI;AACA,YAAM,IAAI,MAAM,MAAM,GAAG,UAAU,6BAA6B;AAAA,QAC5D,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,MAAM,KAAK,EAAE,YAAY,EAAE,CAAC;AAAA,MAC9D,CAAC;AACD,YAAM,IAAK,MAAM,EAAE,KAAK;AACxB,UAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAO,OAAM,IAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE;AACrE,eAAS,EAAE,KAAK;AAChB,cAAQ,MAAM;AAAA,IAClB,SAAS,GAAG;AACR,aAAO,aAAa,QAAQ,EAAE,UAAU,qBAAqB;AAAA,IACjE,UAAE;AACE,cAAQ,KAAK;AAAA,IACjB;AAAA,EACJ;AAEA,iBAAe,OAAO,iBAAyC;AAC3D,YAAQ,IAAI;AACZ,WAAO,IAAI;AACX,QAAI;AACA,YAAM,IAAI,MAAM,MAAM,GAAG,UAAU,wBAAwB;AAAA,QACvD,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACjB,OAAO,MAAM,KAAK,EAAE,YAAY;AAAA,UAChC,MAAM,KAAK,KAAK;AAAA,UAChB;AAAA,UACA,GAAI,kBAAkB,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAAA,QACxD,CAAC;AAAA,MACL,CAAC;AACD,YAAM,IAAK,MAAM,EAAE,KAAK;AACxB,UAAI,EAAE,WAAW,OAAO,EAAE,WAAW,0BAA0B;AAC3D,oBAAY,IAAI;AAChB,gBAAQ,KAAK;AACb;AAAA,MACJ;AACA,UAAI,mBAAmB,GAAG,EAAE,SAAS,gBAAgB,CAAC,EAAG;AACzD,UAAI,sBAAsB,CAAC,GAAG;AAC1B,eAAO,6BAA6B;AACpC,gBAAQ,KAAK;AACb;AAAA,MACJ;AACA,UAAI,CAAC,EAAE,MAAM,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE;AAClE,aAAO;AAAA,IACX,SAAS,GAAG;AACR,aAAO,aAAa,QAAQ,EAAE,UAAU,aAAa;AACrD,cAAQ,KAAK;AAAA,IACjB;AAAA,EACJ;AAEA,SACI,qBAAC,SAAI,OAAO,WAAW,QAAQ,GAAG,mBAAgB,SAC9C;AAAA,wBAAC,gBAAa,gCAAe;AAAA,IAC5B,SAAS,UACN,iCACI;AAAA;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,cAAa;AAAA,UACb,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC,aAAY;AAAA,UACZ,OAAO;AAAA;AAAA,MACX;AAAA,MACC,OAAO,oBAAC,aAAW,eAAI;AAAA,MACxB,qBAAC,eACG;AAAA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,KAAK,MAAM;AAAA,YAC1B,UAAU,QAAQ,MAAM,KAAK,EAAE,SAAS;AAAA,YACxC,OAAO,gBAAgB,QAAQ,MAAM,KAAK,EAAE,SAAS,CAAC;AAAA,YAErD,iBAAO,kBAAa;AAAA;AAAA,QACzB;AAAA,QACA,oBAAC,YAAO,MAAK,UAAS,SAAS,UAAU,OAAO,cAAc,IAAI,GAAG,oBAErE;AAAA,SACJ;AAAA,OACJ,IACA,WACA;AAAA,MAAC;AAAA;AAAA,QACG,MAAK;AAAA,QACL;AAAA,QACA,WAAW,MAAM,KAAK,OAAO,IAAI;AAAA,QACjC;AAAA,QACA;AAAA;AAAA,IACJ,IAEA,iCACI;AAAA,2BAAC,OAAE,OAAO,WAAW;AAAA;AAAA,QAAgC;AAAA,QAAM;AAAA,SAAC;AAAA,MAC5D;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,WAAU;AAAA,UACV,cAAa;AAAA,UACb,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,MAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,UACtE,aAAY;AAAA,UACZ,OAAO,EAAE,GAAG,YAAY,eAAe,QAAQ;AAAA;AAAA,MACnD;AAAA,MACC,OAAO,oBAAC,aAAW,eAAI;AAAA,MACxB,qBAAC,eACG;AAAA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,KAAK,OAAO,KAAK;AAAA,YAChC,UAAU,QAAQ,KAAK,WAAW;AAAA,YAClC,OAAO,gBAAgB,QAAQ,KAAK,WAAW,CAAC;AAAA,YAE/C,iBAAO,kBAAa;AAAA;AAAA,QACzB;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,QAAQ,OAAO;AAAA,YAC9B,UAAU;AAAA,YACV,OAAO,cAAc,IAAI;AAAA,YAC5B;AAAA;AAAA,QAED;AAAA,SACJ;AAAA,OACJ;AAAA,KAER;AAER;AAIA,SAAS,YAAY;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAKuB;AACnB,QAAM,CAAC,MAAM,OAAO,IAAU,eAA6B,SAAS;AACpE,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,EAAE;AACzC,QAAM,CAAC,WAAW,YAAY,IAAU,eAAS,EAAE;AACnD,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,KAAK;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAU,eAAwB,IAAI;AAGxD,QAAM,CAAC,YAAY,aAAa,IAAU,eAAwB,IAAI;AAEtE,iBAAe,eAA8B;AACzC,QAAI,CAAC,OAAO;AACR,aAAO,+CAA4C;AACnD;AAAA,IACJ;AACA,YAAQ,IAAI;AACZ,WAAO,IAAI;AACX,QAAI;AAKA,YAAM,IAAI,MAAM;AAAA,QACZ,GAAG,UAAU,uBAAuB,mBAAmB,IAAI,CAAC,0BAC9B,mBAAmB,KAAK,CAAC;AAAA,MAC3D;AACA,YAAM,IAAK,MAAM,EAAE,KAAK;AACxB,UAAI,CAAC,EAAE,MAAM,CAAC,EAAE,QAAS,OAAM,IAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE;AACvE,mBAAa,EAAE,OAAO;AACtB,cAAQ,MAAM;AAAA,IAClB,SAAS,GAAG;AACR,aAAO,aAAa,QAAQ,EAAE,UAAU,kBAAkB;AAAA,IAC9D,UAAE;AACE,cAAQ,KAAK;AAAA,IACjB;AAAA,EACJ;AAEA,iBAAe,SAAS,KAAa,iBAAyC;AAC1E,YAAQ,IAAI;AACZ,WAAO,IAAI;AACX,QAAI;AACA,YAAM,IAAI,MAAM,MAAM,GAAG,UAAU,sBAAsB;AAAA,QACrD,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACjB,SAAS;AAAA,UACT,WAAW;AAAA,UACX,GAAI,kBAAkB,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAAA,QACxD,CAAC;AAAA,MACL,CAAC;AACD,YAAM,IAAK,MAAM,EAAE,KAAK;AACxB,UAAI,EAAE,WAAW,OAAO,EAAE,WAAW,4BAA4B;AAC7D,sBAAc,GAAG;AACjB,gBAAQ,KAAK;AACb;AAAA,MACJ;AACA,UAAI,mBAAmB,GAAG,EAAE,SAAS,yBAAyB,CAAC,EAAG;AAClE,UAAI,sBAAsB,CAAC,GAAG;AAC1B,eAAO,6BAA6B;AACpC,gBAAQ,KAAK;AACb;AAAA,MACJ;AACA,UAAI,CAAC,EAAE,MAAM,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE;AAClE,aAAO;AAAA,IACX,SAAS,GAAG;AACR,aAAO,aAAa,QAAQ,EAAE,UAAU,aAAa;AACrD,cAAQ,KAAK;AAAA,IACjB;AAAA,EACJ;AAEA,SACI,qBAAC,SAAI,OAAO,WAAW,QAAQ,GAAG,mBAAgB,OAC9C;AAAA,wBAAC,gBAAa,yCAAwB;AAAA,IACrC,SAAS,YACN,iCACI;AAAA,0BAAC,OAAE,OAAO,WAAW,8GAGrB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,MAAM,KAAK,CAAC;AAAA,UAC9C,aAAY;AAAA,UACZ,gBAAe;AAAA,UACf,YAAY;AAAA,UACZ,OAAO,EAAE,GAAG,YAAY,YAAY,YAAY;AAAA;AAAA,MACpD;AAAA,MACC,OAAO,oBAAC,aAAW,eAAI;AAAA,MACxB,qBAAC,eACG;AAAA;AAAA,UAAC;AAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,MAAM,KAAK,aAAa;AAAA,YACjC,UAAU,QAAQ,KAAK,SAAS;AAAA,YAChC,OAAO,gBAAgB,QAAQ,KAAK,SAAS,EAAE;AAAA,YAE9C,iBAAO,qBAAgB;AAAA;AAAA,QAC5B;AAAA,QACA,oBAAC,YAAO,MAAK,UAAS,SAAS,UAAU,OAAO,cAAc,IAAI,GAAG,oBAErE;AAAA,SACJ;AAAA,OACJ,IACA,aACA;AAAA,MAAC;AAAA;AAAA,QACG,MAAK;AAAA,QACL;AAAA,QACA,WAAW,MAAM,KAAK,SAAS,YAAY,IAAI;AAAA,QAC/C;AAAA,QACA;AAAA;AAAA,IACJ,IAEA,iCACI;AAAA,2BAAC,OAAE,OAAO,WAAW;AAAA;AAAA,QACgC;AAAA,QACjD,oBAAC,UAAK,OAAO,EAAE,YAAY,aAAa,WAAW,YAAY,GAAI,gBAAK;AAAA,QAAO;AAAA,SACnF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACG,SAAS;AAAA,UACT,SAAS;AAAA,UACT,YAAU;AAAA,UACV,QAAO;AAAA,UACP,SAAS;AAAA,UACT,UAAU,CAAC,QAAgB,KAAK,SAAS,KAAK,KAAK;AAAA,UACnD,SAAS,CAAC,MACN,OAAO,GAAG,WAAW,iBAAiB;AAAA;AAAA,MAE9C;AAAA,MACC,OAAO,oBAAC,aAAW,eAAI;AAAA,MACxB,oBAAC,eACG;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,SAAS,MAAM;AACX,oBAAQ,SAAS;AACjB,mBAAO,IAAI;AAAA,UACf;AAAA,UACA,UAAU;AAAA,UACV,OAAO,cAAc,IAAI;AAAA,UAC5B;AAAA;AAAA,MAED,GACJ;AAAA,OACJ;AAAA,KAER;AAER;AAIA,SAAS,gBAAgB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAMuB;AACnB,SACI,iCACI;AAAA,yBAAC,OAAE,OAAO,WAAW;AAAA;AAAA,MACX;AAAA,MAAK;AAAA,OAIf;AAAA,IACC,OAAO,oBAAC,aAAW,eAAI;AAAA,IACxB,qBAAC,eACG;AAAA;AAAA,QAAC;AAAA;AAAA,UACG,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU;AAAA,UACV,OAAO,eAAe,IAAI;AAAA,UAEzB,iBAAO,uBAAkB;AAAA;AAAA,MAC9B;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,SAAS,UAAU,UAAU,MAAM,OAAO,cAAc,IAAI,GAAG,oBAErF;AAAA,OACJ;AAAA,KACJ;AAER;AAcO,SAAS,iBAAiB,OAA8C;AAC3E,QAAM,CAAC,MAAM,OAAO,IAAU,eAAwD,IAAI;AAC1F,QAAM,CAAC,QAAQ,SAAS,IAAU,eAAS,KAAK;AAEhD,EAAM,gBAAU,MAAM;AAClB,QAAI,QAAQ;AAGZ,WAAO,mCAAmC,EACrC,KAAK,CAAC,MAAM;AACT,UAAI,OAAO;AACP;AAAA,UACI,MAAO,EACF;AAAA,QACT;AAAA,MACJ;AAAA,IACJ,CAAC,EACA,MAAM,MAAM;AACT,UAAI,MAAO,WAAU,IAAI;AAAA,IAC7B,CAAC;AACL,WAAO,MAAM;AACT,cAAQ;AAAA,IACZ;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,MAAI,QAAQ;AACR,WACI,oBAAC,aAAU,qEAEX;AAAA,EAER;AACA,MAAI,CAAC,KAAM,QAAO,oBAAC,UAAK,OAAO,WAAW,0CAAuB;AACjE,SAAO,oBAAC,QAAM,GAAG,OAAO;AAC5B;AAIA,SAAS,KAAK,OAAe,MAA+B;AACxD,MAAI,SAAS,SAAS;AAClB,UAAM,CAAC,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG;AACpC,QAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAC3B,UAAM,IACF,KAAK,UAAU,IACT,SAAI,OAAO,KAAK,MAAM,IACtB,KAAK,CAAC,IAAI,SAAI,OAAO,KAAK,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE;AAC/D,WAAO,GAAG,CAAC,IAAI,IAAI;AAAA,EACvB;AACA,MAAI,MAAM,UAAU,GAAI,QAAO,SAAI,OAAO,MAAM,MAAM;AACtD,SAAO,GAAG,MAAM,MAAM,GAAG,CAAC,CAAC,SAAI,MAAM,MAAM,EAAE,CAAC;AAClD;AAIO,SAAS,aAAa;AAAA,EACzB;AAAA,EACA,OAAO;AACX,GAGuB;AACnB,QAAM,QAAQ,SAAS,YAAY,EAAE,UAAU,SAAS,SAAS,EAAE,OAAO,EAAE;AAC5E,SACI;AAAA,IAAC;AAAA;AAAA,MACG,OAAO;AAAA,QACH;AAAA,QACA,YAAY;AAAA,QACZ,UAAU,WAAW,EAAE;AAAA,QACvB,eAAe,eAAe,QAAQ;AAAA,QACtC,eAAe;AAAA,QACf,cAAc;AAAA,MAClB;AAAA,MAEC;AAAA;AAAA,EACL;AAER;AAEO,SAAS,UAAU,EAAE,SAAS,GAAsD;AACvF,SACI;AAAA,IAAC;AAAA;AAAA,MACG,MAAK;AAAA,MACL,OAAO;AAAA,QACH,QAAQ;AAAA,QACR,OAAO,EAAE;AAAA,QACT,YAAY;AAAA,QACZ,UAAU,WAAW,EAAE;AAAA,MAC3B;AAAA,MAEC;AAAA;AAAA,EACL;AAER;AAEO,SAAS,YAAY,EAAE,SAAS,GAAsD;AACzF,SACI,oBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,UAAU,QAAQ,KAAK,GAAG,WAAW,GAAG,GAAI,UAAS;AAE5F;AAKO,IAAM,IAAI;AAAA,EACb,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,SAAS;AAAA,EACT,MAAM;AACV;AAEO,SAAS,WAAW,OAA4D,WAAgC;AACnH,QAAM,OAA4B;AAAA,IAC9B,YAAY,EAAE;AAAA,IACd,QAAQ,aAAa,EAAE,MAAM;AAAA,IAC7B,SAAS;AAAA,EACb;AACA,MAAI,SAAS,QAAS,QAAO,EAAE,GAAG,MAAM,SAAS,EAAE;AACnD,MAAI,SAAS,SAAU,QAAO,EAAE,GAAG,MAAM,aAAa,EAAE,SAAS,YAAY,uBAAuB;AACpG,MAAI,SAAS,UAAW,QAAO,EAAE,GAAG,MAAM,aAAa,EAAE,QAAQ;AACjE,MAAI,SAAS,OAAQ,QAAO,EAAE,GAAG,MAAM,aAAa,EAAE,KAAK;AAC3D,SAAO;AACX;AAEO,IAAM,YAAiC;AAAA,EAC1C,OAAO,EAAE;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,QAAQ;AACZ;AAEO,IAAM,YAAiC;AAAA,EAC1C,OAAO,EAAE;AAAA,EACT,YAAY;AAAA,EACZ,UAAU,WAAW,IAAI;AAAA,EACzB,SAAS;AACb;AAEA,IAAM,WAAgC;AAAA,EAClC,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,KAAK;AAAA,EACL,SAAS;AACb;AAEA,IAAM,gBAAqC;AAAA,EACvC,OAAO,EAAE;AAAA,EACT,YAAY;AAAA,EACZ,UAAU,WAAW,IAAI;AAAA,EACzB,eAAe,eAAe,QAAQ;AAAA,EACtC,eAAe;AAAA,EACf,SAAS;AACb;AAEO,IAAM,aAAkC;AAAA,EAC3C,OAAO;AAAA,EACP,SAAS;AAAA,EACT,YAAY,EAAE;AAAA,EACd,OAAO,EAAE;AAAA,EACT,QAAQ,aAAa,EAAE,MAAM;AAAA,EAC7B,cAAc,aAAa,CAAC;AAAA,EAC5B,YAAY;AAAA;AAAA,EAEZ,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AACf;AAEA,SAAS,QAAQ,UAAwC;AACrD,SAAO;AAAA,IACH,SAAS;AAAA,IACT,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,UAAU,WAAW,IAAI;AAAA,IACzB,eAAe,eAAe,QAAQ;AAAA,IACtC,eAAe;AAAA,IACf,QAAQ,WAAW,gBAAgB;AAAA,IACnC,SAAS,WAAW,MAAM;AAAA,EAC9B;AACJ;AAEO,SAAS,gBAAgB,UAAwC;AACpE,SAAO;AAAA,IACH,GAAG,QAAQ,QAAQ;AAAA,IACnB,YAAY,EAAE;AAAA,IACd,OAAO;AAAA,IACP,QAAQ,aAAa,EAAE,OAAO;AAAA,IAC9B,YAAY;AAAA,EAChB;AACJ;AAEO,SAAS,cAAc,UAAwC;AAClE,SAAO;AAAA,IACH,GAAG,QAAQ,QAAQ;AAAA,IACnB,YAAY,EAAE;AAAA,IACd,OAAO,EAAE;AAAA,IACT,QAAQ,aAAa,EAAE,MAAM;AAAA,EACjC;AACJ;AAEO,SAAS,eAAe,UAAwC;AACnE,SAAO;AAAA,IACH,GAAG,QAAQ,QAAQ;AAAA,IACnB,YAAY;AAAA,IACZ,OAAO,EAAE;AAAA,IACT,QAAQ,aAAa,EAAE,WAAW;AAAA,EACtC;AACJ;AAEA,IAAM,mBAAwC;AAAA,EAC1C,GAAG,QAAQ,KAAK;AAAA,EAChB,YAAY;AAAA,EACZ,OAAO,EAAE;AAAA,EACT,QAAQ,aAAa,EAAE,OAAO;AAClC;","names":[]}
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorLine,
|
|
3
|
+
FormButtons,
|
|
4
|
+
LazyWalletButton,
|
|
5
|
+
SectionLabel,
|
|
6
|
+
V,
|
|
7
|
+
bodyStyle,
|
|
8
|
+
dangerBtnStyle,
|
|
9
|
+
ghostBtnStyle,
|
|
10
|
+
hintStyle,
|
|
11
|
+
inputStyle,
|
|
12
|
+
panelStyle,
|
|
13
|
+
primaryBtnStyle
|
|
14
|
+
} from "./chunk-D3HXJ64H.mjs";
|
|
15
|
+
import {
|
|
16
|
+
SIGNIN_FONT
|
|
17
|
+
} from "./chunk-H2TXOCXH.mjs";
|
|
18
|
+
import {
|
|
19
|
+
readTabSession,
|
|
20
|
+
writeTabSession
|
|
21
|
+
} from "./chunk-TJJNTITX.mjs";
|
|
22
|
+
|
|
23
|
+
// src/bond.tsx
|
|
24
|
+
import * as React from "react";
|
|
25
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
26
|
+
var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
|
|
27
|
+
var MIN_CONFIRMATIONS = 6;
|
|
28
|
+
var MIN_SATS = 1e3;
|
|
29
|
+
var n = (v) => v.toLocaleString("en-US");
|
|
30
|
+
function bondFloorsText(f) {
|
|
31
|
+
const sats = f.sats_floor > 0 ? `at least ${n(f.sats_floor)} sats` : "under 1,000 sats";
|
|
32
|
+
const days = f.days_floor > 0 ? `held ${f.days_floor} days or more` : "held under 7 days";
|
|
33
|
+
return `${sats}, ${days}`;
|
|
34
|
+
}
|
|
35
|
+
var short = (outpoint) => {
|
|
36
|
+
const [txid = "", vout = ""] = outpoint.split(":");
|
|
37
|
+
return `${txid.slice(0, 8)}\u2026${txid.slice(-4)}:${vout}`;
|
|
38
|
+
};
|
|
39
|
+
var usable = (c) => c.sats >= MIN_SATS && c.age_blocks >= MIN_CONFIRMATIONS;
|
|
40
|
+
var dustText = (count) => `${n(count)} ${count === 1 ? "coin" : "coins"} under ${n(MIN_SATS)} sats, too small to back an account`;
|
|
41
|
+
var REFUSAL = {
|
|
42
|
+
invalid_address: "that isn't a Bitcoin address we can read",
|
|
43
|
+
chain_unavailable: "couldn't read the chain just now \xB7 nothing changed, try again in a minute",
|
|
44
|
+
sig_invalid: "that signature isn't from this coin's address \xB7 sign with the wallet that holds it",
|
|
45
|
+
bond_expired: "the message expired \xB7 start again",
|
|
46
|
+
bond_not_yet_valid: "your device's clock looks wrong \xB7 check it and start again",
|
|
47
|
+
bond_subject_mismatch: "that message was for another account \xB7 start again",
|
|
48
|
+
bond_malformed: "the signed message changed on the way \xB7 start again",
|
|
49
|
+
bond_window_too_long: "that message's signing window is too long \xB7 start again",
|
|
50
|
+
coin_not_found: "that coin doesn't exist on-chain",
|
|
51
|
+
coin_address_mismatch: "that coin isn't at this address",
|
|
52
|
+
coin_unconfirmed: "that coin isn't confirmed yet",
|
|
53
|
+
coin_too_recent: "that coin needs six confirmations first, about an hour",
|
|
54
|
+
coin_spent: "that coin has been spent \xB7 pick one still in your wallet",
|
|
55
|
+
coin_too_small: `that coin is under ${n(MIN_SATS)} sats, too small to back an account`,
|
|
56
|
+
coin_backs_another_account: "that coin already backs another orangecheck account",
|
|
57
|
+
coin_backed_another_recently: "that coin backed another account until recently \xB7 it can back this one 30 days after it was released",
|
|
58
|
+
not_authenticated: "your session ended \xB7 sign in again",
|
|
59
|
+
rate_limited: "too many tries \xB7 wait a minute, then try again"
|
|
60
|
+
};
|
|
61
|
+
var bondRefusal = (reason) => reason && REFUSAL[reason] || "your backing didn't save \xB7 nothing changed, try again";
|
|
62
|
+
var smallStyle = { ...bodyStyle, fontSize: 12, margin: 0 };
|
|
63
|
+
var explainerStyle = {
|
|
64
|
+
...hintStyle,
|
|
65
|
+
fontSize: 12,
|
|
66
|
+
lineHeight: 1.55,
|
|
67
|
+
opacity: 1,
|
|
68
|
+
margin: 0
|
|
69
|
+
};
|
|
70
|
+
var fieldLabelStyle = {
|
|
71
|
+
display: "block",
|
|
72
|
+
color: V.foreground,
|
|
73
|
+
fontSize: 13,
|
|
74
|
+
fontWeight: 500,
|
|
75
|
+
margin: "0 0 4px"
|
|
76
|
+
};
|
|
77
|
+
var addressStyle = { wordBreak: "break-all", color: V.foreground };
|
|
78
|
+
function coinStyle(picked, young) {
|
|
79
|
+
return {
|
|
80
|
+
background: V.card,
|
|
81
|
+
border: `1px ${young ? "dashed" : "solid"} ${picked ? V.primary : V.border}`,
|
|
82
|
+
padding: "0.75rem",
|
|
83
|
+
display: "flex",
|
|
84
|
+
gap: 10,
|
|
85
|
+
alignItems: "flex-start",
|
|
86
|
+
cursor: young ? "not-allowed" : "pointer"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function fetchOcBond(opts) {
|
|
90
|
+
const r = await fetch(`${opts?.authOrigin ?? DEFAULT_AUTH_ORIGIN}/api/auth/bond`, {
|
|
91
|
+
credentials: "include"
|
|
92
|
+
});
|
|
93
|
+
const j = await r.json().catch(() => ({}));
|
|
94
|
+
if (!r.ok || !j.ok) throw new Error(`http_${r.status}`);
|
|
95
|
+
return j.bond ?? null;
|
|
96
|
+
}
|
|
97
|
+
function OcBond({
|
|
98
|
+
authOrigin = DEFAULT_AUTH_ORIGIN,
|
|
99
|
+
onChange,
|
|
100
|
+
className
|
|
101
|
+
}) {
|
|
102
|
+
const [bond, setBond] = React.useState(void 0);
|
|
103
|
+
const [loadErr, setLoadErr] = React.useState(false);
|
|
104
|
+
const [nonce, setNonce] = React.useState(0);
|
|
105
|
+
const [adding, setAdding] = React.useState(false);
|
|
106
|
+
const [confirming, setConfirming] = React.useState(false);
|
|
107
|
+
const [busy, setBusy] = React.useState(false);
|
|
108
|
+
const [err, setErr] = React.useState(null);
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
let alive = true;
|
|
111
|
+
fetchOcBond({ authOrigin }).then(
|
|
112
|
+
(b) => alive && setBond(b),
|
|
113
|
+
() => alive && setLoadErr(true)
|
|
114
|
+
);
|
|
115
|
+
return () => {
|
|
116
|
+
alive = false;
|
|
117
|
+
};
|
|
118
|
+
}, [authOrigin, nonce]);
|
|
119
|
+
const call = React.useCallback(
|
|
120
|
+
async (method, body) => {
|
|
121
|
+
const r = await fetch(`${authOrigin}/api/auth/bond`, {
|
|
122
|
+
method,
|
|
123
|
+
credentials: "include",
|
|
124
|
+
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
125
|
+
body: body ? JSON.stringify(body) : void 0
|
|
126
|
+
});
|
|
127
|
+
const j = await r.json().catch(() => ({}));
|
|
128
|
+
const pin = readTabSession();
|
|
129
|
+
if (pin && r.ok && typeof j.token === "string") {
|
|
130
|
+
writeTabSession({ token: j.token, didOc: pin.didOc });
|
|
131
|
+
}
|
|
132
|
+
return { ok: r.ok && j.ok === true, ...j };
|
|
133
|
+
},
|
|
134
|
+
[authOrigin]
|
|
135
|
+
);
|
|
136
|
+
async function release() {
|
|
137
|
+
setBusy(true);
|
|
138
|
+
setErr(null);
|
|
139
|
+
const j = await call("DELETE").catch(() => ({ ok: false, reason: "network" }));
|
|
140
|
+
setBusy(false);
|
|
141
|
+
if (!j.ok) return setErr(bondRefusal(j.reason));
|
|
142
|
+
setConfirming(false);
|
|
143
|
+
setBond(null);
|
|
144
|
+
onChange?.(null);
|
|
145
|
+
}
|
|
146
|
+
if (loadErr) {
|
|
147
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-oc-bond": "", style: panelStyle("warn"), role: "alert", children: [
|
|
148
|
+
/* @__PURE__ */ jsx("p", { style: bodyStyle, children: "couldn't read your backing \xB7 nothing was changed" }),
|
|
149
|
+
/* @__PURE__ */ jsx(
|
|
150
|
+
"button",
|
|
151
|
+
{
|
|
152
|
+
type: "button",
|
|
153
|
+
style: ghostBtnStyle(false),
|
|
154
|
+
onClick: () => {
|
|
155
|
+
setLoadErr(false);
|
|
156
|
+
setNonce((x) => x + 1);
|
|
157
|
+
},
|
|
158
|
+
children: "try again"
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
] });
|
|
162
|
+
}
|
|
163
|
+
if (bond === void 0) {
|
|
164
|
+
return /* @__PURE__ */ jsx("div", { className, "data-oc-bond": "", style: panelStyle(), children: /* @__PURE__ */ jsx("span", { style: hintStyle, children: "reading your backing\u2026" }) });
|
|
165
|
+
}
|
|
166
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-oc-bond": "", style: { display: "grid", gap: 16 }, children: [
|
|
167
|
+
bond && !adding ? /* @__PURE__ */ jsxs("div", { style: panelStyle("success"), children: [
|
|
168
|
+
/* @__PURE__ */ jsx(SectionLabel, { tone: "success", children: "\xA7 backed by bitcoin" }),
|
|
169
|
+
/* @__PURE__ */ jsxs("p", { style: { ...bodyStyle, color: V.foreground }, children: [
|
|
170
|
+
"apps see a coin ",
|
|
171
|
+
bondFloorsText(bond),
|
|
172
|
+
"."
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
175
|
+
"coin ",
|
|
176
|
+
short(bond.outpoint),
|
|
177
|
+
", ",
|
|
178
|
+
n(bond.sats),
|
|
179
|
+
" sats \xB7 last found unspent at block ",
|
|
180
|
+
n(bond.checked_height),
|
|
181
|
+
" \xB7 only you and orangecheck see this line"
|
|
182
|
+
] }),
|
|
183
|
+
confirming ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "stop backing", "data-oc-bond-confirm": "", children: [
|
|
184
|
+
/* @__PURE__ */ jsx("p", { style: { ...bodyStyle, color: V.foreground, margin: 0 }, children: "stop backing? apps that ask for a backed account may stop letting you in." }),
|
|
185
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
type: "button",
|
|
190
|
+
style: dangerBtnStyle(busy),
|
|
191
|
+
disabled: busy,
|
|
192
|
+
onClick: () => void release(),
|
|
193
|
+
children: "stop backing"
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
/* @__PURE__ */ jsx(
|
|
197
|
+
"button",
|
|
198
|
+
{
|
|
199
|
+
type: "button",
|
|
200
|
+
style: ghostBtnStyle(busy),
|
|
201
|
+
disabled: busy,
|
|
202
|
+
autoFocus: true,
|
|
203
|
+
onClick: () => {
|
|
204
|
+
setErr(null);
|
|
205
|
+
setConfirming(false);
|
|
206
|
+
},
|
|
207
|
+
children: "keep"
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
] })
|
|
211
|
+
] }) : /* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"button",
|
|
214
|
+
{
|
|
215
|
+
type: "button",
|
|
216
|
+
style: ghostBtnStyle(false),
|
|
217
|
+
onClick: () => {
|
|
218
|
+
setErr(null);
|
|
219
|
+
setAdding(true);
|
|
220
|
+
},
|
|
221
|
+
children: "use another coin"
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
"button",
|
|
226
|
+
{
|
|
227
|
+
type: "button",
|
|
228
|
+
style: dangerBtnStyle(false),
|
|
229
|
+
onClick: () => setConfirming(true),
|
|
230
|
+
children: "stop backing"
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
] }),
|
|
234
|
+
err ? /* @__PURE__ */ jsx(ErrorLine, { children: err }) : null
|
|
235
|
+
] }) : null,
|
|
236
|
+
!bond && !adding ? /* @__PURE__ */ jsxs("div", { style: panelStyle(), children: [
|
|
237
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 not backed" }),
|
|
238
|
+
/* @__PURE__ */ jsx("p", { style: bodyStyle, children: "your account works everywhere as it is. some apps let in only accounts backed by a coin you hold. it stays in your wallet, and apps never see which coin." }),
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
"button",
|
|
241
|
+
{
|
|
242
|
+
type: "button",
|
|
243
|
+
style: primaryBtnStyle(false),
|
|
244
|
+
onClick: () => setAdding(true),
|
|
245
|
+
children: "back your account"
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
] }) : null,
|
|
249
|
+
adding ? /* @__PURE__ */ jsx(
|
|
250
|
+
BondForm,
|
|
251
|
+
{
|
|
252
|
+
call,
|
|
253
|
+
onCancel: () => setAdding(false),
|
|
254
|
+
onDone: (b) => {
|
|
255
|
+
setBond(b);
|
|
256
|
+
setAdding(false);
|
|
257
|
+
onChange?.(b);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
) : null,
|
|
261
|
+
/* @__PURE__ */ jsx("p", { style: explainerStyle, children: "Apps see only rough floors, like \u201Cat least 100,000 sats, held 30 days or more\u201D, and a number unique to that app. Never the coin, your address or your balance. An app can block that number; only moving the coin gets past it, and a moved coin starts its age again." })
|
|
262
|
+
] });
|
|
263
|
+
}
|
|
264
|
+
function BondForm({
|
|
265
|
+
call,
|
|
266
|
+
onCancel,
|
|
267
|
+
onDone
|
|
268
|
+
}) {
|
|
269
|
+
const [address, setAddress] = React.useState("");
|
|
270
|
+
const [coins, setCoins] = React.useState(null);
|
|
271
|
+
const [chosen, setChosen] = React.useState("");
|
|
272
|
+
const [message, setMessage] = React.useState(null);
|
|
273
|
+
const [busy, setBusy] = React.useState(false);
|
|
274
|
+
const [err, setErr] = React.useState(null);
|
|
275
|
+
const id = React.useId();
|
|
276
|
+
const addr = address.trim();
|
|
277
|
+
const canFind = addr.length >= 14;
|
|
278
|
+
async function run(fn) {
|
|
279
|
+
setBusy(true);
|
|
280
|
+
setErr(null);
|
|
281
|
+
try {
|
|
282
|
+
await fn();
|
|
283
|
+
} catch {
|
|
284
|
+
setErr(bondRefusal("chain_unavailable"));
|
|
285
|
+
} finally {
|
|
286
|
+
setBusy(false);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const findCoins = () => run(async () => {
|
|
290
|
+
const j = await call("POST", { action: "coins", address: addr });
|
|
291
|
+
if (!j.ok) return setErr(bondRefusal(j.reason));
|
|
292
|
+
const list = j.coins ?? [];
|
|
293
|
+
setCoins(list);
|
|
294
|
+
setChosen(list.find(usable)?.outpoint ?? "");
|
|
295
|
+
});
|
|
296
|
+
const getMessage = () => run(async () => {
|
|
297
|
+
const j = await call("POST", { action: "message", address: addr, outpoint: chosen });
|
|
298
|
+
if (!j.ok || typeof j.message !== "string") return setErr(bondRefusal(j.reason));
|
|
299
|
+
setMessage(j.message);
|
|
300
|
+
});
|
|
301
|
+
const submit = (signature) => run(async () => {
|
|
302
|
+
const j = await call("POST", { action: "bond", message, signature });
|
|
303
|
+
if (!j.ok || !j.bond) return setErr(bondRefusal(j.reason));
|
|
304
|
+
onDone(j.bond);
|
|
305
|
+
});
|
|
306
|
+
function back() {
|
|
307
|
+
setErr(null);
|
|
308
|
+
if (message) setMessage(null);
|
|
309
|
+
else setCoins(null);
|
|
310
|
+
}
|
|
311
|
+
const error = err ? /* @__PURE__ */ jsx(ErrorLine, { children: err }) : null;
|
|
312
|
+
const backButton = /* @__PURE__ */ jsx("button", { type: "button", style: ghostBtnStyle(busy), disabled: busy, onClick: back, children: "back" });
|
|
313
|
+
const cancelButton = /* @__PURE__ */ jsx("button", { type: "button", style: ghostBtnStyle(busy), disabled: busy, onClick: onCancel, children: "cancel" });
|
|
314
|
+
let step;
|
|
315
|
+
if (message) {
|
|
316
|
+
step = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
317
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
318
|
+
"sign with the wallet that holds ",
|
|
319
|
+
/* @__PURE__ */ jsx("span", { style: addressStyle, children: addr }),
|
|
320
|
+
". signing spends nothing, and the coin stays in your wallet."
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsx(
|
|
323
|
+
LazyWalletButton,
|
|
324
|
+
{
|
|
325
|
+
address: addr,
|
|
326
|
+
message,
|
|
327
|
+
showManual: true,
|
|
328
|
+
onSigned: (sig) => void submit(sig),
|
|
329
|
+
onError: (e) => setErr(e.message ?? "the wallet refused to sign")
|
|
330
|
+
}
|
|
331
|
+
),
|
|
332
|
+
error,
|
|
333
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
334
|
+
backButton,
|
|
335
|
+
cancelButton
|
|
336
|
+
] })
|
|
337
|
+
] });
|
|
338
|
+
} else if (coins) {
|
|
339
|
+
const eligible = coins.filter((c) => c.sats >= MIN_SATS);
|
|
340
|
+
const dust = coins.length - eligible.length;
|
|
341
|
+
step = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
342
|
+
coins.length === 0 ? /* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
343
|
+
"no confirmed coins at that address \xB7 send at least ",
|
|
344
|
+
n(MIN_SATS),
|
|
345
|
+
" sats there and wait for six confirmations, about an hour. the longer a coin sits, the more it says."
|
|
346
|
+
] }) : eligible.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
347
|
+
/* @__PURE__ */ jsx("p", { style: { ...bodyStyle, color: V.foreground }, children: dustText(dust) }),
|
|
348
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
349
|
+
"send at least ",
|
|
350
|
+
n(MIN_SATS),
|
|
351
|
+
" sats there in one payment and wait for six confirmations, about an hour. coins aren't added together."
|
|
352
|
+
] })
|
|
353
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
354
|
+
/* @__PURE__ */ jsx("p", { id: `${id}-pick`, style: bodyStyle, children: "pick one coin \xB7 coins aren't added together" }),
|
|
355
|
+
/* @__PURE__ */ jsx(
|
|
356
|
+
"div",
|
|
357
|
+
{
|
|
358
|
+
role: "radiogroup",
|
|
359
|
+
"aria-labelledby": `${id}-pick`,
|
|
360
|
+
style: { display: "grid", gap: 8 },
|
|
361
|
+
children: eligible.map((c) => {
|
|
362
|
+
const young = c.age_blocks < MIN_CONFIRMATIONS;
|
|
363
|
+
return /* @__PURE__ */ jsxs(
|
|
364
|
+
"label",
|
|
365
|
+
{
|
|
366
|
+
style: coinStyle(chosen === c.outpoint, young),
|
|
367
|
+
children: [
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
"input",
|
|
370
|
+
{
|
|
371
|
+
type: "radio",
|
|
372
|
+
name: "oc-bond-coin",
|
|
373
|
+
value: c.outpoint,
|
|
374
|
+
checked: chosen === c.outpoint,
|
|
375
|
+
disabled: young,
|
|
376
|
+
onChange: () => setChosen(c.outpoint),
|
|
377
|
+
style: { accentColor: V.primary, margin: "2px 0 0" }
|
|
378
|
+
}
|
|
379
|
+
),
|
|
380
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "grid", gap: 2, minWidth: 0 }, children: [
|
|
381
|
+
/* @__PURE__ */ jsxs(
|
|
382
|
+
"span",
|
|
383
|
+
{
|
|
384
|
+
style: {
|
|
385
|
+
color: young ? V.muted : V.foreground,
|
|
386
|
+
fontSize: 13
|
|
387
|
+
},
|
|
388
|
+
children: [
|
|
389
|
+
n(c.sats),
|
|
390
|
+
" sats, held",
|
|
391
|
+
" ",
|
|
392
|
+
Math.floor(c.age_blocks / 144),
|
|
393
|
+
" days"
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
),
|
|
397
|
+
/* @__PURE__ */ jsx("span", { style: smallStyle, children: young ? "too recent: needs six confirmations" : `apps would see ${bondFloorsText(c)}` }),
|
|
398
|
+
/* @__PURE__ */ jsx(
|
|
399
|
+
"span",
|
|
400
|
+
{
|
|
401
|
+
style: {
|
|
402
|
+
...smallStyle,
|
|
403
|
+
fontFamily: SIGNIN_FONT,
|
|
404
|
+
wordBreak: "break-all"
|
|
405
|
+
},
|
|
406
|
+
children: short(c.outpoint)
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
] })
|
|
410
|
+
]
|
|
411
|
+
},
|
|
412
|
+
c.outpoint
|
|
413
|
+
);
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
),
|
|
417
|
+
dust > 0 ? /* @__PURE__ */ jsx("p", { style: { ...smallStyle, marginTop: 8 }, children: dustText(dust) }) : null
|
|
418
|
+
] }),
|
|
419
|
+
error,
|
|
420
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
421
|
+
eligible.length > 0 ? /* @__PURE__ */ jsx(
|
|
422
|
+
"button",
|
|
423
|
+
{
|
|
424
|
+
type: "button",
|
|
425
|
+
style: primaryBtnStyle(busy || !chosen),
|
|
426
|
+
disabled: busy || !chosen,
|
|
427
|
+
onClick: () => void getMessage(),
|
|
428
|
+
children: "continue"
|
|
429
|
+
}
|
|
430
|
+
) : null,
|
|
431
|
+
backButton,
|
|
432
|
+
cancelButton
|
|
433
|
+
] })
|
|
434
|
+
] });
|
|
435
|
+
} else {
|
|
436
|
+
step = /* @__PURE__ */ jsxs(
|
|
437
|
+
"form",
|
|
438
|
+
{
|
|
439
|
+
style: { margin: 0 },
|
|
440
|
+
onSubmit: (e) => {
|
|
441
|
+
e.preventDefault();
|
|
442
|
+
if (canFind && !busy) void findCoins();
|
|
443
|
+
},
|
|
444
|
+
children: [
|
|
445
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${id}-address`, style: fieldLabelStyle, children: "bitcoin address that holds the coin" }),
|
|
446
|
+
/* @__PURE__ */ jsx("p", { id: `${id}-advice`, style: { ...bodyStyle, margin: "0 0 8px" }, children: "use one that holds nothing else of yours: orangecheck learns its address and amount. apps never do." }),
|
|
447
|
+
/* @__PURE__ */ jsx(
|
|
448
|
+
"input",
|
|
449
|
+
{
|
|
450
|
+
id: `${id}-address`,
|
|
451
|
+
"aria-describedby": `${id}-advice`,
|
|
452
|
+
style: inputStyle,
|
|
453
|
+
value: address,
|
|
454
|
+
onChange: (e) => setAddress(e.target.value),
|
|
455
|
+
placeholder: "bc1q\u2026",
|
|
456
|
+
spellCheck: false,
|
|
457
|
+
autoComplete: "off",
|
|
458
|
+
autoCapitalize: "off"
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
error,
|
|
462
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
463
|
+
/* @__PURE__ */ jsx(
|
|
464
|
+
"button",
|
|
465
|
+
{
|
|
466
|
+
type: "submit",
|
|
467
|
+
style: primaryBtnStyle(busy || !canFind),
|
|
468
|
+
disabled: busy || !canFind,
|
|
469
|
+
children: "find its coins"
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
cancelButton
|
|
473
|
+
] })
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("accent"), "data-oc-bond-form": "", children: [
|
|
479
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 back your account" }),
|
|
480
|
+
step
|
|
481
|
+
] });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export {
|
|
485
|
+
bondFloorsText,
|
|
486
|
+
bondRefusal,
|
|
487
|
+
fetchOcBond,
|
|
488
|
+
OcBond
|
|
489
|
+
};
|
|
490
|
+
//# sourceMappingURL=chunk-FDWEXEZL.mjs.map
|