Skip to content
Open
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
180 changes: 180 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,186 @@ jobs:
STELLARCRED_REGISTRY_ID: ${{ vars.STELLARCRED_REGISTRY_ID }}
STELLARCRED_RPC_URL: ${{ vars.STELLARCRED_RPC_URL || 'https://soroban-testnet.stellar.org' }}

cli:
name: CLI typecheck, tests & build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 20
cache: pnpm
cache-dependency-path: |
frontend/packages/sdk/pnpm-lock.yaml
frontend/packages/cli/pnpm-lock.yaml

# stellarcred is a standalone package outside the frontend pnpm
# workspace, depending on @stellarcred/sdk via a local `link:` path —
# same reasoning as the middleware job below.
- name: Install and build SDK
working-directory: frontend/packages/sdk
run: |
pnpm install --ignore-workspace --frozen-lockfile
pnpm build

- name: Install CLI
working-directory: frontend/packages/cli
run: pnpm install --ignore-workspace --frozen-lockfile

- name: Typecheck
working-directory: frontend/packages/cli
run: pnpm typecheck

- name: Test
working-directory: frontend/packages/cli
run: pnpm test

- name: Build
working-directory: frontend/packages/cli
run: pnpm build

- name: Smoke test
working-directory: frontend/packages/cli
run: |
node dist/cli.js --help
node dist/cli.js verify-url --return-url "https://example.com/vault" --claim kyc

middleware:
name: Middleware typecheck & tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 20
cache: pnpm
cache-dependency-path: |
frontend/packages/sdk/pnpm-lock.yaml
frontend/packages/middleware/pnpm-lock.yaml

# @stellarcred/middleware is a standalone package outside the frontend
# pnpm workspace, depending on @stellarcred/sdk via a local `link:`
# path — so unlike the frontend/sdk-integration jobs above, it can't
# rely on hoisting from a `frontend` workspace install and needs its
# own install (and the SDK it links to needs to be built first).
- name: Install and build SDK
working-directory: frontend/packages/sdk
run: |
pnpm install --ignore-workspace --frozen-lockfile
pnpm build

- name: Install middleware
working-directory: frontend/packages/middleware
run: pnpm install --ignore-workspace --frozen-lockfile

- name: Typecheck
working-directory: frontend/packages/middleware
run: pnpm typecheck

- name: Test
working-directory: frontend/packages/middleware
run: pnpm test

sdk-docs:
name: SDK API docs (TypeDoc)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 20
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Same pattern as the sdk-integration job above: @stellarcred/sdk is a
# standalone publish-only package outside the pnpm workspace, so its
# script runs from its own directory but resolves shared devDependencies
# (typedoc, typescript) hoisted from this workspace root install.
- name: Build API reference
working-directory: frontend/packages/sdk
run: pnpm docs:api

- name: Upload API reference artifact
uses: actions/upload-artifact@v4
with:
name: sdk-api-docs
path: frontend/packages/sdk/docs/api
retention-days: 7

sdk-docs-deploy:
name: Publish SDK API docs to GitHub Pages
needs: sdk-docs
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 20
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

- name: Install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile

- name: Build API reference
working-directory: frontend/packages/sdk
run: pnpm docs:api

- name: Configure GitHub Pages
uses: actions/configure-pages@v5

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: frontend/packages/sdk/docs/api

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

a11y:
name: Accessibility (axe-core)
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ node_modules
.next
dist

# Generated TypeDoc API reference (built by CI, published to GitHub Pages)
frontend/packages/sdk/docs/api

# Local settings
.soroban
.stellar
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ frontend/ Next.js 14 app (App Router)
app/api/issue/ server-side credential issuance, via @stellarcred/issuer
packages/sdk/ @stellarcred/sdk — hasClaim / getClaims / buildVerifyUrl
packages/issuer/ @stellarcred/issuer — server-only issuance (value/salt/commitment/sig)
packages/middleware/ @stellarcred/middleware — Express + Next.js claim-gating middleware
packages/cli/ stellarcred — CLI: check, issuers, verify-url, issuer register/status
lib/ proof.ts (noir_js + bb.js), contracts.ts (stellar-sdk), wallet
services/
indexer/ off-chain event indexing service (SQLite/Postgres, [README](services/indexer/README.md))
Expand Down Expand Up @@ -165,7 +167,15 @@ Two entry points, one backend:

