Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
* [Authentication](for-developers/taco-sdk/references/authentication/README.md)
* [Condition Context](for-developers/taco-sdk/references/authentication/conditioncontext-and-context-variables.md)
* [Conditions](for-developers/taco-sdk/references/conditions/README.md)
* [Building Conditions with an LLM](for-developers/taco-sdk/references/conditions/building-with-llms.md)
* [Cookbook](for-developers/taco-sdk/references/conditions/cookbook.md)
* [Discord Tipping Bot Deep Dive](for-developers/taco-sdk/references/conditions/discord-tipping-bot-deep-dive.md)
* [Context Variables Cheatsheet](for-developers/taco-sdk/references/conditions/context-variables.md)
* [Validating Conditions](for-developers/taco-sdk/references/conditions/validating-conditions.md)
* [Troubleshooting](for-developers/taco-sdk/references/conditions/troubleshooting.md)
* [TimeCondition](for-developers/taco-sdk/references/conditions/timecondition.md)
* [RpcCondition](for-developers/taco-sdk/references/conditions/rpccondition.md)
* [ContractCondition](for-developers/taco-sdk/references/conditions/contractcondition/README.md)
Expand Down
11 changes: 11 additions & 0 deletions for-developers/taco-sdk/references/conditions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

This section focuses on `Condition` types, composition and usage.

{% hint style="success" %}
**Authoring conditions with an LLM?** Start with [Building Conditions with an LLM](building-with-llms.md). The four key references are:

