Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
39baa14
feat: add tap-to-play overlay for Twitter link referrals
claude Jan 30, 2026
7580388
Merge pull request #85 from tacticalnoot/claude/twitter-id-music-play…
tacticalnoot Jan 30, 2026
22eb34b
feat: implement real Session Keys and Batch Settlement in StreamPay lab
Jan 31, 2026
bcd27f6
refactor: optimize Stellar SDK usage and lazy-load passkey-kit to red…
Jan 31, 2026
bbe0058
fix: resolve 'rpc is not defined' regression in batch-transfer simula…
Jan 31, 2026
06e2261
fix: move assembleTransaction import to rpc subpath to resolve runtim…
Jan 31, 2026
8584e7c
fix: consolidate SDK imports to root minimal entrypoint to fix Transa…
Jan 31, 2026
73ae46a
fix(streampay): sign and submit session key authorization transaction
Jan 31, 2026
bc932ec
feat(streampay): add retry handler with exponential backoff for 503 e…
Jan 31, 2026
c6716dd
feat(streampay): add polished success UI with confetti, stats, and st…
Jan 31, 2026
c8b1e7b
style(streampay): refresh flavor text with punchy, on-brand copy
Jan 31, 2026
87a0e36
fix(streampay): construct full audio URL from Song_1 UUID
Jan 31, 2026
8f38e11
style(streampay): clean up spacing and line breaks in agreement UI
Jan 31, 2026
7e0c2ce
fix(streampay): correct session key text - no burn, just eject and se…
Jan 31, 2026
5b9edcb
fix(streampay): filter out NULL_ACCOUNT and invalid artist addresses …
Jan 31, 2026
841c134
feat(streampay): add balance pre-check before batch settlement
Jan 31, 2026
119b1b9
style(streampay): change confetti animation to center explosion
Jan 31, 2026
1338c54
perf(kaleorfail): increase batch size to 50 for improved throughput
Jan 31, 2026
173226d
fix(passkey): serialize singleton initialization to prevent race cond…
claude Jan 31, 2026
3194672
Merge pull request #86 from tacticalnoot/claude/fix-passkey-singleton…
tacticalnoot Jan 31, 2026
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
43 changes: 43 additions & 0 deletions .agent/skills/astro/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: astro
description: Comprehensive guide for developing with the Astro web framework, including component architecture, routing, and deployment.
---

# Astro Framework Skill

Use this skill when developing, refactoring, or architecting applications using Astro.

## Core Concepts

### 1. Component Architecture
- **Astro Components (`.astro`)**: Zero-client-side JS by default. Components are processed at build time.
- **Island Architecture**: Hydrate interactive UI (Svelte, React, Vue) only when needed using `client:load`, `client:visible`, etc.
- **Content Collections**: Type-safe Markdown/MDX management via `src/content/config.ts`.

### 2. Routing & Pages
- **File-based Routing**: Any file in `src/pages/` becomes a route.
- **Dynamic Routes**: Use `[id].astro` and export `getStaticPaths()` for SSG, or use SSR mode.
- **Middleware**: Use `src/middleware.ts` for auth, logging, and request/response manipulation.

## CLI & Workflow
- `npx astro dev`: Start local development server.
- `npx astro build`: Build production site.
- `npx astro check`: Run type-checking and diagnostics.
- `npx astro add <integration>`: Add official or community integrations (e.g., `svelte`, `tailwind`).
- `npx astro sync`: Generate TypeScript types for content collections and configurations.

## Best Practices
- **Prefer SSG**: Build for performance whenever possible.
- **Optimize Assets**: Use `<Image />` component for automatic optimization.
- **Styling**: Prefer Tailwind or scoped CSS within `.astro` components.
- **SSR Optimization**: For Cloudflare/Edge deployments, keep dependencies lean to minimize bundle size.

## Project Structure
```
src/
├── components/ # Reusable UI components
├── layouts/ # Base HTML templates
├── pages/ # Route files (required)
├── content/ # Markdown/Data collections
└── middleware.ts # Auth/Request logic
```
54 changes: 54 additions & 0 deletions .agent/skills/cloudflare/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
name: cloudflare
description: Best practices for Cloudflare Workers, Durable Objects, and infrastructure (D1, R2, KV).
---

