Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"name": "@mbga/core",
"path": "packages/core/dist/index.js",
"limit": "4 kB"
"limit": "4.5 kB"
},
{
"name": "@mbga/core/actions",
Expand Down
27 changes: 7 additions & 20 deletions NEXT_TASK.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,28 @@ After the task is done, Claude will update this file with the next task in line.

## Current Task

**Flashnet authentication**
**Flashnet swaps and clawbacks**

Simple, effortless auth flow — OAuth or API-key based — so apps can authenticate with Flashnet before any swap/trade operations.
Requires auth above; atomic swaps between BTC/Lightning/Spark and other assets via Flashnet, plus clawback support for reversible transfers.

## Prompt

```
Work on the quantumlyy/mbga repo.

Your current task: **Flashnet authentication**
Your current task: **Flashnet swaps and clawbacks**

This includes:

### Flashnet authentication
- Simple, effortless auth flow — OAuth or API-key based — so apps can authenticate with Flashnet before any swap/trade operations
### Flashnet swaps and clawbacks
- Requires auth above; atomic swaps between BTC/Lightning/Spark and other assets via Flashnet, plus clawback support for reversible transfers

After completing the task:
1. Mark it done in plans/todos.md (change `- [ ]` to `- [x]`)
2. Update NEXT_TASK.md — replace the "Current Task" section and the task
description inside the prompt with the next unchecked item from plans/todos.md.
Pick the highest-priority unchecked item (Medium Term first).

Git conventions:
- Author: Nejc Drobnic <nejc@flashnet.xyz>
- Committer: Claude (so commits are signed/verified)
- To achieve this, run commits like:
GIT_COMMITTER_NAME="Claude" GIT_COMMITTER_EMAIL="noreply@anthropic.com" \
git -c user.name="Nejc Drobnic" -c user.email="nejc@flashnet.xyz" \
commit -m "message"
- Include Co-authored-by and Signed-off-by trailers:
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Claude <noreply@anthropic.com>
- Push to whatever branch Claude Code created for this session.
- Do NOT create a PR unless told to.

Split work into logical commits. Run tests before pushing.
```

Expand All @@ -56,6 +43,6 @@ Split work into logical commits. Run tests before pushing.
4. ~~Build documentation site with VitePress~~ (DONE)
5. ~~Multi-wallet simultaneous connections~~ (DONE)
6. ~~Spark token operations~~ (DONE)
7. Flashnet authentication **(CURRENT)**
8. Flashnet swaps and clawbacks
7. ~~Flashnet authentication~~ (DONE)
8. Flashnet swaps and clawbacks **(CURRENT)**
9. Configure npm publishing workflow
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Reactive primitives for Spark apps.
- **Balance Queries** — fetch wallet balances with automatic caching
- **Payments** — send payments, create invoices, estimate fees, and wait for payment confirmations
- **Message Signing** — sign messages with connected wallets
- **Flashnet Auth** — API-key authentication for Flashnet orchestration (swaps, cross-chain)
- **React Hooks** — first-class React bindings powered by TanStack Query
- **TypeScript** — fully typed APIs with strict mode
- **SSR Ready** — compatible with server-side rendering frameworks
Expand Down Expand Up @@ -92,6 +93,7 @@ pnpm create mbga
| [`@mbga/core`](./packages/core) | VanillaJS library for Spark |
| [`@mbga/connectors`](./packages/connectors) | Collection of wallet connectors for MBGA |
| [`@mbga/test`](./packages/test) | Test utilities for MBGA |
| [`@mbga/flashnet`](./packages/flashnet) | Flashnet authentication and orchestration |
| [`create-mbga`](./packages/create-mbga) | Scaffold a new MBGA project |

## Community
Expand Down
1 change: 1 addition & 0 deletions examples/vite-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@mbga/connectors": "workspace:*",
"@mbga/flashnet": "workspace:*",
"@tanstack/react-query": "^5.49.2",
"@mbga/kit": "workspace:*",
"mbga": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions examples/vite-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { Layout } from './components/Layout'
import { config } from './mbga'
import { AddressPage } from './pages/AddressPage'
import { FlashnetPage } from './pages/FlashnetPage'
import { FeesPage } from './pages/FeesPage'
import { HomePage } from './pages/HomePage'
import { PlaygroundPage } from './pages/PlaygroundPage'
Expand All @@ -24,6 +25,7 @@ export default function App() {
<Route path="fees" element={<FeesPage />} />
<Route path="transactions" element={<TransactionsPage />} />
<Route path="address" element={<AddressPage />} />
<Route path="flashnet" element={<FlashnetPage />} />
<Route path="playground" element={<PlaygroundPage />} />
</Route>
</Routes>
Expand Down
73 changes: 73 additions & 0 deletions examples/vite-react/src/components/FlashnetAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
useFlashnetAuth,
useFlashnetExtension,
} from '@mbga/flashnet/react'
import { useConnection } from 'mbga'

export function FlashnetAuth() {
const { isAuthenticated, isAuthenticating, isError, publicKey, error } =
useFlashnetAuth()
const extension = useFlashnetExtension()
const { isConnected } = useConnection()

return (
<div className="space-y-3 rounded-lg border border-[var(--color-border)] p-4">
<div className="flex items-center gap-2">
<span
className={`inline-block h-2 w-2 rounded-full ${
isAuthenticating
? 'bg-yellow-400'
: isAuthenticated
? 'bg-green-500'
: 'bg-red-400'
}`}
/>
<span className="text-sm font-medium">
{isAuthenticating
? 'Authenticating...'
: isAuthenticated
? 'Authenticated'
: 'Not authenticated'}
</span>
</div>

{publicKey && (
<p className="text-xs text-[var(--color-text-secondary)]">
Public Key: <code className="break-all">{publicKey}</code>
</p>
)}

{isError && error && (
<p className="text-xs text-red-500">Error: {error.message}</p>
)}

<div className="flex gap-2">
{!isAuthenticated && (
<button
type="button"
onClick={() => extension.authenticate()}
disabled={isAuthenticating || !isConnected}
className="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90 disabled:opacity-50"
>
{isAuthenticating ? 'Authenticating...' : 'Authenticate'}
</button>
)}
{isAuthenticated && (
<button
type="button"
onClick={() => extension.disconnect()}
className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]"
>
Disconnect
</button>
)}
</div>

{!isConnected && !isAuthenticated && (
<p className="text-xs text-[var(--color-text-tertiary)]">
Connect a wallet first (Spark SDK connector required).
</p>
)}
</div>
)
}
1 change: 1 addition & 0 deletions examples/vite-react/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const navItems = [
{ to: '/fees', label: 'Estimate Fee', icon: '≈' },
{ to: '/transactions', label: 'Transactions', icon: '↔' },
{ to: '/address', label: 'Address Tools', icon: '◎' },
{ to: '/flashnet', label: 'Flashnet Auth', icon: '⚡' },
{ to: '/playground', label: 'Playground', icon: '▶' },
]

