diff --git a/CLAUDE.md b/CLAUDE.md index ef18ea3..b9572cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ Any agent should be able to take a product PR and update these docs without a hu 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: `node scripts/check-ste.mjs ` (blocking ASD-STE100 checks), `node scripts/check-prose.mjs ` (warnings needing judgment), `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 @@ -58,19 +58,20 @@ If a change moves a fact's canonical home, update this table in the same PR. ## Terminology +- **Use the controlled terms in [`STE-TERMS.md`](STE-TERMS.md).** Add a product term there before you introduce it in user-facing docs. Use the listed part of speech and meaning. - **Core concepts are Team, Member, Account** (not "Person" or "User"; those nouns don't name product entities in the docs). - **The product is "the app"** at app.splits.org (teams.splits.org is deprecated). Write "onchain", "offchain", and "crosschain" (no hyphens). No marketing jargon: be explicit and precise about what things are. - **Capitalization: product-named entities are proper nouns; generic concepts are lowercase.** Capitalize roles (Owner, Member), named accounts (Root, Treasury), and feature names (Earn, Automated Earn, Splits Connect, Recovery-the-settings-surface). Lowercase concepts: signer, signing key, passkey, threshold, account, team, member-the-person, module, automation, invoice, memo. The pair this enables: "member" = a person in a team; "Member" = the role. - **"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. +- **Em dashes and semicolons: never, anywhere.** List items and definition lists use a colon separator (`` `command`: description ``). In prose, use a colon, period, comma, or parentheses. The prose linters flag these characters. - **"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). ## Voice & formatting -- **Facts in declarative present tense; procedures in second person** ("you must be an Owner", "go to…"). +- **Facts in declarative present tense; procedures in the imperative form** ("Go to…"). Give one instruction per numbered step unless actions occur at the same time. - **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." @@ -79,6 +80,18 @@ If a change moves a fact's canonical home, update this table in the same PR. - **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. - **Every sentence must be falsifiable.** If a clause tells the reader how to feel about a fact instead of stating the fact ("seamlessly", "so you can…"), cut it or replace it with the mechanism. Test: delete the clause; if only persuasion is lost, it was jargon. +## ASD-STE100 human review + +The deterministic checker enforces sentence length, paragraph length, contractions, and punctuation. `pnpm build` rejects all findings. It cannot decide whether technical prose uses words correctly. For every changed page, also verify: + +1. Use words from the ASD-STE100 dictionary or technical terms from `STE-TERMS.md`. Use each word only with its approved meaning and part of speech. +2. Keep multi-word nouns to three words. If an official technical name is longer, write it in full first and define a short form. +3. Use simple verb forms and active voice. Use passive voice only in descriptive text when the agent is unknown. +4. Use an `-ing` form only as a technical noun or as a modifier in a technical noun. +5. Give each descriptive sentence one subject. Give each paragraph one topic. +6. Start each procedural step with a command. Put only one instruction in a sentence unless actions occur at the same time. +7. Read the rendered page and its `.md` twin. Confirm that headings, lists, links, and UI labels preserve the intended meaning. + ## Links - **First mention links, later mentions stay plain.** The first mention of another page's concept links to its canonical home; repeat mentions on the same page and mentions inside table cells stay plain (except a table's designated link column, e.g. "Learn more"). diff --git a/STE-TERMS.md b/STE-TERMS.md new file mode 100644 index 0000000..cde0640 --- /dev/null +++ b/STE-TERMS.md @@ -0,0 +1,60 @@ +# Splits controlled terminology + +This list controls Splits-specific words that appear in user-facing documentation. Use each term only with the part of speech and meaning shown here. The list supplements the ASD-STE100 Issue 9 dictionary. It does not replace that dictionary. + +An unapproved word can be a technical noun, part of a technical noun, or an eligible technical verb. Do not use a product term as another part of speech. Add a term here before you introduce it in the docs. + +## Product terms + +| Term | Part of speech | Meaning and usage | +| --- | --- | --- | +| account | Technical noun | A Splits smart account that holds assets. Do not call it a wallet. | +| automation account | Technical noun | An account with a policy that processes deposits. Use the full term at first use. | +| EOA | Technical noun | An externally owned Ethereum account. Define the abbreviation at first use on each page. | +| external account | Technical noun | An address outside Splits that a team watches. It has no signing authority in Splits. | +| Member | Technical noun | The product role named Member. Capitalize it. | +| member | Technical noun | A person who belongs to a team. Lowercase it. | +| module | Technical noun | An onchain program that can execute approved calls from an account. | +| operating account | Technical noun | A general-purpose account that a team uses for transactions. | +| Owner | Technical noun | The product role named Owner. Capitalize it. | +| passkey | Technical noun | A type of signing key that is stored in a password manager. | +| proposal | Technical noun | A transaction request that waits for the required approvals. | +| recovery signer | Technical noun | An EOA that controls the Root at the recovery threshold. | +| Root | Technical noun | The self-owned account that recovery signers control. Write “the Root” in prose. | +| signer | Technical noun | A signing key that is in one account's signer set. Do not use this term for all signing keys. | +| signer set | Technical noun | The signing keys that can approve transactions for one account. | +| signing key | Technical noun | A passkey or EOA that belongs to a member. Use this term instead of “key” when precision is necessary. | +| smart account | Technical noun | An account implemented by a smart contract. Use this term instead of “smart wallet.” | +| team | Technical noun | The product unit that contains members, accounts, and one set of books. | +| threshold | Technical noun | The number of signer approvals that an account requires. | +| transaction | Technical noun | An onchain action from an account. | +| Treasury | Technical noun | The primary team account. Write “the Treasury” in prose. | + +## Blockchain modifiers and verbs + +| Term | Part of speech | Meaning and usage | +| --- | --- | --- | +| onchain | Technical-noun modifier | Use only in a technical noun such as “onchain state.” Otherwise write “on the blockchain.” | +| offchain | Technical-noun modifier | Use only in a technical noun such as “offchain metadata.” Otherwise write “outside the blockchain.” | +| crosschain | Technical-noun modifier | Use only in a technical noun such as “crosschain transaction.” Otherwise write “between networks.” | +| bridge | Technical verb | Transfer an asset between blockchain networks. Use only for this action. | +| offramp | Technical verb | Convert a crypto asset to fiat currency and send it to a bank account. | +| onramp | Technical verb | Convert fiat currency from a bank account to a crypto asset. | +| swap | Technical verb | Exchange one token for another token. | + +## Do not use + +| Avoid | Use | +| --- | --- | +| bootstrap | create, set up | +| finicky | does not operate correctly, or state the observed behavior | +| flip side | result, limitation | +| flops | fails | +| folks | people, members, team members | +| misbehaves | fails, disconnects, or state the observed behavior | +| smart wallet | smart account | +| workspace | team (until the product rename ships) | + +## Human review + +The deterministic checker cannot validate approved meanings, active voice, noun clusters, or technical-term eligibility. For each changed page, complete the human STE review in `CLAUDE.md` after the checker passes. diff --git a/package.json b/package.json index 14083c4..ab019b2 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vocs dev", - "build": "vocs build", + "build": "node scripts/check-ste.mjs && vocs build", "preview": "vocs preview", "start": "node dist/serve-node.js" }, diff --git a/scripts/check-ste.mjs b/scripts/check-ste.mjs new file mode 100644 index 0000000..c4846f8 --- /dev/null +++ b/scripts/check-ste.mjs @@ -0,0 +1,149 @@ +#!/usr/bin/env node +// Deterministic ASD-STE100 checks for user-facing MDX prose. +// +// Usage: +// node scripts/check-ste.mjs +// node scripts/check-ste.mjs --changed +// +// This checker intentionally leaves vocabulary, noun clusters, voice, meaning, +// and one-action procedure review to a human. See STE-TERMS.md and CLAUDE.md. + +import { execFileSync } from 'node:child_process' +import { readFileSync, readdirSync, statSync } from 'node:fs' +import { join } from 'node:path' + +const CONTRACTION = /\b(?:aren['’]t|can['’]t|couldn['’]t|didn['’]t|doesn['’]t|don['’]t|hasn['’]t|haven['’]t|isn['’]t|it['’]s|shouldn['’]t|that['’]s|there['’]s|they['’](?:re|ve|ll)|wasn['’]t|we['’](?:re|ve|ll)|weren['’]t|won['’]t|wouldn['’]t|you['’](?:re|ve|ll))\b/gi +const sentenceSegmenter = new Intl.Segmenter('en', { granularity: 'sentence' }) + +function walk(path, files = []) { + if (!statSync(path).isDirectory()) return path.endsWith('.mdx') ? [...files, path] : files + for (const entry of readdirSync(path)) { + const item = join(path, entry) + if (statSync(item).isDirectory()) walk(item, files) + else if (item.endsWith('.mdx')) files.push(item) + } + return files +} + +function changedFiles(base) { + const output = execFileSync('git', ['diff', '--name-only', '--diff-filter=ACMR', `${base}...HEAD`], { encoding: 'utf8' }) + return output.split('\n').filter((path) => path.startsWith('src/pages/') && path.endsWith('.mdx')) +} + +function cleanInline(value) { + return value + .replace(/!\[[^\]]*\]\([^)]*\)/g, ' ') + .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') + .replace(/`[^`]+`/g, ' IDENTIFIER ') + .replace(/(?]+>/g, ' ') + .replace(/[>*_~]/g, '') + .replace(/\s+/g, ' ') + .trim() +} + +function extractBlocks(path) { + const lines = readFileSync(path, 'utf8').split('\n') + const blocks = [] + let paragraph = [] + let frontmatter = false + let code = false + + function flush() { + if (paragraph.length) { + blocks.push({ type: 'descriptive', paragraph: true, line: paragraph[0].line, text: cleanInline(paragraph.map(({ text }) => text).join(' ')) }) + } + paragraph = [] + } + + lines.forEach((raw, index) => { + const line = index + 1 + const trimmed = raw.trim() + if (line === 1 && trimmed === '---') { frontmatter = true; return } + if (frontmatter) { if (trimmed === '---') frontmatter = false; return } + if (/^\s*```/.test(raw)) { flush(); code = !code; return } + if (code || /^\s*(?:import|export)\b/.test(raw)) return + if (!trimmed || /^\s*#{1,6}\s/.test(raw) || /^\s*:::\w*/.test(raw) || /^\s*\|/.test(raw) || /^\s*<\/?[A-Z]/.test(raw)) { + flush() + return + } + + const list = raw.match(/^\s*(?:[-+*]|\d+\.)\s+(.*)$/) + if (list) { + flush() + blocks.push({ + type: /^\s*\d+\./.test(raw) ? 'procedural' : 'descriptive', + vertical: true, + line, + text: cleanInline(list[1]), + }) + return + } + paragraph.push({ line, text: trimmed }) + }) + flush() + return blocks.filter(({ text }) => text) +} + +function sentences(text, splitColon) { + // Under STE Rule 8.4, a colon in a vertical list ends a sentence for word count. + return [...sentenceSegmenter.segment(splitColon ? text.replaceAll(':', '.') : text)] + .map(({ segment }) => segment.trim()) + .filter(Boolean) +} + +function wordCount(text) { + return (text.match(/\b[\p{L}\p{N}]+(?:[-'][\p{L}\p{N}]+)*\b/gu) || []).length +} + +function inspect(path) { + const findings = [] + for (const block of extractBlocks(path)) { + const parts = sentences(block.text, block.vertical) + if (block.paragraph && parts.length > 6) { + findings.push({ line: block.line, rule: '6.6', message: `paragraph has ${parts.length} sentences; maximum is 6`, source: block.text }) + } + for (const sentence of parts) { + const limit = block.type === 'procedural' ? 20 : 25 + const count = wordCount(sentence) + if (count > limit) { + const rule = block.type === 'procedural' ? '5.1' : '6.3' + findings.push({ line: block.line, rule, message: `sentence has ${count} words; maximum is ${limit}`, source: sentence }) + } + if (sentence.includes(';')) findings.push({ line: block.line, rule: '8.1', message: 'semicolon is not permitted', source: sentence }) + for (const match of sentence.matchAll(CONTRACTION)) { + findings.push({ line: block.line, rule: '4.2', message: `contraction is not permitted: ${match[0]}`, source: sentence }) + } + } + } + return findings +} + +const args = process.argv.slice(2) +let files +if (args[0] === '--changed') { + if (!args[1]) throw new Error('--changed requires a base ref') + files = changedFiles(args[1]) +} else { + const targets = args.length ? args : ['src/pages'] + files = targets.flatMap((target) => walk(target)) +} + +const findings = [] +for (const path of [...new Set(files)].sort()) { + for (const finding of inspect(path)) { + findings.push({ ...finding, path }) + } +} + +for (const finding of findings) { + console.error(`${finding.path}:${finding.line}: ASD-STE100 ${finding.rule}: ${finding.message}`) +} + +if (findings.length) { + console.error(`\n${findings.length} deterministic ASD-STE100 finding(s).`) + process.exit(1) +} +console.log(`ASD-STE100 deterministic checks passed for ${new Set(files).size} file(s).`) diff --git a/src/pages/accounting/index.mdx b/src/pages/accounting/index.mdx index 2a95e5e..a7e4106 100644 --- a/src/pages/accounting/index.mdx +++ b/src/pages/accounting/index.mdx @@ -5,7 +5,7 @@ description: View, filter, and export all of a team's transactions on the Accoun # Accounting [View, filter, and export all activity across a team's accounts] -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. +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. ## Filtering @@ -18,8 +18,8 @@ The main *Export as CSV* action downloads a transaction-and-tax-lot timeline mat - **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). +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). ## 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. +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. diff --git a/src/pages/accounts/earn.mdx b/src/pages/accounts/earn.mdx index 5bfac78..a5ce5b6 100644 --- a/src/pages/accounts/earn.mdx +++ b/src/pages/accounts/earn.mdx @@ -5,29 +5,29 @@ description: "Earn interest on idle capital in a team's accounts: how deposits a # Earn [Earn interest on idle capital in a team's accounts] -Earn puts an account's idle capital to work in a yield source. Positions have no lockup, can be withdrawn at any time with no fee or queue, compound automatically (there is nothing to claim), and can be spent directly when sending funds. +Earn puts an account's idle capital to work in a yield source. Positions have no lockup and can be withdrawn at any time with no fee or queue. They compound automatically, so there is nothing to claim. You can also spend them directly when sending funds. Deposits are manual (per account, for a chosen amount) or automatic via [Automated Earn](#automated-earn). ## The current offering -Today Earn supports USDC on Base, deposited into [Aave](https://aave.com/), the largest lending protocol on Ethereum. More assets and yield sources are planned; email support with requests. +Today Earn supports USDC on Base, deposited into [Aave](https://aave.com/), the largest lending protocol on Ethereum. More assets and yield sources are planned. Email support with requests. -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. +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. ## How it works Deposits flow into the [Aave USDC Base Vault](https://app.aave.com/reserve-overview/?underlyingAsset=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913&marketName=proto_base_v3), an ERC-4626 vault that supplies the Aave v3 Pool on Base. The account holds **vault shares** representing its portion of the pooled USDC: - **Auto-compounding yield**: interest earned in Aave is harvested and re-supplied, so the position grows automatically. -- **One token to track**: each ERC-4626 share maps to a clear amount of USDC; no rebasing tokens or wrappers. +- **One token to track**: each ERC-4626 share maps to a clear amount of USDC. No rebasing tokens or wrappers. - **Lower gas**: deposits, harvests, and withdrawals are batched. Everything happens natively on Base: no bridges, no wrapped assets. For a deeper look, see the [Aave Vault docs](https://aave.com/docs/developers/aave-vaults). ## 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 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. 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/0x204DDc725f37786072a4D4102CBF3945F3597D13#code)) to the account, authorizing it to move deposits into the vault. @@ -36,7 +36,7 @@ Enabling it adds the [Auto Earn Module](/accounts/modules) (an open-source [cont The current offering is lending, and lending carries risk: - **Smart-contract risk**: a critical bug in Aave or the vault could freeze or drain funds. -- **Oracle & liquidation risk**: Aave relies on Chainlink oracles; if USDC de-pegs and the oracle lags, bad debt can accrue. +- **Oracle & liquidation risk**: Aave relies on Chainlink oracles. If USDC de-pegs and the oracle lags, bad debt can accrue. - **Interest-rate variability**: Aave's variable rate can fall to nearly zero if borrowing demand drops. - **Governance risk**: Aave governance can change parameters affecting positions without notice. - **Liquidity exhaustion**: if utilization hits 100%, withdrawals pause until borrowers repay. Soaring borrow rates strongly incentivize new deposits that restore liquidity, so exits are rarely delayed for long, but short freezes are possible. diff --git a/src/pages/accounts/editing.mdx b/src/pages/accounts/editing.mdx index fca2f70..89910da 100644 --- a/src/pages/accounts/editing.mdx +++ b/src/pages/accounts/editing.mdx @@ -5,47 +5,58 @@ description: "How an account changes after creation: renaming, changing signers # Editing [Change an account's name, signers, or threshold, or reset lost signers] -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: +You can change all account properties except the address. The name is offchain metadata. The signer set and threshold are onchain state. Two methods can change them: -- [**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. +- [**Update**](#changing-signers-and-thresholds): The account's current signers approve the change at the current threshold. Use this method for standard changes. +- [**Reset**](#resetting-signers): The account's **owner** replaces the signer set without current signer approval. Use this method after the team loses signers. -Editing account settings requires the **Owner** role in all cases. +You must have the **Owner** role to edit account settings. ## Renaming -An account's name is offchain and changes with no signatures: edit it from the account's settings. +An account name is offchain metadata. A name change requires no signatures. Edit the name in the account settings. ## Changing signers and thresholds -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. +The signer set and threshold are one onchain state. One transaction can add signers, remove signers, change the threshold, or combine these changes. A signer replacement with no threshold change is also one transaction. -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. +1. Open the account settings. +2. Edit the signers or threshold. +3. Select any applicable [team member](/members) who has a [signing key](/members/keys). +4. Save the change to create an *Update signers state* transaction. +5. Approve the transaction with the current signers at the current threshold. -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. +Use this procedure for the Treasury and operating accounts. + +One signing session covers every network. The signers sign a Merkle root that contains one update transaction for each active network. Each network executes its transaction with a proof against that root. The app never signs for one network separately. Thus, signer state cannot differ between networks. + +This procedure is unavailable when the account does not have enough active signers to meet its threshold. The account settings show this condition. Use a [reset](#resetting-signers) instead. ## Resetting signers -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: +A reset replaces an account's signers and threshold **without approval from the current signers**. Accounts form an [ownership chain](/accounts). An account owner can directly replace the account's signer set. The account type determines who approves the reset: | Account being reset | Its owner | Who signs the reset | | --- | --- | --- | | 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 | -The Root itself can't be reset: it has no owner above it and is controlled only by the recovery signers. +You cannot reset the Root. It has no owner above it, and only the recovery signers control it. -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. +1. Open the account settings. Alternatively, select the account in [Settings > Recovery](https://app.splits.org/settings/team/recovery/). +2. Select *Reset signers*. +3. Select the new signers and threshold. +4. Save the reset. +5. For a sub-account, approve the reset with the Treasury signers. +6. For the Treasury, connect the recovery wallets and approve the reset. -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 loses every passkey, reset the Treasury with the recovery wallets. Then, use the restored Treasury to reset the sub-accounts. See [Recovery](/teams/recovery) for the complete procedure. ## Programmatic access Via 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 update-signers
--threshold N ...`: propose signer or threshold changes. Approval occurs in the web UI (**Owner** scope) - `splits accounts rename
`: rename an account (**Owner** scope) Resets are web-only today. diff --git a/src/pages/accounts/index.mdx b/src/pages/accounts/index.mdx index 7976d9e..82b87e3 100644 --- a/src/pages/accounts/index.mdx +++ b/src/pages/accounts/index.mdx @@ -5,7 +5,7 @@ description: "Accounts in Splits: the four account types, the onchain ownership # Accounts [Where assets live: account types, ownership, and thresholds] -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 stores assets for a team. Each account has the same address on every active network. A [threshold](/accounts/thresholds) specifies the required approvals. [Signers](/accounts/signers) control the account, and optional [modules](/accounts/modules) can also act. | Type | Controlled by | Owned by (onchain) | | --- | --- | --- | @@ -14,37 +14,43 @@ An account is where assets are stored within a team. Every account exists at the | **Operating** | Signers the team configures | Treasury | | **Automation** | Owners' passkeys + a Splits server key | Treasury | -Accounts form an ownership chain (Root → Treasury → operating and automation accounts) in which each account is the onchain owner of those below it. +Accounts form an ownership chain: Root → Treasury → operating and automation accounts. Each account is the onchain owner of the accounts below it. -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. +Ownership provides powers that signers do not have. Signers can change their account's signer set and threshold. **Only the owner can upgrade the contract implementation or transfer ownership.** The owner can also execute actions directly from the account. This power enables a [signer reset](/accounts/editing#resetting-signers) and [recovery](/teams/recovery). + +A signer on a low-threshold account can reach only that account's balance. The signer cannot change its ownership or code. The Root has no external owner and 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](/teams) creates the Root from the team's recovery signers and recovery threshold. The Root does not appear in the app. You cannot view it or transact from it. Recovery signers use the Root to regain control of all accounts below it after the team loses its passkeys. + +The recovery configuration determines each account address. Thus, changing recovery signers changes the team's account addresses ([more](/teams/recovery#changing-recovery-signers)). ## 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 automatically creates the Treasury to hold most of the team's assets. The owners' passkeys are its initial signers. The team selects the initial threshold during setup. Owners can [change the signers and threshold later](/accounts/editing). The team cannot archive the Treasury. -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) keeps high-value assets in the Treasury and day-to-day money in operating accounts. Thus, one compromised account does not endanger the other accounts. ## 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). +Operating accounts are general-purpose accounts that a team uses for transactions. The team selects their signers and thresholds and can change them later. See [Editing](/accounts/editing). Use operating accounts in the app or in third-party apps. Connect third-party apps with the [browser extension](/introduction/extension) or [WalletConnect](/integrations/walletconnect). For programmatic access, use the [CLI](/introduction/agents). -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: +Most active teams use multiple operating accounts. Each account can be a labeled sub-ledger for a specific activity. Common patterns include: -- A dedicated account per project, revenue source, campaign, or partner, isolated for accounting and archived when done +- A dedicated account for each project, revenue source, campaign, or partner - 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 +- A low-threshold account for frequent transactions, funded from the higher-threshold Treasury For more patterns, see [Personal usage](/introduction/personal-usage) and [suggested thresholds by team size](/accounts/thresholds#choosing-a-threshold). ## 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, each token worth at least $5 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. +An automation account is a deposit address with a policy that processes all deposits. Every 10 minutes, the policy processes each token worth at least $5. The policy can swap, bridge, forward, or offramp the token. The selected destination token and network determine the action. A [connected bank account](/banking/offramping) receives fiat currency from an offramp. Select *Trigger* to run the policy immediately. -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. +Runs require no approval. The signers are the Treasury's passkeys and a Splits server key, with a 1-of-n threshold. Splits charges no fees. Automations run on every active network. + +Typical policies swap all deposits to USDC, reserve a share for taxes, or divide income among multiple destinations. Select *Edit rules* on the account page to change the policy. An onchain signature approves the change. ## External accounts @@ -52,9 +58,11 @@ Runs need no approval (the signers are the Treasury's passkeys plus a Splits ser This feature is in beta. Email support to enable it for your team. ::: -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. +An external account is an address outside Splits that the team watches. Its name and token balances appear with the team's accounts. The watch function is read-only. **An external account grants no signing authority or custody.** Splits cannot move its funds. Use an external account to show cold storage, a Safe, or a partner address with the applicable books. + +Use an external account to add an existing address. **You cannot import an existing smart account, such as a Safe, into Splits.** The Splits account contract provides passkey signing, identical addresses, synchronized signers, and the [ownership chain](#root). This ownership chain enables [recovery](/teams/recovery). External contracts do not have these Splits properties. -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. +Teams with an existing Safe typically keep it with Splits. They use Splits for day-to-day operations and watch the Safe as an external account. ## Programmatic access diff --git a/src/pages/accounts/modules.mdx b/src/pages/accounts/modules.mdx index b00be5c..d9eb529 100644 --- a/src/pages/accounts/modules.mdx +++ b/src/pages/accounts/modules.mdx @@ -11,11 +11,13 @@ A module has full access to the account. Only enable addresses you trust complet ## 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. +**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. This includes calls to permissioned contracts that check `msg.sender`, such as calls that withdraw LP fees. -**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. +Everything the account receives stays managed in Splits. Secure the Executor key based on the funds it can reach. This includes funds in the account and in any contract where the account has privileges. -**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. +**Automating token transfers only: do not 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. + +**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. **Deposit-address automation** (split/swap/forward incoming tokens): use an [automation account](/accounts#automation-accounts), not a module. diff --git a/src/pages/accounts/signers.mdx b/src/pages/accounts/signers.mdx index d9e5302..3043cf5 100644 --- a/src/pages/accounts/signers.mdx +++ b/src/pages/accounts/signers.mdx @@ -5,9 +5,9 @@ description: "Signers are the keys in an account's signer set that approve its t # Signers [The keys in an account's signer set that approve its transactions] -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) (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. -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. +Managing signers is an onchain edit to one account's signer set (see [Editing](/accounts/editing)). It is separate from managing a member's [signing keys](/members/keys) in personal settings. Adding or removing a key does not change an account's signer set. Removing a signer from an account does not delete the member's key. ## Signers vs membership @@ -35,5 +35,5 @@ The [Root account](/accounts#root)'s signers are the team's recovery signers: EO Via 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 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) diff --git a/src/pages/accounts/thresholds.mdx b/src/pages/accounts/thresholds.mdx index daed09a..6ed3ead 100644 --- a/src/pages/accounts/thresholds.mdx +++ b/src/pages/accounts/thresholds.mdx @@ -5,7 +5,7 @@ description: "Every account requires M-of-N signer approvals per transaction: wh # Thresholds [The number of signer approvals an account requires per 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). +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). ## Choosing a threshold diff --git a/src/pages/banking/index.mdx b/src/pages/banking/index.mdx index 92d3a4b..71e4319 100644 --- a/src/pages/banking/index.mdx +++ b/src/pages/banking/index.mdx @@ -15,14 +15,14 @@ On/offramps cost 0.25%, deducted from the transaction amount. Setting up banking ## Getting verified -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.** +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 will 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. +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. ## 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)). -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. +If you live elsewhere, you can still use banking if your entity is legally registered in the US. You must also have a US- or EU-based bank account. Bank accounts are added by routing and account number (US) or IBAN (EU). Thus, accounts at fintechs like [Wise](https://wise.com) generally work like other bank accounts. 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. diff --git a/src/pages/banking/offramping.mdx b/src/pages/banking/offramping.mdx index 54cdbfc..515a775 100644 --- a/src/pages/banking/offramping.mdx +++ b/src/pages/banking/offramping.mdx @@ -5,15 +5,15 @@ description: "Move crypto out to the team's bank accounts: connecting banks via # Offramping [Move crypto out to the team's bank accounts] -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. +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. 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*. -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). +Offramps carry the 0.25% [banking fee](/banking), deducted from the amount. To send fiat to a bank account that is not yours, see [Paying vendors](/banking/paying-vendors). ## 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. +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. The corresponding cutoffs are 9:15 am, 1:30 pm, and 3:30 pm. These times include a 15-minute buffer relative to Bridge's documentation. This buffer has been necessary in our experience. Offramps sent after 3:30 pm ET arrive the following business day. For IBAN banks supporting SEPA Instant, settlement occurs in under 30 seconds. @@ -21,4 +21,4 @@ For IBAN banks supporting SEPA Instant, settlement occurs in under 30 seconds. 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. -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. +Splits treats these as ordinary sends: the 0.25% banking fee and the settlement times above do not apply. Your provider's own fees and timing do. diff --git a/src/pages/banking/paying-vendors.mdx b/src/pages/banking/paying-vendors.mdx index f082fa2..9b5c2c6 100644 --- a/src/pages/banking/paying-vendors.mdx +++ b/src/pages/banking/paying-vendors.mdx @@ -5,18 +5,18 @@ description: "Pay third parties in fiat from Splits accounts: add a vendor's ban # Paying vendors [Pay third parties in fiat from Splits accounts] -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 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. Prerequisites: [banking](/banking) set up with a verified entity. Adding and managing vendor accounts requires the [Owner](/teams/roles) role. ## Adding a vendor -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. +In [Settings > Banks](https://app.splits.org/settings/team/banks/), add the vendor's bank account as an **external account**. For US accounts, enter the routing and account numbers. For EU accounts, enter the IBAN. Also enter the account owner's name and address. External accounts are listed separately from the team's own (internal) bank accounts. ## Paying -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). +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). -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. +Delivery uses Same Day ACH (US) or SEPA (EU). The 0.25% fee and [settlement timing](/banking/offramping#settlement) are the same as for other offramps. The fee is deducted from the amount sent. To collect tax forms from vendors and contractors you pay, see [Compliance](/contacts/compliance). diff --git a/src/pages/contacts/compliance.mdx b/src/pages/contacts/compliance.mdx index 988ad2b..3cee670 100644 --- a/src/pages/contacts/compliance.mdx +++ b/src/pages/contacts/compliance.mdx @@ -14,7 +14,7 @@ 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. +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. 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. diff --git a/src/pages/contacts/index.mdx b/src/pages/contacts/index.mdx index bae34dc..9e2099b 100644 --- a/src/pages/contacts/index.mdx +++ b/src/pages/contacts/index.mdx @@ -9,26 +9,26 @@ A contact is a team-wide name for an external address. Once saved, the name stan ## Creating -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. +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 restrictions: contacts must be EVM addresses, and the team's own Splits accounts can't be added as contacts (they're already named). +Two restrictions: contacts must be EVM addresses, and the team's own Splits accounts cannot be added as contacts (they are already named). ## 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. +When Splits displays an address, it uses the first name it finds. It checks the team's account and bank-account names first, then contact labels. Next, it checks live ENS and Farcaster lookups, then [payee](/contacts/compliance) details, and finally the shortened address. Saving a contact pins a name that the whole team sees. The display does not depend on what an address resolves to. ## Other named recipients Two recipient types are stored separately from contacts but appear alongside them in recipient search: -- **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**: 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). ## Programmatic access Via 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 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) Creating and editing contacts is web-only today. diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 8a66dad..e77c76d 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -3,7 +3,7 @@ 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. --- -# Introduction [What Splits is, who it's for, and how to start] +# Introduction [What Splits is, who it is for, and how to start] **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). @@ -13,7 +13,7 @@ These docs cover the Splits app. Docs for the Splits protocol (the splitter cont Key properties: -- **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. +- **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). @@ -21,7 +21,7 @@ Key properties: - **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`). - **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`. -## Who it's for +## Who it is for - **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. diff --git a/src/pages/integrations/bankr.mdx b/src/pages/integrations/bankr.mdx index 599592c..5a757fb 100644 --- a/src/pages/integrations/bankr.mdx +++ b/src/pages/integrations/bankr.mdx @@ -5,9 +5,9 @@ description: "Let a Bankr agent operate your Splits treasury through the CLI: si # Bankr [Let a Bankr agent operate your treasury through the CLI] -[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 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. -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. +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 setup, commands, and safety rules. Install it and give the agent an API key. The agent then completes the remaining steps. ## Access paths @@ -21,13 +21,13 @@ The agent gets execution power on an account one of two ways: | Reach | Only the accounts a human added it to | The account's full balance | | Revoking | Remove the signer | Disable the module | -**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. +**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. -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.** +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 are willing to expose. **Never enable a module on the Treasury.** ## 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. +2. The agent authenticates (`splits auth login`). It generates and registers its own signing EOA (`splits auth create-key --register`). It then creates a new account with itself and a human passkey as signers. Alternatively, it proposes adding itself to an existing account. A human approves this proposal in the app. 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. diff --git a/src/pages/integrations/clanker.mdx b/src/pages/integrations/clanker.mdx index 8fda876..e5b755e 100644 --- a/src/pages/integrations/clanker.mdx +++ b/src/pages/integrations/clanker.mdx @@ -9,9 +9,9 @@ description: "Clanker LP rewards paying a Splits account appear as positions on Which rewards index automatically 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 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. -Add a token from the plus button next to the *Positions* header; it's saved once for the whole team. +Add a token from the plus button next to the *Positions* header. It is saved once for the whole team. 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). diff --git a/src/pages/integrations/ens.mdx b/src/pages/integrations/ens.mdx index 2e4f734..ed156d4 100644 --- a/src/pages/integrations/ens.mdx +++ b/src/pages/integrations/ens.mdx @@ -7,7 +7,7 @@ description: "Use an ENS name with a Splits account: register a new name, transf 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). -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. +Prerequisites: Ethereum Mainnet enabled in [Settings > Networks](https://app.splits.org/settings/team/networks/), and enough mainnet ETH in the account if you are registering a name. ## Registering a new name @@ -17,7 +17,7 @@ Prerequisites: Ethereum Mainnet enabled in [Settings > Networks](https://app.spl ## Transferring an existing 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. +If the name is currently in an EOA, update its profile and ETH address record *before* the transfer. Updating it from Splits afterward requires more transactions, especially at thresholds above 1. 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). diff --git a/src/pages/integrations/farcaster.mdx b/src/pages/integrations/farcaster.mdx index 00ef90c..131de53 100644 --- a/src/pages/integrations/farcaster.mdx +++ b/src/pages/integrations/farcaster.mdx @@ -5,25 +5,25 @@ description: "Using a Splits account with Farcaster: verifying it on your profil # Farcaster [Verify, secure, and manage a Farcaster account with a Splits account] -[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 built on Ethereum. A Splits account can hold a Farcaster account's onchain roles. These roles include a verified profile address, the account recovery address, and the payer for protocol storage. -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 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. ## 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: +Farcaster verifies a contract account by checking an [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) signature **on Ethereum mainnet**, and does not 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: 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. +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. 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. ## 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 account is controlled by two addresses. A **custody address** owns the account and authorizes apps. A **recovery address** can move the account to a new custody address. The Farcaster app manages both by default. You can point the recovery address at a Splits account in *Settings > Advanced > Change recovery address*. This puts your social account behind the same signers and [threshold](/accounts/thresholds) as your funds. 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. ## 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 yearly rent for the space that an account's messages use. To pay from a Splits account, create a custom transaction. Call `rent(fid, units)` on the [StorageRegistry](https://docs.farcaster.xyz/reference/contracts/reference/storage-registry) (`0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D` on Optimism). Set *Amount to pay* to the unit price in ETH (`unitPrice()` on the contract). The contract refunds excess ETH. diff --git a/src/pages/integrations/hedgey.mdx b/src/pages/integrations/hedgey.mdx index 79c356e..0832817 100644 --- a/src/pages/integrations/hedgey.mdx +++ b/src/pages/integrations/hedgey.mdx @@ -5,6 +5,6 @@ description: Hedgey vesting and lockup streams paying a Splits account appear as # Hedgey [View and claim Hedgey vesting and lockup streams] -[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) issues onchain token vesting and lockup plans. Plans that pay a Splits account appear as [positions](/integrations#positions) on the account page. Each position shows the claimable amount. If the plan has a cliff, the position also shows its unlock date. -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)). +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)). diff --git a/src/pages/integrations/index.mdx b/src/pages/integrations/index.mdx index 1eec093..4985209 100644 --- a/src/pages/integrations/index.mdx +++ b/src/pages/integrations/index.mdx @@ -7,7 +7,7 @@ description: How Splits connects to other apps, protocols, and platforms, includ Splits accounts plug into the rest of the ecosystem through two generic paths: -- **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. +- **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). Some integrations have behavior of their own: @@ -22,10 +22,10 @@ Some integrations have behavior of their own: | [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 | +| [WalletConnect](/integrations/walletconnect) | Connect accounts to apps when the extension cannot run | ## 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)). +Claimable balances in external protocols appear under **Positions** on the account page, next to its token balances. Positions include vesting and lockup streams from [Hedgey](/integrations/hedgey) and [Sablier](/integrations/sablier). They also include LP rewards from [Uniswap](/integrations/uniswap) and [Clanker](/integrations/clanker). -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. +Claiming a position is a normal [transaction](/transactions), approved at the account's [threshold](/accounts/thresholds). If a protocol charges a claim fee, the claim dialog shows it. The account must hold enough of the fee token to claim. Email support to request another protocol. diff --git a/src/pages/integrations/rain.mdx b/src/pages/integrations/rain.mdx index 57c6a39..f4ad80b 100644 --- a/src/pages/integrations/rain.mdx +++ b/src/pages/integrations/rain.mdx @@ -1,19 +1,19 @@ --- 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." +description: "Fund and track Rain Spend corporate cards from Splits: stablecoin collateral deposits, the Cards page, and what Splits does and does not control." --- # Rain [Corporate cards collateralized by stablecoins from your accounts] -[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 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. -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' 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. ## 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. +3. In Rain's wallet settings, link the Splits account via [WalletConnect](/integrations/walletconnect) and sign the verification message. Rain does not 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*. ## Funding diff --git a/src/pages/integrations/sablier.mdx b/src/pages/integrations/sablier.mdx index 972cee5..b14e856 100644 --- a/src/pages/integrations/sablier.mdx +++ b/src/pages/integrations/sablier.mdx @@ -5,6 +5,6 @@ description: Sablier v2 and v3 vesting streams paying a Splits account appear as # Sablier [View and claim Sablier vesting streams] -[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) streams token vesting onchain. Sablier v2 and v3 streams that pay a Splits account appear as [positions](/integrations#positions) on the account page. Each position shows the claimable amount. If the stream has a cliff, the position also shows its unlock date. -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. +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. diff --git a/src/pages/integrations/uniswap.mdx b/src/pages/integrations/uniswap.mdx index 9a89c64..58d555c 100644 --- a/src/pages/integrations/uniswap.mdx +++ b/src/pages/integrations/uniswap.mdx @@ -5,6 +5,6 @@ description: LP positions opened with a Splits account on Uniswap v3 and v4 appe # Uniswap [View and claim LP fees from Uniswap positions] -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. +Liquidity positions opened with a Splits account on [Uniswap](https://uniswap.org) v3 and v4 appear automatically on the account page. They appear under [Positions](/integrations#positions) and show the fees available to claim. Claiming collects all of a position's accrued fees (one or two tokens) into the account in a single transaction. diff --git a/src/pages/integrations/walletconnect.mdx b/src/pages/integrations/walletconnect.mdx index f38fee5..6974ef2 100644 --- a/src/pages/integrations/walletconnect.mdx +++ b/src/pages/integrations/walletconnect.mdx @@ -1,14 +1,14 @@ --- 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. +description: Connect a Splits account to third-party apps over WalletConnect when the browser extension is not an option, and sign the app's requests in Splits. --- # WalletConnect [The backstop for connecting accounts to third-party apps] -[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 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 cannot run: - 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 that do not 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 ## Connecting diff --git a/src/pages/introduction/agents.mdx b/src/pages/introduction/agents.mdx index c3cf1e7..24acbdb 100644 --- a/src/pages/introduction/agents.mdx +++ b/src/pages/introduction/agents.mdx @@ -13,7 +13,7 @@ The Splits API and CLI let applications, scripts, and agents operate Splits prog npx @splits/splits-cli@latest --llms # machine-readable manifest of every command ``` -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. +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 does not: scopes, the proposal model, headless signing, and output tuning. ## Get set up @@ -22,10 +22,10 @@ Every command answers `--help` and `--schema`, and over [MCP](#connect-to-ai-too 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)): - **Read**: query data only -- **Write**: create and propose transactions; update transaction memos and properties +- **Write**: create and propose transactions. Update transaction memos and properties - **Owner**: manage accounts and team settings -Copy the key (it starts with `sk_`) and save it somewhere secure. You won't be able to see it again. +Copy the key (it starts with `sk_`) and save it somewhere secure. You will not be able to see it again. ### Install the CLI and sign in @@ -57,7 +57,7 @@ For any stdio MCP client, run `SPLITS_API_KEY=sk_... npx @splits/splits-cli --mc 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. -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. +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. ## Transaction metadata @@ -71,7 +71,7 @@ By default, signing happens in the web UI with a passkey. To operate headlessly, 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. -`splits auth delete-key` removes the local key but does **not** revoke it onchain; remove it from accounts via `update-signers`. +`splits auth delete-key` removes the local key but does **not** revoke it onchain. Remove it from accounts via `update-signers`. ## Tune output for agents diff --git a/src/pages/introduction/core-concepts.mdx b/src/pages/introduction/core-concepts.mdx index 6c1ada1..052685d 100644 --- a/src/pages/introduction/core-concepts.mdx +++ b/src/pages/introduction/core-concepts.mdx @@ -1,31 +1,38 @@ --- title: Core concepts -description: The three concepts Splits is built around (Team, Member, and Account) and how they relate to each other. +description: The three central Splits concepts (Team, Member, and Account) and their relationships. --- # Core concepts [Team, Member, and Account: the three ideas everything else builds on] -- **[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. +- **[Team](/teams)**: the product unit that contains one set of books. +- **[Member](/members)**: a person who belongs to a team and has a role and signing keys. +- **[Account](/accounts)**: a smart account that holds a team's assets. ## 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). +The current team contains the accounts, balances, transactions, and banking information in the app. The team selector shows the current team. Personal settings are not part of a team. -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 contains one set of books for a company, an individual, or a project. A team does not require a legal entity. Each team can have only one verified entity. Onramps, offramps, and compliant payments require [entity verification](/banking). -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). +Use the tax ID to help select the team structure. For example, Alice belongs to the Splits Labs team, which uses an EIN. Her personal team uses her SSN. These two tax IDs require two teams. + +A project without an entity can use a separate team from its first day. The project can [verify through an individual](/introduction/personal-usage#no-entity-needed) before incorporation. + +Use the same team when the Owners do not change. Create a new team when the Owners are different. This choice affects Recovery. See [Change recovery signers](/teams/recovery#changing-recovery-signers). To create a team, see [Teams](/teams). ## 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 is a person who belongs to a [team](/teams). Members use email to authenticate. One member can belong to multiple teams. Each member has an Owner or Member [role](/teams/roles). The role determines which settings the member can administer. + +Each member also has **[signing keys](/members/keys)**. A signing key is a passkey in the app or an EOA [registered through the CLI](/introduction/agents#sign-locally-with-an-eoa). -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). +An account can add a member's signing key as a [signer](/accounts/signers). **Membership alone grants no signing authority.** Signing keys, email, display name, and avatar belong to the member. They are available in each team that the member joins. Manage them in [Settings > Personal](https://app.splits.org/settings/personal/general/). For roles, invitations, and removal, see [Members](/members). ## 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**. +An account holds a team's assets at the same address on every active network. Every account has a [threshold](/accounts/thresholds). A transaction executes after the required number of [signers](/accounts/signers) approve it. -For account types and the ownership chain, see [Accounts](/accounts). +Members hold the signing keys that become signers. The public key is on the blockchain, and the private key stays with the member. **Splits never has access to private keys.** +For account types and the ownership chain, see [Accounts](/accounts). diff --git a/src/pages/introduction/extension.mdx b/src/pages/introduction/extension.mdx index 83e6eb8..c0ef4ff 100644 --- a/src/pages/introduction/extension.mdx +++ b/src/pages/introduction/extension.mdx @@ -5,7 +5,7 @@ description: Connect Splits accounts to third-party apps using the Splits Connec # Browser extension [Use Splits accounts inside third-party apps] -[Splits Connect](https://chromewebstore.google.com/detail/splits/ghfacfafnbcgkielpaeifdpoggfeakif) 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 all [fully supported networks](/introduction/networks-and-assets). +[Splits Connect](https://chromewebstore.google.com/detail/splits/ghfacfafnbcgkielpaeifdpoggfeakif) 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 does not support the extension (e.g. Safari), [WalletConnect](/integrations/walletconnect) works as a fallback. The extension works on all [fully supported networks](/introduction/networks-and-assets). ## Connecting @@ -15,8 +15,8 @@ description: Connect Splits accounts to third-party apps using the Splits Connec ## Signing -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). +When you perform a transaction in the connected app, the Splits dialog opens. Sign with your passkey as you would sign a transaction in the Splits app. You can also add transactions from external apps 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. +If an app does not show Splits as a wallet option, the likely cause is that the app does not 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. diff --git a/src/pages/introduction/networks-and-assets.mdx b/src/pages/introduction/networks-and-assets.mdx index cff1188..593d41f 100644 --- a/src/pages/introduction/networks-and-assets.mdx +++ b/src/pages/introduction/networks-and-assets.mdx @@ -7,9 +7,9 @@ description: Networks and asset types supported by Splits, full and partial supp Splits supports some networks fully (every feature) and a longer tail partially (sending and asset recovery only). Owners turn networks on and off per team in [Settings > Networks](https://app.splits.org/settings/team/networks/) (see [Settings](/teams/settings)). -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. +Enabled networks stay in sync. An account has the same address, signers, threshold, and owner on all enabled networks. This occurs 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 no onchain action. -Email support to request another network or asset type; most EVM-equivalent networks can be supported, pending our underlying providers. +Email support to request another network or asset type. Most EVM-equivalent networks can be supported, pending our underlying providers. ## Fully supported networks diff --git a/src/pages/introduction/personal-usage.mdx b/src/pages/introduction/personal-usage.mdx index ad25d38..b418efe 100644 --- a/src/pages/introduction/personal-usage.mdx +++ b/src/pages/introduction/personal-usage.mdx @@ -9,7 +9,7 @@ Splits works for teams of one. A solo builder gets the same setup as a company ( ## 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). +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). ## Multisigs of one @@ -21,8 +21,8 @@ Two members on one team, registered with different emails (e.g. Gmail and Proton ## 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. +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. ## 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. +Delegate operations to AI tools through the [CLI, API, and MCP server](/introduction/agents). Scoped API keys and optional headless signing let an agent work with your treasury while you keep custody. diff --git a/src/pages/invoicing/index.mdx b/src/pages/invoicing/index.mdx index f346cbc..3f25fdd 100644 --- a/src/pages/invoicing/index.mdx +++ b/src/pages/invoicing/index.mdx @@ -5,7 +5,9 @@ description: "Onchain invoicing in Splits: issuing an invoice, requesting tokens # Invoicing [Request payment into the team's accounts, in crypto or by bank transfer] -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 do not have line items. The payer can pay in crypto or, if enabled, [by bank transfer](#pay-by-bank). + +Invoicing is free. There is no fee for issuing an invoice or making a crypto payment. Only [Owners](/teams/roles) create and manage invoices. Any member can view them. See also [Recurring](/invoicing/recurring), [Paying](/invoicing/paying), and [Tracking](/invoicing/tracking). @@ -15,7 +17,7 @@ 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. +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. Invoice numbers increment automatically per team. diff --git a/src/pages/invoicing/paying.mdx b/src/pages/invoicing/paying.mdx index 8eb3065..04e6f3c 100644 --- a/src/pages/invoicing/paying.mdx +++ b/src/pages/invoicing/paying.mdx @@ -5,7 +5,7 @@ description: "The ways an invoicee pays a Splits invoice: connecting a wallet, s # Paying invoices [How an invoicee pays: wallet, manual send, or bank] -The invoice page offers the payer up to three payment paths. The payment address shown is the receiving account itself; there is no intermediary. +The invoice page offers the payer up to three payment paths. The payment address shown is the receiving account itself. There is no intermediary. ## Connect a wallet @@ -17,4 +17,4 @@ The payer copies the payment address, sends the amount on the invoice's network ## 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 [pay-by-bank](/invoicing#pay-by-bank) is enabled, the invoice has a *Bank* tab. This tab shows the transfer details for fiat payments. It also shows the 0.25% processing fee added to the amount owed. diff --git a/src/pages/invoicing/recurring.mdx b/src/pages/invoicing/recurring.mdx index 1864572..b4aa23c 100644 --- a/src/pages/invoicing/recurring.mdx +++ b/src/pages/invoicing/recurring.mdx @@ -5,8 +5,10 @@ description: Issue the same invoice on a weekly or monthly schedule, with due da # Recurring invoices [Issue the same invoice on a schedule] -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. +When [issuing an invoice](/invoicing), turn on *Make recurring* and select a weekly or monthly interval. Each occurrence creates a new invoice. Its due date has the same offset from its issue date. If the invoice has the payer's email, Splits emails the payer. -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. +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. The first invoice generates on that date. If the issue date is today or in the past, Splits checks the due date. If the due date has not passed, the first invoice is issued when the schedule is created. + +If the first due date has passed, Splits can issue or skip that occurrence. If it skips the occurrence, the schedule advances to the next one. The form previews the result before creation. New occurrences generate daily at 11:00 UTC. View, edit, and pause schedules from the *Schedules* tab on the *Invoices* page. Like all invoicing, managing schedules requires the [Owner](/teams/roles) role. diff --git a/src/pages/invoicing/tracking.mdx b/src/pages/invoicing/tracking.mdx index 854828e..e041412 100644 --- a/src/pages/invoicing/tracking.mdx +++ b/src/pages/invoicing/tracking.mdx @@ -7,4 +7,4 @@ description: When Splits marks an invoice paid automatically, how to mark one pa 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. -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. +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. diff --git a/src/pages/members/index.mdx b/src/pages/members/index.mdx index 1dbb5fe..1b2116b 100644 --- a/src/pages/members/index.mdx +++ b/src/pages/members/index.mdx @@ -10,11 +10,11 @@ A **member** is a person in Splits, identified by their email address. One membe - **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. -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. +Membership confers **no onchain authority**. A member cannot move funds unless one of their signing keys is in an account's signer set. This makes them a [signer](/accounts/signers#signers-vs-membership) on that account. ## Signing up -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). +Members authenticate by email. Enter your email at [app.splits.org](https://app.splits.org) and select the sign-in link in the email. After you save a passkey, you can also use it to sign in. Most members register by [accepting an invite](#inviting). Registering directly leads into [creating a team](/teams#creating-a-team). ## Inviting @@ -22,9 +22,9 @@ Prerequisites: you must be an **Owner** of the team. 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. +3. The invitee receives an email with a join link (it may land in spam). If they are new to Splits, they register as part of accepting. +4. During acceptance, save a passkey when prompted. An invitee must have 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 have accepted. To give the new member signing power on an account, continue to [Changing signers](/accounts/editing#changing-signers-and-thresholds). diff --git a/src/pages/members/keys.mdx b/src/pages/members/keys.mdx index 96c151a..3837d08 100644 --- a/src/pages/members/keys.mdx +++ b/src/pages/members/keys.mdx @@ -5,41 +5,60 @@ description: "The signing keys a member holds, passkeys and EOAs: password manag # Signing keys [The passkeys and EOAs a member signs with, across every team] -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 member uses signing keys to sign transactions. A signing key is a **passkey** saved in the app or an **EOA** registered through the CLI. Signing keys belong to the [member](/members), not to a team. Thus, the member can use them in each team. The private key stays with the member. **Splits never has access to private keys.** -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. +A signing key alone cannot move assets. It gets signing authority when an account adds it to the signer set. The signing key then becomes a [signer](/accounts/signers) for that account. Its approval counts toward the account [threshold](/accounts/thresholds). + +Changes to signing keys do not change an account's signer set. See [Signers](/accounts/signers) for this distinction. See [Editing](/accounts/editing) for the onchain procedure. ## 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/). +[Passkeys](https://www.dashlane.com/blog/what-is-a-passkey-and-how-does-it-work) are key pairs in a password manager. Biometrics or a PIN activate them. Add passkeys in [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. +- **On mobile, use OS-native passkeys**: They sign without a third-party app. **Do not use 1Password as a mobile signer**: Android does not always show third-party apps during signing. -Browsers order password manager options differently. If yours doesn't appear, click *Cancel*/*X* (Chrome) or *Other options* (Safari) to reveal the rest. +Browsers order password manager options differently. If your password manager does not appear, select *Cancel* or *X* in Chrome. In Safari, select *Other options*. ## 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 with a standard Ethereum key pair. You can [register an EOA through the CLI](/introduction/agents#sign-locally-with-an-eoa). An account can then add the EOA as a signer. This method lets agents and servers sign without a user interface. A team's [recovery signers](/teams/recovery#recovery-signers) are also EOAs. ## Verifying 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. +Use verification to confirm control of a passkey without a fund transfer. Splits uses the key to sign a test message. + +1. Go to *Settings > Personal > Passkeys*. +2. Open the applicable three-dot menu. +3. Select *Verify access…*. +4. Sign the test message. + +Each passkey shows when a member last verified or used it. -Recovery signers have their own verification flow; see [Verifying recovery signers](/teams/recovery#verifying-recovery-signers). +Recovery signers have a separate verification procedure. See [Verifying recovery signers](/teams/recovery#verifying-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). +### Passkey does not open + +1. Check the top-right corner of the browser for the 1Password prompt. +2. [Verify the passkey](#verifying-access) to test it separately. +3. Disable and enable the password manager extension. +4. Refresh the page without using the browser cache. +5. Test the passkey at [webauthn.io](https://webauthn.io/). + +If the problem also occurs at webauthn.io, contact the password manager provider. If it occurs only in Splits, email support. + +If Windows passkeys respond slowly, delete temporary files. Open `%temp%` and `prefetch` with `Windows + R`, and then restart Windows. If you cannot recover a passkey, see [Recovery](/teams/recovery). ## Programmatic access Via 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 before an account adds it as a signer +- `splits members signers `: get a member's passkey IDs before an account adds them as signers (**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). diff --git a/src/pages/resources/how-we-work.mdx b/src/pages/resources/how-we-work.mdx index 7b3f9a1..c507400 100644 --- a/src/pages/resources/how-we-work.mdx +++ b/src/pages/resources/how-we-work.mdx @@ -1,69 +1,73 @@ --- 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." +description: "How the Splits team runs itself: our accounts, signers, thresholds, and financial tools, from banking to payroll." --- -# How we work [Our own workspace, and the financial tools around it] +# How we work [Our team accounts and the financial tools around them] -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 page describes how Splits manages its team and financial tools. Splits is a 10-person, venture-backed team with a US C Corporation. -## 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 accounts + +We keep stablecoins, ETH, project tokens, NFTs, and the `splits.eth` ENS name in purpose-specific [accounts](/accounts). We also keep the Farcaster recovery keys there. Passkeys approve standard transactions. Hardware wallets hold the [recovery signers](/teams/recovery#recovery-signers). | 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 | Holds approximately 90% of our assets and our ENS name. Different senior team members hold company YubiKeys. | +| Operating | 1-of-3 | Pays day-to-day expenses and funds the other accounts. Uses the Treasury signers. | +| Payroll | 1-of-n | Pays contractors. Holds at most one month of payments and has limited signing privileges. Operating funds it. | +| Engineering test | 1-of-n | Holds approximately $1,000 and is available to all engineers. | +| Project | Varies | Isolates an experiment or external collaboration. We archive it after the project ends. | -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. +Recovery uses a 2-of-3 threshold with company hardware wallets. Recovery holds no assets. It can [reset accounts](/teams/recovery) if the passkeys become unavailable. -Why this shape works for us: +This structure provides the following controls: -- **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. +- **No unilateral Treasury access.** Two approvals move the assets. The other two signers can replace a lost YubiKey. +- **Low-threshold accounts have limited balances.** A compromised signing key can reach only the applicable account balance. Each signature identifies one person. +- **Accounts support accounting.** We [require memos](/transactions/memos), and each account maps to accounting categories. ## 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. +- [Splits](https://app.splits.org/): We keep approximately 20% of the company assets on the blockchain. Approximately half of these assets [earn interest](/accounts/earn). The rates are higher than the rates that we receive outside the blockchain. The dedicated accounts [above](#our-accounts) hold all of these assets. + - FDIC insurance and custodians protect the assets that remain outside the blockchain. + - We increase the share on the blockchain because yields are higher and transfers are faster. +- [Mercury](https://mercury.com/r/0xsplits)\*: We keep a small balance for invoices and other small payments. +- [Meow](https://www.meow.com/)\*: Meow provides higher yields than Mercury. Payroll, credit cards, and health insurance debit these accounts. We selected Meow after we raised external capital because its yields were higher than Mercury yields. -*\* 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.* +*\* These custody products require a legal entity. They are not available to unincorporated projects and many entities outside the US.* ## 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. +- [Splits](https://app.splits.org/): Gives our accounting team historical prices for transactions in all accounts. **We use the memo field** to reduce questions during book closing. +- [Acuity](https://acuity.co/): Provides monthly bookkeeping and tax services. We selected Acuity when the company was small. We now prefer a dedicated part-time bookkeeper to a larger firm. +- [Xero](https://www.xero.com): Contains our chart of accounts. Our accountants update it as operational complexity increases. +- [Integral](https://integral.xyz/): Tracks the cost basis of our onchain transactions and synchronizes with the Xero chart of accounts. + - The minimum price is $7,200 each year, and the price increases with the transaction count. ## 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. +- [Ramp](https://ramp.com/): We selected Ramp before Mercury offered cards. + - Customers frequently request corporate cards that use an onchain balance. See [Rain](/integrations/rain) for the current integration. ## 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. +- [Splits](https://app.splits.org/): Pays USDC twice each month to applicable team members outside the US. +- [Gusto](https://gusto.com/): Provides payroll and benefits. We started to use Gusto in 2021 and continue to use it. +- [Deel](https://www.deel.com/): Pays team members outside the US who do not want USDC. Local conversion and offramp limitations can make USDC unsuitable for them. ## 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/): Manages the capitalization table. +- [Clerky](https://www.clerky.com/): Provides templates for contractor agreements, employment offers, and other legal documents. +- External law firm: Provides legal services for important matters. ## 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. +We no longer use centralized exchanges, Gnosis Safes, or individual software wallets. This change reduced operational problems as the team grew. See the [previous tool structure](https://splits.org/blog/onchain-team-security/). + +Splits remains an offchain-first company, but a large onchain balance provides benefits. These benefits include international payments and faster access to assets that earn interest. -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. +A Meow software error caused a missed payroll. We sent USDC from Splits to Meow through an offramp to correct the problem. The USDC earned more than 6% in Splits. diff --git a/src/pages/resources/incorporating-and-raising-capital.mdx b/src/pages/resources/incorporating-and-raising-capital.mdx index f8d87ae..9eddc86 100644 --- a/src/pages/resources/incorporating-and-raising-capital.mdx +++ b/src/pages/resources/incorporating-and-raising-capital.mdx @@ -5,13 +5,13 @@ description: Options for forming a US legal entity and setting up banking, plus # Incorporating & raising capital [Forming a US entity, and funding it onchain] -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. +You can use the product without a legal entity (such as an EIN or SSN). However, some features, such as [on/offramping](/banking), require one. There are many ways to create a US legal entity. This guide describes some available options. This guide is not legal advice. Do your own research. ## 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. +[Clerky](https://www.clerky.com/) helps you set up a Delaware C Corp. They also help with post-incorporation paperwork, equity issuance, 83b elections, and other basic legal documents. Clerky is designed for US-based founders who create Silicon Valley startups. Thus, it is not designed specifically for crypto companies. It is a cost-effective way to establish 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. @@ -39,13 +39,13 @@ The onchain-native funding mechanisms we see teams use in conjunction with Split [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 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 creators earn rewards based on the trading volume of their tokens. When a token launches, Clanker automatically sets up a single-sided Uniswap pool. It directs earnings to the creator's account. You can [view and claim these earnings in Splits](/integrations/clanker). For more information, see the [Clanker documentation](https://clanker.gitbook.io/clanker-documentation/). ### 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](https://noice.so/) helps teams form capital and distribute tokens. [Here is a recent example](https://noice.so/roof). Once you are accepted (it is 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 partners with Splits. The Noice team can help you create Splits accounts. You can then use these accounts to manage raised funds and token positions, such as Uniswap pools. For more information, see the [Noice documentation](https://docs.noice.so/). ### Echo @@ -55,14 +55,14 @@ Public raises are different and look more like a "permissioned token sale", wher ### 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/) began with onchain governance for DAOs (i.e. proposals, voting, membership, etc). [Here is an example](https://www.tally.xyz/gov/arbitrum). They have now expanded to cover token sales and distributions as well (i.e. airdrop, ICO, and vesting). -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/). +Tally's token launcher tools are similar to Clanker but are designed for more established teams. You can define the token symbol, network, and other parameters. Tally creates a white-label site and provides more types of token sales. These include fixed-price sales, continuous clearing auctions, and liquidity bootstrapping pools. Tally also offers governance tools after the ICO. For more information, see the [Tally documentation](https://docs.tally.xyz/). ## Receiving investor funding For a traditional offchain raise (e.g. a SAFE) where investors send 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. +2. **Create a dedicated account for each investor** (e.g. "Jane Doe $10k") and share its address. This makes inbound capital attributable and outstanding commitments clear. USDC transfers are final. For larger checks, have the investor send $1 first. Confirm receipt before they send the rest. 3. **Sweep and archive.** Forward each account's balance to your Treasury with a [memo](/transactions/memos), then archive the emptied account. diff --git a/src/pages/resources/security.mdx b/src/pages/resources/security.mdx index 6897aea..1029639 100644 --- a/src/pages/resources/security.mdx +++ b/src/pages/resources/security.mdx @@ -5,12 +5,12 @@ description: "Splits contract security: open source, independently audited, and # Security & bug bounty [Audited contracts, and up to $50,000 for critical vulnerabilities] -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. +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. -The bounty's source of truth is [SECURITY.md](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/SECURITY.md); in summary: +The bounty's source of truth is [SECURITY.md](https://github.com/0xSplits/splits-contracts-monorepo/blob/main/SECURITY.md). In summary: - **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. +- **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. Keep the issue confidential until it is patched. - **Safe harbor**: good-faith research that follows the policy is protected. diff --git a/src/pages/teams/index.mdx b/src/pages/teams/index.mdx index a137d2d..728fa88 100644 --- a/src/pages/teams/index.mdx +++ b/src/pages/teams/index.mdx @@ -9,9 +9,9 @@ A [team](/introduction/core-concepts#team) is the primary organizational unit in ## 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)). +Creating a team takes less than a minute. You can change all settings after setup **except Recovery**. Changing recovery signers later generates new account addresses. See [Change recovery signers](/teams/recovery#changing-recovery-signers). -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 any number of teams. If you just registered, you are guided through creating one. Otherwise click your team's name in the top left and select *Create a team*. :::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. @@ -23,14 +23,14 @@ A member can create and belong to any number of teams. If you just registered, y 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*. +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*. ### What gets created Finishing setup creates two accounts: - 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. +- The **Treasury** is owned by the Root and controlled by the owners' passkeys. It is intended to hold most of your assets. It owns every account you create later. 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). diff --git a/src/pages/teams/recovery.mdx b/src/pages/teams/recovery.mdx index 5917f6e..3bc091b 100644 --- a/src/pages/teams/recovery.mdx +++ b/src/pages/teams/recovery.mdx @@ -5,37 +5,50 @@ description: "Why recovery exists, the recovery signers that control the Root ac # Recovery [Regain control of every account if a team's passkeys are lost] -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 lets a team regain its accounts without help from Splits. [Recovery signers](#recovery-signers) control the [Root account](/accounts#root) at the recovery threshold. The Root owns all other accounts. Thus, recovery signers can reset the signers and threshold of each account. ## 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. +Splits never has custody of a team's assets. Thus, the team needs a recovery method that does not depend on Splits or its passkeys. + +Passkeys resist phishing because the issuing domain limits their use. The team cannot use its passkeys if splits.org becomes unavailable. A compromised or lost passkey manager can also make the passkeys unavailable. Recovery signers are standard Ethereum keys that do not have these dependencies. They operate while the blockchain operates. ## 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)). +Recovery signers are EOAs that the Owners select during [team setup](/teams). They control the Root at the selected recovery threshold. They provide recovery for the day-to-day signers. Owners can see them in [Settings > Recovery](https://app.splits.org/settings/team/recovery/). Changing recovery signers changes the team's account addresses ([why](#changing-recovery-signers)). ### Verifying recovery signers -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. +Use verification to confirm access to a recovery signer's private key without a fund transfer. Splits uses the key to sign a test message. + +1. Open *Settings > Recovery*. +2. Open the signer's three-dot menu and select *Verify signer*. +3. Connect the recovery wallet and sign the test message. + +Each signer shows its most recent verification time. -**If a wallet won't connect or disconnect**: MetaMask can't reliably connect a Ledger; use Rabby. If a wallet won't disconnect, navigate to another page and back, or disconnect from app.splits.org in the wallet extension. +If MetaMask does not connect to a Ledger, use Rabby. If a wallet does not disconnect, go to a different page and return. Alternatively, disconnect app.splits.org in the wallet extension. ## 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/). +Use recovery only after you confirm that the passkeys are lost. -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. +1. [Verify your passkeys](/members/keys#verifying-access) in [Settings > Personal > Passkeys](https://app.splits.org/settings/personal/passkeys/). +2. Check each teammate's passkey status in [Settings > Members](https://app.splits.org/settings/team/members/). Hover over the key icon in the applicable row. +3. Open the Treasury settings and select *Reset signers*. You can also select the Treasury in *Settings > Recovery*. +4. Select the new signers and threshold. +5. Connect the recovery wallets and approve the reset at the recovery threshold. +6. [Reset each applicable sub-account](/accounts/editing#resetting-signers). The Treasury signers approve these resets. -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. +Recovery is a [signer reset](/accounts/editing#resetting-signers) on the Treasury. The Root owns the Treasury. Thus, the recovery wallets approve the Treasury reset instead of the passkeys. ## Changing recovery signers -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 recovery configuration and [`CREATE2`](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2) determine the account addresses. This method gives each account the same address on every network, including networks that the team adds later. The team keeps full custody. **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. +To change recovery signers, create a new team. Email support to migrate the offchain data, including members, contacts, banking information, and schedules. Update each revenue or funding source with the new team addresses. These sources include smart contracts and invoices. ## Keeping recovery healthy -- [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). +- [Verify recovery signers](#verifying-recovery-signers) periodically. Each signer shows its most recent verification time. +- Use multiple signers on hardware wallets. Splits uses a [2-of-3 threshold with hardware wallets](/resources/how-we-work#our-accounts). diff --git a/src/pages/teams/roles.mdx b/src/pages/teams/roles.mdx index 8a02b3a..caee895 100644 --- a/src/pages/teams/roles.mdx +++ b/src/pages/teams/roles.mdx @@ -5,16 +5,18 @@ description: The authoritative reference for what each role (Owner, Member) and # Roles [What each role and API key scope can do] -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). +Every team [member](/members) has one of two roles: **Owner** or **Member**. Roles are stored offchain and govern what a person can see and do in the app. Each role applies to one team. The same person can be an Owner of one team and a Member of another. -A related surface (**API key scopes**) governs what programmatic clients can do; see [below](#api-key-scopes). +A role never grants signing authority. Authority to approve transactions belongs to an account's onchain signer set. This boundary is explained in [Signers](/accounts/signers#signers-vs-membership). + +A related surface (**API key scopes**) governs what programmatic clients can do. See [below](#api-key-scopes). ## 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 | Only accounts they are a signer on | | 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 | ✅ | ❌ | @@ -30,9 +32,9 @@ A related surface (**API key scopes**) governs what programmatic clients can do; ## 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 person with the **Member** role is read-only if their signing keys are not signers on any account. They can see the team's accounts, balances, and transactions. They cannot propose or sign transactions. Owners can propose from any account, so read-only access requires the Member role. -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. +To set one up, [invite them](/members#inviting) with the Member role and do not add them to any account's signer set. Useful for accountants, auditors, and anyone who needs visibility without the ability to move funds. ## Settings visibility by role diff --git a/src/pages/teams/settings.mdx b/src/pages/teams/settings.mdx index 141afb0..0325443 100644 --- a/src/pages/teams/settings.mdx +++ b/src/pages/teams/settings.mdx @@ -5,7 +5,7 @@ description: A map of team and personal settings in Splits. Members, accounts, r # Settings [A map of team and personal settings, and who can see each] -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). +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 are part of). ## Team settings diff --git a/src/pages/transactions/batch.mdx b/src/pages/transactions/batch.mdx index 8c6eb3a..24dc01c 100644 --- a/src/pages/transactions/batch.mdx +++ b/src/pages/transactions/batch.mdx @@ -5,9 +5,9 @@ description: Batches group multiple transactions into one signature, from inside # Batch [Group multiple transactions into one 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 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. -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 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. ## Batching from external apps diff --git a/src/pages/transactions/custom.mdx b/src/pages/transactions/custom.mdx index 8794b52..38dfa3f 100644 --- a/src/pages/transactions/custom.mdx +++ b/src/pages/transactions/custom.mdx @@ -1,11 +1,11 @@ --- 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. +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 is not. --- # Custom transactions [Call any writable function on any 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 built from arbitrary contract calls. Use it for an action that does not have a dedicated flow. Examples include ERC-20 approvals, protocol interactions, and [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). 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. diff --git a/src/pages/transactions/index.mdx b/src/pages/transactions/index.mdx index c651357..a1d2282 100644 --- a/src/pages/transactions/index.mdx +++ b/src/pages/transactions/index.mdx @@ -5,15 +5,15 @@ description: How transactions work in Splits (proposals, signing against a thres # Transactions [Proposals that execute onchain once signers meet the threshold] -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. +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. -Who can propose is governed by [roles](/teams/roles): Owners can propose from any account, Members only from accounts they're a signer on. +Who can propose is governed by [roles](/teams/roles): Owners can propose from any account, Members only from accounts they are a signer on. ## Types | Type | What it does | | --- | --- | -| [Sends](/transactions/sends) | Move tokens to any recipient, including tokens you don't hold, via just-in-time swaps | +| [Sends](/transactions/sends) | Move tokens to any recipient, including tokens you do not 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 | @@ -32,7 +32,7 @@ Splits sponsors transaction fees with a monthly gas stipend, applied at the team | $10k+ | $10 per month | | $100k+ | $100 per month | -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. +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 does not hold enough. There is no way to view the remaining stipend yet. Sponsorship policies can change at any time without notice. ## Programmatic access diff --git a/src/pages/transactions/memos.mdx b/src/pages/transactions/memos.mdx index 67562d3..a58c5b1 100644 --- a/src/pages/transactions/memos.mdx +++ b/src/pages/transactions/memos.mdx @@ -9,15 +9,15 @@ A memo is a short note attached to a transaction. Memos appear alongside transac ## 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 when you create a transaction. You can also add one later from the transaction row menu (*Add memo*) in any feed. This option also applies to inbound transactions, where later memos are most useful. Any member can add or edit memos. ## Requiring memos -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. +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. ## Programmatic access Via the [Splits CLI / MCP](/introduction/agents): - `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) +- `splits transactions properties set --properties '{"k":"v"}'`: structured JSON metadata beyond a memo. See [Agents & API](/introduction/agents#transaction-metadata) (**Write** scope) diff --git a/src/pages/transactions/schedules.mdx b/src/pages/transactions/schedules.mdx index 77e38b6..8eca098 100644 --- a/src/pages/transactions/schedules.mdx +++ b/src/pages/transactions/schedules.mdx @@ -9,4 +9,4 @@ A schedule creates a transfer on a daily, weekly, or monthly interval from a cho 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. -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. +Schedules handle transfers only. They cannot run swaps or custom transactions. [Owners](/teams/roles) create, edit, pause, and delete schedules from the *Schedules* page. Any member can view them. diff --git a/src/pages/transactions/sends.mdx b/src/pages/transactions/sends.mdx index 7934421..0efbe57 100644 --- a/src/pages/transactions/sends.mdx +++ b/src/pages/transactions/sends.mdx @@ -5,15 +5,15 @@ description: "Sends move tokens or NFTs from a Splits account to any recipient: # Sends [Move tokens from an account to any 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 moves tokens from one of your [accounts](/accounts) to any recipient. A recipient can be a raw address, an ENS name, a Farcaster username, or a saved [contact](/contacts). You can also send to another account that you own. If [banking](/banking) is set up, you can send to a bank account. The amount you enter is the amount **the recipient receives**. You can send NFTs (ERC-721 and ERC-1155) in the same way. -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). +You can send a token that the account does not hold. The account must hold enough value in another token to cover the send. Select the token to sell. The swap and send then execute as one transaction. See [just-in-time swaps](/transactions/swaps#just-in-time-swaps). To pay a recipient without revealing which account paid them, use a [private transfer](#private-transfers). ## Memos -Add a [memo](/transactions/memos) when creating the send; it makes your [accounting](/accounting) dramatically easier later. +Add a [memo](/transactions/memos) when creating the send. It makes your [accounting](/accounting) dramatically easier later. ## Batching @@ -25,7 +25,7 @@ Like every [transaction](/transactions), a send executes once signatures meet th ## 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. See the table for supported tokens. Private transfers are behind a feature flag; contact [support@splits.org](mailto:support@splits.org) for access. +A private transfer hides the connection between your account and the recipient. The payout cannot be traced back to your account onchain. Your team can still see the recipient and amount in the Splits UI. See the table for supported tokens. Private transfers are behind a feature flag. Contact [support@splits.org](mailto:support@splits.org) for access. | Token | Networks | | --- | --- | diff --git a/src/pages/transactions/swaps.mdx b/src/pages/transactions/swaps.mdx index b038682..6a1ab00 100644 --- a/src/pages/transactions/swaps.mdx +++ b/src/pages/transactions/swaps.mdx @@ -7,11 +7,11 @@ description: "How swaps work in Splits: multi-provider routing with no fees, bri 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. -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. +Start a swap from the Dashboard or an account page. Any ERC-20 can be swapped: if a token is not listed, paste its address with the right network selected. ## 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. +[Sends](/transactions/sends) let you pay in a token the account does not 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. ## Slippage @@ -27,7 +27,7 @@ Swap quotes are signed by the provider and expire quickly, often before an accou ## Asset recovery -Tokens received on [partially supported networks](/introduction/networks-and-assets#partially-supported-networks) can be bridged back to a fully supported one from *Asset recovery* in the account's settings, which lists the account's balances on those networks and opens the same swap flow with the source network fixed. +You can bridge tokens from [partially supported networks](/introduction/networks-and-assets#partially-supported-networks) back to a fully supported network. Open *Asset recovery* in the account settings. This page lists the account balances on partially supported networks. It opens the same swap flow with the source network fixed. ## Programmatic access