From 1bdb04fe33b3cc1b1c2f73c1c0bdd7dd6db44884 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 01:21:28 -0400 Subject: [PATCH 01/21] docs: add mypaytag sdk realignment plan --- .../2026-06-28-mypaytag-mvp-realignment.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 agent-context/2026-06-28-mypaytag-mvp-realignment.md diff --git a/agent-context/2026-06-28-mypaytag-mvp-realignment.md b/agent-context/2026-06-28-mypaytag-mvp-realignment.md new file mode 100644 index 0000000..cdd5a22 --- /dev/null +++ b/agent-context/2026-06-28-mypaytag-mvp-realignment.md @@ -0,0 +1,70 @@ +# MyPayTag MVP Realignment Instruction + +Date: 2026-06-28 +Repo: `mypaytag-sdk` +Status: implementation instruction + +## Cross-Repo Instruction Set + +This is one of five coordinated instruction docs: + +- `mypaytag-backend`: `/Users/botmaster/src/myPayTag/mypaytag-backend/agent-context/2026-06-28-mypaytag-mvp-realignment.md` +- `mypaytag-sdk`: `/Users/botmaster/src/myPayTag/mypaytag-sdk/agent-context/2026-06-28-mypaytag-mvp-realignment.md` +- `mypaytag-site`: `/Users/botmaster/src/myPayTag/mypaytag-site/agent-context/2026-06-28-mypaytag-mvp-realignment.md` +- `cubid-monorepo`: `/Users/botmaster/src/cubid/cubid-monorepo/agent-context/2026-06-28-mypaytag-mvp-realignment.md` +- `cubid-sdk-v2`: `/Users/botmaster/src/cubid/cubid-sdk-v2/agent-context/2026-06-28-mypaytag-mvp-realignment.md` + +When touching this repo, read the other four docs first and preserve the ownership split. + +## Naming Requirement + +Request and implement the rename from `GlobalPayTo` / `globalpayto` / `gptr` language to `MyPayTag` / `mypaytag` / `mpt` where that text is public package metadata, schemas, generated types, examples, docs, OpenAPI, Postman collections, fixtures, tests, and published-contract names. Keep backwards-compatible aliases only when needed for package consumers or staged migrations, and mark them explicitly as compatibility. + +## Product Boundary + +Do not remove public docs or helper material about future solver/execution adapters solely because it is beyond MVP. Instead, ensure the MVP contract is correct and future execution helpers are clearly not required for MVP resolution. + +`mypaytag-sdk` owns the public MyPayTag contract for: + +- PayingDapp resolve requests and responses. +- PayToDapp route registration requests and responses. +- Provider callback request/response contracts. +- Hosted route-selection action contracts that MyPayTag owns. +- MyPayTag intent schema and fixtures. + +It must not define Cubid-owned user stamp elevation, opaque paytag creation, Cubid grant management, or Cubid authenticated user-session APIs. Those belong in `cubid-monorepo` and `cubid-sdk-v2`. + +## SDK Work Required + +1. Make OpenAPI match the canonical MyPayTag intent schema. + - `api/openapi.yaml` must require the same intent fields as `packages/protocol/schemas/mypaytag-intent.schema.json`. + - Require `provider_json` payload fields: provider intent id, chain, network, asset, destination, amount, reference, and expiry. + - Remove weaker examples that only require `destination`. + +2. Keep Cubid out of the MyPayTag API contract except as an external identity/consent dependency. + - MyPayTag public contracts should accept a paytag/opaque paytag reference and rely on backend validation against Cubid. + - Do not expose Cubid internal ids, raw Cubid user ids, Cubid grant internals, or Cubid stamp management APIs in this SDK. + +3. Separate MVP helpers from future execution helpers. + - Resolve and provider callback helpers are MVP. + - Solver quote helpers, preferred solver ids, bridge/swap/cross-chain quote previews, and execution fanout are future/extension helpers. + - Keep future helpers if useful, but label them as non-MVP and ensure examples do not imply they are required for the core resolve flow. + +4. Align terminology. + - Use `PayingDapp`, `PayToDapp`, `paytag`, `opaque paytag`, `route`, and `MyPayTag intent` consistently. + - Avoid saying Cubid resolves payments or routes. + - Avoid saying Cubid owns wallets or payment destinations. + +5. Add contract tests. + - OpenAPI examples, JSON Schemas, generated TypeScript types, and SDK fixtures must agree. + - Add tests that reject route registration with wallet addresses or payment instructions. + - Add tests that reject provider responses missing required `provider_json` fields. + - Add negative-disclosure fixture coverage for `no_route`, `authorization_required`, and `user_action_required`. + +6. Validation target. + - Run the repo validation command after changes. + - Regenerate derived OpenAPI/Postman/generated schema artifacts only through repo scripts. + +## Done Means + +This repo is realigned when integrators can build against an accurate MVP MyPayTag protocol without learning or depending on Cubid internals, and without needing any solver/execution helper to complete the MVP resolve flow. From 17523360695bc7d50d4807e95fcc2b78292637b9 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 01:48:55 -0400 Subject: [PATCH 02/21] docs: clarify mypaytag product decisions --- .../2026-06-28-mypaytag-mvp-realignment.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/agent-context/2026-06-28-mypaytag-mvp-realignment.md b/agent-context/2026-06-28-mypaytag-mvp-realignment.md index cdd5a22..bff3d26 100644 --- a/agent-context/2026-06-28-mypaytag-mvp-realignment.md +++ b/agent-context/2026-06-28-mypaytag-mvp-realignment.md @@ -34,6 +34,17 @@ Do not remove public docs or helper material about future solver/execution adapt It must not define Cubid-owned user stamp elevation, opaque paytag creation, Cubid grant management, or Cubid authenticated user-session APIs. Those belong in `cubid-monorepo` and `cubid-sdk-v2`. +## Product Decisions To Preserve + +- A paytag is a MyPayTag-branded payment identity powered by Cubid identity and consent primitives. +- Paytags are universal global identifiers for PayingDapps at the public protocol layer. +- Implementation may default to PayToDapp-scoped identifiers behind the scenes until global availability and non-Cubid identity provider support are ready. +- Opaque Cubid-backed paytags such as `abd123@cubid.mypaytag` are the default. Raw stamp-based paytags such as `+1234569999@phone.cubid.mypaytag` require explicit user choice. +- MyPayTag validates paytag uniqueness and availability before issuance, in preparation for future identity providers beyond Cubid. +- MyPayTag stores Cubid-issued opaque aliases or stable references where possible, plus hashes for lookup and audit. Raw paytags are avoided unless explicitly user-chosen. +- MVP supports multiple paytags per user, with each paytag initially mapped to one Cubid stamp or one opaque Cubid-backed alias. +- Launch readiness requires local tests first, then hosted staging smoke across Cubid, MyPayTag, one test PayingDapp, and one test PayToDapp. + ## SDK Work Required 1. Make OpenAPI match the canonical MyPayTag intent schema. @@ -44,6 +55,7 @@ It must not define Cubid-owned user stamp elevation, opaque paytag creation, Cub 2. Keep Cubid out of the MyPayTag API contract except as an external identity/consent dependency. - MyPayTag public contracts should accept a paytag/opaque paytag reference and rely on backend validation against Cubid. - Do not expose Cubid internal ids, raw Cubid user ids, Cubid grant internals, or Cubid stamp management APIs in this SDK. + - PayingDapps integrate with MyPayTag, not Cubid. Do not add public examples where a PayingDapp directly probes Cubid for paytag state. 3. Separate MVP helpers from future execution helpers. - Resolve and provider callback helpers are MVP. @@ -54,16 +66,19 @@ It must not define Cubid-owned user stamp elevation, opaque paytag creation, Cub - Use `PayingDapp`, `PayToDapp`, `paytag`, `opaque paytag`, `route`, and `MyPayTag intent` consistently. - Avoid saying Cubid resolves payments or routes. - Avoid saying Cubid owns wallets or payment destinations. + - Present paytags as universal for PayingDapps while noting the implementation may use PayToDapp-scoped aliases internally. 5. Add contract tests. - OpenAPI examples, JSON Schemas, generated TypeScript types, and SDK fixtures must agree. - Add tests that reject route registration with wallet addresses or payment instructions. - Add tests that reject provider responses missing required `provider_json` fields. - Add negative-disclosure fixture coverage for `no_route`, `authorization_required`, and `user_action_required`. + - Add fixtures for opaque and raw-explicit paytag examples and for paytag uniqueness/availability checks. 6. Validation target. - Run the repo validation command after changes. - Regenerate derived OpenAPI/Postman/generated schema artifacts only through repo scripts. + - Include a staged smoke checklist that pairs this SDK with Cubid SDK and one test PayingDapp/PayToDapp integration. ## Done Means From bae0e540e119f548a6118e9740df1b2cd3bb7150 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 01:59:05 -0400 Subject: [PATCH 03/21] docs: break mypaytag realignment into sdk todos --- agent-context/todo.md | 98 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/agent-context/todo.md b/agent-context/todo.md index a9d82d0..1df41f7 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -371,3 +371,101 @@ Acceptance notes: - Fixtures include SmarTrust-like examples without SmarTrust-specific exported type names. - Mock quote providers support preferred-solver and quote-fanout tests. - Fixtures remain public-safe and do not reference private backend implementation details. + +## Sprint 6: MyPayTag MVP Contract Realignment + +### GPTS-S6-T1 Rename Public GlobalPayTo Contract Surfaces To MyPayTag + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: TBD + +Implement the public-contract rename requested in `agent-context/2026-06-28-mypaytag-mvp-realignment.md`. + +Acceptance notes: + +- Package metadata, schemas, generated types, examples, docs, OpenAPI, Postman collections, fixtures, tests, and published-contract names use `MyPayTag`, `mypaytag`, and `Paytag`. +- Backwards-compatible aliases are retained only where needed and are explicitly documented as compatibility. +- Examples present paytags as universal identifiers for PayingDapps while noting implementation may use PayToDapp-scoped aliases internally. +- The other four realignment docs are checked before implementation. + +### GPTS-S6-T2 Make OpenAPI And Schemas Match The Canonical Intent Contract + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: mypaytag-sdk:GPTS-S1-T5 + +Align OpenAPI, JSON Schema, generated TypeScript, and examples around the MVP MyPayTag intent. + +Acceptance notes: + +- `api/openapi.yaml` requires the same intent fields as `packages/protocol/schemas/mypaytag-intent.schema.json`. +- `provider_json` examples require provider intent id, chain, network, asset, destination, amount, reference, and expiry. +- Weak examples that require only `destination` are removed or corrected. +- OpenAPI examples, JSON Schemas, generated TypeScript types, and SDK fixtures agree. + +### GPTS-S6-T3 Keep Cubid Internals Out Of MyPayTag Public APIs + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: cubid-sdk-v2:S18.1 + +Ensure the SDK contract treats Cubid as an external identity and consent dependency only. + +Acceptance notes: + +- Public contracts accept a paytag or opaque paytag reference and rely on backend validation against Cubid. +- Public SDK surfaces do not expose Cubid internal ids, raw Cubid user ids, Cubid grant internals, or Cubid stamp management APIs. +- PayingDapp examples integrate with MyPayTag rather than directly probing Cubid. +- MyPayTag route authorization remains separate from Cubid identity/paytag validation grants. + +### GPTS-S6-T4 Split MVP Helpers From Future Execution Helpers + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: mypaytag-sdk:GPTS-S5-T1, mypaytag-sdk:GPTS-S5-T2 + +Preserve future execution helper work while making it clearly non-MVP. + +Acceptance notes: + +- Resolve, route registration, provider callback, hosted route selection, and MyPayTag intent helpers are labeled MVP. +- Solver quote helpers, preferred solver ids, bridge/swap/cross-chain quote previews, and execution fanout are labeled future or extension helpers. +- Examples do not imply execution helpers are required for the core MyPayTag resolve flow. +- Future helper docs avoid saying Cubid resolves payments or owns wallets/payment destinations. + +### GPTS-S6-T5 Add Paytag Availability And Privacy Fixtures + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: mypaytag-sdk:GPTS-S2-T4 + +Extend fixtures for opaque/default paytags, explicit raw paytags, uniqueness checks, and safe negative states. + +Acceptance notes: + +- Fixtures include opaque Cubid-backed paytags such as `abd123@cubid.mypaytag`. +- Fixtures include raw stamp-based paytags such as `+1234569999@phone.cubid.mypaytag` only as explicit user-choice examples. +- Fixtures cover paytag uniqueness and availability checks before issuance. +- Negative-disclosure fixtures cover `no_route`, `authorization_required`, and `user_action_required`. + +### GPTS-S6-T6 Add Contract Tests And Cross-Repo Smoke Checklist + +Status: TBD +Feature branch: TBD +Session log: TBD +Depends on: mypaytag-sdk:GPTS-S6-T2, mypaytag-sdk:GPTS-S6-T3, mypaytag-sdk:GPTS-S6-T5 + +Add acceptance coverage for the corrected MVP contract and document the staged integration smoke. + +Acceptance notes: + +- Tests reject route registration with wallet addresses or payment instructions. +- Tests reject provider responses missing required `provider_json` fields. +- Generated artifacts are refreshed only through repo scripts. +- A staged smoke checklist pairs this SDK with Cubid SDK, MyPayTag backend, one test PayingDapp, and one test PayToDapp after local validation passes. From 47f8e31b93c2fcb9d49a09dfe574aa56bac72126 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 02:11:01 -0400 Subject: [PATCH 04/21] chore: realign mypaytag public naming --- agent-context/session-log/main.md | 11 ++++++++++ agent-context/todo.md | 6 +++--- api/openapi.yaml | 4 ++-- api/postman_collection.json | 6 +++--- docs/engineering/mvp-api-contracts.md | 20 +++++++++---------- .../protocol-and-sdk-architecture.md | 10 +++++----- packages/protocol/src/fixtures.ts | 16 +++++++-------- packages/protocol/src/protocol.test.ts | 6 +++--- .../provider-sdk/src/provider-sdk.test.ts | 2 +- packages/sdk/src/sdk.test.ts | 2 +- packages/testing/src/index.ts | 10 +++++----- 11 files changed, 52 insertions(+), 41 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index f2a822c..d6d0c15 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -248,3 +248,14 @@ todo: mypaytag-sdk:GPTS-S5-T4 summary: Extended the public testing package with generic route option fixtures, direct/exchange/bridge/cross-chain quote previews, exact-send and exact-receive execution quote requests, and reusable mock execution quote providers for preferred-solver and fanout tests. validation: Ran pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/sdk build, pnpm install, pnpm --filter @mypaytag/testing typecheck, and pnpm --filter @mypaytag/testing test; testing package coverage now passes 44 workspace tests. follow-ups: Continue with the public site solver-adapter content and checks. + +--- + +## 2026-06-28-gpts-s6-t1 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T1 +summary: Renamed public contract fixture IDs, action IDs, docs, OpenAPI copy, and regenerated Postman examples from old gptr/pay-to-tag wording to MyPayTag mpt/paytag terminology while preserving PayToDapp compatibility terms. +validation: Ran pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, and pnpm scan:public-boundary; focused tests passed with 44 workspace cases and the public-boundary scan passed. +follow-ups: Continue Sprint 6 by aligning OpenAPI intent schemas with the canonical JSON Schema contract. diff --git a/agent-context/todo.md b/agent-context/todo.md index 1df41f7..5dead5c 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -376,9 +376,9 @@ Acceptance notes: ### GPTS-S6-T1 Rename Public GlobalPayTo Contract Surfaces To MyPayTag -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t1 Depends on: TBD Implement the public-contract rename requested in `agent-context/2026-06-28-mypaytag-mvp-realignment.md`. diff --git a/api/openapi.yaml b/api/openapi.yaml index 97d2e38..4c8f6bb 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -4,7 +4,7 @@ info: version: 0.1.0 summary: Public resolver contracts for sending apps and receiving apps. description: | - MyPayTag lets sending apps resolve a user pay-to tag into a one-time payment + MyPayTag lets sending apps resolve a user paytag into a one-time payment intent, while receiving apps register supported routes and create dynamic provider intents only after selection. @@ -66,7 +66,7 @@ paths: tags: - Resolve operationId: resolvePayment - summary: Resolve a pay-to tag into a payment intent or safe next action. + summary: Resolve a paytag into a payment intent or safe next action. description: | Sending apps submit a Cubid verified-stamp recipient, supported payment paths, amount, purpose, and reconciliation reference. The response is a diff --git a/api/postman_collection.json b/api/postman_collection.json index f26d0f3..1acf6e6 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -169,9 +169,9 @@ "description": "", "item": [ { - "name": "Resolve a pay-to tag into a payment intent or safe next action.", + "name": "Resolve a paytag into a payment intent or safe next action.", "request": { - "name": "Resolve a pay-to tag into a payment intent or safe next action.", + "name": "Resolve a paytag into a payment intent or safe next action.", "description": { "content": "Sending apps submit a Cubid verified-stamp recipient, supported payment\npaths, amount, purpose, and reconciliation reference. The response is a\nresolved one-time MyPayTag intent, a hosted action, or a public safe\nstatus.\n", "type": "text/plain" @@ -2021,7 +2021,7 @@ "name": "MyPayTag Public API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "description": { - "content": "MyPayTag lets sending apps resolve a user pay-to tag into a one-time payment\nintent, while receiving apps register supported routes and create dynamic\nprovider intents only after selection.\n\nThis public OpenAPI document describes integration-facing contracts only.\nIt does not expose private resolver storage, deployment wiring, audit\ninternals, admin APIs, service-role behavior, or provider credentials.\n\n\nContact Support:\n Name: MyPayTag", + "content": "MyPayTag lets sending apps resolve a user paytag into a one-time payment\nintent, while receiving apps register supported routes and create dynamic\nprovider intents only after selection.\n\nThis public OpenAPI document describes integration-facing contracts only.\nIt does not expose private resolver storage, deployment wiring, audit\ninternals, admin APIs, service-role behavior, or provider credentials.\n\n\nContact Support:\n Name: MyPayTag", "type": "text/plain" } } diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index 5e96e71..6cc4f9f 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -73,7 +73,7 @@ Public statuses: | `provider_error` | Selected PayToDapp returned an invalid or failed response. | Show failure and retry only if user flow allows. | | `expired_authorization` | Required authorization has expired. | Send user through authorization again. | | `revoked_authorization` | User or system revoked authorization. | Do not retry without new authorization. | -| `invalid_identifier` | Cubid validation rejected the pay-to identifier. | Ask for a different identifier. | +| `invalid_identifier` | Cubid validation rejected the paytag identifier. | Ask for a different identifier. | | `invalid_request` | Request failed schema or policy validation. | Fix integrator request before retrying. | Private backend diagnostics must never appear as public statuses. @@ -124,7 +124,7 @@ Resolved registration response: "status": "resolved", "routes": [ { - "id": "gptr_route_123", + "id": "mpt_route_123", "payToDappId": "smartrust-wallet", "chain": "base", "network": "mainnet", @@ -142,7 +142,7 @@ Route selection required response: "status": "user_action_required", "action": { "type": "route_selection", - "url": "https://mypaytag.com/actions/route-selection/gptr_act_123", + "url": "https://mypaytag.com/actions/route-selection/mpt_act_123", "expiresAt": "2026-06-24T20:00:00Z" } } @@ -206,7 +206,7 @@ User action response: "status": "user_action_required", "action": { "type": "route_selection", - "url": "https://mypaytag.com/actions/route-selection/gptr_act_789", + "url": "https://mypaytag.com/actions/route-selection/mpt_act_789", "expiresAt": "2026-06-24T20:00:00Z" } } @@ -235,7 +235,7 @@ Request: ```json { - "resolverRequestId": "gptr_req_123", + "resolverRequestId": "mpt_req_123", "recipient": { "identifierType": "verified_stamp", "identifierAlias": "cubid_stamp_alias_abc" @@ -296,7 +296,7 @@ Rules: { "status": "resolved", "intent": { - "id": "gptr_pi_123", + "id": "mpt_pi_123", "schema": "mypaytag.intent.v1", "status": "ready", "modality": "provider_intent", @@ -334,7 +334,7 @@ Rules: } }, "references": { - "resolverReference": "gptr_pi_123", + "resolverReference": "mpt_pi_123", "providerReference": "st_pi_456", "payingDappReference": "chaincrew:payout_987" } @@ -376,7 +376,7 @@ Payor-app route option and intent option flows may return quote previews before ```json { - "id": "gptr_quote_123", + "id": "mpt_quote_123", "method": "cross_chain_intent", "methodLabel": "Cross-chain intent route", "send": { @@ -401,7 +401,7 @@ Payor-app route option and intent option flows may return quote previews before } ], "expiresAt": "2026-06-24T20:00:00Z", - "resolverReference": "gptr_req_123" + "resolverReference": "mpt_req_123" } ``` @@ -431,7 +431,7 @@ Common payload fields: "currency": "USDC" }, "references": { - "resolverReference": "gptr_pi_123", + "resolverReference": "mpt_pi_123", "providerReference": "st_pi_456", "payingDappReference": "chaincrew:payout_987" }, diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 3814530..ec9b601 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -28,7 +28,7 @@ The public protocol package should be importable by both integrators and the pri The MVP protocol supports: -- verified Cubid stamps as pay-to identifiers, +- verified Cubid stamps as paytag identifiers, - PayToDapp route registration using supported routes only, - PayingDapp resolution requests, - resolver-to-PayToDapp Modality B payment-intent callback schemas, @@ -67,7 +67,7 @@ Short list for SDK adapter support: Generic payor-app flows distinguish three stages: -- route availability query: the app asks what safe receive options can be considered for a pay-to tag and sender-supported path set; +- route availability query: the app asks what safe receive options can be considered for a paytag and sender-supported path set; - payment intent option query: the app includes amount and exactness context so the resolver can return executable or selectable quote previews; - final resolved intent: the app receives one `mypaytag.intent.v1` instruction for execution or handoff. @@ -149,7 +149,7 @@ Callback request: ```json { - "resolverRequestId": "gptr_req_123", + "resolverRequestId": "mpt_req_123", "recipient": { "identifierType": "verified_stamp", "identifierAlias": "cubid_stamp_alias_abc" @@ -181,7 +181,7 @@ Resolved response shape: { "status": "resolved", "intent": { - "id": "gptr_pi_123", + "id": "mpt_pi_123", "schema": "mypaytag.intent.v1", "status": "ready", "modality": "provider_intent", @@ -219,7 +219,7 @@ Resolved response shape: } }, "references": { - "resolverReference": "gptr_pi_123", + "resolverReference": "mpt_pi_123", "providerReference": "st_pi_456", "payingDappReference": "chaincrew:payout_987" } diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index a605f93..5ece855 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -30,7 +30,7 @@ export const validRouteRegistrationResponse: RouteRegistrationResponse = { status: "resolved", routes: [ { - id: "gptr_route_123", + id: "mpt_route_123", payToDappId: "smartrust-wallet", chain: "base", network: "mainnet", @@ -62,7 +62,7 @@ export const validResolveRequest: ResolveRequest = { }; export const validProviderCallbackRequest: ProviderCallbackRequest = { - resolverRequestId: "gptr_req_123", + resolverRequestId: "mpt_req_123", recipient: { identifierType: "verified_stamp", identifierAlias: "cubid_stamp_alias_abc", @@ -104,7 +104,7 @@ export const validProviderResponse: ProviderResponse = { }; export const validMyPayTagIntent: MyPayTagIntent = { - id: "gptr_pi_123", + id: "mpt_pi_123", schema: "mypaytag.intent.v1", status: "ready", modality: "provider_intent", @@ -126,7 +126,7 @@ export const validMyPayTagIntent: MyPayTagIntent = { singleUse: true, paymentInstruction: validProviderResponse.paymentInstruction, references: { - resolverReference: "gptr_pi_123", + resolverReference: "mpt_pi_123", providerReference: "st_pi_456", payingDappReference: "chaincrew:payout_987", }, @@ -145,7 +145,7 @@ export const validRouteSelectionResponse: ResolveResponse = { status: "user_action_required", action: { type: "route_selection", - url: "https://mypaytag.com/actions/route-selection/gptr_act_789", + url: "https://mypaytag.com/actions/route-selection/mpt_act_789", expiresAt: "2026-06-24T20:00:00Z", }, }; @@ -162,7 +162,7 @@ export const validNotificationEvent: NotificationEvent = { currency: "USDC", }, references: { - resolverReference: "gptr_pi_123", + resolverReference: "mpt_pi_123", providerReference: "st_pi_456", payingDappReference: "chaincrew:payout_987", }, @@ -172,7 +172,7 @@ export const validNotificationEvent: NotificationEvent = { }; export const validRouteQuotePreview: RouteQuotePreview = { - id: "gptr_quote_123", + id: "mpt_quote_123", method: "cross_chain_intent", methodLabel: "Cross-chain intent route", send: { @@ -204,5 +204,5 @@ export const validRouteQuotePreview: RouteQuotePreview = { }, ], expiresAt: "2026-06-24T20:00:00Z", - resolverReference: "gptr_req_123", + resolverReference: "mpt_req_123", }; diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index c0eca19..54273c5 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -125,12 +125,12 @@ describe("@mypaytag/protocol", () => { for (const action of [ { type: "setup", - url: "https://mypaytag.com/actions/setup/gptr_act_456", + url: "https://mypaytag.com/actions/setup/mpt_act_456", expiresAt: "2026-06-24T20:00:00Z", }, { type: "authorization", - url: "https://mypaytag.com/actions/setup/gptr_act_auth", + url: "https://mypaytag.com/actions/setup/mpt_act_auth", expiresAt: "2026-06-24T20:00:00Z", }, ] as const) { @@ -229,7 +229,7 @@ describe("@mypaytag/protocol", () => { ...validNotificationEvent, action: { type: "setup", - url: "https://mypaytag.com/actions/setup/gptr_act_456", + url: "https://mypaytag.com/actions/setup/mpt_act_456", expiresAt: "2026-06-24T20:00:00Z", }, }), diff --git a/packages/provider-sdk/src/provider-sdk.test.ts b/packages/provider-sdk/src/provider-sdk.test.ts index e7d9e45..4706d55 100644 --- a/packages/provider-sdk/src/provider-sdk.test.ts +++ b/packages/provider-sdk/src/provider-sdk.test.ts @@ -31,7 +31,7 @@ const authEnvelope: CallbackAuthEnvelope = { method: "POST", url: "https://wallet.example/payment-intents", bodyDigest: "sha256:abc", - resolverRequestId: "gptr_req_123", + resolverRequestId: "mpt_req_123", signature: "sig_123", nonce: "nonce_123", timestamp: "2026-06-24T20:00:00Z", diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 98d86c2..8ed117a 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -110,7 +110,7 @@ describe("@mypaytag/sdk", () => { const response = parseResolveResponse(validRouteSelectionResponse); expect(isActionRequired(response)).toBe(true); - expect(getActionUrl(response)).toBe("https://mypaytag.com/actions/route-selection/gptr_act_789"); + expect(getActionUrl(response)).toBe("https://mypaytag.com/actions/route-selection/mpt_act_789"); }); it("treats no-route responses as status-only", () => { diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index a99dfa4..3c9c1b8 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -99,28 +99,28 @@ export const myPayTagFixtures: MyPayTagFixtures = { }, routeOptions: { directTransfer: buildQuotePreview({ - id: "gptr_quote_direct", + id: "mpt_quote_direct", method: "direct_transfer", methodLabel: "Direct transfer", send: { chain: "eip155", network: "8453", asset: "USDC", amount: "25.00" }, receive: { chain: "eip155", network: "8453", asset: "USDC", amount: "25.00" }, }), sameChainExchange: buildQuotePreview({ - id: "gptr_quote_exchange", + id: "mpt_quote_exchange", method: "provider_exchange", methodLabel: "Same-chain exchange", send: { chain: "eip155", network: "8453", asset: "ETH", amount: "0.008" }, receive: { chain: "eip155", network: "8453", asset: "USDC", amount: "25.00" }, }), bridge: buildQuotePreview({ - id: "gptr_quote_bridge", + id: "mpt_quote_bridge", method: "bridge", methodLabel: "Bridge transfer", send: { chain: "eip155", network: "1", asset: "USDC", amount: "25.12" }, receive: { chain: "eip155", network: "8453", asset: "USDC", amount: "25.00" }, }), crossChainIntent: buildQuotePreview({ - id: "gptr_quote_cross_chain_intent", + id: "mpt_quote_cross_chain_intent", method: "cross_chain_intent", methodLabel: "Cross-chain intent", send: { chain: "solana", network: "mainnet-beta", asset: "USDC", amount: "25.18" }, @@ -278,7 +278,7 @@ function buildQuotePreview(input: Omit Date: Sun, 28 Jun 2026 02:12:52 -0400 Subject: [PATCH 05/21] fix: align openapi intent contract --- agent-context/session-log/main.md | 11 ++ agent-context/todo.md | 6 +- api/openapi.yaml | 185 ++++++++++++++++++++++++++---- api/postman_collection.json | 2 +- 4 files changed, 175 insertions(+), 29 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index d6d0c15..88208cc 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -259,3 +259,14 @@ todo: mypaytag-sdk:GPTS-S6-T1 summary: Renamed public contract fixture IDs, action IDs, docs, OpenAPI copy, and regenerated Postman examples from old gptr/pay-to-tag wording to MyPayTag mpt/paytag terminology while preserving PayToDapp compatibility terms. validation: Ran pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, and pnpm scan:public-boundary; focused tests passed with 44 workspace cases and the public-boundary scan passed. follow-ups: Continue Sprint 6 by aligning OpenAPI intent schemas with the canonical JSON Schema contract. + +--- + +## 2026-06-28-gpts-s6-t2 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T2 +summary: Updated OpenAPI resolved-intent and provider-response components to match the canonical MyPayTag intent schema, including required envelope fields, selected route, references, provider_json metadata, and full provider payload requirements. +validation: Ran pnpm api:postman and pnpm api:validate; Postman was regenerated from api/openapi.yaml and Redocly lint passed without warnings. +follow-ups: Continue Sprint 6 by checking the public SDK contract for Cubid-internal leakage and direct Cubid probing examples. diff --git a/agent-context/todo.md b/agent-context/todo.md index 5dead5c..cd2c7cc 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -392,9 +392,9 @@ Acceptance notes: ### GPTS-S6-T2 Make OpenAPI And Schemas Match The Canonical Intent Contract -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t2 Depends on: mypaytag-sdk:GPTS-S1-T5 Align OpenAPI, JSON Schema, generated TypeScript, and examples around the MVP MyPayTag intent. diff --git a/api/openapi.yaml b/api/openapi.yaml index 4c8f6bb..4554eaf 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -108,21 +108,41 @@ paths: value: status: resolved intent: - type: mypaytag.intent.v1 - resolverRequestId: mpt_req_123 - selectedPath: + id: mpt_pi_123 + schema: mypaytag.intent.v1 + status: ready + modality: provider_intent + recipient: + identifierType: verified_stamp + identifierHash: sha256:example + selectedRoute: + payToDappId: example-wallet chain: base network: mainnet asset: USDC amount: value: "25.00" currency: USDC + expiresAt: "2026-06-24T20:00:00Z" + singleUse: true paymentInstruction: - kind: provider_json + type: provider_json + provider: example-wallet payload: + providerIntentId: provider_pi_456 + chain: base + network: mainnet + asset: USDC destination: kind: blockchain_address recipientAddress: "0xabc0000000000000000000000000000000000000" + amount: "25.00" + reference: example-wallet:provider_pi_456 + expiresAt: "2026-06-24T20:00:00Z" + references: + resolverReference: mpt_pi_123 + providerReference: provider_pi_456 + payingDappReference: sender:payout_987 actionRequired: summary: User route selection required value: @@ -599,44 +619,139 @@ components: expiresAt: type: string format: date-time + recipientAddress: false + address: false + account: false MyPayTagIntent: type: object additionalProperties: false required: - - type - - resolverRequestId - - selectedPath + - id + - schema + - status + - modality + - recipient + - selectedRoute + - amount + - expiresAt + - singleUse - paymentInstruction + - references properties: - type: + id: + type: string + minLength: 1 + schema: type: string const: mypaytag.intent.v1 - resolverRequestId: + status: type: string - selectedPath: - $ref: "#/components/schemas/RoutePath" + const: ready + modality: + type: string + const: provider_intent + recipient: + $ref: "#/components/schemas/IntentRecipient" + selectedRoute: + $ref: "#/components/schemas/SelectedRoute" amount: $ref: "#/components/schemas/Amount" + expiresAt: + type: string + format: date-time + singleUse: + type: boolean + const: true paymentInstruction: $ref: "#/components/schemas/PaymentInstruction" + references: + $ref: "#/components/schemas/IntentReferences" + IntentRecipient: + type: object + additionalProperties: false + required: + - identifierType + - identifierHash + properties: + identifierType: + type: string + const: verified_stamp + identifierHash: + type: string + minLength: 1 + SelectedRoute: + type: object + additionalProperties: false + required: + - payToDappId + - chain + - network + - asset + properties: + payToDappId: + type: string + minLength: 1 + chain: + type: string + minLength: 1 + network: + type: string + minLength: 1 + asset: + type: string + minLength: 1 PaymentInstruction: type: object additionalProperties: false required: - - kind + - type + - provider - payload properties: - kind: + type: type: string const: provider_json + provider: + type: string + minLength: 1 payload: - type: object - additionalProperties: true - required: - - destination - properties: - destination: - $ref: "#/components/schemas/PaymentDestination" + $ref: "#/components/schemas/ProviderPayload" + ProviderPayload: + type: object + additionalProperties: true + required: + - providerIntentId + - chain + - network + - asset + - destination + - amount + - reference + - expiresAt + properties: + providerIntentId: + type: string + minLength: 1 + chain: + type: string + minLength: 1 + network: + type: string + minLength: 1 + asset: + type: string + minLength: 1 + destination: + $ref: "#/components/schemas/PaymentDestination" + amount: + type: string + pattern: "^[0-9]+(\\.[0-9]+)?$" + reference: + type: string + minLength: 1 + expiresAt: + type: string + format: date-time PaymentDestination: type: object additionalProperties: false @@ -649,6 +764,24 @@ components: const: blockchain_address recipientAddress: type: string + minLength: 1 + IntentReferences: + type: object + additionalProperties: false + required: + - resolverReference + - providerReference + - payingDappReference + properties: + resolverReference: + type: string + minLength: 1 + providerReference: + type: string + minLength: 1 + payingDappReference: + type: string + minLength: 1 RouteRegistrationRequest: type: object additionalProperties: false @@ -839,15 +972,17 @@ components: type: object additionalProperties: false required: + - providerIntentId - status + - paymentInstruction properties: + providerIntentId: + type: string + minLength: 1 status: type: string - enum: - - ready - - unavailable - - rejected - provider_json: + const: ready + paymentInstruction: $ref: "#/components/schemas/PaymentInstruction" NotificationEvent: type: object diff --git a/api/postman_collection.json b/api/postman_collection.json index 1acf6e6..51db6b5 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -261,7 +261,7 @@ "value": "application/json" } ], - "body": "{\n \"status\": \"\",\n \"intent\": {\n \"type\": \"\",\n \"resolverRequestId\": \"\",\n \"selectedPath\": {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n },\n \"paymentInstruction\": {\n \"kind\": \"\",\n \"payload\": {\n \"destination\": {\n \"kind\": \"\",\n \"recipientAddress\": \"\"\n }\n }\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n }\n }\n}", + "body": "{\n \"status\": \"\",\n \"intent\": {\n \"id\": \"\",\n \"schema\": \"\",\n \"status\": \"\",\n \"modality\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifierHash\": \"\"\n },\n \"selectedRoute\": {\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"expiresAt\": \"\",\n \"singleUse\": \"\",\n \"paymentInstruction\": {\n \"type\": \"\",\n \"provider\": \"\",\n \"payload\": {\n \"providerIntentId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"destination\": {\n \"kind\": \"\",\n \"recipientAddress\": \"\"\n },\n \"amount\": \"\",\n \"reference\": \"\",\n \"expiresAt\": \"\"\n }\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, From 4ff295343c57ab9e66bb05d0299b230316854f1c Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 02:15:03 -0400 Subject: [PATCH 06/21] fix: keep cubid internals out of public api --- agent-context/session-log/main.md | 13 +++++++- agent-context/todo.md | 6 ++-- api/openapi.yaml | 33 ++++++++++--------- api/postman_collection.json | 10 +++--- docs/engineering/mvp-api-contracts.md | 26 +++++++-------- .../protocol-and-sdk-architecture.md | 20 +++++------ docs/integration/paying-dapps.md | 4 +-- examples/paying-dapp-basic/README.md | 2 +- .../schemas/mypaytag-intent.schema.json | 2 +- .../schemas/notification-event.schema.json | 2 +- .../provider-callback-request.schema.json | 6 ++-- .../schemas/resolve-request.schema.json | 2 +- .../schemas/resolve-response.schema.json | 2 +- .../route-registration-request.schema.json | 6 ++-- packages/protocol/src/fixtures.ts | 18 +++++----- packages/protocol/src/generated/schemas.ts | 20 +++++------ .../src/generated/types/mypaytag-intent.ts | 2 +- .../src/generated/types/notification-event.ts | 2 +- .../types/provider-callback-request.ts | 4 +-- .../src/generated/types/resolve-request.ts | 2 +- .../src/generated/types/resolve-response.ts | 2 +- .../types/route-registration-request.ts | 4 +-- .../src/provider-conformance.test.ts | 6 ++-- packages/sdk/src/index.ts | 2 +- packages/sdk/src/sdk.test.ts | 6 ++-- packages/testing/src/index.ts | 14 ++++---- packages/testing/src/testing.test.ts | 10 +++--- 27 files changed, 119 insertions(+), 107 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 88208cc..cc43b2c 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -174,7 +174,7 @@ follow-ups: Continue SDK Sprint 4 with PayToDapp provider conformance tests for agent: Codex branch: main head: 90432f6 -summary: Completed GPTS-S4-T2 by adding PayToDapp provider conformance tests for valid callback handling, Cubid alias-only callbacks, auth freshness and replay expectations, provider error status rejection, malformed provider payload rejection, callback/response matching, and MyPayTag intent-envelope containment. +summary: Completed GPTS-S4-T2 by adding PayToDapp provider conformance tests for valid callback handling, MyPayTag paytag reference-only callbacks, auth freshness and replay expectations, provider error status rejection, malformed provider payload rejection, callback/response matching, and MyPayTag intent-envelope containment. validation: Ran pnpm --filter @mypaytag/provider-sdk test, typecheck, and build; provider conformance added seven passing cases and the filtered run covered 32 passing workspace tests. follow-ups: Continue SDK Sprint 4 with package build and publish workflow scaffolding. @@ -270,3 +270,14 @@ todo: mypaytag-sdk:GPTS-S6-T2 summary: Updated OpenAPI resolved-intent and provider-response components to match the canonical MyPayTag intent schema, including required envelope fields, selected route, references, provider_json metadata, and full provider payload requirements. validation: Ran pnpm api:postman and pnpm api:validate; Postman was regenerated from api/openapi.yaml and Redocly lint passed without warnings. follow-ups: Continue Sprint 6 by checking the public SDK contract for Cubid-internal leakage and direct Cubid probing examples. + +--- + +## 2026-06-28-gpts-s6-t3 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T3 +summary: Reframed public request, callback, intent, notification, fixture, OpenAPI, docs, and testing surfaces around MyPayTag paytag references and generic authorization tokens instead of Cubid verified-stamp fields, Cubid aliases, or Cubid consent-token fields. +validation: Ran pnpm generate, pnpm api:postman, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/provider-sdk test, pnpm --filter @mypaytag/testing test, and pnpm api:validate; focused tests passed with 44 workspace cases and OpenAPI lint passed. +follow-ups: Continue Sprint 6 by labeling execution quote helpers and solver surfaces as future/non-MVP extensions. diff --git a/agent-context/todo.md b/agent-context/todo.md index cd2c7cc..e420f47 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -408,9 +408,9 @@ Acceptance notes: ### GPTS-S6-T3 Keep Cubid Internals Out Of MyPayTag Public APIs -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t3 Depends on: cubid-sdk-v2:S18.1 Ensure the SDK contract treats Cubid as an external identity and consent dependency only. diff --git a/api/openapi.yaml b/api/openapi.yaml index 4554eaf..2c9e881 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -68,7 +68,7 @@ paths: operationId: resolvePayment summary: Resolve a paytag into a payment intent or safe next action. description: | - Sending apps submit a Cubid verified-stamp recipient, supported payment + Sending apps submit a paytag or opaque paytag reference, supported payment paths, amount, purpose, and reconciliation reference. The response is a resolved one-time MyPayTag intent, a hosted action, or a public safe status. @@ -83,8 +83,8 @@ paths: summary: Base USDC payout value: recipient: - identifierType: verified_stamp - identifier: email:recipient@example.com + identifierType: paytag + identifier: abd123@cubid.mypaytag supportedPaths: - chain: base network: mainnet @@ -113,7 +113,7 @@ paths: status: ready modality: provider_intent recipient: - identifierType: verified_stamp + identifierType: paytag identifierHash: sha256:example selectedRoute: payToDappId: example-wallet @@ -180,14 +180,14 @@ paths: summary: Register Base USDC support value: recipient: - identifierType: verified_stamp - identifier: email:recipient@example.com + identifierType: paytag + identifier: abd123@cubid.mypaytag payToDappId: example-wallet supportedRoutes: - chain: base network: mainnet asset: USDC - consentToken: cubid_consent_token + authorizationToken: mpt_auth_123 responses: "200": description: Route registration outcome. @@ -479,24 +479,25 @@ components: properties: identifierType: type: string - const: verified_stamp + const: paytag identifier: type: string examples: - - email:recipient@example.com + - abd123@cubid.mypaytag + - "+1234569999@phone.cubid.mypaytag" RecipientAlias: type: object additionalProperties: false required: - identifierType - - identifierAlias + - paytagReference properties: identifierType: type: string - const: verified_stamp - identifierAlias: + const: paytag + paytagReference: type: string - description: Cubid alias or masked server-side recipient reference. + description: MyPayTag server-side paytag reference for the selected provider callback. RoutePath: type: object additionalProperties: false @@ -675,7 +676,7 @@ components: properties: identifierType: type: string - const: verified_stamp + const: paytag identifierHash: type: string minLength: 1 @@ -789,7 +790,7 @@ components: - recipient - payToDappId - supportedRoutes - - consentToken + - authorizationToken properties: recipient: $ref: "#/components/schemas/Recipient" @@ -800,7 +801,7 @@ components: minItems: 1 items: $ref: "#/components/schemas/RoutePath" - consentToken: + authorizationToken: type: string RouteRegistrationResponse: oneOf: diff --git a/api/postman_collection.json b/api/postman_collection.json index 51db6b5..1e908e1 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -173,7 +173,7 @@ "request": { "name": "Resolve a paytag into a payment intent or safe next action.", "description": { - "content": "Sending apps submit a Cubid verified-stamp recipient, supported payment\npaths, amount, purpose, and reconciliation reference. The response is a\nresolved one-time MyPayTag intent, a hosted action, or a public safe\nstatus.\n", + "content": "Sending apps submit a paytag or opaque paytag reference, supported payment\npaths, amount, purpose, and reconciliation reference. The response is a\nresolved one-time MyPayTag intent, a hosted action, or a public safe\nstatus.\n", "type": "text/plain" }, "url": { @@ -418,7 +418,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"consentToken\": \"\"\n}", + "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"authorizationToken\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -463,7 +463,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"consentToken\": \"\"\n}", + "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"authorizationToken\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -518,7 +518,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"consentToken\": \"\"\n}", + "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"authorizationToken\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -573,7 +573,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"consentToken\": \"\"\n}", + "raw": "{\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifier\": \"\"\n },\n \"payToDappId\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"authorizationToken\": \"\"\n}", "options": { "raw": { "headerFamily": "json", diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index 6cc4f9f..176d5dc 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -16,16 +16,16 @@ The contract intentionally covers only verified-stamp identity, Modality B route ```json { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" } ``` Rules: -- `identifierType` is `verified_stamp` for MVP. +- `identifierType` is `paytag` for MVP public requests. - `identifier` is provided by the integrator request and is validated server-side through Cubid. -- Responses must use hashes, masked displays, or Cubid aliases instead of exposing raw identifiers where possible. +- Responses must use hashes, masked displays, or MyPayTag paytag references instead of exposing raw identifiers where possible. ### Route Or Path @@ -102,8 +102,8 @@ Create/update request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "payToDappId": "smartrust-wallet", "supportedRoutes": [ @@ -113,7 +113,7 @@ Create/update request: "asset": "USDC" } ], - "consentToken": "cubid_consent_token" + "authorizationToken": "mpt_auth_123" } ``` @@ -167,8 +167,8 @@ Request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "supportedPaths": [ { @@ -237,8 +237,8 @@ Request: { "resolverRequestId": "mpt_req_123", "recipient": { - "identifierType": "verified_stamp", - "identifierAlias": "cubid_stamp_alias_abc" + "identifierType": "paytag", + "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", "selectedPath": { @@ -301,7 +301,7 @@ Rules: "status": "ready", "modality": "provider_intent", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "identifierHash": "sha256:..." }, "selectedRoute": { @@ -423,7 +423,7 @@ Common payload fields: "eventType": "payment_intent_created", "schema": "mypaytag.notification.v1", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "maskedDisplay": "n***@example.com" }, "amount": { diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index ec9b601..777afb0 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -19,7 +19,7 @@ Planned packages: - `@mypaytag/protocol`: schemas, TypeScript types, enums, OpenAPI source, error/status codes, and test vectors. - `@mypaytag/sdk`: client helpers for PayingDapp and resolver integrations. - `@mypaytag/provider-sdk`: helpers for PayToDapp route registration and Modality B intent callbacks. -- `@mypaytag/testing`: mock resolver, mock Cubid validator, mock PayToDapp, fixtures, and conformance test vectors. +- `@mypaytag/testing`: mock resolver, mock paytag validator, mock PayToDapp, fixtures, and conformance test vectors. - Notification-related public event types belong in `@mypaytag/protocol`; delivery uses Cubid comms rather than a MyPayTag notification provider SDK. The public protocol package should be importable by both integrators and the private resolver backend. @@ -28,7 +28,7 @@ The public protocol package should be importable by both integrators and the pri The MVP protocol supports: -- verified Cubid stamps as paytag identifiers, +- paytags as MyPayTag payment identifiers backed by external identity and consent providers, - PayToDapp route registration using supported routes only, - PayingDapp resolution requests, - resolver-to-PayToDapp Modality B payment-intent callback schemas, @@ -91,8 +91,8 @@ Route registration request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "payToDappId": "smartrust-wallet", "supportedRoutes": [ @@ -102,7 +102,7 @@ Route registration request: "asset": "USDC" } ], - "consentToken": "cubid_consent_token" + "authorizationToken": "mpt_auth_123" } ``` @@ -119,8 +119,8 @@ Resolve request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "supportedPaths": [ { @@ -151,8 +151,8 @@ Callback request: { "resolverRequestId": "mpt_req_123", "recipient": { - "identifierType": "verified_stamp", - "identifierAlias": "cubid_stamp_alias_abc" + "identifierType": "paytag", + "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", "selectedPath": { @@ -186,7 +186,7 @@ Resolved response shape: "status": "ready", "modality": "provider_intent", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "identifierHash": "sha256:..." }, "selectedRoute": { diff --git a/docs/integration/paying-dapps.md b/docs/integration/paying-dapps.md index fe0c5b4..8f1448c 100644 --- a/docs/integration/paying-dapps.md +++ b/docs/integration/paying-dapps.md @@ -1,6 +1,6 @@ # PayingDapp Integration -PayingDapps ask MyPayTag to resolve a Cubid verified-stamp recipient into a +PayingDapps ask MyPayTag to resolve a paytag recipient into a one-time MyPayTag JSON intent. ## Use The SDK @@ -20,7 +20,7 @@ Runnable example: `examples/paying-dapp-basic`. PayingDapps provide: -- a Cubid verified-stamp recipient, +- a paytag recipient, - supported paths containing `chain`, `network`, and `asset`, - a one-time amount, - a purpose, diff --git a/examples/paying-dapp-basic/README.md b/examples/paying-dapp-basic/README.md index 00038a6..82cd8a4 100644 --- a/examples/paying-dapp-basic/README.md +++ b/examples/paying-dapp-basic/README.md @@ -1,6 +1,6 @@ # PayingDapp Basic Example -Runnable example for a PayingDapp resolving a Cubid verified-stamp recipient +Runnable example for a PayingDapp resolving a paytag recipient into a MyPayTag JSON intent. Run from the repository root after building packages: diff --git a/packages/protocol/schemas/mypaytag-intent.schema.json b/packages/protocol/schemas/mypaytag-intent.schema.json index c2061b8..e114115 100644 --- a/packages/protocol/schemas/mypaytag-intent.schema.json +++ b/packages/protocol/schemas/mypaytag-intent.schema.json @@ -15,7 +15,7 @@ "additionalProperties": false, "required": ["identifierType", "identifierHash"], "properties": { - "identifierType": { "const": "verified_stamp" }, + "identifierType": { "const": "paytag" }, "identifierHash": { "type": "string", "minLength": 1 } } }, diff --git a/packages/protocol/schemas/notification-event.schema.json b/packages/protocol/schemas/notification-event.schema.json index 03ee453..3dc09b7 100644 --- a/packages/protocol/schemas/notification-event.schema.json +++ b/packages/protocol/schemas/notification-event.schema.json @@ -13,7 +13,7 @@ "additionalProperties": false, "required": ["identifierType", "maskedDisplay"], "properties": { - "identifierType": { "const": "verified_stamp" }, + "identifierType": { "const": "paytag" }, "maskedDisplay": { "type": "string", "minLength": 1 } } }, diff --git a/packages/protocol/schemas/provider-callback-request.schema.json b/packages/protocol/schemas/provider-callback-request.schema.json index 172cecb..2a5a6d0 100644 --- a/packages/protocol/schemas/provider-callback-request.schema.json +++ b/packages/protocol/schemas/provider-callback-request.schema.json @@ -10,10 +10,10 @@ "recipient": { "type": "object", "additionalProperties": false, - "required": ["identifierType", "identifierAlias"], + "required": ["identifierType", "paytagReference"], "properties": { - "identifierType": { "const": "verified_stamp" }, - "identifierAlias": { "type": "string", "minLength": 1 } + "identifierType": { "const": "paytag" }, + "paytagReference": { "type": "string", "minLength": 1 } } }, "payingDappId": { "type": "string", "minLength": 1 }, diff --git a/packages/protocol/schemas/resolve-request.schema.json b/packages/protocol/schemas/resolve-request.schema.json index 83da12f..94f5714 100644 --- a/packages/protocol/schemas/resolve-request.schema.json +++ b/packages/protocol/schemas/resolve-request.schema.json @@ -23,7 +23,7 @@ "additionalProperties": false, "required": ["identifierType", "identifier"], "properties": { - "identifierType": { "const": "verified_stamp" }, + "identifierType": { "const": "paytag" }, "identifier": { "type": "string", "minLength": 1 } } }, diff --git a/packages/protocol/schemas/resolve-response.schema.json b/packages/protocol/schemas/resolve-response.schema.json index ae0a06f..be3193d 100644 --- a/packages/protocol/schemas/resolve-response.schema.json +++ b/packages/protocol/schemas/resolve-response.schema.json @@ -67,7 +67,7 @@ "additionalProperties": false, "required": ["identifierType", "identifierHash"], "properties": { - "identifierType": { "const": "verified_stamp" }, + "identifierType": { "const": "paytag" }, "identifierHash": { "type": "string", "minLength": 1 } } }, diff --git a/packages/protocol/schemas/route-registration-request.schema.json b/packages/protocol/schemas/route-registration-request.schema.json index cb6f9a1..d6b5d61 100644 --- a/packages/protocol/schemas/route-registration-request.schema.json +++ b/packages/protocol/schemas/route-registration-request.schema.json @@ -4,7 +4,7 @@ "title": "RouteRegistrationRequest", "type": "object", "additionalProperties": false, - "required": ["recipient", "payToDappId", "supportedRoutes", "consentToken"], + "required": ["recipient", "payToDappId", "supportedRoutes", "authorizationToken"], "properties": { "recipient": { "$ref": "#/$defs/recipient" }, "payToDappId": { "type": "string", "minLength": 1 }, @@ -13,7 +13,7 @@ "minItems": 1, "items": { "$ref": "#/$defs/route" } }, - "consentToken": { "type": "string", "minLength": 1 } + "authorizationToken": { "type": "string", "minLength": 1 } }, "not": { "anyOf": [ @@ -31,7 +31,7 @@ "additionalProperties": false, "required": ["identifierType", "identifier"], "properties": { - "identifierType": { "const": "verified_stamp" }, + "identifierType": { "const": "paytag" }, "identifier": { "type": "string", "minLength": 1 } } }, diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 5ece855..f50f62f 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -12,8 +12,8 @@ import type { export const validRouteRegistrationRequest: RouteRegistrationRequest = { recipient: { - identifierType: "verified_stamp", - identifier: "email:noak@example.com", + identifierType: "paytag", + identifier: "abd123@cubid.mypaytag", }, payToDappId: "smartrust-wallet", supportedRoutes: [ @@ -23,7 +23,7 @@ export const validRouteRegistrationRequest: RouteRegistrationRequest = { asset: "USDC", }, ], - consentToken: "cubid_consent_token", + authorizationToken: "mpt_auth_123", }; export const validRouteRegistrationResponse: RouteRegistrationResponse = { @@ -42,8 +42,8 @@ export const validRouteRegistrationResponse: RouteRegistrationResponse = { export const validResolveRequest: ResolveRequest = { recipient: { - identifierType: "verified_stamp", - identifier: "email:noak@example.com", + identifierType: "paytag", + identifier: "abd123@cubid.mypaytag", }, supportedPaths: [ { @@ -64,8 +64,8 @@ export const validResolveRequest: ResolveRequest = { export const validProviderCallbackRequest: ProviderCallbackRequest = { resolverRequestId: "mpt_req_123", recipient: { - identifierType: "verified_stamp", - identifierAlias: "cubid_stamp_alias_abc", + identifierType: "paytag", + paytagReference: "paytag_ref_abc", }, payingDappId: "chaincrew", selectedPath: { @@ -109,7 +109,7 @@ export const validMyPayTagIntent: MyPayTagIntent = { status: "ready", modality: "provider_intent", recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", identifierHash: "sha256:example", }, selectedRoute: { @@ -154,7 +154,7 @@ export const validNotificationEvent: NotificationEvent = { eventType: "payment_intent_created", schema: "mypaytag.notification.v1", recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", maskedDisplay: "n***@example.com", }, amount: { diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index a2b6429..3203742 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -43,7 +43,7 @@ export const MypaytagIntentSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifierHash": { "type": "string", @@ -263,7 +263,7 @@ export const NotificationEventSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "maskedDisplay": { "type": "string", @@ -352,13 +352,13 @@ export const ProviderCallbackRequestSchema = { "additionalProperties": false, "required": [ "identifierType", - "identifierAlias" + "paytagReference" ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, - "identifierAlias": { + "paytagReference": { "type": "string", "minLength": 1 } @@ -603,7 +603,7 @@ export const ResolveRequestSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifier": { "type": "string", @@ -777,7 +777,7 @@ export const ResolveResponseSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifierHash": { "type": "string", @@ -1111,7 +1111,7 @@ export const RouteRegistrationRequestSchema = { "recipient", "payToDappId", "supportedRoutes", - "consentToken" + "authorizationToken" ], "properties": { "recipient": { @@ -1128,7 +1128,7 @@ export const RouteRegistrationRequestSchema = { "$ref": "#/$defs/route" } }, - "consentToken": { + "authorizationToken": { "type": "string", "minLength": 1 } @@ -1177,7 +1177,7 @@ export const RouteRegistrationRequestSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifier": { "type": "string", diff --git a/packages/protocol/src/generated/types/mypaytag-intent.ts b/packages/protocol/src/generated/types/mypaytag-intent.ts index 66a782d..2943cf6 100644 --- a/packages/protocol/src/generated/types/mypaytag-intent.ts +++ b/packages/protocol/src/generated/types/mypaytag-intent.ts @@ -7,7 +7,7 @@ export interface MyPayTagIntent { status: "ready"; modality: "provider_intent"; recipient: { - identifierType: "verified_stamp"; + identifierType: "paytag"; identifierHash: string; }; selectedRoute: { diff --git a/packages/protocol/src/generated/types/notification-event.ts b/packages/protocol/src/generated/types/notification-event.ts index a4e2cc2..82a8f82 100644 --- a/packages/protocol/src/generated/types/notification-event.ts +++ b/packages/protocol/src/generated/types/notification-event.ts @@ -5,7 +5,7 @@ export interface NotificationEvent { eventType: "payment_intent_created"; schema: "mypaytag.notification.v1"; recipient: { - identifierType: "verified_stamp"; + identifierType: "paytag"; maskedDisplay: string; }; amount: { diff --git a/packages/protocol/src/generated/types/provider-callback-request.ts b/packages/protocol/src/generated/types/provider-callback-request.ts index 70866dc..3e52cb1 100644 --- a/packages/protocol/src/generated/types/provider-callback-request.ts +++ b/packages/protocol/src/generated/types/provider-callback-request.ts @@ -4,8 +4,8 @@ export interface ProviderCallbackRequest { resolverRequestId: string; recipient: { - identifierType: "verified_stamp"; - identifierAlias: string; + identifierType: "paytag"; + paytagReference: string; }; payingDappId: string; selectedPath: Path; diff --git a/packages/protocol/src/generated/types/resolve-request.ts b/packages/protocol/src/generated/types/resolve-request.ts index 8dea061..976a33f 100644 --- a/packages/protocol/src/generated/types/resolve-request.ts +++ b/packages/protocol/src/generated/types/resolve-request.ts @@ -13,7 +13,7 @@ export interface ResolveRequest { payingDappReference: string; } export interface Recipient { - identifierType: "verified_stamp"; + identifierType: "paytag"; identifier: string; } export interface Path { diff --git a/packages/protocol/src/generated/types/resolve-response.ts b/packages/protocol/src/generated/types/resolve-response.ts index b1506a0..480ee50 100644 --- a/packages/protocol/src/generated/types/resolve-response.ts +++ b/packages/protocol/src/generated/types/resolve-response.ts @@ -29,7 +29,7 @@ export interface Intent { status: "ready"; modality: "provider_intent"; recipient: { - identifierType: "verified_stamp"; + identifierType: "paytag"; identifierHash: string; }; selectedRoute: SelectedRoute; diff --git a/packages/protocol/src/generated/types/route-registration-request.ts b/packages/protocol/src/generated/types/route-registration-request.ts index 42f4695..0622a3f 100644 --- a/packages/protocol/src/generated/types/route-registration-request.ts +++ b/packages/protocol/src/generated/types/route-registration-request.ts @@ -8,10 +8,10 @@ export interface RouteRegistrationRequest { * @minItems 1 */ supportedRoutes: [Route, ...Route[]]; - consentToken: string; + authorizationToken: string; } export interface Recipient { - identifierType: "verified_stamp"; + identifierType: "paytag"; identifier: string; } export interface Route { diff --git a/packages/provider-sdk/src/provider-conformance.test.ts b/packages/provider-sdk/src/provider-conformance.test.ts index 0f608ff..a59c3da 100644 --- a/packages/provider-sdk/src/provider-conformance.test.ts +++ b/packages/provider-sdk/src/provider-conformance.test.ts @@ -50,13 +50,13 @@ describe("PayToDapp provider conformance", () => { expect(assertProviderResponseMatchesCallback(callback, response)).toEqual(response); }); - it("requires callbacks to use Cubid aliases instead of raw identifiers", () => { + it("requires callbacks to use MyPayTag paytag references instead of raw identifiers", () => { expect(() => parseProviderCallbackRequest({ ...validProviderCallbackRequest, recipient: { - identifierType: "verified_stamp", - identifier: "email:noak@example.com", + identifierType: "paytag", + identifier: "abd123@cubid.mypaytag", }, }), ).toThrow(); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 4c39777..a176f2d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -118,7 +118,7 @@ export function buildPayorAppResolveRequest(input: PayorAppResolveRequestInput): const request = buildResolveRequest({ recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", identifier: requireNonEmpty(input.recipientIdentifier, "recipientIdentifier"), }, supportedPaths: [firstPath, ...remainingPaths], diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 8ed117a..229382c 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -42,7 +42,7 @@ describe("@mypaytag/sdk", () => { }); const result = buildPayorAppResolveRequest({ - recipientIdentifier: "email:noak@example.com", + recipientIdentifier: "abd123@cubid.mypaytag", supportedPaths: [supportedPath], amount, purpose: "wallet_send", @@ -70,7 +70,7 @@ describe("@mypaytag/sdk", () => { it("defaults payor-app helper exactness to exact receive", () => { const result = buildPayorAppResolveRequest({ - recipientIdentifier: "email:noak@example.com", + recipientIdentifier: "abd123@cubid.mypaytag", supportedPaths: validResolveRequest.supportedPaths, amount: validResolveRequest.amount, purpose: validResolveRequest.purpose, @@ -83,7 +83,7 @@ describe("@mypaytag/sdk", () => { it("rejects empty payor-app request fields before schema validation", () => { expect(() => buildPayorAppResolveRequest({ - recipientIdentifier: "email:noak@example.com", + recipientIdentifier: "abd123@cubid.mypaytag", supportedPaths: [], amount: validResolveRequest.amount, purpose: validResolveRequest.purpose, diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 3c9c1b8..1f90f76 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -172,22 +172,22 @@ export const myPayTagFixtures: MyPayTagFixtures = { intent: validMyPayTagIntent, }; -export interface MockCubidValidator { - validateStamp(identifier: string): Promise<{ - alias: string; +export interface MockPaytagValidator { + validatePaytag(identifier: string): Promise<{ + paytagReference: string; hash: string; maskedDisplay: string; valid: boolean; }>; } -export function createMockCubidValidator(): MockCubidValidator { +export function createMockPaytagValidator(): MockPaytagValidator { return { - async validateStamp(identifier: string) { + async validatePaytag(identifier: string) { return { - alias: "cubid_stamp_alias_abc", + paytagReference: "paytag_ref_abc", hash: `sha256:${identifier}`, - maskedDisplay: "n***@example.com", + maskedDisplay: "a***@cubid.mypaytag", valid: identifier.length > 0, }; }, diff --git a/packages/testing/src/testing.test.ts b/packages/testing/src/testing.test.ts index 13e789c..8ff49da 100644 --- a/packages/testing/src/testing.test.ts +++ b/packages/testing/src/testing.test.ts @@ -5,7 +5,7 @@ import { } from "@mypaytag/sdk"; import { - createMockCubidValidator, + createMockPaytagValidator, createMockExecutionQuoteProvider, createMockPayToDapp, createMockResolver, @@ -40,11 +40,11 @@ describe("@mypaytag/testing", () => { ); }); - it("provides a mock Cubid validator", async () => { - const validator = createMockCubidValidator(); + it("provides a mock paytag validator", async () => { + const validator = createMockPaytagValidator(); - await expect(validator.validateStamp("email:noak@example.com")).resolves.toMatchObject({ - alias: "cubid_stamp_alias_abc", + await expect(validator.validatePaytag("abd123@cubid.mypaytag")).resolves.toMatchObject({ + paytagReference: "paytag_ref_abc", valid: true, }); }); From 12d9f614aca92d13d2cb1a4a81e07608294fa493 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 02:16:12 -0400 Subject: [PATCH 07/21] docs: mark execution helpers as future extensions --- agent-context/session-log/main.md | 11 +++++++++++ agent-context/todo.md | 6 +++--- docs/engineering/mvp-api-contracts.md | 6 +++--- .../engineering/protocol-and-sdk-architecture.md | 16 ++++++++-------- docs/integration/paying-dapps.md | 6 ++---- packages/sdk/src/index.ts | 13 +++++++++++++ 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index cc43b2c..a13f330 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -281,3 +281,14 @@ todo: mypaytag-sdk:GPTS-S6-T3 summary: Reframed public request, callback, intent, notification, fixture, OpenAPI, docs, and testing surfaces around MyPayTag paytag references and generic authorization tokens instead of Cubid verified-stamp fields, Cubid aliases, or Cubid consent-token fields. validation: Ran pnpm generate, pnpm api:postman, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/provider-sdk test, pnpm --filter @mypaytag/testing test, and pnpm api:validate; focused tests passed with 44 workspace cases and OpenAPI lint passed. follow-ups: Continue Sprint 6 by labeling execution quote helpers and solver surfaces as future/non-MVP extensions. + +--- + +## 2026-06-28-gpts-s6-t4 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T4 +summary: Labeled crypto-native solver ids, execution quote helpers, route quote previews, bridge/swap language, and fanout behavior as future/non-MVP extensions while keeping core resolve, route registration, provider callback, hosted route selection, and intent helpers as the MVP path. +validation: Ran pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, and pnpm scan:public-boundary; SDK typecheck passed, focused tests passed with 44 workspace cases, and the public-boundary scan passed. +follow-ups: Continue Sprint 6 by adding paytag availability, opaque paytag, raw-explicit paytag, and negative-disclosure fixtures. diff --git a/agent-context/todo.md b/agent-context/todo.md index e420f47..d8321e6 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -424,9 +424,9 @@ Acceptance notes: ### GPTS-S6-T4 Split MVP Helpers From Future Execution Helpers -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t4 Depends on: mypaytag-sdk:GPTS-S5-T1, mypaytag-sdk:GPTS-S5-T2 Preserve future execution helper work while making it clearly non-MVP. diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index 176d5dc..d945b35 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -370,9 +370,9 @@ Matching rules: - Top-level `recipientAddress`, `address`, and `account` fields are rejected so integrators do not confuse route registration with provider-selected destinations. - Providers may add extension fields, but SDK validators must preserve unknown extension fields without allowing them to replace required keys. -## Route Quote Preview +## Future Extension: Route Quote Preview -Payor-app route option and intent option flows may return quote previews before a final intent is selected. A quote preview describes one executable candidate without revealing recipient wallet inventory or unrelated PayToDapps. +Payor-app route option and intent option flows are future extensions, not requirements for the MVP resolve path. They may return quote previews before a final intent is selected. A quote preview describes one executable candidate without revealing recipient wallet inventory or unrelated PayToDapps. ```json { @@ -409,7 +409,7 @@ Rules: - `method` is one of `direct_transfer`, `provider_exchange`, `provider_intent`, `bridge`, or `cross_chain_intent`. - Fee `source` is one of `payor_app`, `provider`, or `resolver`. -- MVP quote fees are charged to the sender. +- Modeled quote fees are charged to the sender. - Quote previews must not include route preferences, unrelated PayToDapps, wallet addresses, or wallet graph details. ## Cubid Comms Notification Events diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 777afb0..99f1e66 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -46,9 +46,9 @@ The MVP protocol does not support: - public profile or directory APIs, - notification inbox APIs, marketing notification APIs, or non-Cubid notification provider APIs. -## Crypto-Native Execution Solvers +## Future Extension: Crypto-Native Execution Solvers -Future payor-app helpers should support crypto-native execution adapters alongside the selected PayToDapp intent flow. These adapters consume a resolved receive requirement and produce quotes or transaction requests; they do not decide which PayToDapp the recipient prefers. +Crypto-native execution adapters are a future extension, not part of the core MVP resolve flow. These adapters consume a resolved receive requirement and produce quotes or transaction requests; they do not decide which PayToDapp the recipient prefers. Short list for SDK adapter support: @@ -61,17 +61,17 @@ Short list for SDK adapter support: | Across | Fast bridge-focused EVM/L2 stablecoin transfers where supported. | | LayerZero Value Transfer API / Stargate | Cross-chain token transfer for OFT, LayerZero ecosystem assets, and routes where Stargate coverage is strong. | -`@mypaytag/sdk` should expose a provider interface for these quote sources. When a payor-app passes a preferred solver id, the SDK asks only that quote provider. When no preferred solver id is selected, the SDK fans out quote requests to every configured quote provider and returns the successful quotes for app-side display or resolver-side selection. +`@mypaytag/sdk` may expose a provider interface for these quote sources as non-MVP extension helpers. When a payor-app passes a preferred solver id, the SDK asks only that quote provider. When no preferred solver id is selected, the SDK fans out quote requests to every configured quote provider and returns the successful quotes for app-side display or future extension selection. -## Route Query And Quote Contracts +## Future Extension: Route Query And Quote Contracts -Generic payor-app flows distinguish three stages: +Generic payor-app extension flows can distinguish three stages: - route availability query: the app asks what safe receive options can be considered for a paytag and sender-supported path set; -- payment intent option query: the app includes amount and exactness context so the resolver can return executable or selectable quote previews; +- payment intent option query: the app includes amount and exactness context so MyPayTag or a future extension can return executable or selectable quote previews; - final resolved intent: the app receives one `mypaytag.intent.v1` instruction for execution or handoff. -Route quote previews are public contract objects with method, send amount, receive amount, fee rows, expiry, and resolver reference. Fee rows identify whether the source is the payor app, provider, or resolver, and all MVP fees are charged to the sender. Quote previews must not include recipient wallet inventory, route preferences, unrelated PayToDapps, or wallet graph details. +Route quote previews are public future-extension contract objects with method, send amount, receive amount, fee rows, expiry, and resolver reference. Fee rows identify whether the source is the payor app, provider, or resolver, and all modeled fees are charged to the sender. Quote previews must not include recipient wallet inventory, route preferences, unrelated PayToDapps, or wallet graph details. ## Public API Shapes @@ -262,7 +262,7 @@ Notification payloads must use masked display values, public references, and act `@mypaytag/sdk` should provide: - request builders for PayingDapp resolution, -- quote helpers for crypto-native execution solvers, +- non-MVP quote helpers for crypto-native execution solver extensions, - schema validation for responses, - status narrowing helpers, - notification event type guards for Cubid comms payloads, diff --git a/docs/integration/paying-dapps.md b/docs/integration/paying-dapps.md index 8f1448c..a03e7ef 100644 --- a/docs/integration/paying-dapps.md +++ b/docs/integration/paying-dapps.md @@ -6,8 +6,6 @@ one-time MyPayTag JSON intent. ## Use The SDK - Build resolve requests with `buildResolveRequest`. -- Request crypto-native execution quotes with `requestExecutionQuotes` when your - app needs solver/router options for a resolved receive requirement. - Parse resolver responses with `parseResolveResponse`. - Branch with `isResolved`, `isActionRequired`, `isRetryable`, and `isInvalidForRetry`. @@ -48,9 +46,9 @@ MVP payment instructions use `provider_json` with a typed provider destination: Do not infer reusable wallet details from this destination. It belongs to the selected one-time provider intent. -## Crypto-Native Execution Quotes +## Future Extension: Crypto-Native Execution Quotes -When a PayingDapp supports execution adapters, configure quote providers for the +The MVP resolve flow does not require execution quote helpers. When a PayingDapp supports future execution adapters, configure quote providers for the solver/router surfaces the app can use. The initial MyPayTag SDK solver ids are: diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index a176f2d..480990f 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -35,6 +35,11 @@ export interface ExactnessAwareResolveRequest { export type ActionRequiredStatus = "user_action_required"; export type ActionRequiredResponse = Extract; +/** + * Future extension solver ids. These are not required for the MVP MyPayTag + * resolve flow, route registration flow, provider callback flow, or hosted + * route-selection flow. + */ export const cryptoNativeExecutionSolvers = [ "near_intents_1click", "lifi", @@ -46,6 +51,9 @@ export const cryptoNativeExecutionSolvers = [ export type CryptoNativeExecutionSolverId = (typeof cryptoNativeExecutionSolvers)[number]; +/** + * Future extension quote request consumed by app-configured execution adapters. + */ export interface ExecutionQuoteRequest { amount: { value: string; @@ -184,6 +192,11 @@ function requireNonEmpty(value: string, fieldName: string): string { return trimmed; } +/** + * Requests non-MVP execution quotes from caller-provided providers. Core + * MyPayTag MVP integrations can resolve paytags and handle provider callbacks + * without calling this helper. + */ export async function requestExecutionQuotes({ preferredSolverId, providers, From 5b1216793ee857af683db181e9e8fb0774381d88 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 02:17:15 -0400 Subject: [PATCH 08/21] test: add paytag privacy fixtures --- agent-context/session-log/main.md | 11 +++ agent-context/todo.md | 6 +- .../integration/notifications-and-fixtures.md | 5 ++ packages/testing/src/index.ts | 68 +++++++++++++++++++ packages/testing/src/testing.test.ts | 20 ++++++ 5 files changed, 107 insertions(+), 3 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index a13f330..fd71fd7 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -292,3 +292,14 @@ todo: mypaytag-sdk:GPTS-S6-T4 summary: Labeled crypto-native solver ids, execution quote helpers, route quote previews, bridge/swap language, and fanout behavior as future/non-MVP extensions while keeping core resolve, route registration, provider callback, hosted route selection, and intent helpers as the MVP path. validation: Ran pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, and pnpm scan:public-boundary; SDK typecheck passed, focused tests passed with 44 workspace cases, and the public-boundary scan passed. follow-ups: Continue Sprint 6 by adding paytag availability, opaque paytag, raw-explicit paytag, and negative-disclosure fixtures. + +--- + +## 2026-06-28-gpts-s6-t5 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T5 +summary: Added reusable testing fixtures for opaque default paytags, explicit raw-stamp paytags, availability/unavailable/reserved issuance checks, and negative-disclosure responses for no_route, authorization_required, and user_action_required. +validation: Ran pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm --filter @mypaytag/protocol test; typecheck passed and focused tests passed with 44 workspace cases. +follow-ups: Complete Sprint 6 with contract tests for OpenAPI/schema parity, forbidden route registration fields, provider payload requirements, and staged smoke documentation. diff --git a/agent-context/todo.md b/agent-context/todo.md index d8321e6..c41ec06 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -440,9 +440,9 @@ Acceptance notes: ### GPTS-S6-T5 Add Paytag Availability And Privacy Fixtures -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t5 Depends on: mypaytag-sdk:GPTS-S2-T4 Extend fixtures for opaque/default paytags, explicit raw paytags, uniqueness checks, and safe negative states. diff --git a/docs/integration/notifications-and-fixtures.md b/docs/integration/notifications-and-fixtures.md index 6760e22..7ce5db2 100644 --- a/docs/integration/notifications-and-fixtures.md +++ b/docs/integration/notifications-and-fixtures.md @@ -33,5 +33,10 @@ Use `@mypaytag/testing` for reusable fixtures and mocks: - valid route registration, - forbidden address registration, - resolved/no-route/route-selection/provider-failure resolver responses, +- opaque default paytag examples such as `abd123@cubid.mypaytag`, +- explicit raw-stamp paytag examples such as `+1234569999@phone.cubid.mypaytag`, +- paytag availability, unavailable, and reserved-name checks before issuance, +- negative-disclosure responses for `no_route`, `authorization_required`, and + `user_action_required`, - provider callback and provider response, - `payment_intent_created` notification payload. diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 1f90f76..fa25ef1 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -30,6 +30,20 @@ import type { } from "@mypaytag/sdk"; export interface MyPayTagFixtures { + paytags: { + opaqueDefault: PaytagExampleFixture; + rawExplicit: PaytagExampleFixture; + availability: { + available: PaytagAvailabilityFixture; + unavailable: PaytagAvailabilityFixture; + reserved: PaytagAvailabilityFixture; + }; + negativeDisclosure: { + noRoute: ResolveResponse; + authorizationRequired: ResolveResponse; + userActionRequired: ResolveResponse; + }; + }; routeRegistration: { valid: RouteRegistrationRequest; forbiddenAddress: RouteRegistrationRequest & { address: string }; @@ -69,7 +83,61 @@ export interface MyPayTagFixtures { intent: MyPayTagIntent; } +export interface PaytagExampleFixture { + paytag: string; + exposure: "opaque_default" | "raw_stamp_explicit"; + identityProvider: "cubid"; + userChoiceRequired: boolean; +} + +export interface PaytagAvailabilityFixture { + paytag: string; + status: "available" | "unavailable" | "reserved"; + canIssue: boolean; + publicReason?: "already_taken" | "reserved_namespace"; +} + export const myPayTagFixtures: MyPayTagFixtures = { + paytags: { + opaqueDefault: { + paytag: "abd123@cubid.mypaytag", + exposure: "opaque_default", + identityProvider: "cubid", + userChoiceRequired: false, + }, + rawExplicit: { + paytag: "+1234569999@phone.cubid.mypaytag", + exposure: "raw_stamp_explicit", + identityProvider: "cubid", + userChoiceRequired: true, + }, + availability: { + available: { + paytag: "new456@cubid.mypaytag", + status: "available", + canIssue: true, + }, + unavailable: { + paytag: "abd123@cubid.mypaytag", + status: "unavailable", + canIssue: false, + publicReason: "already_taken", + }, + reserved: { + paytag: "support@mypaytag", + status: "reserved", + canIssue: false, + publicReason: "reserved_namespace", + }, + }, + negativeDisclosure: { + noRoute: validNoRouteResponse, + authorizationRequired: { + status: "authorization_required", + } satisfies ResolveResponse, + userActionRequired: validRouteSelectionResponse, + }, + }, routeRegistration: { valid: validRouteRegistrationRequest, forbiddenAddress: { diff --git a/packages/testing/src/testing.test.ts b/packages/testing/src/testing.test.ts index 8ff49da..9e9b9dc 100644 --- a/packages/testing/src/testing.test.ts +++ b/packages/testing/src/testing.test.ts @@ -15,6 +15,26 @@ import { describe("@mypaytag/testing", () => { it("exports reusable MVP fixtures", () => { + expect(myPayTagFixtures.paytags.opaqueDefault).toMatchObject({ + paytag: "abd123@cubid.mypaytag", + exposure: "opaque_default", + userChoiceRequired: false, + }); + expect(myPayTagFixtures.paytags.rawExplicit).toMatchObject({ + paytag: "+1234569999@phone.cubid.mypaytag", + exposure: "raw_stamp_explicit", + userChoiceRequired: true, + }); + expect(myPayTagFixtures.paytags.availability.available.canIssue).toBe(true); + expect(myPayTagFixtures.paytags.availability.unavailable.publicReason).toBe("already_taken"); + expect(myPayTagFixtures.paytags.availability.reserved.publicReason).toBe("reserved_namespace"); + expect(myPayTagFixtures.paytags.negativeDisclosure.noRoute).toEqual({ status: "no_route" }); + expect(myPayTagFixtures.paytags.negativeDisclosure.authorizationRequired).toEqual({ + status: "authorization_required", + }); + expect(myPayTagFixtures.paytags.negativeDisclosure.userActionRequired.status).toBe( + "user_action_required", + ); expect(myPayTagFixtures.resolve.request.intentMode).toBe("one_time"); expect(myPayTagFixtures.notifications.paymentIntentCreated.eventType).toBe( "payment_intent_created", From 531e24119e214906a18ed8866d4d0d00664568b9 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 02:18:51 -0400 Subject: [PATCH 09/21] test: add realignment contract acceptance --- agent-context/session-log/main.md | 11 ++++ agent-context/todo.md | 6 +-- docs/integration/README.md | 1 + docs/integration/staged-smoke-checklist.md | 25 +++++++++ packages/protocol/src/protocol.test.ts | 63 ++++++++++++++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 docs/integration/staged-smoke-checklist.md diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index fd71fd7..6943091 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -303,3 +303,14 @@ todo: mypaytag-sdk:GPTS-S6-T5 summary: Added reusable testing fixtures for opaque default paytags, explicit raw-stamp paytags, availability/unavailable/reserved issuance checks, and negative-disclosure responses for no_route, authorization_required, and user_action_required. validation: Ran pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm --filter @mypaytag/protocol test; typecheck passed and focused tests passed with 44 workspace cases. follow-ups: Complete Sprint 6 with contract tests for OpenAPI/schema parity, forbidden route registration fields, provider payload requirements, and staged smoke documentation. + +--- + +## 2026-06-28-gpts-s6-t6 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S6-T6 +summary: Added protocol acceptance tests for required provider_json payload fields and OpenAPI/canonical intent parity, plus a staged smoke checklist linking SDK validation to Cubid SDK, MyPayTag backend, one test PayingDapp, and one test PayToDapp. +validation: Ran pnpm --filter @mypaytag/protocol typecheck, pnpm --filter @mypaytag/protocol test, and pnpm run validate; full validation regenerated types, linted OpenAPI, built and typechecked packages, passed tests with 46 workspace cases, passed the public-boundary scan, cleaned, and rebuilt. +follow-ups: Sprint 6 is complete; use the staged smoke checklist only after local SDK validation remains green. diff --git a/agent-context/todo.md b/agent-context/todo.md index c41ec06..4782bf1 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -456,9 +456,9 @@ Acceptance notes: ### GPTS-S6-T6 Add Contract Tests And Cross-Repo Smoke Checklist -Status: TBD -Feature branch: TBD -Session log: TBD +Status: Complete +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s6-t6 Depends on: mypaytag-sdk:GPTS-S6-T2, mypaytag-sdk:GPTS-S6-T3, mypaytag-sdk:GPTS-S6-T5 Add acceptance coverage for the corrected MVP contract and document the staged integration smoke. diff --git a/docs/integration/README.md b/docs/integration/README.md index 8185639..2dfcdf1 100644 --- a/docs/integration/README.md +++ b/docs/integration/README.md @@ -6,6 +6,7 @@ Use these guides with the SDK packages and runnable examples: - [PayToDapp Modality B integration](./payto-dapps.md) - [Status and hosted-action handling](./statuses-and-actions.md) - [Notifications and test fixtures](./notifications-and-fixtures.md) +- [Staged smoke checklist](./staged-smoke-checklist.md) These docs describe public MVP integration behavior only. Private resolver storage, deployment wiring, service-role access, audit internals, and admin diff --git a/docs/integration/staged-smoke-checklist.md b/docs/integration/staged-smoke-checklist.md new file mode 100644 index 0000000..ab9a9ac --- /dev/null +++ b/docs/integration/staged-smoke-checklist.md @@ -0,0 +1,25 @@ +# Staged Smoke Checklist + +Run this checklist only after local SDK validation passes. + +## Local SDK Gate + +- Run `pnpm run validate` in `mypaytag-sdk`. +- Confirm generated protocol types, OpenAPI lint, package typecheck, tests, public-boundary scan, clean, and build all pass. +- Confirm `api/postman_collection.json` was regenerated from `api/openapi.yaml` when the API contract changed. + +## Hosted Staging Smoke + +- Cubid SDK can open Cubid-hosted paytag identity and consent actions without exposing wallet, route, provider, asset, payment intent, settlement, solver, bridge, swap, or execution fields. +- MyPayTag backend accepts a paytag resolve request using the SDK shape and validates identity/consent through Cubid without exposing Cubid internals to the PayingDapp. +- One test PayingDapp calls MyPayTag, not Cubid, with a paytag such as `abd123@cubid.mypaytag`. +- One test PayToDapp registers supported routes without wallet addresses or payment instructions. +- MyPayTag returns `no_route`, `authorization_required`, and `user_action_required` without leaking whether a person, paytag, Cubid user, route, or PayToDapp exists. +- MyPayTag selects a compatible route or returns hosted route selection, then calls the selected PayToDapp provider callback. +- The selected PayToDapp returns a provider response with all required `provider_json.payload` fields: provider intent id, chain, network, asset, destination, amount, reference, and expiry. +- MyPayTag returns a normalized `mypaytag.intent.v1` response matching the SDK schema. + +## Launch Evidence + +- Attach local SDK validation output before treating hosted staging smoke as launch-readiness evidence. +- Record the Cubid SDK version, MyPayTag backend commit, MyPayTag SDK commit, test PayingDapp commit, and test PayToDapp commit used in staging. diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 54273c5..0c19f0a 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -1,3 +1,6 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + import { describe, expect, it } from "vitest"; import { @@ -36,6 +39,17 @@ const actionStatusFixtures = [ validRouteSelectionResponse, ] as const; +const providerPayloadRequiredFields = [ + "providerIntentId", + "chain", + "network", + "asset", + "destination", + "amount", + "reference", + "expiresAt", +] as const; + describe("@mypaytag/protocol", () => { it("accepts valid MVP fixtures", () => { expect(validateRouteRegistrationRequest(validRouteRegistrationRequest)).toEqual( @@ -158,6 +172,23 @@ describe("@mypaytag/protocol", () => { ).toThrow(); }); + it("rejects provider responses missing required provider_json payload fields", () => { + for (const field of providerPayloadRequiredFields) { + const payload = { ...validProviderResponse.paymentInstruction.payload }; + delete payload[field]; + + expect(() => + validateProviderResponse({ + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload, + }, + }), + ).toThrow(); + } + }); + it("rejects provider payloads that expose address-like fields inside MyPayTag intents", () => { for (const field of ["recipientAddress", "address", "account"] as const) { expect(() => @@ -244,4 +275,36 @@ describe("@mypaytag/protocol", () => { }), ).toThrow(); }); + + it("keeps OpenAPI examples aligned with canonical intent and provider payload fields", () => { + const openApi = readFileSync( + fileURLToPath(new URL("../../../api/openapi.yaml", import.meta.url)), + "utf8", + ); + + for (const field of [ + "id", + "schema", + "status", + "modality", + "recipient", + "selectedRoute", + "amount", + "expiresAt", + "singleUse", + "paymentInstruction", + "references", + ] as const) { + expect(openApi).toContain(` - ${field}`); + } + + for (const field of providerPayloadRequiredFields) { + expect(openApi).toContain(` - ${field}`); + expect(openApi).toContain(`${field}:`); + } + + expect(openApi).toContain("identifierType: paytag"); + expect(openApi).toContain("schema: mypaytag.intent.v1"); + expect(openApi).not.toContain("identifierType: verified_stamp"); + }); }); From 6eee3098217a6164aeeffc47a964220134f36219 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 09:03:38 -0400 Subject: [PATCH 10/21] docs: add SDK MVP gap closure plan --- .../2026-06-28-mypaytag-sdk-mvp-gap-review.md | 121 +++++++++++++ agent-context/todo.md | 169 ++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 agent-context/2026-06-28-mypaytag-sdk-mvp-gap-review.md diff --git a/agent-context/2026-06-28-mypaytag-sdk-mvp-gap-review.md b/agent-context/2026-06-28-mypaytag-sdk-mvp-gap-review.md new file mode 100644 index 0000000..bf70327 --- /dev/null +++ b/agent-context/2026-06-28-mypaytag-sdk-mvp-gap-review.md @@ -0,0 +1,121 @@ +# MyPayTag SDK MVP Gap Review + +Date: 2026-06-28 +Repo: `mypaytag-sdk` +Branch reviewed: `codex/mypaytag-mvp-realignment-20260628` +Status: implementation gap note + +## Scope + +This note evaluates the SDK implementation against: + +- `agent-context/2026-06-28-mypaytag-mvp-realignment.md` +- the updated MyPayTag backend PRD scope that promotes NEAR Intents / 1Click into Phase 1/MVP +- the MyPayTag happy-path use cases for paytag issuance, route CRUD, resolve, NEAR 1Click quotes, quote selection, and SmarTrust swap/bridge + +The branch is mechanically healthy and `pnpm validate` passes, but several public contract gaps remain against the revised MVP scope. + +## Summary + +The SDK now correctly moves most public contract language from GlobalPayTo to MyPayTag, uses `paytag` terminology, keeps Cubid internals out of public request shapes, validates the canonical MyPayTag intent envelope, and rejects wallet/address fields during route registration. + +However, the SDK still treats all execution quoting as a future generic solver extension. That is no longer aligned with the MVP: NEAR Intents / 1Click is now the only Phase 1 swap/bridge execution adapter, while LI.FI, Squid, 0x, Across, LayerZero/Stargate, broad solver fanout, and generic external adapter support are Phase 2. + +## Current Strengths + +- `ResolveRequest` and `ResolveResponse` use `identifierType: "paytag"` and keep Cubid internals out of public response shapes. +- `MyPayTagIntent` requires `schema`, `status`, `modality`, `recipient`, `selectedRoute`, `amount`, `expiresAt`, `singleUse`, `paymentInstruction`, and `references`. +- `provider_json.payload` requires provider intent id, chain, network, asset, destination, amount, reference, and expiry. +- Route registration schemas reject top-level account/address/payment-instruction fields. +- Provider SDK helpers validate provider callback and provider response payloads. +- Testing fixtures include opaque paytags, explicit raw paytag examples, safe negative responses, and provider fixtures. +- `pnpm validate` passes on the reviewed branch. + +## Gaps Against MVP Scope + +### 1. NEAR Intents / 1Click Is Not An MVP Contract + +The SDK still labels all crypto-native execution helpers as future extension helpers and includes generic solver ids plus fanout behavior. The revised MVP requires a concrete NEAR Intents / 1Click contract for SmarTrust swap/bridge use cases. + +Required change: + +- Add explicit Phase 1 NEAR Intents / 1Click quote and selected-payable-instruction contracts. +- Keep LI.FI, Squid, 0x, Across, LayerZero/Stargate, broad fanout, and generic external adapter support as Phase 2 only. +- Update SDK helpers and tests so the MVP path does not rely on generic quote fanout. + +### 2. Quote Selection And Payable Instruction Handoff Are Missing + +The happy path needs: + +1. PayingDapp resolves a paytag and receives one or more NEAR 1Click quotes. +2. PayingDapp selects a quote. +3. MyPayTag validates the selected quote and returns a payable instruction. + +Current schemas expose `RouteQuotePreview` and generic `ExecutionQuote`, but they do not define the public request/response shapes for quote option return, quote selection, or selected payable instruction. + +Required change: + +- Add schemas/types for NEAR 1Click quote options returned by MyPayTag. +- Add schemas/types for selected quote confirmation. +- Add schemas/types for the payable instruction returned after quote selection. +- Add fixtures and validators for quote expiry, route reference, resolver reference, selected PayToDapp route reference, and payable instruction consistency. + +### 3. Provider Callback Binding Is Incomplete + +The protocol schema requires resolver request id, recipient, PayingDapp id, selected path, amount, purpose, and expiry. The revised MVP also requires PayingDapp reference binding. OpenAPI is currently weaker than the JSON Schema and omits purpose and expiry in the provider callback component. + +Required change: + +- Add `payingDappReference` to `ProviderCallbackRequest`. +- Align OpenAPI `ProviderCallbackRequest` with JSON Schema and generated TypeScript. +- Ensure provider SDK conformance tests verify purpose, expiry, resolver request id, selected route, amount, PayingDapp id, and PayingDapp reference matching. + +### 4. OpenAPI And JSON Schemas Drift + +OpenAPI differs from the JSON Schemas for `NotificationEvent` and provider callbacks. This creates integration risk because OpenAPI consumers and TypeScript consumers will build against different payloads. + +Required change: + +- Make OpenAPI consume or mirror the canonical JSON Schema shapes. +- Align `NotificationEvent` around `eventType`, `schema`, `recipient`, `amount`, `references`, and `action`. +- Add tests that compare OpenAPI examples/components against protocol fixtures, not only string snippets. + +### 5. Route CRUD And Hosted Action Contracts Are Incomplete In Protocol Package + +OpenAPI documents route CRUD and hosted action endpoints, but the protocol package only exports route registration request/response validators. There are no canonical protocol schemas for PayToRoute reads, route update requests, delete/revoke responses, hosted action view models, hosted action decisions, or hosted action completion. + +Required change: + +- Add protocol schemas, generated types, validators, and fixtures for route CRUD and hosted route-selection actions. +- Ensure route CRUD types expose only PayToDapp-owned scoped data. +- Ensure hosted action types do not expose route graph, raw identifiers, wallet addresses, unrelated PayToDapps, route preferences, or private diagnostics. + +### 6. Paytag Availability Is Fixture-Only + +Happy path paytag issuance needs Cubid to check availability with MyPayTag before issuing an opaque paytag. The SDK currently has testing fixtures for availability, but no public or service-facing contract. + +Required change: + +- Add paytag availability request/response schemas for the MyPayTag/Cubid boundary if this contract belongs in the public SDK. +- If the contract is private service-to-service only, document that explicitly and keep only fixtures/test vectors here. +- Add availability states for available, unavailable, reserved, idempotent retry, raw-explicit, and opaque-default issuance cases. + +## Required Change Order + +1. Add explicit NEAR Intents / 1Click MVP quote and selected-instruction contracts. +2. Move generic solver fanout docs/tests to Phase 2 language. +3. Fix provider callback binding and OpenAPI/schema drift. +4. Add protocol schemas and validators for route CRUD and hosted action surfaces. +5. Decide and document whether paytag availability is public SDK or private service contract; add schemas if public. +6. Expand fixtures, examples, and conformance tests around the revised MVP happy paths. +7. Run `pnpm validate` after generated artifacts are refreshed through repo scripts. + +## Validation Target + +The SDK is MVP-ready when: + +- `pnpm validate` passes. +- OpenAPI, JSON Schemas, generated TypeScript, fixtures, SDK helpers, provider SDK helpers, and Postman examples agree. +- PayingDapp examples can build same-chain resolve requests and NEAR 1Click swap/bridge quote flows without direct Cubid probing. +- PayToDapp examples can register routes and handle provider callbacks with complete binding fields. +- Testing fixtures cover the MVP happy paths: paytag availability, route registration, route deregistration, resolve, route selection, NEAR 1Click quote options, selected quote payable instruction, and safe negative statuses. diff --git a/agent-context/todo.md b/agent-context/todo.md index 4782bf1..184016f 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -469,3 +469,172 @@ Acceptance notes: - Tests reject provider responses missing required `provider_json` fields. - Generated artifacts are refreshed only through repo scripts. - A staged smoke checklist pairs this SDK with Cubid SDK, MyPayTag backend, one test PayingDapp, and one test PayToDapp after local validation passes. + +## Sprint 7: MVP Gap Closure From SDK Review + +### GPTS-S7-T1 Add NEAR Intents / 1Click MVP Quote Contracts + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t1 +Depends on: mypaytag:GPTR-S7-T7, smartrust-wallet:SMTW-MPT-02 + +Define the public SDK/protocol contract for NEAR Intents / 1Click as the only Phase 1 swap/bridge execution adapter. + +Acceptance notes: + +- Add JSON Schemas, generated TypeScript types, validators, and fixtures for NEAR 1Click quote option responses. +- Quote option responses include quote id, send amount, receive amount, source chain/network/asset, destination chain/network/asset, fees, expiry, resolver reference, and selected PayToDapp route reference. +- Quote option responses do not expose unrelated PayToDapps, route preferences, wallet graph details, raw identifiers, Cubid internals, or private provider diagnostics. +- SDK docs state that NEAR Intents / 1Click is Phase 1/MVP for SmarTrust swap/bridge paytag payments. +- LI.FI, Squid, 0x, Across, LayerZero/Stargate, broad solver fanout, and generic external adapter support are labeled Phase 2. + +### GPTS-S7-T2 Add Selected Quote Confirmation And Payable Instruction Contracts + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t2 +Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag:GPTR-S7-T7, smartrust-wallet:SMTW-MPT-02 + +Define the public request and response shapes for a PayingDapp selecting a NEAR 1Click quote and receiving a payable instruction. + +Acceptance notes: + +- Add a selected quote confirmation request schema with resolver reference, quote id, paying dapp reference, and idempotency/reference fields. +- Add a selected quote response schema for a normalized payable instruction or MyPayTag intent envelope. +- Selected quote validation fixtures cover quote expiry, original resolver request binding, route reference binding, paytag/grant still active, and selected PayToDapp route still eligible. +- Payable instruction fixtures cover NEAR 1Click response consistency and provider callback consistency. +- Same-chain same-token behavior is documented as a PayingDapp choice; MyPayTag supports it when called but does not require the SDK helper path for local transfers. + +### GPTS-S7-T3 Move Generic Solver Fanout To Phase 2 + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t3 +Depends on: mypaytag-sdk:GPTS-S7-T1 + +Keep the broader execution helper work, but stop presenting broad solver fanout as current MVP behavior. + +Acceptance notes: + +- `cryptoNativeExecutionSolvers` and `requestExecutionQuotes` are documented as Phase 2/future helpers unless explicitly scoped to NEAR 1Click MVP behavior. +- Tests that currently require fanout across NEAR, LI.FI, Squid, and other providers are moved under Phase 2 wording or changed to prove they are non-MVP helpers. +- Integration docs no longer tell MVP PayingDapps to configure broad solver providers. +- `docs/engineering/protocol-and-sdk-architecture.md`, `docs/engineering/mvp-api-contracts.md`, `docs/integration/paying-dapps.md`, and examples all agree on NEAR 1Click as the only Phase 1 execution adapter. + +### GPTS-S7-T4 Complete Provider Callback Binding Contract + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t4 +Depends on: mypaytag-sdk:GPTS-S6-T2, mypaytag:GPTR-S7-T5 + +Align provider callback contracts with the MVP requirement that callbacks are bound to resolver request, selected route, amount, purpose, expiry, PayingDapp id, and PayingDapp reference. + +Acceptance notes: + +- `ProviderCallbackRequest` includes `payingDappReference`. +- OpenAPI, JSON Schema, generated TypeScript, fixtures, provider SDK helpers, and conformance tests all include `purpose`, `expiresAt`, and `payingDappReference`. +- Provider SDK matching helpers verify provider response path, amount, provider intent id, expiry, resolver reference, purpose, PayingDapp id, and PayingDapp reference where those fields are available. +- Tests reject callbacks or provider responses with mismatched route, amount, reference, expiry, purpose, or PayingDapp reference. + +### GPTS-S7-T5 Align OpenAPI With Protocol Schemas And Fixtures + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t5 +Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T4 + +Remove remaining drift between OpenAPI and canonical protocol schemas. + +Acceptance notes: + +- `NotificationEvent` OpenAPI uses `eventType`, `schema`, `recipient`, `amount`, `references`, and `action`, matching the protocol schema. +- Provider callback OpenAPI matches the protocol schema and generated TypeScript exactly. +- OpenAPI examples validate against protocol validators where practical, not only string-presence checks. +- Postman generation is refreshed from the corrected OpenAPI source. +- `pnpm api:validate`, protocol tests, provider SDK tests, and public boundary scan pass after regeneration. + +### GPTS-S7-T6 Add Route CRUD Protocol Schemas And SDK Helpers + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t6 +Depends on: mypaytag:GPTR-S7-T1 + +Make PayToDapp route CRUD a typed SDK/protocol surface rather than only an OpenAPI description. + +Acceptance notes: + +- Add schemas, generated types, validators, and fixtures for `PayToRoute`, route list/read responses, `RouteUpdateRequest`, and delete/revoke route responses. +- Route CRUD shapes expose only PayToDapp-owned scoped route capability data. +- Route CRUD shapes do not expose wallet addresses, account ids, payment instructions, route preferences, unrelated PayToDapps, raw identifiers, Cubid internals, or private diagnostics. +- Provider SDK includes helpers for building route create/update requests and parsing route CRUD responses. +- Tests cover create, list/read, patch, delete/revoke, forbidden address fields, and safe not-found/unavailable responses. + +### GPTS-S7-T7 Add Hosted Route-Selection Action Schemas + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t7 +Depends on: mypaytag:GPTR-S7-T6, mypaytag-site:GPTW-S7-T6 + +Add canonical protocol support for hosted route-selection action view, decision, and completion payloads. + +Acceptance notes: + +- Add schemas, generated types, validators, and fixtures for hosted action view, hosted action decision, and hosted action completion. +- Hosted action view exposes only action-scoped route choices after user authentication. +- Hosted action decision supports selecting a route or leaving the choice unchanged/denied according to the backend and site contract. +- Invalid, expired, completed, replayed, and restart-required action states are represented without leaking private diagnostics. +- Tests reject hosted action payloads that include raw identifiers, wallet addresses, route preferences, unrelated PayToDapps, provider internals, or private diagnostics. + +### GPTS-S7-T8 Decide And Document Paytag Availability Contract Placement + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t8 +Depends on: cubid-monorepo:CUBID-PAYTAG-02, mypaytag:GPTR-S7-T4 + +Clarify whether paytag availability and issuance are public SDK contracts or private MyPayTag/Cubid service contracts. + +Acceptance notes: + +- If public or shared, add schemas, generated types, validators, fixtures, and docs for paytag availability and issuance responses. +- If private service-to-service only, document that the public SDK intentionally keeps availability out of integrator APIs and keeps only public-safe fixtures. +- Availability fixtures cover available, unavailable, reserved namespace, idempotent retry, opaque default, raw-explicit, and revoked/expired reuse policy cases. +- Docs state that Cubid owns identity and consent while MyPayTag owns uniqueness/availability checks and does not expose wallet/payment route data to Cubid. + +### GPTS-S7-T9 Expand MVP Happy-Path Fixtures And Examples + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t9 +Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T6, mypaytag-sdk:GPTS-S7-T7, mypaytag-sdk:GPTS-S7-T8 + +Update fixtures and examples so SDK consumers can exercise the MVP happy paths without private backend knowledge. + +Acceptance notes: + +- PayingDapp example covers same-chain resolve and NEAR 1Click swap/bridge quote flow. +- PayToDapp example covers route registration, route update, route deregistration, and provider callback response validation. +- Testing package exposes fixtures for paytag availability, route registration, route deregistration, resolve, route selection, NEAR 1Click quote options, selected quote payable instruction, and safe negative statuses. +- Examples do not probe Cubid directly from PayingDapps. +- Examples do not require broad solver fanout, LI.FI, Squid, 0x, Across, LayerZero/Stargate, or generic external adapter support for MVP. + +### GPTS-S7-T10 Validate Regenerated SDK Contract Artifacts + +Status: Todo +Feature branch: codex/mypaytag-mvp-realignment-20260628 +Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t10 +Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T3, mypaytag-sdk:GPTS-S7-T4, mypaytag-sdk:GPTS-S7-T5, mypaytag-sdk:GPTS-S7-T6, mypaytag-sdk:GPTS-S7-T7, mypaytag-sdk:GPTS-S7-T8, mypaytag-sdk:GPTS-S7-T9 + +Refresh generated artifacts and prove the SDK contract is internally consistent after the MVP gap closure. + +Acceptance notes: + +- Generated TypeScript and schema exports are refreshed only through `pnpm generate`. +- Postman collection is refreshed only through the repo script. +- `pnpm validate` passes. +- Public boundary scan passes and finds no private backend, Cubid internal, wallet graph, service-role, private storage, or deployment internals. +- Staged smoke checklist references the corrected SDK contracts for Cubid SDK, MyPayTag backend, one test PayingDapp, one test PayToDapp, and SmarTrust swap/bridge NEAR 1Click. From 25e359b3ae8329099a6ecf5577581d99b443d404 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:20:56 -0400 Subject: [PATCH 11/21] Add NEAR 1Click MVP quote contract --- agent-context/session-log/main.md | 11 ++ agent-context/todo.md | 2 +- .../protocol-and-sdk-architecture.md | 4 +- docs/integration/paying-dapps.md | 2 +- .../near-one-click-quote-option.schema.json | 59 ++++++++ packages/protocol/src/fixtures.ts | 40 ++++++ packages/protocol/src/generated/schemas.ts | 129 ++++++++++++++++++ packages/protocol/src/generated/types.ts | 1 + .../types/near-one-click-quote-option.ts | 29 ++++ packages/protocol/src/protocol.test.ts | 33 +++++ packages/protocol/src/validators.ts | 4 + packages/sdk/src/index.ts | 12 +- packages/sdk/src/sdk.test.ts | 8 ++ 13 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 packages/protocol/schemas/near-one-click-quote-option.schema.json create mode 100644 packages/protocol/src/generated/types/near-one-click-quote-option.ts diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 6943091..70e11a1 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -314,3 +314,14 @@ todo: mypaytag-sdk:GPTS-S6-T6 summary: Added protocol acceptance tests for required provider_json payload fields and OpenAPI/canonical intent parity, plus a staged smoke checklist linking SDK validation to Cubid SDK, MyPayTag backend, one test PayingDapp, and one test PayToDapp. validation: Ran pnpm --filter @mypaytag/protocol typecheck, pnpm --filter @mypaytag/protocol test, and pnpm run validate; full validation regenerated types, linted OpenAPI, built and typechecked packages, passed tests with 46 workspace cases, passed the public-boundary scan, cleaned, and rebuilt. follow-ups: Sprint 6 is complete; use the staged smoke checklist only after local SDK validation remains green. + +--- + +## 2026-06-28-gpts-s7-t1 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T1 +summary: Added the Phase 1 NEAR Intents / 1Click quote-option protocol contract, fixture, validator, SDK parser export, and tests while documenting broad solver fanout as Phase 2. +validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/sdk typecheck, and pnpm scan:public-boundary; focused tests passed with 48 workspace cases and the public-boundary scan passed. +follow-ups: Add selected quote confirmation and payable instruction contracts. diff --git a/agent-context/todo.md b/agent-context/todo.md index 184016f..953473d 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -474,7 +474,7 @@ Acceptance notes: ### GPTS-S7-T1 Add NEAR Intents / 1Click MVP Quote Contracts -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t1 Depends on: mypaytag:GPTR-S7-T7, smartrust-wallet:SMTW-MPT-02 diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 99f1e66..43ff063 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -48,7 +48,9 @@ The MVP protocol does not support: ## Future Extension: Crypto-Native Execution Solvers -Crypto-native execution adapters are a future extension, not part of the core MVP resolve flow. These adapters consume a resolved receive requirement and produce quotes or transaction requests; they do not decide which PayToDapp the recipient prefers. +NEAR Intents / 1Click is the Phase 1/MVP execution adapter for SmarTrust swap/bridge Paytag payments. It consumes a resolved receive requirement and returns a NEAR 1Click quote option scoped to the selected PayToDapp route. It does not decide which PayToDapp the recipient prefers. + +LI.FI, Squid, 0x, Across, LayerZero/Stargate, broad solver fanout, and generic external adapter support are Phase 2 extensions. They remain useful architecture context, but they are not required for the MVP resolve path. Short list for SDK adapter support: diff --git a/docs/integration/paying-dapps.md b/docs/integration/paying-dapps.md index a03e7ef..1da9f06 100644 --- a/docs/integration/paying-dapps.md +++ b/docs/integration/paying-dapps.md @@ -48,7 +48,7 @@ selected one-time provider intent. ## Future Extension: Crypto-Native Execution Quotes -The MVP resolve flow does not require execution quote helpers. When a PayingDapp supports future execution adapters, configure quote providers for the +The MVP resolve flow can return a NEAR Intents / 1Click quote option for SmarTrust swap/bridge Paytag payments. Broad execution quote fanout is Phase 2. When a PayingDapp supports future execution adapters, configure quote providers for the solver/router surfaces the app can use. The initial MyPayTag SDK solver ids are: diff --git a/packages/protocol/schemas/near-one-click-quote-option.schema.json b/packages/protocol/schemas/near-one-click-quote-option.schema.json new file mode 100644 index 0000000..5d9bede --- /dev/null +++ b/packages/protocol/schemas/near-one-click-quote-option.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-quote-option.schema.json", + "title": "NearOneClickQuoteOption", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "quoteId", + "adapter", + "send", + "receive", + "fees", + "expiresAt", + "resolverReference", + "selectedRouteReference" + ], + "properties": { + "schema": { "const": "mypaytag.near_1click.quote_option.v1" }, + "quoteId": { "type": "string", "minLength": 1 }, + "adapter": { "const": "near_intents_1click" }, + "send": { "$ref": "#/$defs/routeAmount" }, + "receive": { "$ref": "#/$defs/routeAmount" }, + "fees": { + "type": "array", + "items": { "$ref": "#/$defs/fee" } + }, + "expiresAt": { "type": "string", "format": "date-time" }, + "resolverReference": { "type": "string", "minLength": 1 }, + "selectedRouteReference": { "type": "string", "minLength": 1 }, + "nearQuoteReference": { "type": "string", "minLength": 1 }, + "estimatedDurationSeconds": { "type": "integer", "minimum": 0 } + }, + "$defs": { + "routeAmount": { + "type": "object", + "additionalProperties": false, + "required": ["chain", "network", "asset", "amount"], + "properties": { + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "amount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" } + } + }, + "fee": { + "type": "object", + "additionalProperties": false, + "required": ["label", "amount", "asset", "chargedTo", "source"], + "properties": { + "label": { "type": "string", "minLength": 1 }, + "amount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "asset": { "type": "string", "minLength": 1 }, + "chargedTo": { "const": "sender" }, + "source": { "enum": ["near_1click", "resolver", "paying_dapp"] } + } + } + } +} diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index f50f62f..515a5af 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -1,5 +1,6 @@ import type { MyPayTagIntent, + NearOneClickQuoteOption, NotificationEvent, ProviderCallbackRequest, ProviderResponse, @@ -206,3 +207,42 @@ export const validRouteQuotePreview: RouteQuotePreview = { expiresAt: "2026-06-24T20:00:00Z", resolverReference: "mpt_req_123", }; + +export const validNearOneClickQuoteOption: NearOneClickQuoteOption = { + schema: "mypaytag.near_1click.quote_option.v1", + quoteId: "near_1click_quote_123", + adapter: "near_intents_1click", + send: { + chain: "near", + network: "mainnet", + asset: "USDC", + amount: "25.18", + }, + receive: { + chain: "base", + network: "mainnet", + asset: "USDC", + amount: "25.00", + }, + fees: [ + { + label: "NEAR 1Click execution", + amount: "0.16", + asset: "USDC", + chargedTo: "sender", + source: "near_1click", + }, + { + label: "MyPayTag resolver fee", + amount: "0.02", + asset: "USDC", + chargedTo: "sender", + source: "resolver", + }, + ], + expiresAt: "2026-06-24T20:00:00Z", + resolverReference: "mpt_req_123", + selectedRouteReference: "mpt_route_123", + nearQuoteReference: "near_quote_ref_123", + estimatedDurationSeconds: 20, +}; diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index 3203742..5180d34 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -233,6 +233,134 @@ export const MypaytagIntentSchema = { } } as const; +export const NearOneClickQuoteOptionSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-quote-option.schema.json", + "title": "NearOneClickQuoteOption", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "quoteId", + "adapter", + "send", + "receive", + "fees", + "expiresAt", + "resolverReference", + "selectedRouteReference" + ], + "properties": { + "schema": { + "const": "mypaytag.near_1click.quote_option.v1" + }, + "quoteId": { + "type": "string", + "minLength": 1 + }, + "adapter": { + "const": "near_intents_1click" + }, + "send": { + "$ref": "#/$defs/routeAmount" + }, + "receive": { + "$ref": "#/$defs/routeAmount" + }, + "fees": { + "type": "array", + "items": { + "$ref": "#/$defs/fee" + } + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "resolverReference": { + "type": "string", + "minLength": 1 + }, + "selectedRouteReference": { + "type": "string", + "minLength": 1 + }, + "nearQuoteReference": { + "type": "string", + "minLength": 1 + }, + "estimatedDurationSeconds": { + "type": "integer", + "minimum": 0 + } + }, + "$defs": { + "routeAmount": { + "type": "object", + "additionalProperties": false, + "required": [ + "chain", + "network", + "asset", + "amount" + ], + "properties": { + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "amount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + } + } + }, + "fee": { + "type": "object", + "additionalProperties": false, + "required": [ + "label", + "amount", + "asset", + "chargedTo", + "source" + ], + "properties": { + "label": { + "type": "string", + "minLength": 1 + }, + "amount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "chargedTo": { + "const": "sender" + }, + "source": { + "enum": [ + "near_1click", + "resolver", + "paying_dapp" + ] + } + } + } + } +} as const; + export const NotificationEventSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/notification-event.schema.json", @@ -1341,6 +1469,7 @@ export const StatusSchema = { export const protocolSchemas = { "mypaytag-intent": MypaytagIntentSchema, + "near-one-click-quote-option": NearOneClickQuoteOptionSchema, "notification-event": NotificationEventSchema, "provider-callback-request": ProviderCallbackRequestSchema, "provider-response": ProviderResponseSchema, diff --git a/packages/protocol/src/generated/types.ts b/packages/protocol/src/generated/types.ts index fa09f8e..530e555 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -2,6 +2,7 @@ // Generated from packages/protocol/schemas. Do not edit by hand. export type { MyPayTagIntent } from "./types/mypaytag-intent.js"; +export type { NearOneClickQuoteOption } from "./types/near-one-click-quote-option.js"; export type { NotificationEvent } from "./types/notification-event.js"; export type { ProviderCallbackRequest } from "./types/provider-callback-request.js"; export type { ProviderResponse } from "./types/provider-response.js"; diff --git a/packages/protocol/src/generated/types/near-one-click-quote-option.ts b/packages/protocol/src/generated/types/near-one-click-quote-option.ts new file mode 100644 index 0000000..52a268a --- /dev/null +++ b/packages/protocol/src/generated/types/near-one-click-quote-option.ts @@ -0,0 +1,29 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/near-one-click-quote-option.schema.json. Do not edit by hand. + +export interface NearOneClickQuoteOption { + schema: "mypaytag.near_1click.quote_option.v1"; + quoteId: string; + adapter: "near_intents_1click"; + send: RouteAmount; + receive: RouteAmount; + fees: Fee[]; + expiresAt: string; + resolverReference: string; + selectedRouteReference: string; + nearQuoteReference?: string; + estimatedDurationSeconds?: number; +} +export interface RouteAmount { + chain: string; + network: string; + asset: string; + amount: string; +} +export interface Fee { + label: string; + amount: string; + asset: string; + chargedTo: "sender"; + source: "near_1click" | "resolver" | "paying_dapp"; +} diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 0c19f0a..773e092 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest"; import { validateMyPayTagIntent, validateNotificationEvent, + validateNearOneClickQuoteOption, validateProviderResponse, validateResolveRequest, validateResolveResponse, @@ -15,6 +16,7 @@ import { validMyPayTagIntent, validNoRouteResponse, validNotificationEvent, + validNearOneClickQuoteOption, validProviderResponse, validResolveRequest, validResolvedResponse, @@ -65,6 +67,9 @@ describe("@mypaytag/protocol", () => { expect(validateMyPayTagIntent(validMyPayTagIntent)).toEqual(validMyPayTagIntent); expect(validateNotificationEvent(validNotificationEvent)).toEqual(validNotificationEvent); expect(validateRouteQuotePreview(validRouteQuotePreview)).toEqual(validRouteQuotePreview); + expect(validateNearOneClickQuoteOption(validNearOneClickQuoteOption)).toEqual( + validNearOneClickQuoteOption, + ); }); it("accepts every public status value and response shape", () => { @@ -254,6 +259,34 @@ describe("@mypaytag/protocol", () => { ).toThrow(); }); + it("treats NEAR 1Click quote options as the only MVP execution quote contract", () => { + expect(() => + validateNearOneClickQuoteOption({ + ...validNearOneClickQuoteOption, + adapter: "lifi", + }), + ).toThrow(); + + expect(() => + validateNearOneClickQuoteOption({ + ...validNearOneClickQuoteOption, + payToDappOptions: ["smartrust-wallet", "other-wallet"], + }), + ).toThrow(); + + expect(() => + validateNearOneClickQuoteOption({ + ...validNearOneClickQuoteOption, + fees: [ + { + ...validNearOneClickQuoteOption.fees[0], + chargedTo: "receiver", + }, + ], + }), + ).toThrow(); + }); + it("rejects notification payloads with provider receipt or action-link fields", () => { expect(() => validateNotificationEvent({ diff --git a/packages/protocol/src/validators.ts b/packages/protocol/src/validators.ts index 781a030..d736292 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -4,6 +4,7 @@ import addFormats from "ajv-formats"; import { protocolSchemas } from "./generated/schemas.js"; import type { MyPayTagIntent, + NearOneClickQuoteOption, MyPayTagStatus, NotificationEvent, ProviderCallbackRequest, @@ -86,5 +87,8 @@ export const validateNotificationEvent = (payload: unknown) => export const validateRouteQuotePreview = (payload: unknown) => validateProtocolPayload("route-quote-preview", payload); +export const validateNearOneClickQuoteOption = (payload: unknown) => + validateProtocolPayload("near-one-click-quote-option", payload); + export const isNotificationEvent = (payload: unknown): payload is NotificationEvent => isProtocolPayload("notification-event", payload); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 480990f..30db8f7 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,14 +1,17 @@ import { + validateNearOneClickQuoteOption, isNotificationEvent, validateNotificationEvent, validateResolveRequest, validateResolveResponse, type NotificationEvent, + type NearOneClickQuoteOption, type ResolveRequest, type ResolveResponse, } from "@mypaytag/protocol"; export type ResolveRequestInput = ResolveRequest; +export type NearOneClickMvpQuoteOption = NearOneClickQuoteOption; export type SupportedPath = ResolveRequest["supportedPaths"][number]; export type ResolveAmount = ResolveRequest["amount"]; export type PayorAmountExactness = "exact_send" | "exact_receive"; @@ -36,9 +39,8 @@ export type ActionRequiredStatus = "user_action_required"; export type ActionRequiredResponse = Extract; /** - * Future extension solver ids. These are not required for the MVP MyPayTag - * resolve flow, route registration flow, provider callback flow, or hosted - * route-selection flow. + * Phase 2 extension solver ids. NEAR Intents / 1Click has a dedicated Phase 1 + * MVP quote-option contract exported as NearOneClickMvpQuoteOption. */ export const cryptoNativeExecutionSolvers = [ "near_intents_1click", @@ -150,6 +152,10 @@ export function parseNotificationEvent(payload: unknown): NotificationEvent { return validateNotificationEvent(payload); } +export function parseNearOneClickQuoteOption(payload: unknown): NearOneClickMvpQuoteOption { + return validateNearOneClickQuoteOption(payload); +} + export function isMyPayTagNotification(payload: unknown): payload is NotificationEvent { return isNotificationEvent(payload); } diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 229382c..6b3299f 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { validNoRouteResponse, + validNearOneClickQuoteOption, validNotificationEvent, validResolvedResponse, validResolveRequest, @@ -21,6 +22,7 @@ import { parseNotificationEvent, parseResolveResponse, requestExecutionQuotes, + parseNearOneClickQuoteOption, type CryptoNativeExecutionSolverId, type ExecutionQuoteProvider, } from "./index.js"; @@ -125,6 +127,12 @@ describe("@mypaytag/sdk", () => { expect(parseNotificationEvent(validNotificationEvent)).toEqual(validNotificationEvent); }); + it("parses the Phase 1 NEAR 1Click MVP quote option", () => { + expect(parseNearOneClickQuoteOption(validNearOneClickQuoteOption)).toEqual( + validNearOneClickQuoteOption, + ); + }); + it("requests execution quotes from every configured solver when none is preferred", async () => { const calls: CryptoNativeExecutionSolverId[] = []; const providers = [ From f5585b1e0131a5fe69f2e433058a2caaef2a55fa Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:26:56 -0400 Subject: [PATCH 12/21] Add NEAR 1Click payable instruction contract --- agent-context/session-log/main.md | 11 ++ agent-context/todo.md | 2 +- docs/engineering/mvp-api-contracts.md | 51 ++++++ ...-one-click-payable-instruction.schema.json | 58 +++++++ ...-click-quote-selection-request.schema.json | 24 +++ packages/protocol/src/fixtures.ts | 35 ++++ packages/protocol/src/generated/schemas.ts | 153 ++++++++++++++++++ packages/protocol/src/generated/types.ts | 2 + .../near-one-click-payable-instruction.ts | 25 +++ .../near-one-click-quote-selection-request.ts | 12 ++ packages/protocol/src/protocol.test.ts | 68 ++++++++ packages/protocol/src/validators.ts | 8 + packages/sdk/src/index.ts | 18 +++ packages/sdk/src/sdk.test.ts | 13 ++ 14 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/schemas/near-one-click-payable-instruction.schema.json create mode 100644 packages/protocol/schemas/near-one-click-quote-selection-request.schema.json create mode 100644 packages/protocol/src/generated/types/near-one-click-payable-instruction.ts create mode 100644 packages/protocol/src/generated/types/near-one-click-quote-selection-request.ts diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 70e11a1..38849cc 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -325,3 +325,14 @@ todo: mypaytag-sdk:GPTS-S7-T1 summary: Added the Phase 1 NEAR Intents / 1Click quote-option protocol contract, fixture, validator, SDK parser export, and tests while documenting broad solver fanout as Phase 2. validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/sdk typecheck, and pnpm scan:public-boundary; focused tests passed with 48 workspace cases and the public-boundary scan passed. follow-ups: Add selected quote confirmation and payable instruction contracts. + +--- + +## 2026-06-28-gpts-s7-t2 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T2 +summary: Added NEAR 1Click quote selection and payable instruction schemas, generated protocol exports, fixtures, validators, negative tests, and PayingDapp SDK builder/parser helpers. +validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/sdk typecheck, and pnpm scan:public-boundary; focused tests passed with 51 workspace cases and the public-boundary scan passed. +follow-ups: Continue Sprint 7 by moving generic solver fanout wording and tests under Phase 2/non-MVP positioning. diff --git a/agent-context/todo.md b/agent-context/todo.md index 953473d..726b311 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -491,7 +491,7 @@ Acceptance notes: ### GPTS-S7-T2 Add Selected Quote Confirmation And Payable Instruction Contracts -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t2 Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag:GPTR-S7-T7, smartrust-wallet:SMTW-MPT-02 diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index d945b35..4f8000a 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -223,6 +223,57 @@ Rules: - Action URLs must not embed recipient, route, provider, authorization, wallet, preference, or diagnostic data. - Unauthenticated action pages must remain safe even when opened by the wrong user, crawler, browser preview, or expired session. +### NEAR 1Click Quote Confirmation + +The MVP crypto-native execution contract is a NEAR Intents / 1Click quote flow. The resolver may return a `mypaytag.near_1click.quote_option.v1` object when a PayingDapp needs to confirm a concrete cross-chain execution route before receiving payable instructions. + +The PayingDapp confirms the selected option with `mypaytag.near_1click.quote_selection_request.v1`: + +```json +{ + "schema": "mypaytag.near_1click.quote_selection_request.v1", + "resolverReference": "mpt_req_123", + "quoteId": "near_1click_quote_123", + "selectedRouteReference": "mpt_route_123", + "payingDappReference": "chaincrew:payout_987", + "idempotencyKey": "mpt_idem_near_123", + "clientReference": "chaincrew:near_selection_123" +} +``` + +The resolver responds with a `mypaytag.near_1click.payable_instruction.v1` payload: + +```json +{ + "schema": "mypaytag.near_1click.payable_instruction.v1", + "status": "ready", + "adapter": "near_intents_1click", + "resolverReference": "mpt_req_123", + "quoteId": "near_1click_quote_123", + "selectedRouteReference": "mpt_route_123", + "payingDappReference": "chaincrew:payout_987", + "expiresAt": "2026-06-24T20:00:00Z", + "instruction": { + "kind": "near_1click_payable", + "payload": { + "nearQuoteReference": "near_quote_ref_123", + "depositAddress": "near1click-deposit.testnet", + "depositAsset": "USDC", + "depositAmount": "25.18", + "recipientAsset": "USDC", + "recipientAmount": "25.00", + "deadline": "2026-06-24T20:00:00Z" + } + } +} +``` + +Rules: + +- `adapter` is always `near_intents_1click` for the MVP execution flow. +- The quote selection request carries an idempotency key so retries do not mint duplicate payable instructions. +- Payable instructions expose only the selected quote and deposit terms. They must not expose PayToDapp route inventory, route preference data, wallet graphs, or receiver-charged fee structures. + ## Provider Intent Callback Endpoint: diff --git a/packages/protocol/schemas/near-one-click-payable-instruction.schema.json b/packages/protocol/schemas/near-one-click-payable-instruction.schema.json new file mode 100644 index 0000000..001e383 --- /dev/null +++ b/packages/protocol/schemas/near-one-click-payable-instruction.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-payable-instruction.schema.json", + "title": "NearOneClickPayableInstruction", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "status", + "adapter", + "resolverReference", + "quoteId", + "selectedRouteReference", + "payingDappReference", + "expiresAt", + "instruction" + ], + "properties": { + "schema": { "const": "mypaytag.near_1click.payable_instruction.v1" }, + "status": { "const": "ready" }, + "adapter": { "const": "near_intents_1click" }, + "resolverReference": { "type": "string", "minLength": 1 }, + "quoteId": { "type": "string", "minLength": 1 }, + "selectedRouteReference": { "type": "string", "minLength": 1 }, + "payingDappReference": { "type": "string", "minLength": 1 }, + "expiresAt": { "type": "string", "format": "date-time" }, + "instruction": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "payload"], + "properties": { + "kind": { "const": "near_1click_payable" }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": [ + "nearQuoteReference", + "depositAddress", + "depositAsset", + "depositAmount", + "recipientAsset", + "recipientAmount", + "deadline" + ], + "properties": { + "nearQuoteReference": { "type": "string", "minLength": 1 }, + "depositAddress": { "type": "string", "minLength": 1 }, + "depositAsset": { "type": "string", "minLength": 1 }, + "depositAmount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "recipientAsset": { "type": "string", "minLength": 1 }, + "recipientAmount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "deadline": { "type": "string", "format": "date-time" } + } + } + } + } + } +} diff --git a/packages/protocol/schemas/near-one-click-quote-selection-request.schema.json b/packages/protocol/schemas/near-one-click-quote-selection-request.schema.json new file mode 100644 index 0000000..623482a --- /dev/null +++ b/packages/protocol/schemas/near-one-click-quote-selection-request.schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-quote-selection-request.schema.json", + "title": "NearOneClickQuoteSelectionRequest", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "resolverReference", + "quoteId", + "selectedRouteReference", + "payingDappReference", + "idempotencyKey" + ], + "properties": { + "schema": { "const": "mypaytag.near_1click.quote_selection_request.v1" }, + "resolverReference": { "type": "string", "minLength": 1 }, + "quoteId": { "type": "string", "minLength": 1 }, + "selectedRouteReference": { "type": "string", "minLength": 1 }, + "payingDappReference": { "type": "string", "minLength": 1 }, + "idempotencyKey": { "type": "string", "minLength": 8 }, + "clientReference": { "type": "string", "minLength": 1 } + } +} diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 515a5af..022e31a 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -1,6 +1,8 @@ import type { MyPayTagIntent, + NearOneClickPayableInstruction, NearOneClickQuoteOption, + NearOneClickQuoteSelectionRequest, NotificationEvent, ProviderCallbackRequest, ProviderResponse, @@ -246,3 +248,36 @@ export const validNearOneClickQuoteOption: NearOneClickQuoteOption = { nearQuoteReference: "near_quote_ref_123", estimatedDurationSeconds: 20, }; + +export const validNearOneClickQuoteSelectionRequest: NearOneClickQuoteSelectionRequest = { + schema: "mypaytag.near_1click.quote_selection_request.v1", + resolverReference: validNearOneClickQuoteOption.resolverReference, + quoteId: validNearOneClickQuoteOption.quoteId, + selectedRouteReference: validNearOneClickQuoteOption.selectedRouteReference, + payingDappReference: validResolveRequest.payingDappReference, + idempotencyKey: "mpt_idem_near_123", + clientReference: "chaincrew:near_selection_123", +}; + +export const validNearOneClickPayableInstruction: NearOneClickPayableInstruction = { + schema: "mypaytag.near_1click.payable_instruction.v1", + status: "ready", + adapter: "near_intents_1click", + resolverReference: validNearOneClickQuoteOption.resolverReference, + quoteId: validNearOneClickQuoteOption.quoteId, + selectedRouteReference: validNearOneClickQuoteOption.selectedRouteReference, + payingDappReference: validResolveRequest.payingDappReference, + expiresAt: validNearOneClickQuoteOption.expiresAt, + instruction: { + kind: "near_1click_payable", + payload: { + nearQuoteReference: "near_quote_ref_123", + depositAddress: "near1click-deposit.testnet", + depositAsset: "USDC", + depositAmount: "25.18", + recipientAsset: "USDC", + recipientAmount: "25.00", + deadline: validNearOneClickQuoteOption.expiresAt, + }, + }, +}; diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index 5180d34..20ddb33 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -233,6 +233,112 @@ export const MypaytagIntentSchema = { } } as const; +export const NearOneClickPayableInstructionSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-payable-instruction.schema.json", + "title": "NearOneClickPayableInstruction", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "status", + "adapter", + "resolverReference", + "quoteId", + "selectedRouteReference", + "payingDappReference", + "expiresAt", + "instruction" + ], + "properties": { + "schema": { + "const": "mypaytag.near_1click.payable_instruction.v1" + }, + "status": { + "const": "ready" + }, + "adapter": { + "const": "near_intents_1click" + }, + "resolverReference": { + "type": "string", + "minLength": 1 + }, + "quoteId": { + "type": "string", + "minLength": 1 + }, + "selectedRouteReference": { + "type": "string", + "minLength": 1 + }, + "payingDappReference": { + "type": "string", + "minLength": 1 + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "instruction": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "payload" + ], + "properties": { + "kind": { + "const": "near_1click_payable" + }, + "payload": { + "type": "object", + "additionalProperties": false, + "required": [ + "nearQuoteReference", + "depositAddress", + "depositAsset", + "depositAmount", + "recipientAsset", + "recipientAmount", + "deadline" + ], + "properties": { + "nearQuoteReference": { + "type": "string", + "minLength": 1 + }, + "depositAddress": { + "type": "string", + "minLength": 1 + }, + "depositAsset": { + "type": "string", + "minLength": 1 + }, + "depositAmount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "recipientAsset": { + "type": "string", + "minLength": 1 + }, + "recipientAmount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "deadline": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } +} as const; + export const NearOneClickQuoteOptionSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/near-one-click-quote-option.schema.json", @@ -361,6 +467,51 @@ export const NearOneClickQuoteOptionSchema = { } } as const; +export const NearOneClickQuoteSelectionRequestSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/near-one-click-quote-selection-request.schema.json", + "title": "NearOneClickQuoteSelectionRequest", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "resolverReference", + "quoteId", + "selectedRouteReference", + "payingDappReference", + "idempotencyKey" + ], + "properties": { + "schema": { + "const": "mypaytag.near_1click.quote_selection_request.v1" + }, + "resolverReference": { + "type": "string", + "minLength": 1 + }, + "quoteId": { + "type": "string", + "minLength": 1 + }, + "selectedRouteReference": { + "type": "string", + "minLength": 1 + }, + "payingDappReference": { + "type": "string", + "minLength": 1 + }, + "idempotencyKey": { + "type": "string", + "minLength": 8 + }, + "clientReference": { + "type": "string", + "minLength": 1 + } + } +} as const; + export const NotificationEventSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/notification-event.schema.json", @@ -1469,7 +1620,9 @@ export const StatusSchema = { export const protocolSchemas = { "mypaytag-intent": MypaytagIntentSchema, + "near-one-click-payable-instruction": NearOneClickPayableInstructionSchema, "near-one-click-quote-option": NearOneClickQuoteOptionSchema, + "near-one-click-quote-selection-request": NearOneClickQuoteSelectionRequestSchema, "notification-event": NotificationEventSchema, "provider-callback-request": ProviderCallbackRequestSchema, "provider-response": ProviderResponseSchema, diff --git a/packages/protocol/src/generated/types.ts b/packages/protocol/src/generated/types.ts index 530e555..f85d107 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -2,7 +2,9 @@ // Generated from packages/protocol/schemas. Do not edit by hand. export type { MyPayTagIntent } from "./types/mypaytag-intent.js"; +export type { NearOneClickPayableInstruction } from "./types/near-one-click-payable-instruction.js"; export type { NearOneClickQuoteOption } from "./types/near-one-click-quote-option.js"; +export type { NearOneClickQuoteSelectionRequest } from "./types/near-one-click-quote-selection-request.js"; export type { NotificationEvent } from "./types/notification-event.js"; export type { ProviderCallbackRequest } from "./types/provider-callback-request.js"; export type { ProviderResponse } from "./types/provider-response.js"; diff --git a/packages/protocol/src/generated/types/near-one-click-payable-instruction.ts b/packages/protocol/src/generated/types/near-one-click-payable-instruction.ts new file mode 100644 index 0000000..363d3bc --- /dev/null +++ b/packages/protocol/src/generated/types/near-one-click-payable-instruction.ts @@ -0,0 +1,25 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/near-one-click-payable-instruction.schema.json. Do not edit by hand. + +export interface NearOneClickPayableInstruction { + schema: "mypaytag.near_1click.payable_instruction.v1"; + status: "ready"; + adapter: "near_intents_1click"; + resolverReference: string; + quoteId: string; + selectedRouteReference: string; + payingDappReference: string; + expiresAt: string; + instruction: { + kind: "near_1click_payable"; + payload: { + nearQuoteReference: string; + depositAddress: string; + depositAsset: string; + depositAmount: string; + recipientAsset: string; + recipientAmount: string; + deadline: string; + }; + }; +} diff --git a/packages/protocol/src/generated/types/near-one-click-quote-selection-request.ts b/packages/protocol/src/generated/types/near-one-click-quote-selection-request.ts new file mode 100644 index 0000000..69cf85c --- /dev/null +++ b/packages/protocol/src/generated/types/near-one-click-quote-selection-request.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/near-one-click-quote-selection-request.schema.json. Do not edit by hand. + +export interface NearOneClickQuoteSelectionRequest { + schema: "mypaytag.near_1click.quote_selection_request.v1"; + resolverReference: string; + quoteId: string; + selectedRouteReference: string; + payingDappReference: string; + idempotencyKey: string; + clientReference?: string; +} diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 773e092..842c8f1 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -5,8 +5,10 @@ import { describe, expect, it } from "vitest"; import { validateMyPayTagIntent, + validateNearOneClickPayableInstruction, validateNotificationEvent, validateNearOneClickQuoteOption, + validateNearOneClickQuoteSelectionRequest, validateProviderResponse, validateResolveRequest, validateResolveResponse, @@ -14,9 +16,11 @@ import { validateRouteQuotePreview, validateStatus, validMyPayTagIntent, + validNearOneClickPayableInstruction, validNoRouteResponse, validNotificationEvent, validNearOneClickQuoteOption, + validNearOneClickQuoteSelectionRequest, validProviderResponse, validResolveRequest, validResolvedResponse, @@ -70,6 +74,12 @@ describe("@mypaytag/protocol", () => { expect(validateNearOneClickQuoteOption(validNearOneClickQuoteOption)).toEqual( validNearOneClickQuoteOption, ); + expect(validateNearOneClickQuoteSelectionRequest(validNearOneClickQuoteSelectionRequest)).toEqual( + validNearOneClickQuoteSelectionRequest, + ); + expect(validateNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( + validNearOneClickPayableInstruction, + ); }); it("accepts every public status value and response shape", () => { @@ -287,6 +297,64 @@ describe("@mypaytag/protocol", () => { ).toThrow(); }); + it("accepts selected NEAR 1Click quotes and payable instructions", () => { + expect(validateNearOneClickQuoteSelectionRequest(validNearOneClickQuoteSelectionRequest)).toEqual( + validNearOneClickQuoteSelectionRequest, + ); + expect(validateNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( + validNearOneClickPayableInstruction, + ); + }); + + it("rejects unsafe NEAR 1Click quote selection and payable instruction payloads", () => { + expect(() => + validateNearOneClickQuoteSelectionRequest({ + ...validNearOneClickQuoteSelectionRequest, + idempotencyKey: "short", + }), + ).toThrow(); + + expect(() => + validateNearOneClickQuoteSelectionRequest({ + ...validNearOneClickQuoteSelectionRequest, + selectedRouteReference: "", + }), + ).toThrow(); + + expect(() => + validateNearOneClickPayableInstruction({ + ...validNearOneClickPayableInstruction, + adapter: "lifi", + }), + ).toThrow(); + + expect(() => + validateNearOneClickPayableInstruction({ + ...validNearOneClickPayableInstruction, + instruction: { + ...validNearOneClickPayableInstruction.instruction, + payload: { + ...validNearOneClickPayableInstruction.instruction.payload, + depositAmount: "twenty five", + }, + }, + }), + ).toThrow(); + + expect(() => + validateNearOneClickPayableInstruction({ + ...validNearOneClickPayableInstruction, + instruction: { + ...validNearOneClickPayableInstruction.instruction, + payload: { + ...validNearOneClickPayableInstruction.instruction.payload, + payToDappOptions: ["smartrust-wallet", "other-wallet"], + }, + }, + }), + ).toThrow(); + }); + it("rejects notification payloads with provider receipt or action-link fields", () => { expect(() => validateNotificationEvent({ diff --git a/packages/protocol/src/validators.ts b/packages/protocol/src/validators.ts index d736292..7c0b33c 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -4,7 +4,9 @@ import addFormats from "ajv-formats"; import { protocolSchemas } from "./generated/schemas.js"; import type { MyPayTagIntent, + NearOneClickPayableInstruction, NearOneClickQuoteOption, + NearOneClickQuoteSelectionRequest, MyPayTagStatus, NotificationEvent, ProviderCallbackRequest, @@ -90,5 +92,11 @@ export const validateRouteQuotePreview = (payload: unknown) => export const validateNearOneClickQuoteOption = (payload: unknown) => validateProtocolPayload("near-one-click-quote-option", payload); +export const validateNearOneClickQuoteSelectionRequest = (payload: unknown) => + validateProtocolPayload("near-one-click-quote-selection-request", payload); + +export const validateNearOneClickPayableInstruction = (payload: unknown) => + validateProtocolPayload("near-one-click-payable-instruction", payload); + export const isNotificationEvent = (payload: unknown): payload is NotificationEvent => isProtocolPayload("notification-event", payload); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 30db8f7..370ff05 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,17 +1,23 @@ import { + validateNearOneClickPayableInstruction, validateNearOneClickQuoteOption, + validateNearOneClickQuoteSelectionRequest, isNotificationEvent, validateNotificationEvent, validateResolveRequest, validateResolveResponse, type NotificationEvent, + type NearOneClickPayableInstruction, type NearOneClickQuoteOption, + type NearOneClickQuoteSelectionRequest, type ResolveRequest, type ResolveResponse, } from "@mypaytag/protocol"; export type ResolveRequestInput = ResolveRequest; export type NearOneClickMvpQuoteOption = NearOneClickQuoteOption; +export type NearOneClickMvpQuoteSelectionRequest = NearOneClickQuoteSelectionRequest; +export type NearOneClickMvpPayableInstruction = NearOneClickPayableInstruction; export type SupportedPath = ResolveRequest["supportedPaths"][number]; export type ResolveAmount = ResolveRequest["amount"]; export type PayorAmountExactness = "exact_send" | "exact_receive"; @@ -156,6 +162,18 @@ export function parseNearOneClickQuoteOption(payload: unknown): NearOneClickMvpQ return validateNearOneClickQuoteOption(payload); } +export function buildNearOneClickQuoteSelectionRequest( + input: NearOneClickMvpQuoteSelectionRequest, +): NearOneClickMvpQuoteSelectionRequest { + return validateNearOneClickQuoteSelectionRequest(input); +} + +export function parseNearOneClickPayableInstruction( + payload: unknown, +): NearOneClickMvpPayableInstruction { + return validateNearOneClickPayableInstruction(payload); +} + export function isMyPayTagNotification(payload: unknown): payload is NotificationEvent { return isNotificationEvent(payload); } diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 6b3299f..23df5a4 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -2,7 +2,9 @@ import { describe, expect, it } from "vitest"; import { validNoRouteResponse, + validNearOneClickPayableInstruction, validNearOneClickQuoteOption, + validNearOneClickQuoteSelectionRequest, validNotificationEvent, validResolvedResponse, validResolveRequest, @@ -12,6 +14,7 @@ import { import { buildResolveRequest, buildAmountValue, + buildNearOneClickQuoteSelectionRequest, buildPayorAppReference, buildPayorAppResolveRequest, buildSupportedPath, @@ -22,6 +25,7 @@ import { parseNotificationEvent, parseResolveResponse, requestExecutionQuotes, + parseNearOneClickPayableInstruction, parseNearOneClickQuoteOption, type CryptoNativeExecutionSolverId, type ExecutionQuoteProvider, @@ -133,6 +137,15 @@ describe("@mypaytag/sdk", () => { ); }); + it("builds selected NEAR 1Click quote requests and parses payable instructions", () => { + expect(buildNearOneClickQuoteSelectionRequest(validNearOneClickQuoteSelectionRequest)).toEqual( + validNearOneClickQuoteSelectionRequest, + ); + expect(parseNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( + validNearOneClickPayableInstruction, + ); + }); + it("requests execution quotes from every configured solver when none is preferred", async () => { const calls: CryptoNativeExecutionSolverId[] = []; const providers = [ From fddfb8c9557d459c67bd7d871a848f89e02ade8e Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:29:29 -0400 Subject: [PATCH 13/21] Move generic solver fanout to phase 2 --- agent-context/session-log/main.md | 11 ++++++++++ agent-context/todo.md | 2 +- .../protocol-and-sdk-architecture.md | 13 +++++++----- docs/integration/paying-dapps.md | 19 ++++++++++++++---- packages/sdk/src/index.ts | 13 +++++++----- packages/sdk/src/sdk.test.ts | 20 +++++++++---------- packages/testing/src/index.ts | 16 +++++++-------- packages/testing/src/testing.test.ts | 10 +++++----- 8 files changed, 66 insertions(+), 38 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 38849cc..b0d0308 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -336,3 +336,14 @@ todo: mypaytag-sdk:GPTS-S7-T2 summary: Added NEAR 1Click quote selection and payable instruction schemas, generated protocol exports, fixtures, validators, negative tests, and PayingDapp SDK builder/parser helpers. validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/sdk typecheck, and pnpm scan:public-boundary; focused tests passed with 51 workspace cases and the public-boundary scan passed. follow-ups: Continue Sprint 7 by moving generic solver fanout wording and tests under Phase 2/non-MVP positioning. + +--- + +## 2026-06-28-gpts-s7-t3 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T3 +summary: Moved generic execution quote fanout to Phase 2 by removing NEAR 1Click from the generic solver id list, keeping NEAR on the dedicated MVP quote/selection/payable contracts, and updating tests, fixtures, and docs to describe fanout as non-MVP extension behavior. +validation: Ran pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm scan:public-boundary; focused tests passed with 51 workspace cases and the public-boundary scan passed. +follow-ups: Continue Sprint 7 by completing provider callback binding fields and conformance checks. diff --git a/agent-context/todo.md b/agent-context/todo.md index 726b311..b04e508 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -508,7 +508,7 @@ Acceptance notes: ### GPTS-S7-T3 Move Generic Solver Fanout To Phase 2 -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t3 Depends on: mypaytag-sdk:GPTS-S7-T1 diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 43ff063..13d4801 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -41,29 +41,32 @@ The MVP protocol does not support: - wallet graph lookup, - direct address/account registration, - resolver-built Modality A intents, -- swaps, bridges, streams, recurring payments, refunds, or settlement guarantees, +- generic swaps, generic bridges, streams, recurring payments, refunds, or settlement guarantees outside the dedicated MVP NEAR 1Click contract, - ERC-681, Solana Pay, WalletConnect Pay, ENS, FIO, Request Network, Stripe, Circle, Coinbase Commerce, or hosted payment link rendering, - public profile or directory APIs, - notification inbox APIs, marketing notification APIs, or non-Cubid notification provider APIs. -## Future Extension: Crypto-Native Execution Solvers +## MVP NEAR 1Click Execution NEAR Intents / 1Click is the Phase 1/MVP execution adapter for SmarTrust swap/bridge Paytag payments. It consumes a resolved receive requirement and returns a NEAR 1Click quote option scoped to the selected PayToDapp route. It does not decide which PayToDapp the recipient prefers. +The MVP SDK surface for this path is the dedicated `NearOneClickMvpQuoteOption`, `NearOneClickMvpQuoteSelectionRequest`, and `NearOneClickMvpPayableInstruction` contract family. MVP PayingDapps should not use generic quote fanout to obtain NEAR 1Click payable instructions. + +## Future Extension: Crypto-Native Execution Solvers + LI.FI, Squid, 0x, Across, LayerZero/Stargate, broad solver fanout, and generic external adapter support are Phase 2 extensions. They remain useful architecture context, but they are not required for the MVP resolve path. -Short list for SDK adapter support: +Short list for Phase 2 SDK adapter support: | Solver/router | Best fit | | --- | --- | -| NEAR Intents / 1Click | Crypto-to-crypto cross-chain swaps, stablecoin delivery, distribution-channel fees, and default solver-style execution. | | LI.FI | EVM and Solana cross-chain routing, wallet-controlled execution, bridge/DEX aggregation, and quote responses with wallet-ready transaction requests. | | Squid | Broad chain coverage, cross-chain swaps, bridges, contract calls, and Cosmos/Axelar-style routing. | | 0x Cross-Chain API | Cross-chain payments, EVM/Solana routing, stablecoin settlement, fast quote responses, fallback paths, and progress tracking. | | Across | Fast bridge-focused EVM/L2 stablecoin transfers where supported. | | LayerZero Value Transfer API / Stargate | Cross-chain token transfer for OFT, LayerZero ecosystem assets, and routes where Stargate coverage is strong. | -`@mypaytag/sdk` may expose a provider interface for these quote sources as non-MVP extension helpers. When a payor-app passes a preferred solver id, the SDK asks only that quote provider. When no preferred solver id is selected, the SDK fans out quote requests to every configured quote provider and returns the successful quotes for app-side display or future extension selection. +`@mypaytag/sdk` exposes a provider interface for these quote sources as non-MVP extension helpers. When a payor-app passes a preferred solver id, the SDK asks only that quote provider. When no preferred solver id is selected, the SDK fans out quote requests to every configured quote provider and returns the successful quotes for app-side display or future extension selection. ## Future Extension: Route Query And Quote Contracts diff --git a/docs/integration/paying-dapps.md b/docs/integration/paying-dapps.md index 1da9f06..42d9f5a 100644 --- a/docs/integration/paying-dapps.md +++ b/docs/integration/paying-dapps.md @@ -46,13 +46,24 @@ MVP payment instructions use `provider_json` with a typed provider destination: Do not infer reusable wallet details from this destination. It belongs to the selected one-time provider intent. +## MVP NEAR 1Click Quote Flow + +The MVP resolve flow can return a NEAR Intents / 1Click quote option for +SmarTrust swap/bridge paytag payments. PayingDapps parse that quote with +`parseNearOneClickQuoteOption`, confirm the selected route with +`buildNearOneClickQuoteSelectionRequest`, and parse the returned payable +instruction with `parseNearOneClickPayableInstruction`. + +This MVP path is not generic solver fanout. PayingDapps do not need to +configure LI.FI, Squid, 0x, Across, LayerZero/Stargate, or other broad +execution providers to support the Phase 1 NEAR 1Click flow. + ## Future Extension: Crypto-Native Execution Quotes -The MVP resolve flow can return a NEAR Intents / 1Click quote option for SmarTrust swap/bridge Paytag payments. Broad execution quote fanout is Phase 2. When a PayingDapp supports future execution adapters, configure quote providers for the -solver/router surfaces the app can use. The initial MyPayTag SDK solver ids -are: +Broad execution quote fanout is Phase 2. When a PayingDapp supports future +execution adapters, configure quote providers for the solver/router surfaces +the app can use. The Phase 2 MyPayTag SDK solver ids are: -- `near_intents_1click` - `lifi` - `squid` - `zero_x_cross_chain` diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 370ff05..7478b23 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -49,7 +49,6 @@ export type ActionRequiredResponse = Extract { ); }); - it("requests execution quotes from every configured solver when none is preferred", async () => { + it("requests Phase 2 extension quotes from every configured solver when none is preferred", async () => { const calls: CryptoNativeExecutionSolverId[] = []; const providers = [ - createQuoteProvider("near_intents_1click", calls), createQuoteProvider("lifi", calls), createQuoteProvider("squid", calls), + createQuoteProvider("across", calls), ]; const quotes = await requestExecutionQuotes({ @@ -159,16 +159,16 @@ describe("@mypaytag/sdk", () => { request: quoteRequest, }); - expect(calls).toEqual(["near_intents_1click", "lifi", "squid"]); - expect(quotes.map((quote) => quote.solverId)).toEqual(["near_intents_1click", "lifi", "squid"]); + expect(calls).toEqual(["lifi", "squid", "across"]); + expect(quotes.map((quote) => quote.solverId)).toEqual(["lifi", "squid", "across"]); }); - it("requests an execution quote only from the preferred solver when provided", async () => { + it("requests a Phase 2 extension quote only from the preferred solver when provided", async () => { const calls: CryptoNativeExecutionSolverId[] = []; const providers = [ - createQuoteProvider("near_intents_1click", calls), createQuoteProvider("lifi", calls), createQuoteProvider("squid", calls), + createQuoteProvider("across", calls), ]; const quotes = await requestExecutionQuotes({ @@ -181,10 +181,10 @@ describe("@mypaytag/sdk", () => { expect(quotes.map((quote) => quote.solverId)).toEqual(["lifi"]); }); - it("returns successful quotes when some configured solvers fail", async () => { + it("returns successful Phase 2 extension quotes when some configured solvers fail", async () => { const calls: CryptoNativeExecutionSolverId[] = []; const providers = [ - createQuoteProvider("near_intents_1click", calls, { fail: true }), + createQuoteProvider("zero_x_cross_chain", calls, { fail: true }), createQuoteProvider("lifi", calls), createQuoteProvider("squid", calls, { fail: true }), ]; @@ -194,14 +194,14 @@ describe("@mypaytag/sdk", () => { request: quoteRequest, }); - expect(calls).toEqual(["near_intents_1click", "lifi", "squid"]); + expect(calls).toEqual(["zero_x_cross_chain", "lifi", "squid"]); expect(quotes.map((quote) => quote.solverId)).toEqual(["lifi"]); }); it("throws when every configured quote provider fails", async () => { await expect(requestExecutionQuotes({ providers: [ - createQuoteProvider("near_intents_1click", [], { fail: true }), + createQuoteProvider("zero_x_cross_chain", [], { fail: true }), createQuoteProvider("lifi", [], { fail: true }), ], request: quoteRequest, diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index fa25ef1..d696787 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -77,8 +77,8 @@ export interface MyPayTagFixtures { executionQuotes: { exactSendRequest: ExecutionQuoteRequest; exactReceiveRequest: ExecutionQuoteRequest; - near: ExecutionQuote; lifi: ExecutionQuote; + squid: ExecutionQuote; }; intent: MyPayTagIntent; } @@ -218,13 +218,6 @@ export const myPayTagFixtures: MyPayTagFixtures = { recipient: "eip155:8453:0x0000000000000000000000000000000000000001", reference: "example-payor:exact-receive", }, - near: { - solverId: "near_intents_1click", - quoteId: "quote_near_fixture", - sendAmount: "25.18", - receiveAmount: "25.00", - estimatedDurationSeconds: 20, - }, lifi: { solverId: "lifi", quoteId: "quote_lifi_fixture", @@ -236,6 +229,13 @@ export const myPayTagFixtures: MyPayTagFixtures = { data: "0x", }, }, + squid: { + solverId: "squid", + quoteId: "quote_squid_fixture", + sendAmount: "25.22", + receiveAmount: "25.00", + estimatedDurationSeconds: 60, + }, }, intent: validMyPayTagIntent, }; diff --git a/packages/testing/src/testing.test.ts b/packages/testing/src/testing.test.ts index 9e9b9dc..feb925d 100644 --- a/packages/testing/src/testing.test.ts +++ b/packages/testing/src/testing.test.ts @@ -85,11 +85,11 @@ describe("@mypaytag/testing", () => { expect(createPaymentIntentCreatedNotification().eventType).toBe("payment_intent_created"); }); - it("provides mock quote providers for preferred-solver and fanout tests", async () => { + it("provides Phase 2 mock quote providers for preferred-solver and fanout tests", async () => { const providers = [ - createMockExecutionQuoteProvider({ solverId: "near_intents_1click" }), createMockExecutionQuoteProvider({ solverId: "lifi" }), createMockExecutionQuoteProvider({ solverId: "squid", failWith: "provider_unavailable" }), + createMockExecutionQuoteProvider({ solverId: "across" }), ]; await expect(requestExecutionQuotes({ @@ -107,10 +107,10 @@ describe("@mypaytag/testing", () => { request: myPayTagFixtures.executionQuotes.exactSendRequest, })).resolves.toMatchObject([ { - solverId: "near_intents_1click", + solverId: "lifi", }, { - solverId: "lifi", + solverId: "across", }, ]); }); @@ -119,7 +119,7 @@ describe("@mypaytag/testing", () => { await expect(requestExecutionQuotes({ providers: [ createMockExecutionQuoteProvider({ - solverId: "near_intents_1click", + solverId: "zero_x_cross_chain", failWith: "provider_unavailable", }), createMockExecutionQuoteProvider({ From 558e5a0e7e017b9eebfec78d6cc4c903665b7e4c Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:33:22 -0400 Subject: [PATCH 14/21] Bind provider callbacks to resolver context --- agent-context/session-log/main.md | 11 +++++ agent-context/todo.md | 2 +- api/openapi.yaml | 30 +++++++++++++ api/postman_collection.json | 2 +- docs/engineering/mvp-api-contracts.md | 14 +++++++ .../protocol-and-sdk-architecture.md | 7 +++- .../provider-callback-request.schema.json | 3 +- .../schemas/provider-response.schema.json | 19 ++++++++- packages/protocol/src/fixtures.ts | 5 +++ packages/protocol/src/generated/schemas.ts | 25 +++++++++++ .../types/provider-callback-request.ts | 1 + .../src/generated/types/provider-response.ts | 4 ++ packages/protocol/src/protocol.test.ts | 25 +++++++++++ packages/provider-sdk/src/index.ts | 7 +++- .../src/provider-conformance.test.ts | 42 ++++++++++++++++++- .../provider-sdk/src/provider-sdk.test.ts | 23 ++++++++++ 16 files changed, 213 insertions(+), 7 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index b0d0308..5644550 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -347,3 +347,14 @@ todo: mypaytag-sdk:GPTS-S7-T3 summary: Moved generic execution quote fanout to Phase 2 by removing NEAR 1Click from the generic solver id list, keeping NEAR on the dedicated MVP quote/selection/payable contracts, and updating tests, fixtures, and docs to describe fanout as non-MVP extension behavior. validation: Ran pnpm --filter @mypaytag/sdk typecheck, pnpm --filter @mypaytag/sdk test, pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm scan:public-boundary; focused tests passed with 51 workspace cases and the public-boundary scan passed. follow-ups: Continue Sprint 7 by completing provider callback binding fields and conformance checks. + +--- + +## 2026-06-28-gpts-s7-t4 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T4 +summary: Added PayingDapp reference to provider callbacks, added resolver/purpose/PayingDapp binding fields to provider payloads, tightened provider SDK response matching, refreshed OpenAPI/Postman surfaces, and expanded protocol/provider conformance tests for required fields and mismatch rejection. +validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/provider-sdk typecheck, pnpm --filter @mypaytag/provider-sdk test, pnpm --filter @mypaytag/protocol test, pnpm api:validate, pnpm scan:public-boundary, and pnpm api:postman; focused tests passed with 53 workspace cases and OpenAPI/public-boundary checks passed. +follow-ups: Continue Sprint 7 by closing the remaining OpenAPI/protocol fixture drift, especially notification event schema parity. diff --git a/agent-context/todo.md b/agent-context/todo.md index b04e508..bae4ade 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -524,7 +524,7 @@ Acceptance notes: ### GPTS-S7-T4 Complete Provider Callback Binding Contract -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t4 Depends on: mypaytag-sdk:GPTS-S6-T2, mypaytag:GPTR-S7-T5 diff --git a/api/openapi.yaml b/api/openapi.yaml index 2c9e881..b41ceeb 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -130,6 +130,9 @@ paths: provider: example-wallet payload: providerIntentId: provider_pi_456 + resolverReference: mpt_req_123 + payingDappId: sender + payingDappReference: sender:payout_987 chain: base network: mainnet asset: USDC @@ -137,6 +140,7 @@ paths: kind: blockchain_address recipientAddress: "0xabc0000000000000000000000000000000000000" amount: "25.00" + purpose: payout reference: example-wallet:provider_pi_456 expiresAt: "2026-06-24T20:00:00Z" references: @@ -722,17 +726,30 @@ components: additionalProperties: true required: - providerIntentId + - resolverReference + - payingDappId + - payingDappReference - chain - network - asset - destination - amount + - purpose - reference - expiresAt properties: providerIntentId: type: string minLength: 1 + resolverReference: + type: string + minLength: 1 + payingDappId: + type: string + minLength: 1 + payingDappReference: + type: string + minLength: 1 chain: type: string minLength: 1 @@ -747,6 +764,9 @@ components: amount: type: string pattern: "^[0-9]+(\\.[0-9]+)?$" + purpose: + type: string + minLength: 1 reference: type: string minLength: 1 @@ -956,8 +976,11 @@ components: - resolverRequestId - recipient - payingDappId + - payingDappReference - selectedPath - amount + - purpose + - expiresAt properties: resolverRequestId: type: string @@ -965,10 +988,17 @@ components: $ref: "#/components/schemas/RecipientAlias" payingDappId: type: string + payingDappReference: + type: string selectedPath: $ref: "#/components/schemas/RoutePath" amount: $ref: "#/components/schemas/Amount" + purpose: + type: string + expiresAt: + type: string + format: date-time ProviderResponse: type: object additionalProperties: false diff --git a/api/postman_collection.json b/api/postman_collection.json index 1e908e1..ef73bdc 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -261,7 +261,7 @@ "value": "application/json" } ], - "body": "{\n \"status\": \"\",\n \"intent\": {\n \"id\": \"\",\n \"schema\": \"\",\n \"status\": \"\",\n \"modality\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifierHash\": \"\"\n },\n \"selectedRoute\": {\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"expiresAt\": \"\",\n \"singleUse\": \"\",\n \"paymentInstruction\": {\n \"type\": \"\",\n \"provider\": \"\",\n \"payload\": {\n \"providerIntentId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"destination\": {\n \"kind\": \"\",\n \"recipientAddress\": \"\"\n },\n \"amount\": \"\",\n \"reference\": \"\",\n \"expiresAt\": \"\"\n }\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n }\n }\n}", + "body": "{\n \"status\": \"\",\n \"intent\": {\n \"id\": \"\",\n \"schema\": \"\",\n \"status\": \"\",\n \"modality\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"identifierHash\": \"\"\n },\n \"selectedRoute\": {\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"expiresAt\": \"\",\n \"singleUse\": \"\",\n \"paymentInstruction\": {\n \"type\": \"\",\n \"provider\": \"\",\n \"payload\": {\n \"providerIntentId\": \"\",\n \"resolverReference\": \"\",\n \"payingDappId\": \"\",\n \"payingDappReference\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"destination\": {\n \"kind\": \"\",\n \"recipientAddress\": \"\"\n },\n \"amount\": \"\",\n \"purpose\": \"\",\n \"reference\": \"\",\n \"expiresAt\": \"\"\n }\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index 4f8000a..de5bbbf 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -292,6 +292,7 @@ Request: "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "selectedPath": { "chain": "base", "network": "mainnet", @@ -317,6 +318,9 @@ Expected provider response: "provider": "smartrust-wallet", "payload": { "providerIntentId": "st_pi_456", + "resolverReference": "mpt_req_123", + "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "chain": "base", "network": "mainnet", "asset": "USDC", @@ -325,6 +329,7 @@ Expected provider response: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } @@ -338,6 +343,7 @@ Rules: - Public conformance requires signature or equivalent auth metadata, timestamp, nonce, request expiry, and `resolverRequestId`. - Providers must reject callbacks outside the allowed clock-skew window, repeated nonce/timestamp combinations, and expired requests. - Repeated callbacks with the same `resolverRequestId` must be handled idempotently. +- Provider responses must bind back to the callback through resolver reference, PayingDapp id, PayingDapp reference, selected route, amount, purpose, expiry, and provider intent id. - Provider response payloads are preserved inside `paymentInstruction.payload`. - External payment protocol rendering is out of scope for MVP. @@ -372,6 +378,9 @@ Rules: "provider": "smartrust-wallet", "payload": { "providerIntentId": "st_pi_456", + "resolverReference": "mpt_req_123", + "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "chain": "base", "network": "mainnet", "asset": "USDC", @@ -380,6 +389,7 @@ Rules: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } @@ -403,11 +413,15 @@ Rules: Required `provider_json.payload` keys: - `providerIntentId` +- `resolverReference` +- `payingDappId` +- `payingDappReference` - `chain` - `network` - `asset` - `destination` - `amount` +- `purpose` - `reference` - `expiresAt` diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 13d4801..8e708e2 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -160,6 +160,7 @@ Callback request: "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "selectedPath": { "chain": "base", "network": "mainnet", @@ -211,6 +212,9 @@ Resolved response shape: "provider": "smartrust-wallet", "payload": { "providerIntentId": "st_pi_456", + "resolverReference": "mpt_req_123", + "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "chain": "base", "network": "mainnet", "asset": "USDC", @@ -219,6 +223,7 @@ Resolved response shape: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } @@ -232,7 +237,7 @@ Resolved response shape: } ``` -The protocol validates the MyPayTag envelope and the required `provider_json.payload` keys defined in `mvp-api-contracts.md`, including `providerIntentId`, `chain`, `network`, `asset`, `destination`, `amount`, `reference`, and `expiresAt`. MVP provider destinations use `destination.kind = "blockchain_address"` with a nested `recipientAddress`; top-level address fields are rejected. The protocol does not render external protocol formats in the MVP. +The protocol validates the MyPayTag envelope and the required `provider_json.payload` keys defined in `mvp-api-contracts.md`, including `providerIntentId`, `resolverReference`, `payingDappId`, `payingDappReference`, `chain`, `network`, `asset`, `destination`, `amount`, `purpose`, `reference`, and `expiresAt`. MVP provider destinations use `destination.kind = "blockchain_address"` with a nested `recipientAddress`; top-level address fields are rejected. The protocol does not render external protocol formats in the MVP. ## Status Values diff --git a/packages/protocol/schemas/provider-callback-request.schema.json b/packages/protocol/schemas/provider-callback-request.schema.json index 2a5a6d0..dc45500 100644 --- a/packages/protocol/schemas/provider-callback-request.schema.json +++ b/packages/protocol/schemas/provider-callback-request.schema.json @@ -4,7 +4,7 @@ "title": "ProviderCallbackRequest", "type": "object", "additionalProperties": false, - "required": ["resolverRequestId", "recipient", "payingDappId", "selectedPath", "amount", "purpose", "expiresAt"], + "required": ["resolverRequestId", "recipient", "payingDappId", "payingDappReference", "selectedPath", "amount", "purpose", "expiresAt"], "properties": { "resolverRequestId": { "type": "string", "minLength": 1 }, "recipient": { @@ -17,6 +17,7 @@ } }, "payingDappId": { "type": "string", "minLength": 1 }, + "payingDappReference": { "type": "string", "minLength": 1 }, "selectedPath": { "$ref": "#/$defs/path" }, "amount": { "$ref": "#/$defs/amount" }, "purpose": { "type": "string", "minLength": 1 }, diff --git a/packages/protocol/schemas/provider-response.schema.json b/packages/protocol/schemas/provider-response.schema.json index 0bd3635..a5e7eb0 100644 --- a/packages/protocol/schemas/provider-response.schema.json +++ b/packages/protocol/schemas/provider-response.schema.json @@ -23,7 +23,20 @@ "providerPayload": { "type": "object", "additionalProperties": true, - "required": ["providerIntentId", "chain", "network", "asset", "destination", "amount", "reference", "expiresAt"], + "required": [ + "providerIntentId", + "resolverReference", + "payingDappId", + "payingDappReference", + "chain", + "network", + "asset", + "destination", + "amount", + "purpose", + "reference", + "expiresAt" + ], "not": { "anyOf": [ { "required": ["recipientAddress"] }, @@ -33,6 +46,9 @@ }, "properties": { "providerIntentId": { "type": "string", "minLength": 1 }, + "resolverReference": { "type": "string", "minLength": 1 }, + "payingDappId": { "type": "string", "minLength": 1 }, + "payingDappReference": { "type": "string", "minLength": 1 }, "chain": { "type": "string", "minLength": 1 }, "network": { "type": "string", "minLength": 1 }, "asset": { "type": "string", "minLength": 1 }, @@ -46,6 +62,7 @@ } }, "amount": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "purpose": { "type": "string", "minLength": 1 }, "reference": { "type": "string", "minLength": 1 }, "expiresAt": { "type": "string", "format": "date-time" } } diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 022e31a..3e15bd6 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -71,6 +71,7 @@ export const validProviderCallbackRequest: ProviderCallbackRequest = { paytagReference: "paytag_ref_abc", }, payingDappId: "chaincrew", + payingDappReference: "chaincrew:payout_987", selectedPath: { chain: "base", network: "mainnet", @@ -92,6 +93,9 @@ export const validProviderResponse: ProviderResponse = { provider: "smartrust-wallet", payload: { providerIntentId: "st_pi_456", + resolverReference: "mpt_req_123", + payingDappId: "chaincrew", + payingDappReference: "chaincrew:payout_987", chain: "base", network: "mainnet", asset: "USDC", @@ -100,6 +104,7 @@ export const validProviderResponse: ProviderResponse = { recipientAddress: "0xabc123", }, amount: "25.00", + purpose: "payout", reference: "smartrust:st_pi_456", expiresAt: "2026-06-24T20:00:00Z", }, diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index 20ddb33..c305364 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -616,6 +616,7 @@ export const ProviderCallbackRequestSchema = { "resolverRequestId", "recipient", "payingDappId", + "payingDappReference", "selectedPath", "amount", "purpose", @@ -647,6 +648,10 @@ export const ProviderCallbackRequestSchema = { "type": "string", "minLength": 1 }, + "payingDappReference": { + "type": "string", + "minLength": 1 + }, "selectedPath": { "$ref": "#/$defs/path" }, @@ -754,11 +759,15 @@ export const ProviderResponseSchema = { "additionalProperties": true, "required": [ "providerIntentId", + "resolverReference", + "payingDappId", + "payingDappReference", "chain", "network", "asset", "destination", "amount", + "purpose", "reference", "expiresAt" ], @@ -786,6 +795,18 @@ export const ProviderResponseSchema = { "type": "string", "minLength": 1 }, + "resolverReference": { + "type": "string", + "minLength": 1 + }, + "payingDappId": { + "type": "string", + "minLength": 1 + }, + "payingDappReference": { + "type": "string", + "minLength": 1 + }, "chain": { "type": "string", "minLength": 1 @@ -819,6 +840,10 @@ export const ProviderResponseSchema = { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "purpose": { + "type": "string", + "minLength": 1 + }, "reference": { "type": "string", "minLength": 1 diff --git a/packages/protocol/src/generated/types/provider-callback-request.ts b/packages/protocol/src/generated/types/provider-callback-request.ts index 3e52cb1..54c70d9 100644 --- a/packages/protocol/src/generated/types/provider-callback-request.ts +++ b/packages/protocol/src/generated/types/provider-callback-request.ts @@ -8,6 +8,7 @@ export interface ProviderCallbackRequest { paytagReference: string; }; payingDappId: string; + payingDappReference: string; selectedPath: Path; amount: Amount; purpose: string; diff --git a/packages/protocol/src/generated/types/provider-response.ts b/packages/protocol/src/generated/types/provider-response.ts index ca616bb..657a033 100644 --- a/packages/protocol/src/generated/types/provider-response.ts +++ b/packages/protocol/src/generated/types/provider-response.ts @@ -12,6 +12,9 @@ export interface ProviderResponse { } export interface ProviderPayload { providerIntentId: string; + resolverReference: string; + payingDappId: string; + payingDappReference: string; chain: string; network: string; asset: string; @@ -20,6 +23,7 @@ export interface ProviderPayload { recipientAddress: string; }; amount: string; + purpose: string; reference: string; expiresAt: string; [k: string]: unknown; diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 842c8f1..2d6a56c 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -21,6 +21,7 @@ import { validNotificationEvent, validNearOneClickQuoteOption, validNearOneClickQuoteSelectionRequest, + validProviderCallbackRequest, validProviderResponse, validResolveRequest, validResolvedResponse, @@ -47,15 +48,30 @@ const actionStatusFixtures = [ const providerPayloadRequiredFields = [ "providerIntentId", + "resolverReference", + "payingDappId", + "payingDappReference", "chain", "network", "asset", "destination", "amount", + "purpose", "reference", "expiresAt", ] as const; +const providerCallbackRequiredFields = [ + "resolverRequestId", + "recipient", + "payingDappId", + "payingDappReference", + "selectedPath", + "amount", + "purpose", + "expiresAt", +] as const; + describe("@mypaytag/protocol", () => { it("accepts valid MVP fixtures", () => { expect(validateRouteRegistrationRequest(validRouteRegistrationRequest)).toEqual( @@ -204,6 +220,15 @@ describe("@mypaytag/protocol", () => { } }); + it("rejects provider callbacks missing binding fields", () => { + for (const field of providerCallbackRequiredFields) { + const callback = { ...validProviderCallbackRequest }; + delete callback[field]; + + expect(() => validateProviderCallbackRequest(callback)).toThrow(); + } + }); + it("rejects provider payloads that expose address-like fields inside MyPayTag intents", () => { for (const field of ["recipientAddress", "address", "account"] as const) { expect(() => diff --git a/packages/provider-sdk/src/index.ts b/packages/provider-sdk/src/index.ts index c2312d0..cfe511b 100644 --- a/packages/provider-sdk/src/index.ts +++ b/packages/provider-sdk/src/index.ts @@ -87,7 +87,12 @@ export function assertProviderResponseMatchesCallback( payload.network !== callback.selectedPath.network || payload.asset !== callback.selectedPath.asset || payload.amount !== callback.amount.value || - payload.providerIntentId !== response.providerIntentId + payload.providerIntentId !== response.providerIntentId || + payload.resolverReference !== callback.resolverRequestId || + payload.payingDappId !== callback.payingDappId || + payload.payingDappReference !== callback.payingDappReference || + payload.purpose !== callback.purpose || + payload.expiresAt !== callback.expiresAt ) { throw new Error("Provider response does not match callback request"); } diff --git a/packages/provider-sdk/src/provider-conformance.test.ts b/packages/provider-sdk/src/provider-conformance.test.ts index a59c3da..de99696 100644 --- a/packages/provider-sdk/src/provider-conformance.test.ts +++ b/packages/provider-sdk/src/provider-conformance.test.ts @@ -136,7 +136,7 @@ describe("PayToDapp provider conformance", () => { } }); - it("rejects provider responses that do not match callback path, amount, or idempotency identity", () => { + it("rejects provider responses that do not match callback route, amount, or binding identity", () => { const malformedResponses: ProviderResponse[] = [ { ...validProviderResponse, @@ -168,6 +168,46 @@ describe("PayToDapp provider conformance", () => { }, }, }, + { + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload: { + ...validProviderResponse.paymentInstruction.payload, + resolverReference: "mpt_req_other", + }, + }, + }, + { + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload: { + ...validProviderResponse.paymentInstruction.payload, + payingDappReference: "chaincrew:payout_other", + }, + }, + }, + { + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload: { + ...validProviderResponse.paymentInstruction.payload, + purpose: "refund", + }, + }, + }, + { + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload: { + ...validProviderResponse.paymentInstruction.payload, + expiresAt: "2026-06-24T21:00:00Z", + }, + }, + }, ]; for (const response of malformedResponses) { diff --git a/packages/provider-sdk/src/provider-sdk.test.ts b/packages/provider-sdk/src/provider-sdk.test.ts index 4706d55..cfef8cc 100644 --- a/packages/provider-sdk/src/provider-sdk.test.ts +++ b/packages/provider-sdk/src/provider-sdk.test.ts @@ -67,6 +67,29 @@ describe("@mypaytag/provider-sdk", () => { ).toThrow(); }); + it("rejects provider response binding mismatches", () => { + for (const payloadOverride of [ + { resolverReference: "mpt_req_other" }, + { payingDappId: "other-paying-dapp" }, + { payingDappReference: "chaincrew:payout_other" }, + { purpose: "refund" }, + { expiresAt: "2026-06-24T21:00:00Z" }, + ] as const) { + expect(() => + assertProviderResponseMatchesCallback(validProviderCallbackRequest, { + ...validProviderResponse, + paymentInstruction: { + ...validProviderResponse.paymentInstruction, + payload: { + ...validProviderResponse.paymentInstruction.payload, + ...payloadOverride, + }, + }, + }), + ).toThrow(); + } + }); + it("exposes auth and replay integration hooks", async () => { const replayStore = createMemoryReplayStore(); const verifier = { verify: () => true }; From 213a3fee44670493bf0c60182dd41231baf4fbac Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:36:23 -0400 Subject: [PATCH 15/21] Align OpenAPI with protocol examples --- agent-context/session-log/main.md | 11 +++ agent-context/todo.md | 2 +- api/openapi.yaml | 114 +++++++++++++++++++++---- api/postman_collection.json | 8 +- package.json | 3 +- packages/protocol/src/protocol.test.ts | 47 +++++----- pnpm-lock.yaml | 31 ++++--- 7 files changed, 157 insertions(+), 59 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 5644550..574a32a 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -358,3 +358,14 @@ todo: mypaytag-sdk:GPTS-S7-T4 summary: Added PayingDapp reference to provider callbacks, added resolver/purpose/PayingDapp binding fields to provider payloads, tightened provider SDK response matching, refreshed OpenAPI/Postman surfaces, and expanded protocol/provider conformance tests for required fields and mismatch rejection. validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/provider-sdk typecheck, pnpm --filter @mypaytag/provider-sdk test, pnpm --filter @mypaytag/protocol test, pnpm api:validate, pnpm scan:public-boundary, and pnpm api:postman; focused tests passed with 53 workspace cases and OpenAPI/public-boundary checks passed. follow-ups: Continue Sprint 7 by closing the remaining OpenAPI/protocol fixture drift, especially notification event schema parity. + +--- + +## 2026-06-28-gpts-s7-t5 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T5 +summary: Aligned OpenAPI notification events with protocol `eventType`/`schema`/`recipient`/`references`/`action` shape, added provider callback/response examples, removed NEAR from the generic SolverId enum, and replaced the brittle OpenAPI string-presence test with validator-backed example checks. +validation: Ran pnpm api:validate, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/provider-sdk test, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 53 workspace cases and OpenAPI/Postman/public-boundary checks passed. +follow-ups: Continue Sprint 7 by adding route CRUD protocol schemas and SDK helpers. diff --git a/agent-context/todo.md b/agent-context/todo.md index bae4ade..2e40d9b 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -540,7 +540,7 @@ Acceptance notes: ### GPTS-S7-T5 Align OpenAPI With Protocol Schemas And Fixtures -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t5 Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T4 diff --git a/api/openapi.yaml b/api/openapi.yaml index b41ceeb..61be960 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -347,13 +347,20 @@ paths: examples: paymentIntentCreated: value: - event: payment_intent_created - recipientDisplay: r***@example.com + eventType: payment_intent_created + schema: mypaytag.notification.v1 + recipient: + identifierType: paytag + maskedDisplay: r***@example.com amount: value: "25.00" currency: USDC - resolverRequestId: mpt_req_123 - payingDappReference: sender:payout_987 + references: + resolverReference: mpt_req_123 + providerReference: provider_pi_456 + payingDappReference: sender:payout_987 + action: + type: none responses: "202": description: Notification accepted for delivery. @@ -382,6 +389,24 @@ webhooks: application/json: schema: $ref: "#/components/schemas/ProviderCallbackRequest" + examples: + providerCallback: + value: + resolverRequestId: mpt_req_123 + recipient: + identifierType: paytag + paytagReference: paytag_ref_abc + payingDappId: chaincrew + payingDappReference: chaincrew:payout_987 + selectedPath: + chain: base + network: mainnet + asset: USDC + amount: + value: "25.00" + currency: USDC + purpose: payout + expiresAt: "2026-06-24T20:00:00Z" responses: "200": description: Provider intent response. @@ -389,6 +414,29 @@ webhooks: application/json: schema: $ref: "#/components/schemas/ProviderResponse" + examples: + providerResponse: + value: + providerIntentId: st_pi_456 + status: ready + paymentInstruction: + type: provider_json + provider: smartrust-wallet + payload: + providerIntentId: st_pi_456 + resolverReference: mpt_req_123 + payingDappId: chaincrew + payingDappReference: chaincrew:payout_987 + chain: base + network: mainnet + asset: USDC + destination: + kind: blockchain_address + recipientAddress: "0xabc0000000000000000000000000000000000000" + amount: "25.00" + purpose: payout + reference: smartrust:st_pi_456 + expiresAt: "2026-06-24T20:00:00Z" components: responses: BadRequest: @@ -1019,23 +1067,60 @@ components: type: object additionalProperties: false required: - - event - - recipientDisplay + - eventType + - schema + - recipient - amount - - resolverRequestId - - payingDappReference + - references + - action properties: - event: + eventType: type: string const: payment_intent_created - recipientDisplay: + schema: type: string + const: mypaytag.notification.v1 + recipient: + type: object + additionalProperties: false + required: + - identifierType + - maskedDisplay + properties: + identifierType: + type: string + const: paytag + maskedDisplay: + type: string + minLength: 1 amount: $ref: "#/components/schemas/Amount" - resolverRequestId: - type: string - payingDappReference: - type: string + references: + type: object + additionalProperties: false + required: + - resolverReference + - providerReference + - payingDappReference + properties: + resolverReference: + type: string + minLength: 1 + providerReference: + type: string + minLength: 1 + payingDappReference: + type: string + minLength: 1 + action: + type: object + additionalProperties: false + required: + - type + properties: + type: + type: string + const: none NotificationAccepted: type: object additionalProperties: false @@ -1048,7 +1133,6 @@ components: SolverId: type: string enum: - - near_intents_1click - lifi - squid - zero_x_cross_chain diff --git a/api/postman_collection.json b/api/postman_collection.json index ef73bdc..edb04c7 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -1805,7 +1805,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"event\": \"\",\n \"recipientDisplay\": \"\",\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"resolverRequestId\": \"\",\n \"payingDappReference\": \"\"\n}", + "raw": "{\n \"eventType\": \"\",\n \"schema\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"maskedDisplay\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n },\n \"action\": {\n \"type\": \"\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1850,7 +1850,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"event\": \"\",\n \"recipientDisplay\": \"\",\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"resolverRequestId\": \"\",\n \"payingDappReference\": \"\"\n}", + "raw": "{\n \"eventType\": \"\",\n \"schema\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"maskedDisplay\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n },\n \"action\": {\n \"type\": \"\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1905,7 +1905,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"event\": \"\",\n \"recipientDisplay\": \"\",\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"resolverRequestId\": \"\",\n \"payingDappReference\": \"\"\n}", + "raw": "{\n \"eventType\": \"\",\n \"schema\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"maskedDisplay\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n },\n \"action\": {\n \"type\": \"\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1960,7 +1960,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"event\": \"\",\n \"recipientDisplay\": \"\",\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"resolverRequestId\": \"\",\n \"payingDappReference\": \"\"\n}", + "raw": "{\n \"eventType\": \"\",\n \"schema\": \"\",\n \"recipient\": {\n \"identifierType\": \"\",\n \"maskedDisplay\": \"\"\n },\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"references\": {\n \"resolverReference\": \"\",\n \"providerReference\": \"\",\n \"payingDappReference\": \"\"\n },\n \"action\": {\n \"type\": \"\"\n }\n}", "options": { "raw": { "headerFamily": "json", diff --git a/package.json b/package.json index 3d44f89..a3ab001 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "json-schema-to-typescript": "^15.0.4", "openapi-to-postmanv2": "^6.1.0", "typescript": "^5.8.3", - "vitest": "^3.2.4" + "vitest": "^3.2.4", + "yaml": "^2.9.0" } } diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 2d6a56c..3790bd2 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -2,6 +2,7 @@ import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; +import { parse as parseYaml } from "yaml"; import { validateMyPayTagIntent, @@ -9,6 +10,7 @@ import { validateNotificationEvent, validateNearOneClickQuoteOption, validateNearOneClickQuoteSelectionRequest, + validateProviderCallbackRequest, validateProviderResponse, validateResolveRequest, validateResolveResponse, @@ -402,35 +404,24 @@ describe("@mypaytag/protocol", () => { ).toThrow(); }); - it("keeps OpenAPI examples aligned with canonical intent and provider payload fields", () => { - const openApi = readFileSync( + it("validates OpenAPI examples against canonical protocol validators", () => { + const openApi = parseYaml(readFileSync( fileURLToPath(new URL("../../../api/openapi.yaml", import.meta.url)), "utf8", - ); - - for (const field of [ - "id", - "schema", - "status", - "modality", - "recipient", - "selectedRoute", - "amount", - "expiresAt", - "singleUse", - "paymentInstruction", - "references", - ] as const) { - expect(openApi).toContain(` - ${field}`); - } - - for (const field of providerPayloadRequiredFields) { - expect(openApi).toContain(` - ${field}`); - expect(openApi).toContain(`${field}:`); - } - - expect(openApi).toContain("identifierType: paytag"); - expect(openApi).toContain("schema: mypaytag.intent.v1"); - expect(openApi).not.toContain("identifierType: verified_stamp"); + )) as any; + + const resolvedExample = + openApi.paths["/resolve"].post.responses["200"].content["application/json"].examples.resolved.value; + const notificationExample = + openApi.paths["/notifications"].post.requestBody.content["application/json"].examples.paymentIntentCreated.value; + const providerCallbackExample = + openApi.webhooks.providerPaymentIntent.post.requestBody.content["application/json"].examples.providerCallback.value; + const providerResponseExample = + openApi.webhooks.providerPaymentIntent.post.responses["200"].content["application/json"].examples.providerResponse.value; + + expect(validateResolveResponse(resolvedExample)).toEqual(resolvedExample); + expect(validateNotificationEvent(notificationExample)).toEqual(notificationExample); + expect(validateProviderCallbackRequest(providerCallbackExample)).toEqual(providerCallbackExample); + expect(validateProviderResponse(providerResponseExample)).toEqual(providerResponseExample); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a136ed..2557cef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,10 @@ importers: version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.6(@types/node@20.19.43) + version: 3.2.6(@types/node@20.19.43)(yaml@2.9.0) + yaml: + specifier: ^2.9.0 + version: 2.9.0 examples/paying-dapp-basic: dependencies: @@ -984,6 +987,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1188,13 +1196,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.6(vite@7.3.5(@types/node@20.19.43))': + '@vitest/mocker@3.2.6(vite@7.3.5(@types/node@20.19.43)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.5(@types/node@20.19.43) + vite: 7.3.5(@types/node@20.19.43)(yaml@2.9.0) '@vitest/pretty-format@3.2.6': dependencies: @@ -1710,13 +1718,13 @@ snapshots: validate.io-number@1.0.3: {} - vite-node@3.2.4(@types/node@20.19.43): + vite-node@3.2.4(@types/node@20.19.43)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.5(@types/node@20.19.43) + vite: 7.3.5(@types/node@20.19.43)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -1731,7 +1739,7 @@ snapshots: - tsx - yaml - vite@7.3.5(@types/node@20.19.43): + vite@7.3.5(@types/node@20.19.43)(yaml@2.9.0): dependencies: esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) @@ -1742,12 +1750,13 @@ snapshots: optionalDependencies: '@types/node': 20.19.43 fsevents: 2.3.3 + yaml: 2.9.0 - vitest@3.2.6(@types/node@20.19.43): + vitest@3.2.6(@types/node@20.19.43)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(vite@7.3.5(@types/node@20.19.43)) + '@vitest/mocker': 3.2.6(vite@7.3.5(@types/node@20.19.43)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.6 '@vitest/runner': 3.2.6 '@vitest/snapshot': 3.2.6 @@ -1765,8 +1774,8 @@ snapshots: tinyglobby: 0.2.17 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.5(@types/node@20.19.43) - vite-node: 3.2.4(@types/node@20.19.43) + vite: 7.3.5(@types/node@20.19.43)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@20.19.43)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.43 @@ -1806,6 +1815,8 @@ snapshots: yaml@1.10.2: {} + yaml@2.9.0: {} + yargs-parser@21.1.1: {} yargs@17.7.3: From 0256c299e3b8bba7c3d94a7cbb25bfec11ae42b3 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:42:41 -0400 Subject: [PATCH 16/21] Add route CRUD protocol helpers --- agent-context/session-log/main.md | 11 + agent-context/todo.md | 2 +- api/openapi.yaml | 155 ++++++- api/postman_collection.json | 130 +++++- docs/engineering/mvp-api-contracts.md | 44 +- .../protocol-and-sdk-architecture.md | 7 + .../protocol/schemas/pay-to-route.schema.json | 28 ++ .../schemas/route-delete-response.schema.json | 24 ++ .../schemas/route-list-response.schema.json | 30 ++ .../schemas/route-read-response.schema.json | 39 ++ .../schemas/route-update-request.schema.json | 43 ++ packages/protocol/src/fixtures.ts | 41 ++ packages/protocol/src/generated/schemas.ts | 387 ++++++++++++++++++ packages/protocol/src/generated/types.ts | 5 + .../src/generated/types/pay-to-route.ts | 11 + .../generated/types/route-delete-response.ts | 11 + .../generated/types/route-list-response.ts | 15 + .../generated/types/route-read-response.ts | 20 + .../generated/types/route-update-request.ts | 18 + packages/protocol/src/protocol.test.ts | 78 ++++ packages/protocol/src/validators.ts | 20 + packages/provider-sdk/src/index.ts | 24 ++ .../provider-sdk/src/provider-sdk.test.ts | 15 + 23 files changed, 1130 insertions(+), 28 deletions(-) create mode 100644 packages/protocol/schemas/pay-to-route.schema.json create mode 100644 packages/protocol/schemas/route-delete-response.schema.json create mode 100644 packages/protocol/schemas/route-list-response.schema.json create mode 100644 packages/protocol/schemas/route-read-response.schema.json create mode 100644 packages/protocol/schemas/route-update-request.schema.json create mode 100644 packages/protocol/src/generated/types/pay-to-route.ts create mode 100644 packages/protocol/src/generated/types/route-delete-response.ts create mode 100644 packages/protocol/src/generated/types/route-list-response.ts create mode 100644 packages/protocol/src/generated/types/route-read-response.ts create mode 100644 packages/protocol/src/generated/types/route-update-request.ts diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 574a32a..7ad32c1 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -369,3 +369,14 @@ todo: mypaytag-sdk:GPTS-S7-T5 summary: Aligned OpenAPI notification events with protocol `eventType`/`schema`/`recipient`/`references`/`action` shape, added provider callback/response examples, removed NEAR from the generic SolverId enum, and replaced the brittle OpenAPI string-presence test with validator-backed example checks. validation: Ran pnpm api:validate, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/provider-sdk test, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 53 workspace cases and OpenAPI/Postman/public-boundary checks passed. follow-ups: Continue Sprint 7 by adding route CRUD protocol schemas and SDK helpers. + +--- + +## 2026-06-28-gpts-s7-t6 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T6 +summary: Added canonical PayToRoute, route list/read, route update, and route delete schemas with generated exports, fixtures, validators, OpenAPI route CRUD envelopes/examples, provider SDK helpers, and tests for scoped route data plus forbidden wallet/private fields. +validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/provider-sdk typecheck, pnpm --filter @mypaytag/provider-sdk test, pnpm api:validate, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 56 workspace cases and OpenAPI/Postman/public-boundary checks passed. +follow-ups: Continue Sprint 7 by adding hosted route-selection action schemas. diff --git a/agent-context/todo.md b/agent-context/todo.md index 2e40d9b..28fc093 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -557,7 +557,7 @@ Acceptance notes: ### GPTS-S7-T6 Add Route CRUD Protocol Schemas And SDK Helpers -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t6 Depends on: mypaytag:GPTR-S7-T1 diff --git a/api/openapi.yaml b/api/openapi.yaml index 61be960..7ff67e8 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -164,6 +164,31 @@ paths: "401": $ref: "#/components/responses/Unauthorized" /payto-routes: + get: + tags: + - Routes + operationId: listPayToRoutes + summary: List receiving-app routes scoped to the authenticated app. + responses: + "200": + description: Scoped route list. + content: + application/json: + schema: + $ref: "#/components/schemas/RouteListResponse" + examples: + routes: + value: + status: resolved + routes: + - id: mpt_route_123 + payToDappId: example-wallet + chain: base + network: mainnet + asset: USDC + state: active + "401": + $ref: "#/components/responses/Unauthorized" post: tags: - Routes @@ -235,7 +260,18 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/PayToRoute" + $ref: "#/components/schemas/RouteReadResponse" + examples: + route: + value: + status: resolved + route: + id: mpt_route_123 + payToDappId: example-wallet + chain: base + network: mainnet + asset: USDC + state: active "404": $ref: "#/components/responses/NotFound" patch: @@ -249,15 +285,30 @@ paths: required: true content: application/json: - schema: - $ref: "#/components/schemas/RouteUpdateRequest" + schema: + $ref: "#/components/schemas/RouteUpdateRequest" + examples: + disableRoute: + value: + state: disabled responses: "200": description: Updated route. content: application/json: schema: - $ref: "#/components/schemas/PayToRoute" + $ref: "#/components/schemas/RouteReadResponse" + examples: + route: + value: + status: resolved + route: + id: mpt_route_123 + payToDappId: example-wallet + chain: base + network: mainnet + asset: USDC + state: disabled "400": $ref: "#/components/responses/BadRequest" "401": @@ -277,7 +328,12 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/DeleteRouteResponse" + $ref: "#/components/schemas/RouteDeleteResponse" + examples: + revoked: + value: + status: revoked + routeId: mpt_route_123 "401": $ref: "#/components/responses/Unauthorized" "404": @@ -621,7 +677,16 @@ components: oneOf: - $ref: "#/components/schemas/ResolvedIntentResponse" - $ref: "#/components/schemas/ActionRequiredResponse" - - $ref: "#/components/schemas/StatusOnlyResponse" + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable ResolvedIntentResponse: type: object additionalProperties: false @@ -887,7 +952,16 @@ components: items: $ref: "#/components/schemas/PayToRoute" - $ref: "#/components/schemas/ActionRequiredResponse" - - $ref: "#/components/schemas/StatusOnlyResponse" + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable PayToRoute: type: object additionalProperties: false @@ -901,45 +975,88 @@ components: properties: id: type: string + minLength: 1 payToDappId: type: string + minLength: 1 chain: type: string + minLength: 1 network: type: string + minLength: 1 asset: type: string + minLength: 1 state: type: string enum: - active + - disabled - revoked + RouteListResponse: + type: object + additionalProperties: false + required: + - status + - routes + properties: + status: + type: string + const: resolved + routes: + type: array + items: + $ref: "#/components/schemas/PayToRoute" + RouteReadResponse: + oneOf: + - type: object + additionalProperties: false + required: + - status + - route + properties: + status: + type: string + const: resolved + route: + $ref: "#/components/schemas/PayToRoute" + - $ref: "#/components/schemas/StatusOnlyResponse" RouteUpdateRequest: type: object additionalProperties: false + anyOf: + - required: + - state + - required: + - supportedRoutes properties: state: type: string enum: - active + - disabled - revoked supportedRoutes: type: array minItems: 1 items: $ref: "#/components/schemas/RoutePath" - DeleteRouteResponse: - type: object - additionalProperties: false - required: - - status - - routeId - properties: - status: - type: string - const: revoked - routeId: - type: string + RouteDeleteResponse: + oneOf: + - type: object + additionalProperties: false + required: + - status + - routeId + properties: + status: + type: string + const: revoked + routeId: + type: string + minLength: 1 + - $ref: "#/components/schemas/StatusOnlyResponse" HostedActionView: type: object additionalProperties: false diff --git a/api/postman_collection.json b/api/postman_collection.json index edb04c7..474dd1d 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -387,6 +387,122 @@ "name": "payto-routes", "description": "", "item": [ + { + "name": "List receiving-app routes scoped to the authenticated app.", + "request": { + "name": "List receiving-app routes scoped to the authenticated app.", + "description": {}, + "url": { + "path": [ + "payto-routes" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {}, + "auth": null + }, + "response": [ + { + "name": "Scoped route list.", + "originalRequest": { + "url": { + "path": [ + "payto-routes" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "x-mypaytag-signature", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"status\": \"\",\n \"routes\": [\n {\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n },\n {\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n }\n ]\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid app authentication signature.", + "originalRequest": { + "url": { + "path": [ + "payto-routes" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "x-mypaytag-signature", + "value": "" + } + ], + "method": "GET", + "body": {} + }, + "status": "Unauthorized", + "code": 401, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"status\": \"\",\n \"message\": \"\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + } + ], + "event": [], + "protocolProfileBehavior": { + "disableBodyPruning": true + } + }, { "name": "Register supported receive routes for a receiving app.", "request": { @@ -692,7 +808,7 @@ "value": "application/json" } ], - "body": "{\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n}", + "body": "{\n \"status\": \"\",\n \"route\": {\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, @@ -796,7 +912,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ]\n}", + "raw": "{\n \"state\": 5128.764344395074\n}", "options": { "raw": { "headerFamily": "json", @@ -853,7 +969,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ]\n}", + "raw": "{\n \"state\": 5128.764344395074\n}", "options": { "raw": { "headerFamily": "json", @@ -870,7 +986,7 @@ "value": "application/json" } ], - "body": "{\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n}", + "body": "{\n \"status\": \"\",\n \"route\": {\n \"id\": \"\",\n \"payToDappId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"state\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, @@ -920,7 +1036,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ]\n}", + "raw": "{\n \"state\": 5128.764344395074\n}", "options": { "raw": { "headerFamily": "json", @@ -987,7 +1103,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ]\n}", + "raw": "{\n \"state\": 5128.764344395074\n}", "options": { "raw": { "headerFamily": "json", @@ -1054,7 +1170,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\",\n \"supportedRoutes\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ]\n}", + "raw": "{\n \"state\": 5128.764344395074\n}", "options": { "raw": { "headerFamily": "json", diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index de5bbbf..4b4f49a 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -135,6 +135,47 @@ Resolved registration response: } ``` +List/read/update responses are scoped envelopes: + +```json +{ + "status": "resolved", + "routes": [ + { + "id": "mpt_route_123", + "payToDappId": "smartrust-wallet", + "chain": "base", + "network": "mainnet", + "asset": "USDC", + "state": "active" + } + ] +} +``` + +```json +{ + "status": "resolved", + "route": { + "id": "mpt_route_123", + "payToDappId": "smartrust-wallet", + "chain": "base", + "network": "mainnet", + "asset": "USDC", + "state": "disabled" + } +} +``` + +Route revocation response: + +```json +{ + "status": "revoked", + "routeId": "mpt_route_123" +} +``` + Route selection required response: ```json @@ -152,7 +193,8 @@ Rules: - Requests with account/address/payment-instruction fields are `invalid_request`. - Overlapping routes can return `user_action_required` until the user confirms priority/default. -- PayingDapps must not receive route preference details. +- Route CRUD responses expose only PayToDapp-owned scoped route capability data. +- PayingDapps must not receive route preference details, unrelated PayToDapps, wallet graphs, raw identifiers, or payment instructions. ## PayingDapp Resolve diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 8e708e2..542bac8 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -113,6 +113,13 @@ Route registration request: The schema must reject account, address, and chain-specific payment instruction fields. Dynamic addresses belong only in a provider intent response. +Route CRUD responses expose only PayToDapp-owned scoped route capability data. +List/read/update responses use `status: resolved` envelopes, deletion uses +`status: revoked`, and safe unavailable cases use public status-only shapes such +as `no_route` or `provider_unavailable`. They must not expose route +preferences, unrelated PayToDapps, wallet graphs, raw identifiers, or payment +instructions. + ### PayingDapp Resolve ```http diff --git a/packages/protocol/schemas/pay-to-route.schema.json b/packages/protocol/schemas/pay-to-route.schema.json new file mode 100644 index 0000000..44c9c49 --- /dev/null +++ b/packages/protocol/schemas/pay-to-route.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/pay-to-route.schema.json", + "title": "PayToRoute", + "type": "object", + "additionalProperties": false, + "required": ["id", "payToDappId", "chain", "network", "asset", "state"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "payToDappId": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "state": { "enum": ["active", "disabled", "revoked"] } + }, + "not": { + "anyOf": [ + { "required": ["account"] }, + { "required": ["address"] }, + { "required": ["recipientAddress"] }, + { "required": ["memo"] }, + { "required": ["paymentInstruction"] }, + { "required": ["paymentLink"] }, + { "required": ["routePreference"] }, + { "required": ["walletGraph"] } + ] + } +} diff --git a/packages/protocol/schemas/route-delete-response.schema.json b/packages/protocol/schemas/route-delete-response.schema.json new file mode 100644 index 0000000..1ed4f34 --- /dev/null +++ b/packages/protocol/schemas/route-delete-response.schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-delete-response.schema.json", + "title": "RouteDeleteResponse", + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["status", "routeId"], + "properties": { + "status": { "const": "revoked" }, + "routeId": { "type": "string", "minLength": 1 } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["status"], + "properties": { + "status": { "enum": ["no_route", "provider_unavailable"] } + } + } + ] +} diff --git a/packages/protocol/schemas/route-list-response.schema.json b/packages/protocol/schemas/route-list-response.schema.json new file mode 100644 index 0000000..6633312 --- /dev/null +++ b/packages/protocol/schemas/route-list-response.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-list-response.schema.json", + "title": "RouteListResponse", + "type": "object", + "additionalProperties": false, + "required": ["status", "routes"], + "properties": { + "status": { "const": "resolved" }, + "routes": { + "type": "array", + "items": { "$ref": "#/$defs/payToRoute" } + } + }, + "$defs": { + "payToRoute": { + "type": "object", + "additionalProperties": false, + "required": ["id", "payToDappId", "chain", "network", "asset", "state"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "payToDappId": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "state": { "enum": ["active", "disabled", "revoked"] } + } + } + } +} diff --git a/packages/protocol/schemas/route-read-response.schema.json b/packages/protocol/schemas/route-read-response.schema.json new file mode 100644 index 0000000..5d652ce --- /dev/null +++ b/packages/protocol/schemas/route-read-response.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-read-response.schema.json", + "title": "RouteReadResponse", + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["status", "route"], + "properties": { + "status": { "const": "resolved" }, + "route": { "$ref": "#/$defs/payToRoute" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["status"], + "properties": { + "status": { "enum": ["no_route", "provider_unavailable"] } + } + } + ], + "$defs": { + "payToRoute": { + "type": "object", + "additionalProperties": false, + "required": ["id", "payToDappId", "chain", "network", "asset", "state"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "payToDappId": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "state": { "enum": ["active", "disabled", "revoked"] } + } + } + } +} diff --git a/packages/protocol/schemas/route-update-request.schema.json b/packages/protocol/schemas/route-update-request.schema.json new file mode 100644 index 0000000..ab6425a --- /dev/null +++ b/packages/protocol/schemas/route-update-request.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-update-request.schema.json", + "title": "RouteUpdateRequest", + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["state"] }, + { "required": ["supportedRoutes"] } + ], + "properties": { + "state": { "enum": ["active", "disabled", "revoked"] }, + "supportedRoutes": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/route" } + } + }, + "not": { + "anyOf": [ + { "required": ["account"] }, + { "required": ["address"] }, + { "required": ["recipientAddress"] }, + { "required": ["memo"] }, + { "required": ["paymentInstruction"] }, + { "required": ["paymentLink"] }, + { "required": ["routePreference"] }, + { "required": ["walletGraph"] } + ] + }, + "$defs": { + "route": { + "type": "object", + "additionalProperties": false, + "required": ["chain", "network", "asset"], + "properties": { + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 3e15bd6..6cc3dff 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -4,13 +4,18 @@ import type { NearOneClickQuoteOption, NearOneClickQuoteSelectionRequest, NotificationEvent, + PayToRoute, ProviderCallbackRequest, ProviderResponse, + RouteDeleteResponse, + RouteListResponse, RouteQuotePreview, + RouteReadResponse, ResolveRequest, ResolveResponse, RouteRegistrationRequest, RouteRegistrationResponse, + RouteUpdateRequest, } from "./generated/types.js"; export const validRouteRegistrationRequest: RouteRegistrationRequest = { @@ -43,6 +48,42 @@ export const validRouteRegistrationResponse: RouteRegistrationResponse = { ], }; +export const validPayToRoute: PayToRoute = { + id: "mpt_route_123", + payToDappId: "smartrust-wallet", + chain: "base", + network: "mainnet", + asset: "USDC", + state: "active", +}; + +export const validRouteListResponse: RouteListResponse = { + status: "resolved", + routes: [validPayToRoute], +}; + +export const validRouteReadResponse: RouteReadResponse = { + status: "resolved", + route: validPayToRoute, +}; + +export const validRouteUpdateRequest: RouteUpdateRequest = { + state: "disabled", +}; + +export const validRouteDeleteResponse: RouteDeleteResponse = { + status: "revoked", + routeId: validPayToRoute.id, +}; + +export const validRouteNotFoundResponse: RouteReadResponse = { + status: "no_route", +}; + +export const validRouteUnavailableResponse: RouteDeleteResponse = { + status: "provider_unavailable", +}; + export const validResolveRequest: ResolveRequest = { recipient: { identifierType: "paytag", diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index c305364..20c6998 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -606,6 +606,95 @@ export const NotificationEventSchema = { } } as const; +export const PayToRouteSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/pay-to-route.schema.json", + "title": "PayToRoute", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "payToDappId", + "chain", + "network", + "asset", + "state" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "payToDappId": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] + } + }, + "not": { + "anyOf": [ + { + "required": [ + "account" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "memo" + ] + }, + { + "required": [ + "paymentInstruction" + ] + }, + { + "required": [ + "paymentLink" + ] + }, + { + "required": [ + "routePreference" + ] + }, + { + "required": [ + "walletGraph" + ] + } + ] + } +} as const; + export const ProviderCallbackRequestSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/provider-callback-request.schema.json", @@ -1283,6 +1372,112 @@ export const ResolveResponseSchema = { } } as const; +export const RouteDeleteResponseSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-delete-response.schema.json", + "title": "RouteDeleteResponse", + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "routeId" + ], + "properties": { + "status": { + "const": "revoked" + }, + "routeId": { + "type": "string", + "minLength": 1 + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "enum": [ + "no_route", + "provider_unavailable" + ] + } + } + } + ] +} as const; + +export const RouteListResponseSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-list-response.schema.json", + "title": "RouteListResponse", + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "routes" + ], + "properties": { + "status": { + "const": "resolved" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/$defs/payToRoute" + } + } + }, + "$defs": { + "payToRoute": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "payToDappId", + "chain", + "network", + "asset", + "state" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "payToDappId": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] + } + } + } + } +} as const; + export const RouteQuotePreviewSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/route-quote-preview.schema.json", @@ -1405,6 +1600,88 @@ export const RouteQuotePreviewSchema = { } } as const; +export const RouteReadResponseSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-read-response.schema.json", + "title": "RouteReadResponse", + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "route" + ], + "properties": { + "status": { + "const": "resolved" + }, + "route": { + "$ref": "#/$defs/payToRoute" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "enum": [ + "no_route", + "provider_unavailable" + ] + } + } + } + ], + "$defs": { + "payToRoute": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "payToDappId", + "chain", + "network", + "asset", + "state" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "payToDappId": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] + } + } + } + } +} as const; + export const RouteRegistrationRequestSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/route-registration-request.schema.json", @@ -1623,6 +1900,111 @@ export const RouteRegistrationResponseSchema = { } } as const; +export const RouteUpdateRequestSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/route-update-request.schema.json", + "title": "RouteUpdateRequest", + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "state" + ] + }, + { + "required": [ + "supportedRoutes" + ] + } + ], + "properties": { + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] + }, + "supportedRoutes": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/route" + } + } + }, + "not": { + "anyOf": [ + { + "required": [ + "account" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "memo" + ] + }, + { + "required": [ + "paymentInstruction" + ] + }, + { + "required": [ + "paymentLink" + ] + }, + { + "required": [ + "routePreference" + ] + }, + { + "required": [ + "walletGraph" + ] + } + ] + }, + "$defs": { + "route": { + "type": "object", + "additionalProperties": false, + "required": [ + "chain", + "network", + "asset" + ], + "properties": { + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + } + } + } + } +} as const; + export const StatusSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/status.schema.json", @@ -1649,12 +2031,17 @@ export const protocolSchemas = { "near-one-click-quote-option": NearOneClickQuoteOptionSchema, "near-one-click-quote-selection-request": NearOneClickQuoteSelectionRequestSchema, "notification-event": NotificationEventSchema, + "pay-to-route": PayToRouteSchema, "provider-callback-request": ProviderCallbackRequestSchema, "provider-response": ProviderResponseSchema, "resolve-request": ResolveRequestSchema, "resolve-response": ResolveResponseSchema, + "route-delete-response": RouteDeleteResponseSchema, + "route-list-response": RouteListResponseSchema, "route-quote-preview": RouteQuotePreviewSchema, + "route-read-response": RouteReadResponseSchema, "route-registration-request": RouteRegistrationRequestSchema, "route-registration-response": RouteRegistrationResponseSchema, + "route-update-request": RouteUpdateRequestSchema, "status": StatusSchema, } as const; diff --git a/packages/protocol/src/generated/types.ts b/packages/protocol/src/generated/types.ts index f85d107..34da612 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -6,11 +6,16 @@ export type { NearOneClickPayableInstruction } from "./types/near-one-click-paya export type { NearOneClickQuoteOption } from "./types/near-one-click-quote-option.js"; export type { NearOneClickQuoteSelectionRequest } from "./types/near-one-click-quote-selection-request.js"; export type { NotificationEvent } from "./types/notification-event.js"; +export type { PayToRoute } from "./types/pay-to-route.js"; export type { ProviderCallbackRequest } from "./types/provider-callback-request.js"; export type { ProviderResponse } from "./types/provider-response.js"; export type { ResolveRequest } from "./types/resolve-request.js"; export type { ResolveResponse } from "./types/resolve-response.js"; +export type { RouteDeleteResponse } from "./types/route-delete-response.js"; +export type { RouteListResponse } from "./types/route-list-response.js"; export type { RouteQuotePreview } from "./types/route-quote-preview.js"; +export type { RouteReadResponse } from "./types/route-read-response.js"; export type { RouteRegistrationRequest } from "./types/route-registration-request.js"; export type { RouteRegistrationResponse } from "./types/route-registration-response.js"; +export type { RouteUpdateRequest } from "./types/route-update-request.js"; export type { MyPayTagStatus } from "./types/status.js"; diff --git a/packages/protocol/src/generated/types/pay-to-route.ts b/packages/protocol/src/generated/types/pay-to-route.ts new file mode 100644 index 0000000..e882fcb --- /dev/null +++ b/packages/protocol/src/generated/types/pay-to-route.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/pay-to-route.schema.json. Do not edit by hand. + +export interface PayToRoute { + id: string; + payToDappId: string; + chain: string; + network: string; + asset: string; + state: "active" | "disabled" | "revoked"; +} diff --git a/packages/protocol/src/generated/types/route-delete-response.ts b/packages/protocol/src/generated/types/route-delete-response.ts new file mode 100644 index 0000000..3c05c3b --- /dev/null +++ b/packages/protocol/src/generated/types/route-delete-response.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/route-delete-response.schema.json. Do not edit by hand. + +export type RouteDeleteResponse = + | { + status: "revoked"; + routeId: string; + } + | { + status: "no_route" | "provider_unavailable"; + }; diff --git a/packages/protocol/src/generated/types/route-list-response.ts b/packages/protocol/src/generated/types/route-list-response.ts new file mode 100644 index 0000000..ec81bb3 --- /dev/null +++ b/packages/protocol/src/generated/types/route-list-response.ts @@ -0,0 +1,15 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/route-list-response.schema.json. Do not edit by hand. + +export interface RouteListResponse { + status: "resolved"; + routes: PayToRoute[]; +} +export interface PayToRoute { + id: string; + payToDappId: string; + chain: string; + network: string; + asset: string; + state: "active" | "disabled" | "revoked"; +} diff --git a/packages/protocol/src/generated/types/route-read-response.ts b/packages/protocol/src/generated/types/route-read-response.ts new file mode 100644 index 0000000..277aa08 --- /dev/null +++ b/packages/protocol/src/generated/types/route-read-response.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/route-read-response.schema.json. Do not edit by hand. + +export type RouteReadResponse = + | { + status: "resolved"; + route: PayToRoute; + } + | { + status: "no_route" | "provider_unavailable"; + }; + +export interface PayToRoute { + id: string; + payToDappId: string; + chain: string; + network: string; + asset: string; + state: "active" | "disabled" | "revoked"; +} diff --git a/packages/protocol/src/generated/types/route-update-request.ts b/packages/protocol/src/generated/types/route-update-request.ts new file mode 100644 index 0000000..6ca3faf --- /dev/null +++ b/packages/protocol/src/generated/types/route-update-request.ts @@ -0,0 +1,18 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/route-update-request.schema.json. Do not edit by hand. + +export type RouteUpdateRequest = { + [k: string]: unknown; +} & { + state?: "active" | "disabled" | "revoked"; + /** + * @minItems 1 + */ + supportedRoutes?: [Route, ...Route[]]; +}; + +export interface Route { + chain: string; + network: string; + asset: string; +} diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 3790bd2..7c1c7c1 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -10,12 +10,18 @@ import { validateNotificationEvent, validateNearOneClickQuoteOption, validateNearOneClickQuoteSelectionRequest, + validatePayToRoute, validateProviderCallbackRequest, validateProviderResponse, + validateRouteDeleteResponse, + validateRouteListResponse, validateResolveRequest, validateResolveResponse, validateRouteRegistrationRequest, + validateRouteRegistrationResponse, + validateRouteReadResponse, validateRouteQuotePreview, + validateRouteUpdateRequest, validateStatus, validMyPayTagIntent, validNearOneClickPayableInstruction, @@ -25,11 +31,18 @@ import { validNearOneClickQuoteSelectionRequest, validProviderCallbackRequest, validProviderResponse, + validPayToRoute, + validRouteDeleteResponse, + validRouteListResponse, + validRouteNotFoundResponse, validResolveRequest, validResolvedResponse, validRouteRegistrationRequest, validRouteQuotePreview, + validRouteReadResponse, validRouteSelectionResponse, + validRouteUnavailableResponse, + validRouteUpdateRequest, } from "./index.js"; const terminalStatusFixtures = [ @@ -85,6 +98,11 @@ describe("@mypaytag/protocol", () => { expect(validateResolveResponse(validRouteSelectionResponse)).toEqual( validRouteSelectionResponse, ); + expect(validatePayToRoute(validPayToRoute)).toEqual(validPayToRoute); + expect(validateRouteListResponse(validRouteListResponse)).toEqual(validRouteListResponse); + expect(validateRouteReadResponse(validRouteReadResponse)).toEqual(validRouteReadResponse); + expect(validateRouteUpdateRequest(validRouteUpdateRequest)).toEqual(validRouteUpdateRequest); + expect(validateRouteDeleteResponse(validRouteDeleteResponse)).toEqual(validRouteDeleteResponse); expect(validateProviderResponse(validProviderResponse)).toEqual(validProviderResponse); expect(validateMyPayTagIntent(validMyPayTagIntent)).toEqual(validMyPayTagIntent); expect(validateNotificationEvent(validNotificationEvent)).toEqual(validNotificationEvent); @@ -132,6 +150,41 @@ describe("@mypaytag/protocol", () => { } }); + it("accepts route CRUD response and safe unavailable shapes", () => { + expect(validateRouteListResponse(validRouteListResponse)).toEqual(validRouteListResponse); + expect(validateRouteReadResponse(validRouteReadResponse)).toEqual(validRouteReadResponse); + expect(validateRouteReadResponse(validRouteNotFoundResponse)).toEqual(validRouteNotFoundResponse); + expect(validateRouteDeleteResponse(validRouteDeleteResponse)).toEqual(validRouteDeleteResponse); + expect(validateRouteDeleteResponse(validRouteUnavailableResponse)).toEqual( + validRouteUnavailableResponse, + ); + }); + + it("rejects route CRUD payloads that expose private route or wallet data", () => { + for (const [field, value] of [ + ["account", "acct_123"], + ["address", "0xabc123"], + ["recipientAddress", "0xabc123"], + ["paymentInstruction", validProviderResponse.paymentInstruction], + ["routePreference", "default"], + ["walletGraph", { connectedWallets: ["0xabc123"] }], + ] as const) { + expect(() => + validatePayToRoute({ + ...validPayToRoute, + [field]: value, + }), + ).toThrow(); + + expect(() => + validateRouteUpdateRequest({ + ...validRouteUpdateRequest, + [field]: value, + }), + ).toThrow(); + } + }); + it("rejects paths that omit network", () => { expect(() => validateResolveRequest({ @@ -412,6 +465,20 @@ describe("@mypaytag/protocol", () => { const resolvedExample = openApi.paths["/resolve"].post.responses["200"].content["application/json"].examples.resolved.value; + const routeRegistrationRequestExample = + openApi.paths["/payto-routes"].post.requestBody.content["application/json"].examples.smartrustBaseUsdc.value; + const routeRegistrationResponseExample = + openApi.paths["/payto-routes"].post.responses["200"].content["application/json"].examples.registered.value; + const routeListResponseExample = + openApi.paths["/payto-routes"].get.responses["200"].content["application/json"].examples.routes.value; + const routeReadResponseExample = + openApi.paths["/payto-routes/{routeId}"].get.responses["200"].content["application/json"].examples.route.value; + const routeUpdateRequestExample = + openApi.paths["/payto-routes/{routeId}"].patch.requestBody.content["application/json"].examples.disableRoute.value; + const routeUpdateResponseExample = + openApi.paths["/payto-routes/{routeId}"].patch.responses["200"].content["application/json"].examples.route.value; + const routeDeleteResponseExample = + openApi.paths["/payto-routes/{routeId}"].delete.responses["200"].content["application/json"].examples.revoked.value; const notificationExample = openApi.paths["/notifications"].post.requestBody.content["application/json"].examples.paymentIntentCreated.value; const providerCallbackExample = @@ -420,6 +487,17 @@ describe("@mypaytag/protocol", () => { openApi.webhooks.providerPaymentIntent.post.responses["200"].content["application/json"].examples.providerResponse.value; expect(validateResolveResponse(resolvedExample)).toEqual(resolvedExample); + expect(validateRouteRegistrationRequest(routeRegistrationRequestExample)).toEqual( + routeRegistrationRequestExample, + ); + expect(validateRouteRegistrationResponse(routeRegistrationResponseExample)).toEqual( + routeRegistrationResponseExample, + ); + expect(validateRouteListResponse(routeListResponseExample)).toEqual(routeListResponseExample); + expect(validateRouteReadResponse(routeReadResponseExample)).toEqual(routeReadResponseExample); + expect(validateRouteUpdateRequest(routeUpdateRequestExample)).toEqual(routeUpdateRequestExample); + expect(validateRouteReadResponse(routeUpdateResponseExample)).toEqual(routeUpdateResponseExample); + expect(validateRouteDeleteResponse(routeDeleteResponseExample)).toEqual(routeDeleteResponseExample); expect(validateNotificationEvent(notificationExample)).toEqual(notificationExample); expect(validateProviderCallbackRequest(providerCallbackExample)).toEqual(providerCallbackExample); expect(validateProviderResponse(providerResponseExample)).toEqual(providerResponseExample); diff --git a/packages/protocol/src/validators.ts b/packages/protocol/src/validators.ts index 7c0b33c..5ed6395 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -9,13 +9,18 @@ import type { NearOneClickQuoteSelectionRequest, MyPayTagStatus, NotificationEvent, + PayToRoute, ProviderCallbackRequest, ProviderResponse, + RouteDeleteResponse, + RouteListResponse, RouteQuotePreview, + RouteReadResponse, ResolveRequest, ResolveResponse, RouteRegistrationRequest, RouteRegistrationResponse, + RouteUpdateRequest, } from "./generated/types.js"; export class ProtocolValidationError extends Error { @@ -68,6 +73,21 @@ export const validateRouteRegistrationRequest = (payload: unknown) => export const validateRouteRegistrationResponse = (payload: unknown) => validateProtocolPayload("route-registration-response", payload); +export const validatePayToRoute = (payload: unknown) => + validateProtocolPayload("pay-to-route", payload); + +export const validateRouteListResponse = (payload: unknown) => + validateProtocolPayload("route-list-response", payload); + +export const validateRouteReadResponse = (payload: unknown) => + validateProtocolPayload("route-read-response", payload); + +export const validateRouteUpdateRequest = (payload: unknown) => + validateProtocolPayload("route-update-request", payload); + +export const validateRouteDeleteResponse = (payload: unknown) => + validateProtocolPayload("route-delete-response", payload); + export const validateResolveRequest = (payload: unknown) => validateProtocolPayload("resolve-request", payload); diff --git a/packages/provider-sdk/src/index.ts b/packages/provider-sdk/src/index.ts index cfe511b..8314cc6 100644 --- a/packages/provider-sdk/src/index.ts +++ b/packages/provider-sdk/src/index.ts @@ -1,10 +1,18 @@ import { validateProviderCallbackRequest, validateProviderResponse, + validateRouteDeleteResponse, + validateRouteListResponse, validateRouteRegistrationRequest, + validateRouteReadResponse, + validateRouteUpdateRequest, type ProviderCallbackRequest, type ProviderResponse, + type RouteDeleteResponse, + type RouteListResponse, type RouteRegistrationRequest, + type RouteReadResponse, + type RouteUpdateRequest, } from "@mypaytag/protocol"; export interface CallbackAuthEnvelope { @@ -36,6 +44,22 @@ export function buildRouteRegistrationRequest(input: RouteRegistrationRequest): return validateRouteRegistrationRequest(input); } +export function buildRouteUpdateRequest(input: RouteUpdateRequest): RouteUpdateRequest { + return validateRouteUpdateRequest(input); +} + +export function parseRouteListResponse(payload: unknown): RouteListResponse { + return validateRouteListResponse(payload); +} + +export function parseRouteReadResponse(payload: unknown): RouteReadResponse { + return validateRouteReadResponse(payload); +} + +export function parseRouteDeleteResponse(payload: unknown): RouteDeleteResponse { + return validateRouteDeleteResponse(payload); +} + export function parseProviderCallbackRequest(payload: unknown): ProviderCallbackRequest { return validateProviderCallbackRequest(payload); } diff --git a/packages/provider-sdk/src/provider-sdk.test.ts b/packages/provider-sdk/src/provider-sdk.test.ts index cfef8cc..740af13 100644 --- a/packages/provider-sdk/src/provider-sdk.test.ts +++ b/packages/provider-sdk/src/provider-sdk.test.ts @@ -3,14 +3,22 @@ import { describe, expect, it } from "vitest"; import { validProviderCallbackRequest, validProviderResponse, + validRouteDeleteResponse, + validRouteListResponse, validRouteRegistrationRequest, + validRouteReadResponse, + validRouteUpdateRequest, } from "@mypaytag/protocol"; import { assertProviderResponseMatchesCallback, buildRouteRegistrationRequest, + buildRouteUpdateRequest, parseProviderCallbackRequest, parseProviderResponse, + parseRouteDeleteResponse, + parseRouteListResponse, + parseRouteReadResponse, verifyCallbackAuth, type CallbackAuthEnvelope, type ReplayStore, @@ -45,6 +53,13 @@ describe("@mypaytag/provider-sdk", () => { ); }); + it("builds route updates and parses route CRUD responses", () => { + expect(buildRouteUpdateRequest(validRouteUpdateRequest)).toEqual(validRouteUpdateRequest); + expect(parseRouteListResponse(validRouteListResponse)).toEqual(validRouteListResponse); + expect(parseRouteReadResponse(validRouteReadResponse)).toEqual(validRouteReadResponse); + expect(parseRouteDeleteResponse(validRouteDeleteResponse)).toEqual(validRouteDeleteResponse); + }); + it("validates callback requests and provider responses", () => { const callback = parseProviderCallbackRequest(validProviderCallbackRequest); const response = parseProviderResponse(validProviderResponse); From c3ea501d8397e606156d6e0526272090c0d956b0 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:47:57 -0400 Subject: [PATCH 17/21] Add hosted route selection schemas --- agent-context/session-log/main.md | 11 + agent-context/todo.md | 2 +- api/openapi.yaml | 83 +++- api/postman_collection.json | 14 +- docs/engineering/mvp-api-contracts.md | 36 ++ .../protocol-and-sdk-architecture.md | 5 + .../hosted-action-completion.schema.json | 34 ++ .../hosted-action-decision.schema.json | 42 ++ .../schemas/hosted-action-view.schema.json | 60 +++ packages/protocol/src/fixtures.ts | 51 +++ packages/protocol/src/generated/schemas.ts | 365 ++++++++++++++++++ packages/protocol/src/generated/types.ts | 3 + .../types/hosted-action-completion.ts | 18 + .../generated/types/hosted-action-decision.ts | 9 + .../src/generated/types/hosted-action-view.ts | 21 + packages/protocol/src/protocol.test.ts | 100 +++++ packages/protocol/src/validators.ts | 12 + 17 files changed, 845 insertions(+), 21 deletions(-) create mode 100644 packages/protocol/schemas/hosted-action-completion.schema.json create mode 100644 packages/protocol/schemas/hosted-action-decision.schema.json create mode 100644 packages/protocol/schemas/hosted-action-view.schema.json create mode 100644 packages/protocol/src/generated/types/hosted-action-completion.ts create mode 100644 packages/protocol/src/generated/types/hosted-action-decision.ts create mode 100644 packages/protocol/src/generated/types/hosted-action-view.ts diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 7ad32c1..47c255e 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -380,3 +380,14 @@ todo: mypaytag-sdk:GPTS-S7-T6 summary: Added canonical PayToRoute, route list/read, route update, and route delete schemas with generated exports, fixtures, validators, OpenAPI route CRUD envelopes/examples, provider SDK helpers, and tests for scoped route data plus forbidden wallet/private fields. validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm --filter @mypaytag/provider-sdk typecheck, pnpm --filter @mypaytag/provider-sdk test, pnpm api:validate, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 56 workspace cases and OpenAPI/Postman/public-boundary checks passed. follow-ups: Continue Sprint 7 by adding hosted route-selection action schemas. + +--- + +## 2026-06-28-gpts-s7-t7 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T7 +summary: Added hosted route-selection action view, decision, and completion protocol schemas with generated exports, fixtures, validators, OpenAPI examples/components, and privacy/decision-state tests. +validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm api:validate, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 59 workspace cases and OpenAPI/Postman/public-boundary checks passed. +follow-ups: Continue Sprint 7 by deciding/documenting public placement for paytag availability contracts. diff --git a/agent-context/todo.md b/agent-context/todo.md index 28fc093..06e3990 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -574,7 +574,7 @@ Acceptance notes: ### GPTS-S7-T7 Add Hosted Route-Selection Action Schemas -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t7 Depends on: mypaytag:GPTR-S7-T6, mypaytag-site:GPTW-S7-T6 diff --git a/api/openapi.yaml b/api/openapi.yaml index 7ff67e8..92d8215 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -356,6 +356,20 @@ paths: application/json: schema: $ref: "#/components/schemas/HostedActionView" + examples: + readyRouteSelection: + value: + status: ready + actionId: mpt_act_789 + actionType: route_selection + expiresAt: "2026-06-24T20:00:00Z" + options: + - optionId: mpt_route_option_123 + chain: base + network: mainnet + asset: USDC + payToDappId: smartrust-wallet + displayName: SmarTrust Wallet - Base USDC "400": $ref: "#/components/responses/BadRequest" "404": @@ -373,6 +387,11 @@ paths: application/json: schema: $ref: "#/components/schemas/HostedActionDecision" + examples: + selectRoute: + value: + decision: select_route + selectedOptionId: mpt_route_option_123 responses: "200": description: Completion state. @@ -380,6 +399,12 @@ paths: application/json: schema: $ref: "#/components/schemas/HostedActionCompletion" + examples: + selectedRoute: + value: + status: selected_route + actionId: mpt_act_789 + selectedOptionId: mpt_route_option_123 "400": $ref: "#/components/responses/BadRequest" "404": @@ -711,14 +736,6 @@ components: const: user_action_required action: $ref: "#/components/schemas/HostedAction" - StatusOnlyResponse: - type: object - additionalProperties: false - required: - - status - properties: - status: - $ref: "#/components/schemas/PublicStatus" HostedAction: type: object additionalProperties: false @@ -1021,7 +1038,16 @@ components: const: resolved route: $ref: "#/components/schemas/PayToRoute" - - $ref: "#/components/schemas/StatusOnlyResponse" + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable RouteUpdateRequest: type: object additionalProperties: false @@ -1056,7 +1082,16 @@ components: routeId: type: string minLength: 1 - - $ref: "#/components/schemas/StatusOnlyResponse" + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable HostedActionView: type: object additionalProperties: false @@ -1072,13 +1107,18 @@ components: - expired - invalid - completed + - replayed - restart_required actionId: type: string + minLength: 1 actionType: type: string enum: - route_selection + expiresAt: + type: string + format: date-time options: type: array items: @@ -1096,16 +1136,22 @@ components: properties: optionId: type: string + minLength: 1 chain: type: string + minLength: 1 network: type: string + minLength: 1 asset: type: string + minLength: 1 payToDappId: type: string + minLength: 1 displayName: type: string + minLength: 1 HostedActionDecision: type: object additionalProperties: false @@ -1115,25 +1161,36 @@ components: decision: type: string enum: - - approve - - deny - select_route + - leave_unchanged + - deny selectedOptionId: type: string + minLength: 1 HostedActionCompletion: type: object additionalProperties: false required: - status + - actionId properties: status: type: string enum: - - approved + - unchanged - denied - selected_route - completed + - expired + - invalid + - replayed - restart_required + actionId: + type: string + minLength: 1 + selectedOptionId: + type: string + minLength: 1 ProviderCallbackRequest: type: object additionalProperties: false diff --git a/api/postman_collection.json b/api/postman_collection.json index 474dd1d..2930e94 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -912,7 +912,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": 5128.764344395074\n}", + "raw": "{\n \"state\": true\n}", "options": { "raw": { "headerFamily": "json", @@ -969,7 +969,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": 5128.764344395074\n}", + "raw": "{\n \"state\": true\n}", "options": { "raw": { "headerFamily": "json", @@ -1036,7 +1036,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": 5128.764344395074\n}", + "raw": "{\n \"state\": true\n}", "options": { "raw": { "headerFamily": "json", @@ -1103,7 +1103,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": 5128.764344395074\n}", + "raw": "{\n \"state\": true\n}", "options": { "raw": { "headerFamily": "json", @@ -1170,7 +1170,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": 5128.764344395074\n}", + "raw": "{\n \"state\": true\n}", "options": { "raw": { "headerFamily": "json", @@ -1506,7 +1506,7 @@ "value": "application/json" } ], - "body": "{\n \"status\": \"\",\n \"actionId\": \"\",\n \"actionType\": \"\",\n \"options\": [\n {\n \"optionId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payToDappId\": \"\",\n \"displayName\": \"\"\n },\n {\n \"optionId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payToDappId\": \"\",\n \"displayName\": \"\"\n }\n ]\n}", + "body": "{\n \"status\": \"\",\n \"actionId\": \"\",\n \"actionType\": \"\",\n \"expiresAt\": \"\",\n \"options\": [\n {\n \"optionId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payToDappId\": \"\",\n \"displayName\": \"\"\n },\n {\n \"optionId\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payToDappId\": \"\",\n \"displayName\": \"\"\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" }, @@ -1738,7 +1738,7 @@ "value": "application/json" } ], - "body": "{\n \"status\": \"\"\n}", + "body": "{\n \"status\": \"\",\n \"actionId\": \"\",\n \"selectedOptionId\": \"\"\n}", "cookie": [], "_postman_previewlanguage": "json" }, diff --git a/docs/engineering/mvp-api-contracts.md b/docs/engineering/mvp-api-contracts.md index 4b4f49a..0a48d4e 100644 --- a/docs/engineering/mvp-api-contracts.md +++ b/docs/engineering/mvp-api-contracts.md @@ -265,6 +265,42 @@ Rules: - Action URLs must not embed recipient, route, provider, authorization, wallet, preference, or diagnostic data. - Unauthenticated action pages must remain safe even when opened by the wrong user, crawler, browser preview, or expired session. +### Hosted Route Selection Action + +Hosted route-selection actions hydrate an action-scoped browser view only after validation and user context checks: + +```json +{ + "status": "ready", + "actionId": "mpt_act_789", + "actionType": "route_selection", + "expiresAt": "2026-06-24T20:00:00Z", + "options": [ + { + "optionId": "mpt_route_option_123", + "chain": "base", + "network": "mainnet", + "asset": "USDC", + "payToDappId": "smartrust-wallet", + "displayName": "SmarTrust Wallet - Base USDC" + } + ] +} +``` + +Decisions either select an action-scoped route option, leave the current choice unchanged, or deny the action: + +```json +{ + "decision": "select_route", + "selectedOptionId": "mpt_route_option_123" +} +``` + +Completion states include `selected_route`, `unchanged`, `denied`, `completed`, `expired`, `invalid`, `replayed`, and `restart_required`. + +Hosted action payloads must not expose raw identifiers, wallet addresses, route preferences, unrelated PayToDapps, provider internals, wallet graphs, private diagnostics, or broader route inventory. + ### NEAR 1Click Quote Confirmation The MVP crypto-native execution contract is a NEAR Intents / 1Click quote flow. The resolver may return a `mypaytag.near_1click.quote_option.v1` object when a PayingDapp needs to confirm a concrete cross-chain execution route before receiving payable instructions. diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index 542bac8..f0db6b0 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -120,6 +120,11 @@ as `no_route` or `provider_unavailable`. They must not expose route preferences, unrelated PayToDapps, wallet graphs, raw identifiers, or payment instructions. +Hosted route-selection action contracts expose only action-scoped route options +after validation and user context checks. Decisions can select an option, leave +the choice unchanged, or deny the action. Completion states include expired, +invalid, replayed, and restart-required outcomes without private diagnostics. + ### PayingDapp Resolve ```http diff --git a/packages/protocol/schemas/hosted-action-completion.schema.json b/packages/protocol/schemas/hosted-action-completion.schema.json new file mode 100644 index 0000000..2afc140 --- /dev/null +++ b/packages/protocol/schemas/hosted-action-completion.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-completion.schema.json", + "title": "HostedActionCompletion", + "type": "object", + "additionalProperties": false, + "required": ["status", "actionId"], + "properties": { + "status": { "enum": ["selected_route", "unchanged", "denied", "completed", "expired", "invalid", "replayed", "restart_required"] }, + "actionId": { "type": "string", "minLength": 1 }, + "selectedOptionId": { "type": "string", "minLength": 1 } + }, + "allOf": [ + { + "if": { + "properties": { "status": { "const": "selected_route" } }, + "required": ["status"] + }, + "then": { "required": ["selectedOptionId"] } + } + ], + "not": { + "anyOf": [ + { "required": ["account"] }, + { "required": ["address"] }, + { "required": ["recipientAddress"] }, + { "required": ["rawIdentifier"] }, + { "required": ["routePreference"] }, + { "required": ["walletGraph"] }, + { "required": ["providerPayload"] }, + { "required": ["privateDiagnostics"] } + ] + } +} diff --git a/packages/protocol/schemas/hosted-action-decision.schema.json b/packages/protocol/schemas/hosted-action-decision.schema.json new file mode 100644 index 0000000..d5f59e8 --- /dev/null +++ b/packages/protocol/schemas/hosted-action-decision.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-decision.schema.json", + "title": "HostedActionDecision", + "type": "object", + "additionalProperties": false, + "required": ["decision"], + "properties": { + "decision": { "enum": ["select_route", "leave_unchanged", "deny"] }, + "selectedOptionId": { "type": "string", "minLength": 1 } + }, + "allOf": [ + { + "if": { + "properties": { "decision": { "const": "select_route" } }, + "required": ["decision"] + }, + "then": { "required": ["selectedOptionId"] } + }, + { + "if": { + "properties": { "decision": { "enum": ["leave_unchanged", "deny"] } }, + "required": ["decision"] + }, + "then": { + "not": { "required": ["selectedOptionId"] } + } + } + ], + "not": { + "anyOf": [ + { "required": ["account"] }, + { "required": ["address"] }, + { "required": ["recipientAddress"] }, + { "required": ["rawIdentifier"] }, + { "required": ["routePreference"] }, + { "required": ["walletGraph"] }, + { "required": ["providerPayload"] }, + { "required": ["privateDiagnostics"] } + ] + } +} diff --git a/packages/protocol/schemas/hosted-action-view.schema.json b/packages/protocol/schemas/hosted-action-view.schema.json new file mode 100644 index 0000000..40871a9 --- /dev/null +++ b/packages/protocol/schemas/hosted-action-view.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-view.schema.json", + "title": "HostedActionView", + "type": "object", + "additionalProperties": false, + "required": ["status", "actionId", "actionType"], + "properties": { + "status": { "enum": ["ready", "expired", "invalid", "completed", "replayed", "restart_required"] }, + "actionId": { "type": "string", "minLength": 1 }, + "actionType": { "const": "route_selection" }, + "expiresAt": { "type": "string", "format": "date-time" }, + "options": { + "type": "array", + "items": { "$ref": "#/$defs/routeOption" } + } + }, + "allOf": [ + { + "if": { + "properties": { "status": { "const": "ready" } }, + "required": ["status"] + }, + "then": { + "required": ["expiresAt", "options"], + "properties": { + "options": { "minItems": 1 } + } + } + } + ], + "not": { + "anyOf": [ + { "required": ["account"] }, + { "required": ["address"] }, + { "required": ["recipientAddress"] }, + { "required": ["rawIdentifier"] }, + { "required": ["routePreference"] }, + { "required": ["unrelatedPayToDapps"] }, + { "required": ["walletGraph"] }, + { "required": ["providerPayload"] }, + { "required": ["privateDiagnostics"] } + ] + }, + "$defs": { + "routeOption": { + "type": "object", + "additionalProperties": false, + "required": ["optionId", "chain", "network", "asset", "payToDappId", "displayName"], + "properties": { + "optionId": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "payToDappId": { "type": "string", "minLength": 1 }, + "displayName": { "type": "string", "minLength": 1 } + } + } + } +} diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 6cc3dff..08fbc7c 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -1,4 +1,7 @@ import type { + HostedActionCompletion, + HostedActionDecision, + HostedActionView, MyPayTagIntent, NearOneClickPayableInstruction, NearOneClickQuoteOption, @@ -84,6 +87,54 @@ export const validRouteUnavailableResponse: RouteDeleteResponse = { status: "provider_unavailable", }; +export const validHostedActionView: HostedActionView = { + status: "ready", + actionId: "mpt_act_789", + actionType: "route_selection", + expiresAt: "2026-06-24T20:00:00Z", + options: [ + { + optionId: "mpt_route_option_123", + chain: "base", + network: "mainnet", + asset: "USDC", + payToDappId: "smartrust-wallet", + displayName: "SmarTrust Wallet - Base USDC", + }, + ], +}; + +export const expiredHostedActionView: HostedActionView = { + status: "expired", + actionId: "mpt_act_expired", + actionType: "route_selection", +}; + +export const replayedHostedActionCompletion: HostedActionCompletion = { + status: "replayed", + actionId: "mpt_act_replayed", +}; + +export const restartRequiredHostedActionCompletion: HostedActionCompletion = { + status: "restart_required", + actionId: "mpt_act_restart", +}; + +export const validHostedActionDecision: HostedActionDecision = { + decision: "select_route", + selectedOptionId: "mpt_route_option_123", +}; + +export const denyHostedActionDecision: HostedActionDecision = { + decision: "deny", +}; + +export const validHostedActionCompletion: HostedActionCompletion = { + status: "selected_route", + actionId: "mpt_act_789", + selectedOptionId: "mpt_route_option_123", +}; + export const validResolveRequest: ResolveRequest = { recipient: { identifierType: "paytag", diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index 20c6998..206055a 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -1,6 +1,368 @@ /* eslint-disable */ // Generated from packages/protocol/schemas. Do not edit by hand. +export const HostedActionCompletionSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-completion.schema.json", + "title": "HostedActionCompletion", + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "actionId" + ], + "properties": { + "status": { + "enum": [ + "selected_route", + "unchanged", + "denied", + "completed", + "expired", + "invalid", + "replayed", + "restart_required" + ] + }, + "actionId": { + "type": "string", + "minLength": 1 + }, + "selectedOptionId": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "selected_route" + } + }, + "required": [ + "status" + ] + }, + "then": { + "required": [ + "selectedOptionId" + ] + } + } + ], + "not": { + "anyOf": [ + { + "required": [ + "account" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "rawIdentifier" + ] + }, + { + "required": [ + "routePreference" + ] + }, + { + "required": [ + "walletGraph" + ] + }, + { + "required": [ + "providerPayload" + ] + }, + { + "required": [ + "privateDiagnostics" + ] + } + ] + } +} as const; + +export const HostedActionDecisionSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-decision.schema.json", + "title": "HostedActionDecision", + "type": "object", + "additionalProperties": false, + "required": [ + "decision" + ], + "properties": { + "decision": { + "enum": [ + "select_route", + "leave_unchanged", + "deny" + ] + }, + "selectedOptionId": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "decision": { + "const": "select_route" + } + }, + "required": [ + "decision" + ] + }, + "then": { + "required": [ + "selectedOptionId" + ] + } + }, + { + "if": { + "properties": { + "decision": { + "enum": [ + "leave_unchanged", + "deny" + ] + } + }, + "required": [ + "decision" + ] + }, + "then": { + "not": { + "required": [ + "selectedOptionId" + ] + } + } + } + ], + "not": { + "anyOf": [ + { + "required": [ + "account" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "rawIdentifier" + ] + }, + { + "required": [ + "routePreference" + ] + }, + { + "required": [ + "walletGraph" + ] + }, + { + "required": [ + "providerPayload" + ] + }, + { + "required": [ + "privateDiagnostics" + ] + } + ] + } +} as const; + +export const HostedActionViewSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/hosted-action-view.schema.json", + "title": "HostedActionView", + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "actionId", + "actionType" + ], + "properties": { + "status": { + "enum": [ + "ready", + "expired", + "invalid", + "completed", + "replayed", + "restart_required" + ] + }, + "actionId": { + "type": "string", + "minLength": 1 + }, + "actionType": { + "const": "route_selection" + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/$defs/routeOption" + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "ready" + } + }, + "required": [ + "status" + ] + }, + "then": { + "required": [ + "expiresAt", + "options" + ], + "properties": { + "options": { + "minItems": 1 + } + } + } + } + ], + "not": { + "anyOf": [ + { + "required": [ + "account" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "rawIdentifier" + ] + }, + { + "required": [ + "routePreference" + ] + }, + { + "required": [ + "unrelatedPayToDapps" + ] + }, + { + "required": [ + "walletGraph" + ] + }, + { + "required": [ + "providerPayload" + ] + }, + { + "required": [ + "privateDiagnostics" + ] + } + ] + }, + "$defs": { + "routeOption": { + "type": "object", + "additionalProperties": false, + "required": [ + "optionId", + "chain", + "network", + "asset", + "payToDappId", + "displayName" + ], + "properties": { + "optionId": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "payToDappId": { + "type": "string", + "minLength": 1 + }, + "displayName": { + "type": "string", + "minLength": 1 + } + } + } + } +} as const; + export const MypaytagIntentSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/mypaytag-intent.schema.json", @@ -2026,6 +2388,9 @@ export const StatusSchema = { } as const; export const protocolSchemas = { + "hosted-action-completion": HostedActionCompletionSchema, + "hosted-action-decision": HostedActionDecisionSchema, + "hosted-action-view": HostedActionViewSchema, "mypaytag-intent": MypaytagIntentSchema, "near-one-click-payable-instruction": NearOneClickPayableInstructionSchema, "near-one-click-quote-option": NearOneClickQuoteOptionSchema, diff --git a/packages/protocol/src/generated/types.ts b/packages/protocol/src/generated/types.ts index 34da612..bb46ede 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -1,6 +1,9 @@ /* eslint-disable */ // Generated from packages/protocol/schemas. Do not edit by hand. +export type { HostedActionCompletion } from "./types/hosted-action-completion.js"; +export type { HostedActionDecision } from "./types/hosted-action-decision.js"; +export type { HostedActionView } from "./types/hosted-action-view.js"; export type { MyPayTagIntent } from "./types/mypaytag-intent.js"; export type { NearOneClickPayableInstruction } from "./types/near-one-click-payable-instruction.js"; export type { NearOneClickQuoteOption } from "./types/near-one-click-quote-option.js"; diff --git a/packages/protocol/src/generated/types/hosted-action-completion.ts b/packages/protocol/src/generated/types/hosted-action-completion.ts new file mode 100644 index 0000000..a0e9f2a --- /dev/null +++ b/packages/protocol/src/generated/types/hosted-action-completion.ts @@ -0,0 +1,18 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/hosted-action-completion.schema.json. Do not edit by hand. + +export type HostedActionCompletion = { + [k: string]: unknown; +} & { + status: + | "selected_route" + | "unchanged" + | "denied" + | "completed" + | "expired" + | "invalid" + | "replayed" + | "restart_required"; + actionId: string; + selectedOptionId?: string; +}; diff --git a/packages/protocol/src/generated/types/hosted-action-decision.ts b/packages/protocol/src/generated/types/hosted-action-decision.ts new file mode 100644 index 0000000..9499175 --- /dev/null +++ b/packages/protocol/src/generated/types/hosted-action-decision.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/hosted-action-decision.schema.json. Do not edit by hand. + +export type HostedActionDecision = { + [k: string]: unknown; +} & { + decision: "select_route" | "leave_unchanged" | "deny"; + selectedOptionId?: string; +}; diff --git a/packages/protocol/src/generated/types/hosted-action-view.ts b/packages/protocol/src/generated/types/hosted-action-view.ts new file mode 100644 index 0000000..6d64c76 --- /dev/null +++ b/packages/protocol/src/generated/types/hosted-action-view.ts @@ -0,0 +1,21 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/hosted-action-view.schema.json. Do not edit by hand. + +export type HostedActionView = { + [k: string]: unknown; +} & { + status: "ready" | "expired" | "invalid" | "completed" | "replayed" | "restart_required"; + actionId: string; + actionType: "route_selection"; + expiresAt?: string; + options?: RouteOption[]; +}; + +export interface RouteOption { + optionId: string; + chain: string; + network: string; + asset: string; + payToDappId: string; + displayName: string; +} diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index 7c1c7c1..a3b7c43 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -5,6 +5,13 @@ import { describe, expect, it } from "vitest"; import { parse as parseYaml } from "yaml"; import { + denyHostedActionDecision, + expiredHostedActionView, + replayedHostedActionCompletion, + restartRequiredHostedActionCompletion, + validateHostedActionCompletion, + validateHostedActionDecision, + validateHostedActionView, validateMyPayTagIntent, validateNearOneClickPayableInstruction, validateNotificationEvent, @@ -23,6 +30,9 @@ import { validateRouteQuotePreview, validateRouteUpdateRequest, validateStatus, + validHostedActionCompletion, + validHostedActionDecision, + validHostedActionView, validMyPayTagIntent, validNearOneClickPayableInstruction, validNoRouteResponse, @@ -103,6 +113,11 @@ describe("@mypaytag/protocol", () => { expect(validateRouteReadResponse(validRouteReadResponse)).toEqual(validRouteReadResponse); expect(validateRouteUpdateRequest(validRouteUpdateRequest)).toEqual(validRouteUpdateRequest); expect(validateRouteDeleteResponse(validRouteDeleteResponse)).toEqual(validRouteDeleteResponse); + expect(validateHostedActionView(validHostedActionView)).toEqual(validHostedActionView); + expect(validateHostedActionDecision(validHostedActionDecision)).toEqual(validHostedActionDecision); + expect(validateHostedActionCompletion(validHostedActionCompletion)).toEqual( + validHostedActionCompletion, + ); expect(validateProviderResponse(validProviderResponse)).toEqual(validProviderResponse); expect(validateMyPayTagIntent(validMyPayTagIntent)).toEqual(validMyPayTagIntent); expect(validateNotificationEvent(validNotificationEvent)).toEqual(validNotificationEvent); @@ -185,6 +200,78 @@ describe("@mypaytag/protocol", () => { } }); + it("accepts hosted route-selection action view, decision, and completion states", () => { + expect(validateHostedActionView(validHostedActionView)).toEqual(validHostedActionView); + expect(validateHostedActionView(expiredHostedActionView)).toEqual(expiredHostedActionView); + expect(validateHostedActionDecision(validHostedActionDecision)).toEqual(validHostedActionDecision); + expect(validateHostedActionDecision(denyHostedActionDecision)).toEqual(denyHostedActionDecision); + expect(validateHostedActionCompletion(validHostedActionCompletion)).toEqual( + validHostedActionCompletion, + ); + expect(validateHostedActionCompletion(replayedHostedActionCompletion)).toEqual( + replayedHostedActionCompletion, + ); + expect(validateHostedActionCompletion(restartRequiredHostedActionCompletion)).toEqual( + restartRequiredHostedActionCompletion, + ); + }); + + it("rejects hosted action payloads with private route, wallet, provider, or diagnostic data", () => { + for (const [field, value] of [ + ["address", "0xabc123"], + ["recipientAddress", "0xabc123"], + ["rawIdentifier", "abd123@cubid.mypaytag"], + ["routePreference", "default"], + ["unrelatedPayToDapps", ["other-wallet"]], + ["walletGraph", { connectedWallets: ["0xabc123"] }], + ["providerPayload", validProviderResponse.paymentInstruction.payload], + ["privateDiagnostics", { reason: "rls_miss" }], + ] as const) { + expect(() => + validateHostedActionView({ + ...validHostedActionView, + [field]: value, + }), + ).toThrow(); + + expect(() => + validateHostedActionDecision({ + ...validHostedActionDecision, + [field]: value, + }), + ).toThrow(); + + expect(() => + validateHostedActionCompletion({ + ...validHostedActionCompletion, + [field]: value, + }), + ).toThrow(); + } + }); + + it("requires selected hosted route decisions and completions to name the selected option", () => { + expect(() => + validateHostedActionDecision({ + decision: "select_route", + }), + ).toThrow(); + + expect(() => + validateHostedActionDecision({ + decision: "deny", + selectedOptionId: "mpt_route_option_123", + }), + ).toThrow(); + + expect(() => + validateHostedActionCompletion({ + status: "selected_route", + actionId: "mpt_act_789", + }), + ).toThrow(); + }); + it("rejects paths that omit network", () => { expect(() => validateResolveRequest({ @@ -479,6 +566,12 @@ describe("@mypaytag/protocol", () => { openApi.paths["/payto-routes/{routeId}"].patch.responses["200"].content["application/json"].examples.route.value; const routeDeleteResponseExample = openApi.paths["/payto-routes/{routeId}"].delete.responses["200"].content["application/json"].examples.revoked.value; + const hostedActionViewExample = + openApi.paths["/hosted-actions/{actionId}"].get.responses["200"].content["application/json"].examples.readyRouteSelection.value; + const hostedActionDecisionExample = + openApi.paths["/hosted-actions/{actionId}"].post.requestBody.content["application/json"].examples.selectRoute.value; + const hostedActionCompletionExample = + openApi.paths["/hosted-actions/{actionId}"].post.responses["200"].content["application/json"].examples.selectedRoute.value; const notificationExample = openApi.paths["/notifications"].post.requestBody.content["application/json"].examples.paymentIntentCreated.value; const providerCallbackExample = @@ -498,6 +591,13 @@ describe("@mypaytag/protocol", () => { expect(validateRouteUpdateRequest(routeUpdateRequestExample)).toEqual(routeUpdateRequestExample); expect(validateRouteReadResponse(routeUpdateResponseExample)).toEqual(routeUpdateResponseExample); expect(validateRouteDeleteResponse(routeDeleteResponseExample)).toEqual(routeDeleteResponseExample); + expect(validateHostedActionView(hostedActionViewExample)).toEqual(hostedActionViewExample); + expect(validateHostedActionDecision(hostedActionDecisionExample)).toEqual( + hostedActionDecisionExample, + ); + expect(validateHostedActionCompletion(hostedActionCompletionExample)).toEqual( + hostedActionCompletionExample, + ); expect(validateNotificationEvent(notificationExample)).toEqual(notificationExample); expect(validateProviderCallbackRequest(providerCallbackExample)).toEqual(providerCallbackExample); expect(validateProviderResponse(providerResponseExample)).toEqual(providerResponseExample); diff --git a/packages/protocol/src/validators.ts b/packages/protocol/src/validators.ts index 5ed6395..4883767 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -3,6 +3,9 @@ import addFormats from "ajv-formats"; import { protocolSchemas } from "./generated/schemas.js"; import type { + HostedActionCompletion, + HostedActionDecision, + HostedActionView, MyPayTagIntent, NearOneClickPayableInstruction, NearOneClickQuoteOption, @@ -67,6 +70,15 @@ export function isProtocolPayload(schemaName: ProtocolSchemaName, payload: un export const validateStatus = (payload: unknown) => validateProtocolPayload("status", payload); +export const validateHostedActionView = (payload: unknown) => + validateProtocolPayload("hosted-action-view", payload); + +export const validateHostedActionDecision = (payload: unknown) => + validateProtocolPayload("hosted-action-decision", payload); + +export const validateHostedActionCompletion = (payload: unknown) => + validateProtocolPayload("hosted-action-completion", payload); + export const validateRouteRegistrationRequest = (payload: unknown) => validateProtocolPayload("route-registration-request", payload); From eab8f4bf5b2e2af3d8c502f0e2407c140691abf8 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:49:15 -0400 Subject: [PATCH 18/21] Document paytag availability boundary --- agent-context/session-log/main.md | 11 ++++++ agent-context/todo.md | 2 +- .../protocol-and-sdk-architecture.md | 11 ++++++ .../integration/notifications-and-fixtures.md | 16 +++++++++ packages/testing/src/index.ts | 36 +++++++++++++++++-- packages/testing/src/testing.test.ts | 10 ++++++ 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 47c255e..efb8c0c 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -391,3 +391,14 @@ todo: mypaytag-sdk:GPTS-S7-T7 summary: Added hosted route-selection action view, decision, and completion protocol schemas with generated exports, fixtures, validators, OpenAPI examples/components, and privacy/decision-state tests. validation: Ran pnpm generate, pnpm --filter @mypaytag/protocol build, pnpm --filter @mypaytag/protocol test, pnpm api:validate, pnpm api:postman, and pnpm scan:public-boundary; focused tests passed with 59 workspace cases and OpenAPI/Postman/public-boundary checks passed. follow-ups: Continue Sprint 7 by deciding/documenting public placement for paytag availability contracts. + +--- + +## 2026-06-28-gpts-s7-t8 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T8 +summary: Documented paytag availability and issuance as a private MyPayTag/Cubid service-boundary contract rather than a public integrator SDK schema, and expanded public-safe testing fixtures for idempotent retry plus revoked/expired reuse policy cases. +validation: Ran pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm scan:public-boundary; focused tests passed with 59 workspace cases and the public-boundary scan passed. +follow-ups: Continue Sprint 7 by expanding MVP happy-path fixtures and examples. diff --git a/agent-context/todo.md b/agent-context/todo.md index 06e3990..0430544 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -591,7 +591,7 @@ Acceptance notes: ### GPTS-S7-T8 Decide And Document Paytag Availability Contract Placement -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t8 Depends on: cubid-monorepo:CUBID-PAYTAG-02, mypaytag:GPTR-S7-T4 diff --git a/docs/engineering/protocol-and-sdk-architecture.md b/docs/engineering/protocol-and-sdk-architecture.md index f0db6b0..ecd423a 100644 --- a/docs/engineering/protocol-and-sdk-architecture.md +++ b/docs/engineering/protocol-and-sdk-architecture.md @@ -125,6 +125,17 @@ after validation and user context checks. Decisions can select an option, leave the choice unchanged, or deny the action. Completion states include expired, invalid, replayed, and restart-required outcomes without private diagnostics. +### Paytag Availability Boundary + +Paytag availability and issuance are intentionally not public integrator API +contracts in the MVP SDK. They belong to the private MyPayTag/Cubid service +boundary: Cubid owns identity evidence and consent, while MyPayTag owns paytag +uniqueness and availability policy. + +The public SDK keeps public-safe availability fixtures only. No availability +boundary should expose wallet addresses, payment route data, route preferences, +wallet graphs, provider payloads, or payment instructions to Cubid. + ### PayingDapp Resolve ```http diff --git a/docs/integration/notifications-and-fixtures.md b/docs/integration/notifications-and-fixtures.md index 7ce5db2..ce7a3ea 100644 --- a/docs/integration/notifications-and-fixtures.md +++ b/docs/integration/notifications-and-fixtures.md @@ -36,7 +36,23 @@ Use `@mypaytag/testing` for reusable fixtures and mocks: - opaque default paytag examples such as `abd123@cubid.mypaytag`, - explicit raw-stamp paytag examples such as `+1234569999@phone.cubid.mypaytag`, - paytag availability, unavailable, and reserved-name checks before issuance, +- idempotent retry, revoked-reuse-blocked, and expired-reuse-blocked paytag + availability examples, - negative-disclosure responses for `no_route`, `authorization_required`, and `user_action_required`, - provider callback and provider response, - `payment_intent_created` notification payload. + +## Paytag Availability Placement + +Paytag availability and issuance are not public integrator API contracts in the +MVP SDK. They are private MyPayTag/Cubid service-boundary checks. + +Cubid owns identity, stamp evidence, and user consent. MyPayTag owns paytag +uniqueness and availability policy. The availability boundary must not send +wallet addresses, payment routes, route preferences, wallet graphs, provider +payloads, or payment instructions to Cubid. + +The testing package keeps only public-safe availability fixtures so examples can +exercise available, unavailable, reserved, idempotent retry, revoked reuse, and +expired reuse cases without exposing the private service contract. diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index d696787..470a35d 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -37,6 +37,9 @@ export interface MyPayTagFixtures { available: PaytagAvailabilityFixture; unavailable: PaytagAvailabilityFixture; reserved: PaytagAvailabilityFixture; + idempotentRetry: PaytagAvailabilityFixture; + revokedReuseBlocked: PaytagAvailabilityFixture; + expiredReuseBlocked: PaytagAvailabilityFixture; }; negativeDisclosure: { noRoute: ResolveResponse; @@ -92,9 +95,20 @@ export interface PaytagExampleFixture { export interface PaytagAvailabilityFixture { paytag: string; - status: "available" | "unavailable" | "reserved"; + status: + | "available" + | "unavailable" + | "reserved" + | "idempotent_retry" + | "revoked_reuse_blocked" + | "expired_reuse_blocked"; canIssue: boolean; - publicReason?: "already_taken" | "reserved_namespace"; + publicReason?: + | "already_taken" + | "reserved_namespace" + | "same_request_replay" + | "revoked_recently" + | "expired_recently"; } export const myPayTagFixtures: MyPayTagFixtures = { @@ -129,6 +143,24 @@ export const myPayTagFixtures: MyPayTagFixtures = { canIssue: false, publicReason: "reserved_namespace", }, + idempotentRetry: { + paytag: "new456@cubid.mypaytag", + status: "idempotent_retry", + canIssue: true, + publicReason: "same_request_replay", + }, + revokedReuseBlocked: { + paytag: "revoked789@cubid.mypaytag", + status: "revoked_reuse_blocked", + canIssue: false, + publicReason: "revoked_recently", + }, + expiredReuseBlocked: { + paytag: "expired789@cubid.mypaytag", + status: "expired_reuse_blocked", + canIssue: false, + publicReason: "expired_recently", + }, }, negativeDisclosure: { noRoute: validNoRouteResponse, diff --git a/packages/testing/src/testing.test.ts b/packages/testing/src/testing.test.ts index feb925d..55b09e8 100644 --- a/packages/testing/src/testing.test.ts +++ b/packages/testing/src/testing.test.ts @@ -28,6 +28,16 @@ describe("@mypaytag/testing", () => { expect(myPayTagFixtures.paytags.availability.available.canIssue).toBe(true); expect(myPayTagFixtures.paytags.availability.unavailable.publicReason).toBe("already_taken"); expect(myPayTagFixtures.paytags.availability.reserved.publicReason).toBe("reserved_namespace"); + expect(myPayTagFixtures.paytags.availability.idempotentRetry).toMatchObject({ + status: "idempotent_retry", + canIssue: true, + }); + expect(myPayTagFixtures.paytags.availability.revokedReuseBlocked.publicReason).toBe( + "revoked_recently", + ); + expect(myPayTagFixtures.paytags.availability.expiredReuseBlocked.publicReason).toBe( + "expired_recently", + ); expect(myPayTagFixtures.paytags.negativeDisclosure.noRoute).toEqual({ status: "no_route" }); expect(myPayTagFixtures.paytags.negativeDisclosure.authorizationRequired).toEqual({ status: "authorization_required", From b7d4f133b72b9111ea972833e291418619170071 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:52:12 -0400 Subject: [PATCH 19/21] Expand MVP fixtures and examples --- agent-context/session-log/main.md | 11 ++++ agent-context/todo.md | 2 +- examples/paying-dapp-basic/README.md | 7 ++- examples/paying-dapp-basic/index.mjs | 34 +++++++++++++ examples/payto-dapp-modality-b/README.md | 2 + examples/payto-dapp-modality-b/index.mjs | 14 ++++++ packages/testing/src/index.ts | 64 ++++++++++++++++++++++++ packages/testing/src/testing.test.ts | 8 +++ 8 files changed, 140 insertions(+), 2 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index efb8c0c..14d26fa 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -402,3 +402,14 @@ todo: mypaytag-sdk:GPTS-S7-T8 summary: Documented paytag availability and issuance as a private MyPayTag/Cubid service-boundary contract rather than a public integrator SDK schema, and expanded public-safe testing fixtures for idempotent retry plus revoked/expired reuse policy cases. validation: Ran pnpm --filter @mypaytag/testing typecheck, pnpm --filter @mypaytag/testing test, and pnpm scan:public-boundary; focused tests passed with 59 workspace cases and the public-boundary scan passed. follow-ups: Continue Sprint 7 by expanding MVP happy-path fixtures and examples. + +--- + +## 2026-06-28-gpts-s7-t9 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T9 +summary: Expanded public-safe testing fixtures for route CRUD, hosted route selection, and NEAR 1Click quote selection/payable instructions; updated PayingDapp and PayToDapp examples to exercise same-chain resolve, NEAR 1Click, route update/delete, and provider response validation without direct Cubid probing or broad solver fanout. +validation: Ran pnpm build, pnpm --filter @mypaytag/testing test, pnpm example:paying-dapp, pnpm example:payto-dapp, and pnpm scan:public-boundary; examples executed successfully and focused tests passed with 59 workspace cases. +follow-ups: Complete Sprint 7 with regenerated artifact validation. diff --git a/agent-context/todo.md b/agent-context/todo.md index 0430544..6cac577 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -607,7 +607,7 @@ Acceptance notes: ### GPTS-S7-T9 Expand MVP Happy-Path Fixtures And Examples -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t9 Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T6, mypaytag-sdk:GPTS-S7-T7, mypaytag-sdk:GPTS-S7-T8 diff --git a/examples/paying-dapp-basic/README.md b/examples/paying-dapp-basic/README.md index 82cd8a4..aed9a72 100644 --- a/examples/paying-dapp-basic/README.md +++ b/examples/paying-dapp-basic/README.md @@ -15,4 +15,9 @@ The example uses only public SDK and testing helpers. It demonstrates: - building a resolve request with `@mypaytag/sdk`, - parsing resolver responses, - branching on `resolved`, `no_route`, `user_action_required`, and provider failure statuses, -- reading the typed provider destination from the normalized intent. +- reading the typed provider destination from the normalized intent, +- building a same-chain resolve request, +- parsing a NEAR 1Click quote option, confirming the selected quote, and parsing the returned payable instruction. + +The example does not probe Cubid directly and does not require LI.FI, Squid, +0x, Across, LayerZero/Stargate, or generic solver fanout for the MVP path. diff --git a/examples/paying-dapp-basic/index.mjs b/examples/paying-dapp-basic/index.mjs index 9736565..82c84ea 100644 --- a/examples/paying-dapp-basic/index.mjs +++ b/examples/paying-dapp-basic/index.mjs @@ -1,9 +1,12 @@ import { buildResolveRequest, + buildNearOneClickQuoteSelectionRequest, getActionUrl, isActionRequired, isResolved, isRetryable, + parseNearOneClickPayableInstruction, + parseNearOneClickQuoteOption, parseResolveResponse, } from "@mypaytag/sdk"; import { createMockResolver, myPayTagFixtures } from "@mypaytag/testing"; @@ -13,6 +16,18 @@ const request = buildResolveRequest({ payingDappReference: "example:payout_001", }); +const sameChainRequest = buildResolveRequest({ + ...myPayTagFixtures.resolve.request, + supportedPaths: [ + { + chain: "base", + network: "mainnet", + asset: "USDC", + }, + ], + payingDappReference: "example:same_chain_001", +}); + async function runScenario(label, resolverResponse) { const resolver = createMockResolver(resolverResponse); const response = parseResolveResponse(await resolver.resolve(request)); @@ -52,6 +67,7 @@ async function runScenario(label, resolverResponse) { const scenarios = [ ["resolved", myPayTagFixtures.resolve.resolved], + ["same-chain resolved", myPayTagFixtures.resolve.resolved], ["no route", myPayTagFixtures.resolve.noRoute], ["route selection required", myPayTagFixtures.resolve.routeSelectionRequired], ["provider failure", myPayTagFixtures.resolve.providerFailure], @@ -60,3 +76,21 @@ const scenarios = [ for (const [label, response] of scenarios) { console.log(JSON.stringify(await runScenario(label, response), null, 2)); } + +const quoteOption = parseNearOneClickQuoteOption(myPayTagFixtures.nearOneClick.quoteOption); +const quoteSelectionRequest = buildNearOneClickQuoteSelectionRequest({ + ...myPayTagFixtures.nearOneClick.quoteSelectionRequest, + payingDappReference: sameChainRequest.payingDappReference, +}); +const payableInstruction = parseNearOneClickPayableInstruction( + myPayTagFixtures.nearOneClick.payableInstruction, +); + +console.log(JSON.stringify({ + label: "near-1click quote flow", + quoteId: quoteOption.quoteId, + selectedRouteReference: quoteSelectionRequest.selectedRouteReference, + payableKind: payableInstruction.instruction.kind, + depositAsset: payableInstruction.instruction.payload.depositAsset, + recipientAmount: payableInstruction.instruction.payload.recipientAmount, +}, null, 2)); diff --git a/examples/payto-dapp-modality-b/README.md b/examples/payto-dapp-modality-b/README.md index 7305b27..212751b 100644 --- a/examples/payto-dapp-modality-b/README.md +++ b/examples/payto-dapp-modality-b/README.md @@ -12,6 +12,8 @@ pnpm example:payto-dapp The example uses public provider SDK and testing helpers. It demonstrates: - registering supported receive routes without wallet addresses, +- parsing route list/read responses, +- building route update requests and parsing route revocation responses, - validating a resolver provider-intent callback request, - plugging in callback authentication and replay protection, - returning dynamic payment instructions only inside the provider response. diff --git a/examples/payto-dapp-modality-b/index.mjs b/examples/payto-dapp-modality-b/index.mjs index 2d0748e..4127ab8 100644 --- a/examples/payto-dapp-modality-b/index.mjs +++ b/examples/payto-dapp-modality-b/index.mjs @@ -1,8 +1,12 @@ import { assertProviderResponseMatchesCallback, buildRouteRegistrationRequest, + buildRouteUpdateRequest, parseProviderCallbackRequest, parseProviderResponse, + parseRouteDeleteResponse, + parseRouteListResponse, + parseRouteReadResponse, verifyCallbackAuth, } from "@mypaytag/provider-sdk"; import { createMockPayToDapp, myPayTagFixtures } from "@mypaytag/testing"; @@ -19,6 +23,10 @@ function createMemoryReplayStore() { } const routeRegistration = buildRouteRegistrationRequest(myPayTagFixtures.routeRegistration.valid); +const routeList = parseRouteListResponse(myPayTagFixtures.routeCrud.list); +const routeRead = parseRouteReadResponse(myPayTagFixtures.routeCrud.read); +const routeUpdate = buildRouteUpdateRequest(myPayTagFixtures.routeCrud.update); +const routeDelete = parseRouteDeleteResponse(myPayTagFixtures.routeCrud.delete); const callbackAuthEnvelope = { method: "POST", @@ -73,6 +81,12 @@ console.log( supportedRoutes: routeRegistration.supportedRoutes, containsWalletAddress: "address" in routeRegistration || "recipientAddress" in routeRegistration, }, + routeCrud: { + listedRoutes: routeList.routes.length, + readStatus: routeRead.status, + updateState: routeUpdate.state, + deleteStatus: routeDelete.status, + }, callbackAuth: { firstAuthCheck, replayAuthCheck, diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 470a35d..e9f4056 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -1,23 +1,47 @@ import { + denyHostedActionDecision, + expiredHostedActionView, + replayedHostedActionCompletion, + restartRequiredHostedActionCompletion, + validHostedActionCompletion, + validHostedActionDecision, + validHostedActionView, validMyPayTagIntent, + validNearOneClickPayableInstruction, + validNearOneClickQuoteOption, + validNearOneClickQuoteSelectionRequest, validNoRouteResponse, validNotificationEvent, validProviderCallbackRequest, validProviderResponse, + validRouteDeleteResponse, + validRouteListResponse, + validRouteReadResponse, validResolvedResponse, validResolveRequest, validRouteRegistrationRequest, validRouteRegistrationResponse, validRouteSelectionResponse, + validRouteUpdateRequest, + type HostedActionCompletion, + type HostedActionDecision, + type HostedActionView, type MyPayTagIntent, + type NearOneClickPayableInstruction, + type NearOneClickQuoteOption, + type NearOneClickQuoteSelectionRequest, type NotificationEvent, type ProviderCallbackRequest, type ProviderResponse, + type RouteDeleteResponse, + type RouteListResponse, type ResolveRequest, type ResolveResponse, type RouteQuotePreview, + type RouteReadResponse, type RouteRegistrationRequest, type RouteRegistrationResponse, + type RouteUpdateRequest, validateNotificationEvent, validateResolveRequest, validateRouteRegistrationRequest, @@ -52,6 +76,26 @@ export interface MyPayTagFixtures { forbiddenAddress: RouteRegistrationRequest & { address: string }; response: RouteRegistrationResponse; }; + routeCrud: { + list: RouteListResponse; + read: RouteReadResponse; + update: RouteUpdateRequest; + delete: RouteDeleteResponse; + }; + hostedActions: { + view: HostedActionView; + expiredView: HostedActionView; + decision: HostedActionDecision; + denyDecision: HostedActionDecision; + completion: HostedActionCompletion; + replayedCompletion: HostedActionCompletion; + restartRequiredCompletion: HostedActionCompletion; + }; + nearOneClick: { + quoteOption: NearOneClickQuoteOption; + quoteSelectionRequest: NearOneClickQuoteSelectionRequest; + payableInstruction: NearOneClickPayableInstruction; + }; resolve: { request: ResolveRequest; resolved: ResolveResponse; @@ -178,6 +222,26 @@ export const myPayTagFixtures: MyPayTagFixtures = { }, response: validRouteRegistrationResponse, }, + routeCrud: { + list: validRouteListResponse, + read: validRouteReadResponse, + update: validRouteUpdateRequest, + delete: validRouteDeleteResponse, + }, + hostedActions: { + view: validHostedActionView, + expiredView: expiredHostedActionView, + decision: validHostedActionDecision, + denyDecision: denyHostedActionDecision, + completion: validHostedActionCompletion, + replayedCompletion: replayedHostedActionCompletion, + restartRequiredCompletion: restartRequiredHostedActionCompletion, + }, + nearOneClick: { + quoteOption: validNearOneClickQuoteOption, + quoteSelectionRequest: validNearOneClickQuoteSelectionRequest, + payableInstruction: validNearOneClickPayableInstruction, + }, resolve: { request: validResolveRequest, resolved: validResolvedResponse, diff --git a/packages/testing/src/testing.test.ts b/packages/testing/src/testing.test.ts index 55b09e8..59a8296 100644 --- a/packages/testing/src/testing.test.ts +++ b/packages/testing/src/testing.test.ts @@ -50,6 +50,14 @@ describe("@mypaytag/testing", () => { "payment_intent_created", ); expect(myPayTagFixtures.routeRegistration.forbiddenAddress).toHaveProperty("address"); + expect(myPayTagFixtures.routeCrud.update.state).toBe("disabled"); + expect(myPayTagFixtures.routeCrud.delete.status).toBe("revoked"); + expect(myPayTagFixtures.hostedActions.view.actionType).toBe("route_selection"); + expect(myPayTagFixtures.hostedActions.decision.decision).toBe("select_route"); + expect(myPayTagFixtures.nearOneClick.quoteOption.adapter).toBe("near_intents_1click"); + expect(myPayTagFixtures.nearOneClick.payableInstruction.instruction.kind).toBe( + "near_1click_payable", + ); expect(myPayTagFixtures.routeOptions.directTransfer.method).toBe("direct_transfer"); expect(myPayTagFixtures.routeOptions.sameChainExchange.method).toBe("provider_exchange"); expect(myPayTagFixtures.routeOptions.bridge.method).toBe("bridge"); From 62159d2fbe61f0cebbdcfa5b7c2c77f467e80881 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 18:53:49 -0400 Subject: [PATCH 20/21] Validate sprint 7 contract artifacts --- agent-context/session-log/main.md | 11 +++++++++++ agent-context/todo.md | 2 +- api/postman_collection.json | 10 +++++----- docs/integration/staged-smoke-checklist.md | 8 +++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 14d26fa..1cb4bee 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -413,3 +413,14 @@ todo: mypaytag-sdk:GPTS-S7-T9 summary: Expanded public-safe testing fixtures for route CRUD, hosted route selection, and NEAR 1Click quote selection/payable instructions; updated PayingDapp and PayToDapp examples to exercise same-chain resolve, NEAR 1Click, route update/delete, and provider response validation without direct Cubid probing or broad solver fanout. validation: Ran pnpm build, pnpm --filter @mypaytag/testing test, pnpm example:paying-dapp, pnpm example:payto-dapp, and pnpm scan:public-boundary; examples executed successfully and focused tests passed with 59 workspace cases. follow-ups: Complete Sprint 7 with regenerated artifact validation. + +--- + +## 2026-06-28-gpts-s7-t10 + +agent: Codex +branch: codex/mypaytag-mvp-realignment-20260628 +todo: mypaytag-sdk:GPTS-S7-T10 +summary: Refreshed generated protocol artifacts through `pnpm generate`, refreshed Postman through the repo script, updated the staged smoke checklist for Cubid SDK, MyPayTag backend, test PayingDapp, test PayToDapp, and SmarTrust NEAR 1Click coverage, and ran the full workspace validation gate. +validation: Ran pnpm generate, pnpm api:postman, and pnpm validate; full validation regenerated types, linted OpenAPI without warnings, built/typechecked all packages, passed tests with 59 workspace cases, passed the public-boundary scan, cleaned, and rebuilt. +follow-ups: Sprint 7 is complete. diff --git a/agent-context/todo.md b/agent-context/todo.md index 6cac577..2b23c9f 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -624,7 +624,7 @@ Acceptance notes: ### GPTS-S7-T10 Validate Regenerated SDK Contract Artifacts -Status: Todo +Status: Complete Feature branch: codex/mypaytag-mvp-realignment-20260628 Session log: agent-context/session-log/main.md#2026-06-28-gpts-s7-t10 Depends on: mypaytag-sdk:GPTS-S7-T1, mypaytag-sdk:GPTS-S7-T2, mypaytag-sdk:GPTS-S7-T3, mypaytag-sdk:GPTS-S7-T4, mypaytag-sdk:GPTS-S7-T5, mypaytag-sdk:GPTS-S7-T6, mypaytag-sdk:GPTS-S7-T7, mypaytag-sdk:GPTS-S7-T8, mypaytag-sdk:GPTS-S7-T9 diff --git a/api/postman_collection.json b/api/postman_collection.json index 2930e94..906d48b 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -912,7 +912,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": true\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -969,7 +969,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": true\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1036,7 +1036,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": true\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1103,7 +1103,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": true\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1170,7 +1170,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": true\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", diff --git a/docs/integration/staged-smoke-checklist.md b/docs/integration/staged-smoke-checklist.md index ab9a9ac..2f37ee3 100644 --- a/docs/integration/staged-smoke-checklist.md +++ b/docs/integration/staged-smoke-checklist.md @@ -13,10 +13,12 @@ Run this checklist only after local SDK validation passes. - Cubid SDK can open Cubid-hosted paytag identity and consent actions without exposing wallet, route, provider, asset, payment intent, settlement, solver, bridge, swap, or execution fields. - MyPayTag backend accepts a paytag resolve request using the SDK shape and validates identity/consent through Cubid without exposing Cubid internals to the PayingDapp. - One test PayingDapp calls MyPayTag, not Cubid, with a paytag such as `abd123@cubid.mypaytag`. -- One test PayToDapp registers supported routes without wallet addresses or payment instructions. +- One test PayingDapp exercises same-chain resolve and the NEAR 1Click swap/bridge flow: quote option, selected quote confirmation, and payable instruction parsing. +- One test PayToDapp registers supported routes, reads/list routes, updates a route, and revokes a route without wallet addresses or payment instructions. - MyPayTag returns `no_route`, `authorization_required`, and `user_action_required` without leaking whether a person, paytag, Cubid user, route, or PayToDapp exists. -- MyPayTag selects a compatible route or returns hosted route selection, then calls the selected PayToDapp provider callback. -- The selected PayToDapp returns a provider response with all required `provider_json.payload` fields: provider intent id, chain, network, asset, destination, amount, reference, and expiry. +- MyPayTag selects a compatible route or returns hosted route selection, then calls the selected PayToDapp provider callback with resolver request id, PayingDapp id/reference, selected path, amount, purpose, and expiry. +- The selected PayToDapp returns a provider response with all required `provider_json.payload` fields: provider intent id, resolver reference, PayingDapp id/reference, chain, network, asset, destination, amount, purpose, reference, and expiry. +- SmarTrust swap/bridge staging uses the SDK's dedicated NEAR 1Click MVP contracts and does not require broad solver fanout, LI.FI, Squid, 0x, Across, LayerZero/Stargate, or generic external adapters. - MyPayTag returns a normalized `mypaytag.intent.v1` response matching the SDK schema. ## Launch Evidence From 92d1a7637ff6390ffff7a4f2801a9e4700a83cf1 Mon Sep 17 00:00:00 2001 From: Noak Date: Sun, 28 Jun 2026 22:17:31 -0400 Subject: [PATCH 21/21] Stabilize Postman example generation --- scripts/generate-postman-collection.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/generate-postman-collection.mjs b/scripts/generate-postman-collection.mjs index 41c17a8..11e0d5e 100644 --- a/scripts/generate-postman-collection.mjs +++ b/scripts/generate-postman-collection.mjs @@ -46,6 +46,8 @@ const normalizeJsonExample = (value) => { } if (!value || typeof value !== "object") { + if (typeof value === "number") return ""; + if (typeof value === "boolean") return ""; return typeof value === "string" ? "" : value; }