Expand Down
4 changes: 4 additions & 0 deletions examples/vite-react/src/mbga.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { walletStandardDiscovery, xverse } from '@mbga/connectors'
import { createFlashnetExtension } from '@mbga/flashnet'
import { createConfig, sparkMainnet } from 'mbga'

export const flashnet = createFlashnetExtension()

export const config = createConfig({
network: sparkMainnet,
connectors: [xverse()],
plugins: [walletStandardDiscovery()],
extensions: { flashnet },
})
16 changes: 16 additions & 0 deletions examples/vite-react/src/pages/FlashnetPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FlashnetAuth } from '../components/FlashnetAuth'

export function FlashnetPage() {
return (
<>
<h1 className="text-2xl font-semibold tracking-tight">Flashnet Auth</h1>
<p className="mt-1 text-sm text-[var(--color-text-secondary)]">
Authenticate with the Flashnet orchestration API using an API key.
</p>

<div className="mt-6">
<FlashnetAuth />
</div>
</>
)
}
27 changes: 27 additions & 0 deletions examples/vite-react/src/pages/PlaygroundPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type ReactNode, useState } from 'react'
import { AddressValidator } from '../components/AddressValidator'
import { Balance } from '../components/Balance'
import { EstimateFee } from '../components/EstimateFee'
import { FlashnetAuth } from '../components/FlashnetAuth'
import { ReceivePayment } from '../components/ReceivePayment'
import { SendPayment } from '../components/SendPayment'
import { SignMessage } from '../components/SignMessage'
Expand Down Expand Up @@ -170,6 +171,32 @@ isValidLightningInvoice('lnbc1pv...') // true
isValidSparkAddress('sp1qq...') // true`,
component: <AddressValidator />,
},
{
title: 'Flashnet Auth',
description:
'Challenge-response authentication with the Flashnet orchestration API.',
code: `import { createFlashnetExtension } from '@mbga/flashnet'
import { useFlashnetAuth, useFlashnetExtension } from '@mbga/flashnet/react'
import { createConfig, sparkMainnet } from 'mbga'

// Register as an extension — auto-runs plugin, captures config
const flashnet = createFlashnetExtension()
const config = createConfig({
network: sparkMainnet,
extensions: { flashnet },
})

// No FlashnetProvider needed — hooks resolve from config.extensions
function AuthStatus() {
const { isAuthenticated, isAuthenticating, publicKey } = useFlashnetAuth()
const ext = useFlashnetExtension()

if (isAuthenticating) return <p>Authenticating...</p>
if (isAuthenticated) return <p>{publicKey}</p>
return <button onClick={() => ext.authenticate()}>Authenticate</button>
}`,
component: <FlashnetAuth />,
},
]

