From 9ecf126514079e2ffd607eab8b469287c6846e06 Mon Sep 17 00:00:00 2001 From: abramdawson <2624720+abramdawson@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:06:25 -0700 Subject: [PATCH 1/2] docs: adopt Simplified Technical English and add review checks --- .github/pull_request_template.md | 9 + .github/workflows/docs.yml | 29 + .github/workflows/ste-review.yml | 50 + CLAUDE.md | 24 +- README.md | 21 +- STE.md | 92 ++ package-lock.json | 11 +- package.json | 20 +- pnpm-lock.yaml | 21 + scripts/check-prose.mjs | 87 +- scripts/ste/check.mjs | 208 +++ scripts/ste/check.test.mjs | 117 ++ scripts/ste/glossary.mjs | 49 + scripts/ste/glossary.test.mjs | 42 + scripts/ste/review.test.mjs | 59 + scripts/ste/terms.json | 1368 +++++++++++++++++ src/pages.gen.ts | 1 + src/pages/accounting/index.mdx | 36 +- src/pages/accounting/spam.mdx | 43 +- src/pages/accounts/earn.mdx | 72 +- src/pages/accounts/editing.mdx | 83 +- src/pages/accounts/index.mdx | 91 +- src/pages/accounts/modules.mdx | 47 +- src/pages/accounts/signers.mdx | 43 +- src/pages/accounts/thresholds.mdx | 26 +- src/pages/banking/index.mdx | 39 +- src/pages/banking/offramping.mdx | 43 +- src/pages/banking/onramping.mdx | 28 +- src/pages/banking/paying-vendors.mdx | 39 +- src/pages/contacts/compliance.mdx | 30 +- src/pages/contacts/index.mdx | 71 +- src/pages/experiments/index.mdx | 10 +- src/pages/experiments/pact.mdx | 54 +- src/pages/index.mdx | 49 +- src/pages/integrations/bankr.mdx | 52 +- src/pages/integrations/clanker.mdx | 26 +- src/pages/integrations/ens.mdx | 51 +- src/pages/integrations/farcaster.mdx | 49 +- src/pages/integrations/hedgey.mdx | 17 +- src/pages/integrations/index.mdx | 43 +- src/pages/integrations/rain.mdx | 33 +- src/pages/integrations/sablier.mdx | 14 +- src/pages/integrations/uniswap.mdx | 10 +- src/pages/integrations/walletconnect.mdx | 31 +- src/pages/introduction/agents.mdx | 136 +- src/pages/introduction/core-concepts.mdx | 27 +- src/pages/introduction/extension.mdx | 35 +- .../introduction/networks-and-assets.mdx | 52 +- src/pages/introduction/personal-usage.mdx | 20 +- src/pages/invoicing/index.mdx | 50 +- src/pages/invoicing/paying.mdx | 28 +- src/pages/invoicing/recurring.mdx | 30 +- src/pages/invoicing/tracking.mdx | 18 +- src/pages/members/index.mdx | 58 +- src/pages/members/keys.mdx | 59 +- src/pages/resources/brand-assets.mdx | 16 +- src/pages/resources/glossary.mdx | 992 ++++++++++++ src/pages/resources/how-we-work.mdx | 80 +- .../incorporating-and-raising-capital.mdx | 62 +- src/pages/resources/security.mdx | 26 +- src/pages/teams/index.mdx | 57 +- src/pages/teams/recovery.mdx | 90 +- src/pages/teams/roles.mdx | 37 +- src/pages/teams/settings.mdx | 38 +- src/pages/transactions/batch.mdx | 27 +- src/pages/transactions/custom.mdx | 33 +- src/pages/transactions/index.mdx | 54 +- src/pages/transactions/memos.mdx | 36 +- src/pages/transactions/schedules.mdx | 18 +- src/pages/transactions/sends.mdx | 42 +- src/pages/transactions/swaps.mdx | 50 +- vocs.config.ts | 1 + 72 files changed, 4596 insertions(+), 914 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/ste-review.yml create mode 100644 STE.md create mode 100644 scripts/ste/check.mjs create mode 100644 scripts/ste/check.test.mjs create mode 100644 scripts/ste/glossary.mjs create mode 100644 scripts/ste/glossary.test.mjs create mode 100644 scripts/ste/review.test.mjs create mode 100644 scripts/ste/terms.json create mode 100644 src/pages/resources/glossary.mdx diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..93b3f1c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ +Describe the documentation change and its source evidence. + +- [ ] I followed [CLAUDE.md](../CLAUDE.md) and [STE.md](../STE.md). +- [ ] I checked new or changed behavior against product source. +- [ ] I defined new technical terms and regenerated the glossary where necessary. +- [ ] `pnpm build` passes, including prose checks and checker tests. +- [ ] I read the rendered pages and their Markdown twins. + +A maintainer must review the current commit against ASD-STE100 Issue 9, then approve with `STE review complete` in the review body. Passing automation alone does not establish compliance. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..3075ee7 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,29 @@ +name: Documentation + +on: + pull_request: + push: + branches: [main] + merge_group: + +permissions: + contents: read + +concurrency: + group: docs-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + docs-checks: + name: Docs checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + cache: pnpm + - run: pnpm install --frozen-lockfile + # Build includes prose checks, glossary consistency, and checker tests. + - run: pnpm build diff --git a/.github/workflows/ste-review.yml b/.github/workflows/ste-review.yml new file mode 100644 index 0000000..bbf5014 --- /dev/null +++ b/.github/workflows/ste-review.yml @@ -0,0 +1,50 @@ +name: STE editorial review + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + pull_request_review: + types: [submitted, edited, dismissed] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ste-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + editorial-review: + name: STE editorial review + runs-on: ubuntu-latest + steps: + # Metadata only: do not check out or execute pull request code here. + - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const { owner, repo } = context.repo; + const pull_number = context.payload.pull_request.number; + const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); + const reviews = await github.paginate(github.rest.pulls.listReviews, { + owner, repo, pull_number, per_page: 100, + }); + const latest = new Map(); + for (const review of reviews.sort((a, b) => a.id - b.id)) { + if (['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state)) { + latest.set(review.user?.login, review); + } + } + for (const review of latest.values()) { + if (review.state !== 'APPROVED' || review.commit_id !== pr.head.sha) continue; + if (review.user?.type !== 'User' || review.user.login === pr.user.login) continue; + if (!/^STE review complete[.!]?\s*$/m.test(review.body ?? '')) continue; + const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, repo, username: review.user.login, + }); + if (['admin', 'maintain', 'write'].includes(data.permission)) { + core.info(`STE review accepted from ${review.user.login} for ${pr.head.sha}.`); + return; + } + } + core.setFailed('A maintainer must approve the current commit with "STE review complete" in the review body. See STE.md.'); diff --git a/CLAUDE.md b/CLAUDE.md index 1bfa31f..530e719 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,13 +4,13 @@ User-facing docs for Splits (app.splits.org), built with Vocs. Pages serve **two ## The standard job: update docs for a product PR -Any agent should be able to take a product PR and update these docs without a human review pass. The checklist: +An agent can prepare a complete update from a product PR. A separate STE editorial review is required before merge. The checklist: 1. Read the PR's **code diff**, not its description, and list each user-visible behavior that changed. 2. Find each fact's canonical page (map below, plus `git grep`). Edit only that page; update other pages' links if the fact moved, never restate it. 3. Verify every claim you write against the code (rules below). PR descriptions and existing docs prose are not sources. 4. If a file or heading moves: grep for the old path and old `#anchor`, retarget every inbound link, and update the sidebar in `vocs.config.ts` (URLs derive from file paths under `src/pages/`). -5. Check your work: `node scripts/check-prose.mjs ` (findings are warnings needing judgment, not automatic failures), `pnpm build` (validates every internal link), and read the `.md` twin (`curl localhost:5173/.md`); the twin is what agents consume. +5. Check your work: `pnpm check:docs` (findings fail), `pnpm build` (validates every internal link), and read the `.md` twin (`curl localhost:5173/.md`); the twin is what agents consume. 6. Never commit without explicit approval from the human in the session. ## Accuracy @@ -31,6 +31,7 @@ Any agent should be able to take a product PR and update these docs without a hu | Fact | Canonical home | | --- | --- | +| Technical term definitions and permitted uses | `scripts/ste/terms.json`, generated as `/resources/glossary` | | Team definition, creating a team, setup steps | `/teams` | | Roles, capability matrix, settings visibility, API key scopes, read-only members | `/teams/roles` | | Recovery, recovery signers, verifying them | `/teams/recovery` | @@ -66,7 +67,7 @@ If a change moves a fact's canonical home, update this table in the same PR. - **"Signing key", not bare "key"**, whenever precision matters (definitions, invariants, table cells). Bare "key" is fine once the page has established context (e.g. within `/members/keys`). A **signer** is always account-relative: a signing key added to an account's signer set. Don't use "signer" for a key that isn't on an account. - **"the Root" / "the Treasury" in prose; bare "Root" / "Treasury" in table cells.** Table cells carry no leading articles and no explanations; explanations live in surrounding prose. - **"Wallet" means an external EOA wallet** (recovery wallets, MetaMask, hardware wallets), never a Splits account. -- **Em dashes: never, anywhere.** List items and definition lists use a colon separator (`` `command`: description ``); in prose, a colon, period, comma, semicolon, or parentheses replaces the em dash. The prose linter flags every em dash. +- **Semicolons and em dashes: never in public prose.** List items and definition lists use a colon separator (`` `command`: description ``); in prose, a colon, period, comma, or parentheses replaces the em dash. The prose linter flags every em dash. - **"Email support"** (no address) is the phrasing for manual/support-gated processes. - **"Team" → "workspace" rename is planned** in the product. Docs keep saying "team" until the product ships the rename, then migrate in one pass (prose + `/teams/` URLs + section name). @@ -75,7 +76,7 @@ If a change moves a fact's canonical home, update this table in the same PR. - **Facts in declarative present tense; procedures in second person** ("you must be an Owner", "go to…"). - **UI elements in italics**: button and control labels (*Invite member*, *Reset signers*, *Require memos*). **Settings paths with `>`**: Settings > Members. **In-page click chains with `→`**: three dots → *Verify signer*. - **Bold** for: the term a page defines (first use), negative invariants, and scope names in command lists (**Read** scope). -- **Callouts**: `:::note` sparingly. Beta features get exactly: "This feature is in beta. Email support to enable it for your team." +- **Callouts**: `:::note` sparingly. For beta features, put "This feature is in beta." in the note. Put "Email support to enable it for your team." after the note. - **Page titles ≤ 2 words** where possible; sidebar labels match titles. - **No screenshots** until there's a system for generating them automatically. **No "Last updated" lines.** - **Cut anything that can be removed without losing meaning.** No welcome fluff, no roadmap promises, no restating what a link target already says. Answer first. @@ -91,8 +92,19 @@ If a change moves a fact's canonical home, update this table in the same PR. ## Programmatic access - Every page whose surface the CLI/MCP covers **ends** with an H2 named exactly "Programmatic access". -- It opens with exactly: `Via the [Splits CLI / MCP](/introduction/agents):` +- It opens with exactly: `Through the [Splits CLI / MCP](/introduction/agents):` - Commands are bullets in the form `` `splits ` ``: description (**Scope** scope). -- If the surface has no CLI coverage and a user might expect it, say so: "X is web-only today." +- If the surface has no CLI coverage and a user might expect it, say so: "X is available only in the app." - **CLI commands appear nowhere else on a page.** Body prose describes the app flow; conceptual links to `/introduction/agents` (e.g. "registered via the CLI") are fine, inline command names are not. - Don't document the full command surface: the CLI is self-describing (`npx @splits/splits-cli@latest --llms`), and `/introduction/agents` owns setup, scopes, and headless signing. + +## Simplified Technical English + +Public prose must follow [STE.md](STE.md), which targets ASD-STE100 Issue 9. This includes titles, subtitles, metadata, tables, link labels, callouts, and image descriptions. Use at most 20 words per sentence and six sentences per paragraph. This sentence limit is stricter than the standard's descriptive limit. + +- Use the official dictionary for general words and their meanings and parts of speech. The local linter is not a full dictionary checker. +- Define technical terms in `scripts/ste/terms.json`, then run `pnpm glossary:generate`. Canonical feature pages own behavior. The glossary owns lexical definitions. +- Use one instruction per sentence in numbered procedures. State conditions first. Keep instructions out of notes. +- Use active voice and simple verb forms. Expand contractions. Preserve literal UI labels, commands, and identifiers. +- Run `pnpm build`. It runs prose checks, glossary consistency checks, and regression tests before the Vocs build. +- A separate maintainer must review the current commit against the official standard and approve with `STE review complete`. An agent must not claim full compliance from a passing linter. diff --git a/README.md b/README.md index ddd672f..3b119d6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ pnpm preview # preview the build ## Rules -The full authoring ruleset lives in [CLAUDE.md](CLAUDE.md). The load-bearing ones: +The authoring rules are in [CLAUDE.md](CLAUDE.md). [STE.md](STE.md) defines the ASD-STE100 Issue 9 writing, terminology, and review process. The load-bearing ones: - **Code is the source of truth.** Verify every behavioral claim against product source before writing it: `0xSplits/splits` (backend), `splits-teams` (web client), `splits-cli`, `splits-connect`, and `splits-contracts-monorepo`. Never extrapolate a product fact; if the code can't answer it, flag it for a human instead of guessing. - **Every fact has exactly one canonical home.** Everywhere else links to it. Restated copies drift independently; that's how doc errors happen. @@ -31,6 +31,21 @@ The full authoring ruleset lives in [CLAUDE.md](CLAUDE.md). The load-bearing one How these docs get updated, by humans or agents: 1. **Verify with subagents.** Fan out read-only agents per feature area against the source repos, requiring file:line evidence and an explicit "cannot verify" for anything the code doesn't answer. -2. **Act as the single final approver.** Re-check any surprising claim yourself in the primary source before writing it. -3. **Check the output.** Run `node scripts/check-prose.mjs ` on touched prose, `pnpm build` for link validation, and read the `.md` twin (`curl localhost:5173/docs/.md`); the twin is what agents consume. Twins, `llms.txt`, and `llms-full.txt` all live under the base path, locally and in production. +2. **Check source evidence.** Re-check surprising claims in the primary source before writing them. A separate maintainer must complete STE editorial review before merge. +3. **Check the output.** Run `pnpm build` for mandatory prose checks, glossary verification, checker tests, and link validation, and read the `.md` twin (`curl localhost:5173/docs/.md`); the twin is what agents consume. Twins, `llms.txt`, and `llms-full.txt` all live under the base path, locally and in production. 4. **Mind the URLs.** The sidebar lives in `vocs.config.ts` and URLs derive from file paths under `src/pages/`, so moving a file means grepping for inbound links first. + +## STE checks + +```sh +pnpm check:docs # scan all public Markdown and MDX +pnpm glossary:generate # update the glossary after term registry changes +pnpm test:prose # test the checker and editorial review gate +pnpm build # run all checks and build the site +``` + +Edit technical terms in [scripts/ste/terms.json](scripts/ste/terms.json). The generated glossary is available in both HTML and Markdown. The checker reports file and line locations and fails on findings. New pages enter the scan automatically. + +The checker covers a defined subset of STE rules. A qualified editorial review must check vocabulary, meanings, grammar, and instructions against the official standard. The reviewer approves the current commit with `STE review complete` in the review body. + +CI provides `Docs checks` and `STE editorial review`. After the initial merge, an administrator must require both checks and one approval in the existing branch ruleset. See [repository enforcement](STE.md#repository-enforcement). Until that setting changes, GitHub can permit a merge with failed checks. diff --git a/STE.md b/STE.md new file mode 100644 index 0000000..a3f81bc --- /dev/null +++ b/STE.md @@ -0,0 +1,92 @@ +# Simplified Technical English + +Splits documentation targets **ASD-STE100 Simplified Technical English (STE), Issue 9, dated January 15, 2025**. ASD is the standards organization. STE is a controlled form of English for technical documentation. The [official standard](https://www.asd-ste100.org/assets/files/ASD-STE100_ISSUE9.pdf) is the authority for its writing rules and general dictionary. The [official downloads page](https://asd-ste100.org/STE_downloads.html) provides access to the standard and later releases. + +This policy covers all public Markdown and MDX under `src/pages`, including new pages, titles, descriptions, tables, callouts, links, and image descriptions. It also covers rendered component text. README and contributor instructions explain the process. They are not product documentation. + +## Terms + +An **approved general word** is a word in the standard's dictionary, used with its specified meaning, part of speech, and form. A **technical noun** names an item in the subject field. A **technical verb** names a permitted technical action. A **project term** is a technical term selected for these docs. Project registration does not approve a general word or an unrelated use of a technical term. + +The [term registry](scripts/ste/terms.json) contains original project definitions, categories, usage restrictions, and links to canonical pages. It supplements the official dictionary. It is not a copy of that dictionary or an independent STE dictionary. Product names, abbreviations, and exact interface labels have separate classifications in the registry. + +To introduce a term: + +1. Check whether an existing term has the intended meaning. +2. Check the standard's dictionary and technical terminology categories. +3. Add an entry with its definition, category, permitted use, and canonical page. For a technical verb, specify its permitted forms. +4. Define or expand the term at first substantive use on its canonical page. Link to that page at first use elsewhere. +5. Run `pnpm glossary:generate`. Do not edit the generated [glossary](src/pages/resources/glossary.mdx) directly. +6. Request terminology review with the content change. + +Ordinary synonyms do not qualify as technical terms merely to pass a check. A noun entry never permits its use as a verb. For example, the account *threshold* is a number of approvals. An API key *scope* is a permission. A *member* is a person, while *Member* names a role. An *account owner* is an onchain account, while *Owner* names a role. The registry and canonical pages keep these meanings separate. + +## Writing and review + +These are working instructions, not a replacement for the standard: + +- Use the dictionary's permitted meanings and parts of speech (section 1). Define necessary technical terminology. +- Keep noun groups short and clear (section 2). +- Prefer active sentences and simple verb forms. Restrict verb forms ending in `-ing` to permitted technical noun uses (section 3). +- Keep one topic per sentence. Supply articles and make references unambiguous (section 4). +- Write procedures as commands, with one instruction per sentence and necessary conditions first. Keep instructions out of notes (section 5). +- Group related descriptions in paragraphs of at most six sentences (section 6). +- State the risk before the protective instruction when a warning is necessary (section 7). +- Use standard punctuation without semicolons. Apply the standard's counting conventions (section 8). +- Review both meaning and technical correctness (section 9). + +The standard permits 20 words per procedural sentence and 25 per descriptive sentence. **This repository uses a stricter limit of 20 for all checked sentences.** This avoids uncertain automatic classification of procedures. Technical identifiers, labels, proper names, measurements, and parenthetical text have special counting treatment. The checker uses conservative approximations. A reviewer must still check unusually structured sentences and parenthetical text. + +Preserve literal commands, flags, addresses, and interface labels. Rewrite their explanations. A code span is for executable or literal technical text, not a way to hide prose from checks. Keep content literal in MDX. Computed prose fails the check because the checker cannot inspect its rendered value. + +## Local checks + +```sh +pnpm install --frozen-lockfile +pnpm glossary:generate # after a term change +pnpm check:docs # all public prose and glossary consistency +node scripts/check-prose.mjs src/pages/accounts # optional focused check +pnpm test:prose # checker and review-gate regression tests +pnpm build # all checks, then production and link validation +``` + +Findings fail the command. There is no warning-only mode, baseline of ignored pages, or inline suppression mechanism. New `.md` and `.mdx` files enter the scan automatically. The build uses the same checks as CI. A term change also invalidates an outdated generated glossary. + +The checker parses Markdown and MDX. It checks sentence and paragraph limits, selected vocabulary and term variants, contractions, semicolons, em dashes, and common verb problems. It reads prose in metadata, table cells, callouts, link labels, and image descriptions. It excludes code, imports, link destinations, and non-prose component attributes. + +**A passing check is not proof of ASD-STE100 compliance.** The checker does not contain the full general dictionary. It cannot reliably determine every part of speech, approved meaning, passive construction, instruction boundary, or factual claim. The [standard's guidance on software](https://asd-ste100.org/STEsoftware.html) explains the role and limits of checkers. + +## Editorial approval + +A maintainer who understands Issue 9 must review each pull request against the official standard, including text generated by an agent. This review is separate from the author's rewrite and the automated check. + +The reviewer must check: + +- General vocabulary, approved meanings, parts of speech, and technical term eligibility. +- One instruction per sentence, conditions before actions, and informational notes. +- Active voice, permitted verb forms, clear noun groups, and unambiguous references. +- Preservation of product behavior, limitations, exact commands, UI labels, and URLs. +- The rendered page and its `.md` twin, including tables, definitions, and warnings. + +After completing this review, the maintainer must **approve the current commit** and include this line in the review body: + +```text +STE review complete +``` + +The `STE editorial review` workflow requires this statement from a human with write, maintain, or admin permission. It rejects the author's own approval, bot approvals, old commit approvals, dismissed reviews, and later requests for changes. A comment alone is not approval. A new commit requires a new approval. The workflow reads GitHub metadata and does not run pull request code. + +Do not approve language that you cannot verify against the standard. Identify the unresolved wording and obtain a qualified review. An automated rewrite must not label itself certified or fully compliant. + +## Repository enforcement + +After these workflows reach `main`, a repository administrator must update the existing **Default branch protection** ruleset: + +1. Require the `Docs checks` and `STE editorial review` status checks. +2. Require one pull request approval and dismiss approvals after new commits. +3. Require approval after the latest push and resolution of review threads. +4. Keep force pushes, branch deletion, and direct changes to `main` restricted. Do not grant routine bypasses. + +The existing ruleset requires pull requests but currently requires zero approvals and no status checks. Workflow files alone cannot change those remote settings. Activate the checks after the initial merge so subsequent pull requests inherit both workflows. This rollout order avoids blocking other open pull requests that do not yet contain the new workflows. The review status can be required on pull requests. If a merge queue is introduced, add a queue-aware editorial check before requiring that status on merge groups. + +When ASD publishes a new issue, review its changes before updating this policy. Change the registry, checker, tests, and affected pages together. Do not silently change the target issue. diff --git a/package-lock.json b/package-lock.json index adec58d..6b1b143 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "react": "^19", "react-dom": "^19", - "vocs": "latest", + "vocs": "2.3.2", "waku": "^1.0.0-beta.3" }, "devDependencies": { @@ -18,8 +18,15 @@ "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^6", + "remark-directive": "4.0.0", + "remark-frontmatter": "5.0.0", + "remark-gfm": "4.0.1", + "remark-mdx": "3.1.1", + "remark-parse": "11.0.0", "typescript": "^5", - "vite": "^8" + "unified": "11.0.5", + "vite": "^8", + "yaml": "2.9.0" } }, "node_modules/@antfu/install-pkg": { diff --git a/package.json b/package.json index 14083c4..6891f99 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,13 @@ "type": "module", "scripts": { "dev": "vocs dev", - "build": "vocs build", + "build": "pnpm check && vocs build", "preview": "vocs preview", - "start": "node dist/serve-node.js" + "start": "node dist/serve-node.js", + "check:docs": "node scripts/ste/glossary.mjs --check && node scripts/check-prose.mjs", + "test:prose": "node --test scripts/ste/*.test.mjs", + "check": "pnpm check:docs && pnpm test:prose", + "glossary:generate": "node scripts/ste/glossary.mjs" }, "dependencies": { "react": "^19", @@ -20,7 +24,15 @@ "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^6", + "remark-directive": "4.0.0", + "remark-frontmatter": "5.0.0", + "remark-gfm": "4.0.1", + "remark-mdx": "3.1.1", + "remark-parse": "11.0.0", "typescript": "^5", - "vite": "^8" - } + "unified": "11.0.5", + "vite": "^8", + "yaml": "2.9.0" + }, + "packageManager": "pnpm@10.28.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf426e0..825df49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,12 +38,33 @@ importers: '@vitejs/plugin-react': specifier: ^6 version: 6.0.3(vite@8.1.3(@types/node@22.20.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0)) + remark-directive: + specifier: 4.0.0 + version: 4.0.0 + remark-frontmatter: + specifier: 5.0.0 + version: 5.0.0 + remark-gfm: + specifier: 4.0.1 + version: 4.0.1 + remark-mdx: + specifier: 3.1.1 + version: 3.1.1 + remark-parse: + specifier: 11.0.0 + version: 11.0.0 typescript: specifier: ^5 version: 5.9.3 + unified: + specifier: 11.0.5 + version: 11.0.5 vite: specifier: ^8 version: 8.1.3(@types/node@22.20.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.0)(yaml@2.9.0) + yaml: + specifier: 2.9.0 + version: 2.9.0 packages: diff --git a/scripts/check-prose.mjs b/scripts/check-prose.mjs index e0d0715..e014d01 100644 --- a/scripts/check-prose.mjs +++ b/scripts/check-prose.mjs @@ -1,85 +1,4 @@ #!/usr/bin/env node -// Prose linter: flags marketing-jargon markers in docs pages. -// Warn-only — findings need judgment (feature names like "just-in-time" are fine). -// The underlying test: every sentence must be falsifiable. If a clause tells the -// reader how to feel about a fact instead of stating the fact, cut it. -// -// Usage: node scripts/check-prose.mjs [path...] (defaults to src/pages) - -import { readFileSync, readdirSync, statSync } from 'node:fs' -import { join } from 'node:path' - -const PATTERNS = [ - // benefit-selling connectives - /\bso you can\b/i, - /\bwhich means you\b/i, - /\bnever (have to )?worry\b/i, - /\ball you (have to|need to) do\b/i, - // emotional / metaphor abstractions in place of mechanisms - /\bfriction(less)?\b/i, - /\bpeace of mind\b/i, - /\bseamless(ly)?\b/i, - /\beffortless(ly)?\b/i, - /\bdelight/i, - /\bsupercharge/i, - /\bunlock(s|ing)?\b/i, - /\bempower/i, - /\bstreamline/i, - /\bgame.chang/i, - // vague quality adjectives (state the mechanism instead) - /\bpowerful\b/i, - /\bflexible\b/i, - /\brobust\b/i, - /\bintuitive\b/i, - /\bmagical?\b/i, - /\bblazing/i, - /\b(best|world).class\b/i, - /\bbattle.tested\b/i, - /\bcutting.edge\b/i, - // hedges and softeners that dilute facts - /\bjust (click|tap|call|toggle|paste)\b/i, - /\bsimply\b/i, - /\beasy|easily\b/i, - /\bquick(ly)? and (easy|simple)/i, - // rhetorical intensifiers - /\bexactly when\b/i, - /\bthe moment (you|when)\b/i, - /, in some real sense,/i, -] - -function walk(dir, files = []) { - for (const e of readdirSync(dir)) { - const p = join(dir, e) - if (statSync(p).isDirectory()) walk(p, files) - else if (p.endsWith('.mdx') || p.endsWith('.md')) files.push(p) - } - return files -} - -// Em dashes: never allowed. Lists/definitions use a colon separator; prose uses a -// colon, period, comma, semicolon, or parentheses. -const EM_DASH = /—/ - -const targets = process.argv.slice(2).length ? process.argv.slice(2) : ['src/pages'] -let count = 0 -for (const target of targets) { - const files = statSync(target).isDirectory() ? walk(target) : [target] - for (const file of files) { - const lines = readFileSync(file, 'utf8').split('\n') - lines.forEach((line, i) => { - for (const re of PATTERNS) { - const m = line.match(re) - if (m) { - console.log(`${file}:${i + 1}: [${m[0]}] ${line.trim().slice(0, 120)}`) - count++ - } - } - if (EM_DASH.test(line)) { - console.log(`${file}:${i + 1}: [em dash] ${line.trim().slice(0, 120)}`) - count++ - } - }) - } -} -console.log(count ? `\n${count} finding(s) — apply judgment; not all are violations.` : 'clean') -process.exit(0) +// Kept as the existing author/agent entry point. Findings now fail the command. +import { run } from './ste/check.mjs' +process.exitCode = run(process.argv.slice(2)) diff --git a/scripts/ste/check.mjs b/scripts/ste/check.mjs new file mode 100644 index 0000000..2ba093d --- /dev/null +++ b/scripts/ste/check.mjs @@ -0,0 +1,208 @@ +import { readFileSync, readdirSync, statSync } from 'node:fs' +import { resolve, relative, extname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { unified } from 'unified' +import remarkParse from 'remark-parse' +import remarkMdx from 'remark-mdx' +import remarkGfm from 'remark-gfm' +import remarkFrontmatter from 'remark-frontmatter' +import remarkDirective from 'remark-directive' +import { parse as parseYaml } from 'yaml' + +export const root = fileURLToPath(new URL('../../', import.meta.url)) +const parser = unified().use(remarkParse).use(remarkMdx).use(remarkGfm).use(remarkFrontmatter).use(remarkDirective) +export const terms = JSON.parse(readFileSync(new URL('./terms.json', import.meta.url), 'utf8')) +const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +const segmenter = new Intl.Segmenter('en-US', { granularity: 'sentence' }) + +// This is a project checker, not the ASD dictionary or a compliance certificate. +// Never make a blanket exemption for a technical term: only exact occurrences +// are protected from alias checks. Their surrounding sentences are still checked. +const replacements = [ + ['utilize', 'use'], ['utilizes', 'uses'], ['utilizing', 'use'], + ['prior to', 'before'], ['subsequent to', 'after'], ['in order to', 'to'], + ['via', 'through'], ['e.g.', 'for example'], ['i.e.', 'that is'], + ['etc.', 'give the complete list or state its limits'], + ['simply', 'remove this word'], ['seamless', 'describe the function'], + ['seamlessly', 'describe the function'], ['effortlessly', 'describe the function'], + ['easy', 'describe the procedure'], ['easily', 'describe the procedure'], + ['powerful', 'describe the capability'], ['robust', 'describe the tested property'], + ['backstop', 'describe the recovery method'], ['under the hood', 'describe the mechanism'], + ['best-in-class', 'describe the measured property'], ['peace of mind', 'describe the protection'], + ['leverage', 'use'], ['leverages', 'uses'], ['click here', 'name the link target'], + ['hit', 'select'], ['pick', 'select'], ['whilst', 'while'], ['amongst', 'among'], + ['labelled', 'labeled'], ['behaviour', 'behavior'], ['colour', 'color'], + ['on-chain', 'onchain'], ['off-chain', 'offchain'], ['cross-chain', 'crosschain'], + ['keypair', 'key pair'], ['stables', 'stablecoins'], ['invoicee', 'payer'], +] +const patterns = replacements.map(([word, hint]) => ({ + re: new RegExp(`(? filesUnder(resolve(target, name))) +} + +function textOf(node, protect = false) { + if (node.type === 'inlineCode' || ['code', 'pre'].includes(node.name)) return protect ? 'CODE' : '' + if (node.type === 'image') return node.alt ?? '' + if (node.type === 'mdxTextExpression') { + const expression = node.data?.estree?.body?.[0]?.expression + return expression?.type === 'Literal' && typeof expression.value === 'string' ? expression.value : '' + } + if (node.type === 'text') return node.value + return (node.children ?? []).map(child => textOf(child, protect)).join('') +} + +export function sentences(text) { + // Periods inside common abbreviations, identifiers, and decimals are not stops. + const normalized = text.replace(/\s+/g, ' ').replace(/\b(?:e\.g\.|i\.e\.|U\.S\.|U\.K\.)/gi, x => x.replaceAll('.', '∙')) + .replace(/(?<=\w)\.(?=\w)/g, '∙') + return [...segmenter.segment(normalized)].map(x => x.segment.trim()).filter(Boolean) +} + +export function wordCount(text) { + let value = text.replace(/\([^()]*\)/g, ' PAREN ') + .replace(/"[^"\n]+"|“[^”\n]+”/g, ' QUOTE ') + for (const entry of terms.filter(t => ['name', 'label', 'abbreviation'].includes(t.kind)).sort((a,b) => b.term.length-a.term.length)) { + value = value.replace(new RegExp(`(? findings.push({file, line: node.position?.start.line ?? 1, rule, message}) + let tree + try { tree = parser.parse(source) } catch (error) { + return [{file, line: error.line ?? 1, rule: 'parse', message: error.reason ?? error.message}] + } + const isPage = extname(file) === '.mdx' + const headings = tree.children.filter(n => n.type === 'heading' && n.depth === 1) + if (isPage && (headings.length !== 1 || !/\[[^\]]+\]$/.test(textOf(headings[0] ?? {})))) { + add(tree, 'page-title', 'Use one H1 with a [subtitle].') + } + const prose = (node, text, limit = 20, count = text) => { + for (const {re, rule, hint} of patterns) { + re.lastIndex = 0 + for (const match of text.matchAll(re)) add(node, rule, `“${match[0]}”: ${hint}`) + } + for (const entry of terms) for (const alias of entry.avoid ?? []) { + if (new RegExp(`(? limit) add(node, 'sentence-length', `${words} words (maximum ${limit}): ${sentence}`) + } + if (node.type === 'paragraph' && sentences(count).length > 6) add(node, 'paragraph-length', 'Split this paragraph: maximum six sentences.') + } + const visit = node => { + if (node.type === 'yaml') { + try { + const data = parseYaml(node.value, {uniqueKeys: true}) + if (isPage && (!data?.title || !data?.description)) add(node, 'metadata', 'Supply title and description.') + for (const key of ['title', 'description']) if (data?.[key]) { + if (typeof data[key] !== 'string') add(node, 'metadata', `${key} must be a string.`) + else prose(node, data[key]) + } + } catch (error) { add(node, 'metadata', error.message) } + return + } + if (['code', 'mdxjsEsm'].includes(node.type) || ['code', 'pre'].includes(node.name)) return + if (['paragraph', 'heading', 'tableCell'].includes(node.type)) { + let count = textOf(node, true) + // Vocs subtitles are separate text from the title. + if (node.type === 'heading') count = count.replace(/\s+\[([^\]]+)\]$/, '. $1') + prose(node, textOf(node), 20, count) + } + if (['mdxJsxFlowElement', 'mdxJsxTextElement'].includes(node.type)) { + for (const attr of node.attributes ?? []) { + if (['alt', 'title', 'aria-label', 'description', 'label', 'text'].includes(attr.name)) { + if (typeof attr.value === 'string') prose(node, attr.value) + else add(node, 'dynamic-prose', `Use literal text for ${attr.name} so it can be checked.`) + } + } + // HTML paragraphs and headings have no mdast paragraph child. + if ((node.children ?? []).some(c => ['text', 'mdxTextExpression'].includes(c.type)) && + !(node.children ?? []).some(c => c.type === 'paragraph')) prose(node, textOf(node), 20, textOf(node, true)) + } + if (['mdxFlowExpression','mdxTextExpression'].includes(node.type)) { + const expression = node.data?.estree?.body?.[0]?.expression + const isComment = !expression && !node.value.replace(/\/\*[\s\S]*?\*\//g, '').trim() + if (node.type === 'mdxFlowExpression' && expression?.type === 'Literal' && typeof expression.value === 'string') { + prose(node, expression.value) + } + if (!isComment && !(expression?.type === 'Literal' && typeof expression.value === 'string')) { + add(node, 'dynamic-prose', 'Keep page prose literal so the checker can read it.') + } + } + for (const child of node.children ?? []) visit(child) + } + visit(tree) + if (isPage && !tree.children.some(n => n.type === 'yaml')) add(tree, 'metadata', 'Supply YAML title and description.') + return findings +} + +export function checkTerms(entries = terms) { + const errors = [] + if (!Array.isArray(entries) || !entries.length) return ['The term registry must be a nonempty array.'] + const seen = new Set() + for (const entry of entries) { + if (!entry || typeof entry !== 'object' || !['term', 'definition', 'home', 'category', 'usage'].every(key => typeof entry[key] === 'string' && entry[key].trim()) || !['noun', 'verb', 'name', 'label', 'abbreviation'].includes(entry.kind)) { + errors.push(`Incomplete term: ${JSON.stringify(entry)}`) + continue + } + if (seen.has(entry.term)) errors.push(`Duplicate term: ${entry.term}`) + seen.add(entry.term) + if (!entry.category || !entry.usage) errors.push(`Term needs category and usage: ${entry.term}`) + if (!entry.home.startsWith('/')) errors.push(`Term needs an internal canonical home: ${entry.term}`) + const path = entry.home.split('#')[0] + if (!['.mdx', '/index.mdx'].some(suffix => { + try { return statSync(resolve(root, `src/pages${path}${suffix}`)).isFile() } catch { return false } + })) errors.push(`Missing canonical home for ${entry.term}: ${entry.home}`) + if (entry.kind === 'verb' && (!Array.isArray(entry.forms) || !entry.forms.length || entry.forms.some(f => typeof f !== 'string' || !f.trim()))) errors.push(`Technical verb needs forms: ${entry.term}`) + if (entry.avoid && (!Array.isArray(entry.avoid) || entry.avoid.some(a => typeof a !== 'string' || !a.trim()))) errors.push(`Invalid term variants: ${entry.term}`) + } + return errors +} + +export function run(targets = []) { + const paths = targets.length ? targets.map(p => resolve(p)) : [resolve(root, 'src/pages')] + let allFiles + try { allFiles = [...new Set(paths.flatMap(filesUnder))] } catch (error) { + console.error(error.message) + return 1 + } + if (!allFiles.length) { console.error('No Markdown or MDX files found.'); return 1 } + const errors = checkTerms() + for (const error of errors) console.error(`scripts/ste/terms.json:1: [term-registry] ${error}`) + if (errors.length) return 1 + let total = 0 + for (const file of allFiles) { + const findings = checkSource(readFileSync(file, 'utf8'), relative(root, file)) + for (const {file: name, line, rule, message} of findings) console.error(`${name}:${line}: [${rule}] ${message}`) + total += findings.length + } + console.log(`${allFiles.length} files checked. ${total} findings. Editorial STE review is also required.`) + return total ? 1 : 0 +} diff --git a/scripts/ste/check.test.mjs b/scripts/ste/check.test.mjs new file mode 100644 index 0000000..62b2d27 --- /dev/null +++ b/scripts/ste/check.test.mjs @@ -0,0 +1,117 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import { mkdtempSync, writeFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { checkSource, sentences, wordCount, root } from './check.mjs' + +const page = body => `---\ntitle: Test\ndescription: Test page\n---\n\n# Test [Test page]\n\n${body}\n` +const rules = body => checkSource(page(body)).map(f => f.rule) +const long = 'The account shows the selected token balance for each active network and each configured account in the current team today only.' + +test('reads visible prose while ignoring commands, URLs, imports, and styles', () => { + assert.deepEqual(rules(`import Thing from 'package;name'\n\nUse [the app](https://example.org/seamlessly?q=can't;a=b).\n\n\`splits --memo "can't; simply"\`\n\n\`\`\`sh\ncan't; simply\n\`\`\`\n\nToken logo`), []) +}) +test('checks inline and reference link labels', () => { + assert.ok(rules('[Simply select](https://example.org).').includes('vocabulary')) + assert.ok(rules('[Simply select][target].\n\n[target]: https://example.org').includes('vocabulary')) +}) +test('checks prose beside inline code', () => { + assert.ok(rules('Run `command;` simply.').includes('vocabulary')) +}) +test('checks sentences across physical line breaks', () => { + const findings = checkSource(page(long.replace('token balance', 'token\nbalance'))) + assert.ok(findings.some(f => f.rule === 'sentence-length' && f.line === 8)) +}) +test('checks numbered and bulleted instructions with the same limit', () => { + assert.ok(rules(`1. ${long}`).includes('sentence-length')) + assert.ok(rules(`- ${long}`).includes('sentence-length')) +}) +test('checks metadata and Vocs subtitles', () => { + assert.ok(checkSource(page('Text.').replace('description: Test page', 'description: "Simply select it."')).some(f => f.rule === 'vocabulary')) + assert.ok(checkSource(page('Text.').replace('# Test [Test page]', `# Test [${long}]`)).some(f => f.rule === 'sentence-length')) +}) +test('checks Markdown table cells', () => { + assert.ok(rules(`| Name | Description |\n| --- | --- |\n| Token | ${long} |`).includes('sentence-length')) +}) +test('checks callouts and block quotes', () => { + assert.ok(rules(':::note\nIt is automatically created.\n:::').includes('passive-voice')) + assert.ok(rules('> You can’t continue.').includes('contraction')) +}) +test('checks image alt text and JSX accessibility labels', () => { + assert.ok(rules('![Simply select](image.png)').includes('vocabulary')) + assert.ok(rules('Simply select').includes('vocabulary')) + assert.ok(rules('').includes('vocabulary')) +}) +test('checks raw JSX prose and literal expressions', () => { + assert.ok(rules('

Simply select the account.

').includes('vocabulary')) + assert.ok(rules('Text {"simply"}.').includes('vocabulary')) + assert.ok(rules('{"simply"}').includes('vocabulary')) +}) +test('rejects unchecked dynamic prose without flagging comments', () => { + assert.ok(rules('{getProse()}').includes('dynamic-prose')) + assert.deepEqual(rules('{/* author comment */}\n\nSelect the account.'), []) + assert.ok(rules('{getAlt()}').includes('dynamic-prose')) +}) +test('checks apostrophes without rejecting possession', () => { + assert.ok(rules("You can't continue.").includes('contraction')) + assert.ok(rules('You can’t continue.').includes('contraction')) + assert.deepEqual(rules("The member's signing key is available."), []) +}) +test('checks decoded punctuation', () => { + assert.ok(rules('Select the account; open its settings.').includes('punctuation')) + assert.ok(rules('Select the account—open its settings.').includes('punctuation')) +}) +test('limits paragraphs to six sentences', () => { + assert.ok(rules('Select the account. '.repeat(7)).includes('paragraph-length')) + assert.deepEqual(rules('Select the account. '.repeat(6)), []) +}) +test('does not split decimals, identifiers, or common abbreviations', () => { + assert.equal(sentences('The fee is 0.25%. The file is config.json.').length, 2) + assert.equal(sentences('The U.S. team has a balance.').length, 1) +}) +test('counts protected labels, quoted text, parentheses, and hyphenated words', () => { + assert.equal(wordCount('Select "Create a team" (the first option).'), 3) + assert.equal(wordCount('Automated Earn has a 3-day timelock.'), 5) + assert.equal(wordCount('Wait 5 minutes.'), 2) +}) +test('does not exempt a sentence because it contains a technical term', () => { + assert.ok(rules('The API key is automatically created.').includes('passive-voice')) +}) +test('enforces page metadata, one H1, and the subtitle', () => { + assert.ok(checkSource('# Page\n\nText.').some(f => f.rule === 'metadata')) + assert.ok(rules('# Second [Title]').includes('page-title')) + assert.ok(checkSource(page('Text.').replace('# Test [Test page]', '# Test')).some(f => f.rule === 'page-title')) +}) +test('fails closed on invalid MDX and invalid YAML', () => { + assert.ok(rules('').includes('parse')) + assert.ok(checkSource(page('Text.').replace('title: Test', 'title: [')).some(f => f.rule === 'metadata')) +}) +test('CLI discovers future Markdown and MDX pages recursively and fails on findings', () => { + const directory = mkdtempSync(join(tmpdir(), 'splits-ste-')) + try { + writeFileSync(join(directory, 'new.mdx'), page('Select the account.')) + let result = spawnSync(process.execPath, ['scripts/check-prose.mjs', directory], {cwd: root, encoding: 'utf8'}) + assert.equal(result.status, 0, result.stderr) + writeFileSync(join(directory, 'future.md'), 'Simply select the account.') + result = spawnSync(process.execPath, ['scripts/check-prose.mjs', directory], {cwd: root, encoding: 'utf8'}) + assert.equal(result.status, 1) + assert.match(result.stderr, /future\.md:1: \[vocabulary\]/) + result = spawnSync(process.execPath, ['scripts/check-prose.mjs', join(directory, 'missing')], {cwd: root, encoding: 'utf8'}) + assert.equal(result.status, 1) + } finally { rmSync(directory, {recursive: true, force: true}) } +}) + +test('checks visible text in custom components and their text attributes', () => { + assert.ok(rules('Simply select the account.').includes('vocabulary')) + assert.ok(rules('').includes('vocabulary')) +}) +test('ignores literal JSX code without exempting nearby prose', () => { + assert.deepEqual(rules('simply;'), []) + assert.ok(rules('Simply use some;code.').includes('vocabulary')) +}) +test('state descriptions and ordinary nouns do not imply complex verbs', () => { + assert.deepEqual(rules('There is nothing to claim. The experiments have limited testing.'), []) + assert.ok(rules('The team has configured an account.').includes('complex-verb')) +}) diff --git a/scripts/ste/glossary.mjs b/scripts/ste/glossary.mjs new file mode 100644 index 0000000..36301e1 --- /dev/null +++ b/scripts/ste/glossary.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env node +import { readFileSync, writeFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { resolve } from 'node:path' +import { parse as parseYaml } from 'yaml' +import { terms, checkTerms } from './check.mjs' + +const output = new URL('../../src/pages/resources/glossary.mdx', import.meta.url) +export function renderGlossary() { + const intro = `--- +title: Glossary +description: Technical terms used in the Splits docs +--- + +# Glossary [Technical terms used in the Splits docs] + +This **glossary** defines technical terms in these docs. Each entry links to the page that describes the related product behavior. + +` + return intro + terms.map(entry => { + const path = entry.home.split('#')[0] + const base = new URL(`../../src/pages${path}`, import.meta.url) + let source + for (const suffix of ['.mdx', '/index.mdx']) { + try { source = readFileSync(fileURLToPath(base) + suffix, 'utf8'); break } catch {} + } + const title = parseYaml(source.match(/^---\n([\s\S]*?)\n---/)[1]).title + return `## ${entry.term}\n\n${entry.definition}\n\n[${title}](${entry.home}).\n` + }).join('\n') +} +export function runGlossary(args = process.argv.slice(2)) { +const errors = checkTerms() +if (errors.length) { + console.error(errors.join('\n')) + process.exitCode = 1 +} else if (args.includes('--check')) { + let current = '' + try { current = readFileSync(output, 'utf8') } catch {} + if (current !== renderGlossary()) { + console.error('Glossary is outdated. Run pnpm glossary:generate.') + process.exitCode = 1 + } else console.log('Glossary matches the term registry.') +} else { + writeFileSync(output, renderGlossary()) + console.log('Generated src/pages/resources/glossary.mdx.') +} + +} +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) runGlossary() diff --git a/scripts/ste/glossary.test.mjs b/scripts/ste/glossary.test.mjs new file mode 100644 index 0000000..7bf4e8a --- /dev/null +++ b/scripts/ste/glossary.test.mjs @@ -0,0 +1,42 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { mkdtempSync, mkdirSync, cpSync, symlinkSync, writeFileSync, readFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { spawnSync } from 'node:child_process' +import { root, checkTerms } from './check.mjs' + +const entry = { + term: 'account', kind: 'noun', category: 'computing', + definition: 'An account holds assets.', home: '/accounts', usage: 'Use as a noun.', +} +test('validates registry structure, definitions, homes, and technical verb forms', () => { + assert.deepEqual(checkTerms([entry]), []) + for (const entries of [[], {}, [null], [entry, entry], [{...entry, definition: ''}], + [{...entry, kind: 'adjective'}], [{...entry, home: '/missing-page'}], + [{...entry, kind: 'verb'}], [{...entry, avoid: 'anything'}]]) { + assert.ok(checkTerms(entries).length > 0) + } +}) +test('glossary check fails for missing, stale, and manually edited output', () => { + const directory = mkdtempSync(join(tmpdir(), 'splits-glossary-')) + try { + mkdirSync(join(directory, 'src/pages/resources'), {recursive:true}) + mkdirSync(join(directory, 'scripts'), {recursive:true}) + cpSync(join(root, 'scripts/ste'), join(directory, 'scripts/ste'), {recursive:true}) + symlinkSync(join(root, 'node_modules'), join(directory, 'node_modules'), 'dir') + const registry = join(directory, 'scripts/ste/terms.json') + writeFileSync(registry, JSON.stringify([entry])) + writeFileSync(join(directory, 'src/pages/accounts.mdx'), '---\ntitle: Accounts\ndescription: Account records\n---\n\n# Accounts [Account records]\n') + const run = (...args) => spawnSync(process.execPath, ['scripts/ste/glossary.mjs', ...args], {cwd: directory, encoding:'utf8'}) + assert.equal(run('--check').status, 1) + assert.equal(run().status, 0) + assert.equal(run('--check').status, 0) + writeFileSync(registry, JSON.stringify([{...entry, definition: 'An account contains assets.'}])) + assert.equal(run('--check').status, 1) + assert.equal(run().status, 0) + const output = join(directory, 'src/pages/resources/glossary.mdx') + writeFileSync(output, readFileSync(output, 'utf8') + '\nManual edit.\n') + assert.equal(run('--check').status, 1) + } finally { rmSync(directory, {recursive:true, force:true}) } +}) diff --git a/scripts/ste/review.test.mjs b/scripts/ste/review.test.mjs new file mode 100644 index 0000000..5738829 --- /dev/null +++ b/scripts/ste/review.test.mjs @@ -0,0 +1,59 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { parse } from 'yaml' + +const workflow = parse(readFileSync(new URL('../../.github/workflows/ste-review.yml', import.meta.url), 'utf8')) +const script = workflow.jobs['editorial-review'].steps[0].with.script +const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor +const review = (overrides = {}) => ({ + id: 1, state: 'APPROVED', commit_id: 'current', body: 'STE review complete', + user: { login: 'reviewer', type: 'User' }, ...overrides, +}) +async function run(reviews, permission = 'write') { + const result = { errors: [], infos: [] } + const github = { + rest: { + pulls: { get: async () => ({data: {head: {sha: 'current'}, user: {login: 'author'}}}), listReviews: Symbol() }, + repos: { getCollaboratorPermissionLevel: async () => ({data: {permission}}) }, + }, + paginate: async () => reviews, + } + await new AsyncFunction('github', 'context', 'core', script)(github, + {repo: {owner: 'org', repo: 'docs'}, payload: {pull_request: {number: 1}}}, + {info: text => result.infos.push(text), setFailed: text => result.errors.push(text)}) + return result +} + +test('accepts explicit approval by a maintainer on the current commit', async () => { + assert.equal((await run([review()])).errors.length, 0) +}) +test('requires a review, approval state, and explicit statement', async () => { + for (const reviews of [[], [review({state: 'COMMENTED'})], [review({body: 'Looks good'})]]) { + assert.equal((await run(reviews)).errors.length, 1) + } +}) +test('rejects old commits, the author, bots, and readers', async () => { + for (const overrides of [ + {commit_id: 'old'}, {user: {login: 'author', type: 'User'}}, + {user: {login: 'review-bot', type: 'Bot'}}, + ]) assert.equal((await run([review(overrides)])).errors.length, 1) + assert.equal((await run([review()], 'read')).errors.length, 1) +}) +test('a later change request or dismissal invalidates approval', async () => { + for (const state of ['CHANGES_REQUESTED', 'DISMISSED']) { + assert.equal((await run([review(), review({id: 2, state})])).errors.length, 1) + } +}) +test('a later comment does not cancel approval', async () => { + assert.equal((await run([review(), review({id: 2, state: 'COMMENTED', body: 'Thanks'})])).errors.length, 0) +}) +test('a later approval can resolve an earlier request for changes', async () => { + assert.equal((await run([review({state: 'CHANGES_REQUESTED'}), review({id: 2})])).errors.length, 0) +}) +test('does not execute PR code or interpolate PR content as JavaScript', () => { + assert.equal(workflow.jobs['editorial-review'].steps.length, 1) + assert.ok(!script.includes('${{')) + assert.equal(workflow.permissions['pull-requests'], 'read') + assert.ok(!workflow.on.pull_request_target) +}) diff --git a/scripts/ste/terms.json b/scripts/ste/terms.json new file mode 100644 index 0000000..837eaf3 --- /dev/null +++ b/scripts/ste/terms.json @@ -0,0 +1,1368 @@ +[ + { + "term": "ABI", + "kind": "abbreviation", + "category": "computing", + "definition": "Application binary interface. The definition of a contract's functions and encoded inputs.", + "home": "/transactions/custom", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "account", + "kind": "noun", + "category": "computing", + "definition": "An address and contract that hold assets for a team.", + "home": "/accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "account owner", + "kind": "noun", + "category": "computing", + "definition": "The onchain account with authority over another account.", + "home": "/accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "accounting", + "kind": "noun", + "category": "finance", + "definition": "The preparation and maintenance of financial records.", + "home": "/accounting", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "ACH", + "kind": "abbreviation", + "category": "finance", + "definition": "Automated Clearing House. A US system for bank transfers.", + "home": "/banking/onramping", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "address", + "kind": "noun", + "category": "computing", + "definition": "An identifier for an account or contract on a blockchain.", + "home": "/contacts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "agent", + "kind": "noun", + "category": "computing", + "definition": "Software that performs tasks on behalf of a person or team.", + "home": "/introduction/agents", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "allocation", + "kind": "noun", + "category": "finance", + "definition": "An amount or share assigned to a recipient.", + "home": "/experiments/pact", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "allowance", + "kind": "noun", + "category": "computing", + "definition": "A limit on the tokens that a spender can transfer.", + "home": "/accounts/modules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "allowlist", + "kind": "noun", + "category": "computing", + "definition": "A list of addresses or items with permission for a function.", + "home": "/accounts/modules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "API", + "kind": "abbreviation", + "category": "computing", + "definition": "Application programming interface. A defined interface through which programs exchange requests and data.", + "home": "/introduction/agents", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "API key", + "kind": "noun", + "category": "computing", + "definition": "A credential that identifies API requests and their permitted scope.", + "home": "/introduction/agents#get-an-api-key", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "API key scope", + "kind": "noun", + "category": "computing", + "definition": "A permission assigned to an API key.", + "home": "/teams/roles#api-key-scopes", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "APY", + "kind": "abbreviation", + "category": "finance", + "definition": "Annual percentage yield. An annual rate that includes the effect of accumulated interest.", + "home": "/accounts/earn", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "archive", + "kind": "verb", + "category": "computing", + "definition": "Remove an account from active app views without deleting its records.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "archive", + "archives", + "archived" + ] + }, + { + "term": "asset", + "kind": "noun", + "category": "finance", + "definition": "An item with value, such as a token.", + "home": "/accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "authenticate", + "kind": "verb", + "category": "computing", + "definition": "Confirm an identity or credential.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "authenticate", + "authenticates", + "authenticated" + ] + }, + { + "term": "Automated Earn", + "kind": "name", + "category": "product", + "definition": "The feature that deposits an account's available USDC into the Earn vault.", + "home": "/accounts/earn#automated-earn", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "automation account", + "kind": "noun", + "category": "computing", + "definition": "An account that applies a configured policy to deposits.", + "home": "/accounts#automation-accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "avatar", + "kind": "noun", + "category": "computing", + "definition": "An image that identifies a member.", + "home": "/members", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "bank account", + "kind": "noun", + "category": "finance", + "definition": "An account at a bank for deposits and payments.", + "home": "/banking", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "banking", + "kind": "noun", + "category": "finance", + "definition": "Services that connect token transfers to bank accounts.", + "home": "/banking", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "batch", + "kind": "noun", + "category": "computing", + "definition": "A group of transactions prepared for execution together.", + "home": "/transactions/batch", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "bearer token", + "kind": "noun", + "category": "computing", + "definition": "A credential sent with a request to authorize access.", + "home": "/introduction/agents#use-the-api", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "blockchain", + "kind": "noun", + "category": "computing", + "definition": "A network record of transactions maintained by participating computers.", + "home": "/introduction/networks-and-assets", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "bonding curve", + "kind": "noun", + "category": "finance", + "definition": "A rule that relates token price to the number of tokens sold.", + "home": "/experiments/pact", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "bridge transfer", + "kind": "noun", + "category": "computing", + "definition": "A transfer that moves value between blockchain networks.", + "home": "/transactions/swaps", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "bug bounty", + "kind": "noun", + "category": "computing", + "definition": "A program that rewards eligible vulnerability reports.", + "home": "/resources/security", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "calldata", + "kind": "noun", + "category": "computing", + "definition": "Encoded input sent to a smart contract function.", + "home": "/transactions/custom", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "cap table", + "kind": "noun", + "category": "finance", + "definition": "A record of holders and their allocated shares or units.", + "home": "/experiments/pact", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "CLI", + "kind": "abbreviation", + "category": "computing", + "definition": "Command-line interface. A program interface that accepts text commands.", + "home": "/introduction/agents", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "cliff", + "kind": "noun", + "category": "finance", + "definition": "The first date when a vesting plan releases tokens.", + "home": "/integrations#positions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "collateral", + "kind": "noun", + "category": "finance", + "definition": "Assets held to secure a loan or payment obligation.", + "home": "/accounts/earn#risks", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "command", + "kind": "noun", + "category": "computing", + "definition": "A text instruction to a program.", + "home": "/introduction/agents", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "compliance", + "kind": "noun", + "category": "finance", + "definition": "The collection of verified payee information and tax forms in Splits.", + "home": "/contacts/compliance", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "configure", + "kind": "verb", + "category": "computing", + "definition": "Set the values that control a software function.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "configure", + "configures", + "configured" + ] + }, + { + "term": "contact", + "kind": "noun", + "category": "computing", + "definition": "A saved name for an external address.", + "home": "/contacts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "contract", + "kind": "noun", + "category": "computing", + "definition": "A program deployed at a blockchain address.", + "home": "/transactions/custom", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "cost basis", + "kind": "noun", + "category": "finance", + "definition": "The acquisition value assigned to an asset for gain or loss calculations.", + "home": "/accounting", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "CSV", + "kind": "abbreviation", + "category": "computing", + "definition": "Comma-separated values. A text file format for rows and fields.", + "home": "/accounting", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "custody address", + "kind": "noun", + "category": "computing", + "definition": "The address that controls a Farcaster account.", + "home": "/integrations/farcaster#recovery-address", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "custom transaction", + "kind": "noun", + "category": "computing", + "definition": "A transaction proposal with specified contract calls.", + "home": "/transactions/custom", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "deploy", + "kind": "verb", + "category": "computing", + "definition": "Create a contract at a blockchain address.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "deploy", + "deploys", + "deployed" + ] + }, + { + "term": "deposit", + "kind": "noun", + "category": "finance", + "definition": "Funds transferred into an account or vault.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "draft proposal", + "kind": "noun", + "category": "computing", + "definition": "A transaction prepared for approval that has not executed.", + "home": "/transactions/schedules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Earn", + "kind": "name", + "category": "product", + "definition": "The Splits feature for stablecoin deposits that produce interest.", + "home": "/accounts/earn", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ENS", + "kind": "abbreviation", + "category": "computing", + "definition": "Ethereum Name Service. A system that associates names with blockchain addresses.", + "home": "/integrations/ens", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "EOA", + "kind": "abbreviation", + "category": "computing", + "definition": "Externally owned account. A blockchain account controlled by a private key.", + "home": "/members/keys#eoas", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ERC-1155", + "kind": "name", + "category": "computing", + "definition": "An Ethereum standard for multiple token types in one contract.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ERC-20", + "kind": "name", + "category": "computing", + "definition": "The Ethereum standard for fungible tokens.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ERC-4626", + "kind": "name", + "category": "computing", + "definition": "The Ethereum standard for tokenized vaults.", + "home": "/accounts/earn", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ERC-721", + "kind": "name", + "category": "computing", + "definition": "An Ethereum standard for non-fungible tokens.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "ETH", + "kind": "abbreviation", + "category": "finance", + "definition": "Ether. The native token of Ethereum.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "EUR", + "kind": "abbreviation", + "category": "finance", + "definition": "The currency code for the euro.", + "home": "/banking", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "EURC", + "kind": "name", + "category": "finance", + "definition": "Circle's euro stablecoin.", + "home": "/banking/paying-vendors", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "execute", + "kind": "verb", + "category": "computing", + "definition": "Perform a transaction or contract call.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "execute", + "executes", + "executed" + ] + }, + { + "term": "executor", + "kind": "noun", + "category": "computing", + "definition": "An address that performs contract calls.", + "home": "/accounts/modules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "experiment", + "kind": "noun", + "category": "product", + "definition": "A prototype product from Splits.", + "home": "/experiments", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "external account", + "kind": "noun", + "category": "computing", + "definition": "An address outside Splits that a team monitors.", + "home": "/accounts#external-accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "external bank account", + "kind": "noun", + "category": "finance", + "definition": "A bank account owned by a vendor or other recipient outside the team.", + "home": "/banking/paying-vendors", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "fiat currency", + "kind": "noun", + "category": "finance", + "definition": "Money issued under a government's authority, such as USD or EUR.", + "home": "/banking", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "gas", + "kind": "noun", + "category": "computing", + "definition": "The measure of work required to execute a blockchain transaction.", + "home": "/transactions#gas-sponsorship", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "gas sponsorship", + "kind": "noun", + "category": "computing", + "definition": "Payment of an account's transaction fees from a team allowance.", + "home": "/transactions#gas-sponsorship", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "IBAN", + "kind": "abbreviation", + "category": "finance", + "definition": "International bank account number. An identifier for a bank account.", + "home": "/banking", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "incorporation", + "kind": "noun", + "category": "legal", + "definition": "The creation of a legal entity.", + "home": "/resources/incorporating-and-raising-capital", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "integration", + "kind": "noun", + "category": "computing", + "definition": "A connection between Splits and another app or protocol.", + "home": "/integrations", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "invoice", + "kind": "noun", + "category": "finance", + "definition": "A request for payment of a specified amount.", + "home": "/invoicing", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "JSON", + "kind": "abbreviation", + "category": "computing", + "definition": "JavaScript Object Notation. A text format for structured data.", + "home": "/introduction/agents#transaction-metadata", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "just-in-time swap", + "kind": "noun", + "category": "computing", + "definition": "A token exchange that supplies the requested token for a send.", + "home": "/transactions/swaps#just-in-time-swaps", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "key pair", + "kind": "noun", + "category": "computing", + "definition": "A related public key and private key.", + "home": "/members/keys", + "usage": "Use as a noun or noun modifier with this meaning.", + "avoid": [ + "keypair" + ] + }, + { + "term": "KYB", + "kind": "abbreviation", + "category": "finance", + "definition": "Know your business. Verification of a business's identity.", + "home": "/banking#verify-an-entity", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "KYC", + "kind": "abbreviation", + "category": "finance", + "definition": "Know your customer. Verification of an individual's identity.", + "home": "/banking#verify-an-entity", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "legal entity", + "kind": "noun", + "category": "legal", + "definition": "A person or organization recognized by law as having rights and obligations.", + "home": "/banking", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "liquidation address", + "kind": "noun", + "category": "finance", + "definition": "A deposit address that a provider uses to convert tokens and pay a bank account.", + "home": "/banking/offramping#use-an-external-provider", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "liquidity", + "kind": "noun", + "category": "finance", + "definition": "Funds available for a trade or withdrawal.", + "home": "/accounts/earn#risks", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "lockup", + "kind": "noun", + "category": "finance", + "definition": "A period during which a position restricts withdrawals.", + "home": "/integrations/hedgey", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "LP", + "kind": "abbreviation", + "category": "finance", + "definition": "Liquidity provider. A participant that supplies assets to a trading pool.", + "home": "/integrations/uniswap", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "MCP", + "kind": "abbreviation", + "category": "computing", + "definition": "Model Context Protocol. A protocol that connects AI tools to data and operations.", + "home": "/introduction/agents#connect-to-ai-tools-mcp", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "member", + "kind": "noun", + "category": "product", + "definition": "A person who belongs to a Splits team.", + "home": "/members", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Member role", + "kind": "noun", + "category": "product", + "definition": "The team role with fewer administration permissions than the Owner role.", + "home": "/teams/roles", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "memo", + "kind": "noun", + "category": "finance", + "definition": "A short note attached to a transaction.", + "home": "/transactions/memos", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Merkle root", + "kind": "noun", + "category": "computing", + "definition": "A hash that represents a tree of data and permits proofs about its contents.", + "home": "/accounts/editing#change-signers-and-thresholds", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "metadata", + "kind": "noun", + "category": "computing", + "definition": "Additional information attached to a record, such as a memo or JSON properties.", + "home": "/introduction/agents#transaction-metadata", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "microdeposit", + "kind": "noun", + "category": "finance", + "definition": "A small test transfer for bank account verification.", + "home": "/banking/onramping#verify-with-microdeposits", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "module", + "kind": "noun", + "category": "computing", + "definition": "An address with permission to execute account calls through the module interface.", + "home": "/accounts/modules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Multisend", + "kind": "name", + "category": "product", + "definition": "The feature that transfers one token to multiple recipients in one transaction.", + "home": "/transactions/sends#multisend", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "multisig", + "kind": "noun", + "category": "computing", + "definition": "An account that uses a configured number of signer approvals for transactions.", + "home": "/accounts/thresholds", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "native token", + "kind": "noun", + "category": "computing", + "definition": "The token that a network uses for transaction fees.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "network", + "kind": "noun", + "category": "computing", + "definition": "A blockchain on which an account can operate.", + "home": "/introduction/networks-and-assets", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "NFT", + "kind": "abbreviation", + "category": "computing", + "definition": "Non-fungible token. A token with an identity separate from other tokens.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "offchain record", + "kind": "noun", + "category": "computing", + "definition": "Information stored outside a blockchain.", + "home": "/members", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "offering", + "kind": "noun", + "category": "finance", + "definition": "The PACT contract that holds units for sale and deposited USDC.", + "home": "/experiments/pact", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "offramp", + "kind": "noun", + "category": "finance", + "definition": "A conversion from tokens to funds in a bank account.", + "home": "/banking/offramping", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "onchain transaction", + "kind": "noun", + "category": "computing", + "definition": "A transaction recorded on a blockchain.", + "home": "/transactions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "onramp", + "kind": "noun", + "category": "finance", + "definition": "A conversion from a bank transfer to tokens.", + "home": "/banking/onramping", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "operating account", + "kind": "noun", + "category": "product", + "definition": "A Splits account for direct transactions with team-selected signers and threshold.", + "home": "/accounts#operating-accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "oracle", + "kind": "noun", + "category": "computing", + "definition": "A service that supplies external data, such as asset prices, to a contract.", + "home": "/accounts/earn#risks", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "output token", + "kind": "noun", + "category": "computing", + "definition": "A unit of text used to measure AI input or output.", + "home": "/introduction/agents#tune-output-for-agents", + "usage": "Use for text measurement. Do not confuse it with a blockchain token." + }, + { + "term": "Owner role", + "kind": "noun", + "category": "product", + "definition": "The team role with account and team administration permissions.", + "home": "/teams/roles", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "PACT", + "kind": "abbreviation", + "category": "product", + "definition": "Purchase Agreement for Community Tokens. A tool for capital collection and public allocation records.", + "home": "/experiments/pact", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "passkey", + "kind": "noun", + "category": "computing", + "definition": "A key pair for authentication or signatures, with its private key held by a device or password manager.", + "home": "/members/keys#passkeys", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "password manager", + "kind": "noun", + "category": "computing", + "definition": "Software that stores credentials, including supported passkeys.", + "home": "/members/keys#password-managers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "pay-by-bank", + "kind": "noun", + "category": "finance", + "definition": "An invoice payment method that uses a bank transfer.", + "home": "/invoicing#pay-by-bank", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "payee", + "kind": "noun", + "category": "finance", + "definition": "The person or business that receives a payment.", + "home": "/contacts/compliance", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "payer", + "kind": "noun", + "category": "finance", + "definition": "The person or business that makes a payment.", + "home": "/invoicing/paying", + "usage": "Use as a noun or noun modifier with this meaning.", + "avoid": [ + "invoicee" + ] + }, + { + "term": "payroll", + "kind": "noun", + "category": "finance", + "definition": "Payments to employees or contractors.", + "home": "/transactions/schedules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "performance fee", + "kind": "noun", + "category": "finance", + "definition": "A charge calculated from investment yield.", + "home": "/accounts/earn#fees", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "personal team", + "kind": "noun", + "category": "product", + "definition": "A Splits team for an individual.", + "home": "/introduction/personal-usage", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "position", + "kind": "noun", + "category": "finance", + "definition": "An account's balance or claim in a protocol.", + "home": "/integrations#positions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "principal", + "kind": "noun", + "category": "finance", + "definition": "The deposited amount before interest or gains.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "private key", + "kind": "noun", + "category": "computing", + "definition": "Secret key material that produces cryptographic signatures.", + "home": "/members/keys", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "private transfer", + "kind": "noun", + "category": "product", + "definition": "A recipient payment through NEAR Confidential Intents.", + "home": "/transactions/sends#private-transfers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "proposal", + "kind": "noun", + "category": "computing", + "definition": "A requested transaction awaiting the account's required approvals.", + "home": "/transactions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "protocol", + "kind": "noun", + "category": "computing", + "definition": "A defined set of rules and contracts for an operation.", + "home": "/integrations", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "public key", + "kind": "noun", + "category": "computing", + "definition": "Key material that lets others verify a signature without the private key.", + "home": "/members/keys", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "query", + "kind": "verb", + "category": "computing", + "definition": "Request stored data through a software interface.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "query", + "queries", + "queried" + ] + }, + { + "term": "quote", + "kind": "noun", + "category": "finance", + "definition": "A provider's proposed exchange amounts and terms.", + "home": "/transactions/swaps#quotes-and-multisigs", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "realized gain", + "kind": "noun", + "category": "finance", + "definition": "The excess of disposal proceeds over the assigned cost basis.", + "home": "/accounting", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "recovery", + "kind": "noun", + "category": "product", + "definition": "The process that restores account control through recovery signers.", + "home": "/teams/recovery", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "recovery signer", + "kind": "noun", + "category": "computing", + "definition": "An EOA in the Root's signer set.", + "home": "/teams/recovery#recovery-signers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "recurring invoice schedule", + "kind": "noun", + "category": "product", + "definition": "A schedule that creates invoices at weekly or monthly intervals.", + "home": "/invoicing/recurring", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "register", + "kind": "verb", + "category": "computing", + "definition": "Add a credential or account record to a system.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "register", + "registers", + "registered" + ] + }, + { + "term": "reset", + "kind": "noun", + "category": "computing", + "definition": "Replacement of an account's signer state through its onchain owner.", + "home": "/accounts/editing#reset-signers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "REST API", + "kind": "noun", + "category": "computing", + "definition": "An API that exposes resources through HTTP requests.", + "home": "/introduction/agents#use-the-api", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "role", + "kind": "noun", + "category": "computing", + "definition": "A set of app permissions assigned to a team membership.", + "home": "/teams/roles", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Root", + "kind": "name", + "category": "product", + "definition": "The account at the top of a team's ownership chain.", + "home": "/accounts#root", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "schedule", + "kind": "noun", + "category": "computing", + "definition": "A stored instruction to create a transaction proposal at an interval.", + "home": "/transactions/schedules", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "schema", + "kind": "noun", + "category": "computing", + "definition": "A description of a data structure and its constraints.", + "home": "/introduction/agents", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "send", + "kind": "noun", + "category": "product", + "definition": "A transfer of tokens from a Splits account to a recipient.", + "home": "/transactions/sends", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "send rule", + "kind": "noun", + "category": "computing", + "definition": "A restriction on permitted tokens and networks for a contact.", + "home": "/contacts#restrictions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "SEPA", + "kind": "abbreviation", + "category": "finance", + "definition": "Single Euro Payments Area. A system for euro bank transfers.", + "home": "/banking/onramping", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "sign", + "kind": "verb", + "category": "computing", + "definition": "Produce a cryptographic signature with a private key.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "sign", + "signs", + "signed" + ] + }, + { + "term": "signature", + "kind": "noun", + "category": "computing", + "definition": "Cryptographic data that proves approval by a private key.", + "home": "/members/keys", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "signer", + "kind": "noun", + "category": "computing", + "definition": "A signing key in a specific account's signer set.", + "home": "/accounts/signers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "signer set", + "kind": "noun", + "category": "computing", + "definition": "The public signing keys with approval authority on an account.", + "home": "/accounts/signers", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "signing key", + "kind": "noun", + "category": "computing", + "definition": "A key that produces signatures for a member or executor.", + "home": "/members/keys", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "slippage tolerance", + "kind": "noun", + "category": "finance", + "definition": "The permitted price change between a swap quote and execution.", + "home": "/transactions/swaps#slippage", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "spam token", + "kind": "noun", + "category": "computing", + "definition": "A token classified as unwanted.", + "home": "/accounting/spam", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Splits Connect", + "kind": "name", + "category": "product", + "definition": "The browser extension that connects Splits accounts to other apps.", + "home": "/introduction/extension", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "stablecoin", + "kind": "noun", + "category": "finance", + "definition": "A token designed to track a reference currency or asset value.", + "home": "/banking", + "usage": "Use as a noun or noun modifier with this meaning.", + "avoid": [ + "stables" + ] + }, + { + "term": "sub-account", + "kind": "noun", + "category": "computing", + "definition": "An operating or automation account owned by the Treasury.", + "home": "/accounts", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "subname", + "kind": "noun", + "category": "computing", + "definition": "An ENS name below another name, such as treasury.splits.eth.", + "home": "/integrations/ens#subnames", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "swap", + "kind": "noun", + "category": "finance", + "definition": "An exchange of one token or network balance for another.", + "home": "/transactions/swaps", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "synchronize", + "kind": "verb", + "category": "computing", + "definition": "Apply matching data to multiple networks or systems.", + "home": "/introduction/agents", + "usage": "Use for this computing action. A past participle can modify a technical noun.", + "forms": [ + "synchronize", + "synchronizes", + "synchronized" + ] + }, + { + "term": "tax lot", + "kind": "noun", + "category": "finance", + "definition": "An asset acquisition record used to calculate gains and losses.", + "home": "/accounting", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "team", + "kind": "noun", + "category": "product", + "definition": "A group of accounts and records for a company, individual, or project.", + "home": "/teams", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "threshold", + "kind": "noun", + "category": "computing", + "definition": "The number of signer approvals required for an account transaction.", + "home": "/accounts/thresholds", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "timelock", + "kind": "noun", + "category": "computing", + "definition": "A mandatory delay before a contract action can execute.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "token", + "kind": "noun", + "category": "computing", + "definition": "An asset represented in a blockchain record.", + "home": "/introduction/networks-and-assets#supported-assets", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "transaction", + "kind": "noun", + "category": "computing", + "definition": "A request to change blockchain state through one or more calls.", + "home": "/transactions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "Treasury", + "kind": "name", + "category": "product", + "definition": "The team's main asset account and owner of its sub-accounts.", + "home": "/accounts#treasury", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "URI", + "kind": "abbreviation", + "category": "computing", + "definition": "Uniform resource identifier. Text that identifies a resource or connection.", + "home": "/integrations/walletconnect", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "USD", + "kind": "abbreviation", + "category": "finance", + "definition": "The currency code for the US dollar.", + "home": "/banking", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "USDC", + "kind": "name", + "category": "finance", + "definition": "Circle's US dollar stablecoin.", + "home": "/banking", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "USDT", + "kind": "name", + "category": "finance", + "definition": "Tether's US dollar stablecoin.", + "home": "/transactions/sends#private-transfers", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "vault", + "kind": "noun", + "category": "finance", + "definition": "A contract that holds deposits and manages an investment position.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "vault share", + "kind": "noun", + "category": "finance", + "definition": "A token that represents a portion of a vault's assets.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "vendor payment", + "kind": "noun", + "category": "finance", + "definition": "A transfer of funds to a vendor's bank account.", + "home": "/banking/paying-vendors", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "vesting", + "kind": "noun", + "category": "finance", + "definition": "The scheduled release of rights to tokens or other assets.", + "home": "/integrations#positions", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "wallet", + "kind": "noun", + "category": "computing", + "definition": "An external EOA wallet, such as a hardware wallet or MetaMask.", + "home": "/members/keys#eoas", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "wei", + "kind": "noun", + "category": "computing", + "definition": "The smallest unit of ETH.", + "home": "/transactions/custom", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "WETH", + "kind": "abbreviation", + "category": "finance", + "definition": "Wrapped ether. An ERC-20 representation of ETH.", + "home": "/transactions/sends#private-transfers", + "usage": "Preserve this spelling. Use only for the named item." + }, + { + "term": "withdrawal", + "kind": "noun", + "category": "finance", + "definition": "Funds removed from an account or position.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + }, + { + "term": "yield", + "kind": "noun", + "category": "finance", + "definition": "The return that a deposited asset produces.", + "home": "/accounts/earn", + "usage": "Use as a noun or noun modifier with this meaning." + } +] diff --git a/src/pages.gen.ts b/src/pages.gen.ts index f6cf0cf..3afbfb9 100644 --- a/src/pages.gen.ts +++ b/src/pages.gen.ts @@ -47,6 +47,7 @@ type Page = | { path: '/members'; render: 'static' } | { path: '/members/keys'; render: 'static' } | { path: '/resources/brand-assets'; render: 'static' } + | { path: '/resources/glossary'; render: 'static' } | { path: '/resources/how-we-work'; render: 'static' } | { path: '/resources/incorporating-and-raising-capital'; render: 'static' } | { path: '/resources/security'; render: 'static' } diff --git a/src/pages/accounting/index.mdx b/src/pages/accounting/index.mdx index 2a95e5e..69c0f04 100644 --- a/src/pages/accounting/index.mdx +++ b/src/pages/accounting/index.mdx @@ -1,25 +1,37 @@ --- -title: Accounting -description: View, filter, and export all of a team's transactions on the Accounting page, and see historical balances over any date range. +title: "Accounting" +description: "View and export transaction records" --- -# Accounting [View, filter, and export all activity across a team's accounts] +# Accounting [View and export transaction records] -The *Accounting* page shows every inbound and outbound transaction across the team's accounts, filterable and exportable to CSV. To annotate transactions for the books, use [memos](/transactions/memos); add them when creating each transaction. +**Accounting** shows inbound and outbound transactions across the team's accounts. It provides filters and CSV exports. -## Filtering +[Memos](/transactions/memos) add information to transaction records. -Filter transactions by memo, date, account(s), direction, and amount (min and max). + -## Exporting +## Filters -The main *Export as CSV* action downloads a transaction-and-tax-lot timeline matching the current account, network, token, and date filters. The export menu also provides: +The page can filter transactions by memo, date, accounts, direction, and minimum or maximum amount. -- **Open tax lots**: lots still open as of the selected period's end date (or now, when there is no end date), matching the account, network, and token filters. -- **Realized gains**: disposals in the selected date range, matching the account filter, with proceeds, cost basis, gain or loss, and holding term. + -Large reports are generated in the background and emailed when ready; keep the page open to also download them automatically when they finish. To control which tokens appear in the books at all, see [Spam & tokens](/accounting/spam). +## Exports + +*Export as CSV* downloads a transaction and tax lot timeline. It uses the current account, network, token, and date filters. + +The export menu also includes: + +- **Open tax lots**: lots open at the selected end date, or now if no end date exists. Account, network, and token filters apply. +- **Realized gains**: disposals within the selected dates and account filter. Fields include proceeds, cost basis, gain or loss, and holding term. + +Splits generates large reports in the background and emails them when ready. An open page also downloads the completed report automatically. + +[Spam & tokens](/accounting/spam) describes token visibility in exports. ## Historical balances -Setting a date range shows the starting balance, ending balance, and net change for that period; combine with the account(s) filter to scope it to specific accounts. The starting and ending balances break down into per-token holdings on that date. +A date range shows the starting balance, ending balance, and net change. The account filter limits these values to selected accounts. + +Starting and ending balances show holdings by token on each date. diff --git a/src/pages/accounting/spam.mdx b/src/pages/accounting/spam.mdx index bdc76b1..ac57db3 100644 --- a/src/pages/accounting/spam.mdx +++ b/src/pages/accounting/spam.mdx @@ -1,27 +1,44 @@ --- -title: Spam & tokens -description: How Splits filters spam tokens by default, how a team hides or shows tokens, and what hiding removes from balances, feeds, and exports. +title: "Spam & tokens" +description: "Control token visibility for the team" --- -# Spam & tokens [Control which tokens the team sees] +# Spam & tokens [Control token visibility for the team] -Splits classifies spam tokens and hides them by default. The team can override the classification in either direction, per token, and the override always wins. Visibility is team-wide: there is no per-member or per-account token visibility. +A **spam token** is a token that Splits classifies as unwanted. Splits hides these tokens by default. -A hidden token disappears everywhere: balances, the transaction feed, the [Accounting page](/accounting), and CSV exports. +A team can change visibility for each token. The team's choice overrides the default classification. **Visibility applies to the entire team.** It cannot differ by member or account. -## Hiding a token +Hidden tokens do not appear in balances, transaction feeds, [Accounting](/accounting), or CSV exports. -From any transaction feed, open the transaction's row menu and select *Report spam*. This hides the token for the whole team, and also feeds Splits' global spam classification. Any member can report spam. NFTs can be reported the same way from an account's balances. + -## Showing a hidden token +## Hide a token -[Settings > Tokens](https://app.splits.org/settings/team/tokens/) (visible to Owners) lists every token held across the team's accounts, including hidden ones, with a toggle per token: on means displayed, off means hidden. Turn a token on if the default filtering hid something real. +Any member can report a token as spam. + +1. Open a transaction's row menu. +2. Select *Report spam*. + +This hides the token for the team and supplies information to Splits' global spam classification. An account's balances provide the same option for NFTs. + + + +## Show a hidden token + +Owners can view [Settings > Tokens](https://app.splits.org/settings/team/tokens/). It lists tokens across the team's accounts, including hidden tokens. + +1. Open *Settings > Tokens*. +2. Find the token. +3. Turn its visibility setting on. + +An active setting shows the token. An inactive setting hides it. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits tokens whitelist`: tokens your team explicitly turned on (**Read** scope) -- `splits tokens blocklist`: hidden tokens: global spam plus your team's own reports (**Read** scope) +- `splits tokens whitelist`: list tokens the team explicitly shows (**Read** scope). +- `splits tokens blocklist`: list hidden tokens from global classification and team reports (**Read** scope). -Changing visibility is web-only today. +Visibility changes are available only in the app. diff --git a/src/pages/accounts/earn.mdx b/src/pages/accounts/earn.mdx index e28d6d3..1b0fc94 100644 --- a/src/pages/accounts/earn.mdx +++ b/src/pages/accounts/earn.mdx @@ -1,61 +1,79 @@ --- -title: Earn -description: "Earn interest on idle stablecoins in a team's accounts: how deposits and positions work, the fee, Automated Earn, legacy positions, and the risks." +title: "Earn" +description: "Deposit stablecoins in a vault to receive interest" --- -# Earn [Earn interest on idle stablecoins in a team's accounts] +# Earn [Deposit stablecoins in a vault to receive interest] -Earn puts an account's idle stablecoins to work in a yield source. Positions have no lockup, can be withdrawn at any time with no queue, compound automatically (there is nothing to claim), and can be spent directly when sending funds. Splits charges a [performance fee](#fees) on the yield a position earns; **there is no fee on deposits, withdrawals, or principal**. +**Earn** deposits stablecoins from an account into a vault that produces yield. A vault position represents the account's deposit and accumulated interest. -Deposits are manual (per account, for a chosen amount) or automatic via [Automated Earn](#automated-earn). +The position has no lockup or withdrawal queue. Withdrawal depends on available liquidity. Interest increases the position value automatically. A send can use funds from the position directly. + +Deposits can be manual or automatic through [Automated Earn](#automated-earn). **Splits charges no fee on deposits, withdrawals, or principal.** [Fees](#fees) describes the charge on yield. ## The current offering -Today Earn supports USDC on Base, deposited into [Morpho](https://morpho.org/) through a vault curated by [Steakhouse Financial](https://www.steakhouse.financial/). More assets and yield sources are planned; email support with requests. +Earn supports USDC on Base. It uses [Morpho](https://morpho.org/) and a vault managed by [Steakhouse Financial](https://www.steakhouse.financial/). -For yield on ETH today, swap into Lido's wstETH, available on Ethereum Mainnet and [most L2s](https://lido.fi/lido-multichain); it accrues value as Lido's validators earn staking rewards. +Lido's [wstETH documentation](https://lido.fi/lido-multichain) describes a separate token for ETH staking rewards and its network support. ## How it works -Deposits flow into **Splits Earn USDC** ([`0x189A…3C8F`](https://basescan.org/address/0x189A1a23F46321a196646314E6a078a404513C8F)), a [Morpho Vault V2](https://docs.morpho.org/learn/concepts/vault-v2) that Splits operates as a [fee wrapper](https://docs.morpho.org/developers/earn/concepts/fee-wrapper). The wrapper holds a single position: [Steakhouse Prime USDC](https://app.morpho.org/base/vault/0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9/steakhouse-prime-usdc), a Morpho vault on Base that lends USDC into Morpho markets chosen by its curator, Steakhouse Financial. The account holds **vault shares** (`splitsUSDC`) representing its portion of the pooled USDC: +Deposits enter **Splits Earn USDC** ([`0x189A…3C8F`](https://basescan.org/address/0x189A1a23F46321a196646314E6a078a404513C8F)). This contract is a [Morpho Vault V2](https://docs.morpho.org/learn/concepts/vault-v2) that Splits operates as a [fee wrapper](https://docs.morpho.org/developers/earn/concepts/fee-wrapper). + +The wrapper holds a position in [Steakhouse Prime USDC](https://app.morpho.org/base/vault/0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9/steakhouse-prime-usdc). That vault lends USDC into Morpho markets selected by Steakhouse Financial. -- **Auto-compounding yield**: interest accrues to the share price, so the position grows without a claim step. -- **One token to track**: each ERC-4626 share maps to a clear amount of USDC; no rebasing tokens. +The account holds **vault shares** (`splitsUSDC`). These ERC-4626 shares represent its portion of the vault's USDC. Interest increases the share price. The account does not need a separate claim transaction. -The APY shown in the app is the wrapper's net APY, after the Splits fee. +The app shows annual percentage yield (APY) after the Splits fee. ### Fees -Splits charges a **20% performance fee** on the yield the vault earns and **no management fee**. The fee is taken inside the vault: as interest accrues, the wrapper issues shares worth 20% of the new yield to Splits' fee account, and the remaining 80% raises the share price for every depositor. Changing the fee or its recipient is an onchain action with a 3-day timelock. +Splits charges a **20% performance fee** on vault yield. **There is no management fee.** + +As interest accumulates, the wrapper issues shares worth 20% of the new yield to Splits' fee account. The other 80% increases the share price for depositors. + +A change to the fee or recipient requires an onchain action with a 3-day timelock. ## Automated Earn -Automated Earn is a per-account toggle that deposits idle balances automatically. Enabling it triggers a check immediately; after that, a check runs every 5 minutes. Each check deposits whenever the account's USDC balance exceeds $1. +**Automated Earn** deposits available balances for an account. The setting starts an immediate check and then checks every 5 minutes. Each check deposits the account's USDC balance if it exceeds $1. + +The setting adds the [Auto Earn Module](/accounts/modules) to the account. This module can move deposits into the vault. -Enabling it adds the [Auto Earn Module](/accounts/modules) (an open-source [contract](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vault-modules/src/AutoEarnModule.sol) [deployed on Base](https://basescan.org/address/0x4A5aCfc49597D1D326221cd159d42817918B9F5f)) to the account, authorizing it to move deposits into the vault. +The [module source](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vault-modules/src/AutoEarnModule.sol) and [Base deployment](https://basescan.org/address/0x4A5aCfc49597D1D326221cd159d42817918B9F5f) are public. ## Legacy positions -Before Morpho, Earn deposited into an [Aave](https://aave.com/) USDC vault on Base ([`0x4EA7…caCc`](https://basescan.org/address/0x4EA71A20e655794051D1eE8b6e4A3269B13ccaCc)). Those positions are labelled *Legacy* in the app and are **withdraw-only**: they keep earning Aave's rate, but accept no new deposits, manual or automated. +Earlier Earn deposits used an [Aave](https://aave.com/) USDC vault on Base ([`0x4EA7…caCc`](https://basescan.org/address/0x4EA71A20e655794051D1eE8b6e4A3269B13ccaCc)). The app labels these positions *Legacy*. -To move them, open the Treasury (or any account holding a legacy position) and choose *Upgrade to Earn 2.0*. This builds one transaction from the Treasury that, for every account in the team: +**Legacy positions accept no new deposits.** They continue to receive Aave interest and permit withdrawals. -1. Withdraws the account's USDC from the Aave vault and deposits it into Splits Earn USDC. -2. Replaces the legacy Auto Earn Module with the current one, if Automated Earn was enabled. +To move legacy positions: -The transaction is approved by the Treasury's signers at its threshold; nothing moves until then. Automated Earn cannot be re-enabled on an account still running the legacy module until the upgrade runs. The Root is not included in the upgrade; withdraw its legacy position manually. +1. Open the Treasury or an account with a legacy position. +2. Select *Upgrade to Earn 2.0*. +3. Review the transaction. +4. Obtain approvals from the Treasury's signers at its threshold. + +The transaction withdraws each account's USDC from Aave and deposits it into Splits Earn USDC. It also replaces each active legacy Auto Earn Module with the current module. + +**The transaction excludes the Root.** Its legacy position needs a manual withdrawal. An account with the legacy module cannot reactivate Automated Earn until the upgrade executes. ## Risks -The current offering is lending, and lending carries risk: +Earn lends funds through contracts. The risks include: -- **Smart-contract risk**: a critical bug in Morpho's markets, the Steakhouse vault, or the Splits wrapper could freeze or drain funds. Each is a separate contract. -- **Bad-debt risk**: each Morpho market has its own collateral and oracle, chosen by the curator. If collateral falls faster than liquidations clear it, the market's lenders, including the vault, absorb the loss. -- **Interest-rate variability**: rates in Morpho markets are variable and can fall to nearly zero if borrowing demand drops. -- **Curator risk**: Steakhouse selects the markets, collateral, and caps the vault lends into, and can change them within the vault's timelocks. Positions are exposed to those choices. -- **Liquidity exhaustion**: withdrawals draw on the USDC that is not lent out. A withdrawal larger than the available liquidity fails until borrowers repay or the curator reallocates. Short delays are possible; there is no lockup or queue. +- **Contract errors**: a bug in Morpho, the Steakhouse vault, or the Splits wrapper can freeze or remove funds. +- **Bad debt**: collateral can lose value faster than the market can liquidate it. Lenders, including the vault, then absorb the loss. +- **Variable interest**: rates can decrease to almost zero when demand for loans decreases. +- **Curator decisions**: Steakhouse selects markets, collateral, and lending limits. It can change these within the vault's timelocks. +- **Insufficient liquidity**: a withdrawal fails if it exceeds available USDC. Repayments or a change to vault allocations can restore liquidity. ## Accounting and taxes -Using Earn does not require you to manually account for vault shares or calculate the yield yourself. [Accounting](/accounting) treats a deposit as a swap of USDC into vault shares and a withdrawal as a swap back. Vault shares are tracked as their own tax lots, and yield is realized as gain on the shares when you withdraw. -Deposits and withdrawals remain in the transaction history and CSV exports. The displayed yield is net of the performance fee. +[Accounting](/accounting) records a deposit as a swap from USDC to vault shares. It records a withdrawal as a swap back to USDC. + +Vault shares have separate tax lots. The app records yield as a realized gain on the shares at withdrawal. + +Transaction history and CSV exports include deposits and withdrawals. Displayed yield excludes the performance fee. diff --git a/src/pages/accounts/editing.mdx b/src/pages/accounts/editing.mdx index fca2f70..571ef30 100644 --- a/src/pages/accounts/editing.mdx +++ b/src/pages/accounts/editing.mdx @@ -1,51 +1,80 @@ --- -title: Editing -description: "How an account changes after creation: renaming, changing signers and thresholds with the account's own signers, and resetting signers through the account's owner." +title: "Editing" +description: "Change account names, signers, and thresholds" --- -# Editing [Change an account's name, signers, or threshold, or reset lost signers] +# Editing [Change account names, signers, and thresholds] -Everything about an account except its address can change after creation. The name is offchain metadata; the signer set and threshold are onchain state with two paths to change them: +An **account edit** changes an account's settings. The name is offchain information. The signer set and threshold are onchain state. -- [**Update**](#changing-signers-and-thresholds): the account's own signers approve the change at the current threshold. The everyday path. -- [**Reset**](#resetting-signers): the account's **owner** replaces the signer set outright, with no approval from the current signers. The path when signers are lost. +There are two ways to change signers: -Editing account settings requires the **Owner** role in all cases. +- [Update](#change-signers-and-thresholds): the account's current signers approve the change at its current threshold. +- [Reset](#reset-signers): the account's onchain owner replaces the signers without their approval. -## Renaming +Prerequisites: you must have the Owner role to edit account settings. -An account's name is offchain and changes with no signatures: edit it from the account's settings. + -## Changing signers and thresholds +## Change the name -An account's signer set and threshold are stored onchain as a single state and edited through a single flow: from the account's settings, edit the signers or the threshold and save, signing an "Update signers state" transaction. One change can add or remove signers, change the threshold, or both; swapping a signer while keeping the threshold is one transaction. The change is approved by the account's current signers at its *current* threshold, the flow is the same for the Treasury and operating accounts, and any [team member](/members) with a [signing key](/members/keys) can be added as a signer. +1. Open the account's settings. +2. Edit the account name. -One signing session covers every network: the signers sign the merkle root of a tree containing one copy of the update transaction per active network, and each network's copy executes with a proof against that root. Signer state can't diverge across networks because no network is ever signed for separately. +A name change does not require signatures. -If the account no longer has enough active signers to meet its threshold, this flow is unavailable (the app says so on the account's settings page) and a [reset](#resetting-signers) is the way forward. + -## Resetting signers +## Change signers and thresholds -A reset replaces an account's signers and threshold **without the current signers' approval**. It works because accounts form an [ownership chain](/accounts): an account's owner can execute from it directly, including rewriting its signer set. Who signs depends on the account being reset: +The signer set and threshold form one onchain state. One transaction can change either or both. The procedure is the same for the Treasury and operating accounts. -| Account being reset | Its owner | Who signs the reset | +A [team member](/members) needs a [signing key](/members/keys) before that signing key can become an account signer. + +1. Open the account's settings. +2. Edit the signers or threshold. +3. Save the change. +4. Sign the *Update signers state* transaction. + +The account's current signers approve the change at its current threshold. + +One signature covers all active networks through a Merkle root. This root represents a tree with one update transaction for each active network. Each network executes its update with a proof against that root. + +If available signers cannot meet the threshold, the app prevents an update. A reset provides the other change method. + + + +## Reset signers + +A **reset** replaces an account's signers and threshold **without approval from its current signers**. The [ownership chain](/accounts) gives the onchain owner this authority. + +| Account | Onchain owner | Reset approval | | --- | --- | --- | -| Operating or automation account | Treasury (shown as *Primary*) | Treasury signers, at the Treasury's threshold | -| Treasury | Root | [Recovery signers](/teams/recovery#recovery-signers), connected as wallets, at the recovery threshold | +| Operating or automation | Treasury | Treasury signers and threshold | +| Treasury | Root | Recovery signers and threshold | + +The app can show the Treasury as *Primary*. [Recovery](/teams/recovery#recovery-signers) describes the recovery wallets. + +**The Root has no owner above it and cannot be reset through this procedure.** -The Root itself can't be reset: it has no owner above it and is controlled only by the recovery signers. +1. Open the account's settings. +2. Select *Reset signers*. +3. Select the new signers. +4. Set the new threshold. +5. Save the change. +6. For a sub-account reset, obtain the Treasury signers' approvals. +7. For a Treasury reset, connect the recovery wallets. +8. For a Treasury reset, sign with those wallets. -1. Open the account's settings and find **Reset signers**. ([Settings > Recovery](https://app.splits.org/settings/team/recovery/) also lists every account; clicking one lands on the same section.) -2. Select *Reset signers*, choose the account's new signers and threshold, and save. -3. Approve: for a sub-account, the Treasury's signers sign; for the Treasury, connect the recovery wallet(s) and sign. +[Settings > Recovery](https://app.splits.org/settings/team/recovery/) also lists the accounts and links to their reset settings. -If every passkey on the team is lost, reset the Treasury first (recovery wallets), then use the restored Treasury to reset any sub-accounts. See [Recovery](/teams/recovery) for the full lost-passkey scenario. +If the team's normal signing keys are unavailable, reset the Treasury first. The restored Treasury can then reset sub-accounts. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits accounts update-signers
--threshold N ...`: propose signer/threshold changes; approval happens in the web UI (**Owner** scope) -- `splits accounts rename
`: rename an account (**Owner** scope) +- `splits accounts update-signers
--threshold N ...`: propose signer or threshold changes for approval in the app (**Owner** scope). +- `splits accounts rename
`: rename an account (**Owner** scope). -Resets are web-only today. +Resets are available only in the app. diff --git a/src/pages/accounts/index.mdx b/src/pages/accounts/index.mdx index b2359b3..e8cf618 100644 --- a/src/pages/accounts/index.mdx +++ b/src/pages/accounts/index.mdx @@ -1,66 +1,97 @@ --- -title: Accounts -description: "Accounts in Splits: the four account types, the onchain ownership chain, and thresholds." +title: "Accounts" +description: "Asset storage, account types, and ownership" --- -# Accounts [Where assets live: account types, ownership, and thresholds] +# Accounts [Asset storage, account types, and ownership] -An account is where assets are stored within a team. Every account exists at the same address on every network it's active on, has a [threshold](/accounts/thresholds) of required approvals, and is controlled by [signers](/accounts/signers), plus, optionally, [modules](/accounts/modules). +An **account** holds assets within a [team](/teams). It has the same address on each active network. -| Type | Controlled by | Owned by (onchain) | +[Signers](/accounts/signers) approve transactions at the account's [threshold](/accounts/thresholds). An account can also have [modules](/accounts/modules). + +| Type | Control | Onchain owner | | --- | --- | --- | -| **Root** | Recovery signers (EOAs) | Itself | -| **Treasury** | Owners' passkeys | Root | -| **Operating** | Signers the team configures | Treasury | -| **Automation** | Owners' passkeys + a Splits server key | Treasury | +| Root | Recovery signers (EOAs) | Root | +| Treasury | Initial signers: Owners' passkeys | Root | +| Operating | Configured signers | Treasury | +| Automation | Owners' passkeys and a Splits server key | Treasury | + +The ownership chain is Root → Treasury → operating and automation accounts. -Accounts form an ownership chain (Root → Treasury → operating and automation accounts) in which each account is the onchain owner of those below it. +An account's signers can change its signer set and threshold. **Only the account's onchain owner can upgrade its contract or transfer its ownership.** The owner can also execute transactions directly from the account. This authority permits a [signer reset](/accounts/editing#reset-signers). -Ownership carries the powers signers don't have. An account's signers can change the account's own signer set and threshold, but **only its owner can upgrade the account's contract implementation or transfer its ownership**, and the owner can execute from the account directly, which is how a [signer reset](/accounts/editing#resetting-signers) and [recovery](/teams/recovery) work. A rogue signer on a low-threshold account can therefore reach at most that account's balance, never its ownership or code. The Root has no external owner and owns itself: owner actions on it fall to its own signers, the recovery signers, at the recovery threshold. +The Root owns itself. Its recovery signers approve owner actions at the recovery threshold. ## Root -The Root is created from the team's recovery signers during [team setup](/teams), with the recovery threshold. It never appears in the app: it can't be viewed or transacted from. It exists so the recovery signers can regain control of everything below it if the team's passkeys are lost, and its composition determines every address derived beneath it, which is why changing recovery signers changes the team's account addresses ([more](/teams/recovery#changing-recovery-signers)). +Team setup creates the **Root** from the recovery configuration. The app does not show the Root as an account for normal transactions. + +The Root permits [recovery](/teams/recovery) when normal signing keys are unavailable. [Changes to recovery signers](/teams/recovery#change-recovery-signers) affect account addresses. ## Treasury -The Treasury is created automatically during team setup and is intended to hold the bulk of the team's assets. Its initial signers are the owners' passkeys, with the threshold chosen at setup; both can be [changed later](/accounts/editing). It can't be archived. +Team setup creates the **Treasury** to hold the team's main asset balance. Its initial signers are the Owners' passkeys. Setup also sets its threshold. [Editing](/accounts/editing) describes later changes. + +**The Treasury cannot be archived.** It owns the operating and automation accounts. -Keeping the bulk in the Treasury and transacting from operating accounts follows the [Three Address Protocol](https://x.com/punk6529/status/1701623475725533524): high-value assets stay separate from day-to-day money, so one compromised account doesn't endanger the others. +The [Three Address Protocol](https://x.com/punk6529/status/1701623475725533524) describes separation of asset storage from frequent transactions. ## Operating accounts -Operating accounts are general-purpose accounts a team transacts from directly, with signers and a threshold of its choosing, both editable later (see [Editing](/accounts/editing)). Use them in the app, in third-party apps via the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect), and programmatically via the [CLI](/introduction/agents). +An **operating account** is an account for direct transactions. The team selects its signers and threshold. -Most active teams run many: each account acts as a labeled sub-ledger for a specific program, revenue source, campaign, partner, asset flow, or temporary operation. Common patterns: +Operating accounts work in the app, through the [browser extension](/introduction/extension), through [WalletConnect](/integrations/walletconnect), and through the [CLI](/introduction/agents). -- A dedicated account per project, revenue source, campaign, or partner, isolated for accounting and archived when done -- Separate accounts per asset type (stables, investments, NFTs) -- A low-threshold (e.g. 1-of-n) account for frequent operations like swapping, funded from the higher-threshold Treasury +Accounts can separate funds by project, income source, partner, or asset type. An account with a lower threshold can hold a limited balance for frequent transactions. -For more patterns, see [Personal usage](/introduction/personal-usage) and [suggested thresholds by team size](/accounts/thresholds#choosing-a-threshold). +[Personal usage](/introduction/personal-usage) gives examples for individuals. [Thresholds](/accounts/thresholds#threshold-examples) gives examples by team size. ## Automation accounts -An automation account is an automated swap-and-sweep: a deposit address for a revenue stream, with a policy that processes whatever lands there. Every 10 minutes, an automatic run starts when any one token balance reaches $5. Once it starts, every token balance worth at least $1 is split across the policy's destinations: swapped or bridged where the output token or network differs, forwarded as-is where it doesn't, or offramped to fiat when the destination is a [connected bank account](/banking/offramping). *Trigger* on its page runs the policy immediately and processes every token balance worth at least $1. +An **automation account** applies a configured distribution policy to deposits. A policy specifies destinations and amounts or proportions. + +Every 10 minutes, Splits checks token balances. An automatic run starts if any token balance reaches $5. The run processes each token balance worth at least $1. + +The policy can: + +- Exchange tokens when the destination token differs. +- Transfer tokens across networks when the destination network differs. +- Forward tokens without conversion when both match. +- Convert tokens to bank currency for a [connected bank account](/banking/offramping). -Runs need no approval (the signers are the Treasury's passkeys plus a Splits server key, at 1-of-n), Splits charges no fees, and automations run on every active network. Typical policies: sweep everything to USDC, withhold a share for taxes, or split income across sub-accounts, partners, and the bank. The policy can be edited anytime (*Edit rules* on the account's page); saving the change requires an onchain signature. +*Trigger* on the account page starts a run immediately. It processes each token balance worth at least $1. + +Automation accounts use the Treasury's passkeys and a Splits server key at a 1-of-n threshold. **Runs do not need separate signer approval.** Splits charges no automation fee. Automations run on each active network. + +Example policies convert income to USDC, reserve a share for taxes, or divide income among accounts and partners. + +To change a policy: + +1. Open the account page. +2. Select *Edit rules*. +3. Change the policy. +4. Save the policy. +5. Sign the onchain change. ## External accounts :::note -This feature is in beta. Email support to enable it for your team. +This feature is in beta. ::: -An external account is an address outside Splits that the team watches: its name and token balances appear alongside the team's accounts. Watching is read-only. **An external account grants no signing authority or custody**; Splits can never move its funds. Use one to keep cold storage, a Safe, or a partner's address visible next to the books they relate to. +Email support to enable it for your team. + +An **external account** is an address outside Splits that the team monitors. The app shows its name and token balances beside the team's accounts. + +**Monitoring gives no signing authority or custody.** Splits cannot move the external account's funds. -Watching is also the only way to bring an existing address in: **existing smart accounts (e.g. a Safe) can't be imported into Splits.** Passkey signing, identical addresses and synced signers across networks, and the [ownership chain](#root) that makes [recovery](/teams/recovery) work are properties of the Splits account contract, and don't exist on external contracts. Teams with an existing Safe typically keep it alongside Splits, run day-to-day operations from Splits, and watch the Safe as an external account. +**Existing smart accounts, such as a Safe, cannot be imported into Splits.** Their contracts do not provide the Splits account's ownership and recovery functions. A team can monitor a Safe as an external account and use Splits accounts for separate transactions. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits accounts list`: all accounts in the team (**Read** scope) -- `splits accounts create --name "Name" --threshold 2`: create an operating account (**Owner** scope) -- `splits accounts rename
` / `archive` / `unarchive`: manage accounts (**Owner** scope) -- `splits automations list`: the team's automations (**Read** scope) +- `splits accounts list`: list team accounts (**Read** scope). +- `splits accounts create --name "Name" --threshold 2`: create an operating account (**Owner** scope). +- `splits accounts rename
` / `archive` / `unarchive`: manage accounts (**Owner** scope). +- `splits automations list`: list team automations (**Read** scope). diff --git a/src/pages/accounts/modules.mdx b/src/pages/accounts/modules.mdx index b00be5c..dba6391 100644 --- a/src/pages/accounts/modules.mdx +++ b/src/pages/accounts/modules.mdx @@ -1,30 +1,47 @@ --- -title: Modules -description: Modules are contracts or keys allowlisted on an account that can execute transactions from it without signer approvals. +title: "Modules" +description: "Addresses that can execute account transactions without signer approvals" --- -# Modules [Allowlisted contracts and keys that can transact from an account without signatures] +# Modules [Addresses that can execute account transactions without signer approvals] -A module is an address on an account's module allowlist. An enabled module can execute any call from the account via [`executeFromModule`](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vaults/src/utils/ModuleManager.sol#L130-L160), **without signer approvals and regardless of the account's [threshold](/accounts/thresholds)**. Modules are the second control path on an account, alongside [signers](/accounts/signers). +A **module** is an address in an account's module allowlist. It can execute calls through [`executeFromModule`](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vaults/src/utils/ModuleManager.sol#L130-L160). -A module has full access to the account. Only enable addresses you trust completely, use contracts with strictly limited execution privileges (see [Auto Earn Module](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vault-modules/src/AutoEarnModule.sol)), and prefer a dedicated [operating account](/accounts#operating-accounts) over the Treasury. +**Module execution does not require signer approvals or the account's [threshold](/accounts/thresholds).** Modules provide authority separate from [signers](/accounts/signers). + +A module has full access to the account. An incorrect or compromised module can cause loss of funds. + +1. Use a dedicated [operating account](/accounts#operating-accounts) with a limited balance. +2. Enable only addresses that you trust. +3. For a contract module, check the restrictions in its code. ## When to use -**Automating custom transactions.** Enable a key you control (an "Executor": a server or agent EOA) as a module. The Executor can then execute any call from the account, including calls to permissioned contracts that check `msg.sender` (e.g. withdrawing LP fees), while everything the account receives stays managed in Splits. Secure the Executor key commensurate with the funds it can reach, both in the account and in any contract the account has privileges on. +**Custom transaction automation** can use an EOA as a module. This EOA is the executor. It can call contracts that check `msg.sender`, such as contracts with restricted fee withdrawals. + +The executor's private key needs protection for all funds and contract permissions that it can access. + +**Token transfer automation** can use an ERC-20 allowance. This limits access to one token and an amount. + +1. Through a [custom transaction](/transactions/custom), call `approve(spender, allowance)` on the token contract. +2. Use the spender's signing key to call `transferFrom`. -**Automating token transfers only: don't use a module.** Grant an ERC-20 approval instead: call `approve(spender, allowance)` on the token contract via a [custom transaction](/transactions/custom), then have the spender key call `transferFrom`. An approval is scoped to one token up to an allowance; a module can do anything. +**Automated Earn** enables the [Auto Earn Module](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vault-modules/src/AutoEarnModule.sol). [Earn](/accounts/earn#automated-earn) describes that feature. -**Earning interest.** The [Automated Earn](/accounts/earn#automated-earn) toggle enables Splits' open-source [Auto Earn Module](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/smart-vault-modules/src/AutoEarnModule.sol) on the account; no manual setup. +[Automation accounts](/accounts#automation-accounts) apply deposit distribution policies. -**Deposit-address automation** (split/swap/forward incoming tokens): use an [automation account](/accounts#automation-accounts), not a module. + -## Enabling and disabling +## Enable or disable a module -Enabling or disabling a module is an onchain transaction, approved by the account's signers at its current threshold: +A module change is an onchain transaction. The account's signers approve it at the current threshold. -1. On the account's page, open [Custom transactions](/transactions/custom) and paste **the account's own address** as the contract address (a self-call). -2. Select `enableModule` (or `disableModule`) and paste the module's address. -3. Review, submit, and sign. +1. Open *Custom transactions* from the account page. +2. Enter **the account's own address** as the contract address. +3. Select `enableModule` or `disableModule`. +4. Enter the module address. +5. Review the transaction. +6. Submit the transaction. +7. Sign the transaction. -Every module action is logged onchain: enabling, disabling, and each executed call emit events, and executed calls appear in the transaction feed. +Module changes and executed calls emit onchain events. Executed calls also appear in the transaction feed. diff --git a/src/pages/accounts/signers.mdx b/src/pages/accounts/signers.mdx index d9e5302..374fd1a 100644 --- a/src/pages/accounts/signers.mdx +++ b/src/pages/accounts/signers.mdx @@ -1,39 +1,40 @@ --- -title: Signers -description: "Signers are the keys in an account's signer set that approve its transactions: how signing authority relates to membership and roles, and where signers are managed." +title: "Signers" +description: "Signing keys with authority on a specific account" --- -# Signers [The keys in an account's signer set that approve its transactions] +# Signers [Signing keys with authority on a specific account] -A signer is a [signing key](/members/keys) (a passkey or EOA belonging to a [member](/members)) that has been added to an [account](/accounts)'s signer set. Approvals are counted against the account's [threshold](/accounts/thresholds): a transaction executes once enough signers have signed. The signer set stores public keys onchain; private keys stay with the members. +A **signer** is a [signing key](/members/keys) in an [account](/accounts)'s signer set. The account counts its approval toward the [threshold](/accounts/thresholds). -Managing signers is an onchain edit to one account's signer set (see [Editing](/accounts/editing)), and is separate from managing a member's [signing keys](/members/keys) in personal settings: adding or removing a key changes no account's signer set, and removing a signer from an account doesn't delete the member's key. +The signer set stores public keys onchain. Members keep their private keys. + +An account signer change is an onchain transaction. [Editing](/accounts/editing) describes this procedure. + +Personal signing key settings and account signer settings are separate. **A personal signing key change does not change an account's signer set.** Removal of an account signer does not delete the member's signing key. ## Signers vs membership -Signing authority is separate from [membership](/members) and [roles](/teams/roles). The two often overlap 1:1, but are distinct: +[Membership](/members) and [roles](/teams/roles) control access to the app. Signers control account transaction approvals. -| | Team member | Signer | +| Property | Team membership | Account signer | | --- | --- | --- | -| Stored | Offchain | Onchain (account's signer set) | -| Scoped to | A team | An account | -| What it is | A person's association with a team | A signing key belonging to a member | -| Grants | Visibility + [role](/teams/roles) capabilities | Authority to approve transactions, counted against the account's threshold | -| Managed in | [Settings > Members](https://app.splits.org/settings/team/members/) | Account settings > Signers ([how](/accounts/editing#changing-signers-and-thresholds)) | - -Two rules connect them: +| Storage | Offchain | Onchain | +| Scope | Team | Account | +| Record | Person's membership | Signing key | +| Authority | App access under a role | Transaction approval | +| Settings | Settings > Members | Account settings > Signers | -1. **Every signer on a team's accounts must be a member of that team.** -2. **Neither membership nor any role ever makes someone a signer.** Adding a signer is a separate, onchain action. +Normal account signers belong to team members. **Membership and roles do not make a signing key an account signer.** Addition of a signer requires a separate onchain change. ## Recovery signers -The [Root account](/accounts#root)'s signers are the team's recovery signers: EOAs chosen at team setup that can reset every other account's signers. See [Recovery](/teams/recovery#recovery-signers). +The Root uses recovery signers selected at team setup. These are EOAs with a separate purpose from normal member signing keys. [Recovery](/teams/recovery#recovery-signers) describes their authority. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits accounts signers
`: an account's signers and threshold (**Read** scope) -- `splits accounts update-signers
...`: propose signer/threshold changes; approval happens in the web UI (**Owner** scope) -- `splits members signers `: a member's passkey IDs, for adding them as a signer (**Read** scope) +- `splits accounts signers
`: show account signers and threshold (**Read** scope). +- `splits accounts update-signers
...`: propose signer or threshold changes for approval in the app (**Owner** scope). +- `splits members signers `: list a member's passkey IDs (**Read** scope). diff --git a/src/pages/accounts/thresholds.mdx b/src/pages/accounts/thresholds.mdx index daed09a..03aab1f 100644 --- a/src/pages/accounts/thresholds.mdx +++ b/src/pages/accounts/thresholds.mdx @@ -1,21 +1,29 @@ --- -title: Thresholds -description: "Every account requires M-of-N signer approvals per transaction: what thresholds are and how to choose one." +title: "Thresholds" +description: "The number of signer approvals for a transaction" --- -# Thresholds [The number of signer approvals an account requires per transaction] +# Thresholds [The number of signer approvals for a transaction] -Every [account](/accounts) has a threshold: the number of [signer](/accounts/signers) approvals required for an outgoing transaction, written M-of-N (2-of-3 means any two of the account's three signers). A transaction executes once it reaches M signatures; below that it stays pending. Each account's threshold is set independently: at [team setup](/teams) for the Treasury, at creation for operating accounts. To change an account's threshold or signers, see [Editing](/accounts/editing). +A **threshold** is the number of [signer](/accounts/signers) approvals an [account](/accounts) requires for a transaction. -## Choosing a threshold +The notation is M-of-N. For example, 2-of-3 requires any two of three signers. A transaction stays pending until it has M signatures. -During team setup, the app recommends a majority of owners for the Treasury. Suggested thresholds by number of Owners: +Each account has its own threshold. Team setup sets the Treasury's initial threshold. Account creation sets an operating account's initial threshold. [Editing](/accounts/editing) describes later changes. -| Owners | Treasury | [Recovery](/teams/recovery) | + + +## Threshold examples + +During team setup, the app suggests a majority of Owners for the Treasury. + +| Owners | Treasury | Recovery | | --- | --- | --- | | 1 | 1-of-1 | 2-of-3 | | 2 | 2-of-2 | 2-of-3 | | 3 | 2-of-3 | 2-of-3 | -| 4+ | (n/2)+1 | (n/2)+1 | +| 4+ | Majority | Majority | + +A **majority** means more than half of the configured signers. -For solo setups, see [Personal usage](/introduction/personal-usage#multisigs-of-one). +[Recovery](/teams/recovery) describes recovery signers. [Personal usage](/introduction/personal-usage#multisigs-of-one) describes multiple signers controlled by one person. diff --git a/src/pages/banking/index.mdx b/src/pages/banking/index.mdx index 92d3a4b..55fe6c7 100644 --- a/src/pages/banking/index.mdx +++ b/src/pages/banking/index.mdx @@ -1,28 +1,39 @@ --- -title: Banking -description: "Banking in Splits: verify the team's entity (KYB/KYC) to move between crypto and fiat, the supported jurisdictions, and the fee." +title: "Banking" +description: "Convert between tokens and bank currencies" --- -# Banking [Verify the team's entity to move between crypto and fiat] +# Banking [Convert between tokens and bank currencies] -Banking is opt-in: once a team completes legal entity verification, it can move between crypto and fiat directly in Splits. +**Banking** connects token transfers to bank accounts. A team must complete entity verification before it can use Splits banking. -- [Onramping](/banking/onramping): fiat in, by ACH, wire, or SEPA -- [Offramping](/banking/offramping): crypto out, to the team's own bank accounts -- [Paying vendors](/banking/paying-vendors): crypto out, to someone else's bank account +- [Onramping](/banking/onramping) converts a bank transfer to tokens. +- [Offramping](/banking/offramping) converts tokens to funds in the team's bank account. +- [Paying vendors](/banking/paying-vendors) converts tokens to funds in another person's or business's bank account. -On/offramps cost 0.25%, deducted from the transaction amount. Setting up banking (verification, bank accounts, onramp details) requires the [Owner](/teams/roles) role. +The banking fee is **0.25%** of the transaction amount. Splits deducts this fee from the transfer. -## Getting verified +The [Owner](/teams/roles) role permits banking setup, including verification and bank account management. -Go to [Settings > Banks](https://app.splits.org/settings/team/banks/) and select *KYB* (for businesses, including solo-member pass-through LLCs) or *KYC* (for individuals). You'll be brought through an entity verification flow powered by [Bridge](https://www.bridge.xyz/). **Each team can only have one verified entity.** + -KYC is typically near-instant; KYB typically takes 5-7 business days. Bridge will email you directly if additional documentation is needed. The verified entity's details also supply the counterparty information (the "travel rule") required on every on/offramp, so no per-transfer paperwork is needed. +## Verify an entity + +Prerequisites: you must have the Owner role. + +1. Open [Settings > Banks](https://app.splits.org/settings/team/banks/). +2. For a business, select *KYB*. +3. For an individual, select *KYC*. +4. Complete the verification form from [Bridge](https://www.bridge.xyz/). + +KYB means know your business. It includes businesses with one owner. KYC means know your customer. + +**Each team can have only one verified entity.** Bridge requests additional documents by email when necessary. Verification supplies the counterparty information for subsequent bank transfers. ## Jurisdictions -Splits supports **US- and EU-based individuals and businesses**, excluding [countries not supported by Bridge](https://apidocs.bridge.xyz/platform/customers/compliance/supported-countries-list#supported-countries-list) and, within the US, New York and Alaska ([Bridge's US details](https://apidocs.bridge.xyz/get-started/introduction/what-we-support/geo#united-states-specific-details)). +Banking availability depends on the entity, bank account, and Bridge's geographic restrictions. Bridge maintains the [supported country list](https://apidocs.bridge.xyz/platform/customers/compliance/supported-countries-list#supported-countries-list) and [US restrictions](https://apidocs.bridge.xyz/get-started/introduction/what-we-support/geo#united-states-specific-details). -If you live elsewhere, you can still use banking as long as your entity is legally registered in the US and you have a US- or EU-based bank account. Because bank accounts are added by routing/account number (US) or IBAN (EU), accounts at fintechs like [Wise](https://wise.com) generally work like any other bank account. +US bank accounts use routing and account numbers. EU bank accounts use an international bank account number (IBAN). Accounts from providers such as [Wise](https://wise.com) use these same identifiers. -For what KYB requires, see [Bridge's business onboarding docs](https://docs.google.com/document/d/1UjwaXWHNEs3PTaj2R2l_CLxGApj8Uc8NHlL_WVd6TFY/edit?usp=sharing). Email support to request another jurisdiction. +[Bridge's business verification guide](https://docs.google.com/document/d/1UjwaXWHNEs3PTaj2R2l_CLxGApj8Uc8NHlL_WVd6TFY/edit?usp=sharing) describes the required information. Email support to request another jurisdiction. diff --git a/src/pages/banking/offramping.mdx b/src/pages/banking/offramping.mdx index 54cdbfc..e5fe1bc 100644 --- a/src/pages/banking/offramping.mdx +++ b/src/pages/banking/offramping.mdx @@ -1,24 +1,45 @@ --- -title: Offramping -description: "Move crypto out to the team's bank accounts: connecting banks via Plaid or manual entry, initiating an offramp, settlement timing for ACH and SEPA, and using your own offramp provider." +title: "Offramping" +description: "Convert tokens to a transfer into the team bank account" --- -# Offramping [Move crypto out to the team's bank accounts] +# Offramping [Convert tokens to a transfer into the team bank account] -Before you can offramp, add a bank account from [Settings > Banks](https://app.splits.org/settings/team/banks/): connect a US account with [Plaid](https://plaid.com/) or enter its routing and account numbers manually; EU accounts are added by IBAN. You can add as many bank accounts as you wish. +An **offramp** converts tokens to bank currency and sends it to a bank account. -Then offramp either by clicking *Offramp* on the Dashboard, or from Settings > Banks: click the three dots on the target bank account and select *Offramp*. +Prerequisites: the team must complete [banking verification](/banking#verify-an-entity). -Offramps carry the 0.25% [banking fee](/banking), deducted from the amount. To send fiat to a bank account that isn't yours, see [Paying vendors](/banking/paying-vendors). +1. Open [Settings > Banks](https://app.splits.org/settings/team/banks/). +2. For a US account, connect through [Plaid](https://plaid.com/) or enter routing and account numbers. +3. For an EU account, enter its IBAN. +4. Open the bank account's three-dot menu. +5. Select *Offramp*. + +The Dashboard also has an *Offramp* action. You can add multiple bank accounts. + +[Banking](/banking) describes the fee. [Paying vendors](/banking/paying-vendors) describes transfers to bank accounts owned by other people or businesses. ## Settlement -In the US, we send via [Same Day ACH, which settles in batches throughout the day](https://apidocs.bridge.xyz/platform/orchestration/more/cutoffs#processing-windows-and-cutoff-times). In Eastern Time, settlement occurs at 1:00 pm, 5:00 pm, and 6:00 pm, with corresponding cutoffs at 9:15 am, 1:30 pm, and 3:30 pm (we've added a 15m buffer relative to Bridge's documentation, which has been necessary in our experience). Offramps sent after 3:30 pm ET arrive the following business day. +US transfers use Same Day ACH. The following times use Eastern Time and include Splits' 15-minute buffer before Bridge's cutoff: + +| Submit before | Settlement time | +| --- | --- | +| 9:15 a.m. | 1:00 p.m. | +| 1:30 p.m. | 5:00 p.m. | +| 3:30 p.m. | 6:00 p.m. | + +Transfers after the last cutoff arrive on the next business day. [Bridge's processing windows](https://apidocs.bridge.xyz/platform/orchestration/more/cutoffs#processing-windows-and-cutoff-times) describe provider timing. + +SEPA Instant can settle in less than 30 seconds for banks that support it. + + -For IBAN banks supporting SEPA Instant, settlement occurs in under 30 seconds. +## Use an external provider -## Using your own provider +An external provider can supply a **liquidation address**. It converts incoming stablecoins to bank currency and transfers the result to your bank account. -If your team already has an offramping provider, you can offramp through it instead, without completing Splits' [entity verification](/banking#getting-verified). Most providers issue a **liquidation address**: a deposit address that converts incoming stablecoins to fiat and forwards them to your bank account. Save that address as a [contact](/contacts), then [send](/transactions/sends) stablecoins to it like any other recipient. +1. Save the provider's liquidation address as a [contact](/contacts). +2. [Send](/transactions/sends) the provider's accepted stablecoin to that address on its specified network. -Splits treats these as ordinary sends: the 0.25% banking fee and the settlement times above don't apply; your provider's own fees and timing do. +This method does not require Splits banking verification. Splits records it as a normal send. **Splits' banking fee and settlement times do not apply.** The external provider sets its own fees and timing. diff --git a/src/pages/banking/onramping.mdx b/src/pages/banking/onramping.mdx index 4c4438d..87c23f2 100644 --- a/src/pages/banking/onramping.mdx +++ b/src/pages/banking/onramping.mdx @@ -1,16 +1,30 @@ --- -title: Onramping -description: "Move fiat into a team's accounts by bank transfer (ACH or wire in the US, SEPA in the EU), with a configurable destination account, network, and token." +title: "Onramping" +description: "Convert a bank transfer to tokens" --- -# Onramping [Move fiat in by bank transfer] +# Onramping [Convert a bank transfer to tokens] -Once your entity is [verified](/banking#getting-verified), onramp by bank transfer: **ACH or wire** in the US, **SEPA** in the EU. Click *Onramp* on the Dashboard, or find the deposit details (US account and routing numbers, or an EU IBAN) in [Settings > Banks](https://app.splits.org/settings/team/banks/). +An **onramp** converts bank currency to tokens in a Splits account. US transfers use ACH or wire. EU transfers use SEPA. -By default, funds arrive in the Treasury as USDC on Base. You can change the destination account and network at any time. +Prerequisites: the team must complete [banking verification](/banking#verify-an-entity). + +1. Select *Onramp* on the Dashboard. +2. Copy the deposit details. +3. Send a bank transfer with those details. + +[Settings > Banks](https://app.splits.org/settings/team/banks/) also shows the deposit details. + +The default destination is the Treasury, with USDC on Base. You can change the destination account and network. + +[Banking](/banking) describes the transfer fee. ## Verify with microdeposits -If your bank sends microdeposits to verify the account, return to *Settings > Banks*. Enter the amounts shown in the *Microdeposit verification* banner into your bank's verification flow. +A **microdeposit** is a small test transfer that a bank uses for verification. + +If your bank requires microdeposit verification: -Onramps carry the 0.25% [banking fee](/banking), deducted from the amount. +1. Open *Settings > Banks*. +2. Find the amounts in the *Microdeposit verification* banner. +3. Enter those amounts in your bank's verification form. diff --git a/src/pages/banking/paying-vendors.mdx b/src/pages/banking/paying-vendors.mdx index f082fa2..3cd7c66 100644 --- a/src/pages/banking/paying-vendors.mdx +++ b/src/pages/banking/paying-vendors.mdx @@ -1,22 +1,39 @@ --- -title: Paying vendors -description: "Pay third parties in fiat from Splits accounts: add a vendor's bank account as an external account, then send stablecoins to its deposit address." +title: "Paying vendors" +description: "Transfer funds to another person or business through a bank" --- -# Paying vendors [Pay third parties in fiat from Splits accounts] +# Paying vendors [Transfer funds to another person or business through a bank] -A vendor payment is an offramp to someone else's bank account. Each vendor bank account you add gets a dedicated onchain deposit address; paying the vendor is a normal [send](/transactions/sends) of stablecoins to that address, which Bridge converts and delivers to the vendor's bank. +A **vendor payment** is an offramp to another person's or business's bank account. Each vendor bank account has a dedicated onchain deposit address. -Prerequisites: [banking](/banking) set up with a verified entity. Adding and managing vendor accounts requires the [Owner](/teams/roles) role. +A stablecoin [send](/transactions/sends) to that address funds the payment. Bridge converts the stablecoins and transfers bank currency to the vendor. -## Adding a vendor +Prerequisites: the team needs [banking verification](/banking). Vendor account setup requires the [Owner](/teams/roles) role. -In [Settings > Banks](https://app.splits.org/settings/team/banks/), add the vendor's bank account as an **external account**: US accounts by routing and account number, EU accounts by IBAN, along with the account owner's name and address. External accounts are listed separately from the team's own (internal) bank accounts. + -## Paying +## Add a vendor -Send **USDC** (US vendors) or **EURC** (EU vendors) to the vendor account's deposit address; bank-account recipients also appear directly in the send flow. The payment is a transaction like any other: proposed from an account, approved at its threshold, visible in the feed and [accounting](/accounting). +1. Open [Settings > Banks](https://app.splits.org/settings/team/banks/). +2. Add the vendor's bank account as an *external account*. +3. For a US account, enter routing and account numbers. +4. For an EU account, enter the IBAN. +5. Enter the bank account owner's name and address. -Delivery is via Same Day ACH (US) or SEPA (EU), with the same 0.25% fee and [settlement timing](/banking/offramping#settlement) as other offramps, deducted from the amount sent. +The app lists vendor accounts separately from the team's own bank accounts. -To collect tax forms from vendors and contractors you pay, see [Compliance](/contacts/compliance). + + +## Pay a vendor + +1. Select the vendor bank account as the send recipient. +2. For a US vendor, send USDC to the deposit address. +3. For an EU vendor, send EURC to the deposit address. +4. Obtain the account's required approvals. + +The transaction appears in the feed and [Accounting](/accounting). Delivery uses Same Day ACH in the US or SEPA in the EU. + +The banking fee and [settlement times](/banking/offramping#settlement) apply. The fee reduces the delivered amount. + +[Compliance](/contacts/compliance) describes tax form collection for vendors and contractors. diff --git a/src/pages/contacts/compliance.mdx b/src/pages/contacts/compliance.mdx index 988ad2b..f6922a4 100644 --- a/src/pages/contacts/compliance.mdx +++ b/src/pages/contacts/compliance.mdx @@ -1,24 +1,34 @@ --- -title: Compliance -description: Collect W-9 and W-8 tax forms from the payees a team pays, and get year-end 1099 data for filing. +title: "Compliance" +description: "Collect payee identity information and tax forms" --- -# Compliance [Collect tax forms from the people a team pays] +# Compliance [Collect payee identity information and tax forms] -Compliance collects tax documentation from **payees** (the contractors and vendors a team pays) so those payments are documented for year-end filing. +**Compliance** collects identity information and tax forms for payees. A payee is a contractor or vendor that the team pays. :::note -This feature is in beta. Email support to enable it for your team. +This feature is in beta. ::: +Email support to enable it for your team. + ## How it works -1. An [Owner](/teams/roles) invites a payee by email. -2. The payee completes an identity verification flow powered by [Persona](https://withpersona.com/); their tax details are held by the verification provider, not Splits. -3. Splits generates the correct form from the verified details: **W-9** for US persons, **W-8BEN** for foreign individuals, **W-8BEN-E** for foreign entities. +An [Owner](/teams/roles) invites the payee by email. The payee completes identity verification through [Persona](https://withpersona.com/). The verification provider stores the tax information. + +Splits generates a tax form from the verified information: -Once a payee is verified, payments to them are tracked as compliant payments, and the payee appears as a recipient in the [send](/transactions/sends) flow. +| Payee classification | Form | +| --- | --- | +| US person | W-9 | +| Foreign individual | W-8BEN | +| Foreign entity | W-8BEN-E | + +After verification, Splits records payments to that payee as compliant payments. The payee also appears in the [send](/transactions/sends) recipient list. ## Year-end filings -Splits can produce a 1099 CSV for a filing year, covering the team's compliant payments: each payee's verified details plus the total USD paid. Email support to request the export. +Splits can generate a 1099 CSV for a filing year. It includes verified payee information and total USD payments for each payee. + +Email support to request this export. diff --git a/src/pages/contacts/index.mdx b/src/pages/contacts/index.mdx index 6ec35a0..0440cc7 100644 --- a/src/pages/contacts/index.mdx +++ b/src/pages/contacts/index.mdx @@ -1,52 +1,77 @@ --- -title: Contacts -description: "Contacts are team-wide names for external addresses, used everywhere addresses appear: recipient search, transaction feeds, accounting, and CSV exports." +title: "Contacts" +description: "Name external addresses and set send rules" --- -# Contacts [Name the addresses a team transacts with] +# Contacts [Name external addresses and set send rules] -A contact is a team-wide name for an external address. Once saved, the name stands in for the raw address everywhere addresses appear: recipient search in [sends](/transactions/sends), transaction feeds, the [Accounting page](/accounting), and CSV exports. Contacts are shared by the whole team, and any member can create, edit, or delete them. +A **contact** gives an external address a name for the team. The name appears in [send](/transactions/sends) searches, transaction feeds, [Accounting](/accounting), and CSV exports. -## Creating +All team members share contacts. Any member can create, edit, or delete them. -Add contacts from the *Contacts* page, inline from any address shown in a transaction feed, or from the command menu. A contact is an address plus a display name; you can enter an ENS name, which is resolved once and stored as its address. Each address holds one contact per team, applied across all networks. + -Two constraints: contacts must be EVM addresses, and the team's own Splits accounts can't be added as contacts (they're already named). +## Create a contact + +The *Contacts* page, addresses in transaction feeds, and the command menu provide contact creation controls. + +1. Open the contact creation form. +2. Enter an address or ENS name. +3. Enter a display name. +4. Save the contact. + +Splits resolves an ENS name once and stores the address. Each address can have one contact per team across all networks. + +**Contacts require EVM addresses.** The team's own Splits accounts already have names and cannot be contacts. ## Restrictions :::note -This feature is in beta. Email support to enable it for your team. +This feature is in beta. ::: -A **send rule** limits what a contact accepts. Pick one mode under *Restrict sends* when you create or edit the contact: +Email support to enable it for your team. + +A **send rule** limits the token and network combinations for payments to a contact. *Restrict sends* in the contact form offers three modes: -| Mode | What the contact accepts | +| Mode | Permitted payments | | --- | --- | -| No restrictions | Any token on any network. The default. | -| By network | Any token, on the networks you check. | -| By token and network | Only the token and network pairs you add. | +| No restrictions | Any token and network | +| By network | Any token on selected networks | +| By token and network | Selected token and network pairs | -Rules are enforced when the transaction is created, so they cover a [send](/transactions/sends), a [swap](/transactions/swaps), or bridge that pays an external recipient. A send that no rule allows is rejected. +The default has no restrictions. Splits checks rules at transaction creation for sends, swaps, and transfers across networks to an external recipient. -If a [schedule](/transactions/schedules) already pays this contact in a token the new rules don't allow, saving the contact warns you first. The schedule keeps creating its drafts, but no one can sign them until the rules or the schedule changes. +**A send with no permitted rule fails.** + +A rule change can conflict with an existing [schedule](/transactions/schedules). The contact form warns about this conflict before the save. + +The schedule continues to create drafts. **Those drafts cannot receive signatures until the rule or schedule changes.** ## How addresses get named -When Splits displays an address, it uses the first name it finds: the team's account and bank-account names, then contact labels, then live ENS and Farcaster lookups, then [payee](/contacts/compliance) details, and finally the shortened address. Saving a contact pins a name the whole team sees, instead of depending on what an address resolves to. +Splits uses the first available name in this order: + +1. Team account or bank account name. +2. Contact name. +3. Current ENS or Farcaster name. +4. Payee information. +5. Shortened address. + +A saved contact gives all members the same name without a new external name lookup. ## Other named recipients -Two recipient types are stored separately from contacts but appear alongside them in recipient search: +These records appear in recipient searches but are separate from contacts: -- **Payees**: counterparties with a verified identity and tax forms; see [Compliance](/contacts/compliance). -- **Vendor bank accounts**: fiat payees with a deposit address; see [Paying vendors](/banking/paying-vendors). +- **Payees**: recipients with identity verification and tax forms. [Compliance](/contacts/compliance) describes these records. +- **Vendor bank accounts**: bank recipients with deposit addresses. [Paying vendors](/banking/paying-vendors) describes their setup. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits contacts list --q "acme"`: search contacts by name or address; returns up to 500 (**Read** scope) -- `splits contacts lookup --addresses 0x...,0x...`: batch address-to-name lookup, max 100 addresses (**Read** scope) +- `splits contacts list --q "acme"`: search names or addresses, with a maximum of 500 results (**Read** scope). +- `splits contacts lookup --addresses 0x...,0x...`: look up names for up to 100 addresses (**Read** scope). -Creating and editing contacts is web-only today. +Contact creation and changes are available only in the app. diff --git a/src/pages/experiments/index.mdx b/src/pages/experiments/index.mdx index dfefd29..3ab4d16 100644 --- a/src/pages/experiments/index.mdx +++ b/src/pages/experiments/index.mdx @@ -1,12 +1,12 @@ --- -title: Experiments -description: Prototype products from Splits. +title: "Experiments" +description: "Prototype products from Splits" --- # Experiments [Prototype products from Splits] -An experiment is a prototype product from Splits. Experiments are early and unproven and should be used with caution. +An **experiment** is a prototype product from Splits. Experiments can change. Their testing is incomplete. -| Experiment | What it does | Status | +| Experiment | Function | Status | | --- | --- | --- | -| [PACT](/experiments/pact) | Capital formation: a handshake deal with public receipts | Live | +| [PACT](/experiments/pact) | Capital collection with public token records | Live | diff --git a/src/pages/experiments/pact.mdx b/src/pages/experiments/pact.mdx index d0b6eae..222d74f 100644 --- a/src/pages/experiments/pact.mdx +++ b/src/pages/experiments/pact.mdx @@ -1,45 +1,53 @@ --- -title: PACT -description: "PACT lets a project raise a small round on Base: it sells units of its cap table for USDC, and buyers are refunded if the raise misses its minimum." +title: "PACT" +description: "Collect capital and record token allocations on Base" --- -# PACT [Capital formation: a handshake deal with public receipts] +# PACT [Collect capital and record token allocations on Base] -:::note -This is an [experiment](/experiments) and should be used with caution. -::: +**PACT** means Purchase Agreement for Community Tokens. It is an [experiment](/experiments) at [pact.splits.org](https://pact.splits.org) for project capital collection. -**PACT** (Purchase Agreement for Community Tokens) is a lightweight tool at [pact.splits.org](https://pact.splits.org) for raising capital without a legal framework. +A PACT records units that can represent a future project allocation. That allocation can concern equity, tokens, or revenue at the issuer's discretion. -A PACT is a placeholder for future value: equity, tokens, revenue share, or whatever the project turns into. The issuer gets a funded treasury and a programmable cap table; buyers get public receipts and a claim on the project's future value. +**Units are not equity and give no legal, voting, or dividend rights.** A successful raise does not guarantee a future benefit. -For the thinking behind PACT, see [this thread](https://x.com/abram/status/2084689867942908347); for a walkthrough, see [this demo](https://youtu.be/kWzQE2mCtKA). The project is [open source](https://github.com/0xSplits/pact) with no third-party dependencies. +The [project source](https://github.com/0xSplits/pact), [background thread](https://x.com/abram/status/2084689867942908347), and [demo](https://youtu.be/kWzQE2mCtKA) give more information. ## Why -Every project starts before incorporation. Capital can be raised at this stage, but receipts are email threads, working capital sits in personal accounts, and the cap table is undefined. Deals at this stage don't need legal paperwork; trust, reputation, and the repeat game hold participants accountable. PACT gives this stage a treasury and a public ledger. +A project can collect capital before incorporation. PACT provides a treasury and public allocation records for this stage. ## How a raise works -Each raise is a pair of contracts on Base: an offering, which escrows the units for sale, prices them, and holds the deposited USDC; and a cap-table token of 1,000 units, where one unit is 0.1% of the project. +Each raise has two contracts on Base: -1. The issuer fills in [pact.splits.org/create](https://pact.splits.org/create) (founder units, units for sale, pricing, the round minimum, and the close date) and signs one transaction. It deploys both contracts, mints the founders' units to them, and escrows the for-sale units in the offering. -2. Buyers pay USDC. The issuer prices units flat or along a linear bonding curve; with a curve, each unit sold raises the price by a fixed amount, so early buyers pay less. Public buys are open to anyone, up to a cap the issuer can adjust. Private buys go through allocation links, which the issuer signs and shares; each link caps what its holder can spend. -3. Once the raise meets its minimum, it is permanently successful. Buying continues, and the raised USDC can be withdrawn; proceeds can only ever reach the treasury. -4. The issuer closes the offering: the sale ends, remaining proceeds are withdrawn, and unsold units return to the treasury. The cap table is now the founders plus the buyers. -5. If the close date passes with the minimum unmet, the offering is marked failed. Each buyer reclaims their USDC, returning their units in the same call, and the cap table reverts to the founders. +- An **offering** holds units for sale and deposited USDC. +- A **cap table token** records 1,000 units. Each unit represents 0.1% of the token allocation. -The cap table is a [Liquid Split](https://splits.org/protocol/docs/templates/liquid) on Base. Tokens sent to the Liquid Split contract are distributed to holders in proportion to their units. Distribution is locked while the raise is open and unlocks once it closes or fails. +The issuer creates the raise at [pact.splits.org/create](https://pact.splits.org/create). Inputs include founder units, sale units, prices, the minimum raise amount, and the close date. + +One transaction deploys both contracts. It gives founder units to the founders and holds sale units in the offering. + +Buyers pay USDC. A fixed price keeps each unit's price constant. A linear bonding curve increases the price by a fixed amount for each unit sold. + +Public purchases have an issuer-controlled limit. Private purchases use signed allocation links with spending limits. + +When deposits reach the minimum, the raise becomes permanently successful. Purchases can continue. Withdrawals send proceeds only to the treasury. + +The issuer can close the offering. Closure ends sales, withdraws remaining proceeds, and returns unsold units to the treasury. + +If the close date passes before the minimum is met, the offering fails. Buyers can return their units and reclaim USDC in the same call. + +The cap table uses a [Liquid Split](https://splits.org/protocol/docs/templates/liquid) on Base. Tokens sent to it distribute to holders in proportion to their units. Distribution starts only after the raise closes or fails. ## Notes -- Units are **not equity**. They give holders no legal, voting, or dividend rights in the project; any benefit to holders is at the issuer's discretion. -- The contracts **do not protect buyers from a dishonest issuer** after a raise succeeds. The minimum is a coordination signal, not a guarantee. -- There is no sign-in: the wallet that creates the raise manages it. -- For exact pricing, allocation, and lifecycle rules, see the [architecture](https://github.com/0xSplits/pact/blob/main/docs/architecture.md) and [contract specification](https://github.com/0xSplits/pact/blob/main/contracts/docs/contracts.md) in the repo. +- **The contracts do not protect buyers from a dishonest issuer after success.** +- The creator's wallet manages the raise without a separate sign-in. +- The [architecture](https://github.com/0xSplits/pact/blob/main/docs/architecture.md) and [contract specification](https://github.com/0xSplits/pact/blob/main/contracts/docs/contracts.md) define pricing, allocations, and state changes. ## Programmatic access -PACT has no dependency on the app: the chain is the only backend. An agent can run a raise end to end (create the offering, issue allocations, buy, close, refund) by sending transactions on Base directly. +PACT operates through Base contracts without the Splits app backend. An agent can create offerings, issue allocations, buy units, close offerings, and request refunds through contract calls. -- [PACT skill](https://github.com/0xSplits/pact/tree/main/skills/pact): the protocol model, lifecycle, safety rails, and `cast` recipes for every read and write. Install with `npx skills add 0xSplits/pact`. +The [PACT skill](https://github.com/0xSplits/pact/tree/main/skills/pact) describes these calls. Its install command is `npx skills add 0xSplits/pact`. diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 381c60d..33b86da 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -1,35 +1,42 @@ --- -title: Introduction -description: Splits is the onchain operations platform for builders. Manage assets, process revenue, move money, and run operations instantly, via the app, API, or CLI. +title: "Introduction" +description: "Use Splits to manage accounts, payments, and records" --- -# Introduction [What Splits is, who it's for, and how to start] +# Introduction [Use Splits to manage accounts, payments, and records] -**Splits is the onchain operations platform for builders.** Manage assets, process revenue, move money, and run operations instantly, via [the app](https://app.splits.org) or [API/CLI](/introduction/agents). +**Splits** is an app for teams that manage assets on blockchain networks. You can use [the app](https://app.splits.org) or the [API and CLI](/introduction/agents). :::note -These docs cover the Splits app. Docs for the Splits protocol (the splitter contracts) live at [splits.org/protocol/docs](https://splits.org/protocol/docs). +These docs describe the Splits app. The [protocol docs](https://splits.org/protocol/docs) describe the Splits splitter contracts. ::: -Key properties: +The docs cover these functions: -- **Self-custodied.** No custodian, no gatekeepers, no KYC to get started: anyone, anywhere can create accounts and transact instantly. Splits never has access to private keys and cannot move your funds; if passkeys are lost, [recovery signers](/teams/recovery) regain control. -- **Isolated by account, operated as one.** Each [account](/accounts) has its own signers and threshold, so a leaked key is capped at that account's balance. One interface covers every account's balances, transactions, and [accounting exports](/accounting). -- **Crosschain by default.** Accounts keep one address across every [supported network](/introduction/networks-and-assets), with signers and threshold synced automatically. [Swap and bridge](/transactions/swaps) across chains in a single transaction. -- **Integrated fiat.** Move between tokens and fiat: [on/offramps](/banking) to your bank accounts, invoices [payable by bank transfer](/invoicing#pay-by-bank), and [vendor payments](/banking/paying-vendors). -- **Composable.** Connect your multi-signature accounts to any app in the ecosystem via the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect). -- **Professional workflows.** [Accounting](/accounting) with filters and exports, [invoicing](/invoicing), [bill pay](/banking/paying-vendors), payroll via [schedules](/transactions/schedules), [tax withholding](/accounts#automation-accounts), and [compliant payments](/contacts/compliance) (collect W9s/W8s, issue 1099s), all reachable from the command menu (`⌘K`). -- **Experiments.** [Experimental products](/experiments) from Splits, early and unproven. -- **Agent-ready.** Everything is scriptable via the [CLI and MCP](/introduction/agents), and every docs page is available as markdown: append `.md` to any URL, or fetch `/llms.txt` / `/llms-full.txt`. +- [Accounts](/accounts): asset storage and account ownership. +- [Transactions](/transactions): payment proposals and approvals. +- [Networks and assets](/introduction/networks-and-assets): network and token support. +- [Banking](/banking): transfers between tokens and bank currencies. +- [Invoicing](/invoicing): payment requests. +- [Accounting](/accounting): transaction records and exports. +- [Contacts](/contacts): names for external addresses. +- [Integrations](/integrations): connections to other apps. +- [Experiments](/experiments): prototype products. -## Who it's for +The [glossary](/resources/glossary) defines the technical terms in these docs. -- **Teams and companies operating onchain**: shared treasury with per-account signer sets, roles, and approvals. -- **Solo builders**: the same setup works for [teams of one](/introduction/personal-usage), with resilient multisigs, clean books, and separation of project and personal funds. -- **Agents**: AI tools operating on a team's behalf via API keys, with scoped permissions and optional headless signing. + + +## Who uses Splits + +- Teams and companies that manage shared assets. +- Individuals who keep project and personal funds in [separate accounts](/introduction/personal-usage). +- Agents that use the API on behalf of a team. + +Each page also has a Markdown version. Its URL ends in `.md`. The documentation indexes are `/docs/llms.txt` and `/docs/llms-full.txt`. ## Start here -1. Read [Core concepts](/introduction/core-concepts): Team, Member, and Account, the three ideas everything else builds on. -2. [Create a team](/teams). -3. Working from an AI tool? Connect it via [Agents & API](/introduction/agents). +1. Read [Core concepts](/introduction/core-concepts). +2. Follow the instructions to [create a team](/teams). +3. For access through an agent, follow [Agents & API](/introduction/agents). diff --git a/src/pages/integrations/bankr.mdx b/src/pages/integrations/bankr.mdx index 599592c..376fd54 100644 --- a/src/pages/integrations/bankr.mdx +++ b/src/pages/integrations/bankr.mdx @@ -1,33 +1,51 @@ --- -title: Bankr -description: "Let a Bankr agent operate your Splits treasury through the CLI: signer-based access with human co-approval by default, or module-based direct execution on a bounded account." +title: "Bankr" +description: "Use a Bankr agent with Splits" --- -# Bankr [Let a Bankr agent operate your treasury through the CLI] +# Bankr [Use a Bankr agent with Splits] -[Bankr](https://bankr.bot) is an AI agent with its own trading wallet. Paired with Splits, the division of labor is: Bankr handles market reasoning and fast small-value moves from its own wallet; Splits holds the treasury, enforces the approval policy, and executes governed payments and revenue operations. +**[Bankr](https://bankr.bot)** is an AI agent with a trading wallet. It can operate Splits through the [CLI and MCP](/introduction/agents). -A Bankr agent operates Splits through the [CLI / MCP](/introduction/agents). The [Bankr splits skill](https://github.com/BankrBot/skills/tree/main/splits) teaches the agent the full setup, command surface, and safety rules: install it, give the agent an API key, and it walks itself through the rest. +The [Bankr Splits skill](https://github.com/BankrBot/skills/tree/main/splits) describes setup, commands, and key protection. ## Access paths -The agent gets execution power on an account one of two ways: +A Bankr agent can use an account as a signer or through a module: -| | As a signer | As a module | +| Property | Signer | Module | | --- | --- | --- | -| Key | A dedicated EOA the CLI generates, [registered as a signer](/members/keys#eoas) | The Bankr wallet itself, [enabled as a module](/accounts/modules) | -| Execution | Proposals, signed against the account's [threshold](/accounts/thresholds) | Direct, with no per-action approval | -| Human in the loop | Yes, whenever the threshold is 2 or higher | None after enabling | -| Reach | Only the accounts a human added it to | The account's full balance | -| Revoking | Remove the signer | Disable the module | +| Signing key | Registered EOA | Bankr wallet | +| Execution | Proposal and signatures | Direct calls | +| Approval policy | Account threshold | Module authority | +| Removal | Signer removal | Module removal | -**Default to the signer path at a 2-of-n threshold**: the agent proposes and signs, and a human co-signs every execution. A 1-of-n account lets the agent execute alone; use one only deliberately, for low-value operations. +[Signing keys](/members/keys#eoas) describes EOA registration. [Modules](/accounts/modules) describes direct execution authority. -The module path exists for autonomous execution, including calls to contracts that check `msg.sender` (e.g. claiming fees). A module has full, unilateral access, so enable it only on a dedicated, bounded [operating account](/accounts#operating-accounts) funded with only what you're willing to expose. **Never enable a module on the Treasury.** +With a 2-of-n [threshold](/accounts/thresholds), an agent with one signer needs another signer's approval. **A threshold alone does not guarantee human approval.** The other signing keys must remain under human control for that policy. + +A 1-of-n account permits the agent to execute alone. An [operating account](/accounts#operating-accounts) with a limited balance can limit its direct funds access. + +A module can also call contracts that check `msg.sender`. **A module does not need approval for each action.** Its authority can include external contracts on which the account has permissions. ## Setup -1. An Owner creates an API key in [Settings > API Keys](https://app.splits.org/settings/team/api-keys/) (**Owner** scope is required for the agent to create accounts or propose signer changes). -2. The agent authenticates (`splits auth login`), generates and registers its own signing EOA (`splits auth create-key --register`), and either creates a new account with itself and a human passkey as signers, or proposes adding itself to an existing account, approved by a human in the app. +Prerequisites: an Owner must create the API key and approve account signer changes. + +1. Create an API key in [Settings > API Keys](https://app.splits.org/settings/team/api-keys/). +2. Give it the Owner scope if the agent needs account creation or signer change proposals. +3. Install the Bankr Splits skill. +4. Follow the skill to register the agent's EOA. +5. Create an account or propose a signer change with the intended human and agent signers. +6. Approve the signer configuration in the app. + +Proposals appear in the transaction feed. Module calls have onchain events and feed records. + +## Programmatic access + +Through the [Splits CLI / MCP](/introduction/agents): + +- `splits auth login`: configure the API key. +- `splits auth create-key --register`: create and register the agent's signing EOA. -The skill covers the exact commands, key handling, and validation rules. Everything the agent does surfaces like any other activity: proposals in the transaction feed, and module executions logged onchain and in the feed. +The Bankr skill describes the remaining commands. diff --git a/src/pages/integrations/clanker.mdx b/src/pages/integrations/clanker.mdx index 8fda876..f669fc5 100644 --- a/src/pages/integrations/clanker.mdx +++ b/src/pages/integrations/clanker.mdx @@ -1,17 +1,25 @@ --- -title: Clanker -description: "Clanker LP rewards paying a Splits account appear as positions on the account's page: which reward tokens index automatically, and how to add the rest." +title: "Clanker" +description: "View and claim token rewards" --- -# Clanker [View and claim Clanker token rewards] +# Clanker [View and claim token rewards] -[Clanker](https://clanker.world) pays LP rewards to token deployers. Rewards paying a Splits account appear as [positions](/integrations#positions) on the account's page. +**[Clanker](https://clanker.world)** provides liquidity provider (LP) rewards to token creators. Rewards for a Splits account appear as [positions](/integrations#positions). -Which rewards index automatically depends on the Clanker version: +Automatic detection depends on the Clanker version: -- **Clanker v4**: rewards paid in WETH, USDC, and CLANKER appear automatically; rewards in any other token (e.g. your own deployed token) require adding that token's address. -- **Clanker v3**: always requires adding your Clanker token's address, regardless of the reward token. +| Version | Automatic detection | Manual addition | +| --- | --- | --- | +| v4 | WETH, USDC, CLANKER rewards | Other reward token addresses | +| v3 | None | Clanker token address | -Add a token from the plus button next to the *Positions* header; it's saved once for the whole team. +To add a token: -Claiming moves the rewards into the account: per token for v4, and for the whole position (one or two tokens) for v3. To route Clanker revenue automatically, see [automation accounts](/accounts#automation-accounts). +1. Open the account page. +2. Select the plus button beside *Positions*. +3. Enter the token address. + +Splits saves this token for the team. A v4 claim transfers rewards for one token. A v3 claim transfers the position's rewards in one or two tokens. + +[Automation accounts](/accounts#automation-accounts) can process the received revenue. diff --git a/src/pages/integrations/ens.mdx b/src/pages/integrations/ens.mdx index 2e4f734..b9b0cf2 100644 --- a/src/pages/integrations/ens.mdx +++ b/src/pages/integrations/ens.mdx @@ -1,32 +1,47 @@ --- -title: ENS -description: "Use an ENS name with a Splits account: register a new name, transfer an existing one, and extend a name across accounts with subnames." +title: "ENS" +description: "Register and transfer names for account addresses" --- -# ENS [Register or transfer an ENS name to a Splits account] +# ENS [Register and transfer names for account addresses] -An [ENS](https://ens.domains) name can be held by, and resolve to, a Splits account. All ENS management happens in the [ENS app](https://app.ens.domains) with your account connected via [WalletConnect](/integrations/walletconnect). +The **Ethereum Name Service (ENS)** associates names with blockchain addresses. A Splits account can hold an [ENS](https://ens.domains) name and receive funds through it. -Prerequisites: Ethereum Mainnet enabled in [Settings > Networks](https://app.splits.org/settings/team/networks/), and enough mainnet ETH in the account if you're registering a name. +The [ENS app](https://app.ens.domains) provides name management through [WalletConnect](/integrations/walletconnect). -## Registering a new name +Prerequisites: Ethereum Mainnet must be active in [Settings > Networks](https://app.splits.org/settings/team/networks/). Name registration also needs sufficient ETH on mainnet. -1. Connect the Splits account at [app.ens.domains](https://app.ens.domains) via WalletConnect. -2. Choose a name and registration duration, leaving *Use as primary name* on so the name resolves to the account on registration. -3. Approve the registration in Splits: two transactions, separated by ENS's commit delay. + -## Transferring an existing name +## Register a name -If the name is currently in an EOA, update its profile and ETH address record *before* transferring: doing it from Splits afterward costs more transactions, especially at thresholds above 1. +1. Connect the Splits account to the ENS app through WalletConnect. +2. Select a name. +3. Select a registration duration. +4. Keep *Use as primary name* on. +5. Approve the first registration transaction in Splits. +6. Wait for the ENS commit delay. +7. Approve the second registration transaction. -1. Connected as the current holder, set the name's ETH address record to the Splits account. -2. Send the name to the Splits account. An ENS name is an NFT and transfers like [any other](/transactions/sends). -3. Reconnect to the ENS app as the Splits account and select *Set as primary name*. + + +## Transfer a name + +A name held by an EOA can have its profile and address record changed before transfer. This reduces later transactions from the Splits account. + +1. Connect the current name holder to the ENS app. +2. Set the name's ETH address record to the Splits account. +3. [Send the name](/transactions/sends) to the Splits account as an NFT. +4. Reconnect to the ENS app with the Splits account. +5. Select *Set as primary name*. ## Subnames -Subnames point one name at multiple accounts (e.g. `treasury.splits.eth`, `operating.splits.eth`), keeping one brand across the team's [accounts](/accounts). They work in any ENS-enabled app like any other name. +A **subname** extends a parent name, such as `treasury.splits.eth`. Separate subnames can identify different [accounts](/accounts). -1. Connected as the holder of the parent name, add a subname from the name's *Subnames* tab. -2. Set the subname's ETH address to the target Splits account. -3. Connect as that account and select *Set as primary name* on the subname. +1. Connect the parent name holder to the ENS app. +2. Open the name's *Subnames* tab. +3. Add a subname. +4. Set its ETH address record to the target Splits account. +5. Connect with that Splits account. +6. Select *Set as primary name* for the subname. diff --git a/src/pages/integrations/farcaster.mdx b/src/pages/integrations/farcaster.mdx index 00ef90c..e715b7e 100644 --- a/src/pages/integrations/farcaster.mdx +++ b/src/pages/integrations/farcaster.mdx @@ -1,29 +1,52 @@ --- -title: Farcaster -description: "Using a Splits account with Farcaster: verifying it on your profile, setting it as your recovery address, recovering a lost account, and paying for storage." +title: "Farcaster" +description: "Verify an address, configure recovery, and pay for storage" --- -# Farcaster [Verify, secure, and manage a Farcaster account with a Splits account] +# Farcaster [Verify an address, configure recovery, and pay for storage] -[Farcaster](https://farcaster.xyz) is a social network built on Ethereum. A Splits account can hold a Farcaster account's onchain roles: a verified address on your profile, the recovery address that backstops the account, and the payer for protocol storage. +**[Farcaster](https://farcaster.xyz)** is a social network with blockchain contracts. A Splits account can serve as a verified address, recovery address, or storage payer. -Farcaster's contracts live on Optimism, and you call them from Splits with [custom transactions](/transactions/custom). Contract behavior below is Farcaster's; see their [contract docs](https://docs.farcaster.xyz/reference/contracts/) for the authoritative reference. +[Farcaster's contract docs](https://docs.farcaster.xyz/reference/contracts/) describe contract behavior. Splits can call the Optimism contracts through [custom transactions](/transactions/custom). -## Verifying an address + -Farcaster verifies a contract account by checking an [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) signature **on Ethereum mainnet**, and doesn't accept [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) signatures from not-yet-deployed contracts. A Splits account produces a plain EIP-1271 signature only once deployed, so before verifying: +## Verify an address -1. Enable Ethereum Mainnet for your team in [Settings > Networks](https://app.splits.org/settings/team/networks/). -2. Deploy the account on mainnet by sending any transaction from it there. Accounts deploy on their first transaction per network; once deployed, Etherscan shows a *Contract* tab at the account's address. +Farcaster checks contract signatures through [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) on Ethereum Mainnet. It does not accept [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) signatures from undeployed accounts. -Then connect the account at [farcaster.xyz > Settings > Verified addresses](https://farcaster.xyz/~/settings/verified-addresses) using the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect), and sign the verification message. +A Splits account needs deployment before it can supply a plain EIP-1271 signature. + +1. Activate Ethereum Mainnet in [Settings > Networks](https://app.splits.org/settings/team/networks/). +2. Send a transaction from the account on mainnet to deploy it. +3. Confirm that Etherscan shows a *Contract* tab for the account address. +4. Open [Farcaster > Settings > Verified addresses](https://farcaster.xyz/~/settings/verified-addresses). +5. Connect through the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect). +6. Sign the verification message. ## Recovery address -A Farcaster account is controlled by two addresses: a **custody address** that owns the account and authorizes apps, and a **recovery address** that can move the account to a new custody address. The Farcaster app manages both by default; pointing the recovery address at a Splits account (Farcaster app: *Settings > Advanced > Change recovery address*) puts your social account behind the same signers and [threshold](/accounts/thresholds) as your funds. +A Farcaster **custody address** controls the account and app permissions. A **recovery address** can transfer it to a new custody address. The Farcaster app manages both by default. + +To use a Splits account for recovery: + +1. In Farcaster, open *Settings > Advanced > Change recovery address*. +2. Set the Splits account as the recovery address. + +The Splits account's signers and [threshold](/accounts/thresholds) then control its recovery actions. -If you lose the custody address, recover from Splits by calling `recover` on the [IdRegistry](https://docs.farcaster.xyz/reference/contracts/reference/id-registry) (`0x00000000Fc6c5F01Fc30151999387Bb99A9f489b` on Optimism) via a custom transaction. Two requirements: the new custody address must sign an EIP-712 `Transfer` message accepting the move, and it must not already own a Farcaster ID. +Recovery calls use `recover` on the [IdRegistry](https://docs.farcaster.xyz/reference/contracts/reference/id-registry). Its address on Optimism is `0x00000000Fc6c5F01Fc30151999387Bb99A9f489b`. + +The new custody address must sign an EIP-712 `Transfer` message to accept the transfer. **It must not already own a Farcaster ID.** ## Storage -Farcaster charges yearly rent for the space an account's messages use. Pay it from a Splits account by calling `rent(fid, units)` on the [StorageRegistry](https://docs.farcaster.xyz/reference/contracts/reference/storage-registry) (`0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D` on Optimism) via a custom transaction, with *Amount to pay* set to the unit price in ETH (`unitPrice()` on the contract; excess is refunded). +Farcaster charges annual storage rent. A custom transaction can call `rent(fid, units)` on the [StorageRegistry](https://docs.farcaster.xyz/reference/contracts/reference/storage-registry). + +The contract address on Optimism is `0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D`. Its `unitPrice()` function gives the price in ETH. + +1. Prepare the rent call with the Farcaster ID and number of units. +2. Set *Amount to pay* to cover the required rent. +3. Submit the transaction. + +The contract returns excess ETH. diff --git a/src/pages/integrations/hedgey.mdx b/src/pages/integrations/hedgey.mdx index 79c356e..68bce22 100644 --- a/src/pages/integrations/hedgey.mdx +++ b/src/pages/integrations/hedgey.mdx @@ -1,10 +1,17 @@ --- -title: Hedgey -description: Hedgey vesting and lockup streams paying a Splits account appear as positions on the account's page, where you view and claim them. +title: "Hedgey" +description: "View and claim vesting and lockup tokens" --- -# Hedgey [View and claim Hedgey vesting and lockup streams] +# Hedgey [View and claim vesting and lockup tokens] -[Hedgey](https://hedgey.finance) issues onchain token vesting and lockup plans. Plans paying a Splits account appear as [positions](/integrations#positions) on the account's page, showing the claimable amount and, when the plan has a cliff, its unlock date. +**[Hedgey](https://hedgey.finance)** provides token vesting and lockup plans. Plans that pay a Splits account appear as [positions](/integrations#positions). -Claiming moves the vested tokens into the account. Each position links to the plan on Hedgey; viewing full details there requires connecting the account to Hedgey's site (via [WalletConnect](/integrations/walletconnect) or the [extension](/introduction/extension)). +The position shows the amount available to claim. If the plan has a cliff, it also shows the first release date. + +A claim transfers available tokens into the account. Each position links to its Hedgey plan. + +For full plan information: + +1. Open the position's Hedgey link. +2. Connect the account through [WalletConnect](/integrations/walletconnect) or the [browser extension](/introduction/extension). diff --git a/src/pages/integrations/index.mdx b/src/pages/integrations/index.mdx index 1eec093..97c73d6 100644 --- a/src/pages/integrations/index.mdx +++ b/src/pages/integrations/index.mdx @@ -1,31 +1,34 @@ --- -title: Integrations -description: How Splits connects to other apps, protocols, and platforms, including position integrations that surface claimable balances on account pages. +title: "Integrations" +description: "Connect Splits accounts to other services" --- -# Integrations [Using Splits accounts with other apps, protocols, and platforms] +# Integrations [Connect Splits accounts to other services] -Splits accounts plug into the rest of the ecosystem through two generic paths: +An **integration** connects Splits to another app or protocol. -- **Apps**: connect an account to any third-party app via the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect); the app sees Splits as a wallet. -- **Agents and scripts**: operate Splits programmatically via the [CLI / MCP](/introduction/agents). +The [browser extension](/introduction/extension) and [WalletConnect](/integrations/walletconnect) connect accounts to other apps. The [CLI and MCP](/introduction/agents) provide access for agents and scripts. -Some integrations have behavior of their own: - -| Integration | What it is | +| Integration | Function | | --- | --- | -| [Bankr](/integrations/bankr) | Let a Bankr agent operate your treasury through the CLI | -| [Clanker](/integrations/clanker) | Claim token rewards | -| [ENS](/integrations/ens) | Register or transfer an ENS name to an account | -| [Farcaster](/integrations/farcaster) | Verify a Splits account on your profile, use it as your recovery address, pay for storage | -| [Hedgey](/integrations/hedgey) | Claim vesting and lockup streams | -| [Rain](/integrations/rain) | Corporate cards collateralized by stablecoins from your accounts | -| [Sablier](/integrations/sablier) | Claim vesting streams | -| [Uniswap](/integrations/uniswap) | Claim LP fees | -| [WalletConnect](/integrations/walletconnect) | Connect accounts to apps when the extension can't run | +| [Bankr](/integrations/bankr) | Account operations through an agent | +| [Clanker](/integrations/clanker) | Token reward claims | +| [ENS](/integrations/ens) | Names for account addresses | +| [Farcaster](/integrations/farcaster) | Address verification, recovery, and storage payments | +| [Hedgey](/integrations/hedgey) | Vesting and lockup claims | +| [Rain](/integrations/rain) | Card collateral and payment records | +| [Sablier](/integrations/sablier) | Vesting claims | +| [Uniswap](/integrations/uniswap) | Liquidity position fee claims | +| WalletConnect | Account connections | ## Positions -Claimable balances an account holds in external protocols appear under **Positions** on the account's page, next to its token balances: vesting and lockup streams ([Hedgey](/integrations/hedgey), [Sablier](/integrations/sablier)) and LP rewards ([Uniswap](/integrations/uniswap), [Clanker](/integrations/clanker)). +A **position** is an account's balance or claim in an external protocol. The *Positions* section on an account page shows supported positions beside token balances. + +Supported positions include vesting streams, lockup plans, and liquidity provider rewards. + +A claim transfers available tokens to the account. It follows the normal [transaction](/transactions) approval process at the account's [threshold](/accounts/thresholds). + +If the protocol charges a claim fee, the claim dialog shows it. **The account needs enough of the fee token to claim.** -Claiming a position is a normal [transaction](/transactions), approved at the account's [threshold](/accounts/thresholds). When a protocol charges a claim fee, it's shown in the claim dialog, and the account must hold enough of the fee token to claim. Email support to request another protocol. +Email support to request another protocol. diff --git a/src/pages/integrations/rain.mdx b/src/pages/integrations/rain.mdx index 57c6a39..fb74e76 100644 --- a/src/pages/integrations/rain.mdx +++ b/src/pages/integrations/rain.mdx @@ -1,25 +1,32 @@ --- -title: Rain -description: "Fund and track Rain Spend corporate cards from Splits: stablecoin collateral deposits, the Cards page, and what Splits does and doesn't control." +title: "Rain" +description: "Fund corporate cards with stablecoin collateral" --- -# Rain [Corporate cards collateralized by stablecoins from your accounts] +# Rain [Fund corporate cards with stablecoin collateral] -[Rain](https://www.rain.xyz/) issues corporate cards backed by onchain stablecoin collateral ([Rain Spend](https://www.rain.xyz/rain-spend)). You deposit stablecoins up front; at the end of each billing cycle your spend is deducted from the collateral, and the remainder carries forward as the next month's available spend. +**[Rain](https://www.rain.xyz/)** issues corporate cards backed by stablecoin collateral through [Rain Spend](https://www.rain.xyz/rain-spend). Card payments reduce this collateral at the end of a billing cycle. Remaining collateral carries into the next cycle. -Splits' role is funding and visibility: connect an account, deposit USDC, and track collateral and card spending on the *Cards* page. Rain controls card issuance, limits, fees, and liquidation mechanics; for those, contact Rain's support. +Splits provides collateral deposits and records on the *Cards* page. Rain controls card issuance, limits, fees, and liquidation. Rain support handles those subjects. ## Setup -1. Create a dedicated [operating account](/accounts#operating-accounts) for Rain collateral, so card funding stays a clean sub-ledger. -2. Complete Rain's KYB at [use.rain.xyz/signup](https://use.rain.xyz/signup) (have your EIN and formation documents ready). -3. In Rain's wallet settings, link the Splits account via [WalletConnect](/integrations/walletconnect) and sign the verification message. Rain doesn't handle the browser extension well during setup; if linking misbehaves, remove and re-link the account. -4. Copy the Base collateral contract address from Rain's *Smart Contracts* page, and add it in Splits under *Cards*. +1. Create a dedicated [operating account](/accounts#operating-accounts) for card collateral. +2. Complete Rain's business verification at [use.rain.xyz/signup](https://use.rain.xyz/signup). +3. In Rain's wallet settings, connect the Splits account through [WalletConnect](/integrations/walletconnect). +4. Sign the verification message. +5. Copy the Base collateral contract address from Rain's *Smart Contracts* page. +6. Add the collateral address under *Cards* in Splits. -## Funding +Rain setup can have browser extension connection problems. If the connection fails, remove it and connect again through WalletConnect. -Deposit collateral from the *Cards* page: a [send](/transactions/sends) of **USDC on Base** to the collateral address. + -**Collateral addresses are network- and token-specific: sending any other token, or on any other network, loses the funds.** +## Deposit collateral -Deposits and card spending appear in the Cards feed in Splits and in Rain under *Recent transactions*, and each deposit raises the card's monthly spend limit. +**A transfer with the wrong token or network can cause loss of funds.** The collateral address requires USDC on Base. + +1. Open the *Cards* page. +2. [Send](/transactions/sends) USDC on Base to the collateral address. + +Deposits and card payments appear in the Splits Cards feed and Rain's *Recent transactions*. A deposit increases the card's available monthly amount. diff --git a/src/pages/integrations/sablier.mdx b/src/pages/integrations/sablier.mdx index 972cee5..a80869d 100644 --- a/src/pages/integrations/sablier.mdx +++ b/src/pages/integrations/sablier.mdx @@ -1,10 +1,14 @@ --- -title: Sablier -description: Sablier v2 and v3 vesting streams paying a Splits account appear as positions on the account's page, where you view and claim them; v3 charges a claim fee. +title: "Sablier" +description: "View and claim vesting tokens" --- -# Sablier [View and claim Sablier vesting streams] +# Sablier [View and claim vesting tokens] -[Sablier](https://sablier.com) streams token vesting onchain. Sablier v2 and v3 streams paying a Splits account appear as [positions](/integrations#positions) on the account's page, showing the claimable amount and, when the stream has a cliff, its unlock date. +**[Sablier](https://sablier.com)** provides token vesting streams. Streams from v2 and v3 that pay a Splits account appear as [positions](/integrations#positions). -Claiming moves the vested tokens into the account. **Sablier v3 charges a fee on every claim**, shown in the claim dialog; the account must hold enough of the fee token (ETH) to claim. Each position links to the stream on Sablier. +Each position shows the amount available to claim. If the stream has a cliff, it also shows the first release date. + +A claim transfers available tokens into the account. **Sablier v3 charges a fee for each claim.** The dialog shows the fee, and the account needs enough ETH to pay it. + +Each position links to the stream on Sablier. diff --git a/src/pages/integrations/uniswap.mdx b/src/pages/integrations/uniswap.mdx index 9a89c64..c0cc3ff 100644 --- a/src/pages/integrations/uniswap.mdx +++ b/src/pages/integrations/uniswap.mdx @@ -1,10 +1,10 @@ --- -title: Uniswap -description: LP positions opened with a Splits account on Uniswap v3 and v4 appear as positions on the account's page, where you claim their accrued fees. +title: "Uniswap" +description: "View and claim liquidity position fees" --- -# Uniswap [View and claim LP fees from Uniswap positions] +# Uniswap [View and claim liquidity position fees] -Liquidity positions opened with a Splits account on [Uniswap](https://uniswap.org) v3 and v4 appear automatically as [positions](/integrations#positions) on the account's page, showing the fees available to claim. +**[Uniswap](https://uniswap.org)** is a token exchange protocol. Liquidity positions created with a Splits account on Uniswap v3 and v4 appear automatically as [positions](/integrations#positions). -Claiming collects all of a position's accrued fees (one or two tokens) into the account in a single transaction. +The position shows available fees. A claim transfers all accumulated fees from the position to the account in one transaction. The fees can contain one or two tokens. diff --git a/src/pages/integrations/walletconnect.mdx b/src/pages/integrations/walletconnect.mdx index f38fee5..bed9b2e 100644 --- a/src/pages/integrations/walletconnect.mdx +++ b/src/pages/integrations/walletconnect.mdx @@ -1,19 +1,28 @@ --- -title: WalletConnect -description: Connect a Splits account to third-party apps over WalletConnect when the browser extension isn't an option, and sign the app's requests in Splits. +title: "WalletConnect" +description: "Connect an account through a pairing link" --- -# WalletConnect [The backstop for connecting accounts to third-party apps] +# WalletConnect [Connect an account through a pairing link] -[WalletConnect](https://walletconnect.network) connects a Splits account to a third-party app over a pairing link. **Prefer the [browser extension](/introduction/extension)**: it makes Splits appear directly as a wallet in the app. WalletConnect is the backstop for when the extension can't run: +**[WalletConnect](https://walletconnect.network)** connects a Splits account to another app through a pairing link. -- Browsers without extension support (e.g. Safari) -- Apps that don't support [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) provider discovery, so Splits never appears as a wallet option -- Apps known to mishandle the extension: Monerium, and [Rain](/integrations/rain) during setup +The [browser extension](/introduction/extension) provides a direct Splits wallet option. WalletConnect supports other connection cases: -## Connecting +- Browsers without extension support, such as Safari. +- Apps without [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) wallet discovery. +- Apps with extension connection problems, including Monerium and [Rain](/integrations/rain) setup. -1. In the third-party app, choose WalletConnect as the wallet and copy the pairing URI. -2. On the account's page in Splits, paste the URI into the WalletConnect section and approve the connection. + -The app's transaction and signature requests then appear in Splits, where you sign at the account's [threshold](/accounts/thresholds), the same as any transaction. Requests can also be [added to a batch](/transactions/batch#batching-from-external-apps). +## Connect an account + +1. In the other app, select WalletConnect as the wallet. +2. Copy the pairing URI. +3. Open the account page in Splits. +4. Enter the URI in the WalletConnect section. +5. Approve the connection. + +Transaction and signature requests then appear in Splits. The account's [threshold](/accounts/thresholds) determines the required approvals. + +Requests can also enter a [batch](/transactions/batch#batches-from-external-apps). diff --git a/src/pages/introduction/agents.mdx b/src/pages/introduction/agents.mdx index c3cf1e7..c3fb1ce 100644 --- a/src/pages/introduction/agents.mdx +++ b/src/pages/introduction/agents.mdx @@ -1,87 +1,147 @@ --- -title: Agents & API -description: "Operate Splits programmatically via the API or self-describing CLI, or connect AI tools over MCP: scopes, the proposal model, headless signing, and output tuning." +title: "Agents & API" +description: "Operate Splits through the API, CLI, or MCP" --- -# Agents & API [Operate Splits programmatically via the API, CLI, or MCP] +# Agents & API [Operate Splits through the API, CLI, or MCP] -The Splits API and CLI let applications, scripts, and agents operate Splits programmatically. The CLI also runs as an MCP server for AI tools (Claude Code, Cursor, Amp). +The **Splits API** provides access for applications and scripts. The **CLI** is its command-line interface. The CLI also provides a Model Context Protocol (MCP) server for AI tools. -**The CLI is self-describing, and it, not this page, is the reference for the command surface:** +The CLI describes its own commands: ```bash -npx @splits/splits-cli@latest --llms # machine-readable manifest of every command +npx @splits/splits-cli@latest --llms ``` -Every command answers `--help` and `--schema`, and over [MCP](#connect-to-ai-tools-mcp) each command is exposed as a tool with its schema. This page covers what the manifest doesn't: scopes, the proposal model, headless signing, and output tuning. +Each command accepts `--help` and `--schema`. The manifest is the command reference. MCP presents each command as a tool with an input schema. ## Get set up ### Get an API key -Create a key in [Settings > API Keys](https://app.splits.org/settings/team/api-keys/) with the scopes you need (the full matrix is in [Roles](/teams/roles#api-key-scopes)): +Prerequisites: your team role must permit the requested [API key scopes](/teams/roles#api-key-scopes). -- **Read**: query data only -- **Write**: create and propose transactions; update transaction memos and properties -- **Owner**: manage accounts and team settings +1. Open [Settings > API Keys](https://app.splits.org/settings/team/api-keys/). +2. Create an API key with the required scopes. +3. Copy the value that starts with `sk_`. +4. Store it securely. -Copy the key (it starts with `sk_`) and save it somewhere secure. You won't be able to see it again. +**The app does not show the API key again.** ### Install the CLI and sign in -```bash -npm install -g @splits/splits-cli@latest # or run via npx @splits/splits-cli@latest -splits auth login --api-key sk_... -splits auth whoami # verify: team, scopes, key source, local signing key -``` +1. Install the CLI: + + ```bash + npm install -g @splits/splits-cli@latest + ``` + +2. Configure the API key: + + ```bash + splits auth login --api-key sk_... + ``` + +3. Check the team, scopes, API key source, and local signing key: -`splits auth logout` removes the saved key. + ```bash + splits auth whoami + ``` + +`splits auth logout` removes the saved API key. You can also run the CLI with `npx @splits/splits-cli@latest`. ## Use the API -The Splits API is a versioned REST API at `https://api.splits.org/public/v1`. Authenticate API-key-protected endpoints with your key as a bearer token. +The REST API is at `https://api.splits.org/public/v1`. Protected endpoints accept an API key as a bearer token. -The [Swagger docs](https://api.splits.org/public/docs) are the source of truth for available endpoints, authentication requirements, parameters, request and response schemas, and examples. +The [API reference](https://api.splits.org/public/docs) defines endpoints, authentication, parameters, schemas, and examples. ## Connect to AI tools (MCP) -MCP exposes **every CLI command as a tool automatically**, including commands added later. +MCP provides a tool for each CLI command, including later additions. + +1. Add the MCP server to detected clients: + + ```bash + splits mcp add + ``` + +2. Ask the connected agent to run `splits auth whoami`. + +For a client with standard input and output transport, the server command is: ```bash -splits mcp add # auto-detects Claude Code, Cursor, and other clients +SPLITS_API_KEY=sk_... npx @splits/splits-cli --mcp ``` -For any stdio MCP client, run `SPLITS_API_KEY=sk_... npx @splits/splits-cli --mcp`. `splits skills add` syncs ready-made skill files for agents. Verify the connection by asking the tool to run `splits auth whoami`. +`splits skills add` installs the supplied agent skill files. ## The model -Everything visible in the app is queryable with the **Read** scope: accounts, balances, transactions (filterable by account, direction, amount, date, and memo), contacts, tokens, networks, members, settings, and automations. +The Read scope queries team data. This includes accounts, balances, transactions, contacts, tokens, networks, members, settings, and automations. + +Writes use the [transaction proposal model](/transactions). Transfer and custom commands create proposals with the Write scope. -Writes follow the same rule as the app: **a transaction is a [proposal](/transactions)** that executes only once signatures meet the account's threshold. `transactions create transfer` and `create custom` produce proposals (**Write** scope); signing happens in the web UI with a passkey, or headlessly with a [registered EOA](#sign-locally-with-an-eoa). **Owner**-scoped commands create and manage accounts and propose signer changes, whose approval always stays in the web UI. A brand-new team can be bootstrapped with `org create`; completion also happens in the web UI. +**API key scopes do not supply signing authority.** Signatures use passkeys in the app or a registered EOA in the CLI. + +Owner scope permits account management and proposals for signer changes. Signer change approval remains in the app. New team setup through `org create` also requires completion in the app. ## Transaction metadata -Every transaction can carry a [memo](/transactions/memos) (max 500 characters) and custom JSON properties (max 500 characters minified) for structured metadata, settable at creation or after: `transactions memo`, `transactions properties set/replace/clear` (**Write** scope). +A transaction can have a [memo](/transactions/memos) and JSON properties. Each has a maximum of 500 characters. The property limit counts minified JSON. + +The Write scope permits metadata at creation or through `transactions memo` and `transactions properties set/replace/clear`. ## Sign locally with an EOA -By default, signing happens in the web UI with a passkey. To operate headlessly, register a local EOA and sign from the CLI: +An **EOA** is an externally owned account controlled by a private key. CLI signatures use this key without a browser prompt. + +Prerequisites: signer changes need Owner scope and approval in the app. Transaction signatures need Write scope. + +1. Create and register a local EOA: + + ```bash + splits auth create-key --register + ``` + +2. Propose its addition to the account: -1. `splits auth create-key --register`: generate a local EOA (stored in `~/.splits/config.json`) and register it. Import an existing key instead with `echo $PRIVATE_KEY | splits auth import-key`. -2. `splits accounts update-signers --addEoaSignerIds ... --threshold N`: propose adding it as a signer (**Owner** scope). A human approves the proposal in the web UI, and the update applies to every active network. -3. `splits transactions sign `: sign pending proposals with the local key (**Write** scope). Auto-submits once the threshold is met. + ```bash + splits accounts update-signers --addEoaSignerIds ... --threshold N + ``` -`splits auth delete-key` removes the local key but does **not** revoke it onchain; remove it from accounts via `update-signers`. +3. Approve the signer change in the app. +4. Sign a pending proposal: + + ```bash + splits transactions sign + ``` + +The CLI stores the local signing key in `~/.splits/config.json`. An existing private key can enter through standard input to `splits auth import-key`. + +The signer change applies across active networks. The CLI submits a signed proposal when it reaches the threshold. + +**Local key deletion does not revoke onchain authority.** `splits auth delete-key` removes the local copy. Account signer removal requires `update-signers`. ## Tune output for agents -The CLI prints tables for humans and JSON when piped or under MCP. Global flags reshape output: `--format toon|json|yaml|md|jsonl` (`toon` is token-efficient for LLM context), `--filter-output ` to project specific fields, and `--token-count` / `--token-limit` / `--token-offset` to measure or budget output tokens. +An **output token** is a unit of text used to measure AI output. It differs from a blockchain token. + +The CLI displays tables for interactive use and JSON for pipes or MCP. Output options include: + +| Option | Function | +| --- | --- | +| `--format toon\|json\|yaml\|md\|jsonl` | Output format | +| `--filter-output ` | Field selection | +| `--token-count` | Token count | +| `--token-limit` | Output token limit | +| `--token-offset` | Output starting position | ## Examples -- "Show me the balances for all my accounts on Base" -- "Find all outbound payments to Acme between $4,500 and $5,500 last month" -- "Tag the last 5 outbound transfers with property `category=payroll`" -- "Create a new account called 'Ops' with a 2-of-3 threshold" -- "Send 100 USDC from my account on Base to 0x..." -- "Add my local EOA as a signer on the Ops account, then sign the pending transfer" +- Show all account balances on Base. +- Find outbound payments to Acme between $4,500 and $5,500 last month. +- Set `category=payroll` on the last five outbound transfers. +- Create an account named Ops with a 2-of-3 threshold. +- Propose a transfer of 100 USDC on Base. +- Add the local EOA as an Ops signer. diff --git a/src/pages/introduction/core-concepts.mdx b/src/pages/introduction/core-concepts.mdx index 6c1ada1..fb46dcc 100644 --- a/src/pages/introduction/core-concepts.mdx +++ b/src/pages/introduction/core-concepts.mdx @@ -1,31 +1,28 @@ --- -title: Core concepts -description: The three concepts Splits is built around (Team, Member, and Account) and how they relate to each other. +title: "Core concepts" +description: "Understand teams, members, and accounts" --- -# Core concepts [Team, Member, and Account: the three ideas everything else builds on] +# Core concepts [Understand teams, members, and accounts] -- **[Team](/teams)**: the unit everything is scoped to. One team per set of books: a company, an individual, or a project. -- **[Member](/members)**: a person who belongs to a team, with a role and their own signing keys. -- **[Account](/accounts)**: where a team's assets live, at the same address on every network, controlled by signers at a threshold. +Splits uses three core concepts: a **team**, a **member**, and an **account**. ## Team -Everything in the app (accounts, balances, transactions, bank info) except personal settings is scoped to the current team, shown in the team selector (top left). +A [team](/teams) groups the accounts and records for a company, an individual, or a project. The team selector at the top left shows the current team. Personal settings apply across teams. -A team maps to one set of books, whether or not a legal entity exists yet: a company, an individual, or a project that hasn't incorporated. Where an entity does exist, the mapping is one-to-one: on/offramps and compliant payments require [entity verification](/banking), and each team can have only one verified entity. A tax ID is a useful litmus test: Alice belongs to the Splits Labs team (EIN) and her own personal team (SSN), two tax IDs, two teams. A pre-entity project runs as its own team from day one and can [verify through an individual](/introduction/personal-usage#no-entity-needed) until it incorporates. +A team corresponds to one set of accounting records. A project can have a team before it has a legal entity. [Banking](/banking) describes entity verification. [Personal usage](/introduction/personal-usage#no-entity-needed) describes verification for an individual. -When deciding between a new team and an existing one, ask "who are the owners?" Same owners, existing team; different owners, new team. This matters for Recovery: see [Change recovery signers](/teams/recovery#changing-recovery-signers). To create a team, see [Teams](/teams). +For example, Alice can belong to her company's team and her personal team. These teams have separate accounting records. + +A change to the owners can require a separate team. [Recovery](/teams/recovery#change-recovery-signers) describes the effect of a change to recovery signers. ## Member -A member is a person who belongs to a [team](/teams). Members authenticate with email, and one member can belong to many teams. Every member has a [role](/teams/roles) (Owner or Member) that determines what they can administer, and holds **[signing keys](/members/keys)**: passkeys saved in the app, or EOAs [registered via the CLI](/introduction/agents#sign-locally-with-an-eoa). +A [member](/members) is a person who belongs to a team. [Roles](/teams/roles) control access to app functions. [Signing keys](/members/keys) belong to the member. -A member's signing keys can become [signers](/accounts/signers) on accounts; membership alone grants no signing authority. Signing keys, email, display name, and avatar belong to the member, not the team, so they work across every team a member is part of ([Settings > Personal](https://app.splits.org/settings/personal/general/)). For roles, inviting, and removal, see [Members](/members). +**Membership gives no onchain signing authority.** [Signers](/accounts/signers#signers-vs-membership) describes the relationship between membership and signing authority. ## Account -An account is where a team's assets are stored, at the same address on every network it's active on. Every account has a [threshold](/accounts/thresholds): a transaction executes once that many [signers](/accounts/signers) approve it. Signers are keys held by members; the public key lives onchain, the private key stays with the member, and **Splits never has access to private keys**. - -For account types and the ownership chain, see [Accounts](/accounts). - +An [account](/accounts) holds a team's assets. [Thresholds](/accounts/thresholds) describes the number of approvals for a transaction. [Accounts](/accounts) describes account types and the ownership chain. diff --git a/src/pages/introduction/extension.mdx b/src/pages/introduction/extension.mdx index 28821d6..1c6cb22 100644 --- a/src/pages/introduction/extension.mdx +++ b/src/pages/introduction/extension.mdx @@ -1,22 +1,35 @@ --- -title: Browser extension -description: Connect Splits accounts to third-party apps using the Splits Connect browser extension, or WalletConnect as a fallback. +title: "Browser extension" +description: "Connect Splits accounts to other apps" --- -# Browser extension [Use Splits accounts inside third-party apps] +# Browser extension [Connect Splits accounts to other apps] -[Splits Connect](https://chromewebstore.google.com/detail/splits/ghfacfafnbcgkielpaeifdpoggfeakif?utm_source=docs-extension&utm_medium=web) is our browser extension. It lets you use Splits accounts inside third-party apps: the app sees Splits as a wallet, and you sign transactions with your passkey. If your browser doesn't support the extension (e.g. Safari), [WalletConnect](/integrations/walletconnect) works as a fallback. The extension works on every [enabled network](/introduction/networks-and-assets). +**Splits Connect** is the Splits browser extension. It connects a Splits account to another app. The other app shows Splits as a wallet option. -## Connecting +[WalletConnect](/integrations/walletconnect) provides another connection method for browsers without extension support, such as Safari. The extension works on each [enabled network](/introduction/networks-and-assets). -1. Install the latest version of the [Splits Connect](https://chromewebstore.google.com/detail/splits/ghfacfafnbcgkielpaeifdpoggfeakif?utm_source=docs-extension&utm_medium=web) extension. -2. Open the third-party app and select Splits as the wallet. -3. Select the team and account you want to connect and hit *Connect*. The dialog closes on its own and the account appears in the third-party app. + -## Signing +## Connect an account -When you perform a transaction in the connected app, the Splits dialog opens and you sign with your passkey, the same way you'd sign any transaction in the Splits app. You can also add transactions from external apps to a [batch](/transactions/batch). +1. Install [Splits Connect](https://chromewebstore.google.com/detail/splits/ghfacfafnbcgkielpaeifdpoggfeakif?utm_source=docs-extension&utm_medium=web). +2. Open the other app. +3. Select Splits as the wallet. +4. Select the team and account to connect. +5. Select *Connect*. + +The dialog closes. The account appears in the other app. + + + +## Sign transactions + +When you request a transaction in the connected app, the Splits dialog opens. You sign with your passkey. You can also add the transaction to a [batch](/transactions/batch). ## Connection issues -If an app doesn't show Splits as a wallet option, the likely cause is that the app doesn't support [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) provider discovery. Use [WalletConnect](/integrations/walletconnect) instead, and ask the app's team whether they support EIP-6963. +An app can omit Splits from its wallet options if it does not support [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). This standard describes how apps find wallet providers. + +1. If Splits does not appear, connect through WalletConnect. +2. Ask the other app's team to confirm its EIP-6963 support. diff --git a/src/pages/introduction/networks-and-assets.mdx b/src/pages/introduction/networks-and-assets.mdx index 3f19b21..e7d2ab7 100644 --- a/src/pages/introduction/networks-and-assets.mdx +++ b/src/pages/introduction/networks-and-assets.mdx @@ -1,19 +1,25 @@ --- -title: Networks and assets -description: Networks and asset types supported by Splits, full and partial support, and how to enable networks per team. +title: "Networks and assets" +description: "Network capabilities and supported token types" --- -# Networks and assets [Supported networks and asset types] +# Networks and assets [Network capabilities and supported token types] -Splits supports some networks fully (all features available) and a longer tail partially (balances and sending work; accounting history, swaps, bridging, and NFTs may not be available). Owners turn networks on and off per team in [Settings > Networks](https://app.splits.org/settings/team/networks/) (see [Settings](/teams/settings)). Any listed network can be enabled, regardless of tier; **balances are shown for enabled networks only**. +A **network** is a blockchain on which an account can operate. Splits groups networks by their supported capabilities. -Enabled networks are kept in sync: an account has the same address, signers, threshold, and owner on all of them, because [signer changes are signed once for every active network](/accounts/thresholds#changing-signers-and-thresholds). An account's state is synced to a newly enabled network before the account is used there; disabling a network requires nothing onchain. +Owners control active networks in [Settings > Networks](https://app.splits.org/settings/team/networks/). [Settings](/teams/settings) describes access to this page. -Email support to request another network or asset type; most EVM-equivalent networks can be supported, pending our underlying providers. +**Balances appear only for active networks.** Any listed network can be active, regardless of its support group. + +Accounts use the same address, signers, threshold, and owner across active networks. [Signer changes](/accounts/editing#change-signers-and-thresholds) cover those networks with one signature. + +Splits applies account state to a newly active network before account use there. Network deactivation requires no onchain transaction. + +Email support to request another network or asset type. Additional network support depends on Splits' providers. ## Fully supported networks -Every feature works on these networks: +These networks support balances, transactions, swaps, transfers across networks, and accounting: - Ethereum Mainnet - Base @@ -28,7 +34,7 @@ Every feature works on these networks: ## Partially supported networks -On partially supported networks, you can manage balances, send funds, swap, and bridge assets. Transaction history and accounting are not available. NFT management is available only on Zora and Robinhood Chain. +These networks support balances, sends, swaps, and transfers across networks. **Transaction history and accounting are unavailable.** NFT management in this group works only on Zora and Robinhood Chain. - Linea - Zora @@ -37,28 +43,34 @@ On partially supported networks, you can manage balances, send funds, swap, and - Scroll - Robinhood Chain -## Sending to other networks + -Tokens can be bridged and sent to an external address on Solana and any EVM [Relay network](https://docs.relay.link/references/api/api_resources/supported-chains); see [Swaps](/transactions/swaps). +## Transfers to other networks + +A [swap](/transactions/swaps) can send tokens to an external address on Solana or an EVM network supported by Relay. + +[Relay's network reference](https://docs.relay.link/references/api/api_resources/supported-chains) lists destination support. ## Browser extension and WalletConnect -The [browser extension](/introduction/extension) and in-app [WalletConnect](/integrations/walletconnect) work on any enabled network, regardless of tier. +The [browser extension](/introduction/extension) and [WalletConnect](/integrations/walletconnect) work on any active network. ## Supported assets -- Native tokens (e.g. ETH) -- ERC-20 -- ERC-721 -- ERC-1155 +Supported asset types include: + +- Native tokens, such as ETH. +- ERC-20 tokens. +- ERC-721 tokens. +- ERC-1155 tokens. -All supported assets work on all fully supported networks. If you send an unsupported asset to a Splits account, contact us and we will attempt to make it recoverable. +NFT availability depends on the network's capabilities. If you send an unsupported asset to a Splits account, email support to investigate recovery. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits chains list`: every listed network with its tier and per-feature capability flags (**Read** scope) -- `splits chains get --chainId 8453`: one network's entry (**Read** scope) +- `splits chains list`: list networks, support groups, and capability flags (**Read** scope). +- `splits chains get --chainId 8453`: show one network (**Read** scope). -The underlying endpoint is `GET /public/v1/chains` (API key with **Read** scope); see [Use the API](/introduction/agents#use-the-api). Networks in gradual rollout are excluded until fully rolled out. +The endpoint is `GET /public/v1/chains`. It requires Read scope. The list excludes networks that still have a gradual release restriction. diff --git a/src/pages/introduction/personal-usage.mdx b/src/pages/introduction/personal-usage.mdx index ad25d38..75bc825 100644 --- a/src/pages/introduction/personal-usage.mdx +++ b/src/pages/introduction/personal-usage.mdx @@ -1,28 +1,30 @@ --- -title: Personal usage -description: Patterns from solo builders using Splits. Isolated accounts per project, resilient multisigs, no entity required, and agents as collaborators. +title: "Personal usage" +description: "Use Splits as an individual" --- -# Personal usage [Patterns from solo builders running Splits as a team of one] +# Personal usage [Use Splits as an individual] -Splits works for teams of one. A solo builder gets the same setup as a company (accounts, banking, accounting), and it scales when collaborators (human or [agent](/introduction/agents)) join later. Patterns we see from solo builders: +A **personal team** is a Splits [team](/teams) for one individual. It can contain separate [accounts](/accounts) for personal funds and projects. ## Isolated accounts -A dedicated [account](/accounts) per project, experiment, or revenue source. If the project flops, close the account; if it succeeds, it can become the start of a new team. Separate accounts per asset type (stables, investments, NFTs) also keep books clean while still exporting as [one CSV](/accounting). +Separate accounts can group funds by project, source of income, or asset type. [Accounting](/accounting) describes exports across accounts. ## Multisigs of one -1-of-2 or 2-of-3 accounts where every [signer](/accounts/signers) belongs to the same person: a hardware key plus a password manager, or two devices in different locations. No single device, browser profile, or key is a point of failure. +One person can control multiple [signers](/accounts/signers) on an account. For example, that person can use separate devices for a 2-of-3 [threshold](/accounts/thresholds). + +[Signing keys](/members/keys) describes passkeys and external signing keys. [Recovery](/teams/recovery) describes how to restore account access. ## Multiple emails -Two members on one team, registered with different emails (e.g. Gmail and Proton), each with its own passkeys. Moving large amounts then requires access to both inboxes. +One person can register two members with different email addresses. Each member can have separate signing keys. Account signers and thresholds determine the approvals for payments. ## No entity needed -Before incorporating, connect a personal bank account for [on/offramps](/banking) (KYC as an individual). Pay vendors, reimburse yourself, and keep a clean paper trail; when the project incorporates, swap in company banking. Verifying early also means less history to document later. +An individual can use personal identity verification for [banking](/banking). A project can use a personal bank account before incorporation. The [entity formation resources](/resources/incorporating-and-raising-capital) link to providers for later setup. ## Agents as collaborators -Delegate operations to AI tools via the [CLI, API, and MCP server](/introduction/agents): scoped API keys and optional headless signing let an agent work your treasury while you keep custody. +An agent can use the [CLI, API, and MCP server](/introduction/agents) on behalf of a personal team. API key scopes and account signers control different permissions. diff --git a/src/pages/invoicing/index.mdx b/src/pages/invoicing/index.mdx index 29e7f23..10c1c31 100644 --- a/src/pages/invoicing/index.mdx +++ b/src/pages/invoicing/index.mdx @@ -1,35 +1,51 @@ --- -title: Invoicing -description: "Onchain invoicing in Splits: issuing an invoice, requesting tokens, enabling pay-by-bank, plus recurring invoices, payment, and tracking." +title: "Invoicing" +description: "Request payment into a team account" --- -# Invoicing [Request payment into the team's accounts, in crypto or by bank transfer] +# Invoicing [Request payment into a team account] -An invoice requests a single amount of a single token into one of the team's accounts (there are no line items), payable in crypto or, if enabled, [by bank transfer](#pay-by-bank). Invoicing is free: no fee on issuing or on crypto payments. Only [Owners](/teams/roles) create and manage invoices; any member can view them. +An **invoice** requests one amount of one token into a team account. **Invoices have no line items.** Payment can use tokens or [bank transfer](#pay-by-bank). -See also [Recurring](/invoicing/recurring), [Paying](/invoicing/paying), and [Tracking](/invoicing/tracking). +Splits charges no fee to issue an invoice or pay it with tokens. [Owners](/teams/roles) can create and manage invoices. Any member can view them. -## Issuing +[Recurring](/invoicing/recurring), [Paying](/invoicing/paying), and [Tracking](/invoicing/tracking) describe related procedures. -From the *Invoices* page, select *Create*: + -1. Enter the payee and payer names, the amount and token, and the issue and due dates. A memo and file attachments are optional. -2. Pick the receiving account: any of your [accounts](/accounts), including an [automation](/accounts#automation-accounts)'s deposit address. -3. The payer's email is optional. With it, the invoice is emailed on creation; without it, you get the invoice link to share yourself. +## Issue an invoice -Invoice numbers increment automatically per team. +Prerequisites: you must have the Owner role. + +1. Open the *Invoices* page. +2. Select *Create*. +3. Enter the payer and payee names. +4. Enter the amount and token. +5. Enter the issue and due dates. +6. Select the receiving [account](/accounts). +7. If necessary, add a memo or file attachments. +8. If you want Splits to email the invoice, enter the payer's email address. +9. Create the invoice. + +The receiving account can be an automation account. Without a payer email address, the form provides a link for you to share. + +Invoice numbers increase automatically within each team. ## Pay-by-bank -Pay-by-bank lets the invoicee pay in fiat from their bank account. It requires the team to set up [a virtual account in the matching currency](/banking) and request the token: +**Pay-by-bank** lets a payer use bank currency for an invoice. The team needs a virtual account in the matching currency through [Banking](/banking). + +| Invoice token | Networks | Bank currency | +| --- | --- | --- | +| USDC | Base, Ethereum Mainnet, Tempo | USD | +| EURC | Base, Ethereum Mainnet | EUR | -- **USDC** on Base, Ethereum Mainnet, or Tempo, payable in USD -- **EURC** on Base or Ethereum Mainnet, payable in EUR +To permit bank payment, turn on *Allow pay by bank* in the invoice form. -Turn on the *Allow pay by bank* toggle that appears in the create form. The payer covers a 0.25% processing fee, added to the bank amount owed. +The payer pays a **0.25% processing fee** in addition to the invoice amount. -Bank payments settle to the account and network configured in the bank settings, even when the invoice specifies a different account or network. +**Bank settings determine the destination account and network.** These can differ from the account and network on the invoice. ## Programmatic access -Invoicing is web-only today: the [CLI / MCP](/introduction/agents) has no invoice commands. +Invoicing is available only in the app. The [CLI and MCP](/introduction/agents) have no invoice commands. diff --git a/src/pages/invoicing/paying.mdx b/src/pages/invoicing/paying.mdx index 8eb3065..30bd782 100644 --- a/src/pages/invoicing/paying.mdx +++ b/src/pages/invoicing/paying.mdx @@ -1,20 +1,34 @@ --- -title: Paying invoices -description: "The ways an invoicee pays a Splits invoice: connecting a wallet, sending manually and recording the transaction, or paying by bank transfer." +title: "Paying invoices" +description: "Pay with a wallet, manual transfer, or bank transfer" --- -# Paying invoices [How an invoicee pays: wallet, manual send, or bank] +# Paying invoices [Pay with a wallet, manual transfer, or bank transfer] -The invoice page offers the payer up to three payment paths. The payment address shown is the receiving account itself; there is no intermediary. +An **invoice payment** transfers funds to settle an invoice. The invoice page can offer three payment methods. + +The token payment address is the receiving account itself. ## Connect a wallet -*Connect wallet to pay* opens the payer's wallet with the transfer prepared. The wallet is switched to the invoice's network before sending, and the invoice is [marked paid](/invoicing/tracking) automatically once the transaction confirms. +1. Select *Connect wallet to pay*. +2. Connect your wallet. +3. Approve the prepared transfer. + +The wallet changes to the invoice's network before the transfer. Splits [marks the invoice paid](/invoicing/tracking) after transaction verification. ## Send manually -The payer copies the payment address, sends the amount on the invoice's network from anywhere, then pastes the transaction link on the invoice page. Splits verifies the transaction onchain and marks the invoice paid. +1. Copy the payment address from the invoice. +2. Send the requested token amount on the invoice's network. +3. Copy the transaction link. +4. Enter the link on the invoice page. + +Splits verifies the onchain transaction and marks the invoice paid. ## Pay by bank -If [pay-by-bank](/invoicing#pay-by-bank) is enabled on the invoice, a *Bank* tab shows the transfer details for paying in fiat, including the 0.25% processing fee added to the amount owed. +If the invoice permits [pay-by-bank](/invoicing#pay-by-bank), the *Bank* tab shows transfer details. The bank amount includes the processing fee. + +1. Open the *Bank* tab. +2. Send a bank transfer with the displayed details and amount. diff --git a/src/pages/invoicing/recurring.mdx b/src/pages/invoicing/recurring.mdx index 1864572..11839dc 100644 --- a/src/pages/invoicing/recurring.mdx +++ b/src/pages/invoicing/recurring.mdx @@ -1,12 +1,30 @@ --- -title: Recurring invoices -description: Issue the same invoice on a weekly or monthly schedule, with due dates relative to each issue date, until paused. +title: "Recurring invoices" +description: "Issue invoices at weekly or monthly intervals" --- -# Recurring invoices [Issue the same invoice on a schedule] +# Recurring invoices [Issue invoices at weekly or monthly intervals] -When [issuing an invoice](/invoicing), turn on *Make recurring* and pick a weekly or monthly interval. Each occurrence creates a new invoice with the due date set the same number of days after its issue date, and emails the payer if the invoice has their email. +A **recurring invoice schedule** creates invoices at weekly or monthly intervals. Each invoice has a due date relative to its issue date. -Schedules run until paused: there is no end date or occurrence limit. If the first issue date is in the future, only the schedule is created, and the first invoice generates on that date. If the issue date is today or in the past but its due date has not passed, the first invoice is issued when the schedule is created. If the first due date has passed, Splits either issues that occurrence at the next available run or skips it and advances to the next occurrence; the form previews which will happen before creation. New occurrences generate daily at 11:00 UTC. +Prerequisites: you must have the [Owner](/teams/roles) role. -View, edit, and pause schedules from the *Schedules* tab on the *Invoices* page. Like all invoicing, managing schedules requires the [Owner](/teams/roles) role. +1. Open the [invoice creation form](/invoicing). +2. Turn on *Make recurring*. +3. Select a weekly or monthly interval. +4. Check the first occurrence in the preview. +5. Create the schedule. + +Splits emails each invoice if the schedule includes the payer's email address. + +**Schedules have no end date or occurrence limit.** They continue until paused. The daily run creates new occurrences at 11:00 UTC. + +The first issue date controls the initial invoice: + +| First occurrence | Result | +| --- | --- | +| Future issue date | Schedule now, invoice on that date | +| Issue date reached, due date pending | Invoice at schedule creation | +| Due date passed | Late issue or next occurrence, as shown in the preview | + +The *Schedules* tab on the *Invoices* page provides controls to view, edit, and pause schedules. diff --git a/src/pages/invoicing/tracking.mdx b/src/pages/invoicing/tracking.mdx index 854828e..7e56790 100644 --- a/src/pages/invoicing/tracking.mdx +++ b/src/pages/invoicing/tracking.mdx @@ -1,10 +1,18 @@ --- -title: Tracking payments -description: When Splits marks an invoice paid automatically, how to mark one paid manually, and how transactions get attached for bookkeeping. +title: "Tracking payments" +description: "Verify payments and attach transaction records" --- -# Tracking payments [When invoices get marked paid, and attaching transactions] +# Tracking payments [Verify payments and attach transaction records] -An invoice is marked paid automatically when the payer completes payment through the invoice page (by [connecting a wallet or submitting a transaction link](/invoicing/paying)) and Splits verifies the transaction onchain. Verification requires a single transaction covering the full amount: overpayment is accepted, underpayment is rejected, and there are no partial payments. The verified transaction is attached to the invoice automatically. +**Payment tracking** records whether an invoice has a payment. Splits automatically marks an invoice paid after it verifies a transaction submitted through the [invoice page](/invoicing/paying). -For payments that arrive any other way (including bank transfers), mark the invoice paid from the *Invoices* page. You can attach one or more transactions for bookkeeping; manually attached transactions are not amount-checked. Reverting an invoice from paid detaches its transactions. +Verification requires one transaction for the full amount. It accepts overpayments and rejects underpayments. **Automatic verification does not combine partial payments.** Splits attaches the verified transaction to the invoice. + +For other payments, including bank transfers: + +1. Open the *Invoices* page. +2. Mark the invoice paid. +3. If necessary, attach one or more transactions for accounting records. + +**Manual attachments have no amount check.** A change from paid status removes the attached transactions. diff --git a/src/pages/members/index.mdx b/src/pages/members/index.mdx index 1dbb5fe..61b7b7a 100644 --- a/src/pages/members/index.mdx +++ b/src/pages/members/index.mdx @@ -1,44 +1,56 @@ --- -title: Members -description: "What a member is in Splits: one person across many teams, with their own profile and keys and a role per team, and how members are invited and removed." +title: "Members" +description: "People, profiles, team membership, and invitations" --- -# Members [One person across many teams, with their own profile, keys, and a role per team] +# Members [People, profiles, team membership, and invitations] -A **member** is a person in Splits, identified by their email address. One member can belong to any number of [teams](/teams), and holds two kinds of things: +A **member** is a person in Splits with an email address. One member can belong to multiple [teams](/teams). -- **Across every team**: their profile (display name, email, avatar) and their [signing keys](/members/keys) (passkeys and registered EOAs). These belong to the member, not to any team. -- **Per team**: a membership, stored offchain, with a [role](/teams/roles) (Owner or Member), granting read access to that team's accounts, balances, and transactions plus the role's administrative capabilities. +A member has two types of information: -Membership confers **no onchain authority**: a member cannot move funds unless one of their signing keys has been added to an account's signer set, making them a [signer](/accounts/signers#signers-vs-membership) on that account. +- **Personal information**: a display name, email address, avatar, and [signing keys](/members/keys). These belong to the member across teams. +- **Team membership**: an offchain record with a [role](/teams/roles). The role controls the member's access to app functions in that team. -## Signing up +Membership gives **no onchain authority**. A member's signing key needs a separate account assignment to become a [signer](/accounts/signers#signers-vs-membership). -Members authenticate by email: enter yours at [app.splits.org](https://app.splits.org) and click the emailed sign-in link, or sign in with a passkey once you've saved one. Most members register by [accepting an invite](#inviting); registering directly leads into [creating a team](/teams#creating-a-team). + -## Inviting +## Sign up -Prerequisites: you must be an **Owner** of the team. +1. Enter your email address at [app.splits.org](https://app.splits.org). +2. Open the sign-in link in the email. -1. Go to [Settings > Members](https://app.splits.org/settings/team/members/) and select *Invite member*. -2. Enter the invitee's email address and select their role. -3. The invitee receives an email with a join link (it may land in spam). If they're new to Splits, they register as part of accepting. -4. During acceptance, the invitee is prompted to save a passkey: they need at least one [signing key](/members/keys) before they can be added to an account's signer set. -5. You receive an email confirmation once they've accepted. +You can also sign in with a saved passkey. New members can register through an [invitation](#invite-a-member). Direct registration starts [team creation](/teams#create-a-team). -To give the new member signing power on an account, continue to [Changing signers](/accounts/editing#changing-signers-and-thresholds). + + +## Invite a member + +Prerequisites: you must have the Owner role in the team. + +1. Open [Settings > Members](https://app.splits.org/settings/team/members/). +2. Select *Invite member*. +3. Enter the person's email address. +4. Select their role. + +The person receives an email with a join link. The email can appear in the spam folder. New members register when they accept the invitation. + +The app asks the person to save a passkey. They need a signing key before an Owner can add it to an account's signer set. You receive an email after the person accepts. + +[Changing signers](/accounts/editing#change-signers-and-thresholds) describes how to give the member signing authority on an account. ## Role changes and removal -Owners can change a member's role, remove a member, or revoke a pending invitation from the member's row in [Settings > Members](https://app.splits.org/settings/team/members/). +Owners can change roles, remove members, or cancel pending invitations from the member's row in *Settings > Members*. -Removing a member revokes their offchain access only. If their signing keys are signers on any account, remove them via a separate onchain [signer update](/accounts/editing#changing-signers-and-thresholds). +**Member removal revokes offchain access only.** A separate onchain signer update must remove the member's signing keys from each account. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits members list`: list all members of the team (requires **Read** scope) -- `splits members signers `: list a member's passkey IDs, needed when adding them as a signer via `accounts create` or `accounts update-signers` (requires **Read** scope) +- `splits members list`: list team members (**Read** scope). +- `splits members signers `: list a member's passkey IDs for account signer changes (**Read** scope). -Managing members (inviting, removing, changing roles) is web-only today. +Member invitations, removal, and role changes are available only in the app. diff --git a/src/pages/members/keys.mdx b/src/pages/members/keys.mdx index 96c151a..8868162 100644 --- a/src/pages/members/keys.mdx +++ b/src/pages/members/keys.mdx @@ -1,45 +1,66 @@ --- -title: Signing keys -description: "The signing keys a member holds, passkeys and EOAs: password manager guidance, registering an EOA, verifying access, and troubleshooting." +title: "Signing keys" +description: "Passkeys and external keys that members use to sign" --- -# Signing keys [The passkeys and EOAs a member signs with, across every team] +# Signing keys [Passkeys and external keys that members use to sign] -A member's signing keys are what sign transactions: **passkeys** saved in the app, or **EOAs** registered via the CLI. Keys belong to the [member](/members), not to any team, so they work across every team the member is part of. The private key stays with the member; **Splits never has access to private keys.** +A **signing key** produces a signature for a member. Splits supports passkeys saved in the app and externally owned accounts (EOAs) registered through the CLI. -A key on its own moves nothing. It gains signing authority only when added to an account's signer set, becoming one of that account's [signers](/accounts/signers), counted against the account's [threshold](/accounts/thresholds). Managing keys here never edits any account's signer set; see [Signers](/accounts/signers) for that boundary and [Editing](/accounts/editing) for the onchain flow. +Signing keys belong to the [member](/members) across teams. The member keeps the private key. **Splits has no access to the member's private keys.** + +A signing key becomes a [signer](/accounts/signers) when an account's signer set includes it. [Editing](/accounts/editing) describes changes to that set. ## Passkeys -[Passkeys](https://www.dashlane.com/blog/what-is-a-passkey-and-how-does-it-work) are key pairs saved in a password manager, activated by biometrics or a PIN. Add them at [Settings > Personal > Passkeys](https://app.splits.org/settings/personal/passkeys/). +A **passkey** is a public and private key pair. A password manager or security device stores the private key. Biometrics or a PIN can permit its use. + +[This passkey explanation](https://www.dashlane.com/blog/what-is-a-passkey-and-how-does-it-work) gives more information. Passkey settings are at [Settings > Personal > Passkeys](https://app.splits.org/settings/personal/passkeys/). ### Password managers -- **iCloud Keychain and Google Password Manager** are the most reliable. 1Password, BitWarden, and LastPass also work. -- **Yubikeys** are the most reliable for higher-value accounts. -- **On mobile, use OS-native passkeys**: they sign without opening a third-party app. **Don't use 1Password as a mobile signer**: Android doesn't always show third-party apps when signing. +Password managers include iCloud Keychain, Google Password Manager, 1Password, Bitwarden, and LastPass. A YubiKey is a hardware security device. -Browsers order password manager options differently. If yours doesn't appear, click *Cancel*/*X* (Chrome) or *Other options* (Safari) to reveal the rest. +On mobile devices, operating system passkeys do not need to open another app. Android does not always show other password managers for signatures. + +Browsers show password manager options in different orders. If your manager does not appear, select *Cancel* or *X* in Chrome, or *Other options* in Safari. ## EOAs -An EOA (externally owned account, a standard Ethereum keypair) can be [registered via the CLI](/introduction/agents#sign-locally-with-an-eoa) and added as a signer, letting agents and servers sign headlessly. A team's [recovery signers](/teams/recovery#recovery-signers) are also EOAs. +An **EOA** is an externally owned account controlled by a private key. [CLI registration](/introduction/agents#sign-locally-with-an-eoa) lets an agent or server use its signing key. [Recovery signers](/teams/recovery#recovery-signers) also use EOAs. + + -## Verifying access +## Verify access -Confirm you still control a passkey's private key without moving funds: Splits has the key sign a test message. Go to *Settings > Personal > Passkeys* → three dots → *Verify access…*. Each passkey shows when it was last verified or used. +A test signature confirms access to a passkey without a funds transfer. -Recovery signers have their own verification flow; see [Verifying recovery signers](/teams/recovery#verifying-recovery-signers). +1. Open *Settings > Personal > Passkeys*. +2. Open the passkey's three-dot menu. +3. Select *Verify access…*. + +Each passkey shows its last verification or use. Recovery signers have a [separate verification procedure](/teams/recovery#verify-recovery-signers). ## Troubleshooting -**Passkey won't open.** Usually the password manager: 1Password and Bitwarden are finicky, especially in newer browsers (e.g. Arc/Dia), and 1Password's prompt appears in the browser's top-right corner. Isolate the passkey by [verifying it](#verifying-access), toggle the manager's extension off and on, and hard-refresh. Test at [webauthn.io](https://webauthn.io/): reproduces there → the provider; only on our site → email support. Slow Windows passkeys: delete temporary files (`%temp%` and `prefetch` via `Windows + R`) and reboot. If a passkey is unrecoverable, see [Recovery](/teams/recovery). +If a passkey does not open: + +1. Check the password manager for a pending prompt. +2. Follow [Verifying access](#verify-access). +3. Turn the password manager extension off. +4. Turn the extension on. +5. Reload the page. +6. Test the passkey at [webauthn.io](https://webauthn.io/). + +A failure on both sites can indicate a provider problem. A failure only in Splits requires support investigation. Email support with the result. + +[Recovery](/teams/recovery) describes the procedure for lost signing keys. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits auth register-signer
`: register an EOA so it can be added as a signer -- `splits members signers `: a member's passkey IDs, needed when adding them as a signer (**Read** scope) +- `splits auth register-signer
`: register an EOA for use as a signer. +- `splits members signers `: list a member's passkey IDs (**Read** scope). -For the full headless flow (create key → register → attach → sign), see [Sign locally with an EOA](/introduction/agents#sign-locally-with-an-eoa). +[Sign locally with an EOA](/introduction/agents#sign-locally-with-an-eoa) describes setup and signatures through the CLI. diff --git a/src/pages/resources/brand-assets.mdx b/src/pages/resources/brand-assets.mdx index 9202489..55ba10d 100644 --- a/src/pages/resources/brand-assets.mdx +++ b/src/pages/resources/brand-assets.mdx @@ -1,11 +1,15 @@ --- -title: Brand assets -description: Splits logos, wordmarks, and product screenshots, in light and dark variants in SVG and PNG, in the brand repo on GitHub. +title: "Brand assets" +description: "Download Splits logos, wordmarks, and product images" --- -# Brand assets [Logos, wordmarks, and screenshots for using the Splits brand] +# Brand assets [Download Splits logos, wordmarks, and product images] -All brand assets live in the [brand repo on GitHub](https://github.com/0xSplits/brand). +**Brand assets** are the logos, wordmarks, and product images for Splits. The [brand repository](https://github.com/0xSplits/brand) contains the files. -- **[Logos](https://github.com/0xSplits/brand/tree/main/logos)**: the Splits logo (`splits`), compressed mark (`splits_compressed`), and wordmark (`splits_wordmark`), each in light and dark variants, as SVG and PNG. -- **[Screenshots](https://github.com/0xSplits/brand/tree/main/screenshots)**: product screenshots from the Splits app. +| Asset | Files | Formats | +| --- | --- | --- | +| [Logos](https://github.com/0xSplits/brand/tree/main/logos) | `splits`, `splits_compressed`, `splits_wordmark` | SVG, PNG | +| [Product images](https://github.com/0xSplits/brand/tree/main/screenshots) | App screenshots | Image files | + +Logos have light and dark versions. diff --git a/src/pages/resources/glossary.mdx b/src/pages/resources/glossary.mdx new file mode 100644 index 0000000..5ed0e01 --- /dev/null +++ b/src/pages/resources/glossary.mdx @@ -0,0 +1,992 @@ +--- +title: Glossary +description: Technical terms used in the Splits docs +--- + +# Glossary [Technical terms used in the Splits docs] + +This **glossary** defines technical terms in these docs. Each entry links to the page that describes the related product behavior. + +## ABI + +Application binary interface. The definition of a contract's functions and encoded inputs. + +[Custom transactions](/transactions/custom). + +## account + +An address and contract that hold assets for a team. + +[Accounts](/accounts). + +## account owner + +The onchain account with authority over another account. + +[Accounts](/accounts). + +## accounting + +The preparation and maintenance of financial records. + +[Accounting](/accounting). + +## ACH + +Automated Clearing House. A US system for bank transfers. + +[Onramping](/banking/onramping). + +## address + +An identifier for an account or contract on a blockchain. + +[Contacts](/contacts). + +## agent + +Software that performs tasks on behalf of a person or team. + +[Agents & API](/introduction/agents). + +## allocation + +An amount or share assigned to a recipient. + +[PACT](/experiments/pact). + +## allowance + +A limit on the tokens that a spender can transfer. + +[Modules](/accounts/modules). + +## allowlist + +A list of addresses or items with permission for a function. + +[Modules](/accounts/modules). + +## API + +Application programming interface. A defined interface through which programs exchange requests and data. + +[Agents & API](/introduction/agents). + +## API key + +A credential that identifies API requests and their permitted scope. + +[Agents & API](/introduction/agents#get-an-api-key). + +## API key scope + +A permission assigned to an API key. + +[Roles](/teams/roles#api-key-scopes). + +## APY + +Annual percentage yield. An annual rate that includes the effect of accumulated interest. + +[Earn](/accounts/earn). + +## archive + +Remove an account from active app views without deleting its records. + +[Agents & API](/introduction/agents). + +## asset + +An item with value, such as a token. + +[Accounts](/accounts). + +## authenticate + +Confirm an identity or credential. + +[Agents & API](/introduction/agents). + +## Automated Earn + +The feature that deposits an account's available USDC into the Earn vault. + +[Earn](/accounts/earn#automated-earn). + +## automation account + +An account that applies a configured policy to deposits. + +[Accounts](/accounts#automation-accounts). + +## avatar + +An image that identifies a member. + +[Members](/members). + +## bank account + +An account at a bank for deposits and payments. + +[Banking](/banking). + +## banking + +Services that connect token transfers to bank accounts. + +[Banking](/banking). + +## batch + +A group of transactions prepared for execution together. + +[Batch](/transactions/batch). + +## bearer token + +A credential sent with a request to authorize access. + +[Agents & API](/introduction/agents#use-the-api). + +## blockchain + +A network record of transactions maintained by participating computers. + +[Networks and assets](/introduction/networks-and-assets). + +## bonding curve + +A rule that relates token price to the number of tokens sold. + +[PACT](/experiments/pact). + +## bridge transfer + +A transfer that moves value between blockchain networks. + +[Swaps](/transactions/swaps). + +## bug bounty + +A program that rewards eligible vulnerability reports. + +[Security & bug bounty](/resources/security). + +## calldata + +Encoded input sent to a smart contract function. + +[Custom transactions](/transactions/custom). + +## cap table + +A record of holders and their allocated shares or units. + +[PACT](/experiments/pact). + +## CLI + +Command-line interface. A program interface that accepts text commands. + +[Agents & API](/introduction/agents). + +## cliff + +The first date when a vesting plan releases tokens. + +[Integrations](/integrations#positions). + +## collateral + +Assets held to secure a loan or payment obligation. + +[Earn](/accounts/earn#risks). + +## command + +A text instruction to a program. + +[Agents & API](/introduction/agents). + +## compliance + +The collection of verified payee information and tax forms in Splits. + +[Compliance](/contacts/compliance). + +## configure + +Set the values that control a software function. + +[Agents & API](/introduction/agents). + +## contact + +A saved name for an external address. + +[Contacts](/contacts). + +## contract + +A program deployed at a blockchain address. + +[Custom transactions](/transactions/custom). + +## cost basis + +The acquisition value assigned to an asset for gain or loss calculations. + +[Accounting](/accounting). + +## CSV + +Comma-separated values. A text file format for rows and fields. + +[Accounting](/accounting). + +## custody address + +The address that controls a Farcaster account. + +[Farcaster](/integrations/farcaster#recovery-address). + +## custom transaction + +A transaction proposal with specified contract calls. + +[Custom transactions](/transactions/custom). + +## deploy + +Create a contract at a blockchain address. + +[Agents & API](/introduction/agents). + +## deposit + +Funds transferred into an account or vault. + +[Earn](/accounts/earn). + +## draft proposal + +A transaction prepared for approval that has not executed. + +[Schedules](/transactions/schedules). + +## Earn + +The Splits feature for stablecoin deposits that produce interest. + +[Earn](/accounts/earn). + +## ENS + +Ethereum Name Service. A system that associates names with blockchain addresses. + +[ENS](/integrations/ens). + +## EOA + +Externally owned account. A blockchain account controlled by a private key. + +[Signing keys](/members/keys#eoas). + +## ERC-1155 + +An Ethereum standard for multiple token types in one contract. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## ERC-20 + +The Ethereum standard for fungible tokens. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## ERC-4626 + +The Ethereum standard for tokenized vaults. + +[Earn](/accounts/earn). + +## ERC-721 + +An Ethereum standard for non-fungible tokens. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## ETH + +Ether. The native token of Ethereum. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## EUR + +The currency code for the euro. + +[Banking](/banking). + +## EURC + +Circle's euro stablecoin. + +[Paying vendors](/banking/paying-vendors). + +## execute + +Perform a transaction or contract call. + +[Agents & API](/introduction/agents). + +## executor + +An address that performs contract calls. + +[Modules](/accounts/modules). + +## experiment + +A prototype product from Splits. + +[Experiments](/experiments). + +## external account + +An address outside Splits that a team monitors. + +[Accounts](/accounts#external-accounts). + +## external bank account + +A bank account owned by a vendor or other recipient outside the team. + +[Paying vendors](/banking/paying-vendors). + +## fiat currency + +Money issued under a government's authority, such as USD or EUR. + +[Banking](/banking). + +## gas + +The measure of work required to execute a blockchain transaction. + +[Transactions](/transactions#gas-sponsorship). + +## gas sponsorship + +Payment of an account's transaction fees from a team allowance. + +[Transactions](/transactions#gas-sponsorship). + +## IBAN + +International bank account number. An identifier for a bank account. + +[Banking](/banking). + +## incorporation + +The creation of a legal entity. + +[Incorporating & raising capital](/resources/incorporating-and-raising-capital). + +## integration + +A connection between Splits and another app or protocol. + +[Integrations](/integrations). + +## invoice + +A request for payment of a specified amount. + +[Invoicing](/invoicing). + +## JSON + +JavaScript Object Notation. A text format for structured data. + +[Agents & API](/introduction/agents#transaction-metadata). + +## just-in-time swap + +A token exchange that supplies the requested token for a send. + +[Swaps](/transactions/swaps#just-in-time-swaps). + +## key pair + +A related public key and private key. + +[Signing keys](/members/keys). + +## KYB + +Know your business. Verification of a business's identity. + +[Banking](/banking#verify-an-entity). + +## KYC + +Know your customer. Verification of an individual's identity. + +[Banking](/banking#verify-an-entity). + +## legal entity + +A person or organization recognized by law as having rights and obligations. + +[Banking](/banking). + +## liquidation address + +A deposit address that a provider uses to convert tokens and pay a bank account. + +[Offramping](/banking/offramping#use-an-external-provider). + +## liquidity + +Funds available for a trade or withdrawal. + +[Earn](/accounts/earn#risks). + +## lockup + +A period during which a position restricts withdrawals. + +[Hedgey](/integrations/hedgey). + +## LP + +Liquidity provider. A participant that supplies assets to a trading pool. + +[Uniswap](/integrations/uniswap). + +## MCP + +Model Context Protocol. A protocol that connects AI tools to data and operations. + +[Agents & API](/introduction/agents#connect-to-ai-tools-mcp). + +## member + +A person who belongs to a Splits team. + +[Members](/members). + +## Member role + +The team role with fewer administration permissions than the Owner role. + +[Roles](/teams/roles). + +## memo + +A short note attached to a transaction. + +[Memos](/transactions/memos). + +## Merkle root + +A hash that represents a tree of data and permits proofs about its contents. + +[Editing](/accounts/editing#change-signers-and-thresholds). + +## metadata + +Additional information attached to a record, such as a memo or JSON properties. + +[Agents & API](/introduction/agents#transaction-metadata). + +## microdeposit + +A small test transfer for bank account verification. + +[Onramping](/banking/onramping#verify-with-microdeposits). + +## module + +An address with permission to execute account calls through the module interface. + +[Modules](/accounts/modules). + +## Multisend + +The feature that transfers one token to multiple recipients in one transaction. + +[Sends](/transactions/sends#multisend). + +## multisig + +An account that uses a configured number of signer approvals for transactions. + +[Thresholds](/accounts/thresholds). + +## native token + +The token that a network uses for transaction fees. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## network + +A blockchain on which an account can operate. + +[Networks and assets](/introduction/networks-and-assets). + +## NFT + +Non-fungible token. A token with an identity separate from other tokens. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## offchain record + +Information stored outside a blockchain. + +[Members](/members). + +## offering + +The PACT contract that holds units for sale and deposited USDC. + +[PACT](/experiments/pact). + +## offramp + +A conversion from tokens to funds in a bank account. + +[Offramping](/banking/offramping). + +## onchain transaction + +A transaction recorded on a blockchain. + +[Transactions](/transactions). + +## onramp + +A conversion from a bank transfer to tokens. + +[Onramping](/banking/onramping). + +## operating account + +A Splits account for direct transactions with team-selected signers and threshold. + +[Accounts](/accounts#operating-accounts). + +## oracle + +A service that supplies external data, such as asset prices, to a contract. + +[Earn](/accounts/earn#risks). + +## output token + +A unit of text used to measure AI input or output. + +[Agents & API](/introduction/agents#tune-output-for-agents). + +## Owner role + +The team role with account and team administration permissions. + +[Roles](/teams/roles). + +## PACT + +Purchase Agreement for Community Tokens. A tool for capital collection and public allocation records. + +[PACT](/experiments/pact). + +## passkey + +A key pair for authentication or signatures, with its private key held by a device or password manager. + +[Signing keys](/members/keys#passkeys). + +## password manager + +Software that stores credentials, including supported passkeys. + +[Signing keys](/members/keys#password-managers). + +## pay-by-bank + +An invoice payment method that uses a bank transfer. + +[Invoicing](/invoicing#pay-by-bank). + +## payee + +The person or business that receives a payment. + +[Compliance](/contacts/compliance). + +## payer + +The person or business that makes a payment. + +[Paying invoices](/invoicing/paying). + +## payroll + +Payments to employees or contractors. + +[Schedules](/transactions/schedules). + +## performance fee + +A charge calculated from investment yield. + +[Earn](/accounts/earn#fees). + +## personal team + +A Splits team for an individual. + +[Personal usage](/introduction/personal-usage). + +## position + +An account's balance or claim in a protocol. + +[Integrations](/integrations#positions). + +## principal + +The deposited amount before interest or gains. + +[Earn](/accounts/earn). + +## private key + +Secret key material that produces cryptographic signatures. + +[Signing keys](/members/keys). + +## private transfer + +A recipient payment through NEAR Confidential Intents. + +[Sends](/transactions/sends#private-transfers). + +## proposal + +A requested transaction awaiting the account's required approvals. + +[Transactions](/transactions). + +## protocol + +A defined set of rules and contracts for an operation. + +[Integrations](/integrations). + +## public key + +Key material that lets others verify a signature without the private key. + +[Signing keys](/members/keys). + +## query + +Request stored data through a software interface. + +[Agents & API](/introduction/agents). + +## quote + +A provider's proposed exchange amounts and terms. + +[Swaps](/transactions/swaps#quotes-and-multisigs). + +## realized gain + +The excess of disposal proceeds over the assigned cost basis. + +[Accounting](/accounting). + +## recovery + +The process that restores account control through recovery signers. + +[Recovery](/teams/recovery). + +## recovery signer + +An EOA in the Root's signer set. + +[Recovery](/teams/recovery#recovery-signers). + +## recurring invoice schedule + +A schedule that creates invoices at weekly or monthly intervals. + +[Recurring invoices](/invoicing/recurring). + +## register + +Add a credential or account record to a system. + +[Agents & API](/introduction/agents). + +## reset + +Replacement of an account's signer state through its onchain owner. + +[Editing](/accounts/editing#reset-signers). + +## REST API + +An API that exposes resources through HTTP requests. + +[Agents & API](/introduction/agents#use-the-api). + +## role + +A set of app permissions assigned to a team membership. + +[Roles](/teams/roles). + +## Root + +The account at the top of a team's ownership chain. + +[Accounts](/accounts#root). + +## schedule + +A stored instruction to create a transaction proposal at an interval. + +[Schedules](/transactions/schedules). + +## schema + +A description of a data structure and its constraints. + +[Agents & API](/introduction/agents). + +## send + +A transfer of tokens from a Splits account to a recipient. + +[Sends](/transactions/sends). + +## send rule + +A restriction on permitted tokens and networks for a contact. + +[Contacts](/contacts#restrictions). + +## SEPA + +Single Euro Payments Area. A system for euro bank transfers. + +[Onramping](/banking/onramping). + +## sign + +Produce a cryptographic signature with a private key. + +[Agents & API](/introduction/agents). + +## signature + +Cryptographic data that proves approval by a private key. + +[Signing keys](/members/keys). + +## signer + +A signing key in a specific account's signer set. + +[Signers](/accounts/signers). + +## signer set + +The public signing keys with approval authority on an account. + +[Signers](/accounts/signers). + +## signing key + +A key that produces signatures for a member or executor. + +[Signing keys](/members/keys). + +## slippage tolerance + +The permitted price change between a swap quote and execution. + +[Swaps](/transactions/swaps#slippage). + +## spam token + +A token classified as unwanted. + +[Spam & tokens](/accounting/spam). + +## Splits Connect + +The browser extension that connects Splits accounts to other apps. + +[Browser extension](/introduction/extension). + +## stablecoin + +A token designed to track a reference currency or asset value. + +[Banking](/banking). + +## sub-account + +An operating or automation account owned by the Treasury. + +[Accounts](/accounts). + +## subname + +An ENS name below another name, such as treasury.splits.eth. + +[ENS](/integrations/ens#subnames). + +## swap + +An exchange of one token or network balance for another. + +[Swaps](/transactions/swaps). + +## synchronize + +Apply matching data to multiple networks or systems. + +[Agents & API](/introduction/agents). + +## tax lot + +An asset acquisition record used to calculate gains and losses. + +[Accounting](/accounting). + +## team + +A group of accounts and records for a company, individual, or project. + +[Teams](/teams). + +## threshold + +The number of signer approvals required for an account transaction. + +[Thresholds](/accounts/thresholds). + +## timelock + +A mandatory delay before a contract action can execute. + +[Earn](/accounts/earn). + +## token + +An asset represented in a blockchain record. + +[Networks and assets](/introduction/networks-and-assets#supported-assets). + +## transaction + +A request to change blockchain state through one or more calls. + +[Transactions](/transactions). + +## Treasury + +The team's main asset account and owner of its sub-accounts. + +[Accounts](/accounts#treasury). + +## URI + +Uniform resource identifier. Text that identifies a resource or connection. + +[WalletConnect](/integrations/walletconnect). + +## USD + +The currency code for the US dollar. + +[Banking](/banking). + +## USDC + +Circle's US dollar stablecoin. + +[Banking](/banking). + +## USDT + +Tether's US dollar stablecoin. + +[Sends](/transactions/sends#private-transfers). + +## vault + +A contract that holds deposits and manages an investment position. + +[Earn](/accounts/earn). + +## vault share + +A token that represents a portion of a vault's assets. + +[Earn](/accounts/earn). + +## vendor payment + +A transfer of funds to a vendor's bank account. + +[Paying vendors](/banking/paying-vendors). + +## vesting + +The scheduled release of rights to tokens or other assets. + +[Integrations](/integrations#positions). + +## wallet + +An external EOA wallet, such as a hardware wallet or MetaMask. + +[Signing keys](/members/keys#eoas). + +## wei + +The smallest unit of ETH. + +[Custom transactions](/transactions/custom). + +## WETH + +Wrapped ether. An ERC-20 representation of ETH. + +[Sends](/transactions/sends#private-transfers). + +## withdrawal + +Funds removed from an account or position. + +[Earn](/accounts/earn). + +## yield + +The return that a deposited asset produces. + +[Earn](/accounts/earn). diff --git a/src/pages/resources/how-we-work.mdx b/src/pages/resources/how-we-work.mdx index 7b3f9a1..03be603 100644 --- a/src/pages/resources/how-we-work.mdx +++ b/src/pages/resources/how-we-work.mdx @@ -1,69 +1,75 @@ --- -title: How we work -description: "How the Splits team runs itself: our own workspace (accounts, signers, thresholds) and the financial tools around it, from banking to payroll." +title: "How we work" +description: "The Splits team configuration and service providers" --- -# How we work [Our own workspace, and the financial tools around it] +# How we work [The Splits team configuration and service providers] -How we run our own team, as of today: the Splits workspace itself, and the financial stack around it. For context, we're a 10-person, VC-backed team structured as a US C-Corp. +This **team example** describes how Splits organizes its accounts and financial services. Splits is a US C corporation with venture funding. ## Our workspace -Everything we hold onchain (a mix of stablecoins, ETH, project tokens, and NFTs, plus our ENS `splits.eth` and the recovery keys for our Farcaster account) lives in a handful of purpose-specific [accounts](/accounts), signed with passkeys day-to-day and backstopped by [recovery signers](/teams/recovery#recovery-signers) on hardware wallets. +Our onchain assets include stablecoins, ETH, project tokens, NFTs, and the ENS name `splits.eth`. We use separate [accounts](/accounts) for different purposes. | Account | Threshold | Purpose | | --- | --- | --- | -| Treasury | 2-of-3 | The bulk (~90%) of our assets, and our ENS. Signers are company-issued YubiKeys held by different senior people. | -| Operating | 1-of-3 | Day-to-day expenses; funds the other accounts. Same signers as the Treasury. | -| Payroll | 1-of-n | Contractor payments. Holds at most a month of payments, topped up from Operating, with limited signing privileges. | -| Eng testing | 1-of-n | Shared by all engineers, holding roughly $1k so no one is ever blocked. | -| Per-project | varies | Carve-outs for experiments and external collaborators, archived when done. | +| Treasury | 2-of-3 | Main asset balance and ENS name | +| Operating | 1-of-3 | Regular expenses and other account funding | +| Payroll | 1-of-n | Contractor payments | +| Eng testing | 1-of-n | Engineering tests | +| Per-project | Variable | Experiments and external collaborators | -Beneath these sits Recovery: a 2-of-3 of company-issued hardware wallets that holds no assets and exists solely to [reset accounts](/teams/recovery) if our passkeys ever become unusable. +The Treasury holds about 90% of our onchain assets. Senior team members hold its company-issued YubiKeys. Operating uses the same signers. -Why this shape works for us: +Payroll holds at most one month of payments and receives funds from Operating. Engineering tests use a balance of approximately $1,000. We archive project accounts when their work ends. -- **No unilateral access to the Treasury.** Two approvals move the bulk of the assets, and a lost YubiKey is replaced by the other two signers. -- **Low-threshold accounts hold bounded balances.** A leaked key or rage-quit is capped at that account's balance, and because every signature is a specific person's key, every transaction is attributable. -- **Accounts double as accounting.** [Memos are required](/transactions/memos), and each account maps to categorization rules, so books mostly close themselves. +Our [recovery signers](/teams/recovery#recovery-signers) use company-issued hardware wallets with a 2-of-3 threshold. The recovery account holds no assets. + +**No individual Treasury signer can transfer funds alone.** Limited balances in operating accounts reduce funds exposed to individual signing keys. + +We require [memos](/transactions/memos) and assign accounting categories to accounts. ## Banking -- [Splits](https://app.splits.org/) - we keep about \~20% of our company's assets onchain, about half of which is [earning interest](/accounts/earn) at rates higher than what we would get offchain. All of our onchain assets are stored here, spread out across the dedicated accounts [above](#our-workspace). A few thoughts: - - We don't feel comfortable moving 100% of our assets onchain, since we benefit from accessing the peace of mind that comes with FDIC insurance and custodians. - - That said, the percent of our assets we're storing onchain *is* growing, primarily because (1) yield is higher, and (2) we can move money faster. -- [Mercury](https://mercury.com/r/0xsplits)\* - best in class UX. We keep a nominal balance here, and use it if we need to pay invoices and move other relatively small amounts. -- [Meow](https://www.meow.com/)\* - higher yield than Mercury, but worse UX. All our big expenses (payroll, credit card, health insurance, etc) pull from these accounts. We rarely interface with this product; it's almost entirely "set and forget" (except when they have bugs that require us to manually move funds to cover payroll, ugh). We set this up because we had recently raised outside capital and the yield we could earn was considerably higher than what we were earning in Mercury. +We keep approximately 20% of company assets onchain in Splits. About half of those funds use [Earn](/accounts/earn). + +| Provider | Our use | +| --- | --- | +| [Splits](https://app.splits.org/) | Onchain accounts and payments | +| [Mercury](https://mercury.com/r/0xsplits) | Small bank payments and invoices | +| [Meow](https://www.meow.com/) | Payroll, card, and insurance payments | -*\* As with all custody products, a legal entity is required. So these are not applicable for unincorporated projects and, in many cases, entities outside the US.* +Each provider defines its account eligibility and terms. ## Accounting -- [Splits](https://app.splits.org/) - provides our accounting team with historical prices of all our transactions across all of our accounts. **We rely heavily on the memo field**, as to reduce the back and forth needed with our accounting team to close our books. -- [Acuity](https://acuity.co/) - third party firm that handles monthly bookkeeping, taxes, etc. We got started with them as a small company and, for the most part, they've kept up so we haven't felt compelled to change. In hindsight, we would have rather worked with a part-time dedicated bookkeeper vs going with a larger firm. -- [Xero](https://www.xero.com) - we interact with this very infrequently. We do pay for it because our accountants use it. This is our source of truth for our Chart of Accounts, which our accountants modify as operational complexity grows. -- [Integral](https://integral.xyz/) - we interact with this very infrequently. It's a sub-ledger that tracks cost basis for our onchain transactions and syncs to Xero's Chart of Accounts. - - Good product but very expensive (minimum \$7200/year) and pricing is based on transaction count, which we fundamentally disagree with. Our goal is to bring this functionality into Splits in the next few quarters. +| Provider | Our use | +| --- | --- | +| Splits | Transaction records and memos | +| [Acuity](https://acuity.co/) | Monthly records and tax preparation | +| [Xero](https://www.xero.com) | Chart of accounts | +| [Integral](https://integral.xyz/) | Onchain cost basis records and Xero synchronization | + +Our accountants maintain the chart of accounts. Transaction memos reduce the additional information they need from us. ## Cards -- [Ramp](https://ramp.com/) - we started using this prior to Mercury having a card offering of their own. It's not clear we would use Ramp today if we were just getting started. - - Corporate cards tied to an onchain balance is one of the most requested features from current customers (e.g. pay AWS, Figma, Notion, etc using USDC in a Splits account). See [Rain](/integrations/rain) for the integration we offer today. +We use [Ramp](https://ramp.com/) for corporate cards. [Rain](/integrations/rain) describes the card integration available in Splits. ## Payroll -- [Splits](https://app.splits.org/) - we run payroll twice monthly for non-US folks who are amenable to receiving USDC onchain. -- [Gusto](https://gusto.com/) - we've been using this to run payroll and benefits since 2021, so far no issues and customer service has been great. -- [Deel](https://www.deel.com/) - a few of our team members are not US-based and prefer not to be paid in USDC (because of local conversion and offramping issues), so we use Deel instead of Gusto for these folks. +| Provider | Our use | +| --- | --- | +| Splits | Twice-monthly USDC payments to recipients who accept tokens | +| [Gusto](https://gusto.com/) | Payroll and benefits | +| [Deel](https://www.deel.com/) | Payments to some members outside the US | ## Miscellaneous -- [Carta](https://carta.com/) - cap table management -- [Clerky](https://www.clerky.com/) - "fill in the blank" legal docs (e.g. contractor agreements, employment offers, etc) -- Very Expensive Law Firm - we avoid engaging them unless it's Very Important +- [Carta](https://carta.com/): cap table records. +- [Clerky](https://www.clerky.com/): employment and contractor documents. +- External legal counsel: work that needs legal review. ## Outlook -This might look like a lot but it's actually fairly manageable day-to-day, especially when you consider what we [were previously using](https://splits.org/blog/onchain-team-security/). Notably, we no longer use any centralized exchanges (Coinbase, Gemini, etc), Gnosis Safes, or individual software wallets (MetaMask, Rainbow, etc). All of these were huge headaches for us as our team scaled. - -And, being an "offchain first company", we're already discovering instances where keeping a large onchain balance is net beneficial for us. Beyond the obvious aspects of international payments, we found it's actually faster to use onchain funds earning interest offchain than offchain funds earning interest offchain. We had an instance where a Meow bug resulted in us missing payroll, and the fastest way to remedy this was to offramp USDC (earning 6%+) from Splits to Meow. +Our [earlier security setup](https://splits.org/blog/onchain-team-security/) used different tools. We now use Splits accounts for onchain operations instead of individual software wallets, exchanges, and Safes. diff --git a/src/pages/resources/incorporating-and-raising-capital.mdx b/src/pages/resources/incorporating-and-raising-capital.mdx index f8d87ae..f3ae7c4 100644 --- a/src/pages/resources/incorporating-and-raising-capital.mdx +++ b/src/pages/resources/incorporating-and-raising-capital.mdx @@ -1,68 +1,70 @@ --- -title: Incorporating & raising capital -description: Options for forming a US legal entity and setting up banking, plus the onchain funding mechanisms teams use with Splits and how to receive a raise in USDC. +title: "Incorporating & raising capital" +description: "Find entity formation services and capital collection tools" --- -# Incorporating & raising capital [Forming a US entity, and funding it onchain] +# Incorporating & raising capital [Find entity formation services and capital collection tools] -You're able to use the product without a legal entity (i.e. EIN or SSN), however, some features (like [on/offramping](/banking)) aren't available until you have one. There are many ways to create US legal entities, and this guide highlights just a few we are aware of. Not legal advice, do your own research. +**Incorporation** creates a legal entity. A project can use Splits before incorporation. [Banking](/banking) describes verification requirements for bank transfers. + +This page lists providers and payment procedures. **It does not determine legal or tax requirements for a project.** Legal counsel can assess those requirements. ## Entity formation ### Clerky -[Clerky](https://www.clerky.com/) makes it easy to set up a Delaware C Corp. They also help you with post-incorporation paperwork, equity issuance, 83b elections, and other basic legal docs. It's designed for US-based founders creating "Silicon Valley startups", meaning it is not "crypto native" in any way; it is just a cost efficient way of establishing a Delaware C Corp. - -This is what we used to incorporate Splits. We bought the lifetime package and still use the turnkey legal docs for new employees, contractors, etc. +[Clerky](https://www.clerky.com/) provides US startup formation and legal document services. Splits used Clerky to incorporate and continues to use its employment and contractor documents. ### Stripe Atlas -[Stripe Atlas](https://stripe.com/atlas) is similar to Clerky, and has integrated other products as part of the stack (e.g. banking, payments, Stripe credits, etc). [Atlas now accepts stables to incorporate](https://x.com/jeff_weinstein/status/2019221615637328201), which is great if you already cash onchain. +[Stripe Atlas](https://stripe.com/atlas) provides Delaware entity formation services. Its site describes eligibility, payment methods, and included services. ### OtoCo -[OtoCo](https://otoco.io/) lets you create a legal entity using an Etherum account. You can use your Splits accounts to do this (more info on [connecting to third party apps](/introduction/extension)). You choose the jurisdiction and entity type, and OtoCo handles the paperwork filing for you. Once you create your entity, you will be able to request a tax ID. +[OtoCo](https://otoco.io/) connects legal entity formation to blockchain accounts. Its [documentation](https://docs.otoco.io/docs/getting-started) describes jurisdictions, entity types, and filing procedures. -![](/docs/images/incorporating-and-banking-in-the-us/img-1.png) - -You can link as many of your Splits accounts to your entity as you wish. OtoCo is self-serve, but if you need assistance we can put you in touch with their team. [See their docs for more info](https://docs.otoco.io/docs/getting-started). +The [browser extension](/introduction/extension) connects Splits accounts to other apps. ## Banking -[Mercury](https://mercury.com/) is best-in-class banking for US-based startups. Many of the above entity formation options will help you get set up with a Mercury account upon entity formation. +[Mercury](https://mercury.com/) provides banking services for eligible businesses. Its site defines eligibility and application requirements. + + -## Raising capital onchain +## Onchain funding -The onchain-native funding mechanisms we see teams use in conjunction with Splits. Use the [browser extension](/introduction/extension) to connect your Splits accounts to the apps below. +These providers offer token or capital collection tools. Each provider sets its eligibility rules and transaction terms. ### Clanker -[Clanker](https://clanker.world/) is the most popular token launcher within our sphere today. Most builders launch their appcoins using Clanker, and use their Splits accounts for day-to-day token management (e.g. vesting/lockups, swaps, payments, offramps, etc). +[Clanker](https://clanker.world/) provides token creation tools. The [Clanker integration](/integrations/clanker) describes reward claims in Splits. -Clanker creators earn rewards based on trading volume of their token(s). When a token is launched, Clanker automatically sets up a single-sided Uniswap pool and directs earnings to the creator's account, which you can [view and claim in Splits](/integrations/clanker). To learn more, check out their [docs](https://clanker.gitbook.io/clanker-documentation/). +[Clanker's documentation](https://clanker.gitbook.io/clanker-documentation/) describes token creation and rewards. ### Noice -[Noice](https://noice.so/) helps teams form capital and distribute tokens. [Here is a recent example](https://noice.so/roof). Once you're accepted (it's permissioned, so you need to apply), they will help get a fundraise campaign and page set up for you. - -Noice partners with Splits, so the Noice team will also help get your Splits accounts created so that you can more easily manage the funds once they're raised as well as your token positions (Uniswap pools etc). To learn more, check our their [docs](https://docs.noice.so/). +[Noice](https://noice.so/) provides capital collection and token distribution services. The [Noice documentation](https://docs.noice.so/) describes participation and campaign setup. ### Echo -[Echo](https://echo.xyz/), now part of Coinbase, is similar to "AngelList using USDC". They offer two types of raises: private and public. Private raises, once accepted, will pair you with a "deal lead" who will help manage your raise. You define the amount and terms, and your deal lead will handle the logistics of the raise. These raises are done on a SAFE note via a Cayman entity (Echo will set up the Cayman entity for investors to buy into), and tokens are dealt with via a side letter. - -Public raises are different and look more like a "permissioned token sale", where Echo handles the entity verification (i.e. KYC/B) and you handle the rest (website, contracts, etc). To learn more, check out their [docs](https://docs.echo.xyz/). +[Echo](https://echo.xyz/) provides tools for private and public capital collection. The [Echo documentation](https://docs.echo.xyz/) describes its products and requirements. ### Tally -[Tally](https://tally.xyz/) began with onchain governance for DAOs (i.e. proposals, voting, membership, etc). [Here is an example](https://www.tally.xyz/gov/arbitrum). They've now expanded to cover token sales and distributions as well (i.e. airdrop, ICO, and vesting). +[Tally](https://tally.xyz/) provides token distribution and governance tools. The [Tally documentation](https://docs.tally.xyz/) describes sale types and governance functions. + + -Tally's token launcher tools are similar to Clanker (define symbol, network, etc), but geared towards more established teams. They'll create a white-labelled site, provide more granular types of token sales (e.g. fixed-price, continuous clearing auctions, and liquidity bootstrapping pools), and offer governance tools post-ICO. To learn more, check out their [docs](https://docs.tally.xyz/). +## Receive investor funds -## Receiving investor funding +Prerequisites: agree on funding terms and required investor checks with legal counsel. The [payee compliance feature](/contacts/compliance) describes tax form collection, not investor eligibility. -For a traditional offchain raise (e.g. a SAFE) where investors send USDC: +For an investor who pays USDC: -1. **Verify your investors first.** Complete KYC/KYB on each investor before accepting funds: the [Compliance](/contacts/compliance) flow handles this by email invite. (Not legal advice; talk to counsel.) -2. **Create a dedicated account per investor** (e.g. "Jane Doe $10k") and share its address, so inbound capital is attributable and outstanding commitments are obvious. USDC transfers are final; for larger checks, have the investor send $1 first and confirm receipt before the rest. -3. **Sweep and archive.** Forward each account's balance to your Treasury with a [memo](/transactions/memos), then archive the emptied account. +1. Create a dedicated [account](/accounts) for the investor's payment. +2. Share the account address and intended network. +3. Ask the investor to send a small test amount first. +4. Confirm receipt. +5. Ask the investor to send the remaining amount. +6. Forward the funds to the Treasury with a [memo](/transactions/memos). +7. Archive the empty account when its records are complete. diff --git a/src/pages/resources/security.mdx b/src/pages/resources/security.mdx index 6897aea..12ea4b4 100644 --- a/src/pages/resources/security.mdx +++ b/src/pages/resources/security.mdx @@ -1,16 +1,22 @@ --- -title: Security & bug bounty -description: "Splits contract security: open source, independently audited, and a bug bounty of up to $50,000 for critical vulnerabilities, per SECURITY.md." +title: "Security & bug bounty" +description: "Contract audits and vulnerability reports" --- -# Security & bug bounty [Audited contracts, and up to $50,000 for critical vulnerabilities] +# Security & bug bounty [Contract audits and vulnerability reports] -Splits' contracts are open source in [splits-contracts-monorepo](https://github.com/0xSplits/splits-contracts-monorepo) and independently audited; see the [audit reports](https://github.com/0xSplits/splits-contracts-monorepo/tree/main/audits). After launch, a bug bounty keeps independent researchers reviewing the code. +A **bug bounty** rewards eligible vulnerability reports. Splits publishes its contracts in [splits-contracts-monorepo](https://github.com/0xSplits/splits-contracts-monorepo). -The bounty's source of truth is [SECURITY.md](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/SECURITY.md); in summary: +The repository includes independent [audit reports](https://github.com/0xSplits/splits-contracts-monorepo/tree/main/audits). [SECURITY.md](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/SECURITY.md) defines the bounty's scope, rewards, reporting procedure, and research protections. -- **Scope**: vulnerabilities in the monorepo's deployed production contracts that could lead to loss of user funds. -- **Out of scope**: test and script code, dependencies not used by deployed contracts, testnet deployments, third-party contracts, previously reported issues, and non-contract vectors (frontend bugs, phishing, social engineering, private key compromise). -- **Rewards**: up to **$50,000** for critical fund-loss bugs; lower severities at the team's discretion. -- **Reporting**: email **security@splits.org** within 24 hours of discovery, include reproduction steps or a proof of concept, and keep the issue confidential until it's patched. -- **Safe harbor**: good-faith research that follows the policy is protected. +The policy covers deployed production contracts with vulnerabilities that can cause loss of funds. It excludes test code, unrelated dependencies, third-party contracts, known issues, and non-contract attacks. + +Rewards can reach **$50,000** for critical vulnerabilities. The policy determines eligibility and rewards for other severities. + +To report a vulnerability: + +1. Email **security@splits.org** within 24 hours of discovery. +2. Include reproduction steps or a proof of concept. +3. Keep the issue confidential until Splits fixes it and permits disclosure. + +The policy describes protection for research that follows its conditions. diff --git a/src/pages/teams/index.mdx b/src/pages/teams/index.mdx index a137d2d..3f1f90b 100644 --- a/src/pages/teams/index.mdx +++ b/src/pages/teams/index.mdx @@ -1,36 +1,51 @@ --- -title: Teams -description: "Teams are the primary organizational unit in Splits: creating one, the setup steps, and what gets created under the hood." +title: "Teams" +description: "Create a group of accounts and records" --- -# Teams [The primary organizational unit, and how to create one] +# Teams [Create a group of accounts and records] -A [team](/introduction/core-concepts#team) is the primary organizational unit in Splits: every account, balance, transaction, and setting is scoped to one. A team has [members](/members) and [accounts](/accounts) that hold its assets. +A **team** groups accounts and records in Splits. It has [members](/members) and [accounts](/accounts). [Core concepts](/introduction/core-concepts#team) explains how teams relate to accounting records. -## Creating a team + -Creating a team takes under a minute, and everything about it can be changed after setup **except Recovery**: changing recovery signers later generates new account addresses (see [Change recovery signers](/teams/recovery#changing-recovery-signers)). +## Create a team -A member can create and belong to any number of teams. If you just registered, you're guided through creating one; otherwise click your team's name in the top left and select *Create a team*. +A member can create and belong to multiple teams. Direct registration starts team setup. -:::note -**Creating a demo team?** Skip the Owners and Treasury steps (keep the defaults), and on the Recovery step select *1 signer* and paste any address or ENS (e.g. example.eth). You can create a properly configured team later. -::: +For an existing member: + +1. Select the current team's name at the top left. +2. Select *Create a team*. + +**A later change to recovery signers changes account addresses.** [Recovery](/teams/recovery#change-recovery-signers) describes this restriction. ### The setup steps -1. **Team**: enter a name and optionally upload a logo. -2. **User**: confirm your display name and photo, and add at least one passkey. **You must have at least one passkey to continue.** Your preferred passkey becomes your signer on the Treasury. See [Signing keys](/members/keys) for passkey guidance. -3. **Owners**: add teammates as Owners. Owners are automatically added as signers on the Treasury and can invite other members. *Skip if solo or testing.* -4. **Treasury**: set how many owners must approve each outgoing transaction from the Treasury (its [threshold](/accounts/thresholds)). *Skip if solo or testing.* -5. **Recovery**: add at least one EOA (wallet address) as a recovery signer, and set the recovery threshold. Recovery is how you regain your assets if you lose your passkeys. We recommend multiple signers; at Splits we use a [2-of-3 with hardware wallets](/resources/how-we-work#our-workspace). -6. **Review**: check everything, including which networks to activate. Active networks keep signer state in sync onchain. Base, Arbitrum, Optimism, and Ethereum Mainnet are activated by default; click *Change* to adjust. Hit *Finish*. +The setup has six sections: -### What gets created +1. In *Team*, enter the team name. +2. If necessary, upload a logo. +3. In *User*, confirm your display name and photo. +4. Add at least one passkey. +5. In *Owners*, add other Owners if the team needs them. +6. In *Treasury*, set the approval threshold. +7. In *Recovery*, add at least one EOA address as a recovery signer. +8. Set the recovery threshold. +9. In *Review*, check the configuration. +10. If necessary, select *Change* to change the active networks. +11. Select *Finish*. + +**Setup requires at least one passkey.** Your preferred passkey becomes an initial Treasury signer. [Signing keys](/members/keys) describes passkey storage. -Finishing setup creates two accounts: +During setup, added Owners supply initial Treasury signers. An individual can keep the default Owners and Treasury settings. [Roles](/teams/roles) describes later role permissions. + +[Thresholds](/accounts/thresholds) explains the approval count. The Splits team's [recovery example](/resources/how-we-work#our-workspace) uses multiple hardware wallets. + +Base, Arbitrum, Optimism, and Ethereum Mainnet are active by default. + +### What gets created -- A hidden **Root** account controlled by your recovery signers, at the top of the ownership chain. -- The **Treasury**, owned by the Root and controlled by the owners' passkeys. It's intended to hold the bulk of your assets, and it owns every account you create later. +Setup creates the Root and the Treasury. [Accounts](/accounts) describes their ownership and control. -See [Account types](/accounts) for the full ownership model. From your new dashboard you can deposit funds, create accounts, [connect a bank](/banking), and [invite members](/members#inviting). +The new team can receive funds, create accounts, [connect a bank](/banking), and [invite members](/members#invite-a-member). diff --git a/src/pages/teams/recovery.mdx b/src/pages/teams/recovery.mdx index 1a35659..fb11560 100644 --- a/src/pages/teams/recovery.mdx +++ b/src/pages/teams/recovery.mdx @@ -1,52 +1,92 @@ --- -title: Recovery -description: "Why recovery exists, the recovery signers that control the Root account, verifying them, how to recover a team's accounts, and why changing recovery signers changes addresses." +title: "Recovery" +description: "Restore account control when normal signing keys are unavailable" --- -# Recovery [Regain control of every account if a team's passkeys are lost] +# Recovery [Restore account control when normal signing keys are unavailable] -Recovery is how a team regains its accounts without Splits' help. [Recovery signers](#recovery-signers) control the [Root account](/accounts#root) at the recovery threshold, and because the Root owns every other account, they can reset any account's signers and threshold. +**Recovery** restores a team's account control through recovery signers. These signers control the [Root](/accounts#root) at the recovery threshold. The ownership chain permits resets of accounts below the Root. ## Why recovery exists -Splits never has custody of a team's assets, so there must be a way to regain them that depends on neither Splits nor the passkeys. Passkeys are phishing-resistant because they're bound to the issuing domain; the flip side is that if splits.org went offline, or a passkey manager were compromised or lost, the passkeys would become unusable. Recovery signers are ordinary Ethereum keys with no such dependency: they work as long as the chain does. +Passkeys depend on their website domain and the device or manager that stores them. An unavailable domain or lost private key can prevent their use. + +Recovery signers use Ethereum keys that do not depend on Splits passkeys. They can act through the blockchain when normal signing keys are unavailable. ## Recovery signers -Recovery signers are EOAs (wallet addresses) chosen at [team setup](/teams), controlling the Root at the recovery threshold set there. They are the backstop beneath the day-to-day signers, and are listed in [Settings > Recovery](https://app.splits.org/settings/team/recovery/) (visible to Owners). Changing them changes the team's account addresses ([why](#changing-recovery-signers)). +A **recovery signer** is an EOA selected at [team setup](/teams) to control the Root. Setup also sets the recovery threshold. + +Owners can view recovery signers in [Settings > Recovery](https://app.splits.org/settings/team/recovery/). [Changing recovery signers](#change-recovery-signers) describes the effect on account addresses. + + + +### Verify recovery signers + +Verification uses a test signature to check private key access. It does not move funds. + +1. Open *Settings > Recovery*. +2. Open the signer's three-dot menu. +3. Select *Verify signer*. +4. Connect the recovery wallet. +5. Sign the test message. + +Each signer shows its last verification. The test uses the same message format as recovery. [Ledger issues](#known-issues-when-using-a-ledger) describes incompatible signatures. + +If a wallet does not disconnect: + +1. Open another page. +2. Return to Recovery. +3. If the connection remains, disconnect app.splits.org in the wallet extension. + + + +### Ledger connection issues + +Splits signs a 32-byte hash through raw-byte `personal_sign`. The [account contracts](https://github.com/0xSplits/splits-contracts-monorepo/tree/main/packages/smart-vaults) check that signature. + +Some wallet connections convert the hash to text first. The resulting signature fails verification, and the app shows *Signature rejected*. -### Verifying recovery signers +Known cases include Ledger Live through WalletConnect and the Rainbow browser extension with a Ledger. MetaMask provides another connection method for a Ledger. -Confirm a recovery signer's private key is still accessible without moving funds: Splits has the key sign a test message. In *Settings > Recovery*, open the signer's three-dot menu → *Verify signer*; this requires connecting the wallet. Each signer shows when it was last verified. + -Verification signs the same kind of message a recovery does, so a signer that passes can sign a recovery; see [Known issues when using a Ledger](#known-issues-when-using-a-ledger) for wallets that fail it. +## Recover your accounts -**If a wallet won't disconnect**, navigate to another page and back, or disconnect from app.splits.org in the wallet extension. +Before recovery, check whether normal signing keys remain available: -### Known issues when using a Ledger +1. [Verify your passkeys](/members/keys#verify-access) in [Settings > Personal > Passkeys](https://app.splits.org/settings/personal/passkeys/). +2. Open [Settings > Members](https://app.splits.org/settings/team/members/). +3. Point to each member's signing key icon to check their passkey information. -Splits transactions are signed as a 32-byte hash (raw-bytes `personal_sign`, checked by the [account contracts](https://github.com/0xSplits/splits-contracts-monorepo/tree/main/packages/smart-vaults)). Wallets that re-encode that hash as text before signing produce a signature the contract rejects, and the app reports *Signature rejected*: +Recovery uses a [Treasury signer reset](/accounts/editing#reset-signers). The Root owns the Treasury, so recovery wallets approve that reset at the recovery threshold. -- **Ledger Live** over WalletConnect decodes the hash as text and signs the corrupted result. -- **The Rainbow browser extension** with a Ledger signs the hex string as text. +1. Open the Treasury's settings. +2. Select *Reset signers*. +3. Select new signers. +4. Set the new threshold. +5. Connect the recovery wallets. +6. Sign the reset with enough recovery signers to meet the recovery threshold. -Connect a Ledger through MetaMask instead. +After the Treasury reset, its signers can approve sub-account resets. Those resets do not need recovery wallets. -## Recovering your accounts + -Recovery is the last resort: first confirm the passkeys are lost. [Verify your own](/members/keys#verifying-access) at [Settings > Personal > Passkeys](https://app.splits.org/settings/personal/passkeys/), and check teammates' passkeys by hovering over the key icon on their row in [Settings > Members](https://app.splits.org/settings/team/members/). +## Change recovery signers -Recovery is a [signer reset](/accounts/editing#resetting-signers) on the Treasury: because the Treasury's owner is the Root, the reset is signed by the recovery wallets at the recovery threshold instead of by passkeys. Run it from the Treasury's settings (*Reset signers*, also reachable from *Settings > Recovery*, which lists every account), choose the new signers and threshold, then connect the recovery wallet(s) and sign. +Account address calculation uses the recovery configuration and [`CREATE2`](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2). **A change to recovery signers changes the team's account addresses.** -With the Treasury back under control, [reset any sub-account](/accounts/editing#resetting-signers) the same way; sub-account resets are signed by the Treasury's signers, so no recovery wallets are needed. +The current change procedure uses a new team: -## Changing recovery signers +1. Create a new team with the new recovery configuration. +2. Email support to migrate offchain data, such as contacts, members, bank information, and schedules. +3. Update payment sources and invoices to use the new account addresses. -Account addresses are derived from the recovery configuration using [`CREATE2`](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2). That's what makes every account's address identical on every network (including networks added later) while keeping custody fully with the team. The consequence: **changing recovery signers changes the team's account addresses.** + -The practical way to change them today is to create a new team. Email support to migrate your offchain data (members, contacts, on/offramp info, schedules), and remember to update any revenue or funding sources (smart contracts, invoices) to the new team's addresses. +## Maintain recovery access -## Keeping recovery healthy +1. Verify recovery signers at regular intervals. +2. Use multiple recovery signers on hardware wallets. -- [Verify recovery signers](#verifying-recovery-signers) periodically; each shows when it was last verified. -- Use multiple signers on hardware wallets; at Splits we use a [2-of-3 with hardware wallets](/resources/how-we-work#our-workspace). +[How we work](/resources/how-we-work#our-workspace) describes the Splits team's recovery configuration. diff --git a/src/pages/teams/roles.mdx b/src/pages/teams/roles.mdx index 8a02b3a..f0459d3 100644 --- a/src/pages/teams/roles.mdx +++ b/src/pages/teams/roles.mdx @@ -1,38 +1,43 @@ --- -title: Roles -description: The authoritative reference for what each role (Owner, Member) and each API key scope (read, write, owner) can do in Splits. +title: "Roles" +description: "Team permissions and API key scopes" --- -# Roles [What each role and API key scope can do] +# Roles [Team permissions and API key scopes] -Every team [member](/members) has exactly one of two roles: **Owner** or **Member**. Roles are stored offchain, govern what a person can see and do in the app, and are scoped to one team: the same person can be an Owner of one team and a Member of another. A role never grants signing authority: approving transactions belongs to an account's onchain signer set, and the boundary is drawn at [Signers](/accounts/signers#signers-vs-membership). +A **role** controls a [member](/members)'s access to functions in a team. Each membership has one role: **Owner** or **Member**. -A related surface (**API key scopes**) governs what programmatic clients can do; see [below](#api-key-scopes). +Roles apply to individual teams. One person can be an Owner in one team and a Member in another. + +**A role gives no onchain signing authority.** [Signers](/accounts/signers#signers-vs-membership) describes account approval authority. ## What each role can do | Capability | Owner | Member | | --- | --- | --- | | View accounts, balances, and transactions | ✅ | ✅ | -| Propose transactions from an account | ✅ any account | Only accounts they're a signer on | +| Propose transactions from an account | ✅ any account | Accounts with their signer | | Sign transactions | Only if a signer on that account | Only if a signer on that account | | Reject pending transactions | ✅ | Only if a signer on that account | | Create new accounts | ✅ | ❌ | | Edit account settings (name, signers, threshold) | ✅ | ❌ | | Invite, remove, and change roles of members | ✅ | ❌ | | Edit team name and logo | ✅ | ❌ | -| Toggle [*Require memos*](/transactions/memos#requiring-memos) | ✅ | ❌ | +| Toggle [*Require memos*](/transactions/memos#require-memos) | ✅ | ❌ | | Create invoices | ✅ | ❌ | | Create and manage automations and schedules | ✅ | ❌ | -| Onramp / offramp | ✅ | ❌ | +| Bank transfers | ✅ | ❌ | | Entity verification (KYB/KYC) and payment info | ✅ | ❌ | | Manage recovery | ✅ | ❌ | ## Read-only members -A member with the **Member** role whose signing keys aren't signers on any account is effectively read-only: they can see the team's accounts, balances, and transactions, but can't propose or sign any. (Owners can propose from any account, so read-only requires the Member role.) +A Member without a signing key on any account can view accounts, balances, and transactions. **That member cannot propose or sign transactions.** An Owner can propose from any account. + +To configure read-only access: -To set one up, [invite them](/members#inviting) with the Member role and don't add them to any account's signer set. Useful for accountants, auditors, and anyone who needs visibility without the ability to move funds. +1. [Invite the person](/members#invite-a-member) with the Member role. +2. Keep their signing keys out of all account signer sets. ## Settings visibility by role @@ -48,12 +53,12 @@ To set one up, [invite them](/members#inviting) with the Member role and don't a ## API key scopes -API keys power the [CLI and MCP](/introduction/agents). Each key is scoped at creation: +An **API key scope** controls functions available through the [CLI and MCP](/introduction/agents). Each API key receives scopes at creation. -| Scope | Grants | Who can create a key with it | +| Scope | Permissions | Who can create it | | --- | --- | --- | -| `read` | List accounts, transactions, balances, and other team data | Any member | -| `write` | Read, plus propose transactions and update memos/properties | Any member | -| `owner` | Admin operations (manage accounts and org settings) | Owners only | +| `read` | Queries for team data | Any member | +| `write` | Reads, transaction proposals, memo and property changes | Any member | +| `owner` | Account and team administration | Owners | -Like roles, API key scopes never grant onchain signing authority by themselves: signing programmatically requires a [registered EOA signer](/introduction/agents#sign-locally-with-an-eoa) on the account. +**Scopes alone give no onchain signing authority.** CLI signatures also require a registered EOA in the account's signer set. diff --git a/src/pages/teams/settings.mdx b/src/pages/teams/settings.mdx index 141afb0..a0e13ab 100644 --- a/src/pages/teams/settings.mdx +++ b/src/pages/teams/settings.mdx @@ -1,32 +1,32 @@ --- -title: Settings -description: A map of team and personal settings in Splits. Members, accounts, recovery, networks, tokens, banks, and API keys. +title: "Settings" +description: "Team configuration and personal preferences" --- -# Settings [A map of team and personal settings, and who can see each] +# Settings [Team configuration and personal preferences] -The [Settings page](https://app.splits.org/settings/) is split into **team settings** (configuration shared by everyone on the team) and **personal settings** (the member's alone, persisting across every team they're part of). +**Settings** contains team configuration and personal preferences. The [Settings page](https://app.splits.org/settings/) separates these groups. + +Team settings apply to the selected team. Personal settings belong to a member across teams. ## Team settings -| Section | What it controls | Visible to | Learn more | +| Section | Controls | Visible to | Learn more | | --- | --- | --- | --- | -| **General** | Team name, logo, and the *Require memos* setting | Members | [Teams](/teams) | -| **Members** | Inviting members, Owner/Member roles | Members | [Members](/members) | -| **Accounts** | Each account's name, signers, and threshold | Members | [Editing](/accounts/editing) | -| **API Keys** | Keys for programmatic access via the API, CLI, and MCP | Members | [Agents & API](/introduction/agents) | -| **Networks** | Which networks the team's accounts are active on | Owners | [Networks and assets](/introduction/networks-and-assets) | -| **Recovery** | Recovery signers and verification | Owners | [Recovery](/teams/recovery#recovery-signers) | -| **Tokens** | Which tokens are shown or hidden (spam) for the team | Owners | [Spam & tokens](/accounting/spam) | -| **Banks** | Legal entity verification (KYB/KYC), connected bank accounts, onramp details | Owners | [On/offramping](/banking) | +| General | Team name, logo, memo requirement | Members | [Teams](/teams) | +| Members | Invitations and roles | Members | [Members](/members) | +| Accounts | Names, signers, thresholds | Members | [Editing](/accounts/editing) | +| API Keys | Programmatic access | Members | [Agents & API](/introduction/agents) | +| Networks | Active networks | Owners | [Networks and assets](/introduction/networks-and-assets) | +| Recovery | Recovery signers and verification | Owners | [Recovery](/teams/recovery) | +| Tokens | Token visibility | Owners | [Spam & tokens](/accounting/spam) | +| Banks | Entity verification and bank accounts | Owners | [Banking](/banking) | -Changing most team settings requires the **Owner** role. See [Roles](/teams/roles) for the full matrix. +Most team configuration changes require the Owner role. [Roles](/teams/roles) defines permissions. ## Personal settings -Personal settings persist across every team a member is part of: - -- **General**: display name, email address, and avatar -- **Passkeys**: add, verify, and manage the passkeys used to sign transactions +- **General**: display name, email address, and avatar. +- **Passkeys**: passkey registration, verification, and management. -See [Signing keys](/members/keys) for how passkeys work. +[Signing keys](/members/keys) describes passkeys. diff --git a/src/pages/transactions/batch.mdx b/src/pages/transactions/batch.mdx index 8c6eb3a..f56f701 100644 --- a/src/pages/transactions/batch.mdx +++ b/src/pages/transactions/batch.mdx @@ -1,14 +1,27 @@ --- -title: Batch -description: Batches group multiple transactions into one signature, from inside the Splits app or from external apps via the browser extension and WalletConnect. +title: "Batch" +description: "Group transactions for a signature" --- -# Batch [Group multiple transactions into one signature] +# Batch [Group transactions for a signature] -A batch collects multiple transactions and executes them under a single signature. Prepare any transaction and, in the review dialog, select *Add to batch* instead of *Submit*; keep adding, then open the batch (at the top of the transaction feed) and submit once. Before signing, you can inspect each transaction's calls, view a simulation, or remove it from the batch. +A **batch** groups multiple transactions for execution. A signer signs the batch once. -Each team has **one active batch at a time, on a single account and network**; *Add to batch* is disabled elsewhere while a batch is open. A batch holds up to 50 transactions. +**Each team can have only one active batch on one account and network.** Other accounts and networks cannot add transactions to that batch. A batch holds up to 50 transactions. -## Batching from external apps +1. Prepare a transaction. +2. In the review dialog, select *Add to batch*. +3. Repeat for other transactions on the same account and network. +4. Open the batch above the transaction feed. +5. Inspect each transaction's calls and simulation. +6. Remove any unwanted transactions. +7. Submit the batch. +8. Sign the batch. -Transactions initiated in external apps through the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect) offer the same *Add to batch* option in the Splits signing dialog. The external app may not show the transaction as pending until the batch executes. + + +## Batches from external apps + +The [browser extension](/introduction/extension) and [WalletConnect](/integrations/walletconnect) also show *Add to batch* for transactions from other apps. + +The other app might not show the transaction as pending until the batch executes. diff --git a/src/pages/transactions/custom.mdx b/src/pages/transactions/custom.mdx index 8794b52..0c0ce0c 100644 --- a/src/pages/transactions/custom.mdx +++ b/src/pages/transactions/custom.mdx @@ -1,21 +1,34 @@ --- -title: Custom -description: Custom transactions call any writable function on any contract on a supported network, from a form when the ABI is known or from raw calldata when it isn't. +title: "Custom transactions" +description: "Call functions on a contract" --- -# Custom transactions [Call any writable function on any contract] +# Custom transactions [Call functions on a contract] -A custom transaction is a proposal built from arbitrary contract calls: use it for anything without a dedicated flow, like ERC-20 approvals, protocol interactions, or [enabling a module](/accounts/modules#enabling-and-disabling). It can call any writable function on any contract deployed on a [supported network](/introduction/networks-and-assets). +A **custom transaction** is a proposal with contract calls. It can call writable functions on contracts deployed on a [supported network](/introduction/networks-and-assets). -Open the builder with *Custom txn* on an account's page and paste the contract address, selecting the network if the contract exists on more than one. +Examples include ERC-20 approvals and [module changes](/accounts/modules#enable-or-disable-a-module). -- **Contract with a known ABI**: pick from the contract's writable functions and fill in its parameters. Payable functions add an *Amount to pay* input, denominated in ETH. -- **No ABI found**: enter raw calldata, with an optional ETH amount. +1. Open an account page. +2. Select *Custom txn*. +3. Enter the contract address. +4. If the contract exists on multiple networks, select the network. -*Review* shows the decoded calls and a simulation before you submit. Like every [transaction](/transactions), a custom transaction executes once signatures meet the account's threshold. +The remaining inputs depend on the application binary interface (ABI): + +- **Known ABI**: the form lists writable functions and their parameters. Payable functions include an *Amount to pay* field in ETH. +- **Unknown ABI**: the form accepts raw calldata and an optional ETH amount. + +1. Enter the function inputs or calldata. +2. Select *Review*. +3. Check the decoded calls and simulation. +4. Submit the proposal. +5. Obtain the required signatures. + +[Transactions](/transactions) describes proposal execution. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits transactions create custom --account 0x... --chainId 8453 --calls '[{"to":"0x...","data":"0x..."}]'`: propose 1–20 raw calls in one transaction, each with an optional `value` in wei (**Write** scope) +- `splits transactions create custom --account 0x... --chainId 8453 --calls '[{"to":"0x...","data":"0x..."}]'`: propose 1–20 calls with optional `value` amounts in wei (**Write** scope). diff --git a/src/pages/transactions/index.mdx b/src/pages/transactions/index.mdx index c651357..14ff5f6 100644 --- a/src/pages/transactions/index.mdx +++ b/src/pages/transactions/index.mdx @@ -1,44 +1,50 @@ --- -title: Transactions -description: How transactions work in Splits (proposals, signing against a threshold, execution) and the transaction types available. +title: "Transactions" +description: "Proposals, signatures, and execution" --- -# Transactions [Proposals that execute onchain once signers meet the threshold] +# Transactions [Proposals, signatures, and execution] -Every transaction in Splits is a **proposal** that executes onchain once enough of the account's [signers](/accounts/signers) approve it to meet the account's threshold. On a 1-of-n account, proposing and executing happen in one step; on higher thresholds, the transaction waits for the remaining signatures. +A **transaction** in Splits starts as a proposal. It executes onchain when the account's [signers](/accounts/signers) meet its [threshold](/accounts/thresholds). -Who can propose is governed by [roles](/teams/roles): Owners can propose from any account, Members only from accounts they're a signer on. +With a 1-of-n threshold, a signer can propose and execute in one step. A higher threshold requires additional signatures. + +[Roles](/teams/roles) control who can propose transactions. Owners can propose from any account. Members can propose from accounts on which they have a signer. ## Types -| Type | What it does | +| Type | Function | | --- | --- | -| [Sends](/transactions/sends) | Move tokens to any recipient, including tokens you don't hold, via just-in-time swaps | -| [Swaps](/transactions/swaps) | Trade between tokens (bridging included), with no fees from Splits | -| [Custom](/transactions/custom) | Call any writable function on any contract on a supported network | -| [Batch](/transactions/batch) | Group multiple transactions into one signature | -| [Schedules](/transactions/schedules) | Recur a transaction on an interval (e.g. payroll) | +| [Sends](/transactions/sends) | Transfer tokens to a recipient | +| [Swaps](/transactions/swaps) | Exchange tokens within or across networks | +| [Custom](/transactions/custom) | Call contract functions | +| [Batch](/transactions/batch) | Group transactions for a signature | +| [Schedules](/transactions/schedules) | Create transfer proposals at intervals | -Every transaction can carry a [memo](/transactions/memos), and a team can require them on all transactions via the *Require memos* [setting](/teams/settings). +Transactions can have [memos](/transactions/memos). The *Require memos* [setting](/teams/settings) makes a memo mandatory for new transactions. ## Gas sponsorship -Splits sponsors transaction fees with a monthly gas stipend, applied at the team level and shared across all accounts and networks. The stipend is based on the team's [Earn](/accounts/earn) balance: +**Gas sponsorship** pays transaction fees from a team allowance. Accounts and networks share this allowance. + +The team's [Earn](/accounts/earn) balance determines the allowance: -| Earn balance | Stipend | +| Earn balance | Allowance | | --- | --- | -| $0 | $5, one-time | -| $1k+ | $5 per month | -| $10k+ | $10 per month | -| $100k+ | $100 per month | +| $0 | $5 once | +| $1,000+ | $5 per month | +| $10,000+ | $10 per month | +| $100,000+ | $100 per month | + +After the allowance runs out, the account pays fees in the network's native token. The app reports an insufficient balance at submission. -Once the stipend is used, transactions are paid in the network's native token (e.g. ETH); the app tells you at submission if the account doesn't hold enough. There is no way to view the remaining stipend yet. Sponsorship policies can change at any time without notice. +The app does not show the remaining allowance. Sponsorship policies can change without notice. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits transactions list` / `get `: query transactions with rich filters (**Read** scope) -- `splits transactions create transfer` / `create custom`: propose transactions (**Write** scope) -- `splits transactions sign `: sign with a registered EOA (**Write** scope) -- `splits transactions cancel `: cancel a pending proposal (**Write** scope) +- `splits transactions list` / `get `: query transactions (**Read** scope). +- `splits transactions create transfer` / `create custom`: propose transactions (**Write** scope). +- `splits transactions sign `: sign with a registered EOA (**Write** scope). +- `splits transactions cancel `: cancel a pending proposal (**Write** scope). diff --git a/src/pages/transactions/memos.mdx b/src/pages/transactions/memos.mdx index 67562d3..dafc80a 100644 --- a/src/pages/transactions/memos.mdx +++ b/src/pages/transactions/memos.mdx @@ -1,23 +1,37 @@ --- -title: Memos -description: Memos annotate transactions for accounting, added at creation or retroactively, on outbound and inbound activity, and optionally required team-wide. +title: "Memos" +description: "Add transaction notes for accounting records" --- -# Memos [Annotate transactions for clean books] +# Memos [Add transaction notes for accounting records] -A memo is a short note attached to a transaction. Memos appear alongside transactions throughout the app, filter the [Accounting page](/accounting), and are included in CSV exports. +A **memo** is a short note on a transaction. Memos appear with transactions in the app and in CSV exports. [Accounting](/accounting) can filter transactions by memo. -## Adding a memo + -Add memos when creating a transaction, or retroactively from the transaction's row menu (*Add memo*) in any feed, including on inbound transactions, where retroactive memos matter most. Any member can add or edit memos. +## Add a memo -## Requiring memos +Any member can add or edit a memo. A new transaction form accepts a memo. -The *Require memos* [team setting](/teams/settings) rejects any new transaction without a memo, on every surface: the app, the [CLI / MCP](/introduction/agents), and [schedules](/transactions/schedules). It applies at creation only; memos on existing transactions stay editable. +For an existing inbound or outbound transaction: + +1. Open the transaction's row menu. +2. Select *Add memo*. +3. Enter the memo. + + + +## Require memos + +The *Require memos* [team setting](/teams/settings) rejects new transactions without memos. It applies to the app, [CLI and MCP](/introduction/agents), and [schedules](/transactions/schedules). + +The check applies at creation. Memos on existing transactions remain editable. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): + +- `splits transactions memo --memo "text"`: set or clear a memo, with a maximum of 500 characters (**Write** scope). +- `splits transactions properties set --properties '{"k":"v"}'`: set structured transaction properties (**Write** scope). -- `splits transactions memo --memo "text"`: set or clear a memo, max 500 characters (**Write** scope) -- `splits transactions properties set --properties '{"k":"v"}'`: structured JSON metadata beyond a memo; see [Agents & API](/introduction/agents#transaction-metadata) (**Write** scope) +[Transaction metadata](/introduction/agents#transaction-metadata) describes property limits. diff --git a/src/pages/transactions/schedules.mdx b/src/pages/transactions/schedules.mdx index a9d13cd..c670c61 100644 --- a/src/pages/transactions/schedules.mdx +++ b/src/pages/transactions/schedules.mdx @@ -1,14 +1,18 @@ --- -title: Schedules -description: "Schedules create recurring transfer proposals on a daily, weekly, or monthly interval: how drafts are generated, notified, and approved." +title: "Schedules" +description: "Create transfer proposals at regular intervals" --- -# Schedules [Recur a transfer on an interval, like payroll] +# Schedules [Create transfer proposals at regular intervals] -A schedule creates a transfer on a daily, weekly, or monthly interval from a chosen account, paying pre-defined amounts to one or more recipients. Use it for payroll, topping up sub-accounts, or diversifying assets on a cadence. +A **schedule** creates transfer proposals at daily, weekly, or monthly intervals. It specifies an account, recipients, and amounts. -Each occurrence is a **draft proposal, not an executed transaction**: funds are self-custodied, so the account's signers still approve every one at its [threshold](/accounts/thresholds). Drafts are generated daily at 11:00 UTC, the account's signers are emailed when one is ready, and reminders follow while a draft sits unsigned. +**Each occurrence creates a draft proposal without execution.** The account's signers must approve each proposal at its [threshold](/accounts/thresholds). -Schedules handle transfers only; they can't run swaps or custom transactions. [Owners](/teams/roles) create, edit, pause, and delete schedules from the *Schedules* page; any member can view them. +Splits creates drafts daily at 11:00 UTC. It emails the account's signers when a draft is ready. It sends reminders while the draft lacks signatures. -A recipient that is a contact can [restrict what it accepts](/contacts#restrictions), which affects whether this schedule's drafts can be signed. +**Schedules support transfers only.** They cannot execute swaps or custom transactions. + +[Owners](/teams/roles) can create, edit, pause, and delete schedules on the *Schedules* page. Any member can view them. + +A contact's [send rules](/contacts#restrictions) can prevent signatures on a scheduled draft. diff --git a/src/pages/transactions/sends.mdx b/src/pages/transactions/sends.mdx index 3cb8d0e..254248e 100644 --- a/src/pages/transactions/sends.mdx +++ b/src/pages/transactions/sends.mdx @@ -1,33 +1,45 @@ --- -title: Sends -description: "Sends move tokens or NFTs from a Splits account to any recipient: the recipient types accepted, just-in-time swaps, memos, batching, signing, and private transfers." +title: "Sends" +description: "Transfer tokens from an account to a recipient" --- -# Sends [Move tokens from an account to any recipient] +# Sends [Transfer tokens from an account to a recipient] -A send moves tokens from one of your [accounts](/accounts) to any recipient: a raw address, an ENS name, a Farcaster username, a saved [contact](/contacts), another of your accounts, or (with [banking](/banking) set up) a bank account. The amount you enter is the amount **the recipient receives**. NFTs (ERC-721 and ERC-1155) can be sent the same way. +A **send** transfers tokens from a Splits [account](/accounts) to a recipient. The entered amount is the amount the recipient receives. -You can send a token the account doesn't hold: as long as the account holds enough value to cover it, pick which token to sell and the swap and send execute as one transaction. See [just-in-time swaps](/transactions/swaps#just-in-time-swaps). +Recipients can be addresses, ENS names, Farcaster names, saved [contacts](/contacts), other team accounts, or configured [bank accounts](/banking). Sends also support ERC-721 and ERC-1155 NFTs. -A contact can [restrict what it accepts](/contacts#restrictions), by network or by token. +A [just-in-time swap](/transactions/swaps#just-in-time-swaps) can supply a token that the account does not hold. The account needs sufficient value in another token. -To pay a recipient without revealing which account paid them, use a [private transfer](#private-transfers). +A contact's [send rules](/contacts#restrictions) can limit accepted tokens and networks. ## Memos -Add a [memo](/transactions/memos) when creating the send; it makes your [accounting](/accounting) dramatically easier later. +A [memo](/transactions/memos) records the reason for a send. The transaction form accepts a memo before submission. ## Multisend -Use [Multisend](https://app.splits.org/send/?multisend=true) to send the same token on the same network to up to 100 recipients in one transaction. Add recipients individually or upload a CSV containing one `address or ENS name, amount` pair per row. If the account lacks enough of the send token, Multisend can swap another token on the same network; cross-network sends are not supported. +**Multisend** transfers the same token on one network to up to 100 recipients in one transaction. -## Batching +1. Open [Multisend](https://app.splits.org/send/?multisend=true). +2. Add recipients individually or upload a CSV. +3. For a CSV, use one `address or ENS name, amount` pair per row. +4. Review the transfers. +5. Submit the proposal. -Instead of submitting a send right away, select *Add to batch* in the review dialog to group it with other transactions under one signature. See [Batch](/transactions/batch). +Multisend can exchange another token on the same network if the account lacks the requested token. **Multisend does not support transfers across networks.** + + + +## Batches + +*Add to batch* in the review dialog groups a send with other transactions. [Batch](/transactions/batch) describes this procedure. ## Private transfers -A private transfer hides the connection between your account and the recipient: the payout can't be traced back to your account onchain, though your team still sees the recipient and amount in the Splits UI. Private transfers route through [NEAR Confidential Intents](https://intents.near.org/confidential). See the table for supported tokens. +A **private transfer** uses [NEAR Confidential Intents](https://intents.near.org/confidential) for the recipient payment. Your team can still see the recipient and amount in Splits. + +The provider describes the privacy properties of this route. The supported tokens and networks are: | Token | Networks | | --- | --- | @@ -38,10 +50,10 @@ A private transfer hides the connection between your account and the recipient: ## Recurring sends -To pay out pre-defined amounts on an interval (e.g. payroll), use [Schedules](/transactions/schedules). +[Schedules](/transactions/schedules) describes transfer proposals for specified amounts at regular intervals. ## Programmatic access -Via the [Splits CLI / MCP](/introduction/agents): +Through the [Splits CLI / MCP](/introduction/agents): -- `splits transactions create transfer --account 0x... --chainId 8453 --recipient 0x... --token 0x... --amount 100`: propose a transfer, with optional `--memo` and `--properties` (**Write** scope) +- `splits transactions create transfer --account 0x... --chainId 8453 --recipient 0x... --token 0x... --amount 100`: propose a transfer with optional memo and properties (**Write** scope). diff --git a/src/pages/transactions/swaps.mdx b/src/pages/transactions/swaps.mdx index 9b91e97..336b78c 100644 --- a/src/pages/transactions/swaps.mdx +++ b/src/pages/transactions/swaps.mdx @@ -1,30 +1,56 @@ --- -title: Swaps -description: "How swaps work in Splits: multi-provider routing with no fees, bridging across networks, just-in-time swaps inside sends, slippage, and expiring quotes on multisigs." +title: "Swaps" +description: "Exchange tokens within or across networks" --- -# Swaps [Trade and bridge tokens with no fees from Splits] +# Swaps [Exchange tokens within or across networks] -A swap trades one token for another, including across networks: moving ETH on Base to ETH on Optimism is a swap. Splits charges no fees on swaps. Each quote is routed across multiple swap providers (currently 0x, KyberSwap, Velora, Relay, and Fabric; Relay handles cross-network swaps) and the best result is used. The exception is [private transfers](/transactions/sends#private-transfers), which always route through a dedicated provider. +A **swap** exchanges one token for another. It can also move tokens across networks, such as ETH from Base to Optimism. Splits charges no swap fee. -Start a swap from the Dashboard or an account page. Any ERC-20 can be swapped: if a token isn't listed, paste its address with the right network selected. +Splits compares quotes from 0x, KyberSwap, Velora, Relay, and Fabric. Relay handles swaps across networks. [Private transfers](/transactions/sends#private-transfers) use a separate provider. + +To start a swap: + +1. Open the Dashboard or an account page. +2. Start a swap. +3. Select the tokens and networks. +4. If an ERC-20 token is absent from the list, enter its address on the correct network. ## Just-in-time swaps -[Sends](/transactions/sends) let you pay in a token the account doesn't hold. Specify the token the recipient receives, then pick which held token to sell; the swap and the send execute as a single transaction, and the recipient receives the exact amount specified. +A **just-in-time swap** supplies the token for a [send](/transactions/sends). The account can pay a token that it does not currently hold. + +1. Select the token and amount for the recipient. +2. Select a token in the account to sell. + +The swap and send execute in one transaction. The recipient receives the specified amount. ## Slippage -Every swap has a slippage tolerance you can edit before submitting. The default depends on the pair: 0.1% between stablecoins, 0.5% between major tokens, 1.5% otherwise. +**Slippage tolerance** limits the permitted price change for a swap. You can change it before submission. + +| Token pair | Default tolerance | +| --- | --- | +| Stablecoins | 0.1% | +| Major tokens | 0.5% | +| Other pairs | 1.5% | ## Quotes and multisigs -Swap quotes are signed by the provider and expire quickly, often before an account with a threshold above 1 collects its signatures. An expired quote is rejected onchain, and the transaction has to be recreated. Ways to avoid this: +Providers sign swap quotes with an expiration time. A quote can expire before an account collects enough signatures. -1. Swap from a 1-of-n [operating account](/accounts#operating-accounts), funded from higher-threshold accounts. -2. If you always swap into the same output token, use an [automation account](/accounts#automation-accounts) instead and just transfer funds in. -3. Use the [browser extension](/introduction/extension) with a swap app that tolerates slow signing (e.g. CoW Swap). +**An expired quote fails onchain.** The swap then requires a new transaction. + +Options for this situation include: + +- A 1-of-n [operating account](/accounts#operating-accounts) with a limited balance. +- An [automation account](/accounts#automation-accounts) for repeated conversion to the same output token. +- A swap app that supports a longer signature collection period, connected through the [browser extension](/introduction/extension). ## Programmatic access -The [CLI / MCP](/introduction/agents) has no dedicated swap command. Agents can propose swaps as raw calls via `splits transactions create custom` (**Write** scope). +Through the [Splits CLI / MCP](/introduction/agents): + +- `splits transactions create custom`: propose swap contract calls (**Write** scope). + +The CLI has no dedicated swap command. diff --git a/vocs.config.ts b/vocs.config.ts index 8ba73d0..07b6dd7 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -44,6 +44,7 @@ export default defineConfig({ collapsed: false, items: [ { text: 'Core concepts', link: '/introduction/core-concepts' }, + { text: 'Glossary', link: '/resources/glossary' }, { text: 'Networks & assets', link: '/introduction/networks-and-assets' }, { text: 'Browser extension', link: '/introduction/extension' }, { text: 'Agents & API', link: '/introduction/agents' }, From cdb2961aa5cac39942ccd2f00af67960ce8b0fa4 Mon Sep 17 00:00:00 2001 From: abramdawson <2624720+abramdawson@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:39:41 -0700 Subject: [PATCH 2/2] docs: keep STE terminology internal --- .github/pull_request_template.md | 2 +- .github/workflows/docs.yml | 2 +- CLAUDE.md | 6 +- README.md | 5 +- STE.md | 7 +- package.json | 5 +- scripts/ste/glossary.mjs | 49 -- scripts/ste/glossary.test.mjs | 42 -- scripts/ste/terms.test.mjs | 16 + src/pages.gen.ts | 1 - src/pages/index.mdx | 2 - src/pages/resources/glossary.mdx | 992 ------------------------------- vocs.config.ts | 1 - 13 files changed, 28 insertions(+), 1102 deletions(-) delete mode 100644 scripts/ste/glossary.mjs delete mode 100644 scripts/ste/glossary.test.mjs create mode 100644 scripts/ste/terms.test.mjs delete mode 100644 src/pages/resources/glossary.mdx diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 93b3f1c..3b0e9f9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,7 @@ Describe the documentation change and its source evidence. - [ ] I followed [CLAUDE.md](../CLAUDE.md) and [STE.md](../STE.md). - [ ] I checked new or changed behavior against product source. -- [ ] I defined new technical terms and regenerated the glossary where necessary. +- [ ] I defined new technical terms in the internal registry and on their canonical pages. - [ ] `pnpm build` passes, including prose checks and checker tests. - [ ] I read the rendered pages and their Markdown twins. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3075ee7..ebeeebb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,5 +25,5 @@ jobs: node-version: '22' cache: pnpm - run: pnpm install --frozen-lockfile - # Build includes prose checks, glossary consistency, and checker tests. + # Build includes prose checks, term registry validation, and checker tests. - run: pnpm build diff --git a/CLAUDE.md b/CLAUDE.md index 530e719..c016d4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ An agent can prepare a complete update from a product PR. A separate STE editori | Fact | Canonical home | | --- | --- | -| Technical term definitions and permitted uses | `scripts/ste/terms.json`, generated as `/resources/glossary` | +| Internal terminology reference for authors and checks | `scripts/ste/terms.json` | | Team definition, creating a team, setup steps | `/teams` | | Roles, capability matrix, settings visibility, API key scopes, read-only members | `/teams/roles` | | Recovery, recovery signers, verifying them | `/teams/recovery` | @@ -103,8 +103,8 @@ If a change moves a fact's canonical home, update this table in the same PR. Public prose must follow [STE.md](STE.md), which targets ASD-STE100 Issue 9. This includes titles, subtitles, metadata, tables, link labels, callouts, and image descriptions. Use at most 20 words per sentence and six sentences per paragraph. This sentence limit is stricter than the standard's descriptive limit. - Use the official dictionary for general words and their meanings and parts of speech. The local linter is not a full dictionary checker. -- Define technical terms in `scripts/ste/terms.json`, then run `pnpm glossary:generate`. Canonical feature pages own behavior. The glossary owns lexical definitions. +- Record technical terms in `scripts/ste/terms.json` for authors and checks. Define terms for readers on their canonical feature pages. Run `pnpm check:docs` to validate the registry and public prose. - Use one instruction per sentence in numbered procedures. State conditions first. Keep instructions out of notes. - Use active voice and simple verb forms. Expand contractions. Preserve literal UI labels, commands, and identifiers. -- Run `pnpm build`. It runs prose checks, glossary consistency checks, and regression tests before the Vocs build. +- Run `pnpm build`. It runs prose checks, term registry validation, and regression tests before the Vocs build. - A separate maintainer must review the current commit against the official standard and approve with `STE review complete`. An agent must not claim full compliance from a passing linter. diff --git a/README.md b/README.md index 3b119d6..0b6fc4e 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,18 @@ How these docs get updated, by humans or agents: 1. **Verify with subagents.** Fan out read-only agents per feature area against the source repos, requiring file:line evidence and an explicit "cannot verify" for anything the code doesn't answer. 2. **Check source evidence.** Re-check surprising claims in the primary source before writing them. A separate maintainer must complete STE editorial review before merge. -3. **Check the output.** Run `pnpm build` for mandatory prose checks, glossary verification, checker tests, and link validation, and read the `.md` twin (`curl localhost:5173/docs/.md`); the twin is what agents consume. Twins, `llms.txt`, and `llms-full.txt` all live under the base path, locally and in production. +3. **Check the output.** Run `pnpm build` for mandatory prose checks, term registry validation, checker tests, and link validation, and read the `.md` twin (`curl localhost:5173/docs/.md`); the twin is what agents consume. Twins, `llms.txt`, and `llms-full.txt` all live under the base path, locally and in production. 4. **Mind the URLs.** The sidebar lives in `vocs.config.ts` and URLs derive from file paths under `src/pages/`, so moving a file means grepping for inbound links first. ## STE checks ```sh pnpm check:docs # scan all public Markdown and MDX -pnpm glossary:generate # update the glossary after term registry changes pnpm test:prose # test the checker and editorial review gate pnpm build # run all checks and build the site ``` -Edit technical terms in [scripts/ste/terms.json](scripts/ste/terms.json). The generated glossary is available in both HTML and Markdown. The checker reports file and line locations and fails on findings. New pages enter the scan automatically. +Edit technical terms in [scripts/ste/terms.json](scripts/ste/terms.json), the internal reference for authors and checks. Define terms for readers on their canonical feature pages. The checker reports file and line locations and fails on findings. New pages enter the scan automatically. The checker covers a defined subset of STE rules. A qualified editorial review must check vocabulary, meanings, grammar, and instructions against the official standard. The reviewer approves the current commit with `STE review complete` in the review body. diff --git a/STE.md b/STE.md index a3f81bc..9115fe9 100644 --- a/STE.md +++ b/STE.md @@ -16,7 +16,7 @@ To introduce a term: 2. Check the standard's dictionary and technical terminology categories. 3. Add an entry with its definition, category, permitted use, and canonical page. For a technical verb, specify its permitted forms. 4. Define or expand the term at first substantive use on its canonical page. Link to that page at first use elsewhere. -5. Run `pnpm glossary:generate`. Do not edit the generated [glossary](src/pages/resources/glossary.mdx) directly. +5. Run `pnpm check:docs` to validate the internal term registry and public prose. 6. Request terminology review with the content change. Ordinary synonyms do not qualify as technical terms merely to pass a check. A noun entry never permits its use as a verb. For example, the account *threshold* is a number of approvals. An API key *scope* is a permission. A *member* is a person, while *Member* names a role. An *account owner* is an onchain account, while *Owner* names a role. The registry and canonical pages keep these meanings separate. @@ -43,14 +43,13 @@ Preserve literal commands, flags, addresses, and interface labels. Rewrite their ```sh pnpm install --frozen-lockfile -pnpm glossary:generate # after a term change -pnpm check:docs # all public prose and glossary consistency +pnpm check:docs # all public prose and term registry validation node scripts/check-prose.mjs src/pages/accounts # optional focused check pnpm test:prose # checker and review-gate regression tests pnpm build # all checks, then production and link validation ``` -Findings fail the command. There is no warning-only mode, baseline of ignored pages, or inline suppression mechanism. New `.md` and `.mdx` files enter the scan automatically. The build uses the same checks as CI. A term change also invalidates an outdated generated glossary. +Findings fail the command. There is no warning-only mode, baseline of ignored pages, or inline suppression mechanism. New `.md` and `.mdx` files enter the scan automatically. The build uses the same checks as CI. The checker also validates term definitions, canonical pages, permitted uses, and technical verb forms in the internal registry. The checker parses Markdown and MDX. It checks sentence and paragraph limits, selected vocabulary and term variants, contractions, semicolons, em dashes, and common verb problems. It reads prose in metadata, table cells, callouts, link labels, and image descriptions. It excludes code, imports, link destinations, and non-prose component attributes. diff --git a/package.json b/package.json index 6891f99..ec619b2 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,9 @@ "build": "pnpm check && vocs build", "preview": "vocs preview", "start": "node dist/serve-node.js", - "check:docs": "node scripts/ste/glossary.mjs --check && node scripts/check-prose.mjs", + "check:docs": "node scripts/check-prose.mjs", "test:prose": "node --test scripts/ste/*.test.mjs", - "check": "pnpm check:docs && pnpm test:prose", - "glossary:generate": "node scripts/ste/glossary.mjs" + "check": "pnpm check:docs && pnpm test:prose" }, "dependencies": { "react": "^19", diff --git a/scripts/ste/glossary.mjs b/scripts/ste/glossary.mjs deleted file mode 100644 index 36301e1..0000000 --- a/scripts/ste/glossary.mjs +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node -import { readFileSync, writeFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' -import { resolve } from 'node:path' -import { parse as parseYaml } from 'yaml' -import { terms, checkTerms } from './check.mjs' - -const output = new URL('../../src/pages/resources/glossary.mdx', import.meta.url) -export function renderGlossary() { - const intro = `--- -title: Glossary -description: Technical terms used in the Splits docs ---- - -# Glossary [Technical terms used in the Splits docs] - -This **glossary** defines technical terms in these docs. Each entry links to the page that describes the related product behavior. - -` - return intro + terms.map(entry => { - const path = entry.home.split('#')[0] - const base = new URL(`../../src/pages${path}`, import.meta.url) - let source - for (const suffix of ['.mdx', '/index.mdx']) { - try { source = readFileSync(fileURLToPath(base) + suffix, 'utf8'); break } catch {} - } - const title = parseYaml(source.match(/^---\n([\s\S]*?)\n---/)[1]).title - return `## ${entry.term}\n\n${entry.definition}\n\n[${title}](${entry.home}).\n` - }).join('\n') -} -export function runGlossary(args = process.argv.slice(2)) { -const errors = checkTerms() -if (errors.length) { - console.error(errors.join('\n')) - process.exitCode = 1 -} else if (args.includes('--check')) { - let current = '' - try { current = readFileSync(output, 'utf8') } catch {} - if (current !== renderGlossary()) { - console.error('Glossary is outdated. Run pnpm glossary:generate.') - process.exitCode = 1 - } else console.log('Glossary matches the term registry.') -} else { - writeFileSync(output, renderGlossary()) - console.log('Generated src/pages/resources/glossary.mdx.') -} - -} -if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) runGlossary() diff --git a/scripts/ste/glossary.test.mjs b/scripts/ste/glossary.test.mjs deleted file mode 100644 index 7bf4e8a..0000000 --- a/scripts/ste/glossary.test.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' -import { mkdtempSync, mkdirSync, cpSync, symlinkSync, writeFileSync, readFileSync, rmSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { spawnSync } from 'node:child_process' -import { root, checkTerms } from './check.mjs' - -const entry = { - term: 'account', kind: 'noun', category: 'computing', - definition: 'An account holds assets.', home: '/accounts', usage: 'Use as a noun.', -} -test('validates registry structure, definitions, homes, and technical verb forms', () => { - assert.deepEqual(checkTerms([entry]), []) - for (const entries of [[], {}, [null], [entry, entry], [{...entry, definition: ''}], - [{...entry, kind: 'adjective'}], [{...entry, home: '/missing-page'}], - [{...entry, kind: 'verb'}], [{...entry, avoid: 'anything'}]]) { - assert.ok(checkTerms(entries).length > 0) - } -}) -test('glossary check fails for missing, stale, and manually edited output', () => { - const directory = mkdtempSync(join(tmpdir(), 'splits-glossary-')) - try { - mkdirSync(join(directory, 'src/pages/resources'), {recursive:true}) - mkdirSync(join(directory, 'scripts'), {recursive:true}) - cpSync(join(root, 'scripts/ste'), join(directory, 'scripts/ste'), {recursive:true}) - symlinkSync(join(root, 'node_modules'), join(directory, 'node_modules'), 'dir') - const registry = join(directory, 'scripts/ste/terms.json') - writeFileSync(registry, JSON.stringify([entry])) - writeFileSync(join(directory, 'src/pages/accounts.mdx'), '---\ntitle: Accounts\ndescription: Account records\n---\n\n# Accounts [Account records]\n') - const run = (...args) => spawnSync(process.execPath, ['scripts/ste/glossary.mjs', ...args], {cwd: directory, encoding:'utf8'}) - assert.equal(run('--check').status, 1) - assert.equal(run().status, 0) - assert.equal(run('--check').status, 0) - writeFileSync(registry, JSON.stringify([{...entry, definition: 'An account contains assets.'}])) - assert.equal(run('--check').status, 1) - assert.equal(run().status, 0) - const output = join(directory, 'src/pages/resources/glossary.mdx') - writeFileSync(output, readFileSync(output, 'utf8') + '\nManual edit.\n') - assert.equal(run('--check').status, 1) - } finally { rmSync(directory, {recursive:true, force:true}) } -}) diff --git a/scripts/ste/terms.test.mjs b/scripts/ste/terms.test.mjs new file mode 100644 index 0000000..5195691 --- /dev/null +++ b/scripts/ste/terms.test.mjs @@ -0,0 +1,16 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { checkTerms } from './check.mjs' + +const entry = { + term: 'account', kind: 'noun', category: 'computing', + definition: 'An account holds assets.', home: '/accounts', usage: 'Use as a noun.', +} +test('validates registry structure, definitions, homes, and technical verb forms', () => { + assert.deepEqual(checkTerms([entry]), []) + for (const entries of [[], {}, [null], [entry, entry], [{...entry, definition: ''}], + [{...entry, kind: 'adjective'}], [{...entry, home: '/missing-page'}], + [{...entry, kind: 'verb'}], [{...entry, avoid: 'anything'}]]) { + assert.ok(checkTerms(entries).length > 0) + } +}) diff --git a/src/pages.gen.ts b/src/pages.gen.ts index 3afbfb9..f6cf0cf 100644 --- a/src/pages.gen.ts +++ b/src/pages.gen.ts @@ -47,7 +47,6 @@ type Page = | { path: '/members'; render: 'static' } | { path: '/members/keys'; render: 'static' } | { path: '/resources/brand-assets'; render: 'static' } - | { path: '/resources/glossary'; render: 'static' } | { path: '/resources/how-we-work'; render: 'static' } | { path: '/resources/incorporating-and-raising-capital'; render: 'static' } | { path: '/resources/security'; render: 'static' } diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 33b86da..3d6a6f2 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -23,8 +23,6 @@ The docs cover these functions: - [Integrations](/integrations): connections to other apps. - [Experiments](/experiments): prototype products. -The [glossary](/resources/glossary) defines the technical terms in these docs. - ## Who uses Splits diff --git a/src/pages/resources/glossary.mdx b/src/pages/resources/glossary.mdx deleted file mode 100644 index 5ed0e01..0000000 --- a/src/pages/resources/glossary.mdx +++ /dev/null @@ -1,992 +0,0 @@ ---- -title: Glossary -description: Technical terms used in the Splits docs ---- - -# Glossary [Technical terms used in the Splits docs] - -This **glossary** defines technical terms in these docs. Each entry links to the page that describes the related product behavior. - -## ABI - -Application binary interface. The definition of a contract's functions and encoded inputs. - -[Custom transactions](/transactions/custom). - -## account - -An address and contract that hold assets for a team. - -[Accounts](/accounts). - -## account owner - -The onchain account with authority over another account. - -[Accounts](/accounts). - -## accounting - -The preparation and maintenance of financial records. - -[Accounting](/accounting). - -## ACH - -Automated Clearing House. A US system for bank transfers. - -[Onramping](/banking/onramping). - -## address - -An identifier for an account or contract on a blockchain. - -[Contacts](/contacts). - -## agent - -Software that performs tasks on behalf of a person or team. - -[Agents & API](/introduction/agents). - -## allocation - -An amount or share assigned to a recipient. - -[PACT](/experiments/pact). - -## allowance - -A limit on the tokens that a spender can transfer. - -[Modules](/accounts/modules). - -## allowlist - -A list of addresses or items with permission for a function. - -[Modules](/accounts/modules). - -## API - -Application programming interface. A defined interface through which programs exchange requests and data. - -[Agents & API](/introduction/agents). - -## API key - -A credential that identifies API requests and their permitted scope. - -[Agents & API](/introduction/agents#get-an-api-key). - -## API key scope - -A permission assigned to an API key. - -[Roles](/teams/roles#api-key-scopes). - -## APY - -Annual percentage yield. An annual rate that includes the effect of accumulated interest. - -[Earn](/accounts/earn). - -## archive - -Remove an account from active app views without deleting its records. - -[Agents & API](/introduction/agents). - -## asset - -An item with value, such as a token. - -[Accounts](/accounts). - -## authenticate - -Confirm an identity or credential. - -[Agents & API](/introduction/agents). - -## Automated Earn - -The feature that deposits an account's available USDC into the Earn vault. - -[Earn](/accounts/earn#automated-earn). - -## automation account - -An account that applies a configured policy to deposits. - -[Accounts](/accounts#automation-accounts). - -## avatar - -An image that identifies a member. - -[Members](/members). - -## bank account - -An account at a bank for deposits and payments. - -[Banking](/banking). - -## banking - -Services that connect token transfers to bank accounts. - -[Banking](/banking). - -## batch - -A group of transactions prepared for execution together. - -[Batch](/transactions/batch). - -## bearer token - -A credential sent with a request to authorize access. - -[Agents & API](/introduction/agents#use-the-api). - -## blockchain - -A network record of transactions maintained by participating computers. - -[Networks and assets](/introduction/networks-and-assets). - -## bonding curve - -A rule that relates token price to the number of tokens sold. - -[PACT](/experiments/pact). - -## bridge transfer - -A transfer that moves value between blockchain networks. - -[Swaps](/transactions/swaps). - -## bug bounty - -A program that rewards eligible vulnerability reports. - -[Security & bug bounty](/resources/security). - -## calldata - -Encoded input sent to a smart contract function. - -[Custom transactions](/transactions/custom). - -## cap table - -A record of holders and their allocated shares or units. - -[PACT](/experiments/pact). - -## CLI - -Command-line interface. A program interface that accepts text commands. - -[Agents & API](/introduction/agents). - -## cliff - -The first date when a vesting plan releases tokens. - -[Integrations](/integrations#positions). - -## collateral - -Assets held to secure a loan or payment obligation. - -[Earn](/accounts/earn#risks). - -## command - -A text instruction to a program. - -[Agents & API](/introduction/agents). - -## compliance - -The collection of verified payee information and tax forms in Splits. - -[Compliance](/contacts/compliance). - -## configure - -Set the values that control a software function. - -[Agents & API](/introduction/agents). - -## contact - -A saved name for an external address. - -[Contacts](/contacts). - -## contract - -A program deployed at a blockchain address. - -[Custom transactions](/transactions/custom). - -## cost basis - -The acquisition value assigned to an asset for gain or loss calculations. - -[Accounting](/accounting). - -## CSV - -Comma-separated values. A text file format for rows and fields. - -[Accounting](/accounting). - -## custody address - -The address that controls a Farcaster account. - -[Farcaster](/integrations/farcaster#recovery-address). - -## custom transaction - -A transaction proposal with specified contract calls. - -[Custom transactions](/transactions/custom). - -## deploy - -Create a contract at a blockchain address. - -[Agents & API](/introduction/agents). - -## deposit - -Funds transferred into an account or vault. - -[Earn](/accounts/earn). - -## draft proposal - -A transaction prepared for approval that has not executed. - -[Schedules](/transactions/schedules). - -## Earn - -The Splits feature for stablecoin deposits that produce interest. - -[Earn](/accounts/earn). - -## ENS - -Ethereum Name Service. A system that associates names with blockchain addresses. - -[ENS](/integrations/ens). - -## EOA - -Externally owned account. A blockchain account controlled by a private key. - -[Signing keys](/members/keys#eoas). - -## ERC-1155 - -An Ethereum standard for multiple token types in one contract. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## ERC-20 - -The Ethereum standard for fungible tokens. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## ERC-4626 - -The Ethereum standard for tokenized vaults. - -[Earn](/accounts/earn). - -## ERC-721 - -An Ethereum standard for non-fungible tokens. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## ETH - -Ether. The native token of Ethereum. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## EUR - -The currency code for the euro. - -[Banking](/banking). - -## EURC - -Circle's euro stablecoin. - -[Paying vendors](/banking/paying-vendors). - -## execute - -Perform a transaction or contract call. - -[Agents & API](/introduction/agents). - -## executor - -An address that performs contract calls. - -[Modules](/accounts/modules). - -## experiment - -A prototype product from Splits. - -[Experiments](/experiments). - -## external account - -An address outside Splits that a team monitors. - -[Accounts](/accounts#external-accounts). - -## external bank account - -A bank account owned by a vendor or other recipient outside the team. - -[Paying vendors](/banking/paying-vendors). - -## fiat currency - -Money issued under a government's authority, such as USD or EUR. - -[Banking](/banking). - -## gas - -The measure of work required to execute a blockchain transaction. - -[Transactions](/transactions#gas-sponsorship). - -## gas sponsorship - -Payment of an account's transaction fees from a team allowance. - -[Transactions](/transactions#gas-sponsorship). - -## IBAN - -International bank account number. An identifier for a bank account. - -[Banking](/banking). - -## incorporation - -The creation of a legal entity. - -[Incorporating & raising capital](/resources/incorporating-and-raising-capital). - -## integration - -A connection between Splits and another app or protocol. - -[Integrations](/integrations). - -## invoice - -A request for payment of a specified amount. - -[Invoicing](/invoicing). - -## JSON - -JavaScript Object Notation. A text format for structured data. - -[Agents & API](/introduction/agents#transaction-metadata). - -## just-in-time swap - -A token exchange that supplies the requested token for a send. - -[Swaps](/transactions/swaps#just-in-time-swaps). - -## key pair - -A related public key and private key. - -[Signing keys](/members/keys). - -## KYB - -Know your business. Verification of a business's identity. - -[Banking](/banking#verify-an-entity). - -## KYC - -Know your customer. Verification of an individual's identity. - -[Banking](/banking#verify-an-entity). - -## legal entity - -A person or organization recognized by law as having rights and obligations. - -[Banking](/banking). - -## liquidation address - -A deposit address that a provider uses to convert tokens and pay a bank account. - -[Offramping](/banking/offramping#use-an-external-provider). - -## liquidity - -Funds available for a trade or withdrawal. - -[Earn](/accounts/earn#risks). - -## lockup - -A period during which a position restricts withdrawals. - -[Hedgey](/integrations/hedgey). - -## LP - -Liquidity provider. A participant that supplies assets to a trading pool. - -[Uniswap](/integrations/uniswap). - -## MCP - -Model Context Protocol. A protocol that connects AI tools to data and operations. - -[Agents & API](/introduction/agents#connect-to-ai-tools-mcp). - -## member - -A person who belongs to a Splits team. - -[Members](/members). - -## Member role - -The team role with fewer administration permissions than the Owner role. - -[Roles](/teams/roles). - -## memo - -A short note attached to a transaction. - -[Memos](/transactions/memos). - -## Merkle root - -A hash that represents a tree of data and permits proofs about its contents. - -[Editing](/accounts/editing#change-signers-and-thresholds). - -## metadata - -Additional information attached to a record, such as a memo or JSON properties. - -[Agents & API](/introduction/agents#transaction-metadata). - -## microdeposit - -A small test transfer for bank account verification. - -[Onramping](/banking/onramping#verify-with-microdeposits). - -## module - -An address with permission to execute account calls through the module interface. - -[Modules](/accounts/modules). - -## Multisend - -The feature that transfers one token to multiple recipients in one transaction. - -[Sends](/transactions/sends#multisend). - -## multisig - -An account that uses a configured number of signer approvals for transactions. - -[Thresholds](/accounts/thresholds). - -## native token - -The token that a network uses for transaction fees. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## network - -A blockchain on which an account can operate. - -[Networks and assets](/introduction/networks-and-assets). - -## NFT - -Non-fungible token. A token with an identity separate from other tokens. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## offchain record - -Information stored outside a blockchain. - -[Members](/members). - -## offering - -The PACT contract that holds units for sale and deposited USDC. - -[PACT](/experiments/pact). - -## offramp - -A conversion from tokens to funds in a bank account. - -[Offramping](/banking/offramping). - -## onchain transaction - -A transaction recorded on a blockchain. - -[Transactions](/transactions). - -## onramp - -A conversion from a bank transfer to tokens. - -[Onramping](/banking/onramping). - -## operating account - -A Splits account for direct transactions with team-selected signers and threshold. - -[Accounts](/accounts#operating-accounts). - -## oracle - -A service that supplies external data, such as asset prices, to a contract. - -[Earn](/accounts/earn#risks). - -## output token - -A unit of text used to measure AI input or output. - -[Agents & API](/introduction/agents#tune-output-for-agents). - -## Owner role - -The team role with account and team administration permissions. - -[Roles](/teams/roles). - -## PACT - -Purchase Agreement for Community Tokens. A tool for capital collection and public allocation records. - -[PACT](/experiments/pact). - -## passkey - -A key pair for authentication or signatures, with its private key held by a device or password manager. - -[Signing keys](/members/keys#passkeys). - -## password manager - -Software that stores credentials, including supported passkeys. - -[Signing keys](/members/keys#password-managers). - -## pay-by-bank - -An invoice payment method that uses a bank transfer. - -[Invoicing](/invoicing#pay-by-bank). - -## payee - -The person or business that receives a payment. - -[Compliance](/contacts/compliance). - -## payer - -The person or business that makes a payment. - -[Paying invoices](/invoicing/paying). - -## payroll - -Payments to employees or contractors. - -[Schedules](/transactions/schedules). - -## performance fee - -A charge calculated from investment yield. - -[Earn](/accounts/earn#fees). - -## personal team - -A Splits team for an individual. - -[Personal usage](/introduction/personal-usage). - -## position - -An account's balance or claim in a protocol. - -[Integrations](/integrations#positions). - -## principal - -The deposited amount before interest or gains. - -[Earn](/accounts/earn). - -## private key - -Secret key material that produces cryptographic signatures. - -[Signing keys](/members/keys). - -## private transfer - -A recipient payment through NEAR Confidential Intents. - -[Sends](/transactions/sends#private-transfers). - -## proposal - -A requested transaction awaiting the account's required approvals. - -[Transactions](/transactions). - -## protocol - -A defined set of rules and contracts for an operation. - -[Integrations](/integrations). - -## public key - -Key material that lets others verify a signature without the private key. - -[Signing keys](/members/keys). - -## query - -Request stored data through a software interface. - -[Agents & API](/introduction/agents). - -## quote - -A provider's proposed exchange amounts and terms. - -[Swaps](/transactions/swaps#quotes-and-multisigs). - -## realized gain - -The excess of disposal proceeds over the assigned cost basis. - -[Accounting](/accounting). - -## recovery - -The process that restores account control through recovery signers. - -[Recovery](/teams/recovery). - -## recovery signer - -An EOA in the Root's signer set. - -[Recovery](/teams/recovery#recovery-signers). - -## recurring invoice schedule - -A schedule that creates invoices at weekly or monthly intervals. - -[Recurring invoices](/invoicing/recurring). - -## register - -Add a credential or account record to a system. - -[Agents & API](/introduction/agents). - -## reset - -Replacement of an account's signer state through its onchain owner. - -[Editing](/accounts/editing#reset-signers). - -## REST API - -An API that exposes resources through HTTP requests. - -[Agents & API](/introduction/agents#use-the-api). - -## role - -A set of app permissions assigned to a team membership. - -[Roles](/teams/roles). - -## Root - -The account at the top of a team's ownership chain. - -[Accounts](/accounts#root). - -## schedule - -A stored instruction to create a transaction proposal at an interval. - -[Schedules](/transactions/schedules). - -## schema - -A description of a data structure and its constraints. - -[Agents & API](/introduction/agents). - -## send - -A transfer of tokens from a Splits account to a recipient. - -[Sends](/transactions/sends). - -## send rule - -A restriction on permitted tokens and networks for a contact. - -[Contacts](/contacts#restrictions). - -## SEPA - -Single Euro Payments Area. A system for euro bank transfers. - -[Onramping](/banking/onramping). - -## sign - -Produce a cryptographic signature with a private key. - -[Agents & API](/introduction/agents). - -## signature - -Cryptographic data that proves approval by a private key. - -[Signing keys](/members/keys). - -## signer - -A signing key in a specific account's signer set. - -[Signers](/accounts/signers). - -## signer set - -The public signing keys with approval authority on an account. - -[Signers](/accounts/signers). - -## signing key - -A key that produces signatures for a member or executor. - -[Signing keys](/members/keys). - -## slippage tolerance - -The permitted price change between a swap quote and execution. - -[Swaps](/transactions/swaps#slippage). - -## spam token - -A token classified as unwanted. - -[Spam & tokens](/accounting/spam). - -## Splits Connect - -The browser extension that connects Splits accounts to other apps. - -[Browser extension](/introduction/extension). - -## stablecoin - -A token designed to track a reference currency or asset value. - -[Banking](/banking). - -## sub-account - -An operating or automation account owned by the Treasury. - -[Accounts](/accounts). - -## subname - -An ENS name below another name, such as treasury.splits.eth. - -[ENS](/integrations/ens#subnames). - -## swap - -An exchange of one token or network balance for another. - -[Swaps](/transactions/swaps). - -## synchronize - -Apply matching data to multiple networks or systems. - -[Agents & API](/introduction/agents). - -## tax lot - -An asset acquisition record used to calculate gains and losses. - -[Accounting](/accounting). - -## team - -A group of accounts and records for a company, individual, or project. - -[Teams](/teams). - -## threshold - -The number of signer approvals required for an account transaction. - -[Thresholds](/accounts/thresholds). - -## timelock - -A mandatory delay before a contract action can execute. - -[Earn](/accounts/earn). - -## token - -An asset represented in a blockchain record. - -[Networks and assets](/introduction/networks-and-assets#supported-assets). - -## transaction - -A request to change blockchain state through one or more calls. - -[Transactions](/transactions). - -## Treasury - -The team's main asset account and owner of its sub-accounts. - -[Accounts](/accounts#treasury). - -## URI - -Uniform resource identifier. Text that identifies a resource or connection. - -[WalletConnect](/integrations/walletconnect). - -## USD - -The currency code for the US dollar. - -[Banking](/banking). - -## USDC - -Circle's US dollar stablecoin. - -[Banking](/banking). - -## USDT - -Tether's US dollar stablecoin. - -[Sends](/transactions/sends#private-transfers). - -## vault - -A contract that holds deposits and manages an investment position. - -[Earn](/accounts/earn). - -## vault share - -A token that represents a portion of a vault's assets. - -[Earn](/accounts/earn). - -## vendor payment - -A transfer of funds to a vendor's bank account. - -[Paying vendors](/banking/paying-vendors). - -## vesting - -The scheduled release of rights to tokens or other assets. - -[Integrations](/integrations#positions). - -## wallet - -An external EOA wallet, such as a hardware wallet or MetaMask. - -[Signing keys](/members/keys#eoas). - -## wei - -The smallest unit of ETH. - -[Custom transactions](/transactions/custom). - -## WETH - -Wrapped ether. An ERC-20 representation of ETH. - -[Sends](/transactions/sends#private-transfers). - -## withdrawal - -Funds removed from an account or position. - -[Earn](/accounts/earn). - -## yield - -The return that a deposited asset produces. - -[Earn](/accounts/earn). diff --git a/vocs.config.ts b/vocs.config.ts index 07b6dd7..8ba73d0 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -44,7 +44,6 @@ export default defineConfig({ collapsed: false, items: [ { text: 'Core concepts', link: '/introduction/core-concepts' }, - { text: 'Glossary', link: '/resources/glossary' }, { text: 'Networks & assets', link: '/introduction/networks-and-assets' }, { text: 'Browser extension', link: '/introduction/extension' }, { text: 'Agents & API', link: '/introduction/agents' },