diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04cdf5d..211f707 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,6 @@ on: env: CARGO_TERM_COLOR: always PKG_CONFIG_PATH: /usr/lib/pkgconfig - PUBLIC_STELLAR_NETWORK: local jobs: build: @@ -129,6 +128,8 @@ jobs: # Setting pipefail so a failed build's exit status isn't overwritten # by a successful call of `tee` set -o pipefail + # Released CLIs still need --build-clients; newer ones only note + # that it's redundant for a version 2 scaffold.yml stellar-scaffold build --build-clients --network local 2>&1 \ | tee build_clients.log - name: Check client generation summary @@ -156,13 +157,14 @@ jobs: echo "Expected $INDEX to exist and be non-empty after client generation." exit 1 fi - # Count one exported const per contract + # Count one client constructor per contract; the shared `options` + # export is not a client EXPECTED=$(find contracts -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ') - ACTUAL=$(grep -c "^export const " "$INDEX" || true) + ACTUAL=$(grep -c "^export const .* = new " "$INDEX" || true) if [ "$ACTUAL" -ne "$EXPECTED" ]; then echo "Expected $EXPECTED client(s) in $INDEX, found $ACTUAL." echo "Partial client generation, check the \"Build with Scaffold\" step." - grep "^export const " "$INDEX" || echo "(no exports)" + grep "^export const .* = new " "$INDEX" || echo "(no clients)" exit 1 fi echo "Generated $ACTUAL client(s)." diff --git a/.gitignore b/.gitignore index 130941b..8116620 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ target .soroban -# project identity and alias files will be in .config/stellar -.config +# what `stellar scaffold build` deployed from this checkout. The directory +# ignores itself for git; listing it here keeps Prettier out of it too +.scaffold # test snapshot folders from sample contracts test_snapshots diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49b7a44..c700403 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,9 +29,9 @@ npm start --workspace templates/react npm start --workspace templates/svelte ``` -Each runs `stellar scaffold watch --build-clients` alongside the dev server, so -you need the **Stellar CLI** and **Stellar Scaffold CLI** installed (the root -`npm start` prints install instructions if either is missing). +Each runs `stellar scaffold watch` alongside the dev server, so you need the +**Stellar CLI** and **Stellar Scaffold CLI** installed (the root `npm start` +prints install instructions if either is missing). Shared code lives in `app-lib/`; changes there are picked up by every template via the `@stellar-scaffold/app-lib` workspace package. Keep framework-specific diff --git a/README.md b/README.md index 7a92da6..a389584 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ cd my-project 2. Set up your development environment: ```bash -# Copy and configure environment variables like STELLAR_NETWORK +# Copy environment variables (optional STELLAR_NETWORK, secrets) cp .env.example .env # Install frontend dependencies diff --git a/app-lib/env.ts b/app-lib/env.ts index f4d7f2c..9e2dc60 100644 --- a/app-lib/env.ts +++ b/app-lib/env.ts @@ -1,5 +1,5 @@ import { Networks } from "@creit.tech/stellar-wallets-kit" -import { z } from "zod" +import { options } from "./clients" type NetworkType = "mainnet" | "testnet" | "futurenet" | "local" @@ -8,63 +8,97 @@ interface Network { label: string passphrase: string rpcUrl: string - horizonUrl: string } -const envSchema = z.object({ - PUBLIC_STELLAR_NETWORK: z.enum([ - "PUBLIC", - "FUTURENET", - "TESTNET", - "LOCAL", - "STANDALONE", // deprecated in favor of LOCAL - ] as const), - PUBLIC_STELLAR_NETWORK_PASSPHRASE: z.enum(Networks), - PUBLIC_STELLAR_RPC_URL: z.string(), - PUBLIC_STELLAR_HORIZON_URL: z.string(), -}) +/** + * The Stellar networks app-lib knows how to link to and fund on, keyed by + * passphrase. Custom networks that `extends` one of these share its + * passphrase, so they resolve to it too. + */ +const KNOWN_NETWORKS = { + [Networks.PUBLIC]: "PUBLIC", + [Networks.TESTNET]: "TESTNET", + [Networks.FUTURENET]: "FUTURENET", + [Networks.STANDALONE]: "LOCAL", +} as const -const parsed = envSchema.safeParse(import.meta.env) +type StellarNetwork = (typeof KNOWN_NETWORKS)[keyof typeof KNOWN_NETWORKS] -const env: z.infer = parsed.success - ? parsed.data - : { - PUBLIC_STELLAR_NETWORK: "LOCAL", - PUBLIC_STELLAR_NETWORK_PASSPHRASE: Networks.STANDALONE, - PUBLIC_STELLAR_RPC_URL: "http://localhost:8000/rpc", - PUBLIC_STELLAR_HORIZON_URL: "http://localhost:8000", - } +/** + * The network the generated clients were built for. `stellar scaffold` writes + * `options` into `clients/index.ts` from the network selected in + * `scaffold.yml`, so the frontend can never disagree with the contracts it + * calls. An unrecognized passphrase is an error rather than a silent fallback + * to the local network. + */ +const stellarNetworkFor = (passphrase: string): StellarNetwork => { + if (passphrase in KNOWN_NETWORKS) { + return KNOWN_NETWORKS[passphrase as keyof typeof KNOWN_NETWORKS] + } + throw new Error( + `Unsupported network passphrase in generated clients: "${passphrase}". app-lib supports the local, testnet, futurenet, and mainnet passphrases.`, + ) +} -export const stellarNetwork = - env.PUBLIC_STELLAR_NETWORK === "STANDALONE" - ? "LOCAL" - : env.PUBLIC_STELLAR_NETWORK -export const networkPassphrase = env.PUBLIC_STELLAR_NETWORK_PASSPHRASE +export const networkPassphrase = options.networkPassphrase +export const rpcUrl = options.rpcUrl +// Plain HTTP is only allowed where `scaffold.yml` allows it (local by default) +export const allowHttp = options.allowHttp +export const stellarNetwork = stellarNetworkFor(networkPassphrase) const stellarEncode = (str: string) => { return str.replace(/\//g, "//").replace(/;/g, "/;") } -export const labPrefix = () => { +/** + * Stellar Lab's network state for a transaction-dashboard link. Lab also wants + * a Horizon URL: SDF's public instance for the public networks, and the + * quickstart container's root (where its Horizon is served) for local. + */ +const labNetwork = (): { + host: string + id: string + label: string + horizonUrl: string +} => { switch (stellarNetwork) { case "LOCAL": - return `http://localhost:8000/lab/transaction-dashboard?$=network$id=custom&label=Custom&horizonUrl=${stellarEncode(horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` + return { + host: "http://localhost:8000/lab", + id: "custom", + label: "Custom", + horizonUrl: new URL(rpcUrl).origin, + } case "PUBLIC": - return `https://lab.stellar.org/transaction-dashboard?$=network$id=mainnet&label=Mainnet&horizonUrl=${stellarEncode(horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` - case "TESTNET": - return `https://lab.stellar.org/transaction-dashboard?$=network$id=testnet&label=Testnet&horizonUrl=${stellarEncode(horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` + return { + host: "https://lab.stellar.org", + id: "mainnet", + label: "Mainnet", + horizonUrl: "https://horizon.stellar.org", + } case "FUTURENET": - return `https://lab.stellar.org/transaction-dashboard?$=network$id=futurenet&label=Futurenet&horizonUrl=${stellarEncode(horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` - default: - return `https://lab.stellar.org/transaction-dashboard?$=network$id=testnet&label=Testnet&horizonUrl=${stellarEncode(horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` + return { + host: "https://lab.stellar.org", + id: "futurenet", + label: "Futurenet", + horizonUrl: "https://horizon-futurenet.stellar.org", + } + case "TESTNET": + return { + host: "https://lab.stellar.org", + id: "testnet", + label: "Testnet", + horizonUrl: "https://horizon-testnet.stellar.org", + } } } -// NOTE: needs to be exported for contract files in this directory -export const rpcUrl = env.PUBLIC_STELLAR_RPC_URL -export const horizonUrl = env.PUBLIC_STELLAR_HORIZON_URL +export const labPrefix = () => { + const lab = labNetwork() + return `${lab.host}/transaction-dashboard?$=network$id=${lab.id}&label=${lab.label}&horizonUrl=${stellarEncode(lab.horizonUrl)}&rpcUrl=${stellarEncode(rpcUrl)}&passphrase=${stellarEncode(networkPassphrase)};` +} -const networkToId = (network: string): NetworkType => { +const networkToId = (network: StellarNetwork): NetworkType => { switch (network) { case "PUBLIC": return "mainnet" @@ -72,7 +106,7 @@ const networkToId = (network: string): NetworkType => { return "testnet" case "FUTURENET": return "futurenet" - default: + case "LOCAL": return "local" } } @@ -82,5 +116,4 @@ export const network: Network = { label: stellarNetwork.toLowerCase(), passphrase: networkPassphrase, rpcUrl: rpcUrl, - horizonUrl: horizonUrl, } diff --git a/app-lib/friendbot.ts b/app-lib/friendbot.ts index 8d56647..6d39dd0 100644 --- a/app-lib/friendbot.ts +++ b/app-lib/friendbot.ts @@ -11,9 +11,7 @@ export function getFriendbotUrl(address: string) { case "TESTNET": return `https://friendbot.stellar.org/?addr=${address}` default: - throw new Error( - `Unknown or unsupported PUBLIC_STELLAR_NETWORK for friendbot: ${stellarNetwork}`, - ) + throw new Error(`Friendbot is not available on ${stellarNetwork}`) } } diff --git a/app-lib/index.ts b/app-lib/index.ts index bfe1472..519d493 100644 --- a/app-lib/index.ts +++ b/app-lib/index.ts @@ -3,7 +3,7 @@ // Clients in app-lib/clients/index.ts). Framework-specific code (providers, // stores, UI components) stays in each template. -export * from "./env" // rpcUrl, networkPassphrase, stellarNetwork, horizonUrl, network, labPrefix +export * from "./env" // rpcUrl, networkPassphrase, allowHttp, stellarNetwork, network, labPrefix export * from "./format" // shortAddress, formatNetworkName, networkStatus, NetworkState export * from "./friendbot" // getFriendbotUrl export * from "./subscription" // subscribeToEvents diff --git a/app-lib/package.json b/app-lib/package.json index 169ab55..911da4f 100644 --- a/app-lib/package.json +++ b/app-lib/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "@creit.tech/stellar-wallets-kit": "^2.5.0", - "@stellar/stellar-sdk": "^16.1.0", - "zod": "^4.3.5" + "@stellar/stellar-sdk": "^16.1.0" } } diff --git a/app-lib/subscription.ts b/app-lib/subscription.ts index bd94578..b4d0045 100644 --- a/app-lib/subscription.ts +++ b/app-lib/subscription.ts @@ -1,6 +1,6 @@ import { xdr } from "@stellar/stellar-sdk" import { Server, type Api } from "@stellar/stellar-sdk/rpc" -import { rpcUrl, stellarNetwork } from "./env" +import { allowHttp, rpcUrl } from "./env" /** A Soroban contract event delivered to a subscription callback. Re-exported so * templates depend only on @stellar-scaffold/app-lib, not @stellar/stellar-sdk. */ @@ -13,8 +13,8 @@ const paging: Record< { lastLedgerStart?: number; pagingToken?: string } > = {} -// NOTE: Server is configured using envvars which shouldn't change during runtime -const server = new Server(rpcUrl, { allowHttp: stellarNetwork === "LOCAL" }) +// Server is configured from the generated clients' options, which don't change at runtime +const server = new Server(rpcUrl, { allowHttp }) /** * Subscribe to events for a given topic from a given contract. Returns a diff --git a/app-lib/wallet.ts b/app-lib/wallet.ts index 2d23f5b..94b59c5 100644 --- a/app-lib/wallet.ts +++ b/app-lib/wallet.ts @@ -4,8 +4,8 @@ import { StellarWalletsKit, } from "@creit.tech/stellar-wallets-kit" import { defaultModules } from "@creit.tech/stellar-wallets-kit/modules/utils" -import { Horizon } from "@stellar/stellar-sdk" -import { networkPassphrase, stellarNetwork } from "./env" +import { Keypair, rpc, xdr } from "@stellar/stellar-sdk" +import { allowHttp, networkPassphrase, rpcUrl, stellarNetwork } from "./env" // These wallets work on Local and Futurenet while the rest enforce or fail on // any networks beside Testnet and Mainnet. Filter below based on dApp config. @@ -139,52 +139,52 @@ export const onWalletChange = ( } } -function getHorizonHost(mode: string) { - switch (mode) { - case "LOCAL": - return "http://localhost:8000" - case "FUTURENET": - return "https://horizon-futurenet.stellar.org" - case "TESTNET": - return "https://horizon-testnet.stellar.org" - case "PUBLIC": - return "https://horizon.stellar.org" - default: - throw new Error(`Unknown Stellar network: ${mode}`) - } -} - -const horizon = new Horizon.Server(getHorizonHost(stellarNetwork), { - allowHttp: stellarNetwork === "LOCAL", -}) +// Server is configured from the generated clients' options, which don't change at runtime +const server = new rpc.Server(rpcUrl, { allowHttp }) const formatter = new Intl.NumberFormat() -export type MappedBalances = Record +/** One stroop is 10^-7 XLM. */ +const STROOPS_PER_XLM = 10_000_000n + +/** + * Format a stroop amount as XLM. Splits the integer and fractional parts in + * bigint so large balances don't lose stroops before formatting. + */ +const formatStroops = (stroops: bigint): string => { + const whole = stroops / STROOPS_PER_XLM + const fraction = (stroops % STROOPS_PER_XLM).toString().padStart(7, "0") + return formatter.format(Number(`${whole}.${fraction}`)) +} + +/** + * Balances keyed by asset. Only native XLM (`xlm`) is fetched: RPC reads + * ledger entries by key and can't list an account's trustlines. + */ +export type MappedBalances = { xlm?: { balance: string } } -export const fetchBalances = async (address: string) => { +/** + * Fetch the account's native XLM balance over RPC. An unfunded account has no + * ledger entry, so it resolves to `{}` and consumers see no `xlm` key. RPC + * failures are logged and also resolve to `{}`. + */ +export const fetchBalances = async ( + address: string, +): Promise => { try { - const { balances } = await horizon.accounts().accountId(address).call() - const mapped = balances.reduce((acc, b) => { - b.balance = formatter.format(Number(b.balance)) - const key = - b.asset_type === "native" - ? "xlm" - : b.asset_type === "liquidity_pool_shares" - ? b.liquidity_pool_id - : `${b.asset_code}:${b.asset_issuer}` - acc[key] = b - return acc - }, {} as MappedBalances) - return mapped + const key = xdr.LedgerKey.account( + new xdr.LedgerKeyAccount({ + accountId: Keypair.fromPublicKey(address).xdrPublicKey(), + }), + ) + // `getLedgerEntries` rather than `getAccountEntry`, which reports every + // failure (including RPC outages) as "Account not found" + const { entries } = await server.getLedgerEntries(key) + const account = entries[0]?.val.account() + if (!account) return {} + return { xlm: { balance: formatStroops(account.balance().toBigInt()) } } } catch (err) { - // `not found` is sort of expected, indicating an unfunded wallet, which - // the consumer of `balances` can understand via the lack of `xlm` key. - // If the error does NOT match 'not found', log the error. - // We should also possibly not return `{}` in this case? - if (!(err instanceof Error && err.message.match(/not found/i))) { - console.error(err) - } + console.error(err) return {} } } diff --git a/package-lock.json b/package-lock.json index 9f699d7..14fb15c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,14 +22,13 @@ "version": "0.0.0", "dependencies": { "@creit.tech/stellar-wallets-kit": "^2.5.0", - "@stellar/stellar-sdk": "^16.1.0", - "zod": "^4.3.5" + "@stellar/stellar-sdk": "^16.1.0" } }, "app-lib/clients/fungible_allowlist_example": { "version": "0.0.0", "dependencies": { - "@stellar/stellar-sdk": "^14.1.1", + "@stellar/stellar-sdk": "^14.5.0", "buffer": "6.0.3" }, "devDependencies": { @@ -62,7 +61,7 @@ "app-lib/clients/guess_the_number": { "version": "0.0.0", "dependencies": { - "@stellar/stellar-sdk": "^14.1.1", + "@stellar/stellar-sdk": "^14.5.0", "buffer": "6.0.3" }, "devDependencies": { @@ -95,7 +94,7 @@ "app-lib/clients/nft_enumerable_example": { "version": "0.0.0", "dependencies": { - "@stellar/stellar-sdk": "^14.1.1", + "@stellar/stellar-sdk": "^14.5.0", "buffer": "6.0.3" }, "devDependencies": { diff --git a/scaffold.yml b/scaffold.yml index 61cf5b6..ab2ef0e 100644 --- a/scaffold.yml +++ b/scaffold.yml @@ -23,12 +23,19 @@ networks: # Created and funded on first build, the first account will sign deploys accounts: [me] + # Testnet is staging: a dry run for mainnet. Deploy there deliberately, then + # point contracts at those deployments below. Public networks never deploy + # from `build` or `watch` unless you opt in with `allow-deploy: true`. testnet: # Funded with Friendbot on first build. Keep testnet keys separate from # local ones, as you would for mainnet. accounts: [testnet-user] - # Public networks don't deploy unless you opt in - allow-deploy: true + + # To redeploy freely on testnet while developing, add a separate network + # that opts in, and build for it with STELLAR_NETWORK=testnet-dev: + # testnet-dev: + # extends: testnet + # allow-deploy: true # mainnet: # # stellar-cli has no default mainnet RPC; bring your own @@ -43,43 +50,46 @@ networks: # another network skips it and leaves it out of the generated clients. contracts: guess-the-number: - type: workspace - source: guess-the-number # the crate's package name - args: - admin: ${account.me} - # Contract methods called after deploy; they take no arguments. - after-deploy: [reset] networks: local: - testnet: - # Accounts belong to a network, so testnet signs with its own + type: workspace + source: guess-the-number # the crate's package name args: - admin: ${account.testnet-user} - # Don't deploy to mainnet from a dev loop; point at a deployment you - # made and reviewed separately + admin: ${account.me} + # Contract methods called after deploy; they take no arguments. + after-deploy: [reset] + + # Public networks point at a deployment you made and reviewed + # separately, rather than deploying from the dev loop + testnet: + type: contract + source: CAAXJETKPYAATU4HVVQUTE2FFBULNFGZNEOC3MS635U5K3GZLAY2HI4M # replace with your contract ID # mainnet: # type: contract - # source: C... # the deployed contract's ID + # source: C... fungible-allowlist: - type: workspace - source: fungible-allowlist-example - args: - name: ExampleToken - symbol: EXT - admin: ${account.me} - manager: ${account.me} - initial_supply: 1000000000000000000000000 networks: local: + type: workspace + source: fungible-allowlist-example + args: + name: ExampleToken + symbol: EXT + admin: ${account.me} + manager: ${account.me} + initial_supply: 1000000000000000000000000 + testnet: + type: contract + source: CAAXJETKPYAATU4HVVQUTE2FFBULNFGZNEOC3MS635U5K3GZLAY2HI4M # replace with your contract ID nft-enumerable: - type: workspace - source: nft-enumerable-example - args: - uri: https://example.com/nft/ - name: ExampleNFT - symbol: ENFT - owner: ${account.me} networks: local: + type: workspace + source: nft-enumerable-example + args: + uri: https://example.com/nft/ + name: ExampleNFT + symbol: ENFT + owner: ${account.me} diff --git a/templates/react/.env.example b/templates/react/.env.example index 670e438..ce3ec44 100644 --- a/templates/react/.env.example +++ b/templates/react/.env.example @@ -1,29 +1,13 @@ -# The network from scaffold.yml that `stellar scaffold` builds and deploys to. -# Keep the PUBLIC_STELLAR_* values below on the same network. -STELLAR_NETWORK=local +# The `stellar` CLI loads this file, so values here reach `stellar scaffold`. -# Location of the config files for this project for the scaffold stellar CLI. -# Learn more at https://developers.stellar.org/docs/tools/cli/stellar-cli#stellar-config-dir -XDG_CONFIG_HOME=".config" +# Network from scaffold.yml to build for. Defaults to the network set with +# `stellar network use`, then `local`. A value set in your shell wins, e.g. +# STELLAR_NETWORK=testnet npm start +# The frontend follows automatically: it reads the network from the generated +# clients in app-lib/clients. +# STELLAR_NETWORK=testnet -# Prefix with "PUBLIC_" to make available in frontend files -# Which Stellar network to use in the frontend: local, testnet, futurenet, or mainnet -# More on Stellar networks: https://developers.stellar.org/docs/networks +# Secrets referenced from scaffold.yml as ${env.NAME}, e.g. an RPC token: +# MAINNET_RPC_TOKEN= -PUBLIC_STELLAR_NETWORK="LOCAL" -# The Stellar network passphrase, this is local -PUBLIC_STELLAR_NETWORK_PASSPHRASE="Standalone Network ; February 2017" -# The Stellar network RPC URL. this is local -PUBLIC_STELLAR_RPC_URL="http://localhost:8000/rpc" -# The Stellar Horizon URL. this is local -PUBLIC_STELLAR_HORIZON_URL="http://localhost:8000" - -# PUBLIC_STELLAR_NETWORK="TESTNET" -# PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" -# PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org" -# PUBLIC_STELLAR_HORIZON_URL="https://horizon-testnet.stellar.org" - -# PUBLIC_STELLAR_NETWORK="MAINNET" -# PUBLIC_STELLAR_NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015" -# PUBLIC_STELLAR_RPC_URL= -# PUBLIC_STELLAR_HORIZON_URL= +# Prefix with "PUBLIC_" to make your own values available in frontend files. diff --git a/templates/react/CLAUDE.md b/templates/react/CLAUDE.md index 74e5062..4c02206 100644 --- a/templates/react/CLAUDE.md +++ b/templates/react/CLAUDE.md @@ -32,10 +32,10 @@ building Stellar/Soroban smart contract UIs. ### Key flows -**Contract client generation**: `stellar scaffold watch --build-clients` (part -of `npm run dev`) watches for contract changes, compiles Rust contracts to WASM, -deploys them, and generates TypeScript clients into `packages//`. -These packages are npm workspace members and are re-imported as +**Contract client generation**: `stellar scaffold watch` (part of `npm run dev`) +watches for contract changes, compiles Rust contracts to WASM, deploys them, and +generates TypeScript clients into `packages//`. These packages +are npm workspace members and are re-imported as `src/contracts/.ts`. **Stub files**: `src/contracts/*.ts` (except `util.ts`) are stub files that @@ -46,10 +46,13 @@ instance of the contract client and import `rpcUrl`/`networkPassphrase` from **Environment config**: Two separate config systems: -- `.env` (copy from `.env.example`): Runtime env vars for the frontend, prefixed - with `PUBLIC_` to be exposed by Vite - `scaffold.yml`: Stellar Scaffold CLI config defining networks, accounts, and - the contracts deployed to each network + the contracts deployed to each network. The selected network + (`STELLAR_NETWORK`, else `stellar network use`, else `local`) is written into + the generated clients, and `@stellar-scaffold/app-lib` reads the frontend's + network from there +- `.env` (copy from `.env.example`): optional `STELLAR_NETWORK`, secrets + `scaffold.yml` references as `${env.NAME}`, and your own `PUBLIC_` values ### Provider tree (`src/main.tsx`) diff --git a/templates/react/package.json b/templates/react/package.json index a8df491..7366339 100644 --- a/templates/react/package.json +++ b/templates/react/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "dev": "npm start", - "start": "concurrently --kill-others-on-fail --names stellar,vite -c gray,green --pad-prefix \"stellar scaffold watch --build-clients\" \"vite\"", + "start": "concurrently --kill-others-on-fail --names stellar,vite -c gray,green --pad-prefix \"stellar scaffold watch\" \"vite\"", "build": "tsc -b && vite build", "preview": "vite preview", "lint": "eslint .", diff --git a/templates/svelte/.env.example b/templates/svelte/.env.example index 670e438..ce3ec44 100644 --- a/templates/svelte/.env.example +++ b/templates/svelte/.env.example @@ -1,29 +1,13 @@ -# The network from scaffold.yml that `stellar scaffold` builds and deploys to. -# Keep the PUBLIC_STELLAR_* values below on the same network. -STELLAR_NETWORK=local +# The `stellar` CLI loads this file, so values here reach `stellar scaffold`. -# Location of the config files for this project for the scaffold stellar CLI. -# Learn more at https://developers.stellar.org/docs/tools/cli/stellar-cli#stellar-config-dir -XDG_CONFIG_HOME=".config" +# Network from scaffold.yml to build for. Defaults to the network set with +# `stellar network use`, then `local`. A value set in your shell wins, e.g. +# STELLAR_NETWORK=testnet npm start +# The frontend follows automatically: it reads the network from the generated +# clients in app-lib/clients. +# STELLAR_NETWORK=testnet -# Prefix with "PUBLIC_" to make available in frontend files -# Which Stellar network to use in the frontend: local, testnet, futurenet, or mainnet -# More on Stellar networks: https://developers.stellar.org/docs/networks +# Secrets referenced from scaffold.yml as ${env.NAME}, e.g. an RPC token: +# MAINNET_RPC_TOKEN= -PUBLIC_STELLAR_NETWORK="LOCAL" -# The Stellar network passphrase, this is local -PUBLIC_STELLAR_NETWORK_PASSPHRASE="Standalone Network ; February 2017" -# The Stellar network RPC URL. this is local -PUBLIC_STELLAR_RPC_URL="http://localhost:8000/rpc" -# The Stellar Horizon URL. this is local -PUBLIC_STELLAR_HORIZON_URL="http://localhost:8000" - -# PUBLIC_STELLAR_NETWORK="TESTNET" -# PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" -# PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org" -# PUBLIC_STELLAR_HORIZON_URL="https://horizon-testnet.stellar.org" - -# PUBLIC_STELLAR_NETWORK="MAINNET" -# PUBLIC_STELLAR_NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015" -# PUBLIC_STELLAR_RPC_URL= -# PUBLIC_STELLAR_HORIZON_URL= +# Prefix with "PUBLIC_" to make your own values available in frontend files. diff --git a/templates/svelte/CLAUDE.md b/templates/svelte/CLAUDE.md index 2473baa..958bdfd 100644 --- a/templates/svelte/CLAUDE.md +++ b/templates/svelte/CLAUDE.md @@ -35,10 +35,10 @@ for building Stellar/Soroban smart contract UIs. Uses `adapter-static` with ### Key flows -**Contract client generation**: `stellar scaffold watch --build-clients` (part -of `npm run dev`) watches for contract changes, compiles Rust contracts to WASM, -deploys them, and generates TypeScript clients into `packages//`. -These packages are npm workspace members and are re-imported as +**Contract client generation**: `stellar scaffold watch` (part of `npm run dev`) +watches for contract changes, compiles Rust contracts to WASM, deploys them, and +generates TypeScript clients into `packages//`. These packages +are npm workspace members and are re-imported as `src/lib/contracts/.ts`. **Stub files**: `src/lib/contracts/*.ts` (except `util.ts`) are stub files that @@ -49,10 +49,13 @@ instance of the contract client and import `rpcUrl`/`networkPassphrase` from **Environment config**: Two separate config systems: -- `.env` (copy from `.env.example`): Runtime env vars for the frontend, prefixed - with `PUBLIC_` to be exposed by Vite - `scaffold.yml`: Stellar Scaffold CLI config defining networks, accounts, and - the contracts deployed to each network + the contracts deployed to each network. The selected network + (`STELLAR_NETWORK`, else `stellar network use`, else `local`) is written into + the generated clients, and `@stellar-scaffold/app-lib` reads the frontend's + network from there +- `.env` (copy from `.env.example`): optional `STELLAR_NETWORK`, secrets + `scaffold.yml` references as `${env.NAME}`, and your own `PUBLIC_` values ### Important directories diff --git a/templates/svelte/package.json b/templates/svelte/package.json index 7b6b66a..8afddde 100644 --- a/templates/svelte/package.json +++ b/templates/svelte/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "dev": "npm start", - "start": "concurrently --kill-others-on-fail --names stellar,vite -c gray,green --pad-prefix \"stellar scaffold watch --build-clients\" \"vite dev\"", + "start": "concurrently --kill-others-on-fail --names stellar,vite -c gray,green --pad-prefix \"stellar scaffold watch\" \"vite dev\"", "build": "svelte-kit sync && vite build", "preview": "vite preview", "lint": "eslint .",