Skip to content
Draft
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependency-reduced-pom.xml
tmp/

ai-docs/
docs/

# SDK generator download + OpenAPI CLI output
generated/
Expand Down
17 changes: 14 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
.PHONY: fetch-spec compile format
.PHONY: fetch-spec compile format generate check-generated generate-live-diff

fetch-spec:
@mkdir -p apiSpec
@curl -fsSL -o apiSpec/prime-public-spec.yaml https://api.prime.coinbase.com/v1/openapi.yaml
mvn -B -f tools/model-generator/pom.xml compile exec:java@generate-models -Dexec.args="--fetch-spec"

compile:
mvn -B compile

format:
mvn -B spotless:apply

generate:
mvn -B -f tools/model-generator/pom.xml compile exec:java@generate-models
mvn -B spotless:apply

check-generated:
$(MAKE) generate
git diff --exit-code -- src/main/java tools/model-generator/generated-files.json tools/model-generator/generated-model-files.json

generate-live-diff:
mvn -B -f tools/model-generator/pom.xml compile exec:java@generate-models -Dexec.args="--live-diff"
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,31 @@
import java.util.List;

public class CoinbasePrimeClient extends CoinbaseNetHttpClient {
private final String baseUrl;

public CoinbasePrimeClient(CoinbasePrimeCredentials credentials, String baseUrl) {
super(credentials, baseUrl);
this.baseUrl = baseUrl;
}

public CoinbasePrimeClient(
CoinbasePrimeCredentials credentials, String baseUrl, HttpClient client) {
super(credentials, baseUrl, client);
this.baseUrl = baseUrl;
}

public CoinbasePrimeClient(CoinbasePrimeCredentials credentials) {
super(credentials, Constants.CB_PRIME_BASE_URL);
this.baseUrl = Constants.CB_PRIME_BASE_URL;
}

public CoinbasePrimeClient(CoinbasePrimeCredentials credentials, HttpClient client) {
super(credentials, Constants.CB_PRIME_BASE_URL, client);
this.baseUrl = Constants.CB_PRIME_BASE_URL;
}

public String getBaseUrl() {
return baseUrl;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions tools/model-generator/config/generator-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"specUrl": "https://api.prime.coinbase.com/v1/openapi.yaml",
"committedSpecPath": "apiSpec/prime-public-spec.yaml",
"tagToFolderOverrides": {
"Travel Rule": "transactions"
},
"modelTypeMappings": {
"CreateOnchainTransactionRequestEVMParams": "EvmParams",
"CreateOnchainTransactionRequestEvmParams": "EvmParams",
"FcmFuturesSweepRequestAmount": "SweepAmount",
"FcmFuturesSweep": "FuturesSweep",
"EvmParam": "EvmParams",
"RPC": "RpcConfig"
},
"sharedModelMappings": {
"PaginatedResponse": "com.coinbase.prime.common.Pagination"
},
"protectedCompatibilityFiles": [
"com/coinbase/prime/portfolios/GetPortfolioCounterpartyIdRequest.java",
"com/coinbase/prime/portfolios/GetPortfolioCounterpartyIdResponse.java",
"com/coinbase/prime/portfolios/PortfoliosService.java",
"com/coinbase/prime/portfolios/PortfoliosServiceImpl.java"
]
}
182 changes: 182 additions & 0 deletions tools/model-generator/config/operations-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
[
{
"operationId": "PrimeRESTAPI_GetActivity",
"convenienceConstructorParameters": ["activity_id"]
},
{
"operationId": "PrimeRESTAPI_GetEntityActivities",
"parameterTypeOverrides": {
"activity_level": "com.coinbase.prime.model.enums.ActivityLevel",
"symbols": "String[]",
"categories": "com.coinbase.prime.model.enums.ActivityCategory[]",
"statuses": "com.coinbase.prime.model.enums.ActivityStatus[]"
}
},
{
"operationId": "PrimeRESTAPI_GetPortfolioActivities",
"parameterTypeOverrides": {
"symbols": "String[]",
"categories": "com.coinbase.prime.model.enums.ActivityCategory[]",
"statuses": "com.coinbase.prime.model.enums.ActivityStatus[]"
}
},
{
"operationId": "PrimeRESTAPI_ListEntityBalances",
"parameterTypeOverrides": {
"symbols": "String[]",
"aggregation_type": "com.coinbase.prime.model.enums.PortfolioBalanceType"
}
},
{
"operationId": "PrimeRESTAPI_GetPortfolioBalances",
"paginated": true,
"parameterTypeOverrides": {
"symbols": "String[]",
"balance_type": "com.coinbase.prime.model.enums.PortfolioBalanceType"
}
},
{
"operationId": "PrimeRESTAPI_GetCandles",
"parameterTypeOverrides": {
"granularity": "com.coinbase.prime.model.enums.CandlesGranularity"
}
},
{
"operationId": "PrimeRESTAPI_GetOpenOrders",
"parameterTypeOverrides": {
"product_ids": "String[]",
"order_type": "com.coinbase.prime.model.enums.OrderType",
"order_side": "com.coinbase.prime.model.enums.OrderSide"
}
},
{
"operationId": "PrimeRESTAPI_GetOrders",
"sdkMethod": "ListPortfolioOrders",
"responseTypeOverrides": {
"orders": "com.coinbase.prime.model.Order[]"
},
"parameterTypeOverrides": {
"order_type": "com.coinbase.prime.model.enums.OrderType",
"order_side": "com.coinbase.prime.model.enums.OrderSide",
"order_statuses": "com.coinbase.prime.model.enums.OrderStatus[]",
"product_ids": "String[]"
}
},
{
"operationId": "PrimeRESTAPI_GetOrder",
"sdkMethod": "GetOrderByOrderId"
},
{
"operationId": "PrimeRESTAPI_GetEntityPaymentMethods",
"sdkMethod": "ListPaymentMethods"
},
{
"operationId": "PrimeRESTAPI_GetPortfolios",
"omitRequest": false
},
{
"operationId": "PrimeRESTAPI_RotateAPIKey",
"sdkMethod": "RotateApiKey",
"serviceFolder": "apikey",
"serviceName": "ApiKeyService",
"parameterTypeOverrides": {
"duration_seconds": "Long"
}
},
{
"operationId": "PrimeRESTAPI_ListAdvancedTransferTransactions",
"serviceFolder": "advancedtransfer",
"serviceName": "AdvancedTransferService"
},
{
"operationId": "PrimeRESTAPI_GetPortfolioCounterpartyID",
"sdkMethod": "GetPortfolioCounterpartyId"
},
{
"operationId": "PrimeRESTAPI_GetBuyingPower",
"sdkMethod": "GetPortfolioBuyingPower"
},
{
"operationId": "PrimeRESTAPI_GetWithdrawalPower",
"sdkMethod": "GetPortfolioWithdrawalPower"
},
{
"operationId": "PrimeRESTAPI_GetXMLiquidation",
"sdkMethod": "GetCrossMarginLiquidation"
},
{
"operationId": "PrimeRESTAPI_ListXMLiquidations",
"sdkMethod": "ListCrossMarginLiquidations",
"parameterTypeOverrides": {
"status": "com.coinbase.prime.model.enums.XMLiquidationStatus"
}
},
{
"operationId": "PrimeRESTAPI_GetDerivativePositions",
"sdkMethod": "GetDerivativePositions"
},
{
"operationId": "PrimeRESTAPI_StakingClaimRewards",
"sdkMethod": "ClaimRewards"
},
{
"operationId": "PrimeRESTAPI_StakingInitiate",
"sdkMethod": "CreateStake"
},
{
"operationId": "PrimeRESTAPI_StakingUnstake",
"sdkMethod": "CreateUnstake"
},
{
"operationId": "PrimeRESTAPI_ListTransactionValidators",
"parameterTypeOverrides": {
"transaction_ids": "String[]"
}
},
{
"operationId": "PrimeRESTAPI_GetInvoices",
"parameterTypeOverrides": {
"states": "com.coinbase.prime.model.enums.InvoiceState[]"
}
},
{
"operationId": "PrimeRESTAPI_GetPortfolioTransactions",
"parameterTypeOverrides": {
"symbols": "String[]",
"types": "com.coinbase.prime.model.enums.TransactionType[]"
}
},
{
"operationId": "PrimeRESTAPI_GetWalletTransactions",
"parameterTypeOverrides": {
"types": "com.coinbase.prime.model.enums.TransactionType[]"
}
},
{
"operationId": "PrimeRESTAPI_ListWeb3WalletBalances",
"sdkMethod": "ListOnchainWalletBalances"
},
{
"operationId": "PrimeRESTAPI_GetWallets",
"parameterTypeOverrides": {
"type": "com.coinbase.prime.model.enums.WalletType",
"symbols": "String[]"
}
},
{
"operationId": "PrimeRESTAPI_CreateWallet",
"propertyNameOverrides": {
"wallet_type": "type"
}
},
{
"operationId": "PrimeRESTAPI_GetWalletDepositInstructions",
"parameterTypeOverrides": {
"deposit_type": "com.coinbase.prime.model.enums.WalletDepositInstructionType"
}
},
{
"operationId": "PrimeRESTAPI_GetFcmBalance",
"sdkMethod": "GetEntityFcmBalance"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2026-present Coinbase Global, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.coinbase.tools.modelgenerator;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** Groups validated bindings in the deterministic order consumed by source emitters. */
public final class ClientSurfacePhase {
private ClientSurfacePhase() {}

public static Map<String, List<OperationBinding>> groupByService(List<OperationBinding> bindings) {
Map<String, List<OperationBinding>> services = new LinkedHashMap<>();
List<OperationBinding> ordered = new ArrayList<>(bindings);
ordered.sort(Comparator.comparing(OperationBinding::serviceFolder).thenComparing(OperationBinding::sdkMethod));
for (OperationBinding binding : ordered) {
services.computeIfAbsent(binding.serviceFolder(), ignored -> new ArrayList<>()).add(binding);
}
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2026-present Coinbase Global, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.coinbase.tools.modelgenerator;

import java.nio.file.Path;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Emits the static factory surface for all generated Prime services. */
public final class FactoryPhase {
private FactoryPhase() {}
public static Map<Path, String> render(List<OperationBinding> bindings) {
Map<String, OperationBinding> services = new LinkedHashMap<>();
for (OperationBinding binding : bindings) services.putIfAbsent(binding.serviceName(), binding);
List<OperationBinding> ordered = new java.util.ArrayList<>(services.values()); ordered.sort(Comparator.comparing(OperationBinding::serviceName));
Set<String> imports = new LinkedHashSet<>(); imports.add("com.coinbase.prime.client.CoinbasePrimeClient");
for (OperationBinding binding : ordered) { String prefix="com.coinbase.prime." + binding.serviceFolder() + "."; imports.add(prefix + binding.serviceName()); imports.add(prefix + binding.serviceName() + "Impl"); }
StringBuilder source=new StringBuilder(SourceTemplates.header()).append("package com.coinbase.prime.factory;\n\n"); SourceTemplates.imports(source, imports); source.append("public class PrimeServiceFactory {\n");
for (OperationBinding binding : ordered) source.append(" public static ").append(binding.serviceName()).append(" create").append(binding.serviceName()).append("(CoinbasePrimeClient client) {\n return new ").append(binding.serviceName()).append("Impl(client);\n }\n\n");
source.append("}\n"); Map<Path,String> result=new LinkedHashMap<>(); result.put(Path.of("com/coinbase/prime/factory/PrimeServiceFactory.java"),source.toString()); return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2026-present Coinbase Global, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.coinbase.tools.modelgenerator;

/** Distinguishes domain enums from OpenAPI error Subcode and ErrorCode enums. */
public final class GeneratedEnumKind {
public static final String ENUMS_PACKAGE = "com.coinbase.prime.model.enums";
public static final String ERRORS_PACKAGE = "com.coinbase.prime.model.errors";

private GeneratedEnumKind() {}

public static boolean isSubcode(String typeName) {
return typeName.endsWith("Subcode");
}

public static boolean isErrorCode(String typeName) {
return typeName.endsWith("ErrorCode");
}

public static boolean isErrorEnum(String typeName) {
return isSubcode(typeName) || isErrorCode(typeName);
}

public static String packageFor(String typeName) {
return isErrorEnum(typeName) ? ERRORS_PACKAGE : ENUMS_PACKAGE;
}
}
Loading
Loading