# Cloudflare Infrastructure Skill

Use this skill when developing serverless functions, stateful backends, or managing Cloudflare resources.

## Durable Objects (Stateful Coordination)

### 1. Design Patterns
- **Atomic Entities**: Create one Durable Object per logical unit (e.g., chat room, user session, document).
- **SQLite Storage**: Use the built-in SQLite for transactional, strongly consistent storage.
- **Hibernatable WebSockets**: Use for real-time applications to save costs by allowing objects to hibernate while idle.

### 2. Best Practices
- **Persistence**: Use `storage.put()` or SQL transactions for data you cannot lose.
- **Concurrency**: Use `blockConcurrencyWhile()` sparingly. DOs are single-threaded per instance, which prevents race conditions.
- **Initialization**: Run migrations and setup logic in the constructor or an `init()` method.

## Workers & Cloudflare Pages

### 1. API & Performance
- **Lean Bundles**: Keep dependencies minimal for fast cold starts.
- **KV for Config**: Use Cloudflare KV for global configuration or data with high read-concurrency.
- **R2 for Assets**: Store large media and binary data in R2 buckets.

### 2. Security & Compliance
- **Secrets Management**: Use `wrangler secret put` for API keys and sensitive environment variables.
- **Turnstile**: Integrate Cloudflare Turnstile for bot protection on sensitive routes.

## Local Development (Wrangler)
- `npx wrangler dev`: Run locally with Miniflare simulation.
- `npx wrangler deploy`: Deploy to Cloudflare.
- `npx wrangler d1 migrations build <db-name>`: Create database migrations.

## Code Patterns

**Durable Object RPC:**
```ts
export class MyObject extends DurableObject {
async getData() {
return await this.ctx.storage.get("data");
}
}
```

**SQL Injection Protection (D1):**
```ts
const { results } = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(userId).all();
```
15 changes: 13 additions & 2 deletions .agent/skills/defi-streaming-mechanics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ To ensure "Charged Regardless" behavior (Anti-gaming):
- *Fix*: Detailed accounting would require a centralized "Tick Server", which reduces decentralization.


## 4. Implementation Checklist
## 4. Virtual Escrow (Cloudflare Durable Objects)
To avoid excessive ledger fees and latency:
- **Off-Chain Accounting**: Use a Cloudflare Durable Object to track per-second debt.
- **Heartbeat**: If the user's connection drops, the DO stops the session immediately, limiting "unpaid" listening to a few seconds.
- **Settlement**: The DO triggers a Soroban contract call only once per session or when a significant balance threshold is reached.

## 5. State Expiration & Storage
- **Temporary Storage**: Use for transaction nonces and short-lived session IDs to keep contract rent low.
- **TTL Management**: Ensure the contract extends the TTL of active user data to prevent premature expiration during long listening sessions.

## 6. Implementation Checklist
- [ ] **Limit Loop**: Ensure `played_songs.length` does not exceed `MAX_OPS` (safe limit: 50).
- [ ] **Gas Estimation**: Ensure Deposit covers not just the Stream Cost but also the `Rent/Gas` for the Settlement Tx.
- [ ] **Dust**: Handle cases where `time_listened` < 1 second (Micro-payments below minimum granularity).
- [ ] **Dust**: Handle cases where `time_listened` < 1 second.
- [ ] **DO Heartbeat**: Implement client-side `ping` to the Durable Object every 5 seconds.
53 changes: 42 additions & 11 deletions .agent/skills/remotion/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,21 @@ When asking the agent to create animations, use this structured approach:
* "Use `z-transform` for depth."
* "Rotate X 15deg to give perspective."

## Code Patterns
## Code Patterns & Best Practices

### Animation Rules
- **Frame-Driven**: Always derive values from `useCurrentFrame()` and `useVideoConfig()`.
- **NO SIDE EFFECTS**: Do not use CSS animations, `setTimeout`, or `requestAnimationFrame`.
- **Interpolation**: Use `interpolate()` with `extrapolateRight: "clamp"` to prevent values from exceeding bounds.
- **Springs**: Use `spring()` for natural motion. Map the 0-1 output to properties using `interpolate()`.