Prefer Soroban directly? Read `ProofRegistry.is_verified` from your own contract;
no SDK required. See [`/developers`](frontend/app/developers/page.tsx) for the
full reference.
full reference, or the generated [API reference](https://doosewayo.github.io/StellarCred/)
for every SDK export.

Already have Express or Next.js? Skip the boilerplate with
[`@stellarcred/middleware`](frontend/packages/middleware) — one call gates a
route on a set of claims, with a 403 or a redirect to the verify flow on
failure. Prefer the command line? [`stellarcred`](frontend/packages/cli)
(`npx stellarcred`) checks claims, lists issuers, and builds verify links from
a shell or CI job.

---

Expand Down
84 changes: 67 additions & 17 deletions frontend/app/badge/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@ function BadgeContent() {
const searchParams = useSearchParams();
const wallet = searchParams.get("wallet") || "";
const claim = searchParams.get("claim") || searchParams.get("type") || "kyc";
const theme = searchParams.get("theme") || "dark";
// "auto" (the default) matches the host page's OS-level color scheme —
// this badge is meant to be embedded via <iframe> on someone else's site,
// so it has no way to see *that* page's own light/dark toggle, but
// matching the visitor's OS preference is the closest reasonable default.
// Matches buildBadgeUrl(), which only sets ?theme= for an explicit choice.
const theme = searchParams.get("theme") || "auto";
const isCompact = searchParams.get("compact") === "1" || searchParams.get("compact") === "true";

const [verified, setVerified] = useState<boolean | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [prefersDark, setPrefersDark] = useState(false);

useEffect(() => {
if (theme !== "auto" || typeof window === "undefined") return;
const mql = window.matchMedia("(prefers-color-scheme: dark)");
setPrefersDark(mql.matches);
const onChange = (e: MediaQueryListEvent) => setPrefersDark(e.matches);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [theme]);

useEffect(() => {
if (!wallet) {
Expand Down Expand Up @@ -47,7 +62,7 @@ function BadgeContent() {
};
}, [wallet, claim]);

const isDark = theme === "dark" || (theme === "auto" && typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches);
const isDark = theme === "dark" || (theme === "auto" && prefersDark);

const bgColor = isDark ? "#0d1117" : "#f8fafc";
const textColor = isDark ? "#f1f5f9" : "#0f172a";
Expand Down Expand Up @@ -77,6 +92,7 @@ function BadgeContent() {
alignItems: "center",
justifyContent: "flex-start",
background: "transparent",
overflow: "hidden",
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
}}
>
Expand All @@ -96,6 +112,8 @@ function BadgeContent() {
display: "inline-flex",
alignItems: "center",
gap: isCompact ? "0.45rem" : "0.6rem",
minWidth: 0,
maxWidth: "100%",
padding: isCompact ? "0.3rem 0.55rem" : "0.45rem 0.75rem",
background: bgColor,
color: textColor,
Expand All @@ -106,8 +124,17 @@ function BadgeContent() {
fontSize: isCompact ? "0.75rem" : "0.82rem",
userSelect: "none",
cursor: "pointer",
boxSizing: "border-box",
}}
>
<img
src={isDark ? "/brand/mark-dark.svg" : "/brand/mark-light.svg"}
alt=""
width={isCompact ? 14 : 16}
height={isCompact ? 14 : 16}
style={{ flexShrink: 0 }}
/>

<div
style={{
display: "flex",
Expand Down Expand Up @@ -138,15 +165,36 @@ function BadgeContent() {
)}
</div>

<div style={{ display: "flex", flexDirection: "column", lineHeight: 1.2 }}>
<div style={{ display: "flex", alignItems: "center", gap: "0.3rem" }}>
<span style={{ fontWeight: 600 }}>StellarCred</span>
<span style={{ color: faintColor }}>·</span>
<span style={{ fontWeight: 500, color: faintColor }}>{claimText}</span>
<div style={{ display: "flex", flexDirection: "column", lineHeight: 1.2, minWidth: 0, overflow: "hidden" }}>
<div style={{ display: "flex", alignItems: "center", gap: "0.3rem", minWidth: 0, overflow: "hidden" }}>
<span style={{ fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
StellarCred
</span>
<span style={{ color: faintColor, flexShrink: 0 }}>·</span>
<span
style={{
fontWeight: 500,
color: faintColor,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{claimText}
</span>
</div>

{!isCompact && (
<div style={{ fontSize: "0.68rem", color: faintColor, marginTop: "0.1rem" }}>
<div
style={{
fontSize: "0.68rem",
color: faintColor,
marginTop: "0.1rem",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{loading ? (
"Checking on-chain..."
) : verified ? (
Expand All @@ -166,14 +214,16 @@ function BadgeContent() {

export default function BadgePage() {
return (
<Suspense
fallback={
<div style={{ padding: "0.5rem", fontSize: "0.75rem", color: "#888" }}>
Loading verification badge...
</div>
}
>
<BadgeContent />
</Suspense>
<div id="stellarcred-embed-root" style={{ width: "100%", height: "100%" }}>
<Suspense
fallback={
<div style={{ padding: "0.5rem", fontSize: "0.75rem", color: "#888" }}>
Loading verification badge...
</div>
}
>
<BadgeContent />
</Suspense>
</div>
);
}
17 changes: 17 additions & 0 deletions frontend/app/developers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,23 @@ let kyc_ok = registry.check_claim(&holder, &symbol_short!("kyc"), &None, &Some(t
require!(kyc_ok, Error::KycRequired);`}</Code>
</Section>

<Section title="Full API reference">
<p className="muted" style={{ fontSize: "0.95rem", lineHeight: 1.7 }}>
Every export, type, and option above — generated from the SDK&rsquo;s
TSDoc comments and rebuilt on every push to <span className="mono">main</span>,
so it always matches the published package.
</p>
<a
href="https://doosewayo.github.io/StellarCred/"
target="_blank"
rel="noopener noreferrer"
className="btn btn-secondary"
style={{ marginTop: "0.75rem", display: "inline-flex" }}
>
Browse API reference
</a>
</Section>

<div style={{ height: "4rem" }} />
</div>
);
Expand Down
Loading