- The [Cookbook](cookbook.md) — JSON examples for every condition type.
- The [Discord tipping bot deep-dive](discord-tipping-bot-deep-dive.md) — a fully annotated complex condition.
- The [Schema reference](https://github.com/nucypher/taco-web/blob/signing-epic/packages/taco/schema-docs/condition-schemas.md) — auto-generated source of truth (markdown). A [JSON Schema version](https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json) is available for editors and structured-output LLMs.
- The [Validator script](validating-conditions.md) — catch shape errors locally before hitting the network.

When something breaks, see [Troubleshooting](troubleshooting.md) and the [Context Variables cheatsheet](context-variables.md).
{% endhint %}

## Base Conditions

Base conditions define specific criteria, and each includes a `returnValueTest` to compare the actual execution result with the expected value. These include:
Expand Down
128 changes: 128 additions & 0 deletions for-developers/taco-sdk/references/conditions/building-with-llms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Building Conditions with an LLM

TACo conditions are JSON. That makes them an excellent target for LLM-assisted authoring: describe the access policy you want in plain English, hand the LLM the right context, and iterate until the validator is happy.

This page is the recommended workflow.

## The context you give the LLM

Paste these four things into your LLM of choice (Claude, ChatGPT, Cursor, etc.) at the start of a new conversation:

1. **The full condition schema** — the canonical, machine-readable definition of every condition type, every field, every allowed value. Two formats are auto-generated from the same TypeScript source:
- [`condition-schemas.md`](https://github.com/nucypher/taco-web/blob/signing-epic/packages/taco/schema-docs/condition-schemas.md) — human-readable reference for prose-style LLMs.
- [`condition-schema.json`](https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json) — standard [JSON Schema](https://json-schema.org/) document. Use this with structured-output LLM APIs and editor `$schema` references (see [JSON Schema integration](#json-schema-integration) below).

Link these directly — do not vendor copies that will drift.
2. **The cookbook** — [JSON examples covering every condition type](cookbook.md). Examples teach an LLM patterns far faster than prose.
3. **The deep-dive** — the [Discord tipping bot walkthrough](discord-tipping-bot-deep-dive.md). One realistic, complex, fully-annotated condition is worth a thousand toy examples.
4. **The validator script** — [`validate-conditions.ts`](validating-conditions.md). Tell the LLM it can run this and iterate on the output.

## A prompt template

```
You are helping me author a TACo condition in JSON.

Reference material (please read before writing any condition):
- Schema (JSON Schema, machine-readable source of truth):
https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json
- Schema (prose version, same source):
https://github.com/nucypher/taco-web/blob/signing-epic/packages/taco/schema-docs/condition-schemas.md
- Cookbook of examples: https://docs.taco.build/for-developers/taco-sdk/references/conditions/cookbook
- Annotated complex example: https://docs.taco.build/for-developers/taco-sdk/references/conditions/discord-tipping-bot-deep-dive

Rules:
- Output a single JSON object (no prose around it) when I ask for a condition.
- Every field must exist in the schema. Do not invent fields.
- Context variables start with ":" and match /^:[a-zA-Z_][a-zA-Z0-9_]*$/.
- CompoundCondition: max 5 operands.
- MultiConditions (CompoundCondition, IfThenElseCondition, SequentialCondition)
share a combined nesting depth limit of 4.
- SequentialCondition: 2–20 variables.
- After each condition you produce, I will run validate-conditions.ts and
paste the output back. Fix any validation errors and try again.

What I want the condition to enforce:
<describe your access policy in plain English>
```

## The iteration loop

1. LLM produces a condition.
2. Save it as `conditions.json`.
3. Run `npx tsx validate-conditions.ts` ([source](validating-conditions.md)).
4. If invalid, paste the error back to the LLM. If valid, test it end-to-end against a local testnet (see [Quickstart](../../../access-control/quickstart-testnet.md)) or your app.

This loop usually converges in 1–3 rounds even for complex conditions.

## Tips that materially improve LLM output

- **Be explicit about the chain ID.** "Base mainnet" is ambiguous to a model that has not read your config; say `"chain": 8453`.
- **Name the data source.** "Check an NFT balance" is vague. "Call `balanceOf(:userAddress)` on contract `0xabc...` on Polygon (137) and require result `> 0`" is unambiguous.
- **Specify the comparator.** Models default to `==` even when you mean `>=`.
- **For sequential conditions**, list variables in dependency order and remind the model that later variables can reference earlier ones with `:varName`.
- **For ABI validation**, give the model the function signature (e.g. `transfer(address,uint256)`) — it cannot guess parameter order reliably.
- **When using `signing-attribute` / `signing-abi-attribute`**, mention which signing object format you are using (UserOperation, plain transaction, custom struct) so the model picks the right `attributeName`.

## JSON Schema integration

The auto-generated [`condition-schema.json`](https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json) is the highest-leverage piece of tooling available for condition authoring. It works in three places without any TACo dependency:

### Editors

Add `$schema` to the top of any `conditions.json` and your editor (VS Code, Cursor, JetBrains) will validate it inline as you type, with autocomplete for every field:

```json
{
"$schema": "https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json",
"version": "1.0.0",
"condition": {
"conditionType": "time",
"chain": 137,
"method": "blocktime",
"returnValueTest": { "comparator": ">", "value": 1735689600 }
}
}
```

### LLM structured output

Both Anthropic and OpenAI's APIs accept a JSON Schema directly to constrain model output. Example with the Anthropic SDK:

```ts
import Anthropic from '@anthropic-ai/sdk';

const conditionSchema = await fetch(
'https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json'
).then(r => r.json());

const result = await new Anthropic().messages.create({
model: 'claude-opus-4-6',
max_tokens: 2048,
tools: [{
name: 'emit_condition',
description: 'Emit a TACo condition matching the requested policy.',
input_schema: conditionSchema,
}],
tool_choice: { type: 'tool', name: 'emit_condition' },
messages: [{
role: 'user',
content: 'Build a condition that allows decryption only if the requester holds at least one NFT from collection 0xabc on Ethereum mainnet.',
}],
});
```

The model is now structurally constrained — it cannot return invalid shapes.

### Standalone validation (any language)

You no longer need `@nucypher/taco` installed to validate. Any standard JSON Schema validator works:

```bash
pnpm dlx ajv-cli validate \
-s https://raw.githubusercontent.com/nucypher/taco-web/signing-epic/packages/taco/schema-docs/condition-schema.json \
-d conditions.json --strict=false
```

The Python equivalent uses [`jsonschema`](https://python-jsonschema.readthedocs.io/), the Go equivalent uses [`gojsonschema`](https://github.com/xeipuuv/gojsonschema), etc.

The `validate-conditions.ts` script ([page](validating-conditions.md)) is still useful when you want runtime semantics (e.g. catching nesting-depth errors that JSON Schema cannot express), but for shape validation alone the JSON Schema is enough.
119 changes: 119 additions & 0 deletions for-developers/taco-sdk/references/conditions/context-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Context Variables Cheatsheet

A **context variable** is a placeholder used inside a condition. Its value is supplied at decryption (or signing) time, not at encryption time. That is what makes a single encrypted ciphertext usable across many requesters and many runtime states.

This page is a cheatsheet. For a longer narrative, see [Condition Context](../authentication/conditioncontext-and-context-variables.md).

## Naming rules

- Always start with `:` — e.g. `:userAddress`
- After the `:`, the name must match `/^[a-zA-Z_][a-zA-Z0-9_]*$/`
- Case-sensitive
- No dots, dashes, or spaces. `:user-address`, `:user.address`, `:1stParam` are all invalid.

If you forget the leading `:`, the SDK will treat your value as a literal string and your condition will silently match nothing (or worse, match something).

## Built-in context variables

The SDK recognises a small set of reserved or default context-variable names. Their behaviour differs — read the table carefully before assuming a value is "automatic".

| Variable | Kind | Available in | Description |
| :--- | :--- | :--- | :--- |
| `:signingConditionObject` | **Auto-injected** (node side) | `SigningObjectAttributeCondition`, `SigningObjectAbiAttributeCondition` | The full object (e.g. UserOperation) being submitted for threshold signing. You do not need to add this to the condition context — the node injects it at evaluation time. |
| `:nullAddress` | **Auto-injected** (node side) | All conditions | The zero address (`0x0000…0000`). Useful as a sentinel in allowlists or when a field genuinely means "no address". The node injects the value; you cannot override it. |
| `:userAddress` | **Reserved** | All conditions | The Ethereum address of the requester. Its value can only be provided by an `AuthProvider` at request time — it is never set by user code directly. |
| `:message` | **Default name** | `EcdsaCondition` | Default for `EcdsaCondition.message`. Not automatically populated — the decrypter must supply a value at request time. |
| `:signature` | **Default name** | `EcdsaCondition` | Default for `EcdsaCondition.signature`. Not automatically populated — the decrypter must supply a value at request time. |
| `:jwtToken` | **Default name** | `JwtCondition` | Default for `JwtCondition.jwtToken`. Not automatically populated — the decrypter must supply a value at request time. |

> The source of truth for this table is the `AUTOMATICALLY_INJECTED_CONTEXT_PARAMS` and `RESERVED_CONTEXT_PARAMS` arrays in [`taco-web/packages/taco/src/conditions/context/context.ts`](https://github.com/nucypher/taco-web/blob/signing-epic/packages/taco/src/conditions/context/context.ts).

### Auto-injected vs. reserved vs. default

- **Auto-injected** means the node populates the value at evaluation time. Setting these manually via `ConditionContext.addCustomContextVariableValues` will throw. `:signingConditionObject` and `:nullAddress` are the only auto-injected variables.
- **Reserved** means the name is fixed and the value can only come from a specific source. `:userAddress` is reserved and can only be supplied by an `AuthProvider` — attempting to set it as a custom parameter will throw.
- **Default name** means the condition *defaults* to that variable name, but you can override it. For example, if a single compound condition contains two `JwtCondition`s that must validate two different tokens, give each its own variable:

```json
{
"conditionType": "compound",
"operator": "and",
"operands": [
{
"conditionType": "jwt",
"jwtToken": ":idpToken",
"publicKey": "…",
"expectedIssuer": "https://idp.example.com/"
},
{
"conditionType": "jwt",
"jwtToken": ":partnerToken",
"publicKey": "…",
"expectedIssuer": "https://partner.example.com/"
}
]
}
```

The same applies to `EcdsaCondition.message` and `EcdsaCondition.signature` when verifying multiple signatures in one condition.

## Custom context variables

You can define any name you like, as long as it follows the [naming rules](#naming-rules). The decrypter is responsible for supplying its value at decryption time.

```json
{
"conditionType": "json",
"data": ":discordPayload",
"query": "$.member.user.id",
"returnValueTest": { "comparator": ">", "value": 0 }
}
```

Here `:discordPayload` is a custom variable. The bot supplies it as part of the decryption request.

### Variables produced by SequentialCondition

`SequentialCondition` lets each step bind a `varName`. That name becomes a context variable usable by subsequent steps:

```json
{
"conditionType": "sequential",
"conditionVariables": [
{
"varName": "balance",
"condition": { "...": "fetches an ERC20 balance" }
},
{
"varName": "validate",
"condition": {
"conditionType": "context-variable",
"contextVariable": ":balance",
"returnValueTest": { "comparator": ">=", "value": 1000 }
}
}
]
}
```

`varName` is a **plain string** (no leading `:`), but you reference it later **with** the `:` prefix.

## Where context variables can appear

| Field type | Accepts context variable? |
| :--- | :--- |
| `returnValueTest.value` | ✅ |
| `ContractCondition.parameters[*]` | ✅ |
| `JsonCondition.data` | ✅ (required — must be a context variable) |
| `JsonCondition.query` | ✅ (or a JSONPath) |
| `JsonApiCondition.endpoint` | ❌ (must be a literal HTTPS URL) |
| `JsonApiCondition.authorizationToken` | ✅ |
| `EcdsaCondition.message` / `signature` | ✅ |
| `JwtCondition.jwtToken` | ✅ |
| `chain`, `method`, `comparator` | ❌ — must be literal |
| ABI function signature keys (`"transfer(address,uint256)"`) | ❌ — must be literal |
| ABI parameter `value` inside `returnValueTest` | ✅ |

## Common gotcha: JSONPath vs context variable

`JsonCondition.query` accepts **either** a JSONPath expression (`$.member.user.id`) **or** a context variable (`:somePath`). The schema disambiguates by the leading character — `$` means JSONPath, `:` means context variable. Anything else is a validation error.
Loading