Skip to the guide
touchmarkon Stellar

Guide .tmark

.tmark 1.1Repository root

Say what your agent may do. We check the code agrees.

A .tmark is one small JSON file that states what your Stellar agent is and what it is allowed to do. Touchmark reads it, then checks every claim against the code.

  • One JSON object
  • No unknown fields
  • Any error and the whole file is ignored

your-agent/

  • src/
  • tests/
  • .tmark← here
  • package.json
  • README.md
01

Why declare anything?

Without a declaration, Touchmark can only ask whether the code is dangerous in general. With one, it can ask whether the code does what you said it does, and that question has sharp answers.

i.

It states the authority

The network, whether the agent holds a key and where it comes from, what it signs, how much it may pay and to whom, and who approves it. In one place, in one shape.

ii.

The code is held to it

Testnet only? Then no mainnet passphrase in the code that ships. An allow-list? Then one must be enforced where the agent pays. A broken claim is reported as a risk.

iii.

The badge means something

A credential stamped on an honest declaration tells the next person exactly what was promised and checked. It also names the owner who receives the credential.

02

Where it lives

Commit a file named exactly .tmark at the repository root, next to README.md. When a .tmark exists, older touchmark.json, agentveritas.json and veritas.json files are ignored.

Start from your code

$ python scripts/tmark.py init path/to/repo
# writes .tmark, pre-filled from what the code can do
$ python scripts/tmark.py check path/to/repo
# lists every error; an invalid file is ignored

Check it on every push

- uses: furkan3152/Touchmark_agent@main
  with:
    fail-on: needs_fixes
    annotate: medium

The GitHub Action validates the .tmark, annotates each error on the file, and reports tmark: valid, invalid or missing.

Validate as you type