export function PlaygroundPage() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "pnpm run --filter @mbga/core build && pnpm run --filter @mbga/connectors build && pnpm run --filter mbga build && pnpm run --filter @mbga/kit build && pnpm run --filter create-mbga build",
"build": "pnpm run --filter @mbga/core build && pnpm run --filter @mbga/connectors build && pnpm run --filter mbga build && pnpm run --filter @mbga/kit build && pnpm run --filter @mbga/flashnet build && pnpm run --filter create-mbga build",
"check": "biome check --write",
"check:types": "pnpm build && pnpm run --r --parallel check:types && tsc --noEmit",
"clean": "pnpm run --r --parallel clean",
Expand Down
44 changes: 38 additions & 6 deletions packages/core/src/createConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ import { version } from './version'
* })
* ```
*/
export function createConfig(parameters: CreateConfigParameters): Config {
export function createConfig<
const TExtensions extends Record<string, unknown> = Record<string, never>,
>(parameters: CreateConfigParameters<TExtensions>): Config<TExtensions> {
const {
storage = createStorage({
storage: getDefaultStorage(),
}),
ssr = false,
extensions: exts,
...rest
} = parameters
const extensions = (exts ?? {}) as TExtensions

const connectors = createStore(() => {
const collection: Connector[] = []
Expand Down Expand Up @@ -123,7 +127,8 @@ export function createConfig(parameters: CreateConfigParameters): Config {

const cleanups: (() => void)[] = []

const config: Config = {
const config: Config<TExtensions> = {
extensions,
get connectors() {
return connectors.getState()
},
Expand Down Expand Up @@ -165,8 +170,14 @@ export function createConfig(parameters: CreateConfigParameters): Config {
version,
}

// Run plugins after config is fully constructed
for (const plugin of rest.plugins ?? []) {
// Collect plugins: extension plugins first, then explicit plugins
const plugins: ConfigPlugin[] = []
for (const ext of Object.values(extensions)) {
if (ext && typeof (ext as { plugin?: unknown }).plugin === 'function')
plugins.push((ext as { plugin: ConfigPlugin }).plugin)
}
if (rest.plugins) plugins.push(...rest.plugins)
for (const plugin of plugins) {
const cleanup = plugin(config)
if (cleanup) cleanups.push(cleanup)
}
Expand Down Expand Up @@ -216,9 +227,27 @@ export type PartializedState = Compute<
export type ConfigPlugin = (config: Config) => (() => void) | void

/** Parameters for {@link createConfig}. */
export type CreateConfigParameters = {
export type CreateConfigParameters<
TExtensions extends Record<string, unknown> = Record<string, unknown>,
> = {
/** Connector factory functions to register. */
connectors?: CreateConnectorFn[] | undefined
/**
* Extensions to register on the config. Each extension is stored on `config.extensions`
* and its `plugin` (if present) is auto-invoked.
*
* @example
* ```ts
* import { createFlashnetExtension } from '@mbga/flashnet'
*
* const config = createConfig({
* extensions: { flashnet: createFlashnetExtension() },
* })
*
* config.extensions.flashnet // fully typed
* ```
*/
extensions?: TExtensions | undefined
/** The Spark network to operate on (mainnet or testnet). */
network: SparkNetwork
/**
Expand All @@ -242,7 +271,10 @@ export type CreateConfigParameters = {
}

/** Central configuration object returned by {@link createConfig}. Passed to all actions and hooks. */
export type Config = {
export type Config<
TExtensions extends Record<string, unknown> = Record<string, unknown>,
> = {
readonly extensions: TExtensions
readonly connectors: readonly Connector[]
readonly state: State
readonly storage: Storage | null
Expand Down
Loading
Loading