### Layout & Sequencing
- **Sequences**: Use `<Sequence>` with `from` and `durationInFrames`.
- **Series**: Use `<Series>` for consecutive segments. Use `<Series.Step>` or negative `offset` for overlaps.
- **Transitions**: Use `<TransitionSeries>` from `@remotion/transitions` for fullscreen scene changes.
- **Asset Loading**: Always use `staticFile('path/to/asset')` for files in the `public/` folder.

## Essential Code Snippets

**Interpolation:**
```tsx
Expand All @@ -68,23 +82,40 @@ const opacity = interpolate(frame, [0, 30], [0, 1], {
});
```

**Springs:**
**Advanced Springs:**
```tsx
const { fps } = useVideoConfig();
const frame = useCurrentFrame();
const scale = spring({
frame,
fps,
config: { damping: 200 },
config: { damping: 12, stiffness: 100, mass: 1 },
durationInFrames: 30, // Stretch/compress the spring
});
const mappedScale = interpolate(scale, [0, 1], [0.8, 1]);
```

**Sequencing & Series:**
```tsx
<Series>
<Series.Step durationInFrames={30}>
<Intro />
</Series.Step>
<Series.Step durationInFrames={60}>
<MainContent />
</Series.Step>
</Series>
```

**Sequencing:**
Use `<Sequence>` to time events:
**Asset Management:**
```tsx
<Sequence from={0} durationInFrames={60}>
<Intro />
</Sequence>
<Sequence from={60}>
<MainContent />
</Sequence>
import { staticFile, Audio, Video, Img } from "remotion";

const MyComponent = () => (
<>
<Audio src={staticFile("bg-music.mp3")} />
<Video src={staticFile("overlay.mp4")} />
<Img src={staticFile("logo.png")} />
</>
);
```
6 changes: 4 additions & 2 deletions src/components/Account.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

