diff --git a/agent-context/session-log/main.md b/agent-context/session-log/main.md index 1cb4bee..f00de4d 100644 --- a/agent-context/session-log/main.md +++ b/agent-context/session-log/main.md @@ -1,5 +1,23 @@ # Main Session Log +agent: Codex +branch: codex/5-user-portal-sdk +head: pending commit for MyPayTag-sdk#6 +summary: Refreshed generated SDK API artifacts for MyPayTag-sdk#6 after the authenticated portal contract additions, including the generated Postman collection for the new portal preference and history endpoints. +validation: Ran pnpm api:postman and pnpm validate; validation regenerated protocol types, linted OpenAPI, built and typechecked all packages, passed 64 workspace tests per package test run, passed the public-boundary scan, cleaned, and rebuilt package dist outputs. +follow-ups: SDK Goal #4 can be validated after this generated-artifact task is independently reviewed. + +--- + +agent: Codex +branch: codex/5-user-portal-sdk +head: 1adad59 +summary: Implemented MyPayTag-sdk#5 by adding authenticated portal preference and history contracts across protocol schemas, validators, fixtures, focused tests, generated protocol source, and OpenAPI paths/components. +validation: Ran pnpm generate, pnpm build, pnpm api:validate, pnpm test, and pnpm scan:public-boundary; full workspace tests passed after building package entries in the fresh worktree. +follow-ups: MyPayTag-sdk#6 should refresh distribution/package artifacts and final SDK validation evidence from these source contracts. + +--- + agent: Codex branch: main head: b6b5f14 diff --git a/api/openapi.yaml b/api/openapi.yaml index 92d8215..ed87a9e 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -35,6 +35,8 @@ tags: description: Browser-safe action hydration and completion. - name: Notifications description: Cubid comms notification event payloads. + - name: Portal + description: Authenticated user portal preferences and history. security: - MyPayTagSignature: [] paths: @@ -409,6 +411,227 @@ paths: $ref: "#/components/responses/BadRequest" "404": $ref: "#/components/responses/NotFound" + /portal/preferences: + get: + tags: + - Portal + operationId: getPortalPreferences + summary: List authenticated user receive-route preferences. + security: + - CubidUserBearer: [] + responses: + "200": + description: User-scoped receive-route preferences. + content: + application/json: + schema: + $ref: "#/components/schemas/PortalPreferencesResponse" + examples: + preferences: + value: + schema: mypaytag.portal.preferences.v1 + user: + maskedDisplay: a***@cubid.mypaytag + groups: + - paytagRef: paytag_ref_abc + maskedDisplay: a***@cubid.mypaytag + chain: base + network: mainnet + asset: USDC + payingDapp: + id: chaincrew + displayName: ChainCrew + routes: + - routeId: mpt_route_123 + payToDappId: smartrust-wallet + payToDappName: SmarTrust Wallet + appUrl: https://wallet.smartrust.example + priority: 0 + isDefault: true + state: active + "401": + $ref: "#/components/responses/Unauthorized" + put: + tags: + - Portal + operationId: updatePortalPreferences + summary: Reorder authenticated user receive-route preferences. + security: + - CubidUserBearer: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PortalPreferencesUpdateRequest" + examples: + reorder: + value: + schema: mypaytag.portal.preferences.update.v1 + group: + paytagRef: paytag_ref_abc + chain: base + network: mainnet + asset: USDC + payingDappId: chaincrew + orderedRouteIds: + - mpt_route_456 + - mpt_route_123 + responses: + "200": + description: Updated user-scoped preferences. + content: + application/json: + schema: + $ref: "#/components/schemas/PortalPreferencesResponse" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + /portal/history: + get: + tags: + - Portal + operationId: getPortalHistory + summary: List authenticated user questions, answers, quotes, intents, and receipts. + security: + - CubidUserBearer: [] + parameters: + - name: type + in: query + required: false + schema: + type: string + enum: + - questions + - answers + - quotes + - intents + - receipts + - all + - name: groupBy + in: query + required: false + schema: + type: string + enum: + - paying_dapp + - payto_dapp + - token + - chain + - name: cursor + in: query + required: false + schema: + type: string + - name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + responses: + "200": + description: User-scoped history. + content: + application/json: + schema: + $ref: "#/components/schemas/PortalHistoryResponse" + examples: + history: + value: + schema: mypaytag.portal.history.v1 + user: + maskedDisplay: a***@cubid.mypaytag + items: + - kind: question + id: mpt_req_123 + createdAt: "2026-06-24T19:58:00Z" + payingDapp: + id: chaincrew + displayName: ChainCrew + appUrl: https://chaincrew.example + paytagMaskedDisplay: a***@cubid.mypaytag + requestedPaths: + - chain: base + network: mainnet + asset: USDC + amount: + value: "25.00" + currency: USDC + purpose: payout + payingDappReference: chaincrew:payout_987 + - kind: answer + id: mpt_answer_123 + questionId: mpt_req_123 + createdAt: "2026-06-24T19:58:02Z" + status: resolved + selectedPayToDapp: + id: smartrust-wallet + displayName: SmarTrust Wallet + - kind: quote + id: near_1click_quote_123 + questionId: mpt_req_123 + createdAt: "2026-06-24T19:58:03Z" + provider: near_1click + from: + chain: near + network: mainnet + asset: USDC + amount: "25.18" + to: + chain: base + network: mainnet + asset: USDC + amount: "25.00" + fees: + - label: NEAR 1Click execution + amount: "0.16" + asset: USDC + chargedTo: sender + source: near_1click + expiresAt: "2026-06-24T20:00:00Z" + routeSteps: + - label: NEAR USDC to Base USDC + from: + chain: near + network: mainnet + asset: USDC + amount: "25.18" + to: + chain: base + network: mainnet + asset: USDC + amount: "25.00" + status: selected + selectedQuote: + selectedQuoteId: near_1click_quote_123 + selectedAt: "2026-06-24T19:58:10Z" + payableInstructionRef: near_payable_123 + - kind: payment_intent + id: mpt_pi_123 + questionId: mpt_req_123 + createdAt: "2026-06-24T19:58:11Z" + status: ready + payToDapp: + id: smartrust-wallet + displayName: SmarTrust Wallet + amount: + chain: base + network: mainnet + asset: USDC + amount: "25.00" + expiresAt: "2026-06-24T20:00:00Z" + - kind: receipt + id: mpt_receipt_123 + paymentIntentId: mpt_pi_123 + createdAt: "2026-06-24T20:01:00Z" + status: pending + chain: base + network: mainnet + "401": + $ref: "#/components/responses/Unauthorized" /notifications: post: tags: @@ -560,6 +783,11 @@ components: x-mypaytag-timestamp, x-mypaytag-nonce, and x-mypaytag-signature. The signature binds app identity, timestamp, nonce, method, path, and request body. + CubidUserBearer: + type: http + scheme: bearer + bearerFormat: Cubid SIWC/OIDC proof + description: Authenticated Cubid/SIWC user proof for protected portal APIs. parameters: RouteId: name: routeId @@ -1092,6 +1320,381 @@ components: enum: - no_route - provider_unavailable + PortalDappSummary: + type: object + additionalProperties: false + required: + - id + - displayName + properties: + id: + type: string + minLength: 1 + displayName: + type: string + minLength: 1 + appUrl: + type: string + format: uri + PortalAssetAmount: + 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]+)?$" + PortalPreferencesResponse: + type: object + additionalProperties: false + required: + - schema + - user + - groups + properties: + schema: + type: string + const: mypaytag.portal.preferences.v1 + user: + type: object + additionalProperties: false + properties: + maskedDisplay: + type: string + groups: + type: array + items: + type: object + additionalProperties: false + required: + - paytagRef + - chain + - network + - asset + - routes + properties: + paytagRef: + type: string + maskedDisplay: + type: string + chain: + type: string + network: + type: string + asset: + type: string + payingDapp: + $ref: "#/components/schemas/PortalDappSummary" + routes: + type: array + items: + type: object + additionalProperties: false + required: + - routeId + - payToDappId + - payToDappName + - priority + - isDefault + - state + properties: + routeId: + type: string + payToDappId: + type: string + payToDappName: + type: string + appUrl: + type: string + format: uri + priority: + type: integer + minimum: 0 + isDefault: + type: boolean + state: + type: string + enum: + - active + - disabled + - revoked + PortalPreferencesUpdateRequest: + type: object + additionalProperties: false + required: + - schema + - group + - orderedRouteIds + properties: + schema: + type: string + const: mypaytag.portal.preferences.update.v1 + group: + type: object + additionalProperties: false + required: + - paytagRef + - chain + - network + - asset + properties: + paytagRef: + type: string + chain: + type: string + network: + type: string + asset: + type: string + payingDappId: + type: string + orderedRouteIds: + type: array + minItems: 1 + items: + type: string + PortalHistoryResponse: + type: object + additionalProperties: false + required: + - schema + - user + - items + properties: + schema: + type: string + const: mypaytag.portal.history.v1 + user: + type: object + additionalProperties: false + properties: + maskedDisplay: + type: string + items: + type: array + items: + oneOf: + - $ref: "#/components/schemas/PortalQuestionSummary" + - $ref: "#/components/schemas/PortalAnswerSummary" + - $ref: "#/components/schemas/PortalQuoteSummary" + - $ref: "#/components/schemas/PortalPaymentIntentSummary" + - $ref: "#/components/schemas/PortalReceiptSummary" + nextCursor: + type: string + PortalQuestionSummary: + type: object + additionalProperties: false + required: + - kind + - id + - createdAt + - payingDapp + - requestedPaths + - amount + - purpose + - payingDappReference + properties: + kind: + type: string + const: question + id: + type: string + createdAt: + type: string + format: date-time + payingDapp: + $ref: "#/components/schemas/PortalDappSummary" + paytagMaskedDisplay: + type: string + requestedPaths: + type: array + minItems: 1 + items: + $ref: "#/components/schemas/RoutePath" + amount: + $ref: "#/components/schemas/Amount" + purpose: + type: string + payingDappReference: + type: string + PortalAnswerSummary: + type: object + additionalProperties: false + required: + - kind + - id + - questionId + - createdAt + - status + properties: + kind: + type: string + const: answer + id: + type: string + questionId: + type: string + createdAt: + type: string + format: date-time + status: + type: string + enum: + - resolved + - no_route + - user_action_required + - authorization_required + - provider_unavailable + - provider_error + - invalid_identifier + - invalid_request + selectedPayToDapp: + $ref: "#/components/schemas/PortalDappSummary" + actionState: + type: string + enum: + - ready + - selected_route + - expired + - completed + PortalQuoteSummary: + type: object + additionalProperties: false + required: + - kind + - id + - questionId + - createdAt + - provider + - from + - to + - fees + - expiresAt + - routeSteps + - status + properties: + kind: + type: string + const: quote + id: + type: string + questionId: + type: string + createdAt: + type: string + format: date-time + provider: + type: string + from: + $ref: "#/components/schemas/PortalAssetAmount" + to: + $ref: "#/components/schemas/PortalAssetAmount" + fees: + type: array + items: + type: object + expiresAt: + type: string + format: date-time + routeSteps: + type: array + items: + type: object + status: + type: string + enum: + - available + - selected + - expired + - failed + selectedQuote: + type: object + PortalPaymentIntentSummary: + type: object + additionalProperties: false + required: + - kind + - id + - questionId + - createdAt + - status + - payToDapp + - amount + - expiresAt + properties: + kind: + type: string + const: payment_intent + id: + type: string + questionId: + type: string + createdAt: + type: string + format: date-time + status: + type: string + enum: + - ready + - expired + - used + - failed + payToDapp: + $ref: "#/components/schemas/PortalDappSummary" + amount: + $ref: "#/components/schemas/PortalAssetAmount" + expiresAt: + type: string + format: date-time + PortalReceiptSummary: + type: object + additionalProperties: false + required: + - kind + - id + - paymentIntentId + - createdAt + - status + properties: + kind: + type: string + const: receipt + id: + type: string + paymentIntentId: + type: string + createdAt: + type: string + format: date-time + status: + type: string + enum: + - pending + - completed + - failed + - unavailable + transactionHash: + type: string + chain: + type: string + network: + type: string + explorerUrl: + type: string + format: uri HostedActionView: type: object additionalProperties: false diff --git a/api/postman_collection.json b/api/postman_collection.json index 906d48b..1ce089c 100644 --- a/api/postman_collection.json +++ b/api/postman_collection.json @@ -912,7 +912,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\"\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -969,7 +969,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\"\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1036,7 +1036,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\"\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1103,7 +1103,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\"\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1170,7 +1170,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"state\": \"\"\n}", + "raw": "{\n \"state\": \"\"\n}", "options": { "raw": { "headerFamily": "json", @@ -1886,6 +1886,611 @@ } ] }, + { + "name": "portal", + "description": "", + "item": [ + { + "name": "preferences", + "description": "", + "item": [ + { + "name": "List authenticated user receive-route preferences.", + "request": { + "name": "List authenticated user receive-route preferences.", + "description": {}, + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {}, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}" + } + ] + } + }, + "response": [ + { + "name": "User-scoped receive-route preferences.", + "originalRequest": { + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"schema\": \"\",\n \"user\": {\n \"maskedDisplay\": \"\"\n },\n \"groups\": [\n {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"routes\": [\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n },\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n }\n ],\n \"maskedDisplay\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n }\n },\n {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"routes\": [\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n },\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n }\n ],\n \"maskedDisplay\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n }\n }\n ]\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid app authentication signature.", + "originalRequest": { + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "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": "Reorder authenticated user receive-route preferences.", + "request": { + "name": "Reorder authenticated user receive-route preferences.", + "description": {}, + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "PUT", + "body": { + "mode": "raw", + "raw": "{\n \"schema\": \"\",\n \"group\": {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payingDappId\": \"\"\n },\n \"orderedRouteIds\": [\n \"\"\n ]\n}", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + } + }, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}" + } + ] + } + }, + "response": [ + { + "name": "Updated user-scoped preferences.", + "originalRequest": { + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "method": "PUT", + "body": { + "mode": "raw", + "raw": "{\n \"schema\": \"\",\n \"group\": {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payingDappId\": \"\"\n },\n \"orderedRouteIds\": [\n \"\"\n ]\n}", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + } + } + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"schema\": \"\",\n \"user\": {\n \"maskedDisplay\": \"\"\n },\n \"groups\": [\n {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"routes\": [\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n },\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n }\n ],\n \"maskedDisplay\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n }\n },\n {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"routes\": [\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n },\n {\n \"routeId\": \"\",\n \"payToDappId\": \"\",\n \"payToDappName\": \"\",\n \"priority\": \"\",\n \"isDefault\": \"\",\n \"state\": \"\",\n \"appUrl\": \"\"\n }\n ],\n \"maskedDisplay\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n }\n }\n ]\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Invalid public request shape or unsupported request.", + "originalRequest": { + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "method": "PUT", + "body": { + "mode": "raw", + "raw": "{\n \"schema\": \"\",\n \"group\": {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payingDappId\": \"\"\n },\n \"orderedRouteIds\": [\n \"\"\n ]\n}", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + } + } + }, + "status": "Bad Request", + "code": 400, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"status\": \"\",\n \"message\": \"\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid app authentication signature.", + "originalRequest": { + "url": { + "path": [ + "portal", + "preferences" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "method": "PUT", + "body": { + "mode": "raw", + "raw": "{\n \"schema\": \"\",\n \"group\": {\n \"paytagRef\": \"\",\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\",\n \"payingDappId\": \"\"\n },\n \"orderedRouteIds\": [\n \"\"\n ]\n}", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + } + } + }, + "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": "history", + "description": "", + "item": [ + { + "name": "List authenticated user questions, answers, quotes, intents, and receipts.", + "request": { + "name": "List authenticated user questions, answers, quotes, intents, and receipts.", + "description": {}, + "url": { + "path": [ + "portal", + "history" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "description": { + "content": " (This can only be one of questions,answers,quotes,intents,receipts,all)", + "type": "text/plain" + }, + "key": "type", + "value": "intents" + }, + { + "disabled": false, + "description": { + "content": " (This can only be one of paying_dapp,payto_dapp,token,chain)", + "type": "text/plain" + }, + "key": "groupBy", + "value": "paying_dapp" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "cursor", + "value": "" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "limit", + "value": "" + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "body": {}, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}" + } + ] + } + }, + "response": [ + { + "name": "User-scoped history.", + "originalRequest": { + "url": { + "path": [ + "portal", + "history" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "description": { + "content": " (This can only be one of questions,answers,quotes,intents,receipts,all)", + "type": "text/plain" + }, + "key": "type", + "value": "intents" + }, + { + "disabled": false, + "description": { + "content": " (This can only be one of paying_dapp,payto_dapp,token,chain)", + "type": "text/plain" + }, + "key": "groupBy", + "value": "paying_dapp" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "cursor", + "value": "" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "limit", + "value": "" + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "method": "GET", + "body": {} + }, + "status": "OK", + "code": 200, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{\n \"schema\": \"\",\n \"user\": {\n \"maskedDisplay\": \"\"\n },\n \"items\": [\n {\n \"kind\": \"\",\n \"id\": \"\",\n \"createdAt\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n },\n \"requestedPaths\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"purpose\": \"\",\n \"payingDappReference\": \"\",\n \"paytagMaskedDisplay\": \"\"\n },\n {\n \"kind\": \"\",\n \"id\": \"\",\n \"createdAt\": \"\",\n \"payingDapp\": {\n \"id\": \"\",\n \"displayName\": \"\",\n \"appUrl\": \"\"\n },\n \"requestedPaths\": [\n {\n \"chain\": \"\",\n \"network\": \"\",\n \"asset\": \"\"\n }\n ],\n \"amount\": {\n \"value\": \"\",\n \"currency\": \"\"\n },\n \"purpose\": \"\",\n \"payingDappReference\": \"\",\n \"paytagMaskedDisplay\": \"\"\n }\n ],\n \"nextCursor\": \"\"\n}", + "cookie": [], + "_postman_previewlanguage": "json" + }, + { + "name": "Missing or invalid app authentication signature.", + "originalRequest": { + "url": { + "path": [ + "portal", + "history" + ], + "host": [ + "{{baseUrl}}" + ], + "query": [ + { + "disabled": false, + "description": { + "content": " (This can only be one of questions,answers,quotes,intents,receipts,all)", + "type": "text/plain" + }, + "key": "type", + "value": "intents" + }, + { + "disabled": false, + "description": { + "content": " (This can only be one of paying_dapp,payto_dapp,token,chain)", + "type": "text/plain" + }, + "key": "groupBy", + "value": "paying_dapp" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "cursor", + "value": "" + }, + { + "disabled": false, + "description": { + "content": "", + "type": "text/plain" + }, + "key": "limit", + "value": "" + } + ], + "variable": [] + }, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: bearer", + "type": "text/plain" + }, + "key": "Authorization", + "value": "Bearer " + } + ], + "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": "notifications", "description": "", diff --git a/packages/protocol/schemas/portal-history-response.schema.json b/packages/protocol/schemas/portal-history-response.schema.json new file mode 100644 index 0000000..63caeca --- /dev/null +++ b/packages/protocol/schemas/portal-history-response.schema.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-history-response.schema.json", + "title": "PortalHistoryResponse", + "type": "object", + "additionalProperties": false, + "required": ["schema", "user", "items"], + "properties": { + "schema": { "const": "mypaytag.portal.history.v1" }, + "user": { "$ref": "#/$defs/portalUser" }, + "items": { + "type": "array", + "items": { "$ref": "#/$defs/historyItem" } + }, + "nextCursor": { "type": "string", "minLength": 1 } + }, + "$defs": { + "portalUser": { + "type": "object", + "additionalProperties": false, + "properties": { + "maskedDisplay": { "type": "string", "minLength": 1 } + } + }, + "dappSummary": { + "type": "object", + "additionalProperties": false, + "required": ["id", "displayName"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "displayName": { "type": "string", "minLength": 1 }, + "appUrl": { "type": "string", "format": "uri" } + } + }, + "assetAmount": { + "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": { "enum": ["sender", "recipient"] }, + "source": { "type": "string", "minLength": 1 } + } + }, + "routeStep": { + "type": "object", + "additionalProperties": false, + "required": ["label", "from", "to"], + "properties": { + "label": { "type": "string", "minLength": 1 }, + "from": { "$ref": "#/$defs/assetAmount" }, + "to": { "$ref": "#/$defs/assetAmount" } + } + }, + "requestedPath": { + "type": "object", + "additionalProperties": false, + "required": ["chain", "network", "asset"], + "properties": { + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 } + } + }, + "question": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "createdAt", "payingDapp", "requestedPaths", "amount", "purpose", "payingDappReference"], + "properties": { + "kind": { "const": "question" }, + "id": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "payingDapp": { "$ref": "#/$defs/dappSummary" }, + "paytagMaskedDisplay": { "type": "string", "minLength": 1 }, + "requestedPaths": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/requestedPath" } + }, + "amount": { + "type": "object", + "additionalProperties": false, + "required": ["value", "currency"], + "properties": { + "value": { "type": "string", "pattern": "^[0-9]+(\\.[0-9]+)?$" }, + "currency": { "type": "string", "minLength": 1 } + } + }, + "purpose": { "type": "string", "minLength": 1 }, + "payingDappReference": { "type": "string", "minLength": 1 } + } + }, + "answer": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "questionId", "createdAt", "status"], + "properties": { + "kind": { "const": "answer" }, + "id": { "type": "string", "minLength": 1 }, + "questionId": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "status": { + "enum": [ + "resolved", + "no_route", + "user_action_required", + "authorization_required", + "provider_unavailable", + "provider_error", + "invalid_identifier", + "invalid_request" + ] + }, + "selectedPayToDapp": { "$ref": "#/$defs/dappSummary" }, + "actionState": { "enum": ["ready", "selected_route", "expired", "completed"] } + } + }, + "quote": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "questionId", "createdAt", "provider", "from", "to", "fees", "expiresAt", "routeSteps", "status"], + "properties": { + "kind": { "const": "quote" }, + "id": { "type": "string", "minLength": 1 }, + "questionId": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "provider": { "type": "string", "minLength": 1 }, + "from": { "$ref": "#/$defs/assetAmount" }, + "to": { "$ref": "#/$defs/assetAmount" }, + "fees": { + "type": "array", + "items": { "$ref": "#/$defs/fee" } + }, + "expiresAt": { "type": "string", "format": "date-time" }, + "routeSteps": { + "type": "array", + "items": { "$ref": "#/$defs/routeStep" } + }, + "status": { "enum": ["available", "selected", "expired", "failed"] }, + "selectedQuote": { + "type": "object", + "additionalProperties": false, + "required": ["selectedQuoteId", "selectedAt"], + "properties": { + "selectedQuoteId": { "type": "string", "minLength": 1 }, + "selectedAt": { "type": "string", "format": "date-time" }, + "payableInstructionRef": { "type": "string", "minLength": 1 } + } + } + } + }, + "paymentIntent": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "questionId", "createdAt", "status", "payToDapp", "amount", "expiresAt"], + "properties": { + "kind": { "const": "payment_intent" }, + "id": { "type": "string", "minLength": 1 }, + "questionId": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "status": { "enum": ["ready", "expired", "used", "failed"] }, + "payToDapp": { "$ref": "#/$defs/dappSummary" }, + "amount": { "$ref": "#/$defs/assetAmount" }, + "expiresAt": { "type": "string", "format": "date-time" } + } + }, + "receipt": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "paymentIntentId", "createdAt", "status"], + "properties": { + "kind": { "const": "receipt" }, + "id": { "type": "string", "minLength": 1 }, + "paymentIntentId": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "status": { "enum": ["pending", "completed", "failed", "unavailable"] }, + "transactionHash": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "explorerUrl": { "type": "string", "format": "uri" } + } + }, + "historyItem": { + "oneOf": [ + { "$ref": "#/$defs/question" }, + { "$ref": "#/$defs/answer" }, + { "$ref": "#/$defs/quote" }, + { "$ref": "#/$defs/paymentIntent" }, + { "$ref": "#/$defs/receipt" } + ] + } + } +} diff --git a/packages/protocol/schemas/portal-preferences-response.schema.json b/packages/protocol/schemas/portal-preferences-response.schema.json new file mode 100644 index 0000000..aad9275 --- /dev/null +++ b/packages/protocol/schemas/portal-preferences-response.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-preferences-response.schema.json", + "title": "PortalPreferencesResponse", + "type": "object", + "additionalProperties": false, + "required": ["schema", "user", "groups"], + "properties": { + "schema": { "const": "mypaytag.portal.preferences.v1" }, + "user": { "$ref": "#/$defs/portalUser" }, + "groups": { + "type": "array", + "items": { "$ref": "#/$defs/preferenceGroup" } + } + }, + "$defs": { + "portalUser": { + "type": "object", + "additionalProperties": false, + "properties": { + "maskedDisplay": { "type": "string", "minLength": 1 } + } + }, + "dappSummary": { + "type": "object", + "additionalProperties": false, + "required": ["id", "displayName"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "displayName": { "type": "string", "minLength": 1 } + } + }, + "preferenceGroup": { + "type": "object", + "additionalProperties": false, + "required": ["paytagRef", "chain", "network", "asset", "routes"], + "properties": { + "paytagRef": { "type": "string", "minLength": 1 }, + "maskedDisplay": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "payingDapp": { "$ref": "#/$defs/dappSummary" }, + "routes": { + "type": "array", + "items": { "$ref": "#/$defs/receiveRoute" } + } + } + }, + "receiveRoute": { + "type": "object", + "additionalProperties": false, + "required": ["routeId", "payToDappId", "payToDappName", "priority", "isDefault", "state"], + "properties": { + "routeId": { "type": "string", "minLength": 1 }, + "payToDappId": { "type": "string", "minLength": 1 }, + "payToDappName": { "type": "string", "minLength": 1 }, + "appUrl": { "type": "string", "format": "uri" }, + "priority": { "type": "integer", "minimum": 0 }, + "isDefault": { "type": "boolean" }, + "state": { "enum": ["active", "disabled", "revoked"] } + } + } + } +} diff --git a/packages/protocol/schemas/portal-preferences-update-request.schema.json b/packages/protocol/schemas/portal-preferences-update-request.schema.json new file mode 100644 index 0000000..d7d7e42 --- /dev/null +++ b/packages/protocol/schemas/portal-preferences-update-request.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-preferences-update-request.schema.json", + "title": "PortalPreferencesUpdateRequest", + "type": "object", + "additionalProperties": false, + "required": ["schema", "group", "orderedRouteIds"], + "properties": { + "schema": { "const": "mypaytag.portal.preferences.update.v1" }, + "group": { + "type": "object", + "additionalProperties": false, + "required": ["paytagRef", "chain", "network", "asset"], + "properties": { + "paytagRef": { "type": "string", "minLength": 1 }, + "chain": { "type": "string", "minLength": 1 }, + "network": { "type": "string", "minLength": 1 }, + "asset": { "type": "string", "minLength": 1 }, + "payingDappId": { "type": "string", "minLength": 1 } + } + }, + "orderedRouteIds": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + } + } +} diff --git a/packages/protocol/src/fixtures.ts b/packages/protocol/src/fixtures.ts index 08fbc7c..781521a 100644 --- a/packages/protocol/src/fixtures.ts +++ b/packages/protocol/src/fixtures.ts @@ -8,6 +8,9 @@ import type { NearOneClickQuoteSelectionRequest, NotificationEvent, PayToRoute, + PortalHistoryResponse, + PortalPreferencesResponse, + PortalPreferencesUpdateRequest, ProviderCallbackRequest, ProviderResponse, RouteDeleteResponse, @@ -87,6 +90,181 @@ export const validRouteUnavailableResponse: RouteDeleteResponse = { status: "provider_unavailable", }; +export const validPortalPreferencesResponse: PortalPreferencesResponse = { + schema: "mypaytag.portal.preferences.v1", + user: { + maskedDisplay: "a***@cubid.mypaytag", + }, + groups: [ + { + paytagRef: "paytag_ref_abc", + maskedDisplay: "a***@cubid.mypaytag", + chain: "base", + network: "mainnet", + asset: "USDC", + payingDapp: { + id: "chaincrew", + displayName: "ChainCrew", + }, + routes: [ + { + routeId: "mpt_route_123", + payToDappId: "smartrust-wallet", + payToDappName: "SmarTrust Wallet", + appUrl: "https://wallet.smartrust.example", + priority: 0, + isDefault: true, + state: "active", + }, + { + routeId: "mpt_route_456", + payToDappId: "vaultpay-wallet", + payToDappName: "VaultPay Wallet", + appUrl: "https://vaultpay.example", + priority: 1, + isDefault: false, + state: "active", + }, + ], + }, + ], +}; + +export const validPortalPreferencesUpdateRequest: PortalPreferencesUpdateRequest = { + schema: "mypaytag.portal.preferences.update.v1", + group: { + paytagRef: "paytag_ref_abc", + chain: "base", + network: "mainnet", + asset: "USDC", + payingDappId: "chaincrew", + }, + orderedRouteIds: ["mpt_route_456", "mpt_route_123"], +}; + +export const validPortalHistoryResponse: PortalHistoryResponse = { + schema: "mypaytag.portal.history.v1", + user: { + maskedDisplay: "a***@cubid.mypaytag", + }, + items: [ + { + kind: "question", + id: "mpt_req_123", + createdAt: "2026-06-24T19:58:00Z", + payingDapp: { + id: "chaincrew", + displayName: "ChainCrew", + appUrl: "https://chaincrew.example", + }, + paytagMaskedDisplay: "a***@cubid.mypaytag", + requestedPaths: [ + { + chain: "base", + network: "mainnet", + asset: "USDC", + }, + ], + amount: { + value: "25.00", + currency: "USDC", + }, + purpose: "payout", + payingDappReference: "chaincrew:payout_987", + }, + { + kind: "answer", + id: "mpt_answer_123", + questionId: "mpt_req_123", + createdAt: "2026-06-24T19:58:02Z", + status: "resolved", + selectedPayToDapp: { + id: "smartrust-wallet", + displayName: "SmarTrust Wallet", + }, + }, + { + kind: "quote", + id: "near_1click_quote_123", + questionId: "mpt_req_123", + createdAt: "2026-06-24T19:58:03Z", + provider: "near_1click", + from: { + chain: "near", + network: "mainnet", + asset: "USDC", + amount: "25.18", + }, + to: { + chain: "base", + network: "mainnet", + asset: "USDC", + amount: "25.00", + }, + fees: [ + { + label: "NEAR 1Click execution", + amount: "0.16", + asset: "USDC", + chargedTo: "sender", + source: "near_1click", + }, + ], + expiresAt: "2026-06-24T20:00:00Z", + routeSteps: [ + { + label: "NEAR USDC to Base USDC", + from: { + chain: "near", + network: "mainnet", + asset: "USDC", + amount: "25.18", + }, + to: { + chain: "base", + network: "mainnet", + asset: "USDC", + amount: "25.00", + }, + }, + ], + status: "selected", + selectedQuote: { + selectedQuoteId: "near_1click_quote_123", + selectedAt: "2026-06-24T19:58:10Z", + payableInstructionRef: "near_payable_123", + }, + }, + { + kind: "payment_intent", + id: "mpt_pi_123", + questionId: "mpt_req_123", + createdAt: "2026-06-24T19:58:11Z", + status: "ready", + payToDapp: { + id: "smartrust-wallet", + displayName: "SmarTrust Wallet", + }, + amount: { + chain: "base", + network: "mainnet", + asset: "USDC", + amount: "25.00", + }, + expiresAt: "2026-06-24T20:00:00Z", + }, + { + kind: "receipt", + id: "mpt_receipt_123", + paymentIntentId: "mpt_pi_123", + createdAt: "2026-06-24T20:01:00Z", + status: "pending", + chain: "base", + network: "mainnet", + }, + ], +}; + export const validHostedActionView: HostedActionView = { status: "ready", actionId: "mpt_act_789", diff --git a/packages/protocol/src/generated/schemas.ts b/packages/protocol/src/generated/schemas.ts index 206055a..f405b7e 100644 --- a/packages/protocol/src/generated/schemas.ts +++ b/packages/protocol/src/generated/schemas.ts @@ -1057,6 +1057,707 @@ export const PayToRouteSchema = { } } as const; +export const PortalHistoryResponseSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-history-response.schema.json", + "title": "PortalHistoryResponse", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "user", + "items" + ], + "properties": { + "schema": { + "const": "mypaytag.portal.history.v1" + }, + "user": { + "$ref": "#/$defs/portalUser" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/historyItem" + } + }, + "nextCursor": { + "type": "string", + "minLength": 1 + } + }, + "$defs": { + "portalUser": { + "type": "object", + "additionalProperties": false, + "properties": { + "maskedDisplay": { + "type": "string", + "minLength": 1 + } + } + }, + "dappSummary": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "displayName" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "displayName": { + "type": "string", + "minLength": 1 + }, + "appUrl": { + "type": "string", + "format": "uri" + } + } + }, + "assetAmount": { + "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": { + "enum": [ + "sender", + "recipient" + ] + }, + "source": { + "type": "string", + "minLength": 1 + } + } + }, + "routeStep": { + "type": "object", + "additionalProperties": false, + "required": [ + "label", + "from", + "to" + ], + "properties": { + "label": { + "type": "string", + "minLength": 1 + }, + "from": { + "$ref": "#/$defs/assetAmount" + }, + "to": { + "$ref": "#/$defs/assetAmount" + } + } + }, + "requestedPath": { + "type": "object", + "additionalProperties": false, + "required": [ + "chain", + "network", + "asset" + ], + "properties": { + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + } + } + }, + "question": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "id", + "createdAt", + "payingDapp", + "requestedPaths", + "amount", + "purpose", + "payingDappReference" + ], + "properties": { + "kind": { + "const": "question" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "payingDapp": { + "$ref": "#/$defs/dappSummary" + }, + "paytagMaskedDisplay": { + "type": "string", + "minLength": 1 + }, + "requestedPaths": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/requestedPath" + } + }, + "amount": { + "type": "object", + "additionalProperties": false, + "required": [ + "value", + "currency" + ], + "properties": { + "value": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)?$" + }, + "currency": { + "type": "string", + "minLength": 1 + } + } + }, + "purpose": { + "type": "string", + "minLength": 1 + }, + "payingDappReference": { + "type": "string", + "minLength": 1 + } + } + }, + "answer": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "id", + "questionId", + "createdAt", + "status" + ], + "properties": { + "kind": { + "const": "answer" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "questionId": { + "type": "string", + "minLength": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "enum": [ + "resolved", + "no_route", + "user_action_required", + "authorization_required", + "provider_unavailable", + "provider_error", + "invalid_identifier", + "invalid_request" + ] + }, + "selectedPayToDapp": { + "$ref": "#/$defs/dappSummary" + }, + "actionState": { + "enum": [ + "ready", + "selected_route", + "expired", + "completed" + ] + } + } + }, + "quote": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "id", + "questionId", + "createdAt", + "provider", + "from", + "to", + "fees", + "expiresAt", + "routeSteps", + "status" + ], + "properties": { + "kind": { + "const": "quote" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "questionId": { + "type": "string", + "minLength": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "provider": { + "type": "string", + "minLength": 1 + }, + "from": { + "$ref": "#/$defs/assetAmount" + }, + "to": { + "$ref": "#/$defs/assetAmount" + }, + "fees": { + "type": "array", + "items": { + "$ref": "#/$defs/fee" + } + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "routeSteps": { + "type": "array", + "items": { + "$ref": "#/$defs/routeStep" + } + }, + "status": { + "enum": [ + "available", + "selected", + "expired", + "failed" + ] + }, + "selectedQuote": { + "type": "object", + "additionalProperties": false, + "required": [ + "selectedQuoteId", + "selectedAt" + ], + "properties": { + "selectedQuoteId": { + "type": "string", + "minLength": 1 + }, + "selectedAt": { + "type": "string", + "format": "date-time" + }, + "payableInstructionRef": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "paymentIntent": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "id", + "questionId", + "createdAt", + "status", + "payToDapp", + "amount", + "expiresAt" + ], + "properties": { + "kind": { + "const": "payment_intent" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "questionId": { + "type": "string", + "minLength": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "enum": [ + "ready", + "expired", + "used", + "failed" + ] + }, + "payToDapp": { + "$ref": "#/$defs/dappSummary" + }, + "amount": { + "$ref": "#/$defs/assetAmount" + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + } + }, + "receipt": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "id", + "paymentIntentId", + "createdAt", + "status" + ], + "properties": { + "kind": { + "const": "receipt" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "paymentIntentId": { + "type": "string", + "minLength": 1 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "enum": [ + "pending", + "completed", + "failed", + "unavailable" + ] + }, + "transactionHash": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "explorerUrl": { + "type": "string", + "format": "uri" + } + } + }, + "historyItem": { + "oneOf": [ + { + "$ref": "#/$defs/question" + }, + { + "$ref": "#/$defs/answer" + }, + { + "$ref": "#/$defs/quote" + }, + { + "$ref": "#/$defs/paymentIntent" + }, + { + "$ref": "#/$defs/receipt" + } + ] + } + } +} as const; + +export const PortalPreferencesResponseSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-preferences-response.schema.json", + "title": "PortalPreferencesResponse", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "user", + "groups" + ], + "properties": { + "schema": { + "const": "mypaytag.portal.preferences.v1" + }, + "user": { + "$ref": "#/$defs/portalUser" + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/$defs/preferenceGroup" + } + } + }, + "$defs": { + "portalUser": { + "type": "object", + "additionalProperties": false, + "properties": { + "maskedDisplay": { + "type": "string", + "minLength": 1 + } + } + }, + "dappSummary": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "displayName" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "displayName": { + "type": "string", + "minLength": 1 + } + } + }, + "preferenceGroup": { + "type": "object", + "additionalProperties": false, + "required": [ + "paytagRef", + "chain", + "network", + "asset", + "routes" + ], + "properties": { + "paytagRef": { + "type": "string", + "minLength": 1 + }, + "maskedDisplay": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "payingDapp": { + "$ref": "#/$defs/dappSummary" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/$defs/receiveRoute" + } + } + } + }, + "receiveRoute": { + "type": "object", + "additionalProperties": false, + "required": [ + "routeId", + "payToDappId", + "payToDappName", + "priority", + "isDefault", + "state" + ], + "properties": { + "routeId": { + "type": "string", + "minLength": 1 + }, + "payToDappId": { + "type": "string", + "minLength": 1 + }, + "payToDappName": { + "type": "string", + "minLength": 1 + }, + "appUrl": { + "type": "string", + "format": "uri" + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "isDefault": { + "type": "boolean" + }, + "state": { + "enum": [ + "active", + "disabled", + "revoked" + ] + } + } + } + } +} as const; + +export const PortalPreferencesUpdateRequestSchema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.mypaytag.com/portal-preferences-update-request.schema.json", + "title": "PortalPreferencesUpdateRequest", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "group", + "orderedRouteIds" + ], + "properties": { + "schema": { + "const": "mypaytag.portal.preferences.update.v1" + }, + "group": { + "type": "object", + "additionalProperties": false, + "required": [ + "paytagRef", + "chain", + "network", + "asset" + ], + "properties": { + "paytagRef": { + "type": "string", + "minLength": 1 + }, + "chain": { + "type": "string", + "minLength": 1 + }, + "network": { + "type": "string", + "minLength": 1 + }, + "asset": { + "type": "string", + "minLength": 1 + }, + "payingDappId": { + "type": "string", + "minLength": 1 + } + } + }, + "orderedRouteIds": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + } +} as const; + export const ProviderCallbackRequestSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.mypaytag.com/provider-callback-request.schema.json", @@ -2397,6 +3098,9 @@ export const protocolSchemas = { "near-one-click-quote-selection-request": NearOneClickQuoteSelectionRequestSchema, "notification-event": NotificationEventSchema, "pay-to-route": PayToRouteSchema, + "portal-history-response": PortalHistoryResponseSchema, + "portal-preferences-response": PortalPreferencesResponseSchema, + "portal-preferences-update-request": PortalPreferencesUpdateRequestSchema, "provider-callback-request": ProviderCallbackRequestSchema, "provider-response": ProviderResponseSchema, "resolve-request": ResolveRequestSchema, diff --git a/packages/protocol/src/generated/types.ts b/packages/protocol/src/generated/types.ts index bb46ede..8f7b9c4 100644 --- a/packages/protocol/src/generated/types.ts +++ b/packages/protocol/src/generated/types.ts @@ -10,6 +10,9 @@ export type { NearOneClickQuoteOption } from "./types/near-one-click-quote-optio 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 { PortalHistoryResponse } from "./types/portal-history-response.js"; +export type { PortalPreferencesResponse } from "./types/portal-preferences-response.js"; +export type { PortalPreferencesUpdateRequest } from "./types/portal-preferences-update-request.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"; diff --git a/packages/protocol/src/generated/types/portal-history-response.ts b/packages/protocol/src/generated/types/portal-history-response.ts new file mode 100644 index 0000000..9b95fc6 --- /dev/null +++ b/packages/protocol/src/generated/types/portal-history-response.ts @@ -0,0 +1,115 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/portal-history-response.schema.json. Do not edit by hand. + +export type HistoryItem = Question | Answer | Quote | PaymentIntent | Receipt; + +export interface PortalHistoryResponse { + schema: "mypaytag.portal.history.v1"; + user: PortalUser; + items: HistoryItem[]; + nextCursor?: string; +} +export interface PortalUser { + maskedDisplay?: string; +} +export interface Question { + kind: "question"; + id: string; + createdAt: string; + payingDapp: DappSummary; + paytagMaskedDisplay?: string; + /** + * @minItems 1 + */ + requestedPaths: [RequestedPath, ...RequestedPath[]]; + amount: { + value: string; + currency: string; + }; + purpose: string; + payingDappReference: string; +} +export interface DappSummary { + id: string; + displayName: string; + appUrl?: string; +} +export interface RequestedPath { + chain: string; + network: string; + asset: string; +} +export interface Answer { + kind: "answer"; + id: string; + questionId: string; + createdAt: string; + status: + | "resolved" + | "no_route" + | "user_action_required" + | "authorization_required" + | "provider_unavailable" + | "provider_error" + | "invalid_identifier" + | "invalid_request"; + selectedPayToDapp?: DappSummary; + actionState?: "ready" | "selected_route" | "expired" | "completed"; +} +export interface Quote { + kind: "quote"; + id: string; + questionId: string; + createdAt: string; + provider: string; + from: AssetAmount; + to: AssetAmount; + fees: Fee[]; + expiresAt: string; + routeSteps: RouteStep[]; + status: "available" | "selected" | "expired" | "failed"; + selectedQuote?: { + selectedQuoteId: string; + selectedAt: string; + payableInstructionRef?: string; + }; +} +export interface AssetAmount { + chain: string; + network: string; + asset: string; + amount: string; +} +export interface Fee { + label: string; + amount: string; + asset: string; + chargedTo: "sender" | "recipient"; + source: string; +} +export interface RouteStep { + label: string; + from: AssetAmount; + to: AssetAmount; +} +export interface PaymentIntent { + kind: "payment_intent"; + id: string; + questionId: string; + createdAt: string; + status: "ready" | "expired" | "used" | "failed"; + payToDapp: DappSummary; + amount: AssetAmount; + expiresAt: string; +} +export interface Receipt { + kind: "receipt"; + id: string; + paymentIntentId: string; + createdAt: string; + status: "pending" | "completed" | "failed" | "unavailable"; + transactionHash?: string; + chain?: string; + network?: string; + explorerUrl?: string; +} diff --git a/packages/protocol/src/generated/types/portal-preferences-response.ts b/packages/protocol/src/generated/types/portal-preferences-response.ts new file mode 100644 index 0000000..fe6c89a --- /dev/null +++ b/packages/protocol/src/generated/types/portal-preferences-response.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/portal-preferences-response.schema.json. Do not edit by hand. + +export interface PortalPreferencesResponse { + schema: "mypaytag.portal.preferences.v1"; + user: PortalUser; + groups: PreferenceGroup[]; +} +export interface PortalUser { + maskedDisplay?: string; +} +export interface PreferenceGroup { + paytagRef: string; + maskedDisplay?: string; + chain: string; + network: string; + asset: string; + payingDapp?: DappSummary; + routes: ReceiveRoute[]; +} +export interface DappSummary { + id: string; + displayName: string; +} +export interface ReceiveRoute { + routeId: string; + payToDappId: string; + payToDappName: string; + appUrl?: string; + priority: number; + isDefault: boolean; + state: "active" | "disabled" | "revoked"; +} diff --git a/packages/protocol/src/generated/types/portal-preferences-update-request.ts b/packages/protocol/src/generated/types/portal-preferences-update-request.ts new file mode 100644 index 0000000..cc52e04 --- /dev/null +++ b/packages/protocol/src/generated/types/portal-preferences-update-request.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +// Generated from packages/protocol/schemas/portal-preferences-update-request.schema.json. Do not edit by hand. + +export interface PortalPreferencesUpdateRequest { + schema: "mypaytag.portal.preferences.update.v1"; + group: { + paytagRef: string; + chain: string; + network: string; + asset: string; + payingDappId?: string; + }; + /** + * @minItems 1 + */ + orderedRouteIds: [string, ...string[]]; +} diff --git a/packages/protocol/src/protocol.test.ts b/packages/protocol/src/protocol.test.ts index a3b7c43..21458c7 100644 --- a/packages/protocol/src/protocol.test.ts +++ b/packages/protocol/src/protocol.test.ts @@ -18,6 +18,9 @@ import { validateNearOneClickQuoteOption, validateNearOneClickQuoteSelectionRequest, validatePayToRoute, + validatePortalHistoryResponse, + validatePortalPreferencesResponse, + validatePortalPreferencesUpdateRequest, validateProviderCallbackRequest, validateProviderResponse, validateRouteDeleteResponse, @@ -39,6 +42,9 @@ import { validNotificationEvent, validNearOneClickQuoteOption, validNearOneClickQuoteSelectionRequest, + validPortalHistoryResponse, + validPortalPreferencesResponse, + validPortalPreferencesUpdateRequest, validProviderCallbackRequest, validProviderResponse, validPayToRoute, @@ -131,6 +137,15 @@ describe("@mypaytag/protocol", () => { expect(validateNearOneClickPayableInstruction(validNearOneClickPayableInstruction)).toEqual( validNearOneClickPayableInstruction, ); + expect(validatePortalPreferencesResponse(validPortalPreferencesResponse)).toEqual( + validPortalPreferencesResponse, + ); + expect(validatePortalPreferencesUpdateRequest(validPortalPreferencesUpdateRequest)).toEqual( + validPortalPreferencesUpdateRequest, + ); + expect(validatePortalHistoryResponse(validPortalHistoryResponse)).toEqual( + validPortalHistoryResponse, + ); }); it("accepts every public status value and response shape", () => { @@ -200,6 +215,76 @@ describe("@mypaytag/protocol", () => { } }); + it("rejects portal preference payloads that omit user scope or expose private wallet data", () => { + expect(() => + validatePortalPreferencesResponse({ + ...validPortalPreferencesResponse, + user: undefined, + }), + ).toThrow(); + + for (const [field, value] of [ + ["address", "0xabc123"], + ["recipientAddress", "0xabc123"], + ["walletGraph", { connectedWallets: ["0xabc123"] }], + ["providerCallbackUrl", "https://wallet.example/callback"], + ["privilegedBackendToken", "private-token"], + ] as const) { + expect(() => + validatePortalPreferencesResponse({ + ...validPortalPreferencesResponse, + groups: [ + { + ...validPortalPreferencesResponse.groups[0], + routes: [ + { + ...validPortalPreferencesResponse.groups[0].routes[0], + [field]: value, + }, + ], + }, + ], + }), + ).toThrow(); + } + + expect(() => + validatePortalPreferencesUpdateRequest({ + ...validPortalPreferencesUpdateRequest, + orderedRouteIds: [], + }), + ).toThrow(); + }); + + it("rejects portal history payloads that omit user scope or expose private diagnostics", () => { + expect(() => + validatePortalHistoryResponse({ + ...validPortalHistoryResponse, + user: undefined, + }), + ).toThrow(); + + for (const [field, value] of [ + ["rawIdentifier", "abd123@cubid.mypaytag"], + ["providerSecret", "secret"], + ["privateDiagnostics", { reason: "rls_miss" }], + ["walletGraph", { connectedWallets: ["0xabc123"] }], + ["privilegedBackendToken", "private-token"], + ] as const) { + expect(() => + validatePortalHistoryResponse({ + ...validPortalHistoryResponse, + items: [ + { + ...validPortalHistoryResponse.items[0], + [field]: value, + }, + ], + }), + ).toThrow(); + } + }); + it("accepts hosted route-selection action view, decision, and completion states", () => { expect(validateHostedActionView(validHostedActionView)).toEqual(validHostedActionView); expect(validateHostedActionView(expiredHostedActionView)).toEqual(expiredHostedActionView); @@ -572,6 +657,12 @@ describe("@mypaytag/protocol", () => { 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 portalPreferencesExample = + openApi.paths["/portal/preferences"].get.responses["200"].content["application/json"].examples.preferences.value; + const portalPreferencesUpdateExample = + openApi.paths["/portal/preferences"].put.requestBody.content["application/json"].examples.reorder.value; + const portalHistoryExample = + openApi.paths["/portal/history"].get.responses["200"].content["application/json"].examples.history.value; const notificationExample = openApi.paths["/notifications"].post.requestBody.content["application/json"].examples.paymentIntentCreated.value; const providerCallbackExample = @@ -598,6 +689,13 @@ describe("@mypaytag/protocol", () => { expect(validateHostedActionCompletion(hostedActionCompletionExample)).toEqual( hostedActionCompletionExample, ); + expect(validatePortalPreferencesResponse(portalPreferencesExample)).toEqual( + portalPreferencesExample, + ); + expect(validatePortalPreferencesUpdateRequest(portalPreferencesUpdateExample)).toEqual( + portalPreferencesUpdateExample, + ); + expect(validatePortalHistoryResponse(portalHistoryExample)).toEqual(portalHistoryExample); 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 4883767..6a91483 100644 --- a/packages/protocol/src/validators.ts +++ b/packages/protocol/src/validators.ts @@ -13,6 +13,9 @@ import type { MyPayTagStatus, NotificationEvent, PayToRoute, + PortalHistoryResponse, + PortalPreferencesResponse, + PortalPreferencesUpdateRequest, ProviderCallbackRequest, ProviderResponse, RouteDeleteResponse, @@ -100,6 +103,15 @@ export const validateRouteUpdateRequest = (payload: unknown) => export const validateRouteDeleteResponse = (payload: unknown) => validateProtocolPayload("route-delete-response", payload); +export const validatePortalPreferencesResponse = (payload: unknown) => + validateProtocolPayload("portal-preferences-response", payload); + +export const validatePortalPreferencesUpdateRequest = (payload: unknown) => + validateProtocolPayload("portal-preferences-update-request", payload); + +export const validatePortalHistoryResponse = (payload: unknown) => + validateProtocolPayload("portal-history-response", payload); + export const validateResolveRequest = (payload: unknown) => validateProtocolPayload("resolve-request", payload);