{
  "$schema": "https://usetouchmark.app/schemas/tmark-v1.json",
  "tmark": "1.1", …

Editors that read $schema (VS Code, JetBrains) check the file against the schema as you write.

03

Build one here

Fill in the form; the file on the right is written as you go and checked against the same rules Touchmark uses. Nothing leaves your browser until you press Check with Touchmark.

Start from
Paste a .tmark

agent What it is required
Network
authority Keys and signing required
Does the agent itself hold a Stellar secret key? holds_keys
Where the signing key comes from key_source
What it signs signs
limits Money required when it moves funds
Per payment optional
Per day optional
Who it may pay recipients
When a person approves human_approval
tools What it exposes optional · up to 60
    forbidden What it must refuse optional · up to 30
      owner Who receives the credential optional
      entrypoints · contact More, optional
      Entry points where outside input enters, for the data-flow analysis · up to 10
        .tmark –

          04

          Field reference

          Every field the format has, from tmark-v1.json. Strings are trimmed; amounts are decimal strings with at most 7 decimal places; assets are XLM (or native), CODE:G…ISSUER, or a C… token contract, so a look-alike asset cannot satisfy a limit. Every G… and C… address must pass its checksum.

          (top level)

          $schemaoptional

          The schema URL, for editors. Any string.

          tmarkrequired

          The string "1" or "1.1". A file that uses any 1.1 field must say "1.1"; every "1" file stays valid unchanged. The builder writes "1.1" only when you use one.

          agentrequired

          What the agent is. See below.

          authorityrequired

          Keys and signing.

          limitswhen it moves funds

          Required when authority.signs has payments or any tool has moves_funds: true.

          owner · tools · forbidden · entrypoints · contactoptional

          See below. Any other top-level field is an error.

          agent

          namerequired

          2 to 80 characters.

          purposerequired

          20 to 1000 characters: what the agent does and for whom. This is the intent the code is judged against.

          kindrequired

          mcp_server, autonomous_agent, x402_client, x402_server, sdk, skill, contract or other.

          networkrequired

          testnet, mainnet or both. testnet with the public network's passphrase or endpoints in shipped code, or with a tool argument, a paid server's challenge or a fallback able to pick mainnet, is a high network mismatch. mainnet when the code only ever names testnet is a low one.

          discloses_aioptional · default true

          Users are told they are talking to an AI.

          authority

          holds_keysrequired

          Boolean: the agent itself holds a Stellar secret key. false while the code loads a key or signs is a high authority mismatch.

          key_sourcerequired

          env, kms, external_signer, wallet_prompt or none, and since 1.1 caller_supplied and agent_generated. Must agree with holds_keys: env, kms and agent_generated need true; none and wallet_prompt need false.

          1.1caller_supplied: the caller passes a secret in with each call and the agent keeps none, so holds_keys: false is honest even though the code loads a key. agent_generated: the agent creates a keypair and stores it itself (a keystore, a file, a database). With caller_supplied, the path the secret takes through the model and the tools is still traced by the data-flow rules.

          signsoptional · default []

          Up to six, no repeats: payments, contract_calls, account_changes, trustlines, foreign_transactions, messages. Code that can sign payments, contract calls, account changes or foreign transactions not listed here is a high authority mismatch.

          agent_accountoptional

          The G… account or C… contract the agent acts from, if fixed. Read from the ledger: signers, thresholds, balances, or the contract. If every payment needs a co-signer, the check ledger counts that as an enforced limit and approval, which no code change or prompt can remove.

          limits

          per_payment · per_dayoptional

          {"amount": "25", "asset": "XLM"}: a positive amount with at most 7 decimal places, and an asset. No amount cap in the code that pays, or no running total near it, is a high “limit not enforced”.

          recipientsrequired

          allowlist (only saved payees), any (whatever the request names) or none (never pays; an error if it moves funds). allowlist with no allow-list enforced in code is a high “limit not enforced”.

          human_approvalrequired

          always, above_limit or never. always or above_limit with no approval gate in the code that pays is a high “limit not enforced”.

          tools[]

          namerequired

          1 to 80 characters of A–Z a–z 0–9 _ . : -. Unique across tools.

          descriptionrequired

          8 to 400 characters.

          moves_funds · signs · networkrequired

          Booleans. A tool that signs needs a non-empty authority.signs; a tool that moves funds needs limits.

          filesoptional · default false

          Reads or writes local files: a spend ledger, a keystore, a cache.

          executes_codeoptional · default false1.1

          The tool runs a shell command or code: a build, a CLI, an interpreter. Declared execution is disclosed in the report, not excused: every path from input to the command is still assessed.

          generatedoptional1.1

          This entry stands for a family of tools the code creates at run time: {"pattern": "invoke_*", "source": "each function of a contract the caller registers"}. pattern is a glob of 1 to 120 characters (A–Z a–z 0–9 _ . : - * ? [ ] { }) that every generated name matches; source (8 to 300 characters) says what they are generated from. A tool the code registers whose name matches the pattern counts as declared, and is held to this entry's flags.

          max_amountoptional

          A money cap for this tool, only when moves_funds is true.

          owner · forbidden[] · entrypoints[] · contact

          owner.stellar_accountrequired in owner

          A valid G… account. The credential is issued to it after it signs the registration in Freighter.

          forbidden[]up to 30

          Each 8 to 300 characters. Used as goals the agent must refuse.

          entrypoints[]up to 10

          {"path": "src/server.ts", "symbol": "handleRequest"}: a relative path inside the repository (no .., no //) and an identifier. Marks where outside input enters, for the data-flow analysis.

          contact.securityrequired in contact

          An email or URL, 5 to 200 characters. Shown in the report.

          05

          Worked examples

          Five honest declarations, each valid against the current schema. The MCP server and the SDK describe real Stellar code; the MCP server and the contract tools use 1.1 fields.

          A read-only agent

          The smallest valid file: three required objects. It holds no key and signs nothing, so it needs no limits. Touchmark will still check that the code never loads a key or signs.

          
                    
          06

          Common mistakes, and what the checker says

          The checker answers by field, in these exact words. Any error and the file is not used at all: a half-read declaration could hide a limit or an authority.

          A paying agent with no limits

          Listing payments in signs (or a tool with moves_funds) and leaving limits out.

          (file): the agent moves funds, so limits is required

          Add limits. If anyone can be paid without approval, say so: "recipients": "any", "human_approval": "never".

          A key that both is and isn't held

          holds_keys: true with key_source: "wallet_prompt", or false with env.

          authority: holds_keys is true, so key_source cannot be none or wallet_promptauthority: key_source env or kms means the agent holds a key; set holds_keys to true

          A signing tool, but nothing in signs

          A tool has signs: true while authority.signs is empty.

          (file): a tool signs, so authority.signs must list what it signs

          Amounts as numbers, assets without issuers

          "amount": 25, "25.00000001", or "asset": "USDC".

          limits.per_payment.amount: must be a positive decimal string with at most 7 decimal places, e.g. "25"limits.per_payment.asset: must be XLM, CODE:ISSUER (G... issuer) or a C... token contract

          Quote amounts and name the issuer: "USDC:GBBD47…LFLA5" on testnet.

          A typo in an address

          A G… account with one wrong character fails its checksum.

          owner.stellar_account: must be a valid Stellar G... address (checksum included)

          Copy the address from Freighter rather than typing it.

          A 1.1 field in a "1" file

          caller_supplied, executes_code or generated in a file that still says "tmark": "1".

          (file): uses .tmark 1.1 fields (tools.0.executes_code); set "tmark": "1.1"

          Change the version line to "tmark": "1.1". Nothing else changes.

          A field the format doesn't have

          spend_limit, description at the top level, or a field from an older file.

          spend_limit: unknown field; see the .tmark schema for allowed fields

          Smaller slips

          A purpose under 20 characters, "tmark": 1 as a number, a cap on a tool that does not move funds, a value listed twice.

          agent.purpose: String should have at least 20 characterstmark: Input should be '1' or '1.1'tools.0: max_amount is set but moves_funds is falseauthority: signs lists a value twice
          07

          How a mismatch affects the verdict

          Each claim is either kept by the code or broken by it. A kept claim is shown as declared. A broken one becomes a finding in the report, with the file and line that break it.

          Claim

          "recipients": "allowlist"

          Code

          Is an allow-list enforced where the agent pays?

          Kept shown as declared
          Broken a high risk → needs fixes
          Finding in the reportSeverityEffect
          Declared testnet only, but the code can use the public networkhighAn actionable risk: the decision is needs fixes, and the score is held at 64 or below.
          The code has more Stellar authority than the .tmark declareshighActionable, as above.
          A limit the .tmark declares is not enforced by the codehighActionable, as above. The evidence names each broken limit.
          The .tmark file is not valid, so nothing in it was usedmediumCosts points; no claim, owner or limit in the file is used.
          Declared mainnet, but the code only targets testnetlowA small deduction: the declaration does not describe the code.
          The repository uses an older declaration filelowA nudge to migrate; older files cannot declare checked limits.
          08

          New in 1.1

          Three additions for agents that 1.0 could only describe in prose. They are optional and backward compatible: a "1" file keeps validating, and a file that uses any of them says "tmark": "1.1".

          a.

          Where the key really comes from

          caller_supplied for a secret passed in with each call; agent_generated for a key the agent creates and keeps.

          "authority": {
            "holds_keys": false,
            "key_source": "caller_supplied",
            "signs": ["contract_calls"]
          }

          Honest about a key the agent never keeps; the secret's path is still traced.

          b.

          Shell and code execution

          executes_code on a tool that runs a command, a build or an interpreter.

          { "name": "build_contract",
            "description": "Compile with the stellar CLI",
            "moves_funds": false, "signs": false,
            "network": false, "files": true,
            "executes_code": true }

          Disclosed, not excused: what reaches the command is still assessed.

          c.

          Generated tool families

          generated declares every tool the code creates at run time with one entry.

          { "name": "invoke_function", …,
            "generated": {
              "pattern": "invoke_*",
              "source": "each registered contract function"
            } }

          Each invoke_… tool in the code counts as declared, with this entry's flags.

          Upgrading is one line: change "tmark": "1" to "1.1" when you add the first 1.1 field. python scripts/tmark.py init and the draft on the home page write "1.1" already.

          Commit it, then test it.

          Put the file at your repository root and run an audit: the report shows each claim the code keeps, and each one it breaks.