onMount(async () => {
try {
const { result } = await kale.get().decimals();
const { result } = await (await kale.get()).decimals();
kaleDecimals = Number(result);
decimalsFactor = 10n ** BigInt(kaleDecimals);
} catch (err) {
Expand Down Expand Up @@ -115,7 +115,9 @@
}

// Build transfer transaction
const tx = await kale.get().transfer({
const tx = await (
await kale.get()
).transfer({
from: userState.contractId,
to: destination,
amount: amountInUnits,
Expand Down
13 changes: 6 additions & 7 deletions src/components/MintTradeModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,16 @@
networkPassphrase: import.meta.env.PUBLIC_NETWORK_PASSPHRASE!,
});

mintTokenClient = sac.get().getSACClient(mintTokenId);
mintTokenClient = (await sac.get()).getSACClient(mintTokenId);

const [
{ result: kaleDecRes },
{ result: mintDecRes },
{ result: ammBalanceRes },
] = await Promise.all([
kale.get().decimals(),
(await kale.get()).decimals(),
mintTokenClient.decimals(),
kale.get().balance({ id: ammId }),
(await kale.get()).balance({ id: ammId }),
]);

kaleDecimals = Number(kaleDecRes);
Expand Down Expand Up @@ -159,7 +159,7 @@
try {
const [{ result: kaleResult }, { result: mintResult }] =
await Promise.all([
kale.get().balance({ id: currentContractId }),
(await kale.get()).balance({ id: currentContractId }),
mintTokenClient.balance({ id: currentContractId }),
]);
userKaleBalance = kaleResult;
Expand All @@ -174,8 +174,7 @@
const promises: Promise<void>[] = [];

promises.push(
kale
.get()
(await kale.get())
.balance({ id: ammId })
.then(({ result }) => {
ammKaleBalance = result;
Expand All @@ -188,7 +187,7 @@
if (currentContractId) {
promises.push(
Promise.all([
kale.get().balance({ id: currentContractId }),
(await kale.get()).balance({ id: currentContractId }),
mintTokenClient.balance({ id: currentContractId }),
])
.then(([{ result: kaleResult }, { result: mintResult }]) => {
Expand Down
20 changes: 11 additions & 9 deletions src/components/Smol.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,17 @@
lastFetchedMintToken = mintToken;
lastFetchedUser = contractId;

const client = sac.get().getSACClient(mintToken);
getTokenBalance(client, contractId)
.then((balance) => {
tradeMintBalance = balance;
})
.catch((error) => {
console.error("Failed to fetch mint token balance:", error);
tradeMintBalance = 0n;
});
sac.get().then((kit) => {
const client = kit.getSACClient(mintToken);
getTokenBalance(client, contractId)
.then((balance) => {
tradeMintBalance = balance;
})
.catch((error) => {
console.error("Failed to fetch mint token balance:", error);
tradeMintBalance = 0n;
});
});
}
} else if (!mintToken) {
tradeMintBalance = 0n;
Expand Down
21 changes: 13 additions & 8 deletions src/components/artist/ArtistResults.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@
const base = shuffleArray(
liveDiscography
.filter((s) => s.Id)
.map((s) => `${API_URL}/image/${s.Id}.png?scale=16`)
.map((s) => `${API_URL}/image/${s.Id}.png?scale=16`),
).slice(0, 40);
collageImages = [...base, ...base];
}
Expand Down Expand Up @@ -513,11 +513,13 @@

$effect(() => {
if (currentSong?.Mint_Token && userState.contractId) {
getTokenBalance(
sac.get().getSACClient(currentSong.Mint_Token),
userState.contractId,
).then((b) => {
tradeMintBalance = b;
sac.get().then((kit) => {
getTokenBalance(
kit.getSACClient(currentSong.Mint_Token!),
userState.contractId!,
).then((b) => {
tradeMintBalance = b;
});
});
}
});
Expand Down Expand Up @@ -817,7 +819,7 @@

onDestroy(() => {
// Clear all pending timeouts
pendingTimeouts.forEach(t => clearTimeout(t));
pendingTimeouts.forEach((t) => clearTimeout(t));
pendingTimeouts.clear();
preloadedImageIds.clear();
});
Expand Down Expand Up @@ -889,7 +891,10 @@
const { scrollTop, scrollHeight, clientHeight } = el;
// Load more when within 800px of bottom
if (scrollHeight - scrollTop - clientHeight < 800) {
if (gridLimit < displayPlaylist.length && gridLimit < GRID_LIMIT_MAX) {
if (
gridLimit < displayPlaylist.length &&
gridLimit < GRID_LIMIT_MAX
) {
// Throttle? Svelte updates are fast enough usually, but let's be safe
gridLimit = Math.min(gridLimit + 50, GRID_LIMIT_MAX);
}
Expand Down
8 changes: 5 additions & 3 deletions src/components/artist/TipArtistModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
balanceState,
isTransactionInProgress,
} from "../../stores/balance.svelte.ts";
import { StrKey } from "@stellar/stellar-sdk";
import { StrKey } from "@stellar/stellar-sdk/minimal";
import Loader from "../ui/Loader.svelte";
import {
parseAndValidateAmount,
Expand Down Expand Up @@ -74,7 +74,7 @@
}

try {
const { result } = await kale.get().decimals();
const { result } = await (await kale.get()).decimals();
kaleDecimals = Number(result);
decimalsFactor = 10n ** BigInt(kaleDecimals);
} catch (err) {
Expand Down Expand Up @@ -174,7 +174,9 @@
submitting = true;
try {
// Build transfer
const tx = await kale.get().transfer({
const tx = await (
await kale.get()
).transfer({
from: userState.contractId,
to: lockedArtistAddress,
amount: amountInStroops,
Expand Down
8 changes: 5 additions & 3 deletions src/components/labs/KaleOrFailCore.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@
throw new Error("No valid recipients found");
}

// Chunk transfers into batches of 5 to avoid Relayer timeouts
const MAX_BATCH_SIZE = 5;
// Chunk transfers into batches of 50 (Soroban can handle ~100)
const MAX_BATCH_SIZE = 50;
const chunks = [];
for (let i = 0; i < allTransfers.length; i += MAX_BATCH_SIZE) {
chunks.push(allTransfers.slice(i, i + MAX_BATCH_SIZE));
Expand Down Expand Up @@ -491,7 +491,9 @@
settleStep = Math.floor((chunkIndex / chunks.length) * 50); // Progress visual

// Sign
const signedXdr = await account.get().sign(batchXdr, {
const signedXdr = await (
await account.get()
).sign(batchXdr, {
rpId: getSafeRpId(window.location.hostname),
keyId: userState.keyId,
expiration: sequence + 60,
Expand Down
Loading