diff --git a/README.md b/README.md index c37a8bd..e60d323 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Trustless Work Development Skill -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented separately in [skills/api/v2/](skills/api/v2/); the V2 React SDK is documented in [skills/react-sdk/v2/](skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](constitution.md). [View on skills.sh](https://www.skills.sh/trustless-work/trustlesswork-skill) diff --git a/SKILL.md b/SKILL.md index 0a84209..c95f53e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -36,7 +36,7 @@ npx skills add trustless-work/trustlesswork-skill Trustless Work has two protocol versions, and they are not interchangeable. - **V1 is production and the default.** It is the only version deployed on mainnet. Build every normal integration from [skills/protocol/v1.md](skills/protocol/v1.md). -- **V2 is beta and testnet-only.** It is deployed on testnet, so you can build on it there — see [skills/protocol/v2.md](skills/protocol/v2.md) — but never on mainnet while it is beta, and never as the recommended production path. Say it is beta every time. +- **V2 is beta and testnet-only.** It is deployed on testnet, so you can build on it there — contract semantics in [skills/protocol/v2.md](skills/protocol/v2.md), REST endpoints in [skills/api/v2/](skills/api/v2/core-concepts.md), React SDK in [skills/react-sdk/v2/](skills/react-sdk/v2/react-sdk.md), JS SDK in [skills/js-sdk/](skills/js-sdk/js-sdk.md) — but never on mainnet while it is beta, and never as the recommended production path. Say it is beta every time. - **Explaining V2 is always in scope.** Answer any V2 question; the restriction is which network you target, not whether the user said "beta". - **Never mix them.** Roles, payload shapes, approval semantics and lifecycle rules belong to one version at a time. - **If the version is ambiguous, use V1** and state which version you are describing. @@ -96,6 +96,17 @@ Load these on demand — only when the task requires them: - Read **[skills/api/multi-release-escrow.md](skills/api/multi-release-escrow.md)** when implementing any multi-release endpoint. - Read **[skills/api/trustlines.md](skills/api/trustlines.md)** when trustline errors occur, when setting up new accounts, or when the user asks about tokens/assets. +### REST API — V2 (beta, testnet only) +- Read **[skills/api/v2/core-concepts.md](skills/api/v2/core-concepts.md)** first for the v2 role model, trustline forms, type rules and what changed from V1. +- Read **[skills/api/v2/single-release.md](skills/api/v2/single-release.md)** when implementing any single-release v2 endpoint. +- Read **[skills/api/v2/multi-release.md](skills/api/v2/multi-release.md)** when implementing any multi-release v2 endpoint. + +### React SDK — V2 (beta, testnet only) +- Read **[skills/react-sdk/v2/react-sdk.md](skills/react-sdk/v2/react-sdk.md)** when integrating `@trustless-work/escrow` 5.x: provider setup, the build/sign/submit loop, every hook, and the new GraphQL hooks. + +### JavaScript SDK — V2 (beta) +- Read **[skills/js-sdk/js-sdk.md](skills/js-sdk/js-sdk.md)** when the integration is **not** React — Node, NestJS, Angular, browser, scripts. `@trustless-work/escrow-js` is framework-agnostic, zero-dependency, and V2-only. + ### React SDK - Read **[skills/react-sdk/react-sdk.md](skills/react-sdk/react-sdk.md)** for SDK setup, provider config, and hook overview. - Read **[skills/react-sdk/hooks-reference.md](skills/react-sdk/hooks-reference.md)** for complete parameter docs and examples of any specific hook (`useInitializeEscrow`, `useFundEscrow`, `useApproveMilestone`, `useReleaseFunds`, `useStartDispute`, `useResolveDispute`, `useUpdateEscrow`, `useChangeMilestoneStatus`, `useWithdrawRemainingFunds`, `useSendTransaction`). diff --git a/skills/api/core-concepts.md b/skills/api/core-concepts.md index afb0e0a..c58ebf9 100644 --- a/skills/api/core-concepts.md +++ b/skills/api/core-concepts.md @@ -1,6 +1,6 @@ # Core Concepts -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented separately in [skills/api/v2/](v2/); the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). ## Overview diff --git a/skills/api/multi-release-escrow.md b/skills/api/multi-release-escrow.md index c9cd845..78fae83 100644 --- a/skills/api/multi-release-escrow.md +++ b/skills/api/multi-release-escrow.md @@ -1,6 +1,6 @@ # Multi-Release Escrow -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented separately in [skills/api/v2/](v2/); the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Multi-release escrows release funds incrementally as each milestone is completed and approved. diff --git a/skills/api/single-release-escrow.md b/skills/api/single-release-escrow.md index 3fbc3dc..19e2657 100644 --- a/skills/api/single-release-escrow.md +++ b/skills/api/single-release-escrow.md @@ -1,6 +1,6 @@ # Single-Release Escrow -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented separately in [skills/api/v2/](v2/); the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Single-release escrows release all funds in one payment after all milestones are completed and approved. diff --git a/skills/api/types.md b/skills/api/types.md index 6adef8a..f6435c6 100644 --- a/skills/api/types.md +++ b/skills/api/types.md @@ -1,6 +1,6 @@ # TypeScript Types Reference -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented separately in [skills/api/v2/](v2/); the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Complete TypeScript type definitions for all Trustless Work API payloads, responses, and errors. diff --git a/skills/api/v2/core-concepts.md b/skills/api/v2/core-concepts.md new file mode 100644 index 0000000..bdcaf90 --- /dev/null +++ b/skills/api/v2/core-concepts.md @@ -0,0 +1,232 @@ +# Core API v2 — Concepts + +> **Protocol version: V2 (BETA).** V2 runs on its own host, `https://beta.api.trustlesswork.com`, against testnet. It is **not** on the V1 hosts and not on mainnet. For production use [V1](../core-concepts.md). See [constitution.md](../../../constitution.md) for the version-selection rule. + +Everything here is verified against the Core API source. Shared shapes and rules live in this file; the per-endpoint reference is in [single-release.md](single-release.md) and [multi-release.md](multi-release.md). + +--- + +## Routes are version-scoped + +Every v2 operation lives under a versioned prefix: + +``` +/escrow/single-release/v2/... +/escrow/multi-release/v2/... +``` + +There is no unversioned v2 route. Sending a v2 payload to a v1 route, or the reverse, fails. + +## Base URL + +``` +https://beta.api.trustlesswork.com +``` + +V2 has its **own host**. It is not served from the V1 hosts (`api.trustlesswork.com` for mainnet, `dev.api.trustlesswork.com` for testnet) — pointing a V2 integration at either of those fails. The beta host settles on Stellar testnet. + +## Authentication + +`x-api-key` on **every** request, including reads. Never `Authorization: Bearer`. + +## The write pattern + +Every write endpoint **builds** a transaction; it does not execute one. The response is: + +```json +{ "unsignedXdr": "AAAAAgAAAAB...", "txHash": "..." } +``` + +The field is **`unsignedXdr`**, not `unsignedTransaction`. `deploy` returns the same shape plus the predicted `contractId`: + +```json +{ "unsignedXdr": "...", "txHash": "...", "contractId": "C..." } +``` + +Three steps, always: + +1. **Build** — call the endpoint, receive the unsigned XDR. +2. **Sign** — sign client-side with the wallet the operation requires. Each endpoint below names that signer. +3. **Submit** — `POST /stellar/send-transaction` with the signed XDR. + +> The submit route differs from V1, which documents `/helper/send-transaction`. In the v2 API there is no `helper` controller. + +Nothing reaches the chain until step 3. + +The submit response always carries `txHash` and `ledger`. The rest depends on what was submitted: + +- **Successful factory deploy** — `contractId` plus the initial `escrow` snapshot. **No `code` field.** +- **Everything else** — a machine-readable `code`: + +| Code | Meaning | +| --- | --- | +| `STELLAR_TX_SUBMITTED` | Plain (non-deploy) transaction submitted successfully. | +| `STELLAR_TX_SUBMITTED_INDEXER_LAGGING` | Deploy **submitted successfully**, but the contract's return value was not indexed in time. Not an error — do not retry. Fetch the contract via `getTransaction(txHash)` or re-read shortly. | + +Branch on `code` when present, or on the presence of `contractId`, to know which variant you received. `message` is human-readable and unstable — never branch on it. + +--- + +## Shared objects + +### `roles` + +The v2 role model. Most roles are **arrays** of up to 5 unique addresses; `platform`, `admin` and `receiver` are single addresses. + +```jsonc +{ + "approvers": ["G..."], // 1-5, unique. Approve milestones + "serviceProviders": ["G..."], // 1-5, unique. Change milestone status + "releaseSigners": ["G..."], // 1-5, unique. Release funds + "disputeResolvers": ["G..."], // 1-5, unique. Resolve disputes + "platform": "G...", // single. Receives the platform fee + "admin": "G...", // single. update, manage-milestones, extend-ttl + "receiver": "G...", // single. SINGLE-RELEASE ONLY + "observers": [] // optional, read-only, no on-chain authority +} +``` + +**Multi-release has no `receiver` in `roles`** — each milestone carries its own. + +Composition rules the API and contract reject: + +- Duplicates inside a role array, or more than 5 entries. +- `disputeResolvers` overlapping any other role, including `platform` and any milestone receiver. +- `admin` overlapping any other role, including a milestone receiver. + +`admin` and `platform` **may** be the same address: that pair is a capability distinction, not an address-separation rule. + +### `trustline` + +v2 accepts **either** form. This differs from v1, which takes the issuer address only. + +```jsonc +// Form A — Soroban token contract +{ "contractId": "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA" } + +// Form B — issuer + symbol, resolved to the contract by the API +{ "symbol": "USDC", "address": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" } +``` + +Supply `contractId`, or supply both `symbol` and `address`. When `contractId` is present the other two are ignored. + +### `milestone` + +**Single-release** — a tracking unit. It holds no funds and no receiver. + +```jsonc +{ + "description": "Phase 1 — UI design delivery", // required + "status": "pending", // optional, defaults to "pending" + "approvalsTarget": 1 // optional, defaults to 1 +} +``` + +**Multi-release** — independently funded and released, so it adds its own amount and receiver. + +```jsonc +{ + "description": "Phase 1 — UI design delivery", // required + "amount": 500, // required, > 0 + "receiver": "G...", // required + "status": "pending", // optional + "approvalsTarget": 1 // optional, defaults to 1 +} +``` + +`approvalsTarget` must be `> 0` and `<= roles.approvers.length`. It is how many **distinct** approvers must approve before the milestone counts as approved. A target of 1 behaves like v1's boolean, but the field is always a threshold. + +--- + +## Type rules + +| Field | Type | Note | +| --- | --- | --- | +| `amount` (operate payloads) | `number` | Human-readable decimals (`1000`, not `"1000"`). | +| `amount` (read responses) | surface-dependent | The read model (`/escrows/...`) returns **decimal strings**; the versioned `GET /escrow/.../v2/:contractId` returns **numbers**. See “Two read surfaces”. | +| `platformFee` | `number` | **Percent**, integer 0–100 (`1` means 1%). Scaled to basis points on-chain. | +| `milestoneIndexes` | `number[]` | **Numbers, not strings.** v1's `milestoneIndex` was a string; v2 takes an array of numbers. | +| `approvalsTarget` | `number` | Integer ≥ 1. | +| `ledgersToExtend` | `number` | Integer. | +| `receiverMemo` | `number` | Optional, u32 on-chain. | +| Addresses | `string` | `G…` accounts, `C…` contracts. | + +## Field limits at deploy + +| Field | Limit | +| --- | --- | +| `engagementId` | 100 chars | +| `title` | 200 chars | +| `description` | 2000 chars | +| `milestones` | max 50 entries | + +> These differ from v1, which caps `title` at 100 and `description` at 500. Do not reuse v1 limits. + +--- + +## What changed from v1 + +| | v1 | v2 | +| --- | --- | --- | +| Route prefix | unversioned | `/v2/` required | +| Roles | one address each | arrays of up to 5, plus `admin` and `observers` | +| Approval | boolean per milestone | threshold (`approvalsTarget`) | +| Milestone index | `"0"` string, one at a time | `[0, 1]` numbers, batched | +| Trustline | issuer address only | `contractId` **or** issuer + symbol | +| Milestones at deploy | at least 1 required | **optional** — deploy empty, add later | +| Batch operations | none | approve, approve-and-release, dispute, release | +| Escrow update | `platformAddress` | `admin`, via `PUT /update` | + +--- + +## Endpoint index + +Both variants expose the same 14 operations. Names differ only for disputes. + +| Operation | Method | Path suffix | Signer | +| --- | --- | --- | --- | +| Deploy | POST | `/deploy` | `signer` | +| Fund | POST | `/fund` | `signer` (any depositor) | +| Update properties | PUT | `/update` | `admin` | +| Manage milestones | POST | `/manage-milestones` | `admin` | +| Change milestone status | POST | `/change-milestone-status` | one of `serviceProviders` | +| Approve milestones | POST | `/approve-milestones` | one of `approvers` | +| Approve and release | POST | `/approve-and-release-milestones` | approver + release signer | +| Release funds | POST | `/release-funds` | one of `releaseSigners` | +| Raise dispute | POST | `/dispute` (single) · `/dispute-milestones` (multi) | see endpoint | +| Resolve dispute | POST | `/resolve-dispute` | one of `disputeResolvers` | +| Withdraw remaining | POST | `/withdraw-remaining-funds` | one of `disputeResolvers` | +| Extend TTL | POST | `/extend-ttl` | `admin` | +| Get escrow | GET | `/:contractId` | — | +| Get balances | GET | `/escrow-balances` | — | + +--- + +## Two read surfaces — do not confuse them + +Reads exist in two places, and **both SDKs use the second one**. + +**1. On the v2 transaction controllers** — version- and type-scoped: + +``` +GET /escrow/{single-release|multi-release}/v2/:contractId +GET /escrow/{single-release|multi-release}/v2/escrow-balances +``` + +**2. The read model** — a separate controller that is **neither version-scoped nor type-scoped**: + +``` +GET /escrows # keyset list, filterable +GET /escrows/:contractId # one escrow +GET /escrows/:contractId/events # event history +GET /escrows/:contractId/milestones # milestones for one escrow +GET /escrows/details # batch detail lookup +GET /escrows/financial # batch financial summary +GET /escrows/milestones # batch milestones +``` + +There is no `/v2/` in these paths. `@trustless-work/escrow` 5.x and `@trustless-work/escrow-js` route every read method here — `getEscrow`, `listEscrows`, `getEscrowDetails`, `listEscrowEvents`, `getEscrowMilestones`, `getEscrowsMilestones`, `getEscrowsFinancial`. + +So if you are reading through an SDK you are on surface 2, and adding `/v2/` to those paths is wrong. Surface 1 is reached only by calling the transaction controller directly. + +**Amount types differ by surface**: the read model (surface 2) returns amounts and balances as **decimal strings**, unlike the numbers you send in operate payloads; the versioned reads (surface 1) return them as numbers. diff --git a/skills/api/v2/multi-release.md b/skills/api/v2/multi-release.md new file mode 100644 index 0000000..0339311 --- /dev/null +++ b/skills/api/v2/multi-release.md @@ -0,0 +1,220 @@ +# Core API v2 — Multi-Release Escrow + +> **Protocol version: V2 (BETA), testnet only.** Read [core-concepts.md](core-concepts.md) first for the shared `roles`, `trustline` and `milestone` shapes, the type rules, and the build/sign/submit pattern. + +Base path: `/escrow/multi-release/v2` + +Multi-release pays out **per milestone**. Each milestone carries its own `amount` and `receiver`, and is approved, released and disputed independently. + +## The three differences from single-release + +1. **No top-level `amount`** on the escrow, and **no `receiver` in `roles`**. Both live on each milestone. +2. **Operations take `milestoneIndexes`.** Release and dispute are scoped to the milestones you name, not the whole escrow. +3. **Disputes are per milestone**, so the endpoint is `/dispute-milestones`, not `/dispute`. + +Everything else — auth, the build/sign/submit pattern, role composition rules, trustline forms, field limits — matches [core-concepts.md](core-concepts.md). + +--- + +## Deploy + +`POST /escrow/multi-release/v2/deploy` — signed by `signer` + +```jsonc +{ + "signer": "G...", + "engagementId": "ENG-12345", + "title": "Grant programme — cohort 3", + "description": "...", + "roles": { /* no `receiver` here */ }, + "platformFee": 1, // no top-level `amount` + "trustline": { "symbol": "USDC", "address": "G..." }, + "milestones": [ // OPTIONAL — may be empty or absent + { "description": "Tranche 1", "amount": 500, "receiver": "G...", "approvalsTarget": 1 }, + { "description": "Tranche 2", "amount": 500, "receiver": "G...", "approvalsTarget": 2 } + ], + "receiverMemo": 0 +} +``` + +Each milestone needs `description`, `amount` (> 0) and `receiver`. Max 50. + +## Fund + +`POST /escrow/multi-release/v2/fund` — signed by `signer` + +```jsonc +{ "contractId": "C...", "signer": "G...", "amount": 1000 } +``` + +Fund the escrow as a whole. The canonical target is the sum of the milestone amounts. + +## Update properties + +`PUT /escrow/multi-release/v2/update` — signed by `admin` + +```jsonc +{ + "contractId": "C...", + "admin": "G...", + "escrow": { + "engagementId": "ENG-12345", + "title": "...", + "description": "...", + "platformFee": 1, + "receiverMemo": 0, + "roles": { /* full roles object, no receiver */ }, + "milestones": [ /* full milestones array */ ], + "trustline": { /* trustline object */ } + } +} +``` + +Complete desired state for the escrow's **properties and roles** — **`milestones` in this payload is ignored**; the contract preserves the existing ones. Use `manage-milestones` for milestones — but the API still **requires** the array (1–50 entries): send the existing milestones back, omitting the field fails validation. `admin` only, rejected while any milestone is disputed, and only **before the first `fund` call** — the lock is the cumulative funded amount, which never decreases. + +## Manage milestones + +`POST /escrow/multi-release/v2/manage-milestones` — signed by `admin` + +```jsonc +{ + "contractId": "C...", + "admin": "G...", + "newMilestones": [ + { "description": "Tranche 3", "amount": 250, "receiver": "G...", "approvalsTarget": 1 } + ], + "milestoneUpdates": [ + { "index": 0, "newDescription": "Tranche 1 — revised", "newAmount": 600 } + ] +} +``` + +Multi-release updates can change a milestone's **amount** as well as its description (`newDescription` max 500 chars) — single-release cannot, since the amount lives on the escrow. **Every** milestone edit (description or amount) is rejected once the escrow has been funded; appending new milestones stays allowed until the escrow is released, disputed or resolved. + +A new milestone's `receiver` must not be the `admin` or any `disputeResolver`. The contract rejects that configuration. + +## Change milestone status + +`POST /escrow/multi-release/v2/change-milestone-status` — signed by one of `serviceProviders` + +```jsonc +{ + "contractId": "C...", + "serviceProvider": "G...", + "updates": [ { "index": 0, "newStatus": "completed", "newEvidence": "https://..." } ] +} +``` + +Same rules as single-release: `newStatus` max 50 chars, `newEvidence` optional, max 500. + +## Approve milestones + +`POST /escrow/multi-release/v2/approve-milestones` — signed by one of `approvers` + +```jsonc +{ "contractId": "C...", "approver": "G...", "milestoneIndexes": [0, 1] } +``` + +One approver's vote on each listed milestone; approved once distinct approvals reach that milestone's `approvalsTarget`. + +## Approve and release + +`POST /escrow/multi-release/v2/approve-and-release-milestones` — signed by an address holding both roles + +```jsonc +{ "contractId": "C...", "signer": "G...", "milestoneIndexes": [0] } +``` + +## Release funds + +`POST /escrow/multi-release/v2/release-funds` — signed by one of `releaseSigners` + +```jsonc +{ "contractId": "C...", "releaseSigner": "G...", "milestoneIndexes": [0, 1] } +``` + +Releases **only** the named milestones — unlike single-release, which pays the whole escrow at once and requires every milestone approved. Each listed milestone must be approved, not already released, and not disputed. Each pays its own `amount` minus fees to its own `receiver`. + +## Raise a dispute + +`POST /escrow/multi-release/v2/dispute-milestones` — signed by `signer` + +```jsonc +{ + "contractId": "C...", + "signer": "G...", + "milestoneIndexes": [1], + "reason": "Tranche 2 deliverable incomplete" +} +``` + +Disputes specific milestones, leaving the rest of the escrow operative. `reason` is required, max 500 chars. Approvers, service providers, release signers, the platform and that milestone's receiver may raise one; a `disputeResolver` is rejected. + +## Resolve a dispute + +`POST /escrow/multi-release/v2/resolve-dispute` — signed by one of `disputeResolvers` + +```jsonc +{ + "contractId": "C...", + "disputeResolver": "G...", + "milestoneIndexes": [1], + "distributions": [ + { "address": "G...", "amount": 300 }, + { "address": "G...", "amount": 200 } + ] +} +``` + +Scoped to the named milestones, which must each be disputed and not already resolved (duplicates in `milestoneIndexes` are rejected). Distributions: max 50 entries, every amount positive, and the total must equal the **combined amount of the named milestones exactly** (`DistributionsMustEqualEscrowBalance` otherwise) — not less, not more. The contract balance must cover that total. It does not have to equal the full escrow balance, since resolution is per milestone. Resolution is terminal for those milestones. + +## Withdraw remaining funds + +`POST /escrow/multi-release/v2/withdraw-remaining-funds` — signed by one of `disputeResolvers` + +```jsonc +{ + "contractId": "C...", + "disputeResolver": "G...", + "distributions": [ { "address": "G...", "amount": 25 } ] +} +``` + +Requires **every** milestone to be terminal — released or its dispute resolved. The distributions must sum to the **entire remaining balance**: a full sweep, not a partial withdrawal. Fee-bearing, same as single-release. + +## Extend TTL + +`POST /escrow/multi-release/v2/extend-ttl` — signed by `admin` + +```jsonc +{ "contractId": "C...", "admin": "G...", "ledgersToExtend": 100000 } +``` + +--- + +## Reads + +`GET /escrow/multi-release/v2/:contractId` + +Same shape as single-release except: no top-level `amount`, no `roles.receiver`, and each milestone carries `amount`, `receiver`, `released` and its own `dispute` object. + +```jsonc +{ + "type": "multi-release", + "contractId": "C...", + "milestones": [ + { + "description": "Tranche 1", + "amount": 500, + "receiver": "G...", + "status": "completed", + "evidence": "https://...", + "approvals": { "target": 2, "approvalCount": 2, "approvedBy": ["G...", "G..."] }, + "released": true, + "dispute": { "isDisputed": false, "reason": "", "resolved": false } + } + ] +} +``` + +`GET /escrow/multi-release/v2/escrow-balances?addresses=C...&addresses=C...` — max 20 addresses per call. diff --git a/skills/api/v2/single-release.md b/skills/api/v2/single-release.md new file mode 100644 index 0000000..e9b9afb --- /dev/null +++ b/skills/api/v2/single-release.md @@ -0,0 +1,237 @@ +# Core API v2 — Single-Release Escrow + +> **Protocol version: V2 (BETA), testnet only.** Read [core-concepts.md](core-concepts.md) first for the shared `roles`, `trustline` and `milestone` shapes, the type rules, and the build/sign/submit pattern. + +Base path: `/escrow/single-release/v2` + +Single-release pays out **once**, to a single `roles.receiver`, after the required milestones are approved. Every write endpoint returns `{ "unsignedXdr": "...", "txHash": "..." }` — the build/sign/submit pattern in core-concepts. + +--- + +## Deploy + +`POST /escrow/single-release/v2/deploy` — signed by `signer` + +```jsonc +{ + "signer": "G...", // signs the deploy; gains no escrow role + "engagementId": "ENG-12345", // your reference, max 100 chars, NOT globally unique + "title": "Website redesign — Q2 2026", // max 200 + "description": "...", // max 2000 + "roles": { /* see core-concepts */ }, + "amount": 1000, // number, > 0. Total escrow amount + "platformFee": 1, // percent, 0-100 integer + "trustline": { "symbol": "USDC", "address": "G..." }, + "milestones": [ // OPTIONAL in v2 — may be empty or absent + { "description": "Phase 1", "approvalsTarget": 1 } + ], + "receiverMemo": 0 // optional +} +``` + +Deploying with no milestones is valid; add them later with `manage-milestones`. Max 50 when present. + +## Fund + +`POST /escrow/single-release/v2/fund` — signed by `signer` + +```jsonc +{ "contractId": "C...", "signer": "G...", "amount": 1000 } +``` + +Any address holding the asset may fund. Funding grants no role. The signer needs the asset's trustline. + +## Update properties + +`PUT /escrow/single-release/v2/update` — signed by `admin` + +```jsonc +{ + "contractId": "C...", + "admin": "G...", + "escrow": { + "engagementId": "ENG-12345", + "title": "...", + "description": "...", + "amount": 1000, + "platformFee": 1, + "receiverMemo": 0, + "roles": { /* full roles object */ }, + "milestones": [ /* full milestones array */ ], + "trustline": { /* trustline object */ } + } +} +``` + +The `escrow` object is the complete desired state for the escrow's **properties and roles** — but **`milestones` in this payload is ignored**: the contract preserves the existing milestones (and the dispute/released state). Change milestones through `manage-milestones` instead. The API still **requires** the array (1–50 entries) even though the contract ignores it — send the existing milestones back; omitting the field fails validation before reaching the contract. + +Only `admin` may call it, it is rejected while a dispute is open, and it only works **before the first `fund` call**: the lock is the cumulative funded amount, which never decreases, so releasing funds does not make the escrow editable again. + +## Manage milestones + +`POST /escrow/single-release/v2/manage-milestones` — signed by `admin` + +```jsonc +{ + "contractId": "C...", + "admin": "G...", + "newMilestones": [ + { "description": "Phase 2", "approvalsTarget": 2 } + ], + "milestoneUpdates": [ + { "index": 0, "newDescription": "Phase 1 — revised" } + ] +} +``` + +Appends new milestones and edits existing descriptions (`newDescription` max 500 chars). + +## Change milestone status + +`POST /escrow/single-release/v2/change-milestone-status` — signed by one of `serviceProviders` + +```jsonc +{ + "contractId": "C...", + "serviceProvider": "G...", + "updates": [ + { "index": 0, "newStatus": "completed", "newEvidence": "https://..." } + ] +} +``` + +Batched. Status is free text (max 50 chars) and moves no funds; convention is `pending → in_progress → completed`. `newEvidence` is optional, max 500 chars. + +## Approve milestones + +`POST /escrow/single-release/v2/approve-milestones` — signed by one of `approvers` + +```jsonc +{ + "contractId": "C...", + "approver": "G...", + "milestoneIndexes": [0, 1] +} +``` + +Casts **one** approver's vote on each listed milestone. A milestone counts as approved once distinct approvals reach its `approvalsTarget`. An approver cannot vote twice, and approval is irreversible. + +## Approve and release + +`POST /escrow/single-release/v2/approve-and-release-milestones` — signed by an address that is **both** an approver and a release signer + +```jsonc +{ + "contractId": "C...", + "signer": "G...", + "milestoneIndexes": [0, 1] +} +``` + +Collapses approval and release into one transaction. Use it only when a single wallet legitimately holds both roles. + +## Release funds + +`POST /escrow/single-release/v2/release-funds` — signed by one of `releaseSigners` + +```jsonc +{ "contractId": "C...", "releaseSigner": "G..." } +``` + +Requires **all** milestones approved and no active dispute. Pays the configured `amount` minus the platform fee and the Trustless Work protocol fee to `roles.receiver`. The contract balance must cover it. An escrow with **zero milestones cannot release** (`NoMilestoneDefined`) — add milestones first. + +## Raise a dispute + +`POST /escrow/single-release/v2/dispute` — signed by `signer` + +```jsonc +{ "contractId": "C...", "signer": "G...", "reason": "Deliverable does not match scope" } +``` + +Disputes the **whole escrow**. `reason` is required in v2 (v1 had none), max 500 chars. Approvers, service providers, release signers, the platform and the receiver may raise one; a `disputeResolver` is rejected. + +## Resolve a dispute + +`POST /escrow/single-release/v2/resolve-dispute` — signed by one of `disputeResolvers` + +```jsonc +{ + "contractId": "C...", + "disputeResolver": "G...", + "distributions": [ + { "address": "G...", "amount": 600 }, + { "address": "G...", "amount": 400 } + ] +} +``` + +Max 50 entries, every amount positive, and the total must equal the **entire current contract balance exactly** (`DistributionsMustEqualEscrowBalance` otherwise). The escrow must be disputed. Every recipient needs the asset's trustline. Resolution is terminal — a dispute cannot be reopened. + +## Withdraw remaining funds + +`POST /escrow/single-release/v2/withdraw-remaining-funds` — signed by one of `disputeResolvers` + +```jsonc +{ + "contractId": "C...", + "disputeResolver": "G...", + "distributions": [ { "address": "G...", "amount": 25 } ] +} +``` + +Sweeps leftover balance — overfunding, stray transfers, rounding dust. In v2 the escrow must be genuinely terminal: `released` or the dispute `resolved`. An open dispute does **not** qualify (it did in v1). The distributions must sum to the **entire remaining balance** — it is a full sweep, not a partial withdrawal, which v1 allowed. + +It is fee-bearing: each recipient's amount is reduced pro rata by the platform and protocol fees, and recipients receive the net. + +## Extend TTL + +`POST /escrow/single-release/v2/extend-ttl` — signed by `admin` + +```jsonc +{ "contractId": "C...", "admin": "G...", "ledgersToExtend": 100000 } +``` + +Extends Soroban storage so the escrow does not expire and get archived. + +--- + +## Reads + +`GET /escrow/single-release/v2/:contractId` + +Returns the escrow. Note the v2 shape: no `flags` object, `approvals` instead of a boolean, role arrays. + +```jsonc +{ + "type": "single-release", + "contractId": "C...", + "contractBaseId": "C...", + "engagementId": "ENG-12345", + "title": "...", + "description": "...", + "amount": 1000, + "balance": 1000, + "platformFee": 1, + "receiverMemo": 0, + "trustline": { "address": "G...", "contractId": "C...", "symbol": "USDC" }, + "roles": { + "approvers": ["G..."], "serviceProviders": ["G..."], "platform": "G...", + "releaseSigners": ["G..."], "disputeResolvers": ["G..."], + "receiver": "G...", "admin": "G...", "observers": [] + }, + "milestones": [ + { + "description": "Phase 1", + "status": "completed", + "evidence": "https://...", + "approvals": { "target": 2, "approvalCount": 1, "approvedBy": ["G..."] } + } + ], + "dispute": { "isDisputed": false, "reason": "", "resolved": false }, + "released": false +} +``` + +`GET /escrow/single-release/v2/escrow-balances?addresses=C...&addresses=C...` + +Batch balance lookup — max 20 contract addresses per call. diff --git a/skills/blocks/components.md b/skills/blocks/components.md index b769e46..009192b 100644 --- a/skills/blocks/components.md +++ b/skills/blocks/components.md @@ -1,6 +1,6 @@ # Blocks Components -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Pre-built UI components for escrow management interfaces. All components can be installed via CLI and are fully customizable. diff --git a/skills/blocks/hooks.md b/skills/blocks/hooks.md index 4393f17..4dd5daa 100644 --- a/skills/blocks/hooks.md +++ b/skills/blocks/hooks.md @@ -1,6 +1,6 @@ # Blocks TanStack Query Hooks -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). TanStack Query hooks for fetching and mutating escrows. diff --git a/skills/blocks/introduction.md b/skills/blocks/introduction.md index 48c0261..ac8a16e 100644 --- a/skills/blocks/introduction.md +++ b/skills/blocks/introduction.md @@ -1,6 +1,6 @@ # Blocks SDK Introduction -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). The Trustless Work Blocks SDK (`@trustless-work/blocks`) provides pre-built UI components, providers, and TanStack Query hooks for quickly building escrow management interfaces. diff --git a/skills/blocks/providers.md b/skills/blocks/providers.md index d3f9647..e35600b 100644 --- a/skills/blocks/providers.md +++ b/skills/blocks/providers.md @@ -1,6 +1,6 @@ # Blocks Providers -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Providers for escrow state, wallet context, dialogs, and amount formatting. **Provider order is critical** — do not reorder. diff --git a/skills/blocks/vibe-coding.md b/skills/blocks/vibe-coding.md index a73cc29..b07cc71 100644 --- a/skills/blocks/vibe-coding.md +++ b/skills/blocks/vibe-coding.md @@ -1,6 +1,6 @@ # Vibe Coding Guide -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Single-file AI context guide for Trustless Work Escrow Blocks with Next.js. Built for agent workflows. diff --git a/skills/js-sdk/js-sdk.md b/skills/js-sdk/js-sdk.md new file mode 100644 index 0000000..df1a280 --- /dev/null +++ b/skills/js-sdk/js-sdk.md @@ -0,0 +1,176 @@ +# JavaScript SDK v2 — `@trustless-work/escrow-js` + +> **Protocol version: V2 (BETA).** Framework-agnostic client over Core API v2, served from `https://beta.api.trustlesswork.com`. There is no V1 equivalent of this package. For React use [@trustless-work/escrow 5.x](../react-sdk/v2/react-sdk.md); for the REST endpoints and payload shapes see [skills/api/v2/](../api/v2/core-concepts.md). + +Use this SDK outside React: Node services, NestJS, Angular, plain browser, scripts, tests, edge runtimes. + +--- + +## Install + +```bash +npm i @trustless-work/escrow-js@beta +``` + +Install under the **`beta`** dist-tag while Core v2 is in beta. + +Requires **Node 18+** for global `fetch`. **Zero runtime dependencies** — no Axios, no React. + +## Create a client + +```ts +import { TrustlessWorkClient, development } from "@trustless-work/escrow-js"; + +const client = new TrustlessWorkClient({ + baseURL: development, + apiKey: process.env.API_KEY, +}); + +await client.rest.listEscrows({ scope: "mine", limit: 20 }); +await client.graphql.getEscrow({ contractId: "C..." }); +``` + +This instance pattern is the one to use with dependency injection — NestJS providers, Angular services — and anywhere you need more than one configuration. + +There is also a module-level default: `configureTrustlessWork(...)` once at startup, then `getTrustlessWorkClient()`, `escrowRest()` and `escrowGraphql()` anywhere. Prefer the explicit instance in server code; the default is convenient for scripts. + +### Options + +| Option | Type | Purpose | +| --- | --- | --- | +| `baseURL` | `string` | API host. | +| `apiKey` | `string` | Sent as `x-api-key`. Required for writes. | +| `getAccessToken` | `() => string \| Promise` | Wallet-session auth. **May be async here**, unlike the React SDK. | +| `defaultHeaders` | `Record` | Extra headers on every request. | + +> **`mainNet` and `development` both resolve to the beta host.** The package exports both names, but while V2 is beta they point at the same URL. `mainNet` does not mean production — V2 is not deployed to mainnet. + +--- + +## The signing loop + +Identical to the React SDK, minus the hooks. + +```ts +// 1. Build +const { unsignedXdr } = await client.rest.fundEscrow( + { contractId, signer, amount: 1000 }, + "single-release", +); + +// 2. Sign with the wallet the operation requires (your wallet layer) +const signedXdr = await signWithWallet(unsignedXdr); + +// 3. Submit +const result = await client.rest.sendTransaction(signedXdr); +``` + +The build response is `{ unsignedXdr, txHash }`. **`deployEscrow` adds the predicted `contractId`**, so you know the escrow's identity before submitting: + +```ts +const { unsignedXdr, txHash, contractId } = await client.rest.deployEscrow( + payload, + "single-release", +); +``` + +### Reading the submit result + +```ts +type SendTransactionResponse = { + txHash: string; + ledger: number; + contractId?: string; // present on deploy confirmation + escrow?: Escrow; // present on deploy confirmation + code?: SendTransactionCode; + message?: string; +}; +``` + +| `code` | Meaning | +| --- | --- | +| `STELLAR_TX_SUBMITTED` | Submitted and indexed. | +| `STELLAR_TX_SUBMITTED_INDEXER_LAGGING` | **The transaction succeeded.** Only the read model is behind. Do not retry — re-read after a moment. | + +Treating the lagging code as a failure and resubmitting is the most expensive mistake available here. + +--- + +## Methods + +Same surface as the React SDK, reached through `client.rest` and `client.graphql` instead of hooks. + +### Operate — build transactions + +`deployEscrow(payload, type, attribution?)` · `fundEscrow(payload, type)` · `updateEscrow(payload, type)` · `manageMilestones(payload, type)` · `changeMilestoneStatus(payload, type)` · `approveMilestones(payload, type)` · `approveAndReleaseMilestones(payload)` · `releaseFunds(payload, type)` · `releaseMilestones(payload)` · `startDispute(payload, type)` · `disputeMilestones(payload)` · `resolveDispute(payload, type)` · `withdrawRemainingFunds(payload, type)` + +`releaseMilestones`, `disputeMilestones` and `approveAndReleaseMilestones` are multi-release only and take no `type`. For the first two the operation does not exist on single-release; approve-and-release **does** exist there in the API (`POST /escrow/single-release/v2/approve-and-release-milestones`) — the SDK just does not wrap it, so call that route directly. + +`extend-ttl` is not wrapped either — call `POST /escrow/{type}/v2/extend-ttl` directly. + +### Submit + +`sendTransaction(signedXdr)` + +### REST reads + +`listEscrows(params?)` · `getEscrow(contractId)` · `getEscrowDetails(contractIds)` · `listEscrowEvents(contractId, params?)` · `getEscrowMilestones(contractId)` · `getEscrowsMilestones(contractIds)` · `getEscrowsFinancial(contractIds)` + +Batch methods accept a `string[]` of contract IDs. Use them rather than looping single reads. + +> These hit the **read model** at `/escrows/...`, which is neither version-scoped nor type-scoped — there is no `/v2/` in those paths. The v2 transaction controllers expose their own `GET /escrow/{type}/v2/:contractId`, but the SDK does not use it. Read amounts come back as **decimal strings**. + +### GraphQL reads + +`client.graphql.getEscrow(variables)` · `client.graphql.listEscrows(variables)` + +`listEscrows` filters on `scope` (`"mine"` | `"all"`), `status`, `contractType`, `engagementId`, `contractIds`, `participant`, `role`, `platformId`, `subjectId`, `createdAfter`/`createdBefore`. Paginate with `limit` + `cursor` (keyset, not offsets); sort by `createdAt` or `updatedAt`. + +--- + +## Errors + +| Class | When | +| --- | --- | +| `TrustlessWorkApiError` | The API answered with a Problem Details body. Carries the status and machine-readable detail. | +| `TrustlessWorkNetworkError` | The request never got a usable response — DNS, timeout, offline. | + +Use `parseProblemDetailsFromResponse` when handling a raw `Response` yourself. The React SDK's Axios-specific `parseProblemDetailsFromAxiosError` does not exist here. + +--- + +## Coming from the React SDK + +Payloads, responses, error codes and the Core v2 contract are **identical**. Only the React layer is removed. + +| React SDK | JS SDK | +| --- | --- | +| `TrustlessWorkConfig` | `new TrustlessWorkClient(...)` or `configureTrustlessWork(...)` | +| `useTrustlessWorkClient()` | your client instance, or `getTrustlessWorkClient()` | +| `useEscrowRest()` | `client.rest` or `escrowRest()` | +| `useEscrowGraphql()` | `client.graphql` or `escrowGraphql()` | +| `useDeployEscrow().deployEscrow` | `client.rest.deployEscrow` | +| `useSendTransaction().sendTransaction` | `client.rest.sendTransaction` | +| `useGraphqlListEscrows().listEscrows` | `client.graphql.listEscrows` | + +Every other hook follows the same rule: drop `use`, call the method on `client.rest`. + +### Transport differences + +| | React SDK | JS SDK | +| --- | --- | --- | +| HTTP | Axios | Native `fetch` | +| Runtime deps | `axios` + React peers | none | +| Token getter | sync only | sync **or** async | +| Non-Problem errors | raw Axios error | `TrustlessWorkNetworkError` | + +--- + +## Common mistakes + +* **Destructuring `unsignedTransaction`.** The field is `unsignedXdr`. +* **Retrying on `STELLAR_TX_SUBMITTED_INDEXER_LAGGING`.** The transaction already landed. +* **Assuming `mainNet` is production.** Both host constants point at the beta host today. +* **Expecting read amounts to be numbers.** Operate payloads take numbers; read responses return decimal strings. +* **Installing `latest`.** Use the `@beta` dist-tag while Core v2 is beta. +* **Running on Node < 18.** There is no `fetch` polyfill in the package. diff --git a/skills/protocol/v2.md b/skills/protocol/v2.md index c82eb83..12b4605 100644 --- a/skills/protocol/v2.md +++ b/skills/protocol/v2.md @@ -2,7 +2,7 @@ > **BETA — deployed on testnet only.** V2 runs on testnet and you can build against it there. It is **not deployed on mainnet**: never target mainnet with V2 and never recommend it as the production path while it is beta. V1 remains the production default. Explaining V2 is always in scope, but **label every V2 answer as beta**, and never mix its roles, fields or lifecycle rules into a V1 answer. -Read [constitution.md](../../constitution.md) first for the universal laws and the tag meanings. For the production profile see [v1.md](v1.md). +Read [constitution.md](../../constitution.md) first for the universal laws and the tag meanings. For the production profile see [v1.md](v1.md). For the v2 REST endpoints and payloads see [../api/v2/core-concepts.md](../api/v2/core-concepts.md). **Deployment:** testnet only. V2 is live on testnet and absent from mainnet. diff --git a/skills/react-sdk/hooks-reference.md b/skills/react-sdk/hooks-reference.md index 550e466..734a4b8 100644 --- a/skills/react-sdk/hooks-reference.md +++ b/skills/react-sdk/hooks-reference.md @@ -1,6 +1,6 @@ # React SDK Hooks Reference -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). Complete reference for all Trustless Work React SDK hooks with detailed usage examples. diff --git a/skills/react-sdk/react-sdk.md b/skills/react-sdk/react-sdk.md index 2320223..f8737e1 100644 --- a/skills/react-sdk/react-sdk.md +++ b/skills/react-sdk/react-sdk.md @@ -1,6 +1,6 @@ # React SDK -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). The Trustless Work React SDK (`@trustless-work/escrow`) provides custom hooks for integrating escrow functionality into React/Next.js applications. It uses Axios for HTTP requests. diff --git a/skills/react-sdk/v2/react-sdk.md b/skills/react-sdk/v2/react-sdk.md new file mode 100644 index 0000000..832ab4e --- /dev/null +++ b/skills/react-sdk/v2/react-sdk.md @@ -0,0 +1,198 @@ +# React SDK v2 — `@trustless-work/escrow` 5.x + +> **Protocol version: V2 (BETA), testnet only.** SDK 5.x targets the **v2** Core API exclusively — every transaction hook builds `/escrow/{type}/v2/...` routes. For V1 integrations use SDK 3.x and the [V1 React SDK reference](../react-sdk.md). + +The REST endpoints and payload shapes this SDK wraps are documented in [skills/api/v2/core-concepts.md](../../api/v2/core-concepts.md). This file covers the React surface: setup, hooks, and the signing loop. + +--- + +## Install + +```bash +npm install @trustless-work/escrow@5 +``` + +Peer requirement: a wallet integration that can sign a Stellar XDR — commonly `@creit.tech/stellar-wallets-kit`. + +## Provider setup + +Wrap your app once. The provider is a client component, so in Next.js App Router it needs `"use client"` in the file that renders it. + +```tsx +"use client"; + +import { TrustlessWorkConfig } from "@trustless-work/escrow"; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +### Configuration options + +| Prop | Type | Purpose | +| --- | --- | --- | +| `baseURL` | `string` | API host. V2 lives on `https://beta.api.trustlesswork.com`, not on the V1 hosts. | +| `apiKey` | `string` | Sent as `x-api-key`. | +| `getAccessToken` | `() => string \| undefined \| null` | Alternative to a static key — called per request, for session-based auth. | +| `defaultHeaders` | `Record` | Extra headers on every request. | + +`apiKey` and `getAccessToken` can coexist; the getter is re-read on each call, so it suits rotating tokens. + +> The package also exports `mainNet` and `development` host constants. Do **not** read them as environments: both point at the beta backend — through its internal hosting URL, not the branded host — and `mainNet` is not production (V2 is not on mainnet). Pass the explicit `baseURL` shown above instead. + +> SDK 5.x does **not** require a `QueryClientProvider`. It has no TanStack Query dependency — hooks return plain async functions, so you own the caching strategy. + +--- + +## The signing loop + +Every transaction hook **builds** a transaction. It never submits one. The shape is always the same: + +```tsx +"use client"; + +import { useFundEscrow, useSendTransaction } from "@trustless-work/escrow"; +import { signTransaction } from "@/lib/wallet"; + +export function FundButton({ contractId, signer }: Props) { + const { fundEscrow } = useFundEscrow(); + const { sendTransaction } = useSendTransaction(); + + async function onFund() { + // 1. Build — the field is `unsignedXdr`, not `unsignedTransaction` + const { unsignedXdr } = await fundEscrow( + { contractId, signer, amount: 1000 }, + "single-release", + ); + + // 2. Sign with the wallet the operation requires + const signedXdr = await signTransaction(unsignedXdr); + + // 3. Submit + const result = await sendTransaction(signedXdr); + // result.code === "STELLAR_TX_SUBMITTED_INDEXER_LAGGING" means it worked + // but the read model is behind — do not retry the transaction. + } + + return ; +} +``` + +Skipping step 3 means nothing reaches the chain. This is the single most common integration mistake. + +### The `type` argument + +Most transaction hooks take the escrow type as a second argument: + +```ts +type EscrowType = "single-release" | "multi-release"; +``` + +It must match the payload type you pass. A multi-release payload with `"single-release"` hits the wrong route and fails. + +A few methods are multi-release only and take no type: `releaseMilestones`, `disputeMilestones` and `approveAndReleaseMilestones`. For the first two the operation does not exist on single-release. Approve-and-release is different: the API **does** expose `POST /escrow/single-release/v2/approve-and-release-milestones` — the SDK just does not wrap it, so for single-release call that route directly. + +--- + +## Transaction hooks + +| Hook | Method | Signed by | Notes | +| --- | --- | --- | --- | +| `useDeployEscrow` | `deployEscrow(payload, type, attribution?)` | `signer` | Accepts optional attribution headers | +| `useFundEscrow` | `fundEscrow(payload, type)` | any depositor | | +| `useUpdateEscrow` | `updateEscrow(payload, type)` | `admin` | Only before the first fund (lock is cumulative `FundedAmount`, never resets) | +| `useManageMilestones` | `manageMilestones(payload, type)` | `admin` | Add or edit milestones | +| `useChangeMilestoneStatus` | `changeMilestoneStatus(payload, type)` | a service provider | Batched `updates` | +| `useApproveMilestones` | `approveMilestones(payload, type)` | an approver | One vote per listed milestone | +| `useApproveAndReleaseMilestones` | `approveAndReleaseMilestones(payload)` | approver + release signer | **Multi-release only** in the SDK (see above) | +| `useReleaseFunds` | `releaseFunds(payload, type)` · `releaseMilestones(payload)` | a release signer | The second is multi-release only | +| `useStartDispute` | `startDispute(payload, type)` · `disputeMilestones(payload)` | see below | `disputeMilestones` is multi-release only | +| `useResolveDispute` | `resolveDispute(payload, type)` | a dispute resolver | | +| `useWithdrawRemainingFunds` | `withdrawRemainingFunds(payload, type)` | a dispute resolver | Terminal escrows only | +| `useSendTransaction` | `sendTransaction(signedXdr)` | — | `POST /stellar/send-transaction` | + +> **The submit route changed.** V1 documents `POST /helper/send-transaction`. SDK 5.x posts to `/stellar/send-transaction`. Use `useSendTransaction` and you never have to hardcode it. + +Dispute permissions follow the contract: approvers, service providers, release signers, the platform and the receiver may open one. A dispute resolver may not — the contract rejects it. + +`extend-ttl` has **no hook** — neither SDK wraps it. When you need it, call `POST /escrow/{type}/v2/extend-ttl` directly. + +## Read hooks + +These query the indexer read model, not the chain directly. + +> These hit the **read model** at `/escrows/...`, which is neither version-scoped nor type-scoped — there is no `/v2/` in those paths. The v2 transaction controllers expose their own `GET /escrow/{type}/v2/:contractId`, but the SDK does not use it. Read amounts come back as **decimal strings**. + +| Hook | Method | Returns | +| --- | --- | --- | +| `useGetEscrow` | `getEscrow(contractId)` | One escrow | +| `useListEscrows` | `listEscrows(params?)` | Keyset-paginated list | +| `useGetEscrowDetails` | `getEscrowDetails(contractIds)` | Batch detail lookup | +| `useGetEscrowMilestones` | `getEscrowMilestones(contractId)` | Milestones for one escrow | +| `useGetEscrowsMilestones` | `getEscrowsMilestones(contractIds)` | Milestones for many | +| `useGetEscrowsFinancial` | `getEscrowsFinancial(contractIds)` | Financial summary for many | +| `useListEscrowEvents` | `listEscrowEvents(contractId, params?)` | Event history | + +Batch hooks accept either a `string[]` of contract IDs or a params object — use the batch form rather than looping single reads. + +## GraphQL hooks + +New in 5.x. Same read model, one round trip, and you choose the fields. + +| Hook | Method | REST twin | +| --- | --- | --- | +| `useGraphqlGetEscrow` | `getEscrow(variables)` | `GET /escrows/:id` | +| `useGraphqlListEscrows` | `listEscrows(variables?)` | `GET /escrows` | + +```tsx +const { listEscrows } = useGraphqlListEscrows(); +const page = await listEscrows({ scope: "mine", limit: 20, sort: "createdAt", order: "desc" }); +``` + +`listEscrows` filters on `scope` (`"mine"` or `"all"`), `status`, `contractType`, `engagementId`, `contractIds`, `participant`, `role`, `platformId`, `subjectId` and a `createdAfter`/`createdBefore` range. Paginate with `limit` plus `cursor` — it is keyset pagination, not offsets. Sort by `createdAt` or `updatedAt`. + +The exported documents `GRAPHQL_GET_ESCROW` and `GRAPHQL_LIST_ESCROWS` are available if you prefer your own client. Errors surface as `GraphqlRequestError`. + +Prefer GraphQL when a screen needs several related pieces of one escrow; prefer the REST batch reads when you need the same shape across many escrows. + +## Attribution headers + +`deployEscrow` accepts an optional third argument that tags the escrow with the platform and end user that created it: + +```ts +await deployEscrow(payload, "single-release", { + platformId: "my-platform", // X-TW-Platform + subjectId: "user-123", // X-TW-Subject +}); +``` + +Useful when one API key serves several platforms or tenants. + +--- + +## Direct service access + +The hooks are thin wrappers. Outside React — a server action, a script, a test — use the services directly: + +```ts +import { TrustlessWorkClient, EscrowRestService } from "@trustless-work/escrow"; +``` + +`useTrustlessWorkClient`, `useEscrowRest` and `useEscrowGraphql` expose the same objects inside React. + +## Common mistakes + +* **Forgetting `sendTransaction`.** The hook returns `unsignedXdr`; nothing happens until you sign and submit. +* **Mismatching `type` and payload.** `"single-release"` needs a `SingleRelease*Payload`. +* **Signing with the wrong wallet.** Each operation names its required signer; the contract rejects anything else. +* **Reusing V1 payload shapes.** Roles are arrays in v2, `milestoneIndexes` is `number[]`, and milestone approval is a threshold. See [skills/api/v2/core-concepts.md](../../api/v2/core-concepts.md). +* **Pointing `baseURL` at a V1 host.** V2 is served only from `https://beta.api.trustlesswork.com`; neither `api.trustlesswork.com` nor `dev.api.trustlesswork.com` serves it. +* **Expecting a `QueryClientProvider` requirement.** 5.x does not use TanStack Query; that was the 3.x pattern. diff --git a/skills/react-sdk/vibe-coding.md b/skills/react-sdk/vibe-coding.md index 935ec4f..c797119 100644 --- a/skills/react-sdk/vibe-coding.md +++ b/skills/react-sdk/vibe-coding.md @@ -1,6 +1,6 @@ # Vibe Coding Guide -> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 API, React SDK and Blocks surfaces are **not** documented in this skill yet; for the contract-level differences between V1 and V2 see [constitution.md](../../constitution.md). +> **Protocol version: V1.** This file documents the production V1 integration — the only version deployed on mainnet. The V2 Core API is documented in [skills/api/v2/](../api/v2/), but the V2 React SDK is documented in [skills/react-sdk/v2/](../../skills/react-sdk/v2/react-sdk.md); the V2 Blocks surface is **not** documented yet. For the contract-level differences see [constitution.md](../../constitution.md). In all of your chat prompts, you should provide the agent with specific document references, including the global content rules and the complete code project. ***GPT 5 gives the best result***