Skip to content
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ ai-docs/
# OpenAPI spec - fetch dynamically instead of committing
apiSpec/*.yaml

# SDK generator download + OpenAPI CLI output
generated/

.java-version
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- `GetStakingStatus` - Wallet staking status

### Changed
- **SDK generator (tools/model-generator)**: Holistic generation aligned with .NET — models, enums, per-operation `*Request`/`*Response`, `*Service`/`*ServiceImpl`, and `PrimeServiceFactory` via `com.coinbase.tools.sdkgenerator`; config in `tools/model-generator/config/generator-config.json` and `operations-overrides.json`. Root Maven profile renamed from `generate-models` to **`generate`** (`mvn -Pgenerate`); use `-Dgenerator.args=--diff` or `--dry-run` as needed.
- Regenerated `com.coinbase.prime.model` and `model.enums` from latest Prime OpenAPI spec (`https://api.prime.coinbase.com/v1/openapi.yaml`)
- **Model generator** (`PostProcessor`): map `GoogleTypeDate` to `DateOfBirth` so `TravelRuleParty.date_of_birth` compiles when `Google*` types are excluded
- `ActivityMetadataConsensus`, `Asset`, `CreateAllocationResponseBody`, `CreateNetAllocationResponseBody`, `CrossMarginOverview`, `EvmParams`, `FcmTradingSessionDetails`, `FuturesSweep`, `MarginSummary`, `NetworkDetails`, `OnchainTransactionDetails`, `Order`, `PmAssetInfo`, `PostTradeCreditInformation`, `RequestToSubmitTravelRuleDataForAnExistingDepositTransaction`, `RfqProductDetails`, `RiskAssessment`, `RpcConfig`, `TravelRuleData`, `TravelRuleParty`, `XmPosition`, `XmRiskNettingInfo`, `UserRole` - field updates per spec
Expand Down
82 changes: 27 additions & 55 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ This is a Maven-based Java project (Java 11+). Common development commands:
This is the **Coinbase Prime Java SDK** - a sample library for interacting with Coinbase Prime REST APIs. Key architectural patterns:

### Service Layer Pattern
- **PrimeServiceFactory**: Factory class that creates service instances for different API domains
- **Service interfaces**: Each API domain (Orders, Portfolios, Wallets, etc.) has a corresponding service interface
- **Service implementations**: Concrete implementations ending with `ServiceImpl` that handle HTTP communication
- **PrimeServiceFactory**: Factory class that creates service instances for different API domains (regenerate with `tools/model-generator`; do not hand-edit)
- **Service interfaces**: Each API domain (Orders, Portfolios, Wallets, etc.) has a corresponding service interface (**generated**)
- **Service implementations**: Concrete implementations ending with `ServiceImpl` that handle HTTP communication (**generated**)

### Core Components
- **CoinbasePrimeClient**: Main HTTP client that extends `CoinbaseNetHttpClient` from `coinbase-core-java`
- **CoinbasePrimeCredentials**: Handles API authentication using access key, passphrase, and signing key
- **Request/Response pattern**: Each API operation has dedicated request and response classes using Builder patterns
- **Request/Response pattern**: Each API operation has dedicated request and response classes using Builder patterns (**generated**; do not hand-write)

### Package Structure
- `com.coinbase.prime.client`: Core HTTP client
Expand Down Expand Up @@ -107,73 +107,45 @@ Working examples available in `src/main/java/com/coinbase/examples/` including:

### AI Agent Code Generation

#### Model Generation - IMPORTANT
**DO NOT create domain models or enums by hand!**
#### SDK code generation - IMPORTANT
**Do not hand-write domain models, enums, per-operation `*Request`/`*Response`, or `*Service`/`*ServiceImpl` files** — they are produced by the holistic generator in `tools/model-generator` (see `com.coinbase.tools.sdkgenerator`). Configuration mirrors the .NET generator: `tools/model-generator/config/generator-config.json` and `operations-overrides.json`.

Use the model generator from the repository root:
From the **repository root** (recommended):
```bash
cd tools/model-generator
mvn -Pgenerate
```

Optional: `--dry-run` or `--diff` (no writes; compare to disk):
```bash
mvn -Pgenerate -Dgenerator.args=--diff
```

From `tools/model-generator` only:
```bash
mvn -Pgenerate
# or: mvn -q compile exec:java@generate-models -Dgenerator.args=--diff
```

This tool:
- Generates models and enums from the OpenAPI spec
- Downloads the OpenAPI spec, generates models and enums (OpenAPI Generator + `PostProcessor`)
- Generates `*Request`, `*Response`, `*Service`, `*ServiceImpl` per tag/operation and `PrimeServiceFactory`
- Maintains SDK conventions (Builder pattern, proper annotations)
- Prevents drift between spec and implementation
- Processes all models from the spec, updating existing files to catch changes
- See `tools/model-generator/README.md` for full documentation

**Manual model creation is prohibited.** All domain models in `com.coinbase.prime.model` and enums in `com.coinbase.prime.model.enums` must be generated from the OpenAPI specification.

**Exception**: Request/Response classes in service packages (e.g., `orders/GetOrderRequest.java`) are hand-crafted and co-located with their service.

#### Speed Optimization for Service Generation
**PRIORITY: Execute quickly, validate afterwards**

**Parallelized Fast Generation Strategy:**
1. **Analyze entire OpenAPI spec** first to identify all missing endpoints
2. **Run model generator first** - `cd tools/model-generator && mvn -Pgenerate`
3. **Plan batches by domain** - group related services together (e.g., FCM, Staking, Orders)
4. **Execute multiple Task agents in parallel** - generate 5-10 services simultaneously across domains
5. **NO premature validation** - generate ALL files first, validate once at the end
6. **Template-based rapid creation** - copy existing files and modify rather than create from scratch
7. **Single build validation** - run `mvn compile` only after all generation is complete

**Parallelization Implementation:**
```
// Execute multiple Task agents simultaneously in a single message
Task 1: Generate FCM models (3 files)
Task 2: Generate Staking models (5 files)
Task 3: Generate Invoice models (5 files)
Task 4: Generate Margin models (7 files)
Task 5: Generate Misc models (7 files)
Task 6: Generate Wallet models (3 files)
Task 7: Generate Order models (3 files)
Task 8: Generate Service A
Task 9: Generate Service B
```

**Fast Template-Based Approach:**
1. **Use existing files as templates** - copy/modify instead of creating from scratch
2. **Batch creation** - generate 5-10 files rapidly per task
3. **Focus on compilation first** - get objects created and building, refine schema compliance later
4. **Template pattern**: Copy existing request/response pair → Search/replace service names → Adjust properties

**Template Locations:**
- Request template: `activities/ListActivitiesRequest.java`
- Response template: `activities/ListActivitiesResponse.java`
- Single object response: `activities/GetActivityResponse.java`
- Service interface: `activities/ActivitiesService.java`
- Service implementation: `activities/ActivitiesServiceImpl.java`
The profile id was formerly `generate-models`; use **`mvn -Pgenerate`** from the repo root.

### Development Workflow
When implementing new endpoints, pull endpoint definitions directly from the OpenAPI specification:
When the OpenAPI spec adds or changes operations:

1. **Reference the live spec** (or `apiSpec/prime-public-spec.yaml` when fetched) as the source of truth
2. **Run the holistic generator** from the repo root: `mvn -Pgenerate` (or `-Dgenerator.args=--diff` to compare without writing)
3. **Build**: `mvn clean install` and fix any generator gaps in `tools/model-generator` (not by editing generated Java by hand, except where noted below)

1. **Reference OpenAPI spec**: Use `apiSpec/prime-public-spec.yaml` as the source of truth for all endpoint definitions
2. **Extract by tags**: Organize endpoints by their OpenAPI tags to determine package structure
3. **Follow Java patterns**: Use existing service implementations as templates
4. **Generate classes**: Create Request/Response classes using Builder patterns
5. **Update factory**: Add new services to `PrimeServiceFactory`
Hand-maintained (not overwritten by the generator) includes: `com.coinbase.prime.common` (e.g. `PrimeListRequest`, `Pagination`), credentials/client/utils, and **curated** examples under `com.coinbase.examples`

### Endpoint Discovery
To identify available endpoints:
Expand Down
27 changes: 7 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,31 +133,18 @@ mvn exec:java -Dexec.mainClass="com.coinbase.examples.wallets.GetWalletDepositIn
mvn exec:java -Dexec.mainClass="com.coinbase.examples.wallets.GetWalletDepositInstructions" -Dexec.args="wallet-id WIRE"
```

## Model Generation
## Code generation

The SDK includes an automated model generator that creates Java domain models and enums from the OpenAPI specification. This ensures the SDK stays in sync with the Prime API specification.
The SDK is generated from the published OpenAPI spec (`https://api.prime.coinbase.com/v1/openapi.yaml`). The spec is downloaded at generation time (not committed).

The OpenAPI spec is fetched automatically from the live API (`https://api.prime.coinbase.com/v1/openapi.yaml`) during model generation and is not committed to source control.

### Generate Models from Root Directory

To generate new models from the OpenAPI spec:
From the repository root:

```bash
mvn -Pgenerate-models
mvn -Pgenerate
```

This command:
- Runs in **incremental mode** (safe - only creates new models that don't exist)
- Generates domain models in `src/main/java/com/coinbase/prime/model/`
- Generates enums in `src/main/java/com/coinbase/prime/model/enums/`
- Automatically applies SDK conventions (Builder patterns, license headers, etc.)

### Advanced Model Generation
Optional: compare without writing files: `mvn -Pgenerate -Dgenerator.args=--diff`, or dry run: `-Dgenerator.args=--dry-run`.

For more control over model generation, see the detailed documentation in [`tools/model-generator/README.md`](tools/model-generator/README.md), which covers:
This regenerates domain models and enums, per-operation `*Request` / `*Response` types, `*Service` / `*ServiceImpl`, and `PrimeServiceFactory`. Example programs under `com.coinbase.examples` are maintained separately.

- Force regenerating all models (dangerous - use with caution)
- Regenerating specific models
- Understanding the generation pipeline
- Troubleshooting generation issues
See [`tools/model-generator/README.md`](tools/model-generator/README.md) for configuration (`config/generator-config.json`, `operations-overrides.json`) and module details.
13 changes: 12 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<jackson.version>2.16.1</jackson.version>
<core.version>1.1.1</core.version>
<junit.version>5.10.0</junit.version>
<generator.args></generator.args>
</properties>
<build>
<plugins>
Expand Down Expand Up @@ -77,12 +78,21 @@
<configuration>
<source>11</source>
<target>11</target>
<!-- Sample programs under examples/ are not regenerated with the OpenAPI client; exclude until updated. -->
<excludes>
<exclude>com/coinbase/examples/**/*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<sourceFileExcludes>
<sourceFileExclude>com/coinbase/examples/**/*.java</sourceFileExclude>
</sourceFileExcludes>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down Expand Up @@ -141,7 +151,7 @@
</build>
<profiles>
<profile>
<id>generate-models</id>
<id>generate</id>
<build>
<defaultGoal>generate-sources</defaultGoal>
<plugins>
Expand Down Expand Up @@ -175,6 +185,7 @@
<workingDirectory>${project.basedir}/tools/model-generator</workingDirectory>
<arguments>
<argument>-Pgenerate</argument>
<argument>-Dgenerator.args=${generator.args}</argument>
</arguments>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import com.coinbase.prime.errors.CoinbasePrimeException;

