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..bff3d26 --- /dev/null +++ b/agent-context/2026-06-28-mypaytag-mvp-realignment.md @@ -0,0 +1,85 @@ +# 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`. + +## 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. + - `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. + - 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. + - 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. + - 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 + +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. 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/session-log/main.md b/agent-context/session-log/main.md index f2a822c..1cb4bee 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. @@ -248,3 +248,179 @@ 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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. + +--- + +## 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 a9d82d0..2b23c9f 100644 --- a/agent-context/todo.md +++ b/agent-context/todo.md @@ -371,3 +371,270 @@ 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: 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`. + +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: 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. + +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: 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. + +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: 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. + +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: 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. + +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: 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. + +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. + +## Sprint 7: MVP Gap Closure From SDK Review + +### GPTS-S7-T1 Add NEAR Intents / 1Click MVP Quote Contracts + +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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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: 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 + +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. diff --git a/api/openapi.yaml b/api/openapi.yaml index 97d2e38..92d8215 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,9 +66,9 @@ 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 + 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 @@ -108,21 +108,45 @@ 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: paytag + 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 + resolverReference: mpt_req_123 + payingDappId: sender + payingDappReference: sender:payout_987 + chain: base + network: mainnet + asset: USDC destination: kind: blockchain_address recipientAddress: "0xabc0000000000000000000000000000000000000" + amount: "25.00" + purpose: payout + 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: @@ -140,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 @@ -160,14 +209,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. @@ -211,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: @@ -225,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": @@ -253,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": @@ -276,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": @@ -293,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. @@ -300,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": @@ -323,13 +428,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. @@ -358,6 +470,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. @@ -365,6 +495,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: @@ -459,24 +612,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 @@ -548,7 +702,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 @@ -573,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 @@ -599,44 +754,155 @@ 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: paytag + 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 + - 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 + network: + type: string + minLength: 1 + asset: + type: string + minLength: 1 + destination: + $ref: "#/components/schemas/PaymentDestination" + amount: + type: string + pattern: "^[0-9]+(\\.[0-9]+)?$" + purpose: + type: string + minLength: 1 + reference: + type: string + minLength: 1 + expiresAt: + type: string + format: date-time PaymentDestination: type: object additionalProperties: false @@ -649,6 +915,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 @@ -656,7 +940,7 @@ components: - recipient - payToDappId - supportedRoutes - - consentToken + - authorizationToken properties: recipient: $ref: "#/components/schemas/Recipient" @@ -667,7 +951,7 @@ components: minItems: 1 items: $ref: "#/components/schemas/RoutePath" - consentToken: + authorizationToken: type: string RouteRegistrationResponse: oneOf: @@ -685,7 +969,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 @@ -699,45 +992,106 @@ 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" + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable 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 + - type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: + - no_route + - provider_unavailable HostedActionView: type: object additionalProperties: false @@ -753,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: @@ -777,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 @@ -796,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 @@ -822,8 +1198,11 @@ components: - resolverRequestId - recipient - payingDappId + - payingDappReference - selectedPath - amount + - purpose + - expiresAt properties: resolverRequestId: type: string @@ -831,45 +1210,91 @@ 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 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 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 @@ -882,7 +1307,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 f26d0f3..906d48b 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -169,11 +169,11 @@ "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", + "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": { @@ -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 \"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" }, @@ -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": { @@ -418,7 +534,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 +579,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 +634,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 +689,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", @@ -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\": \"\"\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\": \"\"\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\": \"\"\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\": \"\"\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\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1390,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" }, @@ -1622,7 +1738,7 @@ "value": "application/json" } ], - "body": "{\n \"status\": \"\"\n}", + "body": "{\n \"status\": \"\",\n \"actionId\": \"\",\n \"selectedOptionId\": \"\"\n}", "cookie": [], "_postman_previewlanguage": "json" }, @@ -1805,7 +1921,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 +1966,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 +2021,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 +2076,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", @@ -2021,7 +2137,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..0a48d4e 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 @@ -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. @@ -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" } ``` @@ -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", @@ -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 @@ -142,7 +183,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" } } @@ -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 @@ -167,8 +209,8 @@ Request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "supportedPaths": [ { @@ -206,7 +248,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" } } @@ -223,6 +265,93 @@ 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. + +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: @@ -235,12 +364,13 @@ Request: ```json { - "resolverRequestId": "gptr_req_123", + "resolverRequestId": "mpt_req_123", "recipient": { - "identifierType": "verified_stamp", - "identifierAlias": "cubid_stamp_alias_abc" + "identifierType": "paytag", + "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "selectedPath": { "chain": "base", "network": "mainnet", @@ -266,6 +396,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", @@ -274,6 +407,7 @@ Expected provider response: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } @@ -287,6 +421,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. @@ -296,12 +431,12 @@ Rules: { "status": "resolved", "intent": { - "id": "gptr_pi_123", + "id": "mpt_pi_123", "schema": "mypaytag.intent.v1", "status": "ready", "modality": "provider_intent", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "identifierHash": "sha256:..." }, "selectedRoute": { @@ -321,6 +456,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", @@ -329,12 +467,13 @@ Rules: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } }, "references": { - "resolverReference": "gptr_pi_123", + "resolverReference": "mpt_pi_123", "providerReference": "st_pi_456", "payingDappReference": "chaincrew:payout_987" } @@ -352,11 +491,15 @@ Rules: Required `provider_json.payload` keys: - `providerIntentId` +- `resolverReference` +- `payingDappId` +- `payingDappReference` - `chain` - `network` - `asset` - `destination` - `amount` +- `purpose` - `reference` - `expiresAt` @@ -370,13 +513,13 @@ 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 { - "id": "gptr_quote_123", + "id": "mpt_quote_123", "method": "cross_chain_intent", "methodLabel": "Cross-chain intent route", "send": { @@ -401,7 +544,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" } ``` @@ -409,7 +552,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 @@ -423,7 +566,7 @@ Common payload fields: "eventType": "payment_intent_created", "schema": "mypaytag.notification.v1", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "maskedDisplay": "n***@example.com" }, "amount": { @@ -431,7 +574,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..ecd423a 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 pay-to 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, @@ -41,37 +41,42 @@ 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. -## Crypto-Native Execution Solvers +## MVP NEAR 1Click Execution -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. +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. -Short list for SDK adapter support: +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 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` 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` 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. -## 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 pay-to tag 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; +- 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 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 @@ -91,8 +96,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,12 +107,35 @@ Route registration request: "asset": "USDC" } ], - "consentToken": "cubid_consent_token" + "authorizationToken": "mpt_auth_123" } ``` 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. + +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. + +### 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 @@ -119,8 +147,8 @@ Resolve request: ```json { "recipient": { - "identifierType": "verified_stamp", - "identifier": "email:noak@example.com" + "identifierType": "paytag", + "identifier": "abd123@cubid.mypaytag" }, "supportedPaths": [ { @@ -149,12 +177,13 @@ Callback request: ```json { - "resolverRequestId": "gptr_req_123", + "resolverRequestId": "mpt_req_123", "recipient": { - "identifierType": "verified_stamp", - "identifierAlias": "cubid_stamp_alias_abc" + "identifierType": "paytag", + "paytagReference": "paytag_ref_abc" }, "payingDappId": "chaincrew", + "payingDappReference": "chaincrew:payout_987", "selectedPath": { "chain": "base", "network": "mainnet", @@ -181,12 +210,12 @@ Resolved response shape: { "status": "resolved", "intent": { - "id": "gptr_pi_123", + "id": "mpt_pi_123", "schema": "mypaytag.intent.v1", "status": "ready", "modality": "provider_intent", "recipient": { - "identifierType": "verified_stamp", + "identifierType": "paytag", "identifierHash": "sha256:..." }, "selectedRoute": { @@ -206,6 +235,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", @@ -214,12 +246,13 @@ Resolved response shape: "recipientAddress": "0xabc..." }, "amount": "25.00", + "purpose": "payout", "reference": "smartrust:st_pi_456", "expiresAt": "2026-06-24T20:00:00Z" } }, "references": { - "resolverReference": "gptr_pi_123", + "resolverReference": "mpt_pi_123", "providerReference": "st_pi_456", "payingDappReference": "chaincrew:payout_987" } @@ -227,7 +260,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 @@ -262,7 +295,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/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/notifications-and-fixtures.md b/docs/integration/notifications-and-fixtures.md index 6760e22..ce7a3ea 100644 --- a/docs/integration/notifications-and-fixtures.md +++ b/docs/integration/notifications-and-fixtures.md @@ -33,5 +33,26 @@ 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, +- 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/docs/integration/paying-dapps.md b/docs/integration/paying-dapps.md index fe0c5b4..42d9f5a 100644 --- a/docs/integration/paying-dapps.md +++ b/docs/integration/paying-dapps.md @@ -1,13 +1,11 @@ # 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 - 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`. @@ -20,7 +18,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, @@ -48,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. -## Crypto-Native Execution Quotes +## MVP NEAR 1Click Quote Flow -When a PayingDapp supports execution adapters, configure quote providers for the -solver/router surfaces the app can use. The initial MyPayTag SDK solver ids -are: +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 + +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/docs/integration/staged-smoke-checklist.md b/docs/integration/staged-smoke-checklist.md new file mode 100644 index 0000000..2f37ee3 --- /dev/null +++ b/docs/integration/staged-smoke-checklist.md @@ -0,0 +1,27 @@ +# 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 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 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 + +- 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/examples/paying-dapp-basic/README.md b/examples/paying-dapp-basic/README.md index 00038a6..aed9a72 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: @@ -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/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/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/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/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-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/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/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/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/provider-callback-request.schema.json b/packages/protocol/schemas/provider-callback-request.schema.json index 172cecb..dc45500 100644 --- a/packages/protocol/schemas/provider-callback-request.schema.json +++ b/packages/protocol/schemas/provider-callback-request.schema.json @@ -4,19 +4,20 @@ "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": { "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 }, + "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/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-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-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/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 a605f93..08fbc7c 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -1,19 +1,30 @@ import type { + HostedActionCompletion, + HostedActionDecision, + HostedActionView, MyPayTagIntent, + NearOneClickPayableInstruction, + NearOneClickQuoteOption, + NearOneClickQuoteSelectionRequest, NotificationEvent, + PayToRoute, ProviderCallbackRequest, ProviderResponse, + RouteDeleteResponse, + RouteListResponse, RouteQuotePreview, + RouteReadResponse, ResolveRequest, ResolveResponse, RouteRegistrationRequest, RouteRegistrationResponse, + RouteUpdateRequest, } from "./generated/types.js"; export const validRouteRegistrationRequest: RouteRegistrationRequest = { recipient: { - identifierType: "verified_stamp", - identifier: "email:noak@example.com", + identifierType: "paytag", + identifier: "abd123@cubid.mypaytag", }, payToDappId: "smartrust-wallet", supportedRoutes: [ @@ -23,14 +34,14 @@ export const validRouteRegistrationRequest: RouteRegistrationRequest = { asset: "USDC", }, ], - consentToken: "cubid_consent_token", + authorizationToken: "mpt_auth_123", }; export const validRouteRegistrationResponse: RouteRegistrationResponse = { status: "resolved", routes: [ { - id: "gptr_route_123", + id: "mpt_route_123", payToDappId: "smartrust-wallet", chain: "base", network: "mainnet", @@ -40,10 +51,94 @@ 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 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: "verified_stamp", - identifier: "email:noak@example.com", + identifierType: "paytag", + identifier: "abd123@cubid.mypaytag", }, supportedPaths: [ { @@ -62,12 +157,13 @@ 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", + identifierType: "paytag", + paytagReference: "paytag_ref_abc", }, payingDappId: "chaincrew", + payingDappReference: "chaincrew:payout_987", selectedPath: { chain: "base", network: "mainnet", @@ -89,6 +185,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", @@ -97,6 +196,7 @@ export const validProviderResponse: ProviderResponse = { recipientAddress: "0xabc123", }, amount: "25.00", + purpose: "payout", reference: "smartrust:st_pi_456", expiresAt: "2026-06-24T20:00:00Z", }, @@ -104,12 +204,12 @@ 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", recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", identifierHash: "sha256:example", }, selectedRoute: { @@ -126,7 +226,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 +245,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", }, }; @@ -154,7 +254,7 @@ export const validNotificationEvent: NotificationEvent = { eventType: "payment_intent_created", schema: "mypaytag.notification.v1", recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", maskedDisplay: "n***@example.com", }, amount: { @@ -162,7 +262,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 +272,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 +304,77 @@ export const validRouteQuotePreview: RouteQuotePreview = { }, ], expiresAt: "2026-06-24T20:00:00Z", - resolverReference: "gptr_req_123", + 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, +}; + +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 a2b6429..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", @@ -43,7 +405,7 @@ export const MypaytagIntentSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifierHash": { "type": "string", @@ -150,43 +512,267 @@ export const MypaytagIntentSchema = { } }, "$defs": { - "providerPayload": { + "providerPayload": { + "type": "object", + "additionalProperties": true, + "required": [ + "providerIntentId", + "chain", + "network", + "asset", + "destination", + "amount", + "reference", + "expiresAt" + ], + "not": { + "anyOf": [ + { + "required": [ + "recipientAddress" + ] + }, + { + "required": [ + "address" + ] + }, + { + "required": [ + "account" + ] + } + ] + }, + "properties": { + "providerIntentId": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "destination": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "recipientAddress" + ], + "properties": { + "kind": { + "const": "blockchain_address" + }, + "recipientAddress": { + "type": "string", + "minLength": 1 + } + } + }, + "amount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "reference": { + "type": "string", + "minLength": 1 + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + } + } + } +} 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", + "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": true, + "additionalProperties": false, "required": [ - "providerIntentId", "chain", "network", "asset", - "destination", - "amount", - "reference", - "expiresAt" + "amount" ], - "not": { - "anyOf": [ - { - "required": [ - "recipientAddress" - ] - }, - { - "required": [ - "address" - ] - }, - { - "required": [ - "account" - ] - } - ] - }, "properties": { - "providerIntentId": { - "type": "string", - "minLength": 1 - }, "chain": { "type": "string", "minLength": 1 @@ -199,40 +785,95 @@ export const MypaytagIntentSchema = { "type": "string", "minLength": 1 }, - "destination": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "recipientAddress" - ], - "properties": { - "kind": { - "const": "blockchain_address" - }, - "recipientAddress": { - "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]+)?$" }, - "reference": { + "asset": { "type": "string", "minLength": 1 }, - "expiresAt": { - "type": "string", - "format": "date-time" + "chargedTo": { + "const": "sender" + }, + "source": { + "enum": [ + "near_1click", + "resolver", + "paying_dapp" + ] } } } } } 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", @@ -263,7 +904,7 @@ export const NotificationEventSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "maskedDisplay": { "type": "string", @@ -327,6 +968,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", @@ -337,6 +1067,7 @@ export const ProviderCallbackRequestSchema = { "resolverRequestId", "recipient", "payingDappId", + "payingDappReference", "selectedPath", "amount", "purpose", @@ -352,13 +1083,13 @@ export const ProviderCallbackRequestSchema = { "additionalProperties": false, "required": [ "identifierType", - "identifierAlias" + "paytagReference" ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, - "identifierAlias": { + "paytagReference": { "type": "string", "minLength": 1 } @@ -368,6 +1099,10 @@ export const ProviderCallbackRequestSchema = { "type": "string", "minLength": 1 }, + "payingDappReference": { + "type": "string", + "minLength": 1 + }, "selectedPath": { "$ref": "#/$defs/path" }, @@ -475,11 +1210,15 @@ export const ProviderResponseSchema = { "additionalProperties": true, "required": [ "providerIntentId", + "resolverReference", + "payingDappId", + "payingDappReference", "chain", "network", "asset", "destination", "amount", + "purpose", "reference", "expiresAt" ], @@ -507,6 +1246,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 @@ -540,6 +1291,10 @@ export const ProviderResponseSchema = { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "purpose": { + "type": "string", + "minLength": 1 + }, "reference": { "type": "string", "minLength": 1 @@ -603,7 +1358,7 @@ export const ResolveRequestSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifier": { "type": "string", @@ -777,7 +1532,7 @@ export const ResolveResponseSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifierHash": { "type": "string", @@ -939,40 +1694,146 @@ export const ResolveResponseSchema = { } } }, - "amount": { + "amount": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "reference": { + "type": "string", + "minLength": 1 + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + } + }, + "references": { + "type": "object", + "additionalProperties": false, + "required": [ + "resolverReference", + "providerReference", + "payingDappReference" + ], + "properties": { + "resolverReference": { + "type": "string", + "minLength": 1 + }, + "providerReference": { "type": "string", - "pattern": "^[0-9]+(\\.[0-9]+)?$" + "minLength": 1 }, - "reference": { + "payingDappReference": { "type": "string", "minLength": 1 + } + } + } + } +} 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" }, - "expiresAt": { + "routeId": { "type": "string", - "format": "date-time" + "minLength": 1 } } }, - "references": { + { "type": "object", "additionalProperties": false, "required": [ - "resolverReference", - "providerReference", - "payingDappReference" + "status" ], "properties": { - "resolverReference": { + "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 }, - "providerReference": { + "payToDappId": { "type": "string", "minLength": 1 }, - "payingDappReference": { + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { "type": "string", "minLength": 1 + }, + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] } } } @@ -1101,6 +1962,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", @@ -1111,7 +2054,7 @@ export const RouteRegistrationRequestSchema = { "recipient", "payToDappId", "supportedRoutes", - "consentToken" + "authorizationToken" ], "properties": { "recipient": { @@ -1128,7 +2071,7 @@ export const RouteRegistrationRequestSchema = { "$ref": "#/$defs/route" } }, - "consentToken": { + "authorizationToken": { "type": "string", "minLength": 1 } @@ -1177,7 +2120,7 @@ export const RouteRegistrationRequestSchema = { ], "properties": { "identifierType": { - "const": "verified_stamp" + "const": "paytag" }, "identifier": { "type": "string", @@ -1319,6 +2262,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", @@ -1340,14 +2388,25 @@ 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, + "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 fa09f8e..bb46ede 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -1,13 +1,24 @@ /* 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"; +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/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/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/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-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/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/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/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/provider-callback-request.ts b/packages/protocol/src/generated/types/provider-callback-request.ts index 70866dc..54c70d9 100644 --- a/packages/protocol/src/generated/types/provider-callback-request.ts +++ b/packages/protocol/src/generated/types/provider-callback-request.ts @@ -4,10 +4,11 @@ export interface ProviderCallbackRequest { resolverRequestId: string; recipient: { - identifierType: "verified_stamp"; - identifierAlias: string; + identifierType: "paytag"; + 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/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-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-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/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 c0eca19..a3b7c43 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -1,23 +1,58 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + import { describe, expect, it } from "vitest"; +import { parse as parseYaml } from "yaml"; import { + denyHostedActionDecision, + expiredHostedActionView, + replayedHostedActionCompletion, + restartRequiredHostedActionCompletion, + validateHostedActionCompletion, + validateHostedActionDecision, + validateHostedActionView, validateMyPayTagIntent, + validateNearOneClickPayableInstruction, validateNotificationEvent, + validateNearOneClickQuoteOption, + validateNearOneClickQuoteSelectionRequest, + validatePayToRoute, + validateProviderCallbackRequest, validateProviderResponse, + validateRouteDeleteResponse, + validateRouteListResponse, validateResolveRequest, validateResolveResponse, validateRouteRegistrationRequest, + validateRouteRegistrationResponse, + validateRouteReadResponse, validateRouteQuotePreview, + validateRouteUpdateRequest, validateStatus, + validHostedActionCompletion, + validHostedActionDecision, + validHostedActionView, validMyPayTagIntent, + validNearOneClickPayableInstruction, validNoRouteResponse, validNotificationEvent, + validNearOneClickQuoteOption, + validNearOneClickQuoteSelectionRequest, + validProviderCallbackRequest, validProviderResponse, + validPayToRoute, + validRouteDeleteResponse, + validRouteListResponse, + validRouteNotFoundResponse, validResolveRequest, validResolvedResponse, validRouteRegistrationRequest, validRouteQuotePreview, + validRouteReadResponse, validRouteSelectionResponse, + validRouteUnavailableResponse, + validRouteUpdateRequest, } from "./index.js"; const terminalStatusFixtures = [ @@ -36,6 +71,32 @@ const actionStatusFixtures = [ validRouteSelectionResponse, ] as const; +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( @@ -47,10 +108,29 @@ 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(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); expect(validateRouteQuotePreview(validRouteQuotePreview)).toEqual(validRouteQuotePreview); + expect(validateNearOneClickQuoteOption(validNearOneClickQuoteOption)).toEqual( + validNearOneClickQuoteOption, + ); + expect(validateNearOneClickQuoteSelectionRequest(validNearOneClickQuoteSelectionRequest)).toEqual( + validNearOneClickQuoteSelectionRequest, + ); + expect(validateNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( + validNearOneClickPayableInstruction, + ); }); it("accepts every public status value and response shape", () => { @@ -85,6 +165,113 @@ 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("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({ @@ -125,12 +312,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) { @@ -158,6 +345,32 @@ 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 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(() => @@ -223,13 +436,99 @@ 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("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({ ...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", }, }), @@ -244,4 +543,63 @@ describe("@mypaytag/protocol", () => { }), ).toThrow(); }); + + it("validates OpenAPI examples against canonical protocol validators", () => { + const openApi = parseYaml(readFileSync( + fileURLToPath(new URL("../../../api/openapi.yaml", import.meta.url)), + "utf8", + )) as any; + + 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 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 = + 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(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(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 781a030..4883767 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -3,16 +3,27 @@ import addFormats from "ajv-formats"; import { protocolSchemas } from "./generated/schemas.js"; import type { + HostedActionCompletion, + HostedActionDecision, + HostedActionView, MyPayTagIntent, + NearOneClickPayableInstruction, + NearOneClickQuoteOption, + NearOneClickQuoteSelectionRequest, MyPayTagStatus, NotificationEvent, + PayToRoute, ProviderCallbackRequest, ProviderResponse, + RouteDeleteResponse, + RouteListResponse, RouteQuotePreview, + RouteReadResponse, ResolveRequest, ResolveResponse, RouteRegistrationRequest, RouteRegistrationResponse, + RouteUpdateRequest, } from "./generated/types.js"; export class ProtocolValidationError extends Error { @@ -59,12 +70,36 @@ 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); 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); @@ -86,5 +121,14 @@ 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 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/provider-sdk/src/index.ts b/packages/provider-sdk/src/index.ts index c2312d0..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); } @@ -87,7 +111,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 0f608ff..de99696 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(); @@ -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 e7d9e45..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, @@ -31,7 +39,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", @@ -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); @@ -67,6 +82,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 }; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 4c39777..7478b23 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,14 +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"; @@ -35,8 +44,11 @@ export interface ExactnessAwareResolveRequest { export type ActionRequiredStatus = "user_action_required"; export type ActionRequiredResponse = Extract; +/** + * 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", "lifi", "squid", "zero_x_cross_chain", @@ -46,6 +58,12 @@ export const cryptoNativeExecutionSolvers = [ export type CryptoNativeExecutionSolverId = (typeof cryptoNativeExecutionSolvers)[number]; +/** + * Phase 2 extension quote request consumed by app-configured execution adapters. + * The Phase 1 NEAR Intents / 1Click path uses the dedicated + * NearOneClickMvpQuoteOption, NearOneClickMvpQuoteSelectionRequest, and + * NearOneClickMvpPayableInstruction contracts instead of this fanout helper. + */ export interface ExecutionQuoteRequest { amount: { value: string; @@ -118,7 +136,7 @@ export function buildPayorAppResolveRequest(input: PayorAppResolveRequestInput): const request = buildResolveRequest({ recipient: { - identifierType: "verified_stamp", + identifierType: "paytag", identifier: requireNonEmpty(input.recipientIdentifier, "recipientIdentifier"), }, supportedPaths: [firstPath, ...remainingPaths], @@ -142,6 +160,22 @@ export function parseNotificationEvent(payload: unknown): NotificationEvent { return validateNotificationEvent(payload); } +export function parseNearOneClickQuoteOption(payload: unknown): NearOneClickMvpQuoteOption { + 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); } @@ -184,6 +218,12 @@ function requireNonEmpty(value: string, fieldName: string): string { return trimmed; } +/** + * Requests Phase 2 non-MVP execution quotes from caller-provided providers. + * Core MyPayTag MVP integrations can resolve paytags, parse NEAR 1Click quote + * options, confirm selected quotes, and handle provider callbacks without + * calling this helper. + */ export async function requestExecutionQuotes({ preferredSolverId, providers, diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 98d86c2..38375be 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -2,6 +2,9 @@ import { describe, expect, it } from "vitest"; import { validNoRouteResponse, + validNearOneClickPayableInstruction, + validNearOneClickQuoteOption, + validNearOneClickQuoteSelectionRequest, validNotificationEvent, validResolvedResponse, validResolveRequest, @@ -11,6 +14,7 @@ import { import { buildResolveRequest, buildAmountValue, + buildNearOneClickQuoteSelectionRequest, buildPayorAppReference, buildPayorAppResolveRequest, buildSupportedPath, @@ -21,6 +25,8 @@ import { parseNotificationEvent, parseResolveResponse, requestExecutionQuotes, + parseNearOneClickPayableInstruction, + parseNearOneClickQuoteOption, type CryptoNativeExecutionSolverId, type ExecutionQuoteProvider, } from "./index.js"; @@ -42,7 +48,7 @@ describe("@mypaytag/sdk", () => { }); const result = buildPayorAppResolveRequest({ - recipientIdentifier: "email:noak@example.com", + recipientIdentifier: "abd123@cubid.mypaytag", supportedPaths: [supportedPath], amount, purpose: "wallet_send", @@ -70,7 +76,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 +89,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, @@ -110,7 +116,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", () => { @@ -125,12 +131,27 @@ describe("@mypaytag/sdk", () => { expect(parseNotificationEvent(validNotificationEvent)).toEqual(validNotificationEvent); }); - it("requests execution quotes from every configured solver when none is preferred", async () => { + it("parses the Phase 1 NEAR 1Click MVP quote option", () => { + expect(parseNearOneClickQuoteOption(validNearOneClickQuoteOption)).toEqual( + validNearOneClickQuoteOption, + ); + }); + + it("builds selected NEAR 1Click quote requests and parses payable instructions", () => { + expect(buildNearOneClickQuoteSelectionRequest(validNearOneClickQuoteSelectionRequest)).toEqual( + validNearOneClickQuoteSelectionRequest, + ); + expect(parseNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( + validNearOneClickPayableInstruction, + ); + }); + + 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({ @@ -138,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({ @@ -160,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 }), ]; @@ -173,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 a99dfa4..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, @@ -30,11 +54,48 @@ import type { } from "@mypaytag/sdk"; export interface MyPayTagFixtures { + paytags: { + opaqueDefault: PaytagExampleFixture; + rawExplicit: PaytagExampleFixture; + availability: { + available: PaytagAvailabilityFixture; + unavailable: PaytagAvailabilityFixture; + reserved: PaytagAvailabilityFixture; + idempotentRetry: PaytagAvailabilityFixture; + revokedReuseBlocked: PaytagAvailabilityFixture; + expiredReuseBlocked: PaytagAvailabilityFixture; + }; + negativeDisclosure: { + noRoute: ResolveResponse; + authorizationRequired: ResolveResponse; + userActionRequired: ResolveResponse; + }; + }; routeRegistration: { valid: RouteRegistrationRequest; 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; @@ -63,13 +124,96 @@ export interface MyPayTagFixtures { executionQuotes: { exactSendRequest: ExecutionQuoteRequest; exactReceiveRequest: ExecutionQuoteRequest; - near: ExecutionQuote; lifi: ExecutionQuote; + squid: ExecutionQuote; }; 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" + | "idempotent_retry" + | "revoked_reuse_blocked" + | "expired_reuse_blocked"; + canIssue: boolean; + publicReason?: + | "already_taken" + | "reserved_namespace" + | "same_request_replay" + | "revoked_recently" + | "expired_recently"; +} + 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", + }, + 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, + authorizationRequired: { + status: "authorization_required", + } satisfies ResolveResponse, + userActionRequired: validRouteSelectionResponse, + }, + }, routeRegistration: { valid: validRouteRegistrationRequest, forbiddenAddress: { @@ -78,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, @@ -99,28 +263,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" }, @@ -150,13 +314,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", @@ -168,26 +325,33 @@ export const myPayTagFixtures: MyPayTagFixtures = { data: "0x", }, }, + squid: { + solverId: "squid", + quoteId: "quote_squid_fixture", + sendAmount: "25.22", + receiveAmount: "25.00", + estimatedDurationSeconds: 60, + }, }, 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, }; }, @@ -278,7 +442,7 @@ function buildQuotePreview(input: Omit { 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.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", + }); + 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", ); 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"); @@ -40,11 +78,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, }); }); @@ -65,11 +103,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({ @@ -87,10 +125,10 @@ describe("@mypaytag/testing", () => { request: myPayTagFixtures.executionQuotes.exactSendRequest, })).resolves.toMatchObject([ { - solverId: "near_intents_1click", + solverId: "lifi", }, { - solverId: "lifi", + solverId: "across", }, ]); }); @@ -99,7 +137,7 @@ describe("@mypaytag/testing", () => { await expect(requestExecutionQuotes({ providers: [ createMockExecutionQuoteProvider({ - solverId: "near_intents_1click", + solverId: "zero_x_cross_chain", failWith: "provider_unavailable", }), createMockExecutionQuoteProvider({ 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: 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; }