From 7d4c57f979e79fe38709b2a7d5fc4518560c0d04 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 25 Aug 2026 12:34:56 -0400 Subject: [PATCH] chore(definitions): delete the vendored protos and their codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :services:opencode and :services:flipcash now compile against com.flipcash:{ocp,flipcash2}-client-protocol (#1325), so the four :definitions:* modules generate output nobody depends on. Nothing outside definitions/ referenced them — the models modules were consumed only by the services, and the protos modules only by their own models — so the whole directory goes, along with scripts/fetch-protos.sh, which pulled the .proto copies that are no longer here. That leaves the protobuf Gradle plugin and protovalidate plugin applied nowhere, so their catalog aliases and the protobuf-plugin version go too. The protobuf, grpc-kotlin, and protovalidate-kt versions stay: the runtime artifacts they pin are still dependencies of the services and libs/encryption. Docs and the /fetch-protos skill described a workflow that no longer exists — fetch upstream .protos, run protoc here, build :definitions:*:models. Rewritten around what actually happens now: find the release, bump the pin, diff the contract between tags, build the service module. The skill keeps its scaffolding half, which is unaffected. 13-protobuf-and-codegen.md is largely rewritten. Verified on device: 603 opencode and 220 flipcash unit tests pass, the debug APK builds and installs, and the launched app reaches a signed-in session with live balances over real RPCs. --- .claude/agents/proto-change-tracer.md | 17 +- .claude/skills/fetch-protos/SKILL.md | 99 +- .github/labeler.yml | 2 - CLAUDE.md | 6 +- README.md | 6 +- build.gradle.kts | 1 - definitions/flipcash/models/.gitignore | 2 - definitions/flipcash/models/build.gradle.kts | 74 - definitions/flipcash/protos/.gitignore | 2 - definitions/flipcash/protos/build.gradle.kts | 11 - .../account/v1/flipcash_account_service.proto | 184 -- .../activity/v1/activity_feed_service.proto | 88 - .../src/main/proto/activity/v1/model.proto | 158 -- .../proto/blob/v1/blob_storage_service.proto | 152 -- .../protos/src/main/proto/blob/v1/model.proto | 326 ---- .../blocklist/v1/blocklist_service.proto | 116 -- .../src/main/proto/blocklist/v1/model.proto | 20 - .../src/main/proto/chat/v1/chat_service.proto | 106 - .../protos/src/main/proto/chat/v1/model.proto | 98 - .../src/main/proto/common/v1/common.proto | 235 --- .../contact/v1/contact_list_service.proto | 134 -- .../src/main/proto/contact/v1/model.proto | 22 - .../email/v1/email_verification_service.proto | 95 - .../src/main/proto/email/v1/model.proto | 16 - .../event/v1/event_streaming_service.proto | 69 - .../src/main/proto/event/v1/model.proto | 132 -- .../src/main/proto/iap/v1/iap_service.proto | 57 - .../src/main/proto/intent/v1/model.proto | 50 - .../messaging/v1/messaging_service.proto | 484 ----- .../src/main/proto/messaging/v1/model.proto | 449 ----- .../src/main/proto/moderation/v1/model.proto | 38 - .../moderation/v1/moderation_service.proto | 75 - .../src/main/proto/phone/v1/model.proto | 16 - .../phone/v1/phone_verification_service.proto | 116 -- .../src/main/proto/profile/v1/model.proto | 119 -- .../proto/profile/v1/profile_service.proto | 215 --- .../protos/src/main/proto/push/v1/model.proto | 79 - .../src/main/proto/push/v1/push_service.proto | 53 - .../src/main/proto/resolver/v1/model.proto | 32 - .../proto/resolver/v1/resolver_service.proto | 36 - .../proto/settings/v1/settings_service.proto | 34 - .../src/main/proto/thirdparty/v1/model.proto | 30 - .../thirdparty/v1/third_party_service.proto | 51 - definitions/opencode/models/.gitignore | 2 - definitions/opencode/models/build.gradle.kts | 74 - definitions/opencode/protos/.gitignore | 2 - definitions/opencode/protos/build.gradle.kts | 11 - .../account/v1/ocp_account_service.proto | 227 --- .../src/main/proto/common/v1/model.proto | 169 -- .../currency/v1/ocp_currency_service.proto | 720 ------- .../messaging/v1/ocp_messaging_service.proto | 282 --- .../v1/ocp_transaction_service.proto | 1703 ----------------- .../architecture/01-modules-and-boundaries.md | 16 +- .../architecture/09-separation-of-concerns.md | 3 +- docs/architecture/13-protobuf-and-codegen.md | 110 +- docs/architecture/16-agents-and-skills.md | 6 +- docs/architecture/README.md | 4 +- docs/architecture/glossary.md | 2 +- gradle/libs.versions.toml | 3 - scripts/fetch-protos.sh | 69 - services/flipcash/README.md | 2 +- services/opencode/README.md | 2 +- settings.gradle.kts | 12 +- 63 files changed, 144 insertions(+), 7380 deletions(-) delete mode 100644 definitions/flipcash/models/.gitignore delete mode 100644 definitions/flipcash/models/build.gradle.kts delete mode 100644 definitions/flipcash/protos/.gitignore delete mode 100644 definitions/flipcash/protos/build.gradle.kts delete mode 100644 definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/activity/v1/activity_feed_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/activity/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/blob/v1/blob_storage_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/blob/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/blocklist/v1/blocklist_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/blocklist/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/chat/v1/chat_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/chat/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/common/v1/common.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/contact/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/email/v1/email_verification_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/email/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/event/v1/event_streaming_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/event/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/iap/v1/iap_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/intent/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/messaging/v1/messaging_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/moderation/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/moderation/v1/moderation_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/phone/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/phone/v1/phone_verification_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/profile/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/profile/v1/profile_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/push/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/push/v1/push_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/resolver/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/resolver/v1/resolver_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/settings/v1/settings_service.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/thirdparty/v1/model.proto delete mode 100644 definitions/flipcash/protos/src/main/proto/thirdparty/v1/third_party_service.proto delete mode 100644 definitions/opencode/models/.gitignore delete mode 100644 definitions/opencode/models/build.gradle.kts delete mode 100644 definitions/opencode/protos/.gitignore delete mode 100644 definitions/opencode/protos/build.gradle.kts delete mode 100644 definitions/opencode/protos/src/main/proto/account/v1/ocp_account_service.proto delete mode 100644 definitions/opencode/protos/src/main/proto/common/v1/model.proto delete mode 100644 definitions/opencode/protos/src/main/proto/currency/v1/ocp_currency_service.proto delete mode 100644 definitions/opencode/protos/src/main/proto/messaging/v1/ocp_messaging_service.proto delete mode 100644 definitions/opencode/protos/src/main/proto/transaction/v1/ocp_transaction_service.proto delete mode 100755 scripts/fetch-protos.sh diff --git a/.claude/agents/proto-change-tracer.md b/.claude/agents/proto-change-tracer.md index a8c06db34c..972e9fe477 100644 --- a/.claude/agents/proto-change-tracer.md +++ b/.claude/agents/proto-change-tracer.md @@ -13,8 +13,8 @@ When proto definitions change, trace the impact through the full dependency chai ## Architecture: Proto → Feature Chain ``` -definitions//protos/src/main/proto/ ← .proto files - [protobuf codegen] +com.flipcash:{ocp,flipcash2}-client-protocol ← published artifact, pinned in libs.versions.toml + [generated in its own repo — nothing runs protoc here] ↓ com.codeinc..gen..v1 ← Generated stubs (GrpcKt, request/response classes) ↓ @@ -29,15 +29,20 @@ services// — *Controller.kt ← User-facing abstraction (r apps/flipcash/shared/*/ or features/*/ ← ViewModels consume controllers ``` -**Proto packages:** -- Flipcash: `com.codeinc.flipcash.gen..v1` (phone, account, email, profile, push, activity, event, settings, iap, moderation, thirdparty) -- OpenCode: `com.codeinc.opencode.gen..v1` (transaction, account, currency, messaging) +**Proto packages** (the artifact coordinate is `com.flipcash`; the packages inside are not): +- Flipcash, from `flipcash2-client-protocol`: `com.codeinc.flipcash.gen..v1` (phone, account, email, profile, push, activity, event, settings, iap, moderation, thirdparty) +- OpenCode, from `ocp-client-protocol`: `com.codeinc.opencode.gen..v1` (transaction, account, currency, messaging) + +To read a generated stub, look in the resolved artifact under `~/.gradle/caches/modules-2/` +or in the client repo's build output — there is no generated source tree in this project. ## Analysis Process ### 1. Identify what changed in the proto definitions -Compare the current proto files with the previous version (use git diff on `definitions/`). Identify: +The `.proto` sources are not in this repo. Diff the contract between the old and new +artifact versions with `gh api repos/code-payments/-client-protocol/compare/...`, +or read `proto/` in a local clone at each tag. Identify: - New services or RPCs - Changed request/response message fields - New or modified enum values diff --git a/.claude/skills/fetch-protos/SKILL.md b/.claude/skills/fetch-protos/SKILL.md index c7984f902f..9c0c3e237d 100644 --- a/.claude/skills/fetch-protos/SKILL.md +++ b/.claude/skills/fetch-protos/SKILL.md @@ -1,10 +1,10 @@ --- name: fetch-protos description: > - Fetch latest protobuf definitions, verify build, summarize API changes, - and scaffold new service stubs. Usage: /fetch-protos [flipcash|opencode] [commit_sha] + Bump a client-protocol artifact, summarize the contract changes it carries, + and scaffold new service stubs. Usage: /fetch-protos [flipcash|opencode] [version] user-invocable: true -argument-hint: "[flipcash|opencode] [commit_sha]" +argument-hint: "[flipcash|opencode] [version]" allowed-tools: - Bash - Read @@ -17,79 +17,92 @@ allowed-tools: # Fetch Protos -Fetch protobuf definitions from upstream repos, verify they compile, summarize -API changes, and scaffold missing service layer implementations. +The protos are no longer vendored here. Both contracts arrive as published +artifacts, so "fetching" is bumping a version pin and reacting to what the new +version changed. + +| Target | Artifact | Client repo | Upstream contract | +|--------|----------|-------------|-------------------| +| `flipcash` | `com.flipcash:flipcash2-client-protocol` | `code-payments/flipcash2-client-protocol` | `code-payments/flipcash2-protobuf-api` | +| `opencode` | `com.flipcash:ocp-client-protocol` | `code-payments/ocp-client-protocol` | `code-payments/ocp-protobuf-api` | ## Pre-flight context -- Current proto files: !`find definitions/*/protos/src/main/proto -name "*.proto" 2>/dev/null | wc -l | tr -d ' '` proto files across targets -- Git status: !`git status --short definitions/` +- Pinned versions: !`grep -E "^(ocp|flipcash2)-client-protocol = " gradle/libs.versions.toml` +- Git status: !`git status --short gradle/libs.versions.toml services/` ## Input -Parse `$ARGUMENTS` to determine targets and optional commit SHA. +Parse `$ARGUMENTS` to determine targets and an optional version. **Rules:** - Known targets: `flipcash`, `opencode` -- If no targets specified, fetch **both** (`flipcash` and `opencode`) -- A hex string (7+ chars) as the last argument is treated as a commit SHA +- If no targets specified, check **both** +- A semver-looking string as the last argument is the version to move to; without + one, use the latest release - Examples: - - `/fetch-protos` → fetch flipcash + opencode at HEAD - - `/fetch-protos flipcash` → fetch flipcash only - - `/fetch-protos opencode abc1234` → fetch opencode at commit abc1234 - - `/fetch-protos flipcash opencode` → fetch both explicitly + - `/fetch-protos` → check both artifacts for newer releases + - `/fetch-protos flipcash` → flipcash only, latest release + - `/fetch-protos opencode 0.2.0` → opencode at 0.2.0 ## Steps -### Step 1 — Fetch protos - -For each target, run the fetch script from the repo root: +### Step 1 — Find the release ```bash -bash scripts/fetch-protos.sh -t [commit_sha] +gh release list --repo code-payments/-client-protocol --limit 10 ``` -Target-to-repo mapping (handled by the script): -| Target | Repository | -|--------|-----------| -| `flipcash` | `git@github.com:code-payments/flipcash2-protobuf-api.git` | -| `opencode` | `git@github.com:code-payments/ocp-protobuf-api.git` | +Compare against the pin in `gradle/libs.versions.toml`. If the pinned version is +already the latest and no version was requested, say so and stop. + +If the contract change you want has **not been released**, it has to land in the +client repo first: sync its protos at the upstream SHA, regenerate, and publish. +That repo's README covers it — this skill does not do it. -Show the script output to the user. +### Step 2 — Bump the pin + +Edit `gradle/libs.versions.toml`: + +```toml +ocp-client-protocol = "" # or flipcash2-client-protocol +``` -### Step 2 — Diff and summarize changes +### Step 3 — Diff and summarize the contract change -Run `git diff` on the proto directories to identify what changed: +The `.proto` sources are not in this repo. Diff them between the two release tags: ```bash -git diff --stat definitions/ -git diff definitions/ +gh api repos/code-payments//compare/... \ + --jq '.files[] | select(.filename | startswith("proto/")) | .filename' ``` -For each changed `.proto` file, summarize: +Read the patch for each changed file. Summarize: - **New RPCs** added to services - **Modified RPCs** (changed request/response types or fields) - **Removed RPCs** - **New/modified messages** and fields -Present a structured change summary table to the user. If nothing changed, report -that protos are already up to date and stop here. +Present a structured change summary table. If the diff carries no `proto/` change, +the release is generator or packaging work only — say so, and expect no service +layer impact. -### Step 3 — Build verification +### Step 4 — Build verification -Build the definitions modules to verify the protos compile: +Build the service module that consumes the artifact: ```bash -./gradlew :definitions:flipcash:models:assembleDebug :definitions:opencode:models:assembleDebug +./gradlew :services::assembleDebug ``` -Only build the targets that were fetched. If the build fails, show errors and stop. +Only build the targets that were bumped. If the build fails, show errors and stop — +a removed or renamed field breaks compilation here, which is the point. -### Step 4 — Detect service layer impact +### Step 5 — Detect service layer impact -#### 4a — RPC changes +#### 5a — RPC changes -For each new or modified RPC found in Step 2: +For each new or modified RPC found in Step 3: 1. Identify which service proto file it belongs to (e.g., `account/v1/flipcash_account_service.proto`) 2. Search for the corresponding Api class in `services//src/**/network/api/` @@ -103,7 +116,7 @@ Present a report: | `NewRpc` | missing | missing | missing | missing | **New — needs scaffolding** | | `ModifiedRpc` | exists | exists | exists | exists | **Signature may need update** | -#### 4b — Message field changes (domain models) +#### 5b — Message field changes (domain models) For each message with added or removed fields (e.g., `UserFlags`, `UserProfile`): @@ -156,7 +169,7 @@ For **read-only** fields (e.g., booleans like `enablePhoneNumberSend`): Present a report of domain model updates needed and apply them after user confirmation. -### Step 5 — Scaffold new service stubs +### Step 6 — Scaffold new service stubs For RPCs marked as needing scaffolding, ask the user if they want to scaffold them. If confirmed, generate code following the patterns below. @@ -269,13 +282,13 @@ suspend fun newRpc(...): Result { If a new Repository interface+impl pair was created, add a `@Provides` binding in the corresponding Hilt module (`FlipcashModule.kt` or `OpenCodeModule.kt`). -### Step 6 — Review and commit +### Step 7 — Review and commit Show the user a summary of all changes (proto updates + any scaffolded code). Offer to commit with a conventional commit message: ``` -chore(protos): update protobuf definitions +chore(protos): bump -client-protocol to ``` If service stubs were also scaffolded, suggest a separate commit: @@ -285,7 +298,7 @@ feat(): scaffold service stubs for new RPCs ## Never -- Edit generated protobuf code in `definitions/*/models/build/` +- Try to edit the generated protobuf code — it lives in the published artifact - Commit without user approval - Skip build verification - Scaffold service code without asking the user first diff --git a/.github/labeler.yml b/.github/labeler.yml index fcd43831d7..8fba540a55 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -8,7 +8,6 @@ - "apps/flipcash/shared/transfers/**" - "apps/flipcash/shared/bills/**" - "apps/flipcash/shared/google-play-billing/**" - - "definitions/**/micropayment/**" "area: crypto": - changed-files: @@ -68,7 +67,6 @@ - "services/flipcash-compose/**" - "services/opencode/**" - "services/opencode-compose/**" - - "definitions/**" "area: onramp": - changed-files: diff --git a/CLAUDE.md b/CLAUDE.md index 82ccd816ac..170a698b52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,10 +44,6 @@ services/ opencode/ — Open Code Protocol gRPC services *-compose/ — Compose wrappers for services -definitions/ - flipcash/ — Protobuf definitions for Flipcash - opencode/ — Protobuf definitions for OCP - libs/ — 20+ internal libraries crypto/ — Solana, Kin, Ed25519, encryption, key management network/ — Connectivity, JWT, exchange rates, Coinbase @@ -86,7 +82,7 @@ The feature plugin automatically includes `:libs:logging`, `:ui:core`, `:ui:comp - **CompositionLocal injection**: `MainActivity` provides dozens of controllers/services via `CompositionLocalProvider` — features access dependencies through `Local*` composition locals rather than direct injection - **Feature modules are self-contained**: Each has its own state, controllers, and UI; communicates via shared modules -- **Protobuf models**: Backend models are generated from `.proto` files in `definitions/`; don't hand-edit generated code +- **Protobuf models**: Backend models come from the published `com.flipcash:{ocp,flipcash2}-client-protocol` artifacts, not from protos in this repo; the contracts are generated in their own repos - **Dark mode only**: App forces `MODE_NIGHT_YES` ## Namespaces diff --git a/README.md b/README.md index 4b93b66639..f829198a2f 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,10 @@ graph TD Features["apps/flipcash/features/* — 26 self-contained screens"] Shared["apps/flipcash/shared/* — coordinators / controllers / services"] Services["services/* — gRPC wrappers (API → Service → Repository → Controller)"] - Defs["definitions/* — protobuf sources + generated models"] UI["ui/* — Compose components, theme, navigation, scanner"] Libs["libs/* — crypto, network, logging, currency (leaf utilities)"] - App --> Features --> Shared --> Services --> Defs --> Libs + App --> Features --> Shared --> Services --> Libs Features --> UI --> Libs Services --> Libs ``` @@ -83,7 +82,7 @@ persistence, payments, the design system, testing, and more. | Navigation | Jetpack **Navigation 3** + a custom `CodeNavigator` | | DI | **Hilt** + `CompositionLocal` | | Async | Kotlin **Coroutines + Flow** (MVI via `BaseViewModel`) | -| Networking | **gRPC + Protobuf**; Retrofit/OkHttp for REST | +| Networking | **gRPC + Protobuf** (contracts from the published `com.flipcash:{ocp,flipcash2}-client-protocol` artifacts); Retrofit/OkHttp for REST | | Persistence | **Room** (per-user database) + DataStore | | Crypto | **Ed25519**, BIP39 mnemonic/key derivation, **Solana** | | Build | Gradle convention plugins, KSP, **Java 21** | @@ -93,7 +92,6 @@ persistence, payments, the design system, testing, and more. ``` apps/flipcash/ Main app + feature (26) and shared modules services/ gRPC clients: flipcash, opencode (+ Compose wrappers) -definitions/ Protobuf sources and generated models libs/ Reusable utilities (crypto, network, logging, currency, …) ui/ Compose design system, navigation, scanner, biometrics vendor/ Third-party SDKs (Kik scanner, OpenCV, TipKit) diff --git a/build.gradle.kts b/build.gradle.kts index 1d3f166c0d..9820a51873 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,6 @@ plugins { alias(libs.plugins.bugsnag.gradle) apply false alias(libs.plugins.secrets) apply false alias(libs.plugins.navigation.safeargs) apply false - alias(libs.plugins.protobuf) apply false alias(libs.plugins.androidx.room) apply false alias(libs.plugins.screenshot) apply false alias(libs.plugins.kover) diff --git a/definitions/flipcash/models/.gitignore b/definitions/flipcash/models/.gitignore deleted file mode 100644 index 9f2a078806..0000000000 --- a/definitions/flipcash/models/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.gradle/ diff --git a/definitions/flipcash/models/build.gradle.kts b/definitions/flipcash/models/build.gradle.kts deleted file mode 100644 index 8bb42623ae..0000000000 --- a/definitions/flipcash/models/build.gradle.kts +++ /dev/null @@ -1,74 +0,0 @@ -import dev.bmcreations.protovalidate.gradle.ProtoVariant -import org.apache.tools.ant.taskdefs.condition.Os - -plugins { - alias(libs.plugins.flipcash.android.library) - alias(libs.plugins.protobuf) - alias(libs.plugins.protobuf.validate) -} - -val archSuffix = if (Os.isFamily(Os.FAMILY_MAC)) { - if (System.getProperty("os.arch") == "aarch64") ":osx-aarch_64" else ":osx-x86_64" -} else "" - -version = "0.0.1" -group = "com.codeinc.flipcash.gen" - -dependencies { - protobuf(project(":definitions:flipcash:protos")) - - implementation(libs.grpc.protobuf.lite) - implementation(libs.grpc.stub) - - // Kotlin Generation - implementation(libs.grpc.kotlin) - implementation(libs.protobuf.kotlin.lite) -} - -android { - namespace = "${Gradle.flipcashNamespace}.defs.models" -} - -val protobufVersion = libs.versions.protobuf.asProvider().get() -val grpcVersion = libs.versions.grpc.asProvider().get() - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${protobufVersion}$archSuffix" - } - plugins { - create("java") { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" - } - create("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" - } - create("grpckt") { - artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" - } - } - generateProtoTasks { - all().forEach { - it.plugins { - create("java") { - option("lite") - } - create("grpc") { - option("lite") - } - create("grpckt") { - option("lite") - } - } - it.builtins { - create("kotlin") { - option("lite") - } - } - } - } -} - -protovalidate { - variant.set(ProtoVariant.PGV) -} \ No newline at end of file diff --git a/definitions/flipcash/protos/.gitignore b/definitions/flipcash/protos/.gitignore deleted file mode 100644 index 9f2a078806..0000000000 --- a/definitions/flipcash/protos/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.gradle/ diff --git a/definitions/flipcash/protos/build.gradle.kts b/definitions/flipcash/protos/build.gradle.kts deleted file mode 100644 index a42e252c72..0000000000 --- a/definitions/flipcash/protos/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -// todo: maybe use variants / configurations to do both stub & stub-lite here - -// Note: We use the java-library plugin to get the protos into the artifact for this subproject -// because there doesn't seem to be an better way. -plugins { - `java-library` -} - -java { - sourceSets.getByName("main").resources.srcDir("src/main/proto") -} diff --git a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto b/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto deleted file mode 100644 index 387a03d355..0000000000 --- a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto +++ /dev/null @@ -1,184 +0,0 @@ -syntax = "proto3"; - -package flipcash.account.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/account/v1;acountpb"; -option java_package = "com.codeinc.flipcash.gen.account.v1"; -option objc_class_prefix = "FPBAccountV1"; - -import "common/v1/common.proto"; -import "google/protobuf/duration.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -service Account { - // Register registers a new user, bound to the provided PublicKey. - // If the PublicKey is already in use, the previous user account is returned. - rpc Register(RegisterRequest) returns (RegisterResponse); - - // Login retrieves the UserId (and in the future, potentially other information) - // required for 'recovering' an account. - rpc Login(LoginRequest) returns (LoginResponse); - - // GetUserFlags gets user-specific flags. - rpc GetUserFlags(GetUserFlagsRequest) returns (GetUserFlagsResponse); - - // GetUserFlags gets user flags for unauthenticated users - rpc GetUnauthenticatedUserFlags(GetUnauthenticatedUserFlagsRequest) returns (GetUnauthenticatedUserFlagsResponse); -} - -message RegisterRequest { - // PublicKey the public key that is authorized to perform actions on the - // registered users behalf. - common.v1.PublicKey public_key = 1 [(validate.rules).message.required = true]; - - // Signature of this message (without the signature), using the provided keypair. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; -} -message RegisterResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_SIGNATURE = 1; - DENIED = 2; - } - - // The UserId associated with the account. - common.v1.UserId user_id = 2; -} - -message LoginRequest { - // Timestamp is the timestamp the request was generated - // - // The server may reject the request if the timestamp is too far off - // the current (server) time. This is to prevent replay attacks. - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} -message LoginResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_TIMESTAMP = 1; - DENIED = 2; - } - - common.v1.UserId user_id = 2; -} - -message GetUserFlagsRequest { - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; - - common.v1.Platform platform = 3; - - common.v1.CountryCode country_code = 4; -} -message GetUserFlagsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - UserFlags user_flags = 2; -} - -message GetUnauthenticatedUserFlagsRequest { - common.v1.Platform platform = 1; - - common.v1.CountryCode country_code = 2; -} -message GetUnauthenticatedUserFlagsResponse { - Result result = 1; - enum Result { - OK = 0; - } - - UserFlags user_flags = 2; -} - -message UserFlags { - // Is this a fully registered account using IAP for account creation? - bool is_registered_account = 1; - - // Is this user associated with a Flipcash staff member? - bool is_staff = 2; - - // Does this user require IAP for registration in the account creation flow? - bool requires_iap_for_registration = 3; - - enum OnRampProvider { - UNKNOWN_ON_RAMP_PROVIDER = 0; - COINBASE_VIRTUAL = 1; - COINBASE_PHYSICAL_DEBIT = 2; - COINBASE_PHYSICAL_CREDIT = 3; - MANUAL_DEPOSIT = 4; - PHANTOM = 5; - SOLFLARE = 6; - BACKPACK = 7; - BASE = 8; - } - - // The set of supported on ramp providers for the user, based on their platform - // and locale if provided - repeated OnRampProvider supported_on_ramp_providers = 4 [(validate.rules).repeated = { - min_items: 0 - max_items: 256 - }]; - - // The preferred on ramp provider for this user. If the value is UNKNOWN, client - // should show the list of all supported providers. - OnRampProvider preferred_on_ramp_provider = 5; - - // The minumum build number for this user. If their build number is less than the - // provided value, client should show a forced upgrade screen. - uint32 min_build_number = 6; - - // Exchange data timeout for sequential give/grabs for bills - google.protobuf.Duration bill_exchange_data_timeout = 7; - - // USDF amount, in quarks, that must be purchased when launching a new currency - uint64 new_currency_purchase_amount = 8; - - // USDF amount, in quarks, that must be paid in a fee when launching a new currency - uint64 new_currency_fee_amount = 9; - - // USDF amount, in quarks, that must be paid when doing a withdrawal - uint64 withdrawal_fee_amount = 10; - - enum UsdcLiquidityPool { - UNKNOWN_USDC_LIQUIDITY_POOL = 0; - FLIPCASH = 1; - COINBASE_STABLE_SWAPPER = 2; - } - - // The preferred USDC liquidity pool for external wallet on ramp flows - UsdcLiquidityPool preferred_on_ramp_usdc_liquidity_pool = 11; - - // Whether the send by phone number feature is enabled - bool enable_phone_number_send = 12; - - // USDF amount, in quarks, that a user must hold to be counted as a holder on the leaderboard - uint64 minimum_holder_value = 13; - - // Whether email verification is required for Coinbase purchase flows - bool require_coinbase_email_verification = 14; - - // Tip presets for all currencies - repeated TipPresets tip_presets = 15; - - // USDF amount, in quarks, that must be held across all currencies in order to set a username - uint64 username_min_balance = 16; -} - -message TipPresets { - common.v1.Region region = 1 [(validate.rules).message.required = true]; - - double minimum = 2; - double low = 3; - double medium = 4; - double high = 5; -} diff --git a/definitions/flipcash/protos/src/main/proto/activity/v1/activity_feed_service.proto b/definitions/flipcash/protos/src/main/proto/activity/v1/activity_feed_service.proto deleted file mode 100644 index 8d847d9d81..0000000000 --- a/definitions/flipcash/protos/src/main/proto/activity/v1/activity_feed_service.proto +++ /dev/null @@ -1,88 +0,0 @@ -syntax = "proto3"; - -package flipcash.activity.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/activity/v1;activitypb"; -option java_package = "com.codeinc.flipcash.gen.activity.v1"; -option objc_class_prefix = "FPBActivityV1"; - -import "activity/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -service ActivityFeed { - // GetLatestNotifications gets the latest N notifications in a user's - // activity feed. Results will be ordered by descending timestamp. - rpc GetLatestNotifications(GetLatestNotificationsRequest) returns (GetLatestNotificationsResponse); - - // GetPagedNotifications gets all notifications using a paging API. - rpc GetPagedNotifications(GetPagedNotificationsRequest) returns (GetPagedNotificationsResponse); - - // GetBatchNotifications gets a batch of notifications by ID. - rpc GetBatchNotifications(GetBatchNotificationsRequest) returns (GetBatchNotificationsResponse); -} - -message GetLatestNotificationsRequest { - // The activity feed to fetch notifications from - ActivityFeedType type = 1 [(validate.rules).enum.in = 1]; - - // Maximum number of notifications to return. If <= 0, the server default is used - int32 max_items = 2 [(validate.rules).int32.lte = 1024]; - - common.v1.Auth auth = 3 [(validate.rules).message.required = true]; -} - -message GetLatestNotificationsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - repeated Notification notifications = 2 [(validate.rules).repeated = { - max_items: 1024 - }]; -} - -message GetPagedNotificationsRequest { - // The activity feed to fetch notifications from - ActivityFeedType type = 1 [(validate.rules).enum.in = 1]; - - common.v1.QueryOptions query_options = 2 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 3 [(validate.rules).message.required = true]; -} - -message GetPagedNotificationsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - repeated Notification notifications = 2 [(validate.rules).repeated = { - max_items: 1024 - }]; -} - -message GetBatchNotificationsRequest { - repeated NotificationId ids = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 - }]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message GetBatchNotificationsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - } - - repeated Notification notifications = 2 [(validate.rules).repeated = { - max_items: 1024 - }]; -} diff --git a/definitions/flipcash/protos/src/main/proto/activity/v1/model.proto b/definitions/flipcash/protos/src/main/proto/activity/v1/model.proto deleted file mode 100644 index 1b92ef18b7..0000000000 --- a/definitions/flipcash/protos/src/main/proto/activity/v1/model.proto +++ /dev/null @@ -1,158 +0,0 @@ -syntax = "proto3"; - -package flipcash.activity.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/activity/v1;activitypb"; -option java_package = "com.codeinc.flipcash.gen.activity.v1"; -option objc_class_prefix = "FCPBActivityV1"; - -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -// The ID of the notification -message NotificationId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; -} - -// Notification is a message that is displayed in an activity feed -message Notification { - // The ID of this notification - NotificationId id = 1 [(validate.rules).message.required = true]; - - // The localized title text for the notification - string localized_text = 2 [(validate.rules).string = { - min_len: 1 - max_len: 256 - }]; - - // If a payment applies, the amount that was paid - // - // Note: For multi-mint operations, amounts are carried in additional_metadata - // (eg. swapped_crypto). - common.v1.CryptoPaymentAmount payment_amount = 3; - - // The timestamp of this notification - google.protobuf.Timestamp ts = 4 [(validate.rules).timestamp.required = true]; - - // The state of this notification - NotificationState state = 5 [(validate.rules).enum.not_in = 0]; - - // Additional metadata for this notification specific to the notification - oneof additional_metadata { - DirectlySentCryptoNotificationMetadata directly_sent_crypto = 7; - ReceivedCryptoNotificationMetadata received_crypto = 8; - WithdrewCryptoNotificationMetadata withdrew_crypto = 9; - IndirectlySentCryptoNotificationMetadata indirectly_sent_crypto = 10; - DepositedCryptoNotificationMetadata deposited_crypto = 11; - BoughtCryptoNotificationMetadata bought_crypto = 12 [deprecated = true]; - SoldCryptoNotificationMetadata sold_crypto = 13 [deprecated = true]; - SwappedCryptoNotificationMetadata swapped_crypto = 14; - } - - reserved 6; // Deprecated WelcomeBonusNotificationMetadata - - // Ordered substitutions to apply to localized_text - repeated common.v1.Substitution text_substitutions = 100; -} - -message DirectlySentCryptoNotificationMetadata { - oneof destination_identifier { - common.v1.PhoneNumber phone = 1; - common.v1.UserId user_id = 2; - } -} - -message ReceivedCryptoNotificationMetadata { - oneof source_identifier { - common.v1.PhoneNumber phone = 1; - common.v1.UserId user_id = 2; - } -} - -message WithdrewCryptoNotificationMetadata { - // Deprecated in favour of swap_metadata - SwapState swap_state = 1 [(validate.rules).enum.not_in = 0]; - - // When a withdraw is a swap, the metadata for that swap - SwappedCryptoNotificationMetadata swap_metadata = 2; -} - -message IndirectlySentCryptoNotificationMetadata { - // The vault of the gift card account that was created for the cash link - common.v1.PublicKey vault = 1 [(validate.rules).message.required = true]; - - // Whether the cancel action can be initiated by the user - bool can_initiate_cancel_action = 2; -} - -message DepositedCryptoNotificationMetadata { -} - -// Deprecated: Use SwappedCryptoNotificationMetadata, which models both halves -// of the swap in a single notification. -message BoughtCryptoNotificationMetadata { - SwapState swap_state = 1 [(validate.rules).enum.not_in = 0]; -} - -// Deprecated: Use SwappedCryptoNotificationMetadata, which models both halves -// of the swap in a single notification. -message SoldCryptoNotificationMetadata { - SwapState swap_state = 1 [(validate.rules).enum.not_in = 0]; -} - -// SwappedCryptoNotificationMetadata represents a swap between two mints as a -// single notification. It supersedes BoughtCryptoNotificationMetadata and -// SoldCryptoNotificationMetadata, which modelled the two halves of a swap as -// separate notifications. -message SwappedCryptoNotificationMetadata { - // The amount the user gave up in the source mint - common.v1.CryptoPaymentAmount from = 1 [(validate.rules).message.required = true]; - - // What the user received in the destination mint. The mint is always known, - // but the amount is only known once the swap has executed. - oneof to { - option (validate.required) = true; - - // The destination mint, when the amount isn't yet known - common.v1.PublicKey to_mint = 2; - - // The amount the user received in the destination mint - common.v1.CryptoPaymentAmount to_amount = 3; - } - - // The fee charged for the swap, which is known upfront and is set regardless - // of the state of the swap - common.v1.FiatPaymentAmount fee = 4 [(validate.rules).message.required = true]; - - // The state of the swap as a whole - SwapState swap_state = 5 [(validate.rules).enum.not_in = 0]; -} - -// ActivityFeedType enables multiple activity feeds, where notifications may be -// split across different parts of the app -enum ActivityFeedType { - UNKNOWN = 0; - TRANSACTION_HISTORY = 1; // Activity feed displayed under the Balance tab -} - -// NotificationState determines the mutability of a notification, and whether -// client should attempt to refetch state. -enum NotificationState { - NOTIFICATION_STATE_UNKNOWN = 0; - // Notification state will change based on some app action in the future - NOTIFICATION_STATE_PENDING = 1; - // Notification state will not change - NOTIFICATION_STATE_COMPLETED = 2; -} - -enum SwapState { - SWAP_STATE_UNKNOWN = 0; - SWAP_STATE_PENDING = 1; - SWAP_STATE_SUCCEEDED = 2; - SWAP_STATE_FAILED = 3; - SWAP_STATE_NONE = 4; -} \ No newline at end of file diff --git a/definitions/flipcash/protos/src/main/proto/blob/v1/blob_storage_service.proto b/definitions/flipcash/protos/src/main/proto/blob/v1/blob_storage_service.proto deleted file mode 100644 index 7e64f23cd6..0000000000 --- a/definitions/flipcash/protos/src/main/proto/blob/v1/blob_storage_service.proto +++ /dev/null @@ -1,152 +0,0 @@ -syntax = "proto3"; - -package flipcash.blob.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/blob/v1;blobpb"; -option java_package = "com.codeinc.flipcash.gen.blob.v1"; -option objc_class_prefix = "FPBBlobV1"; - -import "blob/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -// BlobStorage manages direct-to-storage uploads and authorized, time-limited reads -// of the bytes behind MediaItem renditions (and other blobs). Clients upload bytes -// straight to object storage via a presigned target — the server never proxies -// them — and all blob metadata is server-derived from the stored bytes. -service BlobStorage { - // GetUploadPolicy returns the current upload constraints — which MIME types - // are accepted and the per-type ceilings the server enforces — so the client - // can validate (and resize/transcode) BEFORE reserving an upload. The policy - // is advisory and cacheable; InitiateExternalUpload remains authoritative and - // may still deny. Clients re-fetch when version changes or ttl lapses. - rpc GetUploadPolicy(GetUploadPolicyRequest) returns (GetUploadPolicyResponse); - - // InitiateExternalUpload reserves a BlobId and returns a short-lived presigned - // target the client uploads the bytes to directly. Clients only ever upload - // ORIGINALs; the server derives any additional renditions itself. - rpc InitiateExternalUpload(InitiateExternalUploadRequest) returns (InitiateExternalUploadResponse); - - // CompleteExternalUpload is an ADVISORY signal that the client finished uploading, - // letting the server finalize (validate, derive metadata, transcode - // renditions, moderate) without waiting for the storage-completion event. - // It is idempotent; if never called, the storage event finalizes the blob - // anyway. Clients must not depend on it for correctness. - rpc CompleteExternalUpload(CompleteExternalUploadRequest) returns (CompleteExternalUploadResponse); - - // GetBlobs resolves known BlobIds to their current status and metadata, - // minting a FRESH, short-lived download_url for each READY blob. Clients - // call it to reissue a URL that has expired — the BlobId is the durable - // handle; the URL is disposable. A caller must set GetBlobsRequest.context - // to the surface it is reading from (e.g. a chat) to authorize blobs it - // does not own. - rpc GetBlobs(GetBlobsRequest) returns (GetBlobsResponse); -} - -message GetUploadPolicyRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; -} - -message GetUploadPolicyResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - // The constraints in force for the caller. Set when result == OK. - UploadPolicy policy = 2; -} - -message InitiateExternalUploadRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // Declared MIME type of the bytes the client intends to upload. The server - // pins it into the signed upload policy (storage rejects a mismatched - // Content-Type) and re-derives it authoritatively from the bytes after - // upload. A hint that constrains the upload, never blindly trusted. - string mime_type = 2 [(validate.rules).string = { - min_len: 1 - max_len: 255 - }]; - - // Declared byte size. The server bakes it into the policy's - // content-length-range so storage rejects an upload that exceeds it. - uint64 size_bytes = 3 [(validate.rules).uint64.gte = 1]; -} - -message InitiateExternalUploadResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - UNSUPPORTED_TYPE = 2; // MIME type not accepted - TOO_LARGE = 3; // declared size over the per-type ceiling - QUOTA_EXCEEDED = 4; // caller is over their storage/upload quota - } - - // Server-assigned handle. On OK the client references this as the ORIGINAL - // rendition's blob_id on SendMessage. - BlobId blob_id = 2; - - // Where and how to upload the bytes. Set only when result == OK. - UploadTarget upload_target = 3; - - // On a policy-driven denial (UNSUPPORTED_TYPE / TOO_LARGE), the UploadPolicy - // version in force, so the client can detect a stale cached policy and - // re-fetch. Unset otherwise. - PolicyVersion policy_version = 4; -} - -message CompleteExternalUploadRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // The blob whose upload just finished. - BlobId blob_id = 2 [(validate.rules).message.required = true]; -} - -message CompleteExternalUploadResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; // no pending blob with this id for the caller - NOT_UPLOADED = 2; // bytes not present / upload incomplete - } - - // Blob state after this call (often still PROCESSING). - BlobStatus status = 2; - - // Why the blob was rejected. Set only when status == REJECTED. - RejectionMetadata rejection_metadata = 3; -} - -message GetBlobsRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // The blobs to resolve. - BlobIdBatch blob_ids = 2 [(validate.rules).message.required = true]; - - // The surface the caller is accessing these blobs from. When set, the - // server authorizes the entire batch through this single context, turning - // the read into a direct membership check instead of a search across the - // blob's grants. - // - // OPTIONAL on the wire, but REQUIRED in practice for any blob the caller - // does not OWN: blobs the caller owns resolve without a context, while a - // non-owned id is treated as unauthorized (and omitted from the response) - // unless a context that authorizes it is supplied. A caller reading only - // its own blobs may omit it, so existing owner-only clients are unaffected. - AccessContext context = 3; -} - -message GetBlobsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - // The resolved blobs. Unknown or unauthorized ids are omitted; the batch - // is left unset (not empty) when none resolve. - BlobBatch blobs = 2; -} diff --git a/definitions/flipcash/protos/src/main/proto/blob/v1/model.proto b/definitions/flipcash/protos/src/main/proto/blob/v1/model.proto deleted file mode 100644 index af8c41c181..0000000000 --- a/definitions/flipcash/protos/src/main/proto/blob/v1/model.proto +++ /dev/null @@ -1,326 +0,0 @@ -syntax = "proto3"; - -package flipcash.blob.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/blob/v1;blobpb"; -option java_package = "com.codeinc.flipcash.gen.blob.v1"; -option objc_class_prefix = "FPBBlobV1"; - -import "common/v1/common.proto"; -import "moderation/v1/model.proto"; -import "google/protobuf/duration.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -// Opaque, client-held handle to a stored blob. This is the durable identity for -// the bytes; the bytes it points at are immutable once the upload is finalized. -message BlobId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 16 - }]; -} - -// A batch of BlobIds -message BlobIdBatch { - repeated BlobId blob_ids = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -// Lifecycle state of a blob. -enum BlobStatus { - BLOB_STATUS_UNKNOWN = 0; - BLOB_STATUS_PENDING = 1; // reserved; awaiting the client's upload - BLOB_STATUS_PROCESSING = 2; // bytes present; deriving metadata / transcoding / moderating - BLOB_STATUS_READY = 3; // available; metadata populated and moderation passed - BLOB_STATUS_REJECTED = 4; // failed validation or moderation; not servable -} - -// A blob's current status, plus its metadata once READY — or, once REJECTED, -// the reason it was rejected. -message Blob { - BlobId id = 1 [(validate.rules).message.required = true]; - - BlobStatus status = 2; - - // Server-authoritative metadata, including a freshly minted download_url. - // Set only when status == READY. - BlobMetadata metadata = 3; - - // Why the blob was rejected. Set only when status == REJECTED. - RejectionMetadata rejection = 4; -} - -// A batch of Blobs. -message BlobBatch { - repeated Blob blobs = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -// Server-authoritative metadata describing a stored blob. Never set by clients. -// With the exception of download_url, every field is intrinsic to the stored -// bytes and immutable, derived once by the server. -message BlobMetadata { - // MIME type (e.g. "image/jpeg"). - string mime_type = 1 [(validate.rules).string = { - min_len: 1 - max_len: 255 - }]; - - // Total size of the blob in bytes. - uint64 size_bytes = 2 [(validate.rules).uint64.gte = 1]; - - // Ephemeral, server-minted URL for fetching the blob bytes, together with - // the instant it expires. Re-issued on every fetch — see DownloadUrl. - DownloadUrl download_url = 3 [(validate.rules).message.required = true]; - - // Kind-specific metadata the server derived from the bytes. Exactly one - // variant is set for a recognized media kind; left unset for opaque blobs. - // Only images are supported today; video/audio/etc. will be added as new - // variants. - oneof kind { - ImageMetadata image = 4; - } -} - -// Intrinsic descriptors for a still image. -message ImageMetadata { - // Pixel dimensions, for reserving layout before the bytes arrive. - uint32 width = 1 [(validate.rules).uint32.gte = 1]; - uint32 height = 2 [(validate.rules).uint32.gte = 1]; - - // Compact preview shown while the full image downloads (BlurHash string). - string blurhash = 3 [(validate.rules).string.max_len = 64]; -} - -// One logical piece of media — a chat image, a profile picture — carried as the -// set of renditions it is stored as. Distinct from Blob: a Blob is ONE stored -// object, while a Media is the several stored objects that together represent -// the same content at different qualities/sizes. -// -// Wherever a surface attaches media, it embeds this type: the client uploads a -// single ORIGINAL and the server derives the rest, identically everywhere. -message Media { - // The renditions of this media, each an independently-stored blob. When a - // client attaches media (e.g. SendMessage, SetProfilePicture) it supplies - // exactly one ORIGINAL rendition (its blob_id); the server fills that - // rendition's metadata and appends any derived renditions (e.g. a - // downscaled DISPLAY and a THUMBNAIL). - repeated Rendition renditions = 1 [(validate.rules).repeated = { - min_items: 1 - }]; -} - -// A single stored variant of a Media. -message Rendition { - // The intended use of this rendition within the media. - Role role = 1 [(validate.rules).enum = { - not_in: [0] - }]; - enum Role { - UNKNOWN = 0; - ORIGINAL = 1; // full-quality source the client uploaded - DISPLAY = 2; // downscaled/compressed for inline display - THUMBNAIL = 3; // tiny preview (grid cell, avatar, ...) - } - - // Handle to the blob holding this rendition's bytes. Client-set on the - // ORIGINAL when attaching the media; server-set for derived renditions. - BlobId blob_id = 2 [(validate.rules).message.required = true]; - - // Server-authoritative blob metadata (mime type, size, download URL, and - // the image dimensions/preview), resolved from the blob record. Omitted on - // the request that attaches the media and populated on returned copies. - // - // If unavailable at the time the media is retrieved, the client can use - // GetBlobs to query for the blob metadata. - BlobMetadata blob = 3; -} - -// The constraints the server enforces on uploads, surfaced so clients can -// validate and resize/transcode before reserving an upload. Advisory and -// cacheable; InitiateExternalUpload remains authoritative. Constraints can -// depend on the caller (quota, tier), so this is fetched per-user, not static. -message UploadPolicy { - // Opaque generation token for this policy. Clients cache the policy under it - // and re-fetch when they observe a different version — including one echoed - // on a denied upload. - PolicyVersion version = 1 [(validate.rules).message.required = true]; - - // How long the client may rely on this policy before re-fetching. The client - // should also refresh on any version mismatch, whichever comes first. - google.protobuf.Duration ttl = 2 [(validate.rules).duration.required = true]; - - // Per-MIME-type constraints, ordered MOST SPECIFIC FIRST. The client picks - // the FIRST entry whose mime_type_pattern matches the bytes' declared MIME - // type; later entries (e.g. "image/*", then "*/*") act as fallbacks. An - // upload whose type matches no entry is not accepted. - repeated MimeTypeConstraints mime_type_constraints = 3 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 - }]; -} - -// Opaque generation token identifying a snapshot of an UploadPolicy. Compared -// by equality, never parsed; clients cache the policy under it and re-fetch when -// they observe a different value (including one echoed on a denied upload). -message PolicyVersion { - string value = 1 [(validate.rules).string = { - min_len: 1 - max_len: 256 - }]; -} - -// Upload constraints for one MIME-type matcher. -message MimeTypeConstraints { - // The MIME type(s) this entry governs: an exact type ("image/jpeg"), a - // subtype wildcard ("image/*"), or the catch-all "*/*". - string mime_type_pattern = 1 [(validate.rules).string = { - min_len: 3 - max_len: 255 - }]; - - // Hard ceiling on a matching blob's byte size. - uint64 max_size_bytes = 2 [(validate.rules).uint64.gte = 1]; - - // Kind-specific bounds, mirroring BlobMetadata.kind. Set the variant for the - // pattern's media kind; left unset for opaque blobs. - oneof kind { - ImageConstraints image = 3; - } -} - -// Bounds on a still image, mirroring ImageMetadata. -message ImageConstraints { - // Max pixel dimensions the server will accept (or downscale to). - uint32 max_width = 1 [(validate.rules).uint32.gte = 1]; - uint32 max_height = 2 [(validate.rules).uint32.gte = 1]; - - // Max total pixel count (width * height), guarding against decompression - // bombs that slip under the per-axis caps. - uint64 max_pixels = 3 [(validate.rules).uint64.gte = 1]; -} - -// A short-lived, presigned target for a direct-to-storage upload. Bearer -// credential: anyone holding it can upload to the reserved key until it expires -// — the client must not share or persist it. -// -// Provider-agnostic by design: it describes the HTTP request the client must -// make, with all signed material opaque to the client. Today the server mints -// S3 POST policies (method POST with form_fields); the same shape also -// expresses presigned PUT (S3/GCS) and Azure SAS without a contract change. -message UploadTarget { - // How the client issues the upload request. - Method method = 1 [(validate.rules).enum = { - not_in: [0] - }]; - enum Method { - UNKNOWN = 0; - PUT = 1; // raw-body upload - POST = 2; // multipart/form-data carrying form_fields - } - - // Signed URL to send the request to. For PUT-style uploads this often - // already carries the signature in its query string (presigned PUT, SAS). - string url = 2 [(validate.rules).string = { - uri: true - max_len: 2048 - }]; - - // Headers the client MUST send verbatim (e.g. Content-Type, x-ms-blob-type, - // and any signed headers the presign requires). - map headers = 3; - - // For method == POST: multipart/form-data fields sent before the file part, - // carrying the server-chosen key and the SIGNED policy that storage enforces - // (Content-Type, content-length-range, key prefix, ...). The client cannot - // alter them without invalidating the signature. Empty for PUT uploads. - map form_fields = 4; - - // When the target expires; after this the client must call InitiateUpload - // again for a fresh one. - google.protobuf.Timestamp expires_at = 5 [(validate.rules).timestamp.required = true]; -} - -// An ephemeral, server-minted URL for fetching the blob bytes, paired with the -// instant it expires. Unlike the intrinsic blob metadata, the URL is NOT a -// property of the bytes: it is re-issued on every fetch, expires (signed URL -// with a short TTL), and is authorized at mint time, not at fetch time. Clients -// MUST NOT persist or cache it across fetches; treat the BlobId as the durable -// handle and this as disposable. -message DownloadUrl { - // Signed URL for fetching the blob bytes. - string url = 1 [(validate.rules).string = { - uri: true - max_len: 2048 - }]; - - // When the URL expires; after this the client must call GetBlobs again to - // mint a fresh one. - google.protobuf.Timestamp expires_at = 2 [(validate.rules).timestamp.required = true]; -} - -// Explains why a blob was rejected during finalization. Set on a Blob only when -// status == REJECTED. Rejection is TERMINAL: the bytes behind a BlobId are -// immutable, so this id will never become READY — to try again the client must -// reserve a fresh upload via InitiateExternalUpload. -message RejectionMetadata { - // The top-level reason finalization failed. - RejectionReason reason = 1 [(validate.rules).enum = { - not_in: [0] - }]; - - // The best-fit category that tripped moderation, mirroring the Moderation - // service's vocabulary. Set only when reason == REJECTION_REASON_MODERATION; - // NONE otherwise. - moderation.v1.FlaggedCategory flagged_category = 2; -} - -// Why a blob failed finalization, after its bytes were uploaded. Distinct from -// the pre-upload denials in InitiateExternalUploadResponse.Result, which reject -// before any bytes are stored. -enum RejectionReason { - REJECTION_REASON_UNKNOWN = 0; - REJECTION_REASON_MODERATION = 1; // tripped content moderation; see flagged_category - REJECTION_REASON_UNSUPPORTED_TYPE = 2; // MIME type derived from the bytes is not accepted - REJECTION_REASON_MISMATCHED_TYPE = 3; // derived type didn't match the declared mime_type - REJECTION_REASON_TOO_LARGE = 4; // stored bytes exceeded the per-type ceiling - REJECTION_REASON_CORRUPT = 5; // bytes unreadable; failed to decode or transcode - REJECTION_REASON_INTERNAL = 6; // server-side processing error - REJECTION_REASON_PRIVACY_METADATA = 7; // blob contains privacy metadata that was not stripped -} - -// AccessContext names the surface a caller is accessing blobs THROUGH on a -// request — the "place" the read is happening from. It is the extension point -// for blob authorization contexts: each scope maps to a server-side membership -// check (e.g. a chat scope authorizes the caller iff they belong to that chat -// AND the blob was shared into it). -// -// It is a hint that can only ever NARROW a read, never widen it: the server -// still independently verifies both that the blob is granted to the named scope -// and that the caller belongs to it. A caller never needs a context to read -// blobs it OWNS, but does need one for any blob it does not own. New surfaces (a -// feed, a profile, a public link) are added as new arms of `scope` without -// changing any request plumbing. -message AccessContext { - oneof scope { - option (validate.required) = true; - - // The caller is accessing these blobs from within this chat. Authorized - // iff the caller is a member of the chat and the blob was shared into it. - common.v1.ChatId chat = 1 [(validate.rules).message.required = true]; - - // The caller is accessing these blobs from this user's public profile. - // Authorized iff the blob is a rendition of that user's CURRENT profile - // picture — a profile grants nothing else, and a superseded picture's - // renditions stop resolving through it. - // - // A caller never needs this for its OWN profile picture, since it owns - // those blobs. - common.v1.UserId profile = 2 [(validate.rules).message.required = true]; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/blocklist/v1/blocklist_service.proto b/definitions/flipcash/protos/src/main/proto/blocklist/v1/blocklist_service.proto deleted file mode 100644 index c0ed1ba462..0000000000 --- a/definitions/flipcash/protos/src/main/proto/blocklist/v1/blocklist_service.proto +++ /dev/null @@ -1,116 +0,0 @@ -syntax = "proto3"; - -package flipcash.blocklist.v1; - -import "blocklist/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/blocklist/v1;blocklistpb"; -option java_package = "com.codeinc.flipcash.gen.blocklist.v1"; -option objc_class_prefix = "FPBBlocklistV1"; - -// Blocklist manages the set of users a user has blocked. -service Blocklist { - // BlockUser adds a user to the caller's blocklist. Blocking a user that - // is already blocked is a no-op and returns OK. - rpc BlockUser(BlockUserRequest) returns (BlockUserResponse); - - // UnblockUser removes a user from the caller's blocklist. Unblocking a - // user that isn't blocked is a no-op and returns OK. - rpc UnblockUser(UnblockUserRequest) returns (UnblockUserResponse); - - // IsBlocked checks whether a user is on the caller's blocklist. - rpc IsBlocked(IsBlockedRequest) returns (IsBlockedResponse); - - // GetBlocklist gets the caller's blocklist using a paged API, ordered by - // most recently blocked first. - rpc GetBlocklist(GetBlocklistRequest) returns (GetBlocklistResponse); -} - -message BlockUserRequest { - // The user to block - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message BlockUserResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - // The user to block doesn't exist - USER_NOT_FOUND = 2; - // Users cannot block themselves - CANNOT_BLOCK_SELF = 3; - } -} - -message UnblockUserRequest { - // The user to unblock - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message UnblockUserResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} - -message IsBlockedRequest { - // The user to check against the caller's blocklist - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message IsBlockedResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - // Whether the user is on the caller's blocklist. Set when result is OK. - bool is_blocked = 2; -} - -message GetBlocklistRequest { - // QueryOptions controls page_size. Ordering is fixed to most recently - // blocked first and is not client-selectable. - // - // Leave query_options.paging_token unset on the first request. On every - // subsequent request, set query_options.paging_token to the paging_token - // from the most recent response to advance to the next page. The token is - // opaque and server-generated; do not construct it. - common.v1.QueryOptions query_options = 1; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetBlocklistResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - repeated BlockedUser blocked_users = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 100 - }]; - - // PagingToken is the server-generated cursor for this paginated read. The - // client MUST send the most recent value back in query_options.paging_token - // on the next GetBlocklistRequest. Set when result is OK. - common.v1.PagingToken paging_token = 3; - - // HasMore indicates whether further pages remain. When true, the client - // should issue another GetBlocklistRequest with the returned paging_token. - bool has_more = 4; -} diff --git a/definitions/flipcash/protos/src/main/proto/blocklist/v1/model.proto b/definitions/flipcash/protos/src/main/proto/blocklist/v1/model.proto deleted file mode 100644 index d3a76426e3..0000000000 --- a/definitions/flipcash/protos/src/main/proto/blocklist/v1/model.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; - -package flipcash.blocklist.v1; - -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/blocklist/v1;blocklistpb"; -option java_package = "com.codeinc.flipcash.gen.blocklist.v1"; -option objc_class_prefix = "FPBBlocklistV1"; - -// BlockedUser is a single entry in a user's blocklist -message BlockedUser { - // The user that is blocked - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - // Timestamp when the user was blocked - google.protobuf.Timestamp blocked_at = 2 [(validate.rules).timestamp.required = true]; -} diff --git a/definitions/flipcash/protos/src/main/proto/chat/v1/chat_service.proto b/definitions/flipcash/protos/src/main/proto/chat/v1/chat_service.proto deleted file mode 100644 index eb8ea80195..0000000000 --- a/definitions/flipcash/protos/src/main/proto/chat/v1/chat_service.proto +++ /dev/null @@ -1,106 +0,0 @@ -syntax = "proto3"; - -package flipcash.chat.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/chat/v1;chatpb"; -option java_package = "com.codeinc.flipcash.gen.chat.v1"; -option objc_class_prefix = "FPBChatV1"; - -import "chat/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -service Chat { - // GetChat returns the metadata for a specific chat - rpc GetChat(GetChatRequest) returns (GetChatResponse); - - // GetDmChatFeed gets the set of DM chats for an owner account using - // a paged API, ordered by last activity with the most recent first. - // - // Chats are ordered by a mutable key (last_activity), so pagination alone - // cannot guarantee a complete read: a chat can receive new activity and - // move into a region the client has already paged past. To get the full - // list, the client MUST combine this RPC with the event stream: - // - // 1. Open the event stream to receive ChatUpdate and begin buffering updates - // BEFORE the first GetDmChatFeed call. This ordering is the contract that - // closes the gap; subscribing after pagination starts can drop chats. - // 2. Page through GetDmChatFeed to exhaustion (until has_more is false), - // always echoing back the paging token returned by the prior response. - // All pages are served against a single snapshot pinned by that token, - // so the set is read consistently. - // 3. Merge the buffered and ongoing stream updates onto the paginated - // set. Any chat whose activity changed after the snapshot watermark - // is delivered via the stream rather than via pagination. - // - // Read together, pagination guarantees the set (every chat exactly once) - // and the stream guarantees freshness and ordering. The local last_activity - // sort is maintained by the client from the stream after the initial read. - rpc GetDmChatFeed(GetDmChatFeedRequest) returns (GetDmChatFeedResponse); -} - -message GetChatRequest { - common.v1.ChatId chat_id = 1; - - common.v1.Auth auth = 10; -} - -message GetChatResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - } - - Metadata metadata = 2; -} - -message GetDmChatFeedRequest { - // QueryOptions controls page_size. Ordering is fixed to most-recent - // activity first and is not client-selectable. - // - // Leave query_options.paging_token unset on the first request: the server - // mints a token that pins a new snapshot and returns it in the response. On - // every subsequent request, set query_options.paging_token to the - // paging_token from the most recent response to advance within the same - // snapshot. The token is opaque and server-generated; do not construct it. - common.v1.QueryOptions query_options = 1; - - // The type of DM chat to filter for - // - // For backwards compatiblity, UNKNOWN maps to CONTACT_DM for legacy clients - ChatType dm_chat_type = 2 [(validate.rules).enum = { - in: [0, 1, 2] // UNKNOWN, CONTACT_DM, TIP_DM - }]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetDmChatFeedResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - } - - repeated Metadata chats = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 100 - }]; - - // PagingToken is the server-generated token for this paginated read. On the - // first response it pins a new snapshot; on later responses it carries the - // advanced cursor over (last_activity, chat_id). The client MUST send the - // most recent value back in query_options.paging_token on the next - // GetDmChatFeedRequest. Set when result is OK. - common.v1.PagingToken paging_token = 3; - - // HasMore indicates whether further pages remain in this snapshot. When - // false, the paginated set has been fully read; the complete chat list is - // this set reconciled with the event stream (see GetDmChatFeed). When true, the - // client should issue another GetDmChatFeedRequest with the returned - // paging_token. - bool has_more = 4; -} diff --git a/definitions/flipcash/protos/src/main/proto/chat/v1/model.proto b/definitions/flipcash/protos/src/main/proto/chat/v1/model.proto deleted file mode 100644 index d654e684b9..0000000000 --- a/definitions/flipcash/protos/src/main/proto/chat/v1/model.proto +++ /dev/null @@ -1,98 +0,0 @@ -syntax = "proto3"; - -package flipcash.chat.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/chat/v1;chatpb"; -option java_package = "com.codeinc.flipcash.gen.chat.v1"; -option objc_class_prefix = "FPBChatV1"; - -import "common/v1/common.proto"; -import "profile/v1/model.proto"; -import "messaging/v1/model.proto"; -import "validate/validate.proto"; -import "google/protobuf/timestamp.proto"; - -enum ChatType { - UNKNOWN = 0; - CONTACT_DM = 1; - TIP_DM = 2; - GROUP = 3; -} - -message Metadata { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - // The type of chat - ChatType type = 2 [(validate.rules).enum = { - not_in: [0] // UNKNOWN - }]; - - // Members of this chat - // - // For large group chats, this is a subset of all members. - repeated Member members = 3; - - // The last message in this chat - messaging.v1.Message last_message = 4; - - // The timestamp of the last activity in this chat - google.protobuf.Timestamp last_activity = 5 [(validate.rules).timestamp.required = true]; - - // The chat's head event sequence — the value of the most recent event in its - // event log. A client compares this against its locally stored cursor for - // the chat to decide whether catch-up is needed: if its cursor is behind, it - // calls Messaging.GetDelta; if equal, it is current and can skip it. - // - // This is NOT derivable from last_message: an edit or deletion of an older - // message advances the head without changing last_message, so this value can - // exceed last_message.event_sequence. It is the same head reported by - // GetDeltaResponse.latest_sequence. - uint64 latest_event_sequence = 6; - - // Whether this chat is hidden from the requesting owner's chat list. - // Per-viewer and server-computed (e.g. the chat's peer is on the caller's - // blocklist). Clients should exclude hidden chats from the primary DM list. - bool is_hidden = 7; - - // Title for this chat. Only supported for group chats - string title = 8 [(validate.rules).string = { - min_len: 0 - max_len: 64 - }]; -} - -message Member { - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - // The user profile for this member. It contains a subset of identifiers - // that can be publicly viewed within the chat. - profile.v1.UserProfile user_profile = 2 [(validate.rules).message.required = true]; - - // Chat message state for this member. - // - // If set, the list may contain DELIVERED and READ pointers. SENT pointers - // are only shared between the sender and server, to indicate persistence. - repeated messaging.v1.Pointer pointers = 3 [(validate.rules).repeated = { - min_items: 0 - max_items: 2 - }]; -} - -message MetadataUpdate { - oneof kind { - option (validate.required) = true; - - FullRefresh full_refresh = 1; - LastActivityChanged last_activity_changed = 2; - } - - // Refreshes the entire chat metadata - message FullRefresh { - Metadata metadata = 1 [(validate.rules).message.required = true]; - } - - // The last activity timestamp has changed to a newer value - message LastActivityChanged { - google.protobuf.Timestamp new_last_activity = 1 [(validate.rules).timestamp.required = true]; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/common/v1/common.proto b/definitions/flipcash/protos/src/main/proto/common/v1/common.proto deleted file mode 100644 index 21d46b01a8..0000000000 --- a/definitions/flipcash/protos/src/main/proto/common/v1/common.proto +++ /dev/null @@ -1,235 +0,0 @@ -syntax = "proto3"; - -package flipcash.common.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/common/v1;commonpb"; -option java_package = "com.codeinc.flipcash.gen.common.v1"; -option objc_class_prefix = "FPBCommonV1"; - -// Note: common/v1 is imported by every other domain, so it must remain a leaf -// package. Importing another flipcash domain here creates a Go import cycle, -// since that domain's service protos import common/v1 in turn. -import "validate/validate.proto"; - -message PublicKey { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; -} - -message Signature { - bytes value = 1 [(validate.rules).bytes = { - min_len: 64 - max_len: 64 - }]; -} - -message Hash { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; -} - -// Auth provides an authentication information for RPCs/messages. -// -// Currently, only a single form is supported, but it may be useful in -// the future to rely on session tokens instead. -message Auth { - oneof kind { - option (validate.required) = true; - - // KeyPair uses pub key cryptography to verify. - KeyPair key_pair = 1; - } - - // KeyPair uses a keypair to verify a message. - // - // The signature should be of the encapsulating proto message, - // _without_ the Auth section being set. - message KeyPair { - PublicKey pub_key = 1 [(validate.rules).message.required = true]; - Signature signature = 2 [(validate.rules).message.required = true]; - } -} - -message UserId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 1 - max_len: 32 - }]; -} - -// Username is a user's unique handle on Flipcash. It uses the same character -// set as X — letters, digits and underscores — with the exception that it must -// be lowercase. -message Username { - string value = 1 [(validate.rules).string.pattern = "^[a-z0-9_]{2,15}$"]; -} - -message ChatId { - // value has the following structure: - // - 32 byte hash for DMs - // - 16 byte UUID for group chats - bytes value = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 32 - }]; -} - -message IntentId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; -} - -// AppInstallId is a unque ID tied to a client app installation. It does not -// identify a device. Value should remain private and not be shared across -// installs. -message AppInstallId { - string value = 1 [(validate.rules).string = { - min_len: 1 - max_len: 256 // todo: What's a reasonable size - }]; -} - -// PhoneNumber is an E.164 phone number -message PhoneNumber { - // Regex provided by Twilio here: https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164 - string value = 1 [(validate.rules).string.pattern = "^\\+[1-9]\\d{1,14}$"]; -} - -// EmailAddress is an email address -message EmailAddress { - string value = 1 [(validate.rules).string.pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"]; -} - -enum Platform { - UNKNOWN = 0; - APPLE = 1; - GOOGLE = 2; -} - -// CryptoPaymentAmount defines an amount of crypto with currency exchange data -message CryptoPaymentAmount { - // ISO 4217 alpha-3 currency code the payment was made in - string currency = 1 [(validate.rules).string = { pattern: "^[a-z]{3,4}$" }]; - - // The amount in the native currency that was paid - double native_amount = 2 [(validate.rules).double.gte = 0]; - - // The amount in quarks of crypto that was paid - uint64 quarks = 3; - - // The crypto mint that was paid - PublicKey mint = 4 [(validate.rules).message.required = true]; -} - -// FiatPaymentAmount defines an amount of fiat -message FiatPaymentAmount { - // ISO 4217 alpha-3 currency code the payment was made in - string currency = 1 [(validate.rules).string = { pattern: "^[a-z]{3,4}$" }]; - - // The amount in the native currency that was paid - double native_amount = 2 [(validate.rules).double.gte = 0]; -} - -message PagingToken { - bytes value = 1 [(validate.rules).bytes = { - min_len: 1 - max_len: 128 - }]; -} - -message QueryOptions { - // PageSize limits the maximum page size of a response. - // - // Server may choose to return less items. If <= 0, - // server may select an arbitrary default page size. - int32 page_size = 1 [(validate.rules).int32.lte = 1024]; - - // PagingToken is a token that can be extracted from the - // identifier of a collection. - PagingToken paging_token = 2; - - // Order is the order of elements, if applicable. - Order order = 3; - enum Order { - ASC = 0; - DESC = 1; - } -} - -// Request is a generic wrapper for gRPC requests -message Request { - string version = 1; - string service = 2; - string method = 3; - bytes body = 4; -} - -// Response is a generic wrapper for gRPC responses -message Response { - Result result = 1; - - bytes body = 2; - string message = 3; - - enum Result { - OK = 0; - ERROR = 1; - } -} - -message CountryCode { - // ISO 3166-1 Alpha-2 - string value = 1 [(validate.rules).string = { - min_len: 2 - max_len: 2 - }]; -} - -// Locale represents an IETF BCP 47 language tag (e.g. "en", "en-US", "zh-Hans-CN") -message Locale { - string value = 1 [(validate.rules).string = { - pattern: "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$" - min_len: 2 - max_len: 35 - }]; -} - -// Region represents a fiat currency region identified by its ISO 4217 alpha-3 currency code (e.g. "usd", "eur") -message Region { - string value = 1 [(validate.rules).string = { - pattern: "^[a-z]{3,4}$" - }]; -} - -// Color represents an RGB colour -message Color { - // Hex colour value (e.g. "#19191A") - string hex = 1 [(validate.rules).string = { - pattern: "^#[0-9a-fA-F]{6}$" - }]; -} - -// Substitution is a text subsitution -message Substitution { - // Fallback string for forwards compatibility - string fallback = 1 [(validate.rules).string = { - min_len: 1 - max_len: 4096 // Arbitrary - }]; - - oneof kind { - option (validate.required) = true; - - // Phone number -> contact name or formatted phone number - common.v1.PhoneNumber phone_number_to_contact_name = 2; - - // User ID -> display name - common.v1.UserId user_id_to_display_name = 3; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto b/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto deleted file mode 100644 index 5ed665c404..0000000000 --- a/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto +++ /dev/null @@ -1,134 +0,0 @@ -syntax = "proto3"; - -package flipcash.contact.v1; - -import "contact/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/contact/v1;contactpb"; -option java_package = "com.codeinc.flipcash.gen.contact.v1"; -option objc_class_prefix = "FPBContactV1"; - -// ContactList manages a user's contact list and surfaces which contacts are -// Flipcash users. -// -// Sync model: -// - The client maintains a 32-byte XOR-of-SHA256 checksum over its current -// contact set, and an OS-specific cursor for incremental change discovery. -// The cursor is local-only and never leaves the device. -// - Steady state: client computes a delta from the OS cursor, sends it via -// DeltaUpload with old/new checksums for compare-and-swap. -// - Recovery: on first install, after OS history truncation, or after -// CHECKSUM_DRIFT, the client uses FullUpload to replace the server's state -// wholesale. -service ContactList { - // CheckSync compares the client's checksum to the server's. Cheap, used - // on app foreground to decide whether any upload is needed. - rpc CheckSync(CheckSyncRequest) returns (CheckSyncResponse); - - // DeltaUpload applies a delta under compare-and-swap on the checksum. - // Safe to retry indefinitely with the same payload. - rpc DeltaUpload(DeltaUploadRequest) returns (DeltaUploadResponse); - - // FullUpload replaces the user's contact set entirely. Used when - // a delta cannot be constructed or CHECKSUM_DRIFT was returned. - rpc FullUpload(stream FullUploadRequest) returns (FullUploadResponse); - - // GetFlipcashContacts gets the set of contacts that are on Flipcash - rpc GetFlipcashContacts(GetFlipcashContactsRequest) returns (stream GetFlipcashContactsResponse); -} - - -message CheckSyncRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // XOR-of-SHA256 over the client's current set of normalized E.164 phones. - common.v1.Hash client_checksum = 2 [(validate.rules).message.required = true]; -} - -message CheckSyncResponse { - enum Result { - OK = 0; - DENIED = 1; - OUT_OF_SYNC = 2; - } - Result result = 1; - - // Authoritative server-side checksum. Clients persist this and use it - // as the basis for the next DeltaUpload.old_checksum. - common.v1.Hash server_checksum = 2; -} - -message DeltaUploadRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - repeated common.v1.PhoneNumber adds = 2 [(validate.rules).repeated.max_items = 1000]; - - repeated common.v1.PhoneNumber removes = 3 [(validate.rules).repeated.max_items = 1000]; - - // The checksum the client expected the server to have *before* applying - // this delta. Server applies only if stored == old_checksum. - common.v1.Hash old_checksum = 4 [(validate.rules).message.required = true]; - - // The checksum the client computes for the state *after* applying this - // delta. Server persists this on success. Used to detect retries: if - // stored == new_checksum, the server treats the request as a no-op. - common.v1.Hash new_checksum = 5 [(validate.rules).message.required = true]; -} - -message DeltaUploadResponse { - enum Result { - OK = 0; - DENIED = 1; - // Server's recomputed checksum did not match expected_checksum. - CHECKSUM_MISMATCH = 2; - // Stored checksum matched neither old_checksum nor new_checksum. - // Client should call FullUpload to reconcile. - CHECKSUM_DRIFT = 3; - TOO_MANY_CONTACTS = 4; - } - Result result = 1; -} - -message FullUploadRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // The complete current contact set. Server replaces stored state with - // this list in one transaction. - repeated common.v1.PhoneNumber phones = 2 [(validate.rules).repeated.max_items = 1000]; - - // XOR-of-SHA256 over the client's current set of normalized E.164 phones. - // Sent on the last streamed request to indicate the end of the upload. - common.v1.Hash expected_checksum = 3 [(validate.rules).message.required = true]; -} - -message FullUploadResponse { - enum Result { - OK = 0; - DENIED = 1; - // Server's recomputed checksum did not match expected_checksum. - CHECKSUM_MISMATCH = 2; - TOO_MANY_CONTACTS = 3; - } - Result result = 1; -} - -message GetFlipcashContactsRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - common.v1.Hash checksum = 2 [(validate.rules).message.required = true]; -} - -message GetFlipcashContactsResponse { - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - // Server checksum doesn't match client checksum. - CHECKSUM_DRIFT = 3; - } - Result result = 1; - - repeated FlipcashContact contacts = 2 [(validate.rules).repeated.max_items = 1000]; -} diff --git a/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto b/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto deleted file mode 100644 index 32d89ae592..0000000000 --- a/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package flipcash.contact.v1; - -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/contact/v1;contactpb"; -option java_package = "com.codeinc.flipcash.gen.contact.v1"; -option objc_class_prefix = "FPBContactV1"; - -message FlipcashContact { - common.v1.PhoneNumber phone = 1 [(validate.rules).message.required = true]; - - // The DM chat ID for the Flipcash contact. If the chat doesn't exist, it needs - // to be initiated with a cash send to initialize it - common.v1.ChatId dm_chat_id = 2 [(validate.rules).message.required = true]; - - // Timestamp the contact joined Flipcash - google.protobuf.Timestamp join_ts = 3 [(validate.rules).timestamp.required = true]; -} diff --git a/definitions/flipcash/protos/src/main/proto/email/v1/email_verification_service.proto b/definitions/flipcash/protos/src/main/proto/email/v1/email_verification_service.proto deleted file mode 100644 index 9e4662f6bb..0000000000 --- a/definitions/flipcash/protos/src/main/proto/email/v1/email_verification_service.proto +++ /dev/null @@ -1,95 +0,0 @@ -syntax = "proto3"; - -package flipcash.email.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/email/v1;emailpb"; -option java_package = "com.codeinc.flipcash.gen.email.v1"; -option objc_class_prefix = "FPBEmailV1"; - -import "common/v1/common.proto"; -import "email/v1/model.proto"; -import "validate/validate.proto"; - -service EmailVerification { - // SendVerificationCode sends a verification code to the provided email address. - // If an active verification is already taking place, the existing code will be - // resent. - rpc SendVerificationCode(SendVerificationCodeRequest) returns (SendVerificationCodeResponse); - - // CheckVerificationCode validates a verification code. On success, the email - // address is linked to the user. Any previous links are overwritten. - rpc CheckVerificationCode(CheckVerificationCodeRequest) returns (CheckVerificationCodeResponse); - - // Unlink removes the link of an email address from a user. - rpc Unlink(UnlinkRequest) returns (UnlinkResponse); -} - -message SendVerificationCodeRequest { - // The email address to send a verification code to - common.v1.EmailAddress email_address = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; - - // Additional client data that is sent in the deep link - string client_data = 3 [(validate.rules).string = { - max_len: 1024 - }]; -} - -message SendVerificationCodeResponse { - Result result = 1; - enum Result { - OK = 0; - // Email is denied - DENIED = 1; - // Email is rate limited (eg. by IP, email address, user, etc) and was not sent. - RATE_LIMITED = 2; - // The email address is not real - INVALID_EMAIL_ADDRESS = 3; - } -} - -message CheckVerificationCodeRequest { - // The email address being verified - common.v1.EmailAddress email_address = 1 [(validate.rules).message.required = true]; - - // The verification code received via email - VerificationCode code = 2 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 3 [(validate.rules).message.required = true]; -} - -message CheckVerificationCodeResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - // The call is rate limited (eg. by IP, email address, etc). The code is - // not verified. - RATE_LIMITED = 2; - // The provided verification code is invalid. The user may retry - // enterring the code if this is received. When max attempts are - // received, NO_VERIFICATION will be returned. - INVALID_CODE = 3; - // There is no verification in progress for the email address. Several - // reasons this can occur include a verification being expired or having - // reached a maximum check threshold. The client must initiate a new - // verification using SendVerificationCode. - NO_VERIFICATION = 4; - } -} - -message UnlinkRequest { - // The email address to unlink - common.v1.EmailAddress email_address = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message UnlinkResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/email/v1/model.proto b/definitions/flipcash/protos/src/main/proto/email/v1/model.proto deleted file mode 100644 index 71aa2c3055..0000000000 --- a/definitions/flipcash/protos/src/main/proto/email/v1/model.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package flipcash.email.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/email/v1;emailpb"; -option java_package = "com.codeinc.flipcash.gen.email.v1"; -option objc_class_prefix = "FPBEmailV1"; - -import "validate/validate.proto"; - -// VerificationCode is a 4-10 digit numerical code for verification -message VerificationCode { - string value = 2 [(validate.rules).string = { - pattern: "^[0-9]{4,10}$" - }]; -} diff --git a/definitions/flipcash/protos/src/main/proto/event/v1/event_streaming_service.proto b/definitions/flipcash/protos/src/main/proto/event/v1/event_streaming_service.proto deleted file mode 100644 index bc7f0ed2f8..0000000000 --- a/definitions/flipcash/protos/src/main/proto/event/v1/event_streaming_service.proto +++ /dev/null @@ -1,69 +0,0 @@ -syntax = "proto3"; - -package flipcash.event.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/event/v1;eventpb"; -option java_package = "com.codeinc.flipcash.gen.events.v1"; -option objc_class_prefix = "FPBEventV1"; - -import "event/v1/model.proto"; -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -service EventStreaming { - // StreamEvents streams events for the requesting user. - rpc StreamEvents(stream StreamEventsRequest) returns (stream StreamEventsResponse); - - // ForwardEvents is an internal RPC for forwarding events to another server. - rpc ForwardEvents(ForwardEventsRequest) returns (ForwardEventsResponse); -} - -message StreamEventsRequest { - oneof type { - option (validate.required) = true; - - Params params = 1; - ClientPong pong = 2; - } - - message Params { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - // ts contains the time for stream open. - // - // It is used primarily as a nonce for auth. Server may reject - // timestamps that are too far in the future or past. - google.protobuf.Timestamp ts = 2 [(validate.rules).timestamp.required = true]; - } -} - -message StreamEventsResponse { - oneof type { - option (validate.required) = true; - - ServerPing ping = 1; - StreamError error = 2; - EventBatch events = 3; - } - - message StreamError { - Code code = 1; - enum Code { - DENIED = 0; - INVALID_TIMESTAMP = 1; - } - } -} - -message ForwardEventsRequest { - UserEventBatch user_events = 1 [(validate.rules).message.required = true]; -} - -message ForwardEventsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} \ No newline at end of file diff --git a/definitions/flipcash/protos/src/main/proto/event/v1/model.proto b/definitions/flipcash/protos/src/main/proto/event/v1/model.proto deleted file mode 100644 index be33a880d2..0000000000 --- a/definitions/flipcash/protos/src/main/proto/event/v1/model.proto +++ /dev/null @@ -1,132 +0,0 @@ -syntax = "proto3"; - -package flipcash.event.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/event/v1;eventpb"; -option java_package = "com.codeinc.flipcash.gen.events.v1"; -option objc_class_prefix = "FPBEventV1"; - -import "blob/v1/model.proto"; -import "chat/v1/model.proto"; -import "common/v1/common.proto"; -import "messaging/v1/model.proto"; -import "google/protobuf/duration.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -message EventId { - bytes id = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 16 - }]; -} - - // todo: define additional events -message Event { - EventId id = 1 [(validate.rules).message.required = true]; - - google.protobuf.Timestamp ts = 2 [(validate.rules).timestamp.required = true];; - - oneof type { - option (validate.required) = true; - - TestEvent test = 3; - ChatUpdate chat_update = 4; - BlobUpdate blob_update = 5; - } -} - -message EventBatch { - repeated Event events = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; -} - -message UserEvent { - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - Event event = 2 [(validate.rules).message.required = true]; -} - -message UserEventBatch { - repeated UserEvent events = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; -} - -message TestEvent { - repeated string hops = 1; - - uint64 nonce = 2; -} - -message ServerPing { - // Timestamp the ping was sent on the stream, for client to get a sense - // of potential network latency - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; - - // The delay server will apply before sending the next ping - google.protobuf.Duration ping_delay = 2 [(validate.rules).duration.required = true]; -} - -message ClientPong { - // Timestamp the Pong was sent on the stream, for server to get a sense - // of potential network latency - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; -} - -message ChatUpdate { - // The chat that this update is for - common.v1.ChatId chat = 1 [(validate.rules).message.required = true]; - - // If present, new real-time messages sent on the chat. - // - // Deprecated: superseded by `events` (Event.message_sent), which is - // sequenced and gap-detectable. New messages now arrive as events. - messaging.v1.MessageBatch new_messages = 2 [deprecated = true]; - - // If present, message pointer updates for members in the chat. Pointers are - // convergent (monotonic, last-writer-wins), so they ride the stream as a - // best-effort overlay and are reconciled from current state on reconnect — - // they are intentionally NOT part of the gap-detected event log. - messaging.v1.PointerBatch pointer_updates = 3; - - // If present, message typing notification state changes for members in the - // chat. Transient and best-effort — not part of the event log. - messaging.v1.IsTypingNotificationBatch is_typing_notifications = 4; - - // If present, updates to the chat metadata - repeated chat.v1.MetadataUpdate metadata_updates = 5 [(validate.rules).repeated = { - max_items: 1024 // Arbitrary - }]; - - // If present, durable event-log events for the chat (messages sent, edited, - // and deleted). These are contiguous and ordered: clients apply them by - // ascending Event.sequence and gap-detect via Event.sequence/count, catching - // up with Messaging.GetDelta on a gap. This supersedes new_messages. - messaging.v1.EventBatch events = 6; - - // If present, best-effort real-time reaction changes for messages in the - // chat. Like pointer_updates, reactions are a convergent overlay — NOT part - // of the gap-detected event log; clients apply them last-writer-wins by - // ReactionUpdate.sequence and reconcile any misses by refreshing a message's - // ReactionSummary on view. - messaging.v1.ReactionUpdateBatch reaction_updates = 7; -} - -// BlobUpdate notifies the recipient in real time that blobs they uploaded have -// transitioned to a new lifecycle state — e.g. PROCESSING → READY once the -// server finishes validating, transcoding, and moderating, or → REJECTED on -// failure. It lets clients react to upload completion via the event stream -// instead of polling BlobStorage.GetBlobs. -// -// Best-effort: a client that misses an update reconciles by calling -// BlobStorage.GetBlobs. The BlobId is the durable handle; any download_url -// carried here is ephemeral and may be re-minted via GetBlobs. -message BlobUpdate { - // The blobs that transitioned, each carrying its new status and, when READY, - // its resolved metadata (including a freshly minted download_url). - blob.v1.BlobBatch blobs = 1 [(validate.rules).message.required = true]; -} diff --git a/definitions/flipcash/protos/src/main/proto/iap/v1/iap_service.proto b/definitions/flipcash/protos/src/main/proto/iap/v1/iap_service.proto deleted file mode 100644 index fbd0b99cc4..0000000000 --- a/definitions/flipcash/protos/src/main/proto/iap/v1/iap_service.proto +++ /dev/null @@ -1,57 +0,0 @@ -syntax = "proto3"; - -package flipcash.iap.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/iap/v1;iappb"; -option java_package = "com.codeinc.flipcash.gen.iap.v1"; -option objc_class_prefix = "FPBIapV1"; - -import "common/v1/common.proto"; -import "validate/validate.proto"; - -service Iap { - // OnPurchaseCompleted is called when an IAP has been completed - rpc OnPurchaseCompleted(OnPurchaseCompletedRequest) returns (OnPurchaseCompletedResponse); -} - -message OnPurchaseCompletedRequest { - common.v1.Platform platform = 1 [(validate.rules).enum = {in: [1,2]}]; - - Receipt receipt = 2 [(validate.rules).message.required = true]; - - Metadata metadata = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 4 [(validate.rules).message.required = true]; -} - -message OnPurchaseCompletedResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - INVALID_RECEIPT = 2; // Returned if the receipt is invalid, or not in a completed payment state - INVALID_METADATA = 3; // Returned if the at least one field in the payment metadata is invalid - } -} - -message Receipt { - string value = 1 [(validate.rules).string = { - min_len: 1 - // todo: what's a reasonable max length? - }]; -} - -// Additional IAP metadata, which can be trusted given a verified receipt (they can -// only be generated by production-signed apps). -message Metadata { - string product = 1 [(validate.rules).string = { - min_len: 1 - max_len: 128 - }]; - - string currency = 2 [(validate.rules).string = { - pattern: "^[a-z]{3}$" - }]; - - double amount = 3 [(validate.rules).double.gt = 0]; -} diff --git a/definitions/flipcash/protos/src/main/proto/intent/v1/model.proto b/definitions/flipcash/protos/src/main/proto/intent/v1/model.proto deleted file mode 100644 index 6cd69bcdfc..0000000000 --- a/definitions/flipcash/protos/src/main/proto/intent/v1/model.proto +++ /dev/null @@ -1,50 +0,0 @@ -syntax = "proto3"; - -package flipcash.intent.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/intent/v1;intentpb"; -option java_package = "com.codeinc.flipcash.gen.intent.v1"; -option objc_class_prefix = "FPBIntentV1"; - -import "common/v1/common.proto"; -import "validate/validate.proto"; - -message AppMetadata { - oneof domain { - option (validate.required) = true; - - ChatMetadata chat = 1; - } -} - -// Additional metadata provided to SubmitIntent when doing payments in a chat -message ChatMetadata { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - oneof type { - option (validate.required) = true; - - ContactDmPayment contact_dm_payment = 2; - TipDmPayment tip_dm_payment = 3; - } - - // For sending a payment to a contact in a DM - message ContactDmPayment { - // Source phone number that is paying. This is validated to be linked to the sender. - common.v1.PhoneNumber source = 1 [(validate.rules).message.required = true]; - - // Destination phone number that is being paid. This is validated to be linked to the receiver. - common.v1.PhoneNumber destination = 2 [(validate.rules).message.required = true]; - } - - // For sending a DM payment to someone using their user ID, which maps - // directly to/from a public key. - message TipDmPayment { - // Location in the app the payment was sent from - enum Location { - TIPCARD = 0; - CHAT = 1; - } - Location location = 1; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/messaging/v1/messaging_service.proto b/definitions/flipcash/protos/src/main/proto/messaging/v1/messaging_service.proto deleted file mode 100644 index c392a8d13f..0000000000 --- a/definitions/flipcash/protos/src/main/proto/messaging/v1/messaging_service.proto +++ /dev/null @@ -1,484 +0,0 @@ -syntax = "proto3"; - -package flipcash.messaging.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/messaging/v1;messagingpb"; -option java_package = "com.codeinc.flipcash.gen.messaging.v1"; -option objc_class_prefix = "FPBMessagingV1"; - -import "common/v1/common.proto"; -import "messaging/v1/model.proto"; -import "validate/validate.proto"; - -service Messaging { - // GetMessage gets a single message in a chat - rpc GetMessage(GetMessageRequest) returns (GetMessageResponse); - - // GetMessages gets the set of messages for a chat using paged and batched APIs - rpc GetMessages(GetMessagesRequest) returns (GetMessagesResponse); - - // GetDelta returns, for cold-boot and reconnect catch-up, the current state - // of every message changed since the client's cursor, up to the chat's - // current head. It is a state delta, not a contiguous replay: each changed - // message appears once in its latest state and the client applies it - // last-writer-wins. Transient signals (typing) and convergent state - // (pointers, reactions) are fetched separately, not returned here. - // - // GetDelta always catches up to the head; there is no caller-specified - // upper bound. An online client that detects a gap while already receiving - // live updates does NOT bound the fetch: it calls GetDelta to the head and - // lets last-writer-wins (Message.event_sequence) absorb the overlap with - // live events buffered during the call — a message delivered by both paths - // is applied once, newest wins. A client may also wait briefly for an - // out-of-order live update to close a small gap before calling at all. - // - // On stream completion the client advances its cursor to the highest - // checkpoint_sequence it received, which equals latest_sequence — the client - // is now at the head. When the client is already current the server sends a - // single response with messages omitted (and checkpoint_sequence unset), - // leaving the cursor unchanged; latest_sequence still reports the head. - // - // This is a BOUNDED server stream: the server emits one or more batches and - // then completes once the delta up to the head (as of stream open) is - // exhausted. Unlike StreamEvents it does NOT stay open for live updates. - // Streaming the delta in batches avoids a per-page round trip; the server may - // currently send the whole delta as a single response, so clients must handle - // any number of batches and treat stream completion as "caught up." - // - // The Result field is meaningful on the first response and is OK for - // subsequent data batches; a terminal DENIED or RESET_REQUIRED is delivered - // as a single response that ends the stream. - rpc GetDelta(GetDeltaRequest) returns (stream GetDeltaResponse); - - // SendMessage sends a message to a chat. - rpc SendMessage(SendMessageRequest) returns (SendMessageResponse); - - // EditMessage edits the content of a message the caller previously sent. - rpc EditMessage(EditMessageRequest) returns (EditMessageResponse); - - // DeleteMessage deletes a message the caller previously sent. The message is - // tombstoned (content replaced with DeletedContent), not removed, so the - // per-chat MessageId sequence stays gapless. - rpc DeleteMessage(DeleteMessageRequest) returns (DeleteMessageResponse); - - // AddReaction adds the caller's reaction with a given emoji to a message. - // Idempotent: re-adding the same emoji the caller already reacted with is a - // no-op success. - rpc AddReaction(AddReactionRequest) returns (AddReactionResponse); - - // RemoveReaction removes the caller's reaction with a given emoji from a - // message. Idempotent: removing a reaction the caller does not have is a - // no-op success. - rpc RemoveReaction(RemoveReactionRequest) returns (RemoveReactionResponse); - - // GetReactors returns the paged list of users who reacted to a message with - // a given emoji — the on-demand drill-down behind EmojiReaction.count, which - // never inlines the full reactor list. - rpc GetReactors(GetReactorsRequest) returns (GetReactorsResponse); - - // GetReactionSummary fetches the current aggregate reaction state for a - // single message. - rpc GetReactionSummary(GetReactionSummaryRequest) returns (GetReactionSummaryResponse); - - // GetReactionSummaries fetches the current aggregate reaction state using - // paged and batched APIs - rpc GetReactionSummaries(GetReactionSummariesRequest) returns (GetReactionSummariesResponse); - - // AdvancePointer advances a pointer in message history for a chat member. - rpc AdvancePointer(AdvancePointerRequest) returns (AdvancePointerResponse); - - // NotifyIsTypingRequest notifies a chat that the sending member is typing. - // - // These requests are transient, and may be dropped at any point. - rpc NotifyIsTyping(NotifyIsTypingRequest) returns (NotifyIsTypingResponse); -} - -message GetMessageRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10; -} - -message GetMessageResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - } - - Message message = 2; -} - -message GetMessagesRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - oneof query { - option (validate.required) = true; - - common.v1.QueryOptions options = 2; - MessageIdBatch message_ids = 3; - } - - common.v1.Auth auth = 10; -} - -message GetMessagesResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_FOUND = 2; - } - - MessageBatch messages = 2; -} - -message GetDeltaRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - // The latest event sequence the client has already applied. The server - // returns the current state of messages whose event_sequence is greater than - // this value, up to the current head. Use 0 to fetch from the beginning of - // the retained log. - uint64 after_sequence = 2; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetDeltaResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - // after_sequence is older than the oldest state the server can still - // resolve a delta for. The client must discard its cursor and re-sync - // chat history from GetMessages before resuming the event stream. - RESET_REQUIRED = 2; - } - - // A batch of changed messages in STRICTLY ASCENDING event_sequence order, - // continuing in order across batches (every sequence in a batch is higher - // than every sequence in the prior batch). Across the whole stream this is - // the current state of every message changed since after_sequence, up to the - // head; a message normally appears once in its latest state, but one - // re-edited mid-stream may reappear at its new, higher sequence (apply - // last-writer-wins). Omitted (not an empty batch) when there are no changes - // to report — e.g. when the client is already current; the server still - // sets latest_sequence and the stream then completes. Note MessageBatch - // itself requires at least one message, so "no changes" is signaled by - // leaving this field unset, never by an empty batch. - MessageBatch messages = 2; - - // The chat's latest event sequence (head) as of stream open — the target this - // catch-up converges to. Informational while streaming: it tells the client - // how far the chat has advanced before the final batch arrives. Once the - // stream completes, the client's cursor equals this; it does not need - // contiguous coverage of the intervening points, only the resulting state. - uint64 latest_sequence = 3; - - // Resume checkpoint: the event_sequence through which the delta is complete - // as of this batch — the batch's high-water mark, monotonically increasing - // across the stream toward latest_sequence. Persist it AFTER fully applying - // the batch. If the stream drops mid-catch-up, resume by calling GetDelta - // again with after_sequence set to the last checkpoint_sequence received. - // Because event_sequence only ever increases, this resumes exactly where you - // left off with no skipped messages (and at worst a harmless last-writer-wins - // re-apply of the boundary). - uint64 checkpoint_sequence = 4; -} - -message SendMessageRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - // Allowed content types that can be sent by client: - // - TextContent - // - ReplyContent - // - MediaContent - repeated Content content = 2 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; - - // Client-generated idempotency token for this send. Used to dedup retried - // sends and to correlate the optimistic local echo with the server-assigned - // message returned in the response. - ClientMessageId client_message_id = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message SendMessageResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - // The chat message that was sent if the RPC was succesful, which includes - // server-side metadata like the generated message ID and official timestamp - Message message = 2; -} - -message EditMessageRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - // The new content for the message. Allowed content types match SendMessage: - // - TextContent - // - ReplyContent - // - MediaContent - repeated Content content = 3 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; - - // Required optimistic-concurrency guard: the message's event_sequence as the - // client last observed it. The server applies the edit only if the message's - // current event_sequence still equals this value, and returns CONFLICT - // otherwise — so an edit based on a stale version (e.g. a concurrent - // edit/delete from the sender's other device) is rejected rather than - // clobbering the newer state. There is no last-writer-wins path. - uint64 expected_event_sequence = 4 [(validate.rules).uint64.gte = 1]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message EditMessageResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - CANNOT_EDIT = 3; - // The message changed since expected_event_sequence (a concurrent - // edit/delete won). The edit was not applied; `message` carries the - // current state for the client to reconcile against and retry. - CONFLICT = 4; - } - - // On OK, the updated materialized message (advanced event_sequence, - // last_edited_ts set). On CONFLICT, the message's current state. - Message message = 2; -} - -message DeleteMessageRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - // Required optimistic-concurrency guard: the message's event_sequence as the - // client last observed it. The server applies the delete only if the - // message's current event_sequence still equals this value, and returns - // CONFLICT otherwise — so a delete based on a stale version is rejected - // rather than racing a concurrent edit/delete. There is no - // last-writer-wins path. - uint64 expected_event_sequence = 3 [(validate.rules).uint64.gte = 1]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message DeleteMessageResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - CANNOT_DELETE = 3; - // The message changed since expected_event_sequence (a concurrent - // edit/delete won). The delete was not applied; `message` carries the - // current state for the client to reconcile against and retry. - CONFLICT = 4; - } - - // On OK, the tombstoned materialized message (content replaced with - // DeletedContent, event_sequence advanced). On CONFLICT, the current state. - Message message = 2; -} - -message AddReactionRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - // The emoji to react with. - Emoji emoji = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message AddReactionResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - CANNOT_REACT = 3; - // Adding this emoji would exceed the per-message distinct reaction-type - // cap. Reactions to emojis already present on the message are unaffected. - TOO_MANY_REACTION_TYPES = 4; - } - - // The affected emoji's aggregate after the add (count, reacted_by_self true). - EmojiReaction reaction = 2; -} - -message RemoveReactionRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - // The emoji whose reaction to remove for the caller. - Emoji emoji = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message RemoveReactionResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - } - - // The affected emoji's aggregate after the removal - EmojiReaction reaction = 2; -} - -message GetReactorsRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - // The emoji whose reactors to list. - Emoji emoji = 3 [(validate.rules).message.required = true]; - - // Paging over the reactor list (server-ordered, typically most-recent - // first). Leave options.paging_token unset on the first request; on every - // subsequent request, set it to the paging_token from the most recent - // response to advance through the list. The token is opaque and - // server-generated; do not construct it. - common.v1.QueryOptions options = 4; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetReactorsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - } - - // A page of users who reacted with the requested emoji, with their reaction - // timestamps. Empty when the message exists but has no reactors for the emoji. - repeated Reactor reactors = 2 [(validate.rules).repeated = { - max_items: 100 - }]; - - // The server-generated cursor advanced past this page. The client MUST send - // the most recent value back in options.paging_token on the next - // GetReactorsRequest to fetch the following page. Set when result is OK and - // has_more is true. - common.v1.PagingToken paging_token = 3; - - // HasMore indicates whether further pages of reactors remain. When false, - // the reactor list has been fully read. When true, the client should issue - // another GetReactorsRequest with the returned paging_token. - bool has_more = 4; -} - -message GetReactionSummaryRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - MessageId message_id = 2 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetReactionSummaryResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - } - - // The aggregate reaction state for the message. reacted_by_self is computed - // for the caller; clients still apply per (message, emoji) by - // EmojiReaction.sequence, so a summary that is slightly behind a live update - // is harmlessly ignored rather than regressing state. - ReactionSummary summary = 2; -} - -message GetReactionSummariesRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - oneof query { - option (validate.required) = true; - - common.v1.QueryOptions options = 2; - MessageIdBatch message_ids = 3; - } - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message GetReactionSummariesResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } - - // One summary per requested message, keyed by ReactionSummary.message_id. - // reacted_by_self in each summary is computed for the caller; clients still - // apply per (message, emoji) by EmojiReaction.sequence, so a summary that - // is slightly behind a live update is harmlessly ignored rather than regressing - // state. - repeated ReactionSummary summaries = 2 [(validate.rules).repeated = { - max_items: 100 - }]; -} - -message AdvancePointerRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - Pointer.Type pointer_type = 2 [(validate.rules).enum = { - in: [2, 3] // DELIVERED, READ - }]; - - MessageId new_value = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message AdvancePointerResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - MESSAGE_NOT_FOUND = 2; - } -} - -message NotifyIsTypingRequest { - common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; - - IsTypingNotification.State state = 2; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message NotifyIsTypingResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} \ No newline at end of file diff --git a/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto b/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto deleted file mode 100644 index ea30fa8ce9..0000000000 --- a/definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto +++ /dev/null @@ -1,449 +0,0 @@ -syntax = "proto3"; - -package flipcash.messaging.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/messaging/v1;messagingpb"; -option java_package = "com.codeinc.flipcash.gen.messaging.v1"; -option objc_class_prefix = "FPBMessagingV1"; - -import "blob/v1/model.proto"; -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -message MessageId { - // Per-chat, server-assigned, gapless sequence number. Together with the - // chat ID this is the message's canonical identity, sort key, and - // pagination cursor. Gapless ordering lets clients trivially detect missing - // messages: a complete history has no gaps between consecutive numbers. - uint64 value = 1 [(validate.rules).uint64.gte = 1]; -} - -// ClientMessageId is a client-generated identifier for a message send. -// -// It serves two purposes: -// - Idempotency: the server dedups on this value, so a retried SendMessage -// (e.g. after a network failure) returns the originally created message -// instead of assigning a new sequence number and creating a duplicate. -// - Correlation: clients use it to match an optimistic local echo to the -// server-assigned Message returned in the response. -// -// Unlike MessageId, this is owned by the client and is not the message's -// canonical identity; it is typically a randomly generated UUID. -message ClientMessageId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 16 - }]; -} - -// A message in a chat -message Message { - // Per-chat sequence number identifying this message - MessageId message_id = 1 [(validate.rules).message.required = true]; - - // The chat member that sent the message. For system-level messages, - // this will be ommitted. - common.v1.UserId sender_id = 2; - - // Message content, which is currently guaranteed to have exactly one item. - repeated Content content = 3 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; - - // Timestamp this message was generated at. - google.protobuf.Timestamp ts = 4 [(validate.rules).timestamp.required = true]; - - // The number of unread-eligible messages in this chat up to and including - // this message. This is a SEPARATE sequence from message_id: messages that - // don't count toward unread keep their message_id but do NOT advance this - // value — they carry the previous count forward, so every message reports - // the running total. A member's unread count is computed entirely on the - // client as the difference between the latest message's unread_seq and the - // unread_seq of the message at their READ pointer. - uint64 unread_seq = 5; - - // If set, the timestamp this message was last edited at. Absent on messages - // that have never been edited. The content above always reflects the - // current (materialized) state, so clients render it directly; this field - // only drives an "edited" affordance. Deletions are represented in content - // via DeletedContent, not here. - google.protobuf.Timestamp last_edited_ts = 6; - - // The event-log sequence at which this message reached its current state: - // the point of the most recent mutation (send, edit, or delete) affecting - // it. A per-message VERSION stamp — distinct from message_id (fixed - // identity/order) and unread_seq (unread accounting) — that advances on - // every edit/delete while message_id stays fixed. - // - // It makes a Message self-locating regardless of how it was obtained (event - // stream, GetMessages, SendMessage echo, last_message, push). Clients apply - // last-writer-wins by this value: ignore a copy whose event_sequence is <= - // the version already held, otherwise insert/replace. Cross-message gap - // detection is separate, via the live event log's Event.sequence/count and - // GetDelta catch-up. - uint64 event_sequence = 7 [(validate.rules).uint64.gte = 1]; - - // Aggregate reaction state for this message, current as of the time it was - // read. This is a convergent overlay, NOT part of the content versioned by - // event_sequence: reactions change without advancing event_sequence, so - // clients refresh it on view and via live reaction updates rather than - // through the event log. - ReactionSummary reactions = 8; -} - -// Content for a chat message -message Content { - oneof type { - option (validate.required) = true; - - TextContent text = 1; - CashContent cash = 2; - ReplyContent reply = 3; - MediaContent media = 4; - SystemContent system = 5; - DeletedContent deleted = 6; - } -} - -// Raw text content -message TextContent { - string text = 1 [(validate.rules).string = { - min_len: 1 - max_len: 4096 - }]; -} - -// Cash content -message CashContent { - // Intent ID identifying the cash transaction at the OCP layer - common.v1.IntentId intent_id = 1 [(validate.rules).message.required = true]; - - // The amount of cash that was sent - common.v1.CryptoPaymentAmount amount = 2 [(validate.rules).message.required = true]; - - // Reserved for receiver, which will is required for group chats - reserved 3; - - // Verb for how the cash was sent. Clietns should always show SENT as a - // fallback. - enum Verb { - SENT = 0; - TIPPED = 1; - } - Verb verb = 4; -} - -// Reply content -message ReplyContent { - // ID of the message being replied to - MessageId replied_message_id = 1 [(validate.rules).message.required = true]; - - // Reply message content. Allowed content types are: - // - TextContent - // - MediaContent - repeated Content content = 2 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; -} - -// Media content from blobs the user has already uploaded. The following media -// types are supported: -// - Images -message MediaContent { - // The media items attached to this message. A single item today; raising - // this cap later enables albums (each item self-describes its kind). - // - // On SendMessage the client supplies exactly one ORIGINAL rendition per - // item; the server fills its metadata and appends the derived renditions. - repeated blob.v1.Media items = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; - - // Optional caption rendered alongside the media - TextContent caption = 2; -} - -// System message content -message SystemContent { - // Best-effort, server-rendered text in the user's locale setting. Today this - // is the only way to display a system message; once the structured `event` - // oneof exists it becomes a fallback, rendered ONLY when the client does not - // recognize the variant (old client, new server). It is not localized per - // viewer — clients that know a variant render their own localized string. - string fallback_text = 1 [(validate.rules).string = { - min_len: 1 - max_len: 256 - }]; - - // todo: Define events once we have them -} - -// Deleted message content -message DeletedContent { - // Timestamp the message was deleted. Set whenever a message is tombstoned; - // clients can surface it as a "deleted" affordance. This is the deletion - // analog of Message.last_edited_ts, kept here so all deletion state lives in - // the content rather than as a separate flag on Message. - google.protobuf.Timestamp deleted_ts = 1 [(validate.rules).timestamp.required = true]; - - // When present, the user that deleted the message. If not present, a it is - // a system-level deletion (eg. moderation check). - common.v1.UserId deleted_by = 2; -} - -// Emoji identifies an emoji used in a reaction. The value is a unicode emoji -// sequence — a single grapheme cluster, which may include modifiers such as a -// skin-tone selector or ZWJ joins — or a custom emoji identifier where -// supported. -message Emoji { - // Structural bounds only — these bound size as defense-in-depth (min_len/ - // max_len count code points, max_bytes counts bytes; a complex ZWJ or - // tag-flag sequence is ~8 code points but ~32 bytes, so both earn their - // keep). True emoji validity (a real grapheme, normalization, any supported - // set) is enforced in server code, not here. - string value = 1 [(validate.rules).string = { - min_len: 1 - max_len: 32 - max_bytes: 128 - }]; -} - -// Reactor identifies a user who reacted to a message and when they did so. -message Reactor { - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - // Timestamp the user added this reaction. - google.protobuf.Timestamp reacted_ts = 2 [(validate.rules).timestamp.required = true]; -} - -// ReactionSummary is the aggregate reaction state attached to a message. It is -// bounded: the number of distinct reaction types per message is capped, so the -// summary stays small no matter how many users reacted. The full reactor list -// for any emoji is fetched on demand (paged), never inlined here. -message ReactionSummary { - // The message these reactions belong to. - MessageId message_id = 1 [(validate.rules).message.required = true]; - - // One entry per distinct emoji reacted to this message - repeated EmojiReaction reactions = 2; -} - -// EmojiReaction aggregates all reactions of a single emoji on a message. -message EmojiReaction { - // The emoji reacted with. - Emoji emoji = 1 [(validate.rules).message.required = true]; - - // Total number of users who reacted with this emoji. Authoritative and may - // be arbitrarily large; the individual reactor identities are not all - // returned here. - uint64 count = 2; - - // Whether the requesting user reacted with this emoji. Per-viewer: count and - // sample_reactors are shareable across users, but this bit is computed for - // the caller. - bool reacted_by_self = 3; - - // A small sample of reactors, with their reaction timestamps (e.g. for - // rendering a few avatars), capped well below count. The complete, paged - // reactor list is fetched on demand via GetReactors. - repeated Reactor sample_reactors = 4 [(validate.rules).repeated = { - max_items: 8 // Sample only; not the full list - }]; - - // Monotonic version of this emoji's aggregate on the message, assigned by - // the server and advanced on every change to it. Ordering only: clients - // apply reaction updates last-writer-wins by this value per (message, emoji) - // — and per actor for reacted_by_self — and treat a loaded summary as stale - // when a higher sequence arrives. It is NOT the chat event sequence - // (reactions never advance that), and it is NOT gapless: it carries no - // gap-detection meaning. - uint64 sequence = 5 [(validate.rules).uint64.gte = 1]; -} - -// ReactionUpdate is a best-effort, real-time reaction change for a single -// (message, emoji) cell. Reactions are a convergent overlay, so these ride the -// event stream OUTSIDE the gap-detected event log — a missed update is not -// caught up via GetDelta but reconciled by refreshing the message's -// ReactionSummary on view. -message ReactionUpdate { - // The message whose reactions changed. - MessageId message_id = 1 [(validate.rules).message.required = true]; - - // The emoji that was added or removed. - Emoji emoji = 2 [(validate.rules).message.required = true]; - - // The user who added or removed the reaction. A client renders - // reacted_by_self by comparing this to itself, so a reaction made on the - // user's other device is reflected. - common.v1.UserId actor = 3 [(validate.rules).message.required = true]; - - Action action = 4 [(validate.rules).enum = { - not_in: [0] - }]; - enum Action { - UNKNOWN = 0; - ADDED = 1; - REMOVED = 2; - } - - // The emoji's total reactor count after this change. 0 means no reactors - // remain and the client should drop the entry from the summary. - uint64 count = 5; - - // The emoji aggregate's new version after this change. Clients apply - // last-writer-wins by this value: ignore the count if sequence <= the - // count watermark held, and ignore the actor's reacted_by_self toggle if - // sequence <= the per-actor watermark held. Matches EmojiReaction.sequence. - uint64 sequence = 6 [(validate.rules).uint64.gte = 1]; - - // When the actor reacted. On ADDED, clients record this as the actor's - // Reactor.reacted_ts (e.g. when slotting them into sample_reactors); ignored - // for REMOVED. This is a display timestamp, distinct from `sequence`, which - // is the ordering key. - google.protobuf.Timestamp reacted_ts = 7 [(validate.rules).timestamp.required = true]; -} - -message ReactionUpdateBatch { - repeated ReactionUpdate reaction_updates = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -// Pointer in a chat indicating a user's message history state in a chat. -message Pointer { - // The type of pointer indicates which user's message history state can be - // inferred from the pointer value. It is also possible to infer cross-pointer - // state. For example, if a chat member has a READ pointer for a message with - // ID N, then the DELIVERED pointer must be at least N. - Type type = 1 [(validate.rules).enum = { - not_in: [0] - }]; - enum Type { - UNKNOWN = 0; - SENT = 1; // Always inferred by OK result in SendMessageResponse or message presence in a chat - DELIVERED = 2; - READ = 3; - } - - // The user ID associated with the pointer - common.v1.UserId user_id = 2 [(validate.rules).message.required = true]; - - // Everything at or before this message ID is considered to have the state - // inferred by the type of pointer. - MessageId value = 3 [(validate.rules).message.required = true]; - - // Timestamp the pointer was last advanced at - google.protobuf.Timestamp ts = 4 [(validate.rules).timestamp.required = true]; -} - -message MessageIdBatch { - repeated MessageId message_ids = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -message MessageBatch { - repeated Message messages = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -message PointerBatch { - repeated Pointer pointers = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -// Event is a contiguous run of one or more durable mutations to a chat, delivered -// atomically — the unit of the chat's event log. Newly sent messages, edits, -// and deletions are all mutations within an event. -// -// Only content-bearing, non-idempotent mutations live in the log, because that is -// what gap detection protects: missing one means missing data. Convergent state -// such as pointer advances (last-writer-wins, monotonic) and transient signals -// such as typing notifications are delivered out-of-band and fetched as current -// state, NOT replayed through this log. -// -// Clients apply events in ascending sequence order and use the sequence/count -// pair to detect gaps; on a gap they catch up via GetDelta. -message Event { - // Per-chat event sequence valued AFTER this event applies: the END of the - // half-open range (sequence - count, sequence] this event occupies. This is - // a SEPARATE sequence from MessageId — edits and deletions advance it - // without minting a new MessageId. - uint64 sequence = 1 [(validate.rules).uint64.gte = 1]; - - // The number of points this event consumes, equal to the number of - // mutations it carries — each mutation is one point. The mutation at index - // i sits at point (sequence - count + 1 + i). Clients gap-detect with - // local + count == sequence, so a server that begins emitting count > 1 - // (e.g. a bulk delete) needs no client change. - uint32 count = 2 [(validate.rules).uint32.gte = 1]; - - // Timestamp this event occurred at. - google.protobuf.Timestamp ts = 3 [(validate.rules).timestamp.required = true]; - - // The mutations in this event, ascending by point. Length must equal count. - repeated Mutation mutations = 4 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -// Mutation is a single point in the event log: one message sent, edited, or -// deleted. Each carries the full materialized state of the affected message, so -// clients apply it by inserting or replacing their cached copy without a -// refetch. -message Mutation { - oneof type { - option (validate.required) = true; - - // A newly sent message. Inserts a new message_id at the tail of the - // chat. This is the only mutation that advances the MessageId sequence. - Message message_sent = 1; - - // An edit to an existing message (same message_id, updated content, - // last_edited_ts set). - Message message_edited = 2; - - // A deletion of an existing message (same message_id, content replaced - // with DeletedContent). The message_id is retained as a tombstone, so - // the MessageId sequence stays gapless. - Message message_deleted = 3; - } -} - -message EventBatch { - repeated Event events = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 - }]; -} - -message IsTypingNotification { - common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; - - State state = 2; - enum State { - UNKNOWN_TYPING_STATE = 0; - STARTED_TYPING = 1; - STILL_TYPING = 2; - STOPPED_TYPING = 3; - TYPING_TIMED_OUT = 4; - } -} - -message IsTypingNotificationBatch { - repeated IsTypingNotification is_typing_notifications = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 100 // Arbitrary - }]; -} diff --git a/definitions/flipcash/protos/src/main/proto/moderation/v1/model.proto b/definitions/flipcash/protos/src/main/proto/moderation/v1/model.proto deleted file mode 100644 index 9614d60f18..0000000000 --- a/definitions/flipcash/protos/src/main/proto/moderation/v1/model.proto +++ /dev/null @@ -1,38 +0,0 @@ -syntax = "proto3"; - -package flipcash.moderation.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/moderation/v1;moderationpb"; -option java_package = "com.codeinc.flipcash.gen.moderation.v1"; -option objc_class_prefix = "FPBModerationV1"; - -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; - -// ModerationAttestation is a signed proof of the moderation result. -// The signature is computed over this message without the signature field set. -message ModerationAttestation { - // SHA-256 hash of the moderated content to be allowed - bytes content_hash = 1; - - // Timestamp of the moderation - google.protobuf.Timestamp timestamp = 2; - - // The user who submitted the content - common.v1.UserId user_id = 3; - - // Public key of the attestor that signed this message - common.v1.PublicKey attestor = 4; - - // Attestor signature over this message - common.v1.Signature signature = 5; -} - -enum FlaggedCategory { - NONE = 0; - OTHER = 1; // Fallback category when flagged content does not fit into a well-defined FlaggedCategory - NSFW = 2; - IMPERSONATION = 3; - MISLEADING = 4; - SPAM = 5; -} diff --git a/definitions/flipcash/protos/src/main/proto/moderation/v1/moderation_service.proto b/definitions/flipcash/protos/src/main/proto/moderation/v1/moderation_service.proto deleted file mode 100644 index a8e0f51ee9..0000000000 --- a/definitions/flipcash/protos/src/main/proto/moderation/v1/moderation_service.proto +++ /dev/null @@ -1,75 +0,0 @@ -syntax = "proto3"; - -package flipcash.moderation.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/moderation/v1;moderationpb"; -option java_package = "com.codeinc.flipcash.gen.moderation.v1"; -option objc_class_prefix = "FPBModerationV1"; - -import "common/v1/common.proto"; -import "moderation/v1/model.proto"; -import "validate/validate.proto"; - -service Moderation { - // ModerateText checks text content against moderation policies - rpc ModerateText(ModerateTextRequest) returns (ModerateTextResponse); - - // ModerateImage checks image content against moderation policies - rpc ModerateImage(ModerateImageRequest) returns (ModerateImageResponse); -} - -message ModerateTextRequest { - // The text content to moderate - string text = 1 [(validate.rules).string = { - min_len: 1 - max_len: 4096 - }]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message ModerateTextResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - UNSUPPORTED_LANGUAGE = 2; - } - - // Whether the text content is allowed - bool is_allowed = 2; - - // Signed attestation of the moderation result when content is allowed - ModerationAttestation attestation = 3; - - // The best fit flagged category when content is not allowed - FlaggedCategory flagged_category = 4; -} - -message ModerateImageRequest { - // The raw image data to moderate - bytes image_data = 1 [(validate.rules).bytes = { - min_len: 1 - max_len: 1048576 // 1 MB - }]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message ModerateImageResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - UNSUPPORTED_FORMAT = 2; - } - - // Whether the image content is allowed - bool is_allowed = 2; - - // Signed attestation of the moderation result when content is allowed - ModerationAttestation attestation = 3; - - // The best fit flagged category when content is not allowed - FlaggedCategory flagged_category = 4; -} diff --git a/definitions/flipcash/protos/src/main/proto/phone/v1/model.proto b/definitions/flipcash/protos/src/main/proto/phone/v1/model.proto deleted file mode 100644 index 3c8247c2aa..0000000000 --- a/definitions/flipcash/protos/src/main/proto/phone/v1/model.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package flipcash.phone.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/phone/v1;phonepb"; -option java_package = "com.codeinc.flipcash.gen.phone.v1"; -option objc_class_prefix = "FPBPhoneV1"; - -import "validate/validate.proto"; - -// VerificationCode is a 4-10 digit numerical code for verification -message VerificationCode { - string value = 2 [(validate.rules).string = { - pattern: "^[0-9]{4,10}$" - }]; -} diff --git a/definitions/flipcash/protos/src/main/proto/phone/v1/phone_verification_service.proto b/definitions/flipcash/protos/src/main/proto/phone/v1/phone_verification_service.proto deleted file mode 100644 index f5338660c7..0000000000 --- a/definitions/flipcash/protos/src/main/proto/phone/v1/phone_verification_service.proto +++ /dev/null @@ -1,116 +0,0 @@ -syntax = "proto3"; - -package flipcash.phone.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/phone/v1;phonepb"; -option java_package = "com.codeinc.flipcash.gen.phone.v1"; -option objc_class_prefix = "FPBPhoneV1"; - -import "common/v1/common.proto"; -import "phone/v1/model.proto"; -import "validate/validate.proto"; - -service PhoneVerification { - // SendVerificationCode sends a verification code to the provided phone number - // over SMS. If an active verification is already taking place, the existing code - // will be resent. - rpc SendVerificationCode(SendVerificationCodeRequest) returns (SendVerificationCodeResponse); - - // CheckVerificationCode validates a verification code. On success, the phone number - // is linked to the user. Any previous links are overwritten. - rpc CheckVerificationCode(CheckVerificationCodeRequest) returns (CheckVerificationCodeResponse); - - // Unlink removes the link of a phone number from a user. - rpc Unlink(UnlinkRequest) returns (UnlinkResponse); - - // LinkForPayment links the verified phone number for the requesting user for payment. - rpc LinkForPayment(LinkForPaymentRequest) returns (LinkForPaymentResponse); -} - -message SendVerificationCodeRequest { - // The phone number to send a verification code over SMS to - common.v1.PhoneNumber phone_number = 1 [(validate.rules).message.required = true]; - - // The app platform that's making this request - common.v1.Platform platform = 2 [(validate.rules).enum = {in: [1,2]}]; - - common.v1.Auth auth = 3 [(validate.rules).message.required = true]; -} - -message SendVerificationCodeResponse { - Result result = 1; - enum Result { - OK = 0; - // SMS is denied - DENIED = 1; - // SMS is rate limited (eg. by IP, phone number, user, etc) and was not sent. - RATE_LIMITED = 2; - // The phone number is not real because it fails Twilio lookup. - INVALID_PHONE_NUMBER = 3; - // The phone number is valid, but it maps to an unsupported type of phone - // like a landline. - UNSUPPORTED_PHONE_TYPE = 4; - } -} - -message CheckVerificationCodeRequest { - // The phone number being verified - common.v1.PhoneNumber phone_number = 1 [(validate.rules).message.required = true]; - - // The verification code received via SMS - VerificationCode code = 2 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 3 [(validate.rules).message.required = true]; -} - -message CheckVerificationCodeResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - // The call is rate limited (eg. by IP, phone number, etc). The code is - // not verified. - RATE_LIMITED = 2; - // The provided verification code is invalid. The user may retry - // enterring the code if this is received. When max attempts are - // received, NO_VERIFICATION will be returned. - INVALID_CODE = 3; - // There is no verification in progress for the phone number. Several - // reasons this can occur include a verification being expired or having - // reached a maximum check threshold. The client must initiate a new - // verification using SendVerificationCode. - NO_VERIFICATION = 4; - } -} - -message UnlinkRequest { - // The phone number to unlink - common.v1.PhoneNumber phone_number = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message UnlinkResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} - - -message LinkForPaymentRequest { - // The phone number to link for payment - common.v1.PhoneNumber phone_number = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message LinkForPaymentResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - NOT_ASSOCIATED = 2; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/profile/v1/model.proto b/definitions/flipcash/protos/src/main/proto/profile/v1/model.proto deleted file mode 100644 index c02c0bb755..0000000000 --- a/definitions/flipcash/protos/src/main/proto/profile/v1/model.proto +++ /dev/null @@ -1,119 +0,0 @@ -syntax = "proto3"; - -package flipcash.profile.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/profile/v1;profilepb"; -option java_package = "com.codeinc.flipcash.gen.profile.v1"; -option objc_class_prefix = "FPBProfileV1"; - -import "blob/v1/model.proto"; -import "common/v1/common.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -message UserProfile { - // The ID of the user this profile belongs to. Always set, so a caller that - // looked the profile up by username learns the user's ID from the response. - common.v1.UserId user_id = 9 [(validate.rules).message.required = true]; - - // Display name is the display name of the user (if found). - string display_name = 1 [(validate.rules).string = { - min_len: 0 - max_len: 64 - }]; - - // The user's username on Flipcash. Public, so it is returned for any user, - // not just the caller. Unset when the user hasn't claimed one yet. - common.v1.Username username = 8; - - // Social profiles are links to external social accounts - repeated SocialProfile social_profiles = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 1 - }]; - - - // Phone number linked to this user. This is private and will only be returned - // when the requesting user asks for their own profile - common.v1.PhoneNumber phone_number = 3; - - // Email address linked to this user. This is private and will only be returned - // when the requesting user asks for their own profile - common.v1.EmailAddress email_address = 4; - - // The user's profile picture, as the set of renditions it is stored as — - // typically a DISPLAY for the profile view and a THUMBNAIL for avatars in - // member rows and chat lists. - // - // Unset when the user has not set a picture. Set it with SetProfilePicture. - // - // To fetch the bytes of ANOTHER user's picture, the caller does not own - // these blobs, so a GetBlobs call must carry a blob.v1.AccessContext whose - // `profile` scope names this user. A caller reading its own needs none. - blob.v1.Media profile_picture = 5; - - // Timestamp the user joined Flipcash - google.protobuf.Timestamp join_ts = 6 [(validate.rules).timestamp.required = true]; - - // How the user has customized their Tip Card. Public, so it is returned for - // any user, not just the caller. Always set — the server resolves defaults - // for anything the user hasn't customized. Update it with UpdateTipCard. - TipCardCustomization tip_card_customization = 7 [(validate.rules).message.required = true]; -} - -message SocialProfile { - oneof type { - option (validate.required) = true; - - XProfile x = 1; - } -} - -message XProfile { - // The user's ID on X - string id = 1 [(validate.rules).string = { - min_len: 1 - max_len: 32 - }]; - - // The user's username on X - string username = 2 [(validate.rules).string = { - min_len: 1 - max_len: 15 - }]; - - // The user's friendly name on X - string name = 3 [(validate.rules).string = { - max_len: 256 - }]; - - // The user's description on X - string description = 4 [(validate.rules).string = { - max_len: 4096 // todo: arbitrary - }]; - - // URL to the user's X profile picture - string profile_pic_url = 5 [(validate.rules).string = { - min_len: 1 - max_len: 2048 // todo: arbitrary - }]; - - // The type of X verification associated with the user - VerifiedType verified_type = 6; - enum VerifiedType { - NONE = 0; - BLUE = 1; - BUSINESS = 2; - GOVERNMENT = 3; - } - - // The number of followers the user has on X - uint32 follower_count = 7; -} - -// Customization for a Tip Card -message TipCardCustomization { - // The colour of the Tip Card. Always set — the server falls back to the - // default colour when the user hasn't picked one. - common.v1.Color color = 1 [(validate.rules).message.required = true]; -} diff --git a/definitions/flipcash/protos/src/main/proto/profile/v1/profile_service.proto b/definitions/flipcash/protos/src/main/proto/profile/v1/profile_service.proto deleted file mode 100644 index fb9be2f197..0000000000 --- a/definitions/flipcash/protos/src/main/proto/profile/v1/profile_service.proto +++ /dev/null @@ -1,215 +0,0 @@ -syntax = "proto3"; - -package flipcash.profile.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/profile/v1;profilepb"; -option java_package = "com.codeinc.flipcash.gen.profile.v1"; -option objc_class_prefix = "FPBProfileV1"; - -import "blob/v1/model.proto"; -import "common/v1/common.proto"; -import "moderation/v1/model.proto"; -import "profile/v1/model.proto"; -import "validate/validate.proto"; - -service Profile { - rpc GetProfile(GetProfileRequest) returns (GetProfileResponse); - - rpc SetDisplayName(SetDisplayNameRequest) returns (SetDisplayNameResponse); - - // SetUsername sets the caller's username, replacing any username already - // set. - rpc SetUsername(SetUsernameRequest) returns (SetUsernameResponse); - - // SetProfilePicture sets the caller's profile picture to a blob they have - // already uploaded via BlobStorage, replacing any picture already set. - // - // The client uploads only the ORIGINAL — InitiateExternalUpload, PUT/POST - // the bytes, then (optionally) CompleteExternalUpload — and passes the - // resulting BlobId here once the blob is READY. The server derives the - // DISPLAY and THUMBNAIL renditions itself and returns the full set. - rpc SetProfilePicture(SetProfilePictureRequest) returns (SetProfilePictureResponse); - - // UpdateTipCard updates the caller's Tip Card customization. Every field is - // optional; only the ones set in the request are changed. - rpc UpdateTipCard(UpdateTipCardRequest) returns (UpdateTipCardResponse); - - // LinkSocialAccount links a social account to a user - rpc LinkSocialAccount(LinkSocialAccountRequest) returns (LinkSocialAccountResponse); - - // UnlinkSocialAccount removes a social account link from a user - rpc UnlinkSocialAccount(UnlinkSocialAccountRequest) returns (UnlinkSocialAccountResponse); -} - -message GetProfileRequest { - // The user whose profile is being fetched, identified either by their user - // ID or by their username. Exactly one must be set. - oneof identifier { - option (validate.required) = true; - - common.v1.UserId user_id = 1; - common.v1.Username username = 3; - } - - // Optional auth to retrieve private profile information for self - common.v1.Auth auth = 2; -} - -message GetProfileResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - } - - // UserProfile, if found. - // - // Some fields may or may not be set, depending on the scope of request - // in the future. - UserProfile user_profile = 2; -} - -message SetDisplayNameRequest { - // DisplayName is the new name to set. - string display_name = 1 [(validate.rules).string = { - min_len: 1 - max_len: 64 - }]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message SetDisplayNameResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_DISPLAY_NAME = 1; - DENIED = 2; - FAILED_MODERATED = 3; - } - - // The best-fit category that tripped moderation, mirroring the Moderation - // service's vocabulary. Set only when result == FAILED_MODERATED; NONE - // otherwise. - moderation.v1.FlaggedCategory flagged_category = 2; -} - -message SetUsernameRequest { - // Username is the new username to set. - common.v1.Username username = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message SetUsernameResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_USERNAME = 1; - DENIED = 2; - ALREADY_TAKEN = 3; - FAILED_MODERATED = 4; - INSUFFICIENT_BALANCE = 5; - RESERVED_WORD = 6; - } - - // The best-fit category that tripped moderation, mirroring the Moderation - // service's vocabulary. Set only when result == FAILED_MODERATED; NONE - // otherwise. - moderation.v1.FlaggedCategory flagged_category = 2; -} - -message SetProfilePictureRequest { - // The blob holding the ORIGINAL image the caller uploaded. It must be owned - // by the caller and READY; the server derives the remaining renditions from - // it. A blob may back at most one profile picture — reuse is not implied. - blob.v1.BlobId blob_id = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message SetProfilePictureResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - BLOB_NOT_FOUND = 2; // no such blob, or it is not owned by the caller - BLOB_NOT_READY = 3; // blob is still PENDING/PROCESSING; retry once READY - BLOB_REJECTED = 4; // blob failed validation or moderation; terminal for this id, so the client must upload again - INVALID_BLOB = 5; // blob is READY but unusable as a picture (e.g. not an image) - } - - // The caller's new profile picture, including the renditions the server - // derived. Set only when result == OK. - blob.v1.Media profile_picture = 2; -} - -message UpdateTipCardRequest { - // The new colour of the Tip Card. Left unchanged when unset. - common.v1.Color color = 1; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message UpdateTipCardResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - INVALID_COLOR = 2; - } -} - -message LinkSocialAccountRequest { - LinkingToken linking_token = 1 [(validate.rules).message.required = true]; - - message LinkingToken { - oneof type { - option (validate.required) = true; - - XLinkingToken x = 1; - } - - message XLinkingToken { - // X access token from the OAuth 2.0 flow - string access_token = 1[(validate.rules).string = { - min_len: 1 - max_len: 4096 // todo: arbitrary - }]; - } - } - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message LinkSocialAccountResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_LINKING_TOKEN = 1; - EXISTING_LINK = 2; - DENIED = 3; - } - - SocialProfile social_profile = 2; -} - -message UnlinkSocialAccountRequest { - oneof social_identifier { - option (validate.required) = true; - - string x_user_id = 1 [(validate.rules).string = { - max_len: 32 - }]; - } - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message UnlinkSocialAccountResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/push/v1/model.proto b/definitions/flipcash/protos/src/main/proto/push/v1/model.proto deleted file mode 100644 index 2ccc16e014..0000000000 --- a/definitions/flipcash/protos/src/main/proto/push/v1/model.proto +++ /dev/null @@ -1,79 +0,0 @@ -syntax = "proto3"; - -package flipcash.push.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/push/v1;pushpb"; -option java_package = "com.codeinc.flipcash.gen.push.v1"; -option objc_class_prefix = "FPBPushV1"; - -import "chat/v1/model.proto"; -import "common/v1/common.proto"; -import "validate/validate.proto"; - -enum TokenType { - UNKNOWN = 0; - // FCM registration token for an Android device - FCM_ANDROID = 1; - // FCM registration token or an iOS device - FCM_APNS = 2; -} - -// Payload provided as extra data in a push -message Payload { - // If present, where the app should navigate to after clicking the push - Navigation navigation = 1; - - // Ordered substitutions to apply to push title - repeated common.v1.Substitution title_substitutions = 2; - - // Ordered substitutions to apply to push body - repeated common.v1.Substitution body_substitutions = 3; - - // Push notification category - Category category = 4; - enum Category { - DEFAULT = 0; - DEPOSIT_WITHDRAWAL = 1; - BUY_SELL = 2; - GAIN = 3; - CHAT = 4; - CONTACT_JOIN = 5; - } - - // Push notification key for grouping pushes. If not set, then no grouping - // is applied. - string group_key = 5 [(validate.rules).string = { - max_len: 4096 // Arbitrary - }]; - - ChatMetadata chat_metadata = 6; -} - -// Navigation within the app upon clicking the push -message Navigation { - oneof type { - option (validate.required) = true; - - // Currency info page for the provided mint - common.v1.PublicKey currency_info = 1; - - // Chat for the provided ID - common.v1.ChatId chat_id = 2; - - // Chat for a contact with the provided phone number - common.v1.PhoneNumber chat_contact_phone_number = 3; - } -} -// Additional metadata provided for chat pushes -message ChatMetadata { - // The user ID that sent a chat message - // - // Note: This will not be set for system messages OR for notifications that - // don't relate to a user - common.v1.UserId sending_user_id = 1; - - // The type of chat - chat.v1.ChatType type = 2 [(validate.rules).enum = { - not_in: [0] // UNKNOWN - }]; -} diff --git a/definitions/flipcash/protos/src/main/proto/push/v1/push_service.proto b/definitions/flipcash/protos/src/main/proto/push/v1/push_service.proto deleted file mode 100644 index fa1b9597e5..0000000000 --- a/definitions/flipcash/protos/src/main/proto/push/v1/push_service.proto +++ /dev/null @@ -1,53 +0,0 @@ -syntax = "proto3"; - -package flipcash.push.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/push/v1;pushpb"; -option java_package = "com.codeinc.flipcash.gen.push.v1"; -option objc_class_prefix = "FPBPushV1"; - -import "common/v1/common.proto"; -import "push/v1/model.proto"; -import "validate/validate.proto"; - -service Push { - // AddToken adds a push token associated with a user. - rpc AddToken(AddTokenRequest) returns (AddTokenResponse); - - // DeleteTokens removes all push tokens within an app install for a user - rpc DeleteTokens(DeleteTokensRequest) returns (DeleteTokensResponse); -} - -message AddTokenRequest { - TokenType token_type = 1 [(validate.rules).enum = {in: [1,2]}]; - - string push_token = 2 [(validate.rules).string = { - min_len: 1 - max_len: 4096 - }]; - - common.v1.AppInstallId app_install = 3 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 4 [(validate.rules).message.required = true]; -} - -message AddTokenResponse { - Result result = 1; - enum Result { - OK = 0; - INVALID_PUSH_TOKEN = 1; - } -} - -message DeleteTokensRequest { - common.v1.AppInstallId app_install = 1 [(validate.rules).message.required = true]; - - common.v1.Auth auth = 2 [(validate.rules).message.required = true]; -} - -message DeleteTokensResponse { - Result result = 1; - enum Result { - OK = 0; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/resolver/v1/model.proto b/definitions/flipcash/protos/src/main/proto/resolver/v1/model.proto deleted file mode 100644 index 5c77a739cf..0000000000 --- a/definitions/flipcash/protos/src/main/proto/resolver/v1/model.proto +++ /dev/null @@ -1,32 +0,0 @@ -syntax = "proto3"; - -package flipcash.resolver.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/resolver/v1;resolverpb"; -option java_package = "com.codeinc.flipcash.gen.resolver.v1"; -option objc_class_prefix = "FPBResolverV1"; - -import "common/v1/common.proto"; -import "validate/validate.proto"; - -// Identifier wraps a real-world identifier that can be resolved to a -// payment destination address. -message Identifier { - oneof kind { - option (validate.required) = true; - - common.v1.PhoneNumber phone = 1; - common.v1.UserId user_id = 2; - common.v1.Username username = 3; - } -} - -// Resolution contains a payment destiation address mapping for an -// Identifier -message Resolution { - oneof kind { - option (validate.required) = true; - - common.v1.PublicKey address = 1; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/resolver/v1/resolver_service.proto b/definitions/flipcash/protos/src/main/proto/resolver/v1/resolver_service.proto deleted file mode 100644 index ac70347781..0000000000 --- a/definitions/flipcash/protos/src/main/proto/resolver/v1/resolver_service.proto +++ /dev/null @@ -1,36 +0,0 @@ -syntax = "proto3"; - -package flipcash.resolver.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/resolver/v1;resolverpb"; -option java_package = "com.codeinc.flipcash.gen.resolver.v1"; -option objc_class_prefix = "FPBResolverV1"; - -import "common/v1/common.proto"; -import "resolver/v1/model.proto"; -import "validate/validate.proto"; - -// Resolver maps a real-world identifier (phone number, etc.) to a payment -// destination address. -service Resolver { - // Resolve looks up the payment destination address for the given identifier. - rpc Resolve(ResolveRequest) returns (ResolveResponse); -} - -message ResolveRequest { - common.v1.Auth auth = 1 [(validate.rules).message.required = true]; - - Identifier identifier = 2 [(validate.rules).message.required = true]; -} - -message ResolveResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - DENIED = 2; - } - - // The resolved payment destination address. Set when result == OK. - Resolution resolution = 2; -} diff --git a/definitions/flipcash/protos/src/main/proto/settings/v1/settings_service.proto b/definitions/flipcash/protos/src/main/proto/settings/v1/settings_service.proto deleted file mode 100644 index 3fdf614da8..0000000000 --- a/definitions/flipcash/protos/src/main/proto/settings/v1/settings_service.proto +++ /dev/null @@ -1,34 +0,0 @@ -syntax = "proto3"; - -package flipcash.settings.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/settings/v1;settingspb"; -option java_package = "com.codeinc.flipcash.gen.settings.v1"; -option objc_class_prefix = "FPBSettingsV1"; - -import "common/v1/common.proto"; -import "validate/validate.proto"; - -service Settings { - rpc UpdateSettings(UpdateSettingsRequest) returns (UpdateSettingsResponse); -} - -message UpdateSettingsRequest { - // Locale setting, only updated if present - common.v1.Locale locale = 1; - - // Region setting, only updated if present - common.v1.Region region = 2; - - common.v1.Auth auth = 10 [(validate.rules).message.required = true]; -} - -message UpdateSettingsResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - INVALID_LOCALE = 2; - INVALID_REGION = 3; - } -} diff --git a/definitions/flipcash/protos/src/main/proto/thirdparty/v1/model.proto b/definitions/flipcash/protos/src/main/proto/thirdparty/v1/model.proto deleted file mode 100644 index 5eab691fbc..0000000000 --- a/definitions/flipcash/protos/src/main/proto/thirdparty/v1/model.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; - -package flipcash.thirdparty.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/thirdparty/v1;thirdpartypb"; -option java_package = "com.codeinc.flipcash.gen.thirdparty.v1"; -option objc_class_prefix = "FPBThirdPartyV1"; - -import "validate/validate.proto"; - -enum Provider { - UNKNOWN = 0; - COINBASE = 1; -} - -message ApiKey { - Provider provider = 1 [(validate.rules).enum = {in: [1]}]; - - string value = 2 [(validate.rules).string = { - min_len: 36 - max_len: 36 - }]; -} - -message Jwt { - string value = 1 [(validate.rules).string = { - min_len: 1 - max_len: 1024 // Arbitrary - }]; -} \ No newline at end of file diff --git a/definitions/flipcash/protos/src/main/proto/thirdparty/v1/third_party_service.proto b/definitions/flipcash/protos/src/main/proto/thirdparty/v1/third_party_service.proto deleted file mode 100644 index ee31442636..0000000000 --- a/definitions/flipcash/protos/src/main/proto/thirdparty/v1/third_party_service.proto +++ /dev/null @@ -1,51 +0,0 @@ -syntax = "proto3"; - -package flipcash.thirdparty.v1; - -option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/thirdparty/v1;thirdpartypb"; -option java_package = "com.codeinc.flipcash.gen.thirdparty.v1"; -option objc_class_prefix = "FPBThirdPartyV1"; - -import "common/v1/common.proto"; -import "thirdparty/v1/model.proto"; -import "validate/validate.proto"; - -service ThirdParty { - // GetJwt gets a JWT for auth against a third part - rpc GetJwt(GetJwtRequest) returns (GetJwtResponse); -} - -message GetJwtRequest { - ApiKey api_key = 1 [(validate.rules).message.required = true]; - - string method = 2 [(validate.rules).string = { - min_len: 3 - max_len: 4 - }]; - - string host = 3 [(validate.rules).string = { - min_len: 1 - max_len: 1024 - }]; - - string path = 4 [(validate.rules).string = { - min_len: 1 - max_len: 1024 - }]; - - common.v1.Auth auth = 5 [(validate.rules).message.required = true]; -} - -message GetJwtResponse { - Result result = 1; - enum Result { - OK = 0; - DENIED = 1; - UNSUPPORTED_PROVIDER = 2; - INVALID_API_KEY = 3; - PHONE_VERIFICATION_REQUIRED = 4; - EMAIL_VERIFICATION_REQUIRED = 5; - } - - Jwt jwt = 2; -} diff --git a/definitions/opencode/models/.gitignore b/definitions/opencode/models/.gitignore deleted file mode 100644 index 9f2a078806..0000000000 --- a/definitions/opencode/models/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.gradle/ diff --git a/definitions/opencode/models/build.gradle.kts b/definitions/opencode/models/build.gradle.kts deleted file mode 100644 index 1b58f7cf2d..0000000000 --- a/definitions/opencode/models/build.gradle.kts +++ /dev/null @@ -1,74 +0,0 @@ -import dev.bmcreations.protovalidate.gradle.ProtoVariant -import org.apache.tools.ant.taskdefs.condition.Os - -plugins { - alias(libs.plugins.flipcash.android.library) - alias(libs.plugins.protobuf) - alias(libs.plugins.protobuf.validate) -} - -val archSuffix = if (Os.isFamily(Os.FAMILY_MAC)) { - if (System.getProperty("os.arch") == "aarch64") ":osx-aarch_64" else ":osx-x86_64" -} else "" - -version = "0.0.1" -group = "com.codeinc.opencode.gen" - -dependencies { - protobuf(project(":definitions:opencode:protos")) - - implementation(libs.grpc.protobuf.lite) - implementation(libs.grpc.stub) - - // Kotlin Generation - implementation(libs.grpc.kotlin) - implementation(libs.protobuf.kotlin.lite) -} - -android { - namespace = "${Gradle.codeNamespace}.defs.opencode.models" -} - -val protobufVersion = libs.versions.protobuf.asProvider().get() -val grpcVersion = libs.versions.grpc.asProvider().get() - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${protobufVersion}$archSuffix" - } - plugins { - create("java") { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" - } - create("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" - } - create("grpckt") { - artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" - } - } - generateProtoTasks { - all().forEach { - it.plugins { - create("java") { - option("lite") - } - create("grpc") { - option("lite") - } - create("grpckt") { - option("lite") - } - } - it.builtins { - create("kotlin") { - option("lite") - } - } - } - } -} - -protovalidate { - variant.set(ProtoVariant.PGV) -} \ No newline at end of file diff --git a/definitions/opencode/protos/.gitignore b/definitions/opencode/protos/.gitignore deleted file mode 100644 index 9f2a078806..0000000000 --- a/definitions/opencode/protos/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.gradle/ diff --git a/definitions/opencode/protos/build.gradle.kts b/definitions/opencode/protos/build.gradle.kts deleted file mode 100644 index a42e252c72..0000000000 --- a/definitions/opencode/protos/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -// todo: maybe use variants / configurations to do both stub & stub-lite here - -// Note: We use the java-library plugin to get the protos into the artifact for this subproject -// because there doesn't seem to be an better way. -plugins { - `java-library` -} - -java { - sourceSets.getByName("main").resources.srcDir("src/main/proto") -} diff --git a/definitions/opencode/protos/src/main/proto/account/v1/ocp_account_service.proto b/definitions/opencode/protos/src/main/proto/account/v1/ocp_account_service.proto deleted file mode 100644 index 9a55dca6d0..0000000000 --- a/definitions/opencode/protos/src/main/proto/account/v1/ocp_account_service.proto +++ /dev/null @@ -1,227 +0,0 @@ -syntax = "proto3"; - -package ocp.account.v1; - -option go_package = "github.com/code-payments/ocp-protobuf-api/generated/go/account/v1;account"; -option java_package = "com.codeinc.opencode.gen.account.v1"; -option objc_class_prefix = "CPBAccountV1"; - -import "common/v1/model.proto"; -import "currency/v1/ocp_currency_service.proto"; -import "transaction/v1/ocp_transaction_service.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -service Account { - // IsOcpAccount returns whether an owner account is a OCP account. This hints - // to the client whether the account can be logged in, used for making payments, - // etc. - rpc IsOcpAccount(IsOcpAccountRequest) returns (IsOcpAccountResponse); - - // GetTokenAccountInfos returns token account metadata relevant to the OCP owner - // account. - rpc GetTokenAccountInfos(GetTokenAccountInfosRequest) returns (GetTokenAccountInfosResponse); -} - -message IsOcpAccountRequest { - // The owner account to check against. - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(IsOcpAccountRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - -} - -message IsOcpAccountResponse { - Result result = 1; - enum Result { - // The account is an OCP account. - OK = 0; - // The account is not an OCP account. - NOT_FOUND = 1; - // The account exists, but at least one timelock account is unlocked. - UNLOCKED_TIMELOCK_ACCOUNT = 2; - } -} - -message GetTokenAccountInfosRequest { - // The owner account to fetch balances for, which can also be thought of as a - // parent account for this RPC that links to one or more token accounts. - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(GetTokenAccountInfosRequest) without signature - // fields set using the private key of the owner account. This provides - // an authentication mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - - - // A requesting owner account that is requesting the balance for owner. Additional - // metadata that is considered private will be provided, if applicable. An example - // use case includes a user owner account requesting account info for a gift card - // owner account. - common.v1.SolanaAccountId requesting_owner = 3; - - // The signature is of serialize(GetTokenAccountInfosRequest) without signature - // fields set using the private key of the requesting_owner_signature account. - // This provides an authentication mechanism to the RPC when requesting_owner is - // present. - // - // This must be set when requesting_owner is present. - common.v1.Signature requesting_owner_signature = 4; - - // Filter to apply to limit response sizes - oneof Filter { - common.v1.SolanaAccountId filter_by_token_address = 10; - common.v1.AccountType filter_by_account_type = 11; - common.v1.SolanaAccountId filter_by_mint_address = 12; - } -} - -message GetTokenAccountInfosResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - } - - map token_account_infos = 2; -} - -message TokenAccountInfo { - // The token account's address - common.v1.SolanaAccountId address = 1 [(validate.rules).message.required = true]; - - - - // The owner of the token account, which can also be thought of as a parent - // account that links to one or more token accounts. This is provided when - // available. - common.v1.SolanaAccountId owner = 2; - - // The token account's authority, which has access to moving funds for the - // account. This can be the owner account under certain circumstances (eg. - // ATA, primary account). This is provided when available. - common.v1.SolanaAccountId authority = 3; - - // The type of token account, which infers its intended use. - common.v1.AccountType account_type = 4 [(validate.rules).enum.not_in = 0]; - - - - // The account's derivation index for applicable account types. When this field - // doesn't apply, a zero value is provided. - uint64 index = 5; - - // The source of truth for the balance calculation. - BalanceSource balance_source = 6; - enum BalanceSource { - // The account's balance could not be determined. This may be returned when - // the data source is unstable and a reliable balance cannot be determined. - BALANCE_SOURCE_UNKNOWN = 0; - // The account's balance was fetched directly from a finalized state on the - // blockchain. - BALANCE_SOURCE_BLOCKCHAIN = 1; - // The account's balance was calculated using cached values in OCP. Accuracy - // is only guaranteed when management_state is LOCKED. - BALANCE_SOURCE_CACHE = 2; - } - - // The balance in quarks, as observed by the OCP. This may not reflect the value - // on the blockchain and could be non-zero even if the account hasn't been created. - // Use balance_source to determine how this value was calculated. - uint64 balance = 7; - - // The state of the account as it pertains to the OCP's ability to manage funds. - ManagementState management_state = 8; - enum ManagementState { - // The state of the account is unknown. This may be returned when the - // data source is unstable and a reliable state cannot be determined. - MANAGEMENT_STATE_UNKNOWN = 0; - // OCP does not maintain a management state and won't move funds for this - // account. - MANAGEMENT_STATE_NONE = 1; - // The account is in the process of transitioning to the LOCKED state. - MANAGEMENT_STATE_LOCKING = 2; - // The account's funds are locked and OCP has co-signing authority. - MANAGEMENT_STATE_LOCKED = 3; - // The account is in the process of transitioning to the UNLOCKED state. - MANAGEMENT_STATE_UNLOCKING = 4; - // The account's funds are unlocked and OCP no longer has co-signing - // authority. The account must transition to the LOCKED state to have - // management capabilities. - MANAGEMENT_STATE_UNLOCKED = 5; - // The account is in the process of transitioning to the CLOSED state. - MANAGEMENT_STATE_CLOSING = 6; - // The account has been closed and doesn't exist on the blockchain. - // Subsequently, it also has a zero balance. - MANAGEMENT_STATE_CLOSED = 7; - } - - // The state of the account on the blockchain. - BlockchainState blockchain_state = 9; - enum BlockchainState { - // The state of the account is unknown. This may be returned when the - // data source is unstable and a reliable state cannot be determined. - BLOCKCHAIN_STATE_UNKNOWN = 0; - // The account does not exist on the blockchain. - BLOCKCHAIN_STATE_DOES_NOT_EXIST = 1; - // The account is created and exists on the blockchain. - BLOCKCHAIN_STATE_EXISTS = 2; - } - - // Whether an account is claimed. This only applies to relevant account types - // (eg. REMOTE_SEND_GIFT_CARD). - ClaimState claim_state = 10; - enum ClaimState { - // The account doesn't have a concept of being claimed, or the state - // could not be fetched by server. - CLAIM_STATE_UNKNOWN = 0; - // The account has not yet been claimed. - CLAIM_STATE_NOT_CLAIMED = 1; - // The account is claimed. Attempting to claim it will fail. - CLAIM_STATE_CLAIMED = 2; - // The account hasn't been claimed, but is expired. Funds will move - // back to the issuer. Attempting to claim it will fail. - CLAIM_STATE_EXPIRED = 3; - } - - // For account types used as an intermediary for sending money between two - // users (eg. REMOTE_SEND_GIFT_CARD), this represents the original exchange - // data used to fund the account. Over time, this value will become stale: - // 1. Exchange rates will fluctuate, so the total fiat amount will differ. - // 2. External entities can deposit additional funds into the account, so - // the balance, in quarks, may be greater than the original quark value. - // 3. The balance could have been received, so the total balance can show - // as zero. - transaction.v1.ExchangeData original_exchange_data = 11; - - // The token account's mint - common.v1.SolanaAccountId mint = 12; - - // Mint metadata for the token account's mint - currency.v1.Mint mint_metadata = 16; - - // Live mint reserve state, if applicable - currency.v1.VerifiedLaunchpadCurrencyReserveState live_reserve_state = 17; - - // Time the account was created, if available. For OCP accounts, this is - // the time of intent submission. Otherwise, for external accounts, it is - // the time created on the blockchain. - google.protobuf.Timestamp created_at = 13; - - // For REMOTE_SEND_GIFT_CARD, if requesting_owner was provided, was - // requesting_owner the issuer of the account. - bool is_gift_card_issuer = 14; - - // The USD cost basis for this account, which can be used to compute currency - // appreciation/depreciation - double usd_cost_basis = 15; -} diff --git a/definitions/opencode/protos/src/main/proto/common/v1/model.proto b/definitions/opencode/protos/src/main/proto/common/v1/model.proto deleted file mode 100644 index 301e5ae4ac..0000000000 --- a/definitions/opencode/protos/src/main/proto/common/v1/model.proto +++ /dev/null @@ -1,169 +0,0 @@ -syntax = "proto3"; - -package ocp.common.v1; - -option go_package = "github.com/code-payments/ocp-protobuf-api/generated/go/common/v1;common"; -option java_package = "com.codeinc.opencode.gen.common.v1"; -option objc_class_prefix = "CPBCommonV1"; - -import "google/protobuf/duration.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -// AccountType associates a type to an account, which infers how an account is used -// within the OCP ecosystem. -enum AccountType { - UNKNOWN = 0; - PRIMARY = 1; - REMOTE_SEND_GIFT_CARD = 2; - SWAP = 3; - ASSOCIATED_TOKEN_ACCOUNT = 4; - POOL = 5; -} - -// SolanaAccountId is a raw binary Ed25519 public key for a Solana account -message SolanaAccountId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// A Solana address lookup table used in versioned transactions -message SolanaAddressLookupTable { - common.v1.SolanaAccountId address = 1 [(validate.rules).message.required = true]; - - - - repeated common.v1.SolanaAccountId entries = 2 [(validate.rules).repeated = { - min_items: 1, - max_items: 256, - }]; - - -} - -// Transaction is a raw binary Solana transaction -message Transaction { - // Maximum size taken from: https://github.com/solana-labs/solana/blob/39b3ac6a8d29e14faa1de73d8b46d390ad41797b/sdk/src/packet.rs#L9-L13 - bytes value = 1 [(validate.rules).bytes = { - min_len: 1 - max_len: 1232 - }]; - - -} - -// Blockhash is a raw binary Solana blockchash -message Blockhash { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// Signature is a raw binary Ed25519 signature -message Signature { - bytes value = 1 [(validate.rules).bytes = { - min_len: 64 - max_len: 64 - }]; - - -} - -// IntentId is a client-side generated ID that maps to an intent to perform actions -// on the blockchain fulfilled by the OCP sequencer. -message IntentId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// SwapId is a client-side generated ID that maps to a swap. -message SwapId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// Hash is a raw binary 32 byte hash value -message Hash { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// UUID is a 16 byte UUID value -message UUID { - bytes value = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 16 - }]; - - -} - -// Request is a generic wrapper for gRPC requests -message Request { - string version = 1; - string service = 2; - string method = 3; - bytes body = 4; -} - -// Response is a generic wrapper for gRPC responses -message Response { - Result result = 1; - - bytes body = 2; - string message = 3; - - enum Result { - OK = 0; - ERROR = 1; - } -} - -message ServerPing { - // Timestamp the ping was sent on the stream, for client to get a sense - // of potential network latency - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; - - - - // The delay server will apply before sending the next ping - google.protobuf.Duration ping_delay = 2 [(validate.rules).duration.required = true]; - - -} - -message ClientPong { - // Timestamp the Pong was sent on the stream, for server to get a sense - // of potential network latency - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; - - -} - -enum Interval { - RAW = 0; - SECOND = 1; - MINUTE = 2; - HOUR = 3; - DAY = 4; - WEEK = 5; -} diff --git a/definitions/opencode/protos/src/main/proto/currency/v1/ocp_currency_service.proto b/definitions/opencode/protos/src/main/proto/currency/v1/ocp_currency_service.proto deleted file mode 100644 index b2c9589c7d..0000000000 --- a/definitions/opencode/protos/src/main/proto/currency/v1/ocp_currency_service.proto +++ /dev/null @@ -1,720 +0,0 @@ -syntax = "proto3"; - -package ocp.currency.v1; - -option go_package = "github.com/code-payments/ocp-protobuf-api/generated/go/currency/v1;currency"; -option java_package = "com.codeinc.opencode.gen.currency.v1"; -option objc_class_prefix = "CPBCurrencyV1"; - -import "common/v1/model.proto"; -import "validate/validate.proto"; -import "google/protobuf/timestamp.proto"; - -service Currency { - // GetMints gets mint account metadata by address - rpc GetMints(GetMintsRequest) returns (GetMintsResponse); - - // GetHistoricalMintData returns historical market data for a mint - rpc GetHistoricalMintData(GetHistoricalMintDataRequest) returns (GetHistoricalMintDataResponse); - - // StreamLiveMintData streams live mint data for a set of mints - rpc StreamLiveMintData(stream StreamLiveMintDataRequest) returns (stream StreamLiveMintDataResponse); - - // Launch launches a new currency on the launchpad - rpc Launch(LaunchRequest) returns (LaunchResponse); - - // UpdateIcon uploads and updates the icon for a currency - rpc UpdateIcon(UpdateIconRequest) returns (UpdateIconResponse); - - // UpdateMetadata updates mutable metadata for a currency - rpc UpdateMetadata(UpdateMetadataRequest) returns (UpdateMetadataResponse); - - // Discover returns a set of currencies to discover - rpc Discover(DiscoverRequest) returns (stream DiscoverResponse); - - // CheckAvailability checks whether a currency name is available for launch - rpc CheckAvailability(CheckAvailabilityRequest) returns (CheckAvailabilityResponse); -} - -message GetMintsRequest { - repeated common.v1.SolanaAccountId addresses = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; - - -} - -message GetMintsResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - } - - map metadata_by_address = 2; -} - -message GetHistoricalMintDataRequest { - // The mint address to get historical data for - common.v1.SolanaAccountId address = 1 [(validate.rules).message.required = true]; - - - - // The currency code for the returned market data (e.g., "usd") - string currency_code = 2 [(validate.rules).string = { - pattern: "^[a-z]{3,4}$" - }]; - - - - oneof range { - option (validate.required) = true; - - PredefinedRange predefined_range = 3; - } -} - -message GetHistoricalMintDataResponse { - Result result = 1; - enum Result { - OK = 0; - - // The requested mint or currency was not found - NOT_FOUND = 1; - - // No data available for the requested time range - MISSING_DATA = 2; - } - - repeated HistoricalMintData data = 2; -} - -message StreamLiveMintDataRequest { - oneof type { - option (validate.required) = true; - - Request request = 1; - common.v1.ClientPong pong = 2; - } - - message Request { - // The set of mints to receive live data against. To update the set of mints, - // close the current stream and open a new one with the new set. - repeated common.v1.SolanaAccountId mints = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; - - - } -} - -message StreamLiveMintDataResponse { - oneof type { - option (validate.required) = true; - - LiveData data = 1; - common.v1.ServerPing ping = 2; - } - - message LiveData { - oneof type { - option (validate.required) = true; - - VerifiedCoreMintFiatExchangeRateBatch core_mint_fiat_exchange_rates = 1; - VerifiedLaunchapdCurrencyReserveStateBatch launchpad_currency_reserve_states = 2; - } - } -} - -message Mint { - // Token mint address - common.v1.SolanaAccountId address = 1 [(validate.rules).message.required = true]; - - - - // The number of decimals configured for the mint - uint32 decimals = 2; - - // Currency name - string name = 3 [(validate.rules).string = { - min_len: 1, - max_len: 32, - }]; - - - - // Currency ticker symbol - string symbol = 4 [(validate.rules).string = { - min_len: 1, - max_len: 8, - }]; - - - - // Currency description - string description = 5 [(validate.rules).string = { - min_len: 1, - max_len: 4096, - }]; - - - - // URL to currency image - string image_url = 6 [(validate.rules).string = { - min_len: 1, - max_len: 1024, - }]; - - - - // Available when a VM exists for the given mint, and can be used for deriving - // VM deposit PDAs - // - // Note: Only currencies with a VM are useable for payments - VmMetadata vm_metadata = 7; - - // Available when created by the launchpad via the currency creator program, and - // can be used for calculating price, market cap, etc. based on the exponential - // bonding curve - LaunchpadMetadata launchpad_metadata = 8; - - // Timestamp the currency was created - google.protobuf.Timestamp created_at = 9 [(validate.rules).timestamp.required = true]; - - - - // Social links for this currency - repeated SocialLink social_links = 10 [(validate.rules).repeated = { - min_items: 0 - max_items: 32 // Arbitrary - }]; - - - - // Bill customization for this currency. Use the default if not provided - BillCustomization bill_customization = 11; - - // Holder metrics. This is surfaced where needed (e.g. only in the Discover RPC) - HolderMetrics holder_metrics = 12; - - // Market cap metrics. This is surfaced where needed (e.g. only in the Discover RPC) - MarketCapMetrics market_cap_metrics = 13; -} - -message VmMetadata { - // VM address - common.v1.SolanaAccountId vm = 1 [(validate.rules).message.required = true]; - - - - // Authority that subsidizes and authorizes all transactions against the VM - common.v1.SolanaAccountId authority = 2 [(validate.rules).message.required = true]; - - - - // Lock duration of Virtual Timelock Accounts on the VM, currently hardcoded - // to 21 days - uint32 lock_duration_in_days = 3 [(validate.rules).uint32.const = 21]; - - - - // VM omnibus address - common.v1.SolanaAccountId omnibus = 4 [(validate.rules).message.required = true]; - - -} - -message LaunchpadMetadata { - // The address of the currency config - common.v1.SolanaAccountId currency_config = 1 [(validate.rules).message.required = true]; - - - - // The address of the liquidity pool - common.v1.SolanaAccountId liquidity_pool = 2 [(validate.rules).message.required = true]; - - - - // The random seed used during currency creation - common.v1.SolanaAccountId seed = 3 [(validate.rules).message.required = true]; - - - - // The address of the authority for the currency - common.v1.SolanaAccountId authority = 4 [(validate.rules).message.required = true]; - - - - // The address where this mint's tokens are locked against the liquidity pool - common.v1.SolanaAccountId mint_vault = 5 [(validate.rules).message.required = true]; - - - - // The address where core mint tokens are locked against the liquidity pool - common.v1.SolanaAccountId core_mint_vault = 6 [(validate.rules).message.required = true]; - - - - // Current circulating mint token supply in quarks - uint64 supply_from_bonding = 7; - - // Precent fee for sells in basis points, currently hardcoded to 1% - uint32 sell_fee_bps = 8 [(validate.rules).uint32.const = 100]; - - - - // The current price in USD - double price = 9; - - // The current market capitalization in USD - double market_cap = 10; -} - -message HistoricalMintData { - // Timestamp for this data point - google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true]; - - - - // Market capitalization at this point in time - double market_cap = 2; -} - -message CoreMintFiatExchangeRate { - // The currency code for the fiat exchange rate - string currency_code = 1 [(validate.rules).string = { - pattern: "^[a-z]{3,4}$" - }]; - - - - // The exchange rate against the core mint - double exchange_rate = 2; - - // Timestamp for this data point - google.protobuf.Timestamp timestamp = 3 [(validate.rules).timestamp.required = true]; - - -} - -// CoreMintFiatExchangeRate with a server signature for proof for use in a payment -message VerifiedCoreMintFiatExchangeRate { - CoreMintFiatExchangeRate exchange_rate = 1 [(validate.rules).message.required = true]; - - - - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - -} - -message VerifiedCoreMintFiatExchangeRateBatch { - repeated VerifiedCoreMintFiatExchangeRate exchange_rates = 2 [(validate.rules).repeated = { - min_items: 1 - max_items: 256 // Arbitrary - }]; - - -} - -message LaunchpadCurrencyReserveState { - // Launchpad currency mint address - common.v1.SolanaAccountId mint = 1 [(validate.rules).message.required = true]; - - - - // Current circulating mint token supply in quarks - uint64 supply_from_bonding = 2; - - // Timestamp for this data point - google.protobuf.Timestamp timestamp = 3 [(validate.rules).timestamp.required = true]; - - -} - -// LaunchpadCurrencyReserveState with a server signature for proof for use in a payment -message VerifiedLaunchpadCurrencyReserveState { - LaunchpadCurrencyReserveState reserve_state = 1 [(validate.rules).message.required = true]; - -; - - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - -; -} - -message VerifiedLaunchapdCurrencyReserveStateBatch { - repeated VerifiedLaunchpadCurrencyReserveState reserve_states = 2 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; - - -} - -message SocialLink { - oneof type { - option (validate.required) = true; - - Website website = 1; - X x = 2; - Telegram telegram = 3; - Discord discord = 4; - } - - message Website { - string url = 1 [(validate.rules).string = { - uri: true, - max_len: 2048, - }]; - - - } - - message X { - string username = 1 [(validate.rules).string = { - min_len: 1, - max_len: 15, - pattern: "^[a-zA-Z0-9_]+$", - }]; - - - } - - message Telegram { - // Telegram username (without the @ prefix) - string username = 1 [(validate.rules).string = { - min_len: 1, - max_len: 32, - pattern: "^[a-zA-Z0-9_]+$", - }]; - - - } - - message Discord { - // Discord invite code (e.g. "abc123" from discord.gg/abc123) - string invite_code = 1 [(validate.rules).string = { - min_len: 1, - max_len: 32, - pattern: "^[a-zA-Z0-9]+$", - }]; - - - } -} - -message BillCustomization { - // Bill background colors (from top to bottom) - repeated Color colors = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 3 - }]; - - -} - -message Color { - // Hex colour value (e.g. "#19191A") - string hex = 1 [(validate.rules).string = { - pattern: "^#[0-9a-fA-F]{6}$" - }]; - - -} - -message HolderMetrics { - // The current number of holders for a currency - uint64 current_holders = 1; - - repeated DeltaHolders holder_deltas = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 4 - }]; - - - - message DeltaHolders { - // Predefined range where delta is calculated from - PredefinedRange range = 1; - - // Net holders within the time range - int64 delta = 2; - } -} - -message MarketCapMetrics { - // The current market capitalization in USD for a currency - double current_market_cap = 1; - - repeated DeltaMarketCap market_cap_deltas = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 4 - }]; - - - - message DeltaMarketCap { - // Predefined range where delta is calculated from - PredefinedRange range = 1; - - // Net change in market capitalization in USD within the time range - double delta = 2; - } -} - -message LaunchRequest { - // The owner account launching the currency - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(LaunchRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - - - // The name of the currency to launch. Must be printable ASCII with no - // leading or trailing spaces. - string name = 3 [(validate.rules).string = { - min_len: 1, - max_len: 32, - // [!-~] = printable ASCII excluding space; [ -~] = printable ASCII including space - pattern: "^[!-~]([ -~]*[!-~])?$", - }]; - - - - // The ticker symbol for the currency. Must be printable ASCII with no - // spaces. If not provided, a default will be generated using the currency - // name. - string symbol = 4 [(validate.rules).string = { - max_len: 8, - // [!-~] = printable ASCII excluding space - pattern: "^[!-~]*$", - }]; - - - - // Optional description - string description = 5 [(validate.rules).string = { - max_len: 4096, - }]; - - - - // Optional bill customization. If not provided, a default will be set. - BillCustomization bill_customization = 6; - - // The raw image data for the icon. If not provided, a default will be set. - bytes icon = 7 [(validate.rules).bytes = { - max_len: 1048576, // 1 MB - }]; - - - - // Attestation that the name passed moderation - ModerationAttestation name_moderation_attestation = 8 [(validate.rules).message.required = true]; - - - - // Attestation that the symbol, if provided, passed moderation - ModerationAttestation symbol_moderation_attestation = 9; - - // Attestation that the descritpion, if provided, passed moderation - ModerationAttestation description_moderation_attestation = 10; - - // Attestation that the icon image, if provided, passed moderation - ModerationAttestation icon_moderation_attestation = 11; -} - -message LaunchResponse { - Result result = 1; - enum Result { - OK = 0; - // The launch was denied - DENIED = 1; - // A similar currency already exists - NAME_EXISTS = 2; - // Provided icon is invalid - INVALID_ICON = 3; - } - - // The mint address of the launched currency on success - common.v1.SolanaAccountId mint = 2; -} - -message UpdateIconRequest { - // The owner account of the currency - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(UpdateIconRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - - - // The mint address of the currency to update - common.v1.SolanaAccountId mint = 3 [(validate.rules).message.required = true]; - - - - // The raw image data for the icon - bytes icon = 4 [(validate.rules).bytes = { - min_len: 1, - max_len: 1048576, // 1 MB - }]; - - - - // Attestation that the icon image passed moderation - ModerationAttestation moderation_attestation = 5 [(validate.rules).message.required = true]; - - -} - -message UpdateIconResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - DENIED = 2; - INVALID_ICON = 3; - } -} - -message UpdateMetadataRequest { - // The owner account of the currency - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(UpdateMetadataRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - - - // The mint address of the currency to update - common.v1.SolanaAccountId mint = 3 [(validate.rules).message.required = true]; - - - - // Updated currency description. If not provided, description is not updated. - DescriptionUpdate new_description = 4; - - // Updated bill customization. If not provided, bill customization is not updated. - BillCustomizationUpdate new_bill_customization = 5; - - // Updated social links. This replaces the entire set of social links. If not - // provided, social links are not updated. - SocialLinksUpdate new_social_links = 6; - - message DescriptionUpdate { - string value = 1 [(validate.rules).string = { - min_len: 1, - max_len: 4096, - }]; - - - - // Attestation that the description passed moderation - ModerationAttestation moderation_attestation = 2 [(validate.rules).message.required = true]; - - - } - - message BillCustomizationUpdate { - BillCustomization value = 1 [(validate.rules).message.required = true]; - - - } - - message SocialLinksUpdate { - repeated SocialLink value = 1 [(validate.rules).repeated = { - min_items: 0 - max_items: 32 // Arbitrary - }]; - - - } -} - -message UpdateMetadataResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - DENIED = 2; - } -} - -message DiscoverRequest { - Category category = 1; - enum Category { - POPULAR = 0; - NEW = 1; - } -} - -message DiscoverResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - } - - repeated Mint mints = 2 [(validate.rules).repeated = { - min_items: 0 - max_items: 1024 // Arbitrary - }]; - - -} - -message CheckAvailabilityRequest { - // The currency name to check availability for - string name = 1 [(validate.rules).string = { - min_len: 1, - max_len: 32, - // [!-~] = printable ASCII excluding space; [ -~] = printable ASCII including space - pattern: "^[!-~]([ -~]*[!-~])?$", - }]; - - -} - -message CheckAvailabilityResponse { - Result result = 1; - enum Result { - OK = 0; - } - - // Whether the name is available for use - bool is_available = 2; -} - -enum PredefinedRange { - ALL_TIME = 0; - LAST_DAY = 1; - LAST_WEEK = 2; - LAST_MONTH = 3; - LAST_YEAR = 4; -} - -message ModerationAttestation { - bytes raw_value = 1 [(validate.rules).bytes = { - min_len: 1, - max_len: 4096, - }]; - - -} diff --git a/definitions/opencode/protos/src/main/proto/messaging/v1/ocp_messaging_service.proto b/definitions/opencode/protos/src/main/proto/messaging/v1/ocp_messaging_service.proto deleted file mode 100644 index 733c366bbe..0000000000 --- a/definitions/opencode/protos/src/main/proto/messaging/v1/ocp_messaging_service.proto +++ /dev/null @@ -1,282 +0,0 @@ -syntax = "proto3"; - -package ocp.messaging.v1; - -option go_package = "github.com/code-payments/ocp-protobuf-api/generated/go/messaging/v1;messaging"; -option java_package = "com.codeinc.opencode.gen.messaging.v1"; -option objc_class_prefix = "CPBMessagingV1"; - -import "common/v1/model.proto"; -import "currency/v1/ocp_currency_service.proto"; -import "transaction/v1/ocp_transaction_service.proto"; -import "validate/validate.proto"; - -service Messaging { - // OpenMessageStream opens a stream of messages. Messages are routed using the - // public key of a rendezvous keypair derived by both the sender and the - // recipient of the messages. The sender may be a client or server. - // - // Messages are expected to be acked once they have been processed by the client. - // Ack'd messages will no longer be delivered on future OpenMessageStream calls, - // and are eligible for deletion from the service. Clients should, however, handle - // duplicate delivery of messages. - // - // For giving/grabbing a bill, the expected flow is as follows: - // 1. The payment sender creates a multi-mint cash scan code. - // 2. The payment sender calls OpenMessageStream on the rendezvous public key. - // 3. The payment sender uses SendMessage to send the mint in a RequestToGiveBill message. - // 4. The payment sender shows the bill with the scan code containing the rendezvous public key. - // 4. The payment recipient scans the code. - // 5. The payment recipient uses PollMessages to get the RequestToGiveBill message from part 3. - // 6. The payment recipient sends the destination address in a RequestToGrabBill message. - // 7. The payment sender receives the RequestToGrabBill message in real time, submits the intent - // for the payment to the provided destination, and then closes the stream. - rpc OpenMessageStream(OpenMessageStreamRequest) returns (stream OpenMessageStreamResponse); - - // OpenMessageStreamWithKeepAlive is like OpenMessageStream, but enables a ping/pong - // keepalive to determine the health of the stream at both the client and server. - // - // The keepalive protocol is as follows: - // 1. Client initiates a stream by sending an OpenMessageStreamRequest. - // 2. Upon stream initialization, server begins the keepalive protocol. - // 3. Server sends a ping to the client. - // 4. Client responds with a pong as fast as possible, making note of - // the delay for when to expect the next ping. - // 5. Steps 3 and 4 are repeated until the stream is explicitly terminated - // or is deemed to be unhealthy. - // - // Client notes: - // * Client should be careful to process messages async, so any responses to pings are - // not delayed. - // * Clients should implement a reasonable backoff strategy upon continued timeout failures. - // * Clients that abuse pong messages may have their streams terminated by server. - // - // At any point in the stream, server will respond with messages in real time as - // they are observed. Messages sent over the stream should not affect the ping/pong - // protocol timings. Individual protocols for payment flows remain the same, and are - // documented in OpenMessageStream. - // - // Note: This API will enforce OpenMessageStreamRequest.signature is set as part of migration - // to this newer protocol - rpc OpenMessageStreamWithKeepAlive(stream OpenMessageStreamWithKeepAliveRequest) returns (stream OpenMessageStreamWithKeepAliveResponse); - - // PollMessages is like OpenMessageStream, but uses a polling flow for receiving - // messages. Updates are not real-time and depedent on the polling interval. - // This RPC supports all message types. - // - // This is a temporary RPC until OpenMessageStream can be built out generically on - // both client and server, while supporting things like multiple listeners. - rpc PollMessages(PollMessagesRequest) returns (PollMessagesResponse); - - // AckMessages acks one or more messages that have been successfully delivered to - // the client. - rpc AckMessages(AckMessagesRequest) returns (AckMesssagesResponse); - - // SendMessage sends a message. - rpc SendMessage(SendMessageRequest) returns (SendMessageResponse); -} - -message OpenMessageStreamRequest { - RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(OpenMessageStreamRequest) using rendezvous_key. - // - // todo: Make required once clients migrate - common.v1.Signature signature = 2 [(validate.rules).message.required = false]; - - -} - -message OpenMessageStreamResponse { - repeated Message messages = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 - }]; - - -} - -message OpenMessageStreamWithKeepAliveRequest { - oneof request_or_pong { - option (validate.required) = true; - - OpenMessageStreamRequest request = 1; - common.v1.ClientPong pong = 2; - } -} - -message OpenMessageStreamWithKeepAliveResponse { - oneof response_or_ping { - option (validate.required) = true; - - OpenMessageStreamResponse response = 1; - common.v1.ServerPing ping = 2; - } -} - -message PollMessagesRequest { - RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(PollMessagesRequest) using rendezvous_key. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - -} - -message PollMessagesResponse { - repeated Message messages = 1 [(validate.rules).repeated = { - min_items: 0 - max_items: 1024 - }]; - - -} - -message AckMessagesRequest { - RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true]; - - - - repeated MessageId message_ids = 2 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 - }]; - - -} - -message AckMesssagesResponse { - Result result = 1; - enum Result { - OK = 0; - } -} - -message SendMessageRequest { - // The message to send. Types of messages clients can send are restricted. - Message message = 1 [(validate.rules).message.required = true]; - - - - // The rendezvous key that the message should be routed to. - RendezvousKey rendezvous_key = 2 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(Message) using the PrivateKey of the keypair. - common.v1.Signature signature = 3 [(validate.rules).message.required = true]; - - -} - -message SendMessageResponse { - Result result = 1; - enum Result { - OK = 0; - NO_ACTIVE_STREAM = 1; - } - - // Set if result == OK. - MessageId message_id = 2; -} - -// RendezvousKey is a unique key pair, typically derived from a scan code payload, -// which is used to establish a secure communication channel anonymously to coordinate -// a flow using messages. -message RendezvousKey { - bytes value = 1 [(validate.rules).bytes = { - min_len: 32 - max_len: 32 - }]; - - -} - -// MessageId identifies a message. It is only guaranteed to be unique when -// paired with a destination (i.e. the rendezvous public key). -message MessageId { - bytes value = 1 [(validate.rules).bytes = { - min_len: 16 - max_len: 16 - }]; - - -} - -// Request that a pulled out bill be sent to the requested address. -// -// This message type is only initiated by clients. -message RequestToGrabBill { - // Requestor is the virtual token account on the VM to which a payment - // should be sent. - common.v1.SolanaAccountId requestor_account = 1 [(validate.rules).message.required = true]; - - -} - -message RequestToGiveBillServerContext { - // Mint metadata for the bill's mint - currency.v1.Mint mint_metadata = 1 [(validate.rules).message.required = true]; - - -} - -// Request that a bill be given in the desired mint -// -// This message type is only initiated by clients. -message RequestToGiveBill { - // The mint that the bill will be received in - common.v1.SolanaAccountId mint = 1 [(validate.rules).message.required = true]; - - - - // The validated exchange data that was used to compute the fiat value of the give - // to support subsequent gives. Clients should be aware of timeouts and dismiss a - // bill if the threshold is met. - transaction.v1.VerifiedExchangeData exchange_data = 2; -} - -message Message { - // MessageId is the Id of the message. This ID is generated by the - // server, and will _always_ be set when receiving a message. - // - // Server generates the message to: - // 1. Reserve the ability for any future ID changes - // 2. Prevent clients attempting to collide message IDs. - MessageId id = 1 [(validate.rules).message.required = false]; - - - - // The signature sent from SendMessageRequest, which will be injected by server. - // This enables clients to ensure no MITM attacks were performed to hijack contents - // of the typed message. This is only applicable for messages not generated by server. - common.v1.Signature send_message_request_signature = 2 [(validate.rules).message.required = false]; - - - - oneof kind { - option (validate.required) = true; - - // - // Section: Cash - // - - RequestToGrabBill request_to_grab_bill = 3; - RequestToGiveBill request_to_give_bill = 4; - } - - // Additional server-provided context for messages sent by client - AdditionalServerContext additional_context = 5; -} - -message AdditionalServerContext { - oneof type { - option (validate.required) = true; - - RequestToGiveBillServerContext request_to_give_bill = 1; - } -} diff --git a/definitions/opencode/protos/src/main/proto/transaction/v1/ocp_transaction_service.proto b/definitions/opencode/protos/src/main/proto/transaction/v1/ocp_transaction_service.proto deleted file mode 100644 index 93b1ef69de..0000000000 --- a/definitions/opencode/protos/src/main/proto/transaction/v1/ocp_transaction_service.proto +++ /dev/null @@ -1,1703 +0,0 @@ -syntax = "proto3"; - -package ocp.transaction.v1; - -option go_package = "github.com/code-payments/ocp-protobuf-api/generated/go/transaction/v1;transaction"; -option java_package = "com.codeinc.opencode.gen.transaction.v1"; -option objc_class_prefix = "APBTransactionV1"; - -import "common/v1/model.proto"; -import "currency/v1/ocp_currency_service.proto"; -import "google/protobuf/timestamp.proto"; -import "validate/validate.proto"; - -service Transaction { - // SubmitIntent is the mechanism for client and server to agree upon a set of - // client actions to execute on the blockchain using the Code sequencer for - // fulfillment. - // - // Transactions and virtual instructions are never exchanged between client and server. - // Instead, the required accounts and arguments for instructions known to each actor are - // exchanged to allow independent and local construction. - // - // Client and server are expected to fully validate the intent. Proofs will - // be provided for any parameter requiring one. Signatures should only be - // generated after approval. - // - // This RPC is not a traditional streaming endpoint. It bundles two unary calls - // to enable DB-level transaction semantics. - // - // The high-level happy path flow for the RPC is as follows: - // 1. Client initiates a stream and sends SubmitIntentRequest.SubmitActions - // 2. Server validates the intent, its actions and metadata - // 3a. If there are transactions or virtual instructions requiring the user's signature, - // then server returns SubmitIntentResponse.ServerParameters - // 3b. Otherwise, server returns SubmitIntentResponse.Success and closes the - // stream - // 4. For each transaction or virtual instruction requiring the user's signature, the client - // locally constructs it, performs validation and collects the signature - // 5. Client sends SubmitIntentRequest.SubmitSignatures with the signature - // list generated from 4 - // 6. Server validates all signatures are submitted and are the expected values - // using locally constructed transactions or virtual instructions. - // 7. Server returns SubmitIntentResponse.Success and closes the stream - // In the error case: - // * Server will return SubmitIntentResponse.Error and close the stream - // * Client will close the stream - rpc SubmitIntent(stream SubmitIntentRequest) returns (stream SubmitIntentResponse); - - // GetIntentMetadata gets basic metadata on an intent. It can also be used - // to fetch the status of submitted intents. Metadata exists only for intents - // that have been successfully submitted. - rpc GetIntentMetadata(GetIntentMetadataRequest) returns (GetIntentMetadataResponse); - - // GetLimits gets limits for money moving intents for an owner account in an - // identity-aware manner - rpc GetLimits(GetLimitsRequest) returns (GetLimitsResponse); - - // CanWithdrawToAccount provides hints to clients for submitting withdraw intents. - // The RPC indicates if a withdrawal is possible, and how it should be performed. - rpc CanWithdrawToAccount(CanWithdrawToAccountRequest) returns (CanWithdrawToAccountResponse); - - // VoidGiftCard voids a gift card account by returning the funds to the funds back - // to the issuer via the auto-return action if it hasn't been claimed or already - // returned. - // - // Note: The RPC is idempotent. If the user already claimed/voided the gift card, or - // it is close to or is auto-returned, then OK will be returned. - rpc VoidGiftCard(VoidGiftCardRequest) returns (VoidGiftCardResponse); - - // StatefulSwap swaps tokens using a non-custodial state-management system. - // The high-level flow mirrors SubmitIntent closely. However, due to the - // unreliability of swaps, they do not fit within the broader intent system. - // This results in a few key differences: - // * Client is (potentially) involved in additional steps to complete the - // swap within the state machine - // * Transactions are submitted on a best-effort basis outside of the Code - // Sequencer - // * Balance changes are applied after the transaction has finalized - // - // Swap transaction signatures are collected up-front. They are executed once the - // swap is funded. - rpc StatefulSwap(stream StatefulSwapRequest) returns (stream StatefulSwapResponse); - - // StatelessSwap is like StatefulSwap, but without a state management system and a - // best-effort submission system. - rpc StatelessSwap(stream StatelessSwapRequest) returns (stream StatelessSwapResponse); - - // GetSwap gets metadata for a swap - rpc GetSwap(GetSwapRequest) returns (GetSwapResponse); - - // GetPendingSwaps gets swaps that are pending client actions which include: - // * Swaps that need a call to SubmitIntent to fund the VM swap PDA - rpc GetPendingSwaps(GetPendingSwapsRequest) returns (GetPendingSwapsResponse); -} - -// -// Request and Response Definitions -// - -message SubmitIntentRequest { - oneof request { - option (validate.required) = true; - - SubmitActions submit_actions = 1; - SubmitSignatures submit_signatures = 2; - } - - message SubmitActions { - // The globally unique client generated intent ID. Use the original intent - // ID when operating on actions that mutate the intent. - common.v1.IntentId id = 1 [(validate.rules).message.required = true]; - - - - // The verified owner account public key - common.v1.SolanaAccountId owner = 2 [(validate.rules).message.required = true]; - - - - // Additional metadata that describes the high-level intention - Metadata metadata = 3 [(validate.rules).message.required = true]; - - - - // The set of all ordered actions required to fulfill the intent - repeated Action actions = 4 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary - }]; - - - - // The signature is of serialize(SubmitActions) without this field set using the - // private key of the owner account. This provides an authentication mechanism - // to the RPC. - common.v1.Signature signature = 5 [(validate.rules).message.required = true]; - - - } - - message SubmitSignatures { - // The set of all signatures for each transaction or virtual instruction requiring - // signature from the authority accounts. - // - // The signature for a transaction is for the marshalled transaction. - // The signature for a virtual instruction is the hash of the marshalled instruction. - repeated common.v1.Signature signatures = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Assumes at most 1 client signatures per action - }]; - - - } -} - -message SubmitIntentResponse { - oneof response { - option (validate.required) = true; - - ServerParameters server_parameters = 1; - Success success = 2; - Error error = 3; - } - - message ServerParameters { - // The set of all server paremeters required to fill missing transaction - // or virtual instruction details. Server guarantees to provide a message - // for each client action in an order consistent with the received action - // list. - repeated ServerParameter server_parameters = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1024 // Arbitrary, but must match SubmitActions.actions.max_items - }]; - - - } - - message Success { - Code code = 1; - enum Code { - // The intent was successfully created and is now scheduled. - OK = 0; - } - } - - message Error { - Code code = 1; - enum Code { - // Denied by a guard (spam, money laundering, etc) - DENIED = 0; - // The intent is invalid. - INVALID_INTENT = 1; - // There is an issue with provided signatures. - SIGNATURE_ERROR = 2; - // Server detected client has stale state. - STALE_STATE = 3; - } - - repeated ErrorDetails error_details = 2; - } -} - -message GetIntentMetadataRequest { - // The intent ID to query - common.v1.IntentId intent_id = 1 [(validate.rules).message.required = true]; - - - - // The verified owner account public key when not signing with the rendezvous - // key. Only owner accounts involved in the intent can access the metadata. - common.v1.SolanaAccountId owner = 2; - - // The signature is of serialize(GetIntentStatusRequest) without this field set - // using the private key of the rendezvous or owner account. This provides an - // authentication mechanism to the RPC. - common.v1.Signature signature = 3 [(validate.rules).message.required = true]; - - -} - -message GetIntentMetadataResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - DENIED = 2; - } - - Metadata metadata = 2; -} - -message GetLimitsRequest { - // The owner account whose limits will be calculated. Any other owner accounts - // linked with the same identity of the owner will also be applied. - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(GetLimitsRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - - - // All transactions starting at this time will be incorporated into the consumed - // limit calculation. Clients should set this to the start of the current day in - // the client's current time zone (because server has no knowledge of this atm). - google.protobuf.Timestamp consumed_since = 3 [(validate.rules).timestamp.required = true]; - - -} - -message GetLimitsResponse { - Result result = 1; - enum Result { - OK = 0; - } - - // Send limits keyed by currency - map send_limits_by_currency = 2; - - // The amount of USD transacted since the consumption timestamp - double usd_transacted = 3 [(validate.rules).double.gte = 0]; - -; -} - -message CanWithdrawToAccountRequest { - // The destination account attempted to be withdrawn to. Can be an owner or - // token account. - common.v1.SolanaAccountId account = 1 [(validate.rules).message.required = true]; - - - - // The mint that the withdraw will be operating against - common.v1.SolanaAccountId mint = 2 [(validate.rules).message.required = true]; - -; -} - -message CanWithdrawToAccountResponse { - // Server-controlled flag to indicate if the account can be withdrawn to. - // There are several reasons server may deny it, including: - // - Wrong type of Code account - // - Unsupported external account type (eg. token account but of the wrong mint) - // This is guaranteed to be false when account_type = Unknown. - bool is_valid_payment_destination = 1; - - // Metadata so the client knows how to withdraw to the account. Server cannot - // provide precalculated addresses in this response to maintain non-custodial - // status. - AccountType account_type = 2; - enum AccountType { - Unknown = 0; // Server cannot determine - TokenAccount = 1; // Client uses the address as is in SubmitIntent - OwnerAccount = 2; // Client locally derives the ATA to use in SubmitIntent - } - - // ATA requires initialization before the withdrawal can occur. Server may not - // subsidize the account creation, so a fee may be required. - bool requires_initialization = 3; - - // The CREATE_ON_SEND_WITHDRAWAL fee, in USD, that must be paid in order to - // submit a withdrawal to subsidize the creation of the account at time of - // send. The user must explicitly agree to this fee amount before submitting - // the intent. - // - // This can be set when requires_initialization = true if server decides to - // not subsidize the token account creation. - // - // Note: The fee is always paid in the target mint. - ExchangeDataWithoutRate fee_amount = 4; -} - -message VoidGiftCardRequest { - // The owner account that issued the gift card account - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The vault of the gift card account to void - common.v1.SolanaAccountId gift_card_vault = 2 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(VoidGiftCardRequest) without this field set using - // the private key of the owner account. This provides an authentication mechanism - // to the RPC. - common.v1.Signature signature = 3 [(validate.rules).message.required = true]; - - -} - -message VoidGiftCardResponse { - Result result = 1; - enum Result { - OK = 0; - // The owner account didn't issue the gift card accoun - DENIED = 1; - // A different owner account than the issuer claimed the gift card - CLAIMED_BY_OTHER_USER = 2; - // The gift card doesn't exist - NOT_FOUND = 3; - } -} - -message StatefulSwapRequest { - oneof request { - option (validate.required) = true; - - Initiate initiate = 1; - SubmitSignatures submit_signatures = 2; - } - - message Initiate { - oneof kind { - option (validate.required) = true; - - ReserveSwapClientParameters reserve = 1; - CoinbaseStableSwapperClientParameters stablecoin = 2; - } - - // Client parameters for starting swaps against the Reserve contract - message ReserveSwapClientParameters { - // The unique ID for this swap randomly generated on client - common.v1.SwapId id = 1 [(validate.rules).message.required = true]; - - - - // The source mint that will be swapped from - common.v1.SolanaAccountId from_mint = 2 [(validate.rules).message.required = true]; - - - - // The destination mint that will be swapped to - common.v1.SolanaAccountId to_mint = 3 [(validate.rules).message.required = true]; - - - - // The amount to swap from the source mint in quarks. - uint64 swap_amount = 4 [(validate.rules).uint64.gt = 0]; - - - - // Where "amount" of "from_mint" will be sent from to the VM swap PDA - FundingSource funding_source = 5 [(validate.rules).enum = { - in: [1, 2, 3] // FUNDING_SOURCE_SUBMIT_INTENT, FUNDING_SOURCE_EXTERNAL_WALLET, FUNDING_SOURCE_COINBASE_ONRAMP - }]; - - - - // The ID of the "transaction" to lookup funding state. - // - // For FUNDING_SOURCE_SUBMIT_INTENT, this value is the base58 encoded intent ID. - // For FUNDING_SOURCE_EXTERNAL_WALLET, this value is the base58 encoded transaction signature. - // For FUNDING_SOURCE_COINBASE_ONRAMP, this value is the order ID - string funding_id = 6 [(validate.rules).string = { - min_len: 32, - max_len: 88, - }]; - - - - // The fee amount to pay for this swap - // - // Note: Amounts are coordinated outside this RPC for user verifications - // and validated in this RPC. - uint64 fee_amount = 7; - - // Verified exchange data for flows that require a specific fiat value - // over the full amount (swap + fee). For intent fundings, it's expected - // that this exchange data will be used. - // - // Required for the following flows: - // - Initializing a new reserve currency outside of the core mint - VerifiedExchangeData full_amount_exchange_data = 8; - } - - // Client parameters for starting swaps against the Coinbase Stable Swaper program - message CoinbaseStableSwapperClientParameters { - // The unique ID for this swap randomly generated on client - common.v1.SwapId id = 1 [(validate.rules).message.required = true]; - - - - // The source mint that will be swapped from - // - // Note: Currently always the core mint - common.v1.SolanaAccountId from_mint = 2 [(validate.rules).message.required = true]; - - - - // The destination mint that will be swapped to - common.v1.SolanaAccountId to_mint = 3 [(validate.rules).message.required = true]; - - - - // The amount to swap from the source mint in quarks. - uint64 swap_amount = 4 [(validate.rules).uint64.gt = 0]; - - - - // Where "amount" of "from_mint" will be sent from to the VM swap PDA - FundingSource funding_source = 5 [(validate.rules).enum = { - in: [1] // FUNDING_SOURCE_SUBMIT_INTENT - }]; - - - - // The ID of the "transaction" to lookup funding state. - // - // For FUNDING_SOURCE_SUBMIT_INTENT, this value is the base58 encoded intent ID. - string funding_id = 6 [(validate.rules).string = { - min_len: 32, - max_len: 44, - }]; - - - - // Destination owner account where from_mint tokens will land. Use - // CanWithdrawToAccountResponse to determine if an account is an owner. - common.v1.SolanaAccountId destination_owner = 7 [(validate.rules).message.required = true]; - - - - // The fee amount to pay for this swap, which should be exactly - // CanWithdrawToAccountResponse.fee_amount - uint64 fee_amount = 8; - } - - // The owner account starting the swap - common.v1.SolanaAccountId owner = 9 [(validate.rules).message.required = true]; - - - - // The user authority account that will sign to authorize the swap. - // - // For Reserve contract buy/sell flows against existing currencies, this must be a random one-time use account. - // For Reserve contract buy flows against new currencies, this must be the owner account that is the currency creator. - // For Coinbase Stable Swapper swap flows, this must be a random one-time use account. - common.v1.SolanaAccountId swap_authority = 10 [(validate.rules).message.required = true]; - - - - // The signature of serialize(VerifiedSwapMetadata) for the swap being initiated. - common.v1.Signature proof_signature = 11 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(StatefulSwapRequest.Initiate) without this field - // set using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 12 [(validate.rules).message.required = true]; - - - } - - message SubmitSignatures { - // The signatures for the locally constructed swap transaction: - // - owner is at index 0 - // - swap_authority is at index 1 - repeated common.v1.Signature transaction_signatures = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 2 - }]; - - - } -} - -message StatefulSwapResponse { - oneof response { - option (validate.required) = true; - - ServerParameters server_parameters = 1; - Success success = 2; - Error error = 3; - } - - message ServerParameters { - oneof kind { - option (validate.required) = true; - - ReserveExistingCurrencyServerParameters reserve_existing_currency = 1; - ReserveNewCurrencyServerParameter reserve_new_currency = 2; - CoinbaseStableSwapperServerParameter stablecoin = 3; - } - - // Server parameters when executing stateful buy/sell flows against the - // Reserve contract against an existing currency - // - // Supported Solana transaction version: v0 - // - // Instruction formats: - // - // Buy Tokens (Core Mint -> Launchpad Currency Mint) without a buy fee: - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open Core Mint temporary account) - // 6. VM::TransferForSwap (Core Mint VM swap ATA -> Core Mint temporary account) - // 7. Reserve::BuyAndDepositIntoVm (bounded buy depositing to_mint tokens into the to_mint VM) - // 8. Token::CloseAccount (closes Core Mint temporary account) - // 9. VM::CloseSwapAccountIfEmpty (closes Core Mint VM swap ATA if empty) - // - // Buy Tokens (Core Mint -> Launchpad Currency Mint) with a buy fee, which - // is used when fee_amount is non-zero: - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open Core Mint temporary account) - // 6. VM::TransferForSwapWithFee (Core Mint VM swap ATA -> Core Mint temporary account (swap amount) and fee destination (fee amount)) - // 7. Reserve::BuyAndDepositIntoVm (bounded buy of the swap amount depositing to_mint tokens into the to_mint VM) - // 8. Token::CloseAccount (closes Core Mint temporary account) - // 9. VM::CloseSwapAccountIfEmpty (closes Core Mint VM swap ATA if empty) - // - // Sell Tokens (Launchpad Currency Mint -> Core Mint): - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open from_mint temporary account) - // 6. VM::TransferForSwap (from_mint VM swap ATA -> from_mint temporary account) - // 7. Reserve::SellAndDepositIntoVm (bounded sell depositing Core Mint into the Core Mint VM) - // 8. Token::CloseAccount (closes from_mint temporary account) - // 9. VM::CloseSwapAccountIfEmpty (closes from_mint swap PDA/ATA if empty) - // - // Swap Tokens (Launchpad Currency Mint -> Launchpad Currency Mint): - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open Core Mint temporary account) - // 6. AssociatedTokenAccount::CreateIdempotent (open from_mint temporary account) - // 7. VM::TransferForSwap (from_mint VM swap ATA -> from_mint temporary account) - // 8. Reserve::SellTokens (bounded sell transferring Core Mint into temporary account) - // 9. Reserve::BuyAndDepositIntoVm (unlimited buy depositing to_mint tokens into the to_mint VM) - // 10. Token::CloseAccount (closes Core Mint temporary account) - // 11. Token::CloseAccount (closes from_mint temporary account) - // 12. VM::CloseSwapAccountIfEmpty (closes from_mint VM swap ATA if empty) - message ReserveExistingCurrencyServerParameters { - // Subisdizer account that will be paying for the swap - common.v1.SolanaAccountId payer = 1 [(validate.rules).message.required = true]; - - - - // The nonce that is reserved for use in the swap transaction - common.v1.SolanaAccountId nonce = 2 [(validate.rules).message.required = true]; - - - - // The blockhash that is reserved for use in the swap transaction - common.v1.Blockhash blockhash = 3 [(validate.rules).message.required = true]; - - - - // ALTs that should be used when constructing the versioned transaction - repeated common.v1.SolanaAddressLookupTable alts = 4; - - // Compute unit limit provided to the ComputeBudget::SetComputeUnitLimit - // instruction. If the value is 0, then the instruction can be omitted. - uint32 compute_unit_limit = 5; - - // Compute unit price provided in the ComputeBudget::SetComputeUnitPrice - // instruction. If the value is 0, then the instruction can be omitted. - uint64 compute_unit_price = 6; - - // Value provided into the Memo::Memo instruction. If the value length is 0, - // then the instruction can be omitted. - string memo_value = 7 [(validate.rules).string.max_len = 64]; - - - - // The memory account where the destination virtual Timelock account lives - common.v1.SolanaAccountId memory_account = 8 [(validate.rules).message.required = true]; - - - - // The memory index where the destination virtual Timelock account lives - uint32 memory_index = 9; - - // Destination account where the buy fee should be paid. Only set when - // a non-zero fee_amount was provided in the client parameters. - common.v1.SolanaAccountId fee_destination = 10; - } - - // Server parameters when executing stateful buy flows against the - // Reserve contract against a new currency. Only the creator of the - // currency will be able to execute this flow. - // - // Supported Solana transaction version: v0 - // - // Instruction format (paying with Core Mint): - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. Reserve::InitializeCurrency - // 6. Reserve::InitializePool - // 7. VM::InitializeVm - // 8. AssociatedTokenAccount::CreateIdempotent (open owner's Core Mint ATA) - // 9. AssociatedTokenAccount::CreateIdempotent (open owner's to_mint VM Deposit ATA) - // 10. VM::TransferForSwapWithFee (Core Mint VM swap ATA -> owner's Core Mint ATA (swap amount) and fee destination (fee amount)) - // 11. Reserve::BuyTokens (limited buy transferring to_mint tokens into the to_mint VM Deposit ATA) - // 12. Token::CloseAccount (closes owner's Core Mint ATA) - // - // Instruction format (all other cases): - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open treasury's from_mint ATA) - // 6. VM::TransferForSwapWithFee (from_mint VM swap ATA -> treasury's from_mint ATA (swap + fee amount)) - // 7. Reserve::SellTokens (limited sell of the full swap + fee amount, transferring the Core Mint value into the fee destination) - // 8. Reserve::BuyTokens (limited buy funded by the treasury's Core Mint ATA, transferring to_mint tokens into the to_mint VM Deposit ATA) - // - // Note: The currency, VM and to_mint VM Deposit ATA will be initialized prior to - // executing this transaction. Atomicity cannot be guaranteed due to transaction - // size limits, which will be revisited when the v1 transaction format is released. - // - // Note: Client should verify that the new currency's mint address matches that derived - // from using these server parameters. - message ReserveNewCurrencyServerParameter { - // Subisdizer account that will be paying for the swap - common.v1.SolanaAccountId payer = 1 [(validate.rules).message.required = true]; - - - - // The nonce that is reserved for use in the swap transaction - common.v1.SolanaAccountId nonce = 2 [(validate.rules).message.required = true]; - - - - // The blockhash that is reserved for use in the swap transaction - common.v1.Blockhash blockhash = 3 [(validate.rules).message.required = true]; - - - - // ALTs that should be used when constructing the versioned transaction - repeated common.v1.SolanaAddressLookupTable alts = 4; - - // Compute unit limit provided to the ComputeBudget::SetComputeUnitLimit - // instruction. If the value is 0, then the instruction can be omitted. - uint32 compute_unit_limit = 5; - - // Compute unit price provided in the ComputeBudget::SetComputeUnitPrice - // instruction. If the value is 0, then the instruction can be omitted. - uint64 compute_unit_price = 6; - - // Value provided into the Memo::Memo instruction. If the value length is 0, - // then the instruction can be omitted. - string memo_value = 7 [(validate.rules).string.max_len = 64]; - - - - // The VM and currency authority - common.v1.SolanaAccountId authority = 8 [(validate.rules).message.required = true]; - - - - // The currency name - string name = 9 [(validate.rules).string = { - min_len: 1, - max_len: 32, - }]; - - - - // The currency symbol - string symbol = 10 [(validate.rules).string = { - min_len: 1, - max_len: 8, - }]; - - - - // The random seed value used to generate a unique currency of the given name - common.v1.SolanaAccountId seed = 11 [(validate.rules).message.required = true]; - - - - // Liquidity pool's percent sell fee in basis points - uint32 sell_fee_bps = 12 [(validate.rules).uint32.const = 100]; - - - - // The VM lock duration - uint32 vm_lock_duration_in_days = 13 [(validate.rules).uint32.const = 21]; - - - - // Destination account where fee should be paid - common.v1.SolanaAccountId fee_destination = 14 [(validate.rules).message.required = true]; - - - - // Server-controlled treasury for flows that require it - common.v1.SolanaAccountId treasury = 15; - - // The amount of core mint tokens used for purchase. Client should - // validate this is as expected based on a pre-coordinated amount - // accepted by the user. - uint64 treasury_purchase_amount = 16; - } - - // Server parameters when executing stateful swap flows against the - // Coinbase Stable Swapper program. - // - // Supported Solana transaction version: v0 - // - // Instruction format: - // 1. System::AdvanceNonce - // 2. [Optional] ComputeBudget::SetComputeUnitLimit - // 3. [Optional] ComputeBudget::SetComputeUnitPrice - // 4. [Optional] Memo::Memo - // 5. AssociatedTokenAccount::CreateIdempotent (open swap authority's from_mint ATA) - // 6. AssociatedTokenAccount::CreateIdempotent (open destination owner's to_mint ATA) - // 7. VM::TransferForSwapWithFee (from_mint VM swap ATA -> swap authority's from_mint ATA (swap amount) and fee destination (fee amount)) - // 8. CoinbaseStableSwapper::Swap (from_mint swap authority ATA -> to_mint destination owner ATA) - // 9. Token::CloseAccount (closes swap authority's from_mint ATA) - message CoinbaseStableSwapperServerParameter { - // Subisdizer account that will be paying for the swap - common.v1.SolanaAccountId payer = 1 [(validate.rules).message.required = true]; - - - - // The nonce that is reserved for use in the swap transaction - common.v1.SolanaAccountId nonce = 2 [(validate.rules).message.required = true]; - - - - // The blockhash that is reserved for use in the swap transaction - common.v1.Blockhash blockhash = 3 [(validate.rules).message.required = true]; - - - - // ALTs that should be used when constructing the versioned transaction - repeated common.v1.SolanaAddressLookupTable alts = 4; - - // Compute unit limit provided to the ComputeBudget::SetComputeUnitLimit - // instruction. If the value is 0, then the instruction can be omitted. - uint32 compute_unit_limit = 5; - - // Compute unit price provided in the ComputeBudget::SetComputeUnitPrice - // instruction. If the value is 0, then the instruction can be omitted. - uint64 compute_unit_price = 6; - - // Value provided into the Memo::Memo instruction. If the value length is 0, - // then the instruction can be omitted. - string memo_value = 7 [(validate.rules).string.max_len = 64]; - - - - // Destination account where fee should be paid - common.v1.SolanaAccountId fee_destination = 8 [(validate.rules).message.required = true]; - - - - // The CoinbaseStableSwapper liquidity pool's configured fee recipient, - // sourced from the on-chain LiquidityPool account. Required by the - // CoinbaseStableSwapper::Swap instruction. - common.v1.SolanaAccountId pool_fee_recipient = 9 [(validate.rules).message.required = true]; - - - } - } - - message Success { - Code code = 1; - enum Code { - OK = 0; - } - } - - message Error { - Code code = 1; - enum Code { - // Denied by a guard (spam, money laundering, etc) - DENIED = 0; - // There is an issue with the provided proof or transaction signatures - SIGNATURE_ERROR = 1; - // The swap metadata failed server-side validation - INVALID_SWAP = 2; - } - - repeated ErrorDetails error_details = 2; - } -} - -message StatelessSwapRequest { - oneof request { - option (validate.required) = true; - - Initiate initiate = 1; - SubmitSignatures submit_signatures = 2; - } - - message Initiate { - oneof kind { - option (validate.required) = true; - - CoinbaseStableSwapperClientParameters stablecoin = 1; - } - - // Client parameters for stateless swaps via the Coinbase Stable - // Swapper program. Source funds are drawn from the owner's source-mint - // ATA; destination is the owner's destination-mint VM Deposit ATA. - message CoinbaseStableSwapperClientParameters { - // The source mint that will be swapped from. - common.v1.SolanaAccountId from_mint = 1 [(validate.rules).message.required = true]; - - - - // The destination mint that will be swapped to. - common.v1.SolanaAccountId to_mint = 2 [(validate.rules).message.required = true]; - - - - // The amount to swap from the source mint in quarks. - uint64 swap_amount = 3 [(validate.rules).uint64.gt = 0]; - - - } - - // The owner account that owns the source ATA and the destination VM - // Deposit ATA. The owner is the sole client-side signer of the swap - // transaction. - common.v1.SolanaAccountId owner = 2 [(validate.rules).message.required = true]; - - - - // If true, server waits until the swap transaction is finalized before - // returning Success. If false, server returns Success as soon as the - // transaction is submitted to the cluster. - bool wait_for_finalization = 3; - - // The signature is of serialize(StatelessSwapRequest.Initiate) without - // this field set using the private key of the owner account. This - // provides an authentication mechanism to the RPC. - common.v1.Signature signature = 4 [(validate.rules).message.required = true]; - - - } - - message SubmitSignatures { - // The owner's signature over the locally constructed swap transaction. - repeated common.v1.Signature transaction_signatures = 1 [(validate.rules).repeated = { - min_items: 1 - max_items: 1 - }]; - - - } -} - -message StatelessSwapResponse { - oneof response { - option (validate.required) = true; - - ServerParameters server_parameters = 1; - Success success = 2; - Error error = 3; - } - - message ServerParameters { - oneof kind { - option (validate.required) = true; - - CoinbaseStableSwapperServerParameter stablecoin = 1; - } - - // Server parameters for executing stateless swap flows against the - // Coinbase Stable Swapper program. - // - // Supported Solana transaction version: v0 - // - // Instruction format: - // 1. [Optional] ComputeBudget::SetComputeUnitLimit - // 2. [Optional] ComputeBudget::SetComputeUnitPrice - // 3. [Optional] Memo::Memo - // 4. AssociatedTokenAccount::CreateIdempotent (open owner's to_mint VM Deposit ATA) - // 5. CoinbaseStableSwapper::Swap (owner's from_mint ATA -> owner's to_mint VM Deposit ATA) - message CoinbaseStableSwapperServerParameter { - // Subsidizer account that will pay the transaction fee. - common.v1.SolanaAccountId payer = 1 [(validate.rules).message.required = true]; - - - - // The Solana blockhash to set on the transaction. This is a - // regular recent blockhash, not a durable nonce. - common.v1.Blockhash blockhash = 2 [(validate.rules).message.required = true]; - - - - // ALTs that should be used when constructing the versioned transaction - repeated common.v1.SolanaAddressLookupTable alts = 3; - - // Compute unit limit provided to the ComputeBudget::SetComputeUnitLimit - // instruction. If the value is 0, then the instruction can be omitted. - uint32 compute_unit_limit = 4; - - // Compute unit price provided in the ComputeBudget::SetComputeUnitPrice - // instruction. If the value is 0, then the instruction can be omitted. - uint64 compute_unit_price = 5; - - // Value provided into the Memo::Memo instruction. If the value length is 0, - // then the instruction can be omitted. - string memo_value = 6 [(validate.rules).string.max_len = 64]; - - - - // The CoinbaseStableSwapper liquidity pool's configured fee recipient, - // sourced from the on-chain LiquidityPool account. Required by the - // CoinbaseStableSwapper::Swap instruction. - common.v1.SolanaAccountId pool_fee_recipient = 7 [(validate.rules).message.required = true]; - - - } - } - - message Success { - Code code = 1; - enum Code { - // Transaction was forwarded to the cluster. Returned when - // wait_for_finalization = false. - SUBMITTED = 0; - // Transaction was finalized on-chain. Returned when - // wait_for_finalization = true. - FINALIZED = 1; - } - - // The signature of the submitted swap transaction. Clients may use - // this to look up the transaction on-chain. - common.v1.Signature transaction_signature = 2 [(validate.rules).message.required = true]; - - - } - - message Error { - Code code = 1; - enum Code { - // Denied by a guard (spam, money laundering, etc) - DENIED = 0; - // There is an issue with the provided transaction signature - SIGNATURE_ERROR = 1; - // The swap parameters failed server-side validation (eg. - // unsupported mint pair, insufficient source balance, swap amount - // out of allowed range) - INVALID_SWAP = 2; - // The transaction was submitted but reverted on-chain, or its - // blockhash expired before confirmation. Only relevant when - // wait_for_finalization = true. - TRANSACTION_FAILED = 3; - } - - repeated ErrorDetails error_details = 2; - } -} - -message GetSwapRequest { - common.v1.SwapId id = 1 [(validate.rules).message.required = true]; - - - - common.v1.SolanaAccountId owner = 2 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(GetSwapRequest) without this field set using the - // private key of the owner account. This provides an authentication mechanism - // to the RPC. - common.v1.Signature signature = 3 [(validate.rules).message.required = true]; - - -} - -message GetSwapResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - DENIED = 2; - } - - SwapMetadata swap = 2; -} - -message GetPendingSwapsRequest{ - common.v1.SolanaAccountId owner = 1 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(GetPendingSwapsRequest) without this field set - // using the private key of the owner account. This provides an authentication - // mechanism to the RPC. - common.v1.Signature signature = 2 [(validate.rules).message.required = true]; - - -} - -message GetPendingSwapsResponse { - Result result = 1; - enum Result { - OK = 0; - NOT_FOUND = 1; - } - - repeated SwapMetadata swaps = 2 [(validate.rules).repeated = { - max_items: 1024 // Arbitrary - }]; - - -} - -// -// Metadata definitions -// - -// Metadata describes the high-level details of an intent -message Metadata { - oneof type { - option (validate.required) = true; - - OpenAccountsMetadata open_accounts = 1; - SendPublicPaymentMetadata send_public_payment = 2; - ReceivePaymentsPubliclyMetadata receive_payments_publicly = 3; - PublicDistributionMetadata public_distribution = 4; - } - - // Optional app-level metadata - AppMetadata app_metadata = 10; -} - -// Open a set of accounts -// -// Action Spec (User): -// -// for account in [PRIMARY] -// actions.push_back(OpenAccountAction(account)) -// -// Action Spec (Pool): -// -// for account in [POOL] -// actions.push_back(OpenAccountAction(account)) -message OpenAccountsMetadata { - AccountSet account_set = 1 [(validate.rules).enum.defined_only = true]; - - - enum AccountSet { - USER = 0; // Opens a set of user accounts - POOL = 1; // Opens a pool account - } - - - // The mint that this action will be operating against - common.v1.SolanaAccountId mint = 2 [(validate.rules).message.required = true]; - -; -} - -// Send a payment to a destination account publicly. -// -// Action Spec (Payment): -// -// actions = [NoPrivacyTransferAction(PRIMARY, destination, ExchangeData.Quarks)] -// -// Action Spec (Withdrawal): -// -// actions = [NoPrivacyTransferAction(PRIMARY, destination, ExchangeData.Quarks)] -// if destinationRequiresInitialization { -// actions[0].NoPrivacyTransferAction.ExchangeData.Quarks -= feeAmount -// actions.push_back(FeePaymentAction(PRIMARY, feeAccount, feeAmount)) -// } -// -// Action Spec (Indirect Send): -// -// actions = [ -// OpenAccountAction(REMOTE_SEND_GIFT_CARD), -// NoPrivacyTransferAction(PRIMARY, REMOTE_SEND_GIFT_CARD, ExchangeData.Quarks), -// NoPrivacyWithdrawAction(REMOTE_SEND_GIFT_CARD, PRIMARY, ExchangeData.Quarks, is_auto_return=true), -// ] -message SendPublicPaymentMetadata { - // The source account where funds will be sent from. Currently, this is always - // the user's primary account. - common.v1.SolanaAccountId source = 1 [(validate.rules).message.required = true]; - - - - // The destination token account to send funds to. - common.v1.SolanaAccountId destination = 2 [(validate.rules).message.required = true]; - - - - // Destination owner account, which is required for withdrawals that intend - // to create an ATA. Every other variation of this intent can omit this field. - common.v1.SolanaAccountId destination_owner = 3; - - // The exchange data of total funds being sent to the destination - oneof exchange_data { - option (validate.required) = true; - - // Provided by server for submitted intents - ExchangeData server_exchange_data = 4; - - // Provided by clients when submitting new intents - VerifiedExchangeData client_exchange_data = 8; - } - - // Is the payment a withdrawal? - bool is_withdrawal = 5; - - // Is the payment going to a new gift card? Note is_withdrawal must be false. - bool is_indirect_send = 6; - - // The mint that this intent will be operating against - common.v1.SolanaAccountId mint = 7 [(validate.rules).message.required = true]; - -; -} - -// Receive funds into a user-owned account publicly. All use cases of this intent -// close the account, so all funds must be moved. -// -// Action Spec (Indirect Send): -// -// actions = [NoPrivacyWithdrawAction(REMOTE_SEND_GIFT_CARD, PRIMARY, quarks)] -message ReceivePaymentsPubliclyMetadata { - // The remote send gift card to receive funds from - common.v1.SolanaAccountId source = 1 [(validate.rules).message.required = true]; - - - - // The exact amount of quarks being received - uint64 quarks = 2 [(validate.rules).uint64.gt = 0]; - - - - // Is the receipt of funds from a remote send gift card? Currently, this is - // the only use case for this intent and validation enforces the flag to true. - bool is_indirect_send = 3 [(validate.rules).bool.const = true]; - - - - // If is_indirect_send is true, the original exchange data that was provided as - // part of creating the gift card account. This is purely a server-provided value. - // SubmitIntent will disallow this being set. - ExchangeData exchange_data = 4; - - - // The mint that this intent will be operating against - common.v1.SolanaAccountId mint = 5 [(validate.rules).message.required = true]; - -; -} - -// Distribute funds from a pool account publicly to one or more user-owned accounts. -// -// Action Spec: -// -// for distribution in distributions[:len(distributions)-1] -// actions.push_back(NoPrivacyTransferAction(POOL, distribution.destination, distributions.quarks)) -// actions.push_back(NoPrivacyWithdrawAction(POOL, distributions[len(distributions)-1].destination, distributions[len(distributions)-1].quarks)) -// -// Notes: -// - All funds must distributed. The balance of the pool must be zero at the end of the intent -// - The pool is closed at the end of the intent via a NoPrivacyWithdrawAction -message PublicDistributionMetadata { - // The pool account to distribute from - common.v1.SolanaAccountId source = 1 [(validate.rules).message.required = true]; - - - - // The set of distributions - repeated Distribution distributions = 2 [(validate.rules).repeated = { - min_items: 1, - // todo: max-items? - }]; - -; - message Distribution { - // Destination where a portion of the pool's funds will be distributed. - // This must always be a primary account. - common.v1.SolanaAccountId destination = 1 [(validate.rules).message.required = true]; - - - - // The amount of funds to distribute to the destination - uint64 quarks = 2 [(validate.rules).uint64.gt = 0]; - - - } - - - // The mint that this intent will be operating against - common.v1.SolanaAccountId mint = 3 [(validate.rules).message.required = true]; - -; -} - -// -// Action Definitions -// - -// Action is a well-defined, ordered and small set of transactions or virtual instructions -// for a unit of work that the client wants to perform on the blockchain. Clients provide -// parameters known to them in the action. -message Action { - // The ID of this action, which is unique within an intent. It must match - // the index of the action's location in the SubmitAction's actions field. - uint32 id = 1; - - // The type of action to perform. - oneof type { - option (validate.required) = true; - - OpenAccountAction open_account = 2; - NoPrivacyTransferAction no_privacy_transfer = 3; - NoPrivacyWithdrawAction no_privacy_withdraw = 4; - FeePaymentAction fee_payment = 5; - } -} - -// No client signature required -message OpenAccountAction { - // The type of account, which will dictate its intended use - common.v1.AccountType account_type = 1 [(validate.rules).enum.not_in = 0]; - - - - // The owner of the account. For accounts liked to a user's 12 words, this is - // the verified parent owner account public key. All other account types should - // set this to the authority value. - common.v1.SolanaAccountId owner = 2 [(validate.rules).message.required = true]; - - - - // The index used to for accounts that are derived from owner - uint64 index = 3; - - // The public key of the private key that has authority over the opened token account - common.v1.SolanaAccountId authority = 4 [(validate.rules).message.required = true]; - - - - // The token account being opened - common.v1.SolanaAccountId token = 5 [(validate.rules).message.required = true]; - - - - // The signature is of serialize(OpenAccountAction) without this field set - // using the private key of the authority account. This provides a proof - // of authorization to link authority to owner. - common.v1.Signature authority_signature = 6 [(validate.rules).message.required = true]; - - - - - // The mint that this action will be operating against - common.v1.SolanaAccountId mint = 7 [(validate.rules).message.required = true]; - -; -} - -// Compact message signature required -message NoPrivacyTransferAction { - // The public key of the private key that has authority over source - common.v1.SolanaAccountId authority = 1 [(validate.rules).message.required = true]; - - - - // The source account where funds are transferred from - common.v1.SolanaAccountId source = 2 [(validate.rules).message.required = true]; - - - - // The destination account where funds are transferred to - common.v1.SolanaAccountId destination = 3 [(validate.rules).message.required = true]; - - - - // The quark amount to transfer - uint64 amount = 4 [(validate.rules).uint64.gt = 0]; - - - - - // The mint that this action will be operating against - common.v1.SolanaAccountId mint = 5 [(validate.rules).message.required = true]; - -; -} - -// Compact message signature required -message NoPrivacyWithdrawAction { - // The public key of the private key that has authority over source - common.v1.SolanaAccountId authority = 1 [(validate.rules).message.required = true]; - - - - // The source account where funds are transferred from - common.v1.SolanaAccountId source = 2 [(validate.rules).message.required = true]; - - - - // The destination account where funds are transferred to - common.v1.SolanaAccountId destination = 3 [(validate.rules).message.required = true]; - - - - // The quark amount to withdraw - uint64 amount = 4 [(validate.rules).uint64.gt = 0]; - - - - // Whether the account is closed afterwards. This is always true, since there - // are no current se cases to leave it open. - bool should_close = 5 [(validate.rules).bool.const = true]; - - - - // Whether this action is for an auto-return, which client allows server to defer - // scheduling at its own discretion to return funds back to the owner (to their primary - // account) that funded source. - bool is_auto_return = 6; - - - // The mint that this action will be operating against - common.v1.SolanaAccountId mint = 7 [(validate.rules).message.required = true]; - -; -} - -// Compact message signature required -message FeePaymentAction { - // The type of fee being operated on - FeeType type = 1 [(validate.rules).enum.not_in = 0]; - - - enum FeeType { - UNKNOWN = 0; - CREATE_ON_SEND_WITHDRAWAL = 1; // Server-defined fee for creating an external ATA on withdrawals on send - } - - // The public key of the private key that has authority over source - common.v1.SolanaAccountId authority = 2 [(validate.rules).message.required = true]; - - - - // The source account where funds are transferred from - common.v1.SolanaAccountId source = 3 [(validate.rules).message.required = true]; - - - - // The quark amount to transfer - uint64 amount = 4 [(validate.rules).uint64.gt = 0]; - - - - - // The mint that this action will be operating against - common.v1.SolanaAccountId mint = 5 [(validate.rules).message.required = true]; - -; -} - -// -// Server Parameter Definitions -// - -// ServerParameter are a set of parameters known and returned by server that -// enables clients to complete transaction construction. Any necessary proofs, -// which are required to be locally verifiable, are also provided to ensure -// safe use in the event of a malicious server. -message ServerParameter { - // The action the server parameters belong to - uint32 action_id = 1; - - // The set of nonces used for the action. Server will only provide values - // for transactions requiring client signatures. - repeated NoncedTransactionMetadata nonces = 2 [(validate.rules).repeated = { - max_items: 1 - }]; - - - - // The type of server parameter which maps to the type of action requested - oneof type { - option (validate.required) = true; - - OpenAccountServerParameter open_account = 3; - NoPrivacyTransferServerParameter no_privacy_transfer = 4; - NoPrivacyWithdrawServerParameter no_privacy_withdraw = 5; - FeePaymentServerParameter fee_payment = 6; - } -} - -// For transactions, the nonce is a standard nonce on Solana -// For virtual instructions, the nonce is a virtual nonce on the Code VM -message NoncedTransactionMetadata { - // The nonce account to use in the system::AdvanceNonce instruction - common.v1.SolanaAccountId nonce = 1 [(validate.rules).message.required = true]; - - - - // The blockhash to set in the transaction or virtual instruction - common.v1.Blockhash blockhash = 2 [(validate.rules).message.required = true]; - - -} - -message OpenAccountServerParameter { - // There are no transactions requiring client signatures -} - -message NoPrivacyTransferServerParameter { - // There are no action-specific server parameters -} - -message NoPrivacyWithdrawServerParameter { - // There are no action-specific server parameters -} - -message FeePaymentServerParameter { - // The destination account where OCP fee payments should be sent. This will - // only be set when the corresponding FeePaymentAction.Type: - // - CREATE_ON_SEND_WITHDRAWAL - common.v1.SolanaAccountId destination = 1 [(validate.rules).message.required = true]; - - -} - -// -// Structured Error Definitions -// - -message ErrorDetails { - oneof type { - option (validate.required) = true; - - ReasonStringErrorDetails reason_string = 1; - InvalidSignatureErrorDetails invalid_signature = 2; - DeniedErrorDetails denied = 3; - } -} - -message ReasonStringErrorDetails { - // Human readable string indicating the failure. - string reason = 1 [(validate.rules).string = { - min_len: 1, - max_len: 2048, // Arbitrary - }]; - - -} - -message InvalidSignatureErrorDetails { - // The action whose signature mismatched - uint32 action_id = 1; - - oneof expected_blob { - option (validate.required) = true; - - // The transaction the server expected to have signed. - common.v1.Transaction expected_transaction = 2; - - // The virtual ixn hash the server expected to have signed. - common.v1.Hash expected_vixn_hash = 4; - } - - // The signature that was provided by the client. - common.v1.Signature provided_signature = 3 [(validate.rules).message.required = true]; - - -} - -message DeniedErrorDetails { - Code code = 1; - enum Code { - // Reason code not yet defined - UNSPECIFIED = 0; - } - - // Human readable string indicating the failure. - string reason = 2 [(validate.rules).string = { - min_len: 1, - max_len: 2048, // Arbitrary - }]; - - -} - -// -// Other Model Definitions -// - -// VerifiedExchangeData defines an amount of crypto to use in a payment flow -// with verified server-state for provable fiat exchange data -message VerifiedExchangeData { - // The crypto mint that is being operated against for the payment flow. - common.v1.SolanaAccountId mint = 1 [(validate.rules).message.required = true]; - - - - // The exact amount of quarks being operated in a payment flow. - // This will be used as the source of truth for validating transfer amounts. - uint64 quarks = 2 [(validate.rules).uint64.gt = 0]; - - - - // The agreed upon fiat amount in a payment flow. - double native_amount = 3 [(validate.rules).double.gt = 0]; - - - - // Verified core mint fiat exchange rate used to compute the exchange data - // - // Required when operating against: - // - Core mint - // - Launchpad currency - currency.v1.VerifiedCoreMintFiatExchangeRate core_mint_fiat_exchange_rate = 4 [(validate.rules).message.required = true]; - - - - // Verified launchpad currency reserve state used to compute the exchange data - // - // Required when operating against: - // - Launchpad currency - currency.v1.VerifiedLaunchpadCurrencyReserveState launchpad_currency_reserve_state = 5; -} - -// ExchangeData defines an amount of crypto to use in a payment flow with -// fiat exchange data -message ExchangeData { - // ISO 4217 alpha-3 currency code. - string currency = 1 [(validate.rules).string = { pattern: "^[a-z]{3,4}$" }]; - - - - // The agreed upon exchange rate. This might not be the same as the - // actual exchange rate at the time of intent or fund transfer. - double exchange_rate = 2 [(validate.rules).double.gt = 0]; - - - - // The agreed upon fiat amount in a payment flow. - double native_amount = 3 [(validate.rules).double.gt = 0]; - - - - // The exact amount of quarks being operated in a payment flow. - // This will be used as the source of truth for validating transfer amounts. - uint64 quarks = 4 [(validate.rules).uint64.gt = 0]; - - - - // The crypto mint that is being operated against for the payment flow. - common.v1.SolanaAccountId mint = 5 [(validate.rules).message.required = true]; - -; -} - -message ExchangeDataWithoutRate { - // ISO 4217 alpha-3 currency code. - string currency = 1 [(validate.rules).string = { pattern: "^[a-z]{3,4}$" }]; - - - - // The agreed upon fiat amount in a payment flow. - double native_amount = 2 [(validate.rules).double.gt = 0]; - - -} - -message SendLimit { - // Remaining limit to apply on the next transaction - float next_transaction = 1; - - // Maximum allowed on a per-transaction basis - float max_per_transaction = 2; - - // Maximum allowed on a per-day basis - float max_per_day = 3; -} - -// VerifiedSwapMetadata defines verifiable swap metadata for non-custodial swap -// state management using client signature verification. -message VerifiedSwapMetadata { - oneof kind { - option (validate.required) = true; - - VerifiedReserveSwapMetadata reserve = 1; - VerifiedCoinbaseStableSwapperSwapMetadata stablecoin = 2; - } -} - -// VerifiedReserveSwapMetadata is verified metadata for swaps against the -// Currency Creator program -message VerifiedReserveSwapMetadata { - // Verifiable client-side parameters that were provided during the StatefulSwap RPC - StatefulSwapRequest.Initiate.ReserveSwapClientParameters client_parameters = 1 [(validate.rules).message.required = true]; - - -} - -// VerifiedCoinbaseStableSwapperSwapMetadata is verified metadata for swaps against the -// Coinbase Stable Swapper program -message VerifiedCoinbaseStableSwapperSwapMetadata { - // Verifiable client-side parameters that were provided during the StatefulSwap RPC - StatefulSwapRequest.Initiate.CoinbaseStableSwapperClientParameters client_parameters = 1 [(validate.rules).message.required = true]; - - -} - -message SwapMetadata { - VerifiedSwapMetadata verified_metadata = 1 [(validate.rules).message.required = true]; - - - - State state = 2 [(validate.rules).enum = { - not_in: [0] // UNKNOWN - }]; - - - - // The signature is of serialize(VerifiedSwapMetadata) using the private - // key of the owner account. Use this to guarantee that VerifiedSwapMetadata - // has not been tampered with. - common.v1.Signature signature = 3 [(validate.rules).message.required = true]; - - - - enum State { - UNKNOWN = 0; - CREATED = 1; // Swap state has been created and is pending funding - FUNDING = 2; // The VM swap PDA is in the process of being funded - FUNDED = 3; // The VM swap PDA has been funded - SUBMITTING = 4; // The swap transaction is being submitted to the blockchain - FINALIZED = 5; // The swap transaction has been finalized on the blockchain - FAILED = 6; // The swap transaction failed - CANCELLING = 7; // The swap is in the process of being cancelled. - CANCELLED = 8; // The swap transaction is cancelled. Funds have been deposited back into the VM - } -} - -enum FundingSource { - FUNDING_SOURCE_UNKNOWN = 0; - FUNDING_SOURCE_SUBMIT_INTENT = 1; - FUNDING_SOURCE_EXTERNAL_WALLET = 2; - FUNDING_SOURCE_COINBASE_ONRAMP = 3; -} - -// AppMetadata is additional app-level metadata provided for an intent -message AppMetadata { - bytes value = 1 [(validate.rules).bytes = { - min_len: 1 - max_len: 4096 - }]; - - -} diff --git a/docs/architecture/01-modules-and-boundaries.md b/docs/architecture/01-modules-and-boundaries.md index 2f15dc47ea..042005c4c8 100644 --- a/docs/architecture/01-modules-and-boundaries.md +++ b/docs/architecture/01-modules-and-boundaries.md @@ -14,7 +14,6 @@ This document is the map. | Features | `:apps:flipcash:features:*` | 26 | Self-contained screens (login, cash, balance, tokens, scanner, withdrawal, …). Each owns its state, ViewModels, and UI. | | Shared | `:apps:flipcash:shared:*` | 34 | Cross-feature coordinators, controllers, and services (authentication, session, router, payments, persistence, …). Coordinators own a domain's cached, session-aware state; see [02 — Roles](02-state-and-dependency-injection.md#roles-coordinators-controllers-managers-services). | | Services | `:services:*` | 4 | gRPC wrappers: `flipcash`, `flipcash-compose`, `opencode`, `opencode-compose`. | -| Definitions | `:definitions:*` | 4 | Protobuf sources (`*/protos`) and generated models (`*/models`) for Flipcash and OCP. | | UI | `:ui:*` | 9 | Compose layer: `theme`, `components`, `core`, `resources`, `navigation`, `scanner`, `biometrics`, `emojis`, `testing`. | | Libs | `:libs:*` | 21 | Leaf utilities: crypto/encryption, network, logging, currency, coroutines, permissions, locale, … | | Vendor | `:vendor:*` | 3 | Third-party SDKs wrapped as modules: Kik scanner, OpenCV, TipKit. | @@ -30,7 +29,7 @@ graph TD Shared[":apps:flipcash:shared:*"] Core[":apps:flipcash:core"] Svc[":services:*"] - Defs[":definitions:*:models"] + Defs["com.flipcash:{ocp,flipcash2}-client-protocol"] UI[":ui:*"] Libs[":libs:*"] Vendor[":vendor:*"] @@ -51,7 +50,6 @@ graph TD Core --> Svc Core --> UI UI --> Libs - Defs --> Libs Libs --> Vendor ``` @@ -118,12 +116,14 @@ Consumers depend on `:bindings` and receive the API plus injection. Examples: `ui/*` / `libs/*` and on external libraries only. `ui/components` knows about `libs/currency` and `ui/theme`, never about a feature. 2. **`libs/*` is leaf-level.** It may depend on other libs, `vendor/*`, and - `definitions/*:models` (data only), but never on services or app modules. -3. **`services/*` wrap protobuf, nothing app-specific.** They depend on - `definitions/*:models`, libs, and the gRPC runtime; they re-export public + the generated protobuf models (data only), but never on services or app modules. +3. **`services/*` wrap protobuf, nothing app-specific.** They depend on the + client-protocol artifacts, libs, and the gRPC runtime; they re-export public interfaces with `api(...)`. They never depend on features or shared modules. -4. **`definitions/*:models` is generated; don't hand-edit it.** Regenerate from the - `.proto` sources in `definitions/*/protos`. +4. **The protobuf models are an external dependency.** They arrive as the published + `com.flipcash:ocp-client-protocol` and `com.flipcash:flipcash2-client-protocol` + artifacts; the `.proto` sources and the codegen live in those repos, not here. A + contract change is a version bump in `gradle/libs.versions.toml`. 5. **Features may depend on shared modules, libs, ui, core, and (occasionally) other features** — e.g. `:features:login` pulls in `:features:purchase` for the onboarding hand-off. Keep cross-feature edges rare and acyclic. diff --git a/docs/architecture/09-separation-of-concerns.md b/docs/architecture/09-separation-of-concerns.md index 47f49a13f0..a9fe13ebed 100644 --- a/docs/architecture/09-separation-of-concerns.md +++ b/docs/architecture/09-separation-of-concerns.md @@ -67,7 +67,8 @@ abstractions, opted into via `trace(...)` or a `Local*`, never reimplemented per feature. See [08 — Cross-cutting concerns](08-cross-cutting-concerns.md). ### 7. Generated and signed artifacts are not hand-edited -`definitions/*:models` is generated from `.proto`; don't edit it — regenerate. +The protobuf models come from the published client-protocol artifacts; the `.proto` +sources and the codegen live in those repos, so a contract change is a version bump. Signing and key derivation live in `libs/encryption/*` and `services/*`, not in feature code. See [06 — Payments & operations](06-payments-and-operations.md). diff --git a/docs/architecture/13-protobuf-and-codegen.md b/docs/architecture/13-protobuf-and-codegen.md index 31bbc24932..5c998c7544 100644 --- a/docs/architecture/13-protobuf-and-codegen.md +++ b/docs/architecture/13-protobuf-and-codegen.md @@ -1,84 +1,82 @@ # 13 — Protobuf & code generation -The backend contract is **Protocol Buffers**. Generated gRPC/proto code is the -foundation the whole service layer sits on ([04 — Networking](04-networking.md)), -so this doc explains where it comes from, how it's generated, and how to update it -safely. +The backend contract is **Protocol Buffers**, and none of it is generated in this +repo. The message classes and gRPC stubs the service layer sits on +([04 — Networking](04-networking.md)) arrive as two published artifacts. ```mermaid graph TD - Upstream["Upstream proto repos (Flipcash, OCP)"] - Protos[":definitions:*:protos — .proto sources (java-library)"] - Models[":definitions:*:models — generated Java/Kotlin + grpc stubs"] + Upstream["Upstream contracts (flipcash2-protobuf-api, ocp-protobuf-api)"] + Client["Client repos (flipcash2-client-protocol, ocp-client-protocol) — run protoc"] + Art["com.flipcash:{flipcash2,ocp}-client-protocol — published to Maven Central"] Wrap[":services:* — hand-written Api/Service/Repository/Controller"] Feat["features / shared"] - Upstream -->|/fetch-protos| Protos --> Models --> Wrap --> Feat + Upstream --> Client --> Art --> Wrap --> Feat ``` -## Layout - -| Module | Plugin | Contents | -|--------|--------|----------| -| `:definitions:flipcash:protos`, `:definitions:opencode:protos` | `java-library` | The raw `.proto` source files under `src/main/proto/`. The `java-library` plugin packages them so the `models` module can compile them. | -| `:definitions:flipcash:models`, `:definitions:opencode:models` | `flipcash.android.library` + `protobuf` + `protobuf.validate` | The **generated** Java/Kotlin message classes and gRPC stubs. | - -The `models` build consumes the matching `protos` module via the `protobuf(...)` -configuration and runs `protoc`: - -```kotlin -// definitions//models/build.gradle.kts -plugins { - alias(libs.plugins.flipcash.android.library) - alias(libs.plugins.protobuf) - alias(libs.plugins.protobuf.validate) -} -dependencies { - protobuf(project(":definitions::protos")) // source of .proto files - implementation(libs.grpc.protobuf.lite) - implementation(libs.protobuf.kotlin.lite) -} -protobuf { - protoc { artifact = "com.google.protobuf:protoc:$protobufVersion$archSuffix" } - plugins { /* protoc-gen-grpc-java */ } -} +## Where the code comes from + +| Artifact | Generated from | Consumed by | Packages | +|----------|----------------|-------------|----------| +| `com.flipcash:ocp-client-protocol` | [`ocp-protobuf-api`](https://github.com/code-payments/ocp-protobuf-api) | `:services:opencode` | `com.codeinc.opencode.gen.*` | +| `com.flipcash:flipcash2-client-protocol` | [`flipcash2-protobuf-api`](https://github.com/code-payments/flipcash2-protobuf-api) | `:services:flipcash` | `com.codeinc.flipcash.gen.*` | + +The artifact coordinates and the package names are deliberately different: the +coordinate is `com.flipcash` because that is the verified Maven Central namespace, +while the code inside keeps the `com.codeinc.*` packages the app has always +imported, because `java_package` in the protos sets them. + +Versions are pinned in [`gradle/libs.versions.toml`](../../gradle/libs.versions.toml): + +```toml +ocp-client-protocol = "0.1.0" +flipcash2-client-protocol = "0.1.0" ``` -Generated output uses the **lite** runtime (`protobuf-kotlin-lite`, -`grpc-protobuf-lite`) suited to Android, and `protobuf.validate` wires up -`protovalidate` so requests can be validated at the Api boundary -(`...orThrow()` — see [04](04-networking.md)). +They move independently. The two contracts do not import each other, so there is +nothing to keep aligned. + +Generation still targets the **lite** runtime (`protobuf-kotlin-lite`, +`grpc-protobuf-lite`) and still runs `protovalidate`, so requests validate at the +Api boundary (`...orThrow()` — see [04](04-networking.md)). That configuration now +lives in each client repo's `build.gradle.kts`; this repo supplies only the +matching runtime dependencies. ## The golden rule -> **Never hand-edit anything under `:definitions:*:models`.** It is generated from -> the `.proto` sources and will be overwritten. To change a model, change the -> `.proto` (upstream) and regenerate. +> **Generated protobuf code is not in this repo, and not editable from it.** To +> change a model, change the upstream `.proto`, cut a client-protocol release, and +> bump the version here. The hand-written code lives one layer up, in `:services:*` — the Api/Service/Repository/Controller wrappers and the `LocalToProtobuf` / `ProtobufToLocal` extensions that translate between protobuf and domain types. -## Updating protos +## Updating a contract -Use the **`/fetch-protos`** skill rather than copying files by hand. It fetches the -latest `.proto`s from the upstream repos, verifies they compile, summarizes the API -changes, and scaffolds missing service-layer stubs: +Use the **`/fetch-protos`** skill. It finds the release that carries the change, +bumps the version in the catalog, diffs the contract between the old and new +version, and scaffolds the missing service-layer stubs. ``` -/fetch-protos # both targets at HEAD +/fetch-protos # check both artifacts for newer releases /fetch-protos flipcash # flipcash only -/fetch-protos opencode # opencode at a specific commit +/fetch-protos opencode 0.2.0 # opencode at a specific version ``` -After fetching, run the **`proto-change-tracer`** agent to trace the impact through -`generated models → Api → Service → Repository → Controller → features` and get the +If the contract change has not been released yet, it has to land in the client +repo first — sync the protos there at the upstream SHA, regenerate, and publish. +That repo's README covers it. + +After bumping, run the **`proto-change-tracer`** agent to trace the impact through +`generated stubs → Api → Service → Repository → Controller → features` and get the list of files that need updating. ## Typical workflow -1. `/fetch-protos [commit]` — pull and regenerate. -2. Build `:definitions::models` to confirm codegen succeeds. +1. `/fetch-protos [version]` — bump the pin. +2. Build `:services:` to confirm the new stubs resolve and compile. 3. Run `proto-change-tracer` to find affected wrappers. 4. Update the hand-written `:services:*` layer (new RPCs → new Api/Service methods; changed messages → mapper updates). @@ -86,7 +84,9 @@ list of files that need updating. ## Why this matters -Keeping generated code isolated in `:definitions:*:models` and all -human-maintained code in `:services:*` means a proto bump is a mechanical -regenerate-plus-rewire, and the boundary ([01](01-modules-and-boundaries.md)) keeps -protobuf types from leaking past the service layer. +The app used to vendor its own `.proto` copies and run `protoc` in +`:definitions:*:models`, in parallel with the iOS app doing the same thing — two +copies of one contract with nothing making them agree. One generation point removes +that class of drift, and it makes a contract change a version bump with a +reviewable diff. The boundary ([01](01-modules-and-boundaries.md)) still does the +rest of the work: protobuf types stop at the service layer. diff --git a/docs/architecture/16-agents-and-skills.md b/docs/architecture/16-agents-and-skills.md index 2394a4a37d..d3b4ddcf6a 100644 --- a/docs/architecture/16-agents-and-skills.md +++ b/docs/architecture/16-agents-and-skills.md @@ -13,7 +13,7 @@ right tool so you (or an agent) don't reinvent work the repo already automates. | Investigate a crash/stack trace and trace it to a root cause | `bug-triage` | agent | | Create a new feature / shared / lib module skeleton | `module-scaffolder` | agent | | Add a screen end-to-end (uses the scaffolder) | follow [11 — Adding a feature](11-adding-a-feature.md) | guide | -| Fetch latest protobufs, verify, summarize, scaffold stubs | `/fetch-protos` | skill | +| Bump a client-protocol artifact, summarize, scaffold stubs | `/fetch-protos` | skill | | Trace the impact of a proto change through the codebase | `proto-change-tracer` | agent | | Assess the blast radius of a dependency bump | `dependency-impact` | agent | | Review a Dependabot PR for breaking changes | `/dep-review` | skill | @@ -33,7 +33,7 @@ Agents are launched via the `Agent` tool for open-ended, multi-step work: package structure, entry points, navigation registration, `settings.gradle.kts` inclusion. - **proto-change-tracer** — after `/fetch-protos`, traces - `generated models → Api → Service → Repository → Controller → features` + `generated stubs → Api → Service → Repository → Controller → features` ([13](13-protobuf-and-codegen.md)). - **dependency-impact** — for a dependency bump, finds dependent modules, breaking API changes, and targeted tests to run. @@ -48,7 +48,7 @@ Skills are slash commands for repeatable workflows: - **/triage** — triage a Bugsnag production issue (top open or a specific URL/ID) end-to-end. -- **/fetch-protos** `[flipcash|opencode] [commit]` — pull + regenerate protos +- **/fetch-protos** `[flipcash|opencode] [version]` — bump a client-protocol artifact ([13](13-protobuf-and-codegen.md)). - **/dep-review** `` — review a Dependabot PR for breaking changes and required code updates. diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 655914602a..35598d6b51 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -37,7 +37,7 @@ graph TD Shared["apps/flipcash/shared/* — 34 coordinators / controllers / services"] Core["apps/flipcash/core — app-wide routes, locals, infra"] Services["services/* — gRPC wrappers (API → Service → Repository → Controller)"] - Defs["definitions/* — protobuf sources + generated models"] + Defs["com.flipcash:{ocp,flipcash2}-client-protocol — published protobuf stubs"] UI["ui/* — Compose components, theme, navigation, scanner"] Libs["libs/* — crypto, network, logging, currency (leaf utilities)"] Vendor["vendor/* — Kik scanner, OpenCV, TipKit"] @@ -76,7 +76,7 @@ depend on app modules.* See [01 — Modules & boundaries](01-modules-and-boundar | 10 | [Build & run](10-build-and-run.md) | Prerequisites, the real `local.properties` keys, Gradle commands, variants, CI | | 11 | [Adding a feature](11-adding-a-feature.md) | End-to-end: scaffold module → ViewModel → screen → route → register → share | | 12 | [Testing](12-testing.md) | What to test where, `:libs:test-utils`, Robolectric, fakes, Turbine (+ Compose UI guide) | -| 13 | [Protobuf & codegen](13-protobuf-and-codegen.md) | proto sources → generated models → services; updating protos with `/fetch-protos` | +| 13 | [Protobuf & codegen](13-protobuf-and-codegen.md) | where the published stubs come from, and bumping them with `/fetch-protos` | | 14 | [Error handling](14-error-handling.md) | `Result`, typed sealed errors, `NotifiableError`, `retryable` | | 15 | [CI & release](15-ci-and-release.md) | The CI check, Fastlane lanes, release workflows, helper skills | | 16 | [Agents & skills](16-agents-and-skills.md) | The repo's Claude Code agents/skills and which task each one fits | diff --git a/docs/architecture/glossary.md b/docs/architecture/glossary.md index 8187e13fa4..391148799f 100644 --- a/docs/architecture/glossary.md +++ b/docs/architecture/glossary.md @@ -46,7 +46,7 @@ full story. | **Intent** | A signed unit of money movement (transfer, remote send/receive, withdraw, swap, distribution) submitted over the `SubmitIntent` bidirectional stream. | [04](04-networking.md), [06](06-payments-and-operations.md) | | **Mint** | A Solana token mint address (`PublicKey` subtype); identifies a token such as USDF (`Mint.usdf`) or a launchpad currency. | [06](06-payments-and-operations.md) | | **MintMetadata / Token** | The model for any currency (`MintMetadata`, aliased `Token`). A non-null `launchpadMetadata` makes it a launchpad currency; `null` means it's USDF (the core mint). | [06](06-payments-and-operations.md) | -| **Protobuf / proto** | The Protocol Buffers contract; generated code lives in `:definitions:*:models` and is never hand-edited. | [13](13-protobuf-and-codegen.md) | +| **Protobuf / proto** | The Protocol Buffers contract; the generated code arrives as the `com.flipcash:{ocp,flipcash2}-client-protocol` artifacts, not from sources in this repo. | [13](13-protobuf-and-codegen.md) | | **NotifiableError** | Marker for errors that represent bugs (not user-caused) and should alert via Bugsnag/Slack. | [14](14-error-handling.md) | ## Architecture roles diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c0655af91..e0bdc8a840 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,6 @@ grpc = "1.83.1" grpc-okhttp = "1.83.1" grpc-kotlin = "1.5.0" protobuf = "4.35.1" -protobuf-plugin = "0.10.0" protovalidate-kt = "0.1.1" # Generated client SDKs for the two backend contracts. Separate versions on purpose: the @@ -357,12 +356,10 @@ firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "fireb bugsnag-gradle = { id = "com.bugsnag.gradle", version.ref = "bugsnag-gradle-plugin" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets-gradle-plugin" } navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "androidx-navigation" } -protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" } androidx-room = { id = "androidx.room", version.ref = "androidx-room" } androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidx-benchmark-macro" } screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } -protobuf-validate = { id = "dev.bmcreations.protovalidate", version.ref = "protovalidate-kt" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } kmmbridge = { id = "co.touchlab.kmmbridge.github", version.ref = "kmmbridge" } diff --git a/scripts/fetch-protos.sh b/scripts/fetch-protos.sh deleted file mode 100755 index ed158b7179..0000000000 --- a/scripts/fetch-protos.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -root=$(pwd) -REPO_URL="git@github.com:code-payments/ocp-protobuf-api.git" # Default repo URL -COMMIT_SHA="" -TEMP_DIR=$(mktemp -d) -TARGET="code" - -# Parse options -while getopts ":r:t:" opt; do - case ${opt} in - r ) - REPO_URL=$OPTARG - ;; - t ) - TARGET=$OPTARG - if [ "$TARGET" == "flipchat" ]; then - REPO_URL="git@github.com:code-payments/flipchat-protobuf-api.git" - elif [ "$TARGET" == "flipcash" ]; then - REPO_URL="git@github.com:code-payments/flipcash2-protobuf-api.git" - fi - ;; - \? ) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - esac -done - -shift $((OPTIND -1)) - -DEST_DIR="definitions/$TARGET/protos/src/main/proto" - -# Get the commit SHA if provided -COMMIT_SHA=$1 - -# Clone the repository -git clone "$REPO_URL" "$TEMP_DIR" - -# Change to the cloned repository directory -cd "$TEMP_DIR" || exit - -# If a commit SHA is provided, checkout that commit -if [ -n "$COMMIT_SHA" ]; then - git checkout "$COMMIT_SHA" -else - git checkout main -fi - -# Create the destination directory if it doesn't exist -mkdir -p "${root}/$DEST_DIR" - -# Copy proto files -if [ -d "proto" ]; then - rsync -av --exclude='buf*' proto/ "${root}/$DEST_DIR/" - echo "Proto files copied successfully." -else - echo "Error: 'proto' directory not found in the repository." - exit 1 -fi - -# Clean up: remove the temporary directory -cd ../.. -rm -rf "$TEMP_DIR" - -# Preserve custom opencode namespacing -if [ "$TARGET" = "opencode" ]; then - find "${root}/definitions/$TARGET/protos/src/main/proto" -name "*.proto" -type f -exec sh -c "awk '{gsub(/];/, \"];\n\n\"); gsub(/option java_package = \"com\.codeinc\.gen\./, \"option java_package = \\\"com.codeinc.opencode.gen.\"); print}' {} > tmp && mv tmp {}" \; -fi \ No newline at end of file diff --git a/services/flipcash/README.md b/services/flipcash/README.md index 9b983c81a3..2f1f702e1e 100644 --- a/services/flipcash/README.md +++ b/services/flipcash/README.md @@ -91,7 +91,7 @@ foreground/reconnect). ## Adding an RPC -1. **Proto** — update `definitions/flipcash`, regenerate +1. **Proto** — land the contract change in `flipcash2-client-protocol`, then bump its version ([13](../../docs/architecture/13-protobuf-and-codegen.md)). 2. **Api** — add the call to `internal/network/api/XxxApi` (build request, sign, validate). 3. **Service** — map the response to `Result` + a typed error in diff --git a/services/opencode/README.md b/services/opencode/README.md index 34706a53f4..018071c27e 100644 --- a/services/opencode/README.md +++ b/services/opencode/README.md @@ -148,7 +148,7 @@ and `TokenMetadataProvider`. ## Adding an intent / RPC -1. **Proto** — update `definitions/opencode` and regenerate +1. **Proto** — land the contract change in `ocp-client-protocol`, then bump its version ([13](../../docs/architecture/13-protobuf-and-codegen.md)). 2. **Intent** — add an `IntentType` + its `ActionType`s under `internal/network/api/intents/`; add any new `internal/solana/programs/` instruction. diff --git a/settings.gradle.kts b/settings.gradle.kts index d1059fcbe0..3ef8ed0a17 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -122,14 +122,6 @@ include( ":apps:flipcash:features:userflags", ":apps:flipcash:features:tipping", - // protobuf model and service implementations for the Open Code Protocol - ":definitions:opencode:models", - ":definitions:opencode:protos", - - // protobuf model and service implementations for Flipcash - ":definitions:flipcash:models", - ":definitions:flipcash:protos", - // Internal libs ":libs:analytics", ":libs:biometrics", @@ -233,12 +225,10 @@ val includedProjectPaths = buildList { // Coverage: every module under these paths applies a `flipcash.android.*` // convention plugin (which pulls in Kover); :apps:flipcash:app applies Kover // directly. The denylist holds the modules under those paths that have no Kover. -val koverPaths = listOf(":apps:flipcash", ":libs", ":ui", ":definitions", ":services:flipcash", ":services:opencode") +val koverPaths = listOf(":apps:flipcash", ":libs", ":ui", ":services:flipcash", ":services:opencode") val nonKoverModules = setOf( ":apps:flipcash:benchmark", // com.android.test — no Kover ":apps:flipcash:shared:ksp", // pure-JVM helper — no Kover - ":definitions:opencode:protos", // java-library proto jar — no Kover - ":definitions:flipcash:protos", // java-library proto jar — no Kover ) val koverModules = includedProjectPaths.filter { path -> koverPaths.any { path == it || path.startsWith("$it:") } && path !in nonKoverModules