public interface ActivitiesService {
GetActivityResponse getActivity(GetActivityRequest request) throws CoinbaseClientException, CoinbasePrimeException;
ListEntityActivitiesResponse listEntityActivities(ListEntityActivitiesRequest request) throws CoinbaseClientException, CoinbasePrimeException;
ListPortfolioActivitiesResponse listPortfolioActivities(ListPortfolioActivitiesRequest request) throws CoinbaseClientException, CoinbasePrimeException;
ListEntityActivitiesResponse listEntityActivities(ListEntityActivitiesRequest request) throws CoinbaseClientException, CoinbasePrimeException;
GetActivityResponse getActivity(GetActivityRequest request) throws CoinbaseClientException, CoinbasePrimeException;
GetPortfolioActivityResponse getPortfolioActivity(GetPortfolioActivityRequest request) throws CoinbaseClientException, CoinbasePrimeException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.coinbase.core.service.CoinbaseServiceImpl;
import com.coinbase.prime.client.CoinbasePrimeClient;
import com.coinbase.prime.errors.CoinbasePrimeException;
import com.coinbase.prime.utils.Utils;
import com.fasterxml.jackson.core.type.TypeReference;

import java.util.List;
Expand All @@ -31,8 +30,7 @@ public ActivitiesServiceImpl(CoinbasePrimeClient client) {
}

@Override
public ListPortfolioActivitiesResponse listPortfolioActivities(ListPortfolioActivitiesRequest request)
throws CoinbasePrimeException {
public ListPortfolioActivitiesResponse listPortfolioActivities(ListPortfolioActivitiesRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
String.format("/portfolios/%s/activities", request.getPortfolioId()),
Expand All @@ -42,34 +40,33 @@ public ListPortfolioActivitiesResponse listPortfolioActivities(ListPortfolioActi
}

@Override
public GetActivityResponse getActivity(GetActivityRequest request) throws CoinbasePrimeException {
public ListEntityActivitiesResponse listEntityActivities(ListEntityActivitiesRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
String.format("/activities/%s", request.getActivityId()),
String.format("/entities/%s/activities", request.getEntityId()),
request,
List.of(200),
new TypeReference<GetActivityResponse>() {});
new TypeReference<ListEntityActivitiesResponse>() {});
}

@Override
public ListEntityActivitiesResponse listEntityActivities(ListEntityActivitiesRequest request)
throws CoinbasePrimeException {
public GetActivityResponse getActivity(GetActivityRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
String.format("/entities/%s/activities", request.getEntityId()),
String.format("/activities/%s", request.getActivityId()),
request,
List.of(200),
new TypeReference<ListEntityActivitiesResponse>() {});
new TypeReference<GetActivityResponse>() {});
}

@Override
public GetPortfolioActivityResponse getPortfolioActivity(GetPortfolioActivityRequest request)
throws CoinbasePrimeException {
public GetPortfolioActivityResponse getPortfolioActivity(GetPortfolioActivityRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
String.format("/portfolios/%s/activities/%s", request.getPortfolioId(), request.getActivityId()),
request,
List.of(200),
new TypeReference<GetPortfolioActivityResponse>() {});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@

package com.coinbase.prime.activities;

import com.coinbase.core.errors.CoinbaseClientException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import static com.coinbase.core.utils.Utils.isNullOrEmpty;

/**
* Get Activity by Activity ID
*/
public class GetActivityRequest {
@JsonProperty(required = true, value = "activity_id")
@JsonIgnore
Expand All @@ -27,8 +33,8 @@ public class GetActivityRequest {
public GetActivityRequest() {
}

public GetActivityRequest(String activityId) {
this.activityId = activityId;
public GetActivityRequest(Builder builder) {
this.activityId = builder.activityId;
}

public String getActivityId() {
Expand All @@ -38,4 +44,27 @@ public String getActivityId() {
public void setActivityId(String activityId) {
this.activityId = activityId;
}

public static class Builder {
private String activityId;

public Builder() {
}

public Builder activityId(String activityId) {
this.activityId = activityId;
return this;
}

public GetActivityRequest build() throws CoinbaseClientException {
validate();
return new GetActivityRequest(this);
}

private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.activityId)) {
throw new CoinbaseClientException("ActivityId is required");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@
package com.coinbase.prime.activities;

import com.coinbase.prime.model.Activity;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Response object for retrieving an activity by its activity ID.
*
* This endpoint can retrieve both portfolio and entity activities when passed the appropriate API key.
* Get Activity by Activity ID
*/
public class GetActivityResponse {
/** The activity details */
@JsonProperty("activity")
private Activity activity;

public GetActivityResponse() {
Expand Down
Loading