() {});
}
+
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateConversionRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateConversionRequest.java
index af2fa1cc..14af896f 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateConversionRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateConversionRequest.java
@@ -20,23 +20,32 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.UUID;
-
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Conversion
+ */
public class CreateConversionRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
+ @JsonProperty("amount")
private String amount;
+
+ @JsonProperty("destination")
private String destination;
+
@JsonProperty("idempotency_key")
private String idempotencyKey;
+
@JsonProperty("source_symbol")
private String sourceSymbol;
+
@JsonProperty("destination_symbol")
private String destinationSymbol;
@@ -157,26 +166,16 @@ public Builder destinationSymbol(String destinationSymbol) {
}
public CreateConversionRequest build() throws CoinbaseClientException {
- this.validate();
- if (isNullOrEmpty(this.idempotencyKey)) {
- this.idempotencyKey(UUID.randomUUID().toString());
- }
+ validate();
return new CreateConversionRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("PortfolioId cannot be null");
+ throw new CoinbaseClientException("PortfolioId is required");
}
-
if (isNullOrEmpty(this.walletId)) {
- throw new CoinbaseClientException("WalletId cannot be null");
- }
- if (isNullOrEmpty(this.amount)) {
- throw new CoinbaseClientException("Amount cannot be null");
- }
- if (isNullOrEmpty(this.destination)) {
- throw new CoinbaseClientException("Destination cannot be null");
+ throw new CoinbaseClientException("WalletId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateConversionResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateConversionResponse.java
index a2d11301..9d091398 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateConversionResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateConversionResponse.java
@@ -18,16 +18,28 @@
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Create Conversion
+ */
public class CreateConversionResponse {
@JsonProperty("activity_id")
private String activityId;
+
@JsonProperty("source_symbol")
private String sourceSymbol;
+
@JsonProperty("destination_symbol")
private String destinationSymbol;
+
+ @JsonProperty("amount")
private String amount;
+
+ @JsonProperty("destination")
private String destination;
+
+ @JsonProperty("source")
private String source;
+
@JsonProperty("transaction_id")
private String transactionId;
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionRequest.java
index fea915a1..f9ff5eba 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionRequest.java
@@ -16,14 +16,17 @@
package com.coinbase.prime.transactions;
+import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.model.EvmParams;
import com.coinbase.prime.model.RpcConfig;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.coinbase.core.errors.CoinbaseClientException;
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Onchain Transaction
+ */
public class CreateOnchainTransactionRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
@@ -36,6 +39,7 @@ public class CreateOnchainTransactionRequest {
@JsonProperty("raw_unsigned_txn")
private String rawUnsignedTxn;
+ @JsonProperty("rpc")
private RpcConfig rpc;
@JsonProperty("evm_params")
@@ -127,24 +131,17 @@ public Builder evmParams(EvmParams evmParams) {
return this;
}
- public CreateOnchainTransactionRequest build() {
- this.validate();
+ public CreateOnchainTransactionRequest build() throws CoinbaseClientException {
+ validate();
return new CreateOnchainTransactionRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("PortfolioId cannot be null");
+ throw new CoinbaseClientException("PortfolioId is required");
}
if (isNullOrEmpty(this.walletId)) {
- throw new CoinbaseClientException("WalletId cannot be null");
- }
-
- if (isNullOrEmpty(this.rawUnsignedTxn)) {
- throw new CoinbaseClientException("RawUnsignedTxn cannot be null");
- }
- if (this.rpc == null) {
- throw new CoinbaseClientException("Rpc cannot be null");
+ throw new CoinbaseClientException("WalletId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionResponse.java
index c6ac74d9..ef96b4e3 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateOnchainTransactionResponse.java
@@ -19,12 +19,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for creating an on-chain transaction.
- *
- * Contains the transaction ID for the newly created on-chain transaction.
+ * Create Onchain Transaction
*/
public class CreateOnchainTransactionResponse {
- /** Unique identifier for the created transaction */
@JsonProperty("transaction_id")
private String transactionId;
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateTransferRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateTransferRequest.java
new file mode 100644
index 00000000..d4a16ab2
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/transactions/CreateTransferRequest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.prime.transactions;
+
+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;
+
+/**
+ * Create Transfer
+ */
+public class CreateTransferRequest {
+ @JsonProperty(required = true, value = "portfolio_id")
+ @JsonIgnore
+ private String portfolioId;
+
+ @JsonProperty(required = true, value = "wallet_id")
+ @JsonIgnore
+ private String walletId;
+
+ @JsonProperty("amount")
+ private String amount;
+
+ @JsonProperty("destination")
+ private String destination;
+
+ @JsonProperty("idempotency_key")
+ private String idempotencyKey;
+
+ @JsonProperty("currency_symbol")
+ private String currencySymbol;
+
+ public CreateTransferRequest() {
+ }
+
+ public CreateTransferRequest(Builder builder) {
+ this.portfolioId = builder.portfolioId;
+ this.walletId = builder.walletId;
+ this.amount = builder.amount;
+ this.destination = builder.destination;
+ this.idempotencyKey = builder.idempotencyKey;
+ this.currencySymbol = builder.currencySymbol;
+ }
+
+ public String getPortfolioId() {
+ return portfolioId;
+ }
+
+ public void setPortfolioId(String portfolioId) {
+ this.portfolioId = portfolioId;
+ }
+
+ public String getWalletId() {
+ return walletId;
+ }
+
+ public void setWalletId(String walletId) {
+ this.walletId = walletId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ public String getIdempotencyKey() {
+ return idempotencyKey;
+ }
+
+ public void setIdempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ }
+
+ public String getCurrencySymbol() {
+ return currencySymbol;
+ }
+
+ public void setCurrencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ }
+
+ public static class Builder {
+ private String portfolioId;
+ private String walletId;
+ private String amount;
+ private String destination;
+ private String idempotencyKey;
+ private String currencySymbol;
+
+ public Builder() {
+ }
+
+ public Builder portfolioId(String portfolioId) {
+ this.portfolioId = portfolioId;
+ return this;
+ }
+
+ public Builder walletId(String walletId) {
+ this.walletId = walletId;
+ return this;
+ }
+
+ public Builder amount(String amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder destination(String destination) {
+ this.destination = destination;
+ return this;
+ }
+
+ public Builder idempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ return this;
+ }
+
+ public Builder currencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ return this;
+ }
+
+ public CreateTransferRequest build() throws CoinbaseClientException {
+ validate();
+ return new CreateTransferRequest(this);
+ }
+
+ private void validate() throws CoinbaseClientException {
+ if (isNullOrEmpty(this.portfolioId)) {
+ throw new CoinbaseClientException("PortfolioId is required");
+ }
+ if (isNullOrEmpty(this.walletId)) {
+ throw new CoinbaseClientException("WalletId is required");
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateTransferResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateTransferResponse.java
new file mode 100644
index 00000000..93a41152
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/transactions/CreateTransferResponse.java
@@ -0,0 +1,138 @@
+/*
+ * 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.prime.transactions;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Create Transfer
+ */
+public class CreateTransferResponse {
+ @JsonProperty("activity_id")
+ private String activityId;
+
+ @JsonProperty("approval_url")
+ private String approvalUrl;
+
+ @JsonProperty("symbol")
+ private String symbol;
+
+ @JsonProperty("amount")
+ private String amount;
+
+ @JsonProperty("fee")
+ private String fee;
+
+ @JsonProperty("destination_address")
+ private String destinationAddress;
+
+ @JsonProperty("destination_type")
+ private String destinationType;
+
+ @JsonProperty("source_address")
+ private String sourceAddress;
+
+ @JsonProperty("source_type")
+ private String sourceType;
+
+ @JsonProperty("transaction_id")
+ private String transactionId;
+
+ public CreateTransferResponse() {
+ }
+
+ public String getActivityId() {
+ return activityId;
+ }
+
+ public void setActivityId(String activityId) {
+ this.activityId = activityId;
+ }
+
+ public String getApprovalUrl() {
+ return approvalUrl;
+ }
+
+ public void setApprovalUrl(String approvalUrl) {
+ this.approvalUrl = approvalUrl;
+ }
+
+ public String getSymbol() {
+ return symbol;
+ }
+
+ public void setSymbol(String symbol) {
+ this.symbol = symbol;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getFee() {
+ return fee;
+ }
+
+ public void setFee(String fee) {
+ this.fee = fee;
+ }
+
+ public String getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ public void setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ }
+
+ public String getDestinationType() {
+ return destinationType;
+ }
+
+ public void setDestinationType(String destinationType) {
+ this.destinationType = destinationType;
+ }
+
+ public String getSourceAddress() {
+ return sourceAddress;
+ }
+
+ public void setSourceAddress(String sourceAddress) {
+ this.sourceAddress = sourceAddress;
+ }
+
+ public String getSourceType() {
+ return sourceType;
+ }
+
+ public void setSourceType(String sourceType) {
+ this.sourceType = sourceType;
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public void setTransactionId(String transactionId) {
+ this.transactionId = transactionId;
+ }
+
+}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferRequest.java
index a357991d..a376f21b 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferRequest.java
@@ -20,23 +20,32 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
-import java.util.UUID;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Transfer
+ */
public class CreateWalletTransferRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
+ @JsonProperty("amount")
private String amount;
- @JsonProperty("currency_symbol")
- private String currencySymbol;
+
+ @JsonProperty("destination")
private String destination;
+
@JsonProperty("idempotency_key")
private String idempotencyKey;
+ @JsonProperty("currency_symbol")
+ private String currencySymbol;
+
public CreateWalletTransferRequest() {
}
@@ -44,9 +53,9 @@ public CreateWalletTransferRequest(Builder builder) {
this.portfolioId = builder.portfolioId;
this.walletId = builder.walletId;
this.amount = builder.amount;
- this.currencySymbol = builder.currencySymbol;
this.destination = builder.destination;
this.idempotencyKey = builder.idempotencyKey;
+ this.currencySymbol = builder.currencySymbol;
}
public String getPortfolioId() {
@@ -73,14 +82,6 @@ public void setAmount(String amount) {
this.amount = amount;
}
- public String getCurrencySymbol() {
- return currencySymbol;
- }
-
- public void setCurrencySymbol(String currencySymbol) {
- this.currencySymbol = currencySymbol;
- }
-
public String getDestination() {
return destination;
}
@@ -97,26 +98,37 @@ public void setIdempotencyKey(String idempotencyKey) {
this.idempotencyKey = idempotencyKey;
}
+ public String getCurrencySymbol() {
+ return currencySymbol;
+ }
+
+ public void setCurrencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ }
+
public static class Builder {
- private final String portfolioId;
- private final String walletId;
+ private String portfolioId;
+ private String walletId;
private String amount;
- private String currencySymbol;
private String destination;
private String idempotencyKey;
+ private String currencySymbol;
+
+ public Builder() {
+ }
- public Builder(String portfolioId, String walletId) {
+ public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
- this.walletId = walletId;
+ return this;
}
- public Builder amount(String amount) {
- this.amount = amount;
+ public Builder walletId(String walletId) {
+ this.walletId = walletId;
return this;
}
- public Builder currencySymbol(String currencySymbol) {
- this.currencySymbol = currencySymbol;
+ public Builder amount(String amount) {
+ this.amount = amount;
return this;
}
@@ -130,11 +142,13 @@ public Builder idempotencyKey(String idempotencyKey) {
return this;
}
+ public Builder currencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ return this;
+ }
+
public CreateWalletTransferRequest build() throws CoinbaseClientException {
- this.validate();
- if (isNullOrEmpty(this.idempotencyKey)) {
- this.idempotencyKey(UUID.randomUUID().toString());
- }
+ validate();
return new CreateWalletTransferRequest(this);
}
@@ -145,15 +159,6 @@ private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.walletId)) {
throw new CoinbaseClientException("WalletId is required");
}
- if (isNullOrEmpty(this.amount)) {
- throw new CoinbaseClientException("Amount is required");
- }
- if (isNullOrEmpty(this.currencySymbol)) {
- throw new CoinbaseClientException("Currency symbol is required");
- }
- if (isNullOrEmpty(this.destination)) {
- throw new CoinbaseClientException("Destination is required");
- }
}
}
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferResponse.java
index e864ce50..431b6a10 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWalletTransferResponse.java
@@ -16,41 +16,39 @@
package com.coinbase.prime.transactions;
-import com.coinbase.prime.model.enums.DestinationType;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for creating a wallet transfer.
- *
- * Contains the details of the transfer request including activity ID, approval URL,
- * transfer amounts, addresses, and transaction information.
+ * Create Transfer
*/
public class CreateWalletTransferResponse {
- /** The activity ID for the transfer */
@JsonProperty("activity_id")
private String activityId;
- /** A URL to the activity associated with this transfer for approval */
+
@JsonProperty("approval_url")
private String approvalUrl;
- /** The symbol of the transferred asset */
+
+ @JsonProperty("symbol")
private String symbol;
- /** The amount being transferred */
+
+ @JsonProperty("amount")
private String amount;
- /** The fee associated with the transfer */
+
+ @JsonProperty("fee")
private String fee;
- /** The destination address for the transfer */
+
@JsonProperty("destination_address")
private String destinationAddress;
- /** The type of the destination (e.g., WALLET, EXCHANGE) */
+
@JsonProperty("destination_type")
- private DestinationType destinationType;
- /** The source address for the transfer */
+ private String destinationType;
+
@JsonProperty("source_address")
private String sourceAddress;
- /** The type of the source (e.g., WALLET, EXCHANGE) */
+
@JsonProperty("source_type")
- private DestinationType sourceType;
- /** The unique identifier for the transfer transaction */
+ private String sourceType;
+
@JsonProperty("transaction_id")
private String transactionId;
@@ -105,11 +103,11 @@ public void setDestinationAddress(String destinationAddress) {
this.destinationAddress = destinationAddress;
}
- public DestinationType getDestinationType() {
+ public String getDestinationType() {
return destinationType;
}
- public void setDestinationType(DestinationType destinationType) {
+ public void setDestinationType(String destinationType) {
this.destinationType = destinationType;
}
@@ -121,11 +119,11 @@ public void setSourceAddress(String sourceAddress) {
this.sourceAddress = sourceAddress;
}
- public DestinationType getSourceType() {
+ public String getSourceType() {
return sourceType;
}
- public void setSourceType(DestinationType sourceType) {
+ public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalRequest.java
index c9232b3d..0510366b 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalRequest.java
@@ -25,28 +25,41 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
-import java.util.UUID;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Withdrawal
+ */
public class CreateWalletWithdrawalRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
+ @JsonProperty("amount")
private String amount;
- @JsonProperty("currency_symbol")
- private String currencySymbol;
+
@JsonProperty("destination_type")
private DestinationType destinationType;
+
@JsonProperty("idempotency_key")
private String idempotencyKey;
+
+ @JsonProperty("currency_symbol")
+ private String currencySymbol;
+
@JsonProperty("payment_method")
private PaymentMethodDestination paymentMethod;
+
@JsonProperty("blockchain_address")
private BlockchainAddress blockchainAddress;
+
+ @JsonProperty("counterparty")
private CounterpartyDestination counterparty;
+
@JsonProperty("travel_rule_data")
private TravelRuleData travelRuleData;
@@ -57,9 +70,9 @@ public CreateWalletWithdrawalRequest(Builder builder) {
this.portfolioId = builder.portfolioId;
this.walletId = builder.walletId;
this.amount = builder.amount;
- this.currencySymbol = builder.currencySymbol;
this.destinationType = builder.destinationType;
this.idempotencyKey = builder.idempotencyKey;
+ this.currencySymbol = builder.currencySymbol;
this.paymentMethod = builder.paymentMethod;
this.blockchainAddress = builder.blockchainAddress;
this.counterparty = builder.counterparty;
@@ -90,14 +103,6 @@ public void setAmount(String amount) {
this.amount = amount;
}
- public String getCurrencySymbol() {
- return currencySymbol;
- }
-
- public void setCurrencySymbol(String currencySymbol) {
- this.currencySymbol = currencySymbol;
- }
-
public DestinationType getDestinationType() {
return destinationType;
}
@@ -114,6 +119,14 @@ public void setIdempotencyKey(String idempotencyKey) {
this.idempotencyKey = idempotencyKey;
}
+ public String getCurrencySymbol() {
+ return currencySymbol;
+ }
+
+ public void setCurrencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ }
+
public PaymentMethodDestination getPaymentMethod() {
return paymentMethod;
}
@@ -147,29 +160,32 @@ public void setTravelRuleData(TravelRuleData travelRuleData) {
}
public static class Builder {
- private final String portfolioId;
- private final String walletId;
+ private String portfolioId;
+ private String walletId;
private String amount;
- private String currencySymbol;
private DestinationType destinationType;
private String idempotencyKey;
+ private String currencySymbol;
private PaymentMethodDestination paymentMethod;
private BlockchainAddress blockchainAddress;
private CounterpartyDestination counterparty;
private TravelRuleData travelRuleData;
- public Builder(String portfolioId, String walletId) {
+ public Builder() {
+ }
+
+ public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
- this.walletId = walletId;
+ return this;
}
- public Builder amount(String amount) {
- this.amount = amount;
+ public Builder walletId(String walletId) {
+ this.walletId = walletId;
return this;
}
- public Builder currencySymbol(String currencySymbol) {
- this.currencySymbol = currencySymbol;
+ public Builder amount(String amount) {
+ this.amount = amount;
return this;
}
@@ -183,6 +199,11 @@ public Builder idempotencyKey(String idempotencyKey) {
return this;
}
+ public Builder currencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ return this;
+ }
+
public Builder paymentMethod(PaymentMethodDestination paymentMethod) {
this.paymentMethod = paymentMethod;
return this;
@@ -204,10 +225,7 @@ public Builder travelRuleData(TravelRuleData travelRuleData) {
}
public CreateWalletWithdrawalRequest build() throws CoinbaseClientException {
- this.validate();
- if (isNullOrEmpty(this.idempotencyKey)) {
- this.idempotencyKey(UUID.randomUUID().toString());
- }
+ validate();
return new CreateWalletWithdrawalRequest(this);
}
@@ -218,15 +236,6 @@ private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.walletId)) {
throw new CoinbaseClientException("WalletId is required");
}
- if (isNullOrEmpty(this.amount)) {
- throw new CoinbaseClientException("Amount is required");
- }
- if (isNullOrEmpty(this.currencySymbol)) {
- throw new CoinbaseClientException("Currency symbol is required");
- }
- if (this.destinationType == null) {
- throw new CoinbaseClientException("Destination type is required");
- }
}
}
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalResponse.java
index 90d8539a..f2d4ba94 100644
--- a/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWalletWithdrawalResponse.java
@@ -18,27 +18,42 @@
import com.coinbase.prime.model.BlockchainAddress;
import com.coinbase.prime.model.CounterpartyDestination;
-import com.coinbase.prime.model.enums.DestinationType;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Create Withdrawal
+ */
public class CreateWalletWithdrawalResponse {
@JsonProperty("activity_id")
private String activityId;
+
@JsonProperty("approval_url")
private String approvalUrl;
+
+ @JsonProperty("symbol")
private String symbol;
+
+ @JsonProperty("amount")
private String amount;
+
+ @JsonProperty("fee")
private String fee;
+
@JsonProperty("destination_type")
- private DestinationType destinationType;
+ private String destinationType;
+
@JsonProperty("source_type")
- private DestinationType sourceType;
+ private String sourceType;
+
@JsonProperty("blockchain_destination")
private BlockchainAddress blockchainDestination;
+
@JsonProperty("counterparty_destination")
private CounterpartyDestination counterpartyDestination;
+
@JsonProperty("blockchain_source")
private BlockchainAddress blockchainSource;
+
@JsonProperty("transaction_id")
private String transactionId;
@@ -85,19 +100,19 @@ public void setFee(String fee) {
this.fee = fee;
}
- public DestinationType getDestinationType() {
+ public String getDestinationType() {
return destinationType;
}
- public void setDestinationType(DestinationType destinationType) {
+ public void setDestinationType(String destinationType) {
this.destinationType = destinationType;
}
- public DestinationType getSourceType() {
+ public String getSourceType() {
return sourceType;
}
- public void setSourceType(DestinationType sourceType) {
+ public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalRequest.java b/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalRequest.java
new file mode 100644
index 00000000..46f01a50
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalRequest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.prime.transactions;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.model.BlockchainAddress;
+import com.coinbase.prime.model.CounterpartyDestination;
+import com.coinbase.prime.model.PaymentMethodDestination;
+import com.coinbase.prime.model.TravelRuleData;
+import com.coinbase.prime.model.enums.DestinationType;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+
+/**
+ * Create Withdrawal
+ */
+public class CreateWithdrawalRequest {
+ @JsonProperty(required = true, value = "portfolio_id")
+ @JsonIgnore
+ private String portfolioId;
+
+ @JsonProperty(required = true, value = "wallet_id")
+ @JsonIgnore
+ private String walletId;
+
+ @JsonProperty("amount")
+ private String amount;
+
+ @JsonProperty("destination_type")
+ private DestinationType destinationType;
+
+ @JsonProperty("idempotency_key")
+ private String idempotencyKey;
+
+ @JsonProperty("currency_symbol")
+ private String currencySymbol;
+
+ @JsonProperty("payment_method")
+ private PaymentMethodDestination paymentMethod;
+
+ @JsonProperty("blockchain_address")
+ private BlockchainAddress blockchainAddress;
+
+ @JsonProperty("counterparty")
+ private CounterpartyDestination counterparty;
+
+ @JsonProperty("travel_rule_data")
+ private TravelRuleData travelRuleData;
+
+ public CreateWithdrawalRequest() {
+ }
+
+ public CreateWithdrawalRequest(Builder builder) {
+ this.portfolioId = builder.portfolioId;
+ this.walletId = builder.walletId;
+ this.amount = builder.amount;
+ this.destinationType = builder.destinationType;
+ this.idempotencyKey = builder.idempotencyKey;
+ this.currencySymbol = builder.currencySymbol;
+ this.paymentMethod = builder.paymentMethod;
+ this.blockchainAddress = builder.blockchainAddress;
+ this.counterparty = builder.counterparty;
+ this.travelRuleData = builder.travelRuleData;
+ }
+
+ public String getPortfolioId() {
+ return portfolioId;
+ }
+
+ public void setPortfolioId(String portfolioId) {
+ this.portfolioId = portfolioId;
+ }
+
+ public String getWalletId() {
+ return walletId;
+ }
+
+ public void setWalletId(String walletId) {
+ this.walletId = walletId;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public DestinationType getDestinationType() {
+ return destinationType;
+ }
+
+ public void setDestinationType(DestinationType destinationType) {
+ this.destinationType = destinationType;
+ }
+
+ public String getIdempotencyKey() {
+ return idempotencyKey;
+ }
+
+ public void setIdempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ }
+
+ public String getCurrencySymbol() {
+ return currencySymbol;
+ }
+
+ public void setCurrencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ }
+
+ public PaymentMethodDestination getPaymentMethod() {
+ return paymentMethod;
+ }
+
+ public void setPaymentMethod(PaymentMethodDestination paymentMethod) {
+ this.paymentMethod = paymentMethod;
+ }
+
+ public BlockchainAddress getBlockchainAddress() {
+ return blockchainAddress;
+ }
+
+ public void setBlockchainAddress(BlockchainAddress blockchainAddress) {
+ this.blockchainAddress = blockchainAddress;
+ }
+
+ public CounterpartyDestination getCounterparty() {
+ return counterparty;
+ }
+
+ public void setCounterparty(CounterpartyDestination counterparty) {
+ this.counterparty = counterparty;
+ }
+
+ public TravelRuleData getTravelRuleData() {
+ return travelRuleData;
+ }
+
+ public void setTravelRuleData(TravelRuleData travelRuleData) {
+ this.travelRuleData = travelRuleData;
+ }
+
+ public static class Builder {
+ private String portfolioId;
+ private String walletId;
+ private String amount;
+ private DestinationType destinationType;
+ private String idempotencyKey;
+ private String currencySymbol;
+ private PaymentMethodDestination paymentMethod;
+ private BlockchainAddress blockchainAddress;
+ private CounterpartyDestination counterparty;
+ private TravelRuleData travelRuleData;
+
+ public Builder() {
+ }
+
+ public Builder portfolioId(String portfolioId) {
+ this.portfolioId = portfolioId;
+ return this;
+ }
+
+ public Builder walletId(String walletId) {
+ this.walletId = walletId;
+ return this;
+ }
+
+ public Builder amount(String amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder destinationType(DestinationType destinationType) {
+ this.destinationType = destinationType;
+ return this;
+ }
+
+ public Builder idempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ return this;
+ }
+
+ public Builder currencySymbol(String currencySymbol) {
+ this.currencySymbol = currencySymbol;
+ return this;
+ }
+
+ public Builder paymentMethod(PaymentMethodDestination paymentMethod) {
+ this.paymentMethod = paymentMethod;
+ return this;
+ }
+
+ public Builder blockchainAddress(BlockchainAddress blockchainAddress) {
+ this.blockchainAddress = blockchainAddress;
+ return this;
+ }
+
+ public Builder counterparty(CounterpartyDestination counterparty) {
+ this.counterparty = counterparty;
+ return this;
+ }
+
+ public Builder travelRuleData(TravelRuleData travelRuleData) {
+ this.travelRuleData = travelRuleData;
+ return this;
+ }
+
+ public CreateWithdrawalRequest build() throws CoinbaseClientException {
+ validate();
+ return new CreateWithdrawalRequest(this);
+ }
+
+ private void validate() throws CoinbaseClientException {
+ if (isNullOrEmpty(this.portfolioId)) {
+ throw new CoinbaseClientException("PortfolioId is required");
+ }
+ if (isNullOrEmpty(this.walletId)) {
+ throw new CoinbaseClientException("WalletId is required");
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalResponse.java b/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalResponse.java
new file mode 100644
index 00000000..60f4ea5a
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/transactions/CreateWithdrawalResponse.java
@@ -0,0 +1,151 @@
+/*
+ * 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.prime.transactions;
+
+import com.coinbase.prime.model.BlockchainAddress;
+import com.coinbase.prime.model.CounterpartyDestination;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Create Withdrawal
+ */
+public class CreateWithdrawalResponse {
+ @JsonProperty("activity_id")
+ private String activityId;
+
+ @JsonProperty("approval_url")
+ private String approvalUrl;
+
+ @JsonProperty("symbol")
+ private String symbol;
+
+ @JsonProperty("amount")
+ private String amount;
+
+ @JsonProperty("fee")
+ private String fee;
+
+ @JsonProperty("destination_type")
+ private String destinationType;
+
+ @JsonProperty("source_type")
+ private String sourceType;
+
+ @JsonProperty("blockchain_destination")
+ private BlockchainAddress blockchainDestination;
+
+ @JsonProperty("counterparty_destination")
+ private CounterpartyDestination counterpartyDestination;
+
+ @JsonProperty("blockchain_source")
+ private BlockchainAddress blockchainSource;
+
+ @JsonProperty("transaction_id")
+ private String transactionId;
+
+ public CreateWithdrawalResponse() {
+ }
+
+ public String getActivityId() {
+ return activityId;
+ }
+
+ public void setActivityId(String activityId) {
+ this.activityId = activityId;
+ }
+
+ public String getApprovalUrl() {
+ return approvalUrl;
+ }
+
+ public void setApprovalUrl(String approvalUrl) {
+ this.approvalUrl = approvalUrl;
+ }
+
+ public String getSymbol() {
+ return symbol;
+ }
+
+ public void setSymbol(String symbol) {
+ this.symbol = symbol;
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getFee() {
+ return fee;
+ }
+
+ public void setFee(String fee) {
+ this.fee = fee;
+ }
+
+ public String getDestinationType() {
+ return destinationType;
+ }
+
+ public void setDestinationType(String destinationType) {
+ this.destinationType = destinationType;
+ }
+
+ public String getSourceType() {
+ return sourceType;
+ }
+
+ public void setSourceType(String sourceType) {
+ this.sourceType = sourceType;
+ }
+
+ public BlockchainAddress getBlockchainDestination() {
+ return blockchainDestination;
+ }
+
+ public void setBlockchainDestination(BlockchainAddress blockchainDestination) {
+ this.blockchainDestination = blockchainDestination;
+ }
+
+ public CounterpartyDestination getCounterpartyDestination() {
+ return counterpartyDestination;
+ }
+
+ public void setCounterpartyDestination(CounterpartyDestination counterpartyDestination) {
+ this.counterpartyDestination = counterpartyDestination;
+ }
+
+ public BlockchainAddress getBlockchainSource() {
+ return blockchainSource;
+ }
+
+ public void setBlockchainSource(BlockchainAddress blockchainSource) {
+ this.blockchainSource = blockchainSource;
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public void setTransactionId(String transactionId) {
+ this.transactionId = transactionId;
+ }
+
+}
diff --git a/src/main/java/com/coinbase/prime/transactions/GetTransactionRequest.java b/src/main/java/com/coinbase/prime/transactions/GetTransactionRequest.java
index f9ff6ae1..9513fc5c 100644
--- a/src/main/java/com/coinbase/prime/transactions/GetTransactionRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/GetTransactionRequest.java
@@ -20,12 +20,16 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Get Transaction by Transaction ID
+ */
public class GetTransactionRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "transaction_id")
@JsonIgnore
private String transactionId;
@@ -55,16 +59,24 @@ public void setTransactionId(String transactionId) {
}
public static class Builder {
- private final String portfolioId;
- private final String transactionId;
+ private String portfolioId;
+ private String transactionId;
+
+ public Builder() {
+ }
- public Builder(String portfolioId, String transactionId) {
+ public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
+ return this;
+ }
+
+ public Builder transactionId(String transactionId) {
this.transactionId = transactionId;
+ return this;
}
public GetTransactionRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new GetTransactionRequest(this);
}
diff --git a/src/main/java/com/coinbase/prime/transactions/GetTransactionResponse.java b/src/main/java/com/coinbase/prime/transactions/GetTransactionResponse.java
index bf9f8e2d..1fbb992e 100644
--- a/src/main/java/com/coinbase/prime/transactions/GetTransactionResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/GetTransactionResponse.java
@@ -17,15 +17,13 @@
package com.coinbase.prime.transactions;
import com.coinbase.prime.model.Transaction;
+import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for retrieving a specific transaction by transaction ID.
- *
- * Contains the transaction information for the requested transaction ID.
- * Only transactions that affect balances are accessible.
+ * Get Transaction by Transaction ID
*/
public class GetTransactionResponse {
- /** The transaction information */
+ @JsonProperty("transaction")
private Transaction transaction;
public GetTransactionResponse() {
diff --git a/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataRequest.java b/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataRequest.java
index 48efda4f..3f97b952 100644
--- a/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataRequest.java
@@ -22,6 +22,9 @@
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Get Transaction Travel Rule Data
+ */
public class GetTransactionTravelRuleDataRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
diff --git a/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataResponse.java b/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataResponse.java
index e04703fc..d85102a9 100644
--- a/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/GetTransactionTravelRuleDataResponse.java
@@ -19,16 +19,27 @@
import com.coinbase.prime.model.TravelRuleParty;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Get Transaction Travel Rule Data
+ */
public class GetTransactionTravelRuleDataResponse {
- private String amount;
+ @JsonProperty("fulfilled")
+ private Boolean fulfilled;
- @JsonProperty("amount_currency")
- private String amountCurrency;
+ @JsonProperty("is_self")
+ private Boolean isSelf;
+
+ @JsonProperty("originator")
+ private TravelRuleParty originator;
+ @JsonProperty("beneficiary")
private TravelRuleParty beneficiary;
- @JsonProperty("blockchain_network")
- private String blockchainNetwork;
+ @JsonProperty("amount")
+ private String amount;
+
+ @JsonProperty("amount_currency")
+ private String amountCurrency;
@JsonProperty("fiat_amount")
private String fiatAmount;
@@ -36,30 +47,34 @@ public class GetTransactionTravelRuleDataResponse {
@JsonProperty("fiat_amount_currency")
private String fiatAmountCurrency;
- private Boolean fulfilled;
+ @JsonProperty("blockchain_network")
+ private String blockchainNetwork;
- @JsonProperty("is_self")
- private Boolean isSelf;
+ public GetTransactionTravelRuleDataResponse() {
+ }
- private TravelRuleParty originator;
+ public Boolean getFulfilled() {
+ return fulfilled;
+ }
- public GetTransactionTravelRuleDataResponse() {
+ public void setFulfilled(Boolean fulfilled) {
+ this.fulfilled = fulfilled;
}
- public String getAmount() {
- return amount;
+ public Boolean getIsSelf() {
+ return isSelf;
}
- public void setAmount(String amount) {
- this.amount = amount;
+ public void setIsSelf(Boolean isSelf) {
+ this.isSelf = isSelf;
}
- public String getAmountCurrency() {
- return amountCurrency;
+ public TravelRuleParty getOriginator() {
+ return originator;
}
- public void setAmountCurrency(String amountCurrency) {
- this.amountCurrency = amountCurrency;
+ public void setOriginator(TravelRuleParty originator) {
+ this.originator = originator;
}
public TravelRuleParty getBeneficiary() {
@@ -70,12 +85,20 @@ public void setBeneficiary(TravelRuleParty beneficiary) {
this.beneficiary = beneficiary;
}
- public String getBlockchainNetwork() {
- return blockchainNetwork;
+ public String getAmount() {
+ return amount;
}
- public void setBlockchainNetwork(String blockchainNetwork) {
- this.blockchainNetwork = blockchainNetwork;
+ public void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public String getAmountCurrency() {
+ return amountCurrency;
+ }
+
+ public void setAmountCurrency(String amountCurrency) {
+ this.amountCurrency = amountCurrency;
}
public String getFiatAmount() {
@@ -94,27 +117,12 @@ public void setFiatAmountCurrency(String fiatAmountCurrency) {
this.fiatAmountCurrency = fiatAmountCurrency;
}
- public Boolean getFulfilled() {
- return fulfilled;
- }
-
- public void setFulfilled(Boolean fulfilled) {
- this.fulfilled = fulfilled;
- }
-
- public Boolean getIsSelf() {
- return isSelf;
- }
-
- public void setIsSelf(Boolean isSelf) {
- this.isSelf = isSelf;
+ public String getBlockchainNetwork() {
+ return blockchainNetwork;
}
- public TravelRuleParty getOriginator() {
- return originator;
+ public void setBlockchainNetwork(String blockchainNetwork) {
+ this.blockchainNetwork = blockchainNetwork;
}
- public void setOriginator(TravelRuleParty originator) {
- this.originator = originator;
- }
}
diff --git a/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsRequest.java b/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsRequest.java
index edce5763..3ff3bcac 100644
--- a/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsRequest.java
@@ -19,24 +19,40 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.common.PrimeListRequest;
import com.coinbase.prime.common.Pagination;
-import com.coinbase.prime.model.enums.TransactionType;
import com.coinbase.prime.model.enums.SortDirection;
+import com.coinbase.prime.model.enums.TransactionType;
+import com.coinbase.prime.model.enums.TravelRuleStatus;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Portfolio Transactions
+ */
public class ListPortfolioTransactionsRequest extends PrimeListRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
+ @JsonProperty("symbols")
private String[] symbols;
+
+ @JsonProperty("types")
private TransactionType[] types;
+
@JsonProperty("start_time")
private String startTime;
+
@JsonProperty("end_time")
private String endTime;
+ @JsonProperty("get_network_unified_transactions")
+ private Boolean getNetworkUnifiedTransactions;
+
+ @JsonProperty("travel_rule_status")
+ private TravelRuleStatus[] travelRuleStatus;
+
public ListPortfolioTransactionsRequest() {
}
@@ -47,6 +63,8 @@ public ListPortfolioTransactionsRequest(Builder builder) {
this.types = builder.types;
this.startTime = builder.startTime;
this.endTime = builder.endTime;
+ this.getNetworkUnifiedTransactions = builder.getNetworkUnifiedTransactions;
+ this.travelRuleStatus = builder.travelRuleStatus;
}
public String getPortfolioId() {
@@ -89,12 +107,30 @@ public void setEndTime(String endTime) {
this.endTime = endTime;
}
+ public Boolean getGetNetworkUnifiedTransactions() {
+ return getNetworkUnifiedTransactions;
+ }
+
+ public void setGetNetworkUnifiedTransactions(Boolean getNetworkUnifiedTransactions) {
+ this.getNetworkUnifiedTransactions = getNetworkUnifiedTransactions;
+ }
+
+ public TravelRuleStatus[] getTravelRuleStatus() {
+ return travelRuleStatus;
+ }
+
+ public void setTravelRuleStatus(TravelRuleStatus[] travelRuleStatus) {
+ this.travelRuleStatus = travelRuleStatus;
+ }
+
public static class Builder {
private String portfolioId;
private String[] symbols;
private TransactionType[] types;
private String startTime;
private String endTime;
+ private Boolean getNetworkUnifiedTransactions;
+ private TravelRuleStatus[] travelRuleStatus;
private String cursor;
private SortDirection sortDirection;
private Integer limit;
@@ -127,9 +163,13 @@ public Builder endTime(String endTime) {
return this;
}
- public Builder pagination(Pagination pagination) {
- this.cursor = pagination.getNextCursor();
- this.sortDirection = pagination.getSortDirection();
+ public Builder getNetworkUnifiedTransactions(Boolean getNetworkUnifiedTransactions) {
+ this.getNetworkUnifiedTransactions = getNetworkUnifiedTransactions;
+ return this;
+ }
+
+ public Builder travelRuleStatus(TravelRuleStatus[] travelRuleStatus) {
+ this.travelRuleStatus = travelRuleStatus;
return this;
}
@@ -138,8 +178,14 @@ public Builder limit(Integer limit) {
return this;
}
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
+ return this;
+ }
+
public ListPortfolioTransactionsRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListPortfolioTransactionsRequest(this);
}
diff --git a/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsResponse.java b/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsResponse.java
index 6ce3a7f6..56266fb4 100644
--- a/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/ListPortfolioTransactionsResponse.java
@@ -18,17 +18,16 @@
import com.coinbase.prime.common.Pagination;
import com.coinbase.prime.model.Transaction;
+import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for listing transactions for a given portfolio.
- *
- * Contains an array of transactions and pagination information. Only transactions
- * that affect balances are accessible.
+ * List Portfolio Transactions
*/
public class ListPortfolioTransactionsResponse {
- /** Array of transactions for the portfolio */
+ @JsonProperty("transactions")
private Transaction[] transactions;
- /** Pagination information for the transaction listing */
+
+ @JsonProperty("pagination")
private Pagination pagination;
public ListPortfolioTransactionsResponse() {
diff --git a/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsRequest.java b/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsRequest.java
index 48c7a0dc..6cc35888 100644
--- a/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsRequest.java
@@ -19,23 +19,31 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.common.PrimeListRequest;
import com.coinbase.prime.common.Pagination;
-import com.coinbase.prime.model.enums.TransactionType;
import com.coinbase.prime.model.enums.SortDirection;
+import com.coinbase.prime.model.enums.TransactionType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Wallet Transactions
+ */
public class ListWalletTransactionsRequest extends PrimeListRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
+ @JsonProperty("types")
private TransactionType[] types;
+
@JsonProperty("start_time")
private String startTime;
+
@JsonProperty("end_time")
private String endTime;
@@ -129,19 +137,19 @@ public Builder endTime(String endTime) {
return this;
}
- public Builder pagination(Pagination pagination) {
- this.cursor = pagination.getNextCursor();
- this.sortDirection = pagination.getSortDirection();
+ public Builder limit(Integer limit) {
+ this.limit = limit;
return this;
}
- public Builder limit(Integer limit) {
- this.limit = limit;
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
return this;
}
public ListWalletTransactionsRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListWalletTransactionsRequest(this);
}
@@ -149,7 +157,6 @@ private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
throw new CoinbaseClientException("PortfolioId is required");
}
-
if (isNullOrEmpty(this.walletId)) {
throw new CoinbaseClientException("WalletId is required");
}
diff --git a/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsResponse.java b/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsResponse.java
index 30b676a5..f1c78f49 100644
--- a/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/ListWalletTransactionsResponse.java
@@ -18,11 +18,17 @@
import com.coinbase.prime.common.Pagination;
import com.coinbase.prime.model.Transaction;
+import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * List Wallet Transactions
+ */
public class ListWalletTransactionsResponse {
+ @JsonProperty("transactions")
private Transaction[] transactions;
+
+ @JsonProperty("pagination")
private Pagination pagination;
- private ListWalletTransactionsRequest request;
public ListWalletTransactionsResponse() {
}
@@ -43,12 +49,4 @@ public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
- public ListWalletTransactionsRequest getRequest() {
- return request;
- }
-
- public void setRequest(ListWalletTransactionsRequest request) {
- this.request = request;
- }
-
}
diff --git a/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataRequest.java b/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataRequest.java
index 3bb0c479..c3956b43 100644
--- a/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataRequest.java
+++ b/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataRequest.java
@@ -24,9 +24,7 @@
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
/**
- * Request to submit travel rule data for an existing deposit transaction.
- *
- * Beta: This endpoint is in Beta. Contact your account manager for more information.
+ * Submit Deposit Travel Rule Data
*/
public class SubmitDepositTravelRuleDataRequest {
@JsonProperty(required = true, value = "portfolio_id")
@@ -46,9 +44,6 @@ public class SubmitDepositTravelRuleDataRequest {
@JsonProperty("is_self")
private Boolean isSelf;
- @JsonProperty("is_intermediary")
- private Boolean isIntermediary;
-
@JsonProperty("opt_out_of_ownership_verification")
private Boolean optOutOfOwnershipVerification;
@@ -61,7 +56,6 @@ public SubmitDepositTravelRuleDataRequest(Builder builder) {
this.originator = builder.originator;
this.beneficiary = builder.beneficiary;
this.isSelf = builder.isSelf;
- this.isIntermediary = builder.isIntermediary;
this.optOutOfOwnershipVerification = builder.optOutOfOwnershipVerification;
}
@@ -105,14 +99,6 @@ public void setIsSelf(Boolean isSelf) {
this.isSelf = isSelf;
}
- public Boolean getIsIntermediary() {
- return isIntermediary;
- }
-
- public void setIsIntermediary(Boolean isIntermediary) {
- this.isIntermediary = isIntermediary;
- }
-
public Boolean getOptOutOfOwnershipVerification() {
return optOutOfOwnershipVerification;
}
@@ -127,7 +113,6 @@ public static class Builder {
private TravelRuleParty originator;
private TravelRuleParty beneficiary;
private Boolean isSelf;
- private Boolean isIntermediary;
private Boolean optOutOfOwnershipVerification;
public Builder() {
@@ -158,27 +143,22 @@ public Builder isSelf(Boolean isSelf) {
return this;
}
- public Builder isIntermediary(Boolean isIntermediary) {
- this.isIntermediary = isIntermediary;
- return this;
- }
-
public Builder optOutOfOwnershipVerification(Boolean optOutOfOwnershipVerification) {
this.optOutOfOwnershipVerification = optOutOfOwnershipVerification;
return this;
}
public SubmitDepositTravelRuleDataRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new SubmitDepositTravelRuleDataRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("PortfolioId cannot be null");
+ throw new CoinbaseClientException("PortfolioId is required");
}
if (isNullOrEmpty(this.transactionId)) {
- throw new CoinbaseClientException("TransactionId cannot be null");
+ throw new CoinbaseClientException("TransactionId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataResponse.java b/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataResponse.java
index 58f96886..6b6dd84d 100644
--- a/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataResponse.java
+++ b/src/main/java/com/coinbase/prime/transactions/SubmitDepositTravelRuleDataResponse.java
@@ -19,9 +19,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response after submitting travel rule data for a deposit.
- *
- * Beta: This endpoint is in Beta. Contact your account manager for more information.
+ * Submit Deposit Travel Rule Data
*/
public class SubmitDepositTravelRuleDataResponse {
@JsonProperty("ownership_verification_required")
@@ -37,4 +35,5 @@ public Boolean getOwnershipVerificationRequired() {
public void setOwnershipVerificationRequired(Boolean ownershipVerificationRequired) {
this.ownershipVerificationRequired = ownershipVerificationRequired;
}
+
}
diff --git a/src/main/java/com/coinbase/prime/transactions/TransactionsService.java b/src/main/java/com/coinbase/prime/transactions/TransactionsService.java
index 65514b79..caab8bda 100644
--- a/src/main/java/com/coinbase/prime/transactions/TransactionsService.java
+++ b/src/main/java/com/coinbase/prime/transactions/TransactionsService.java
@@ -20,7 +20,6 @@
import com.coinbase.prime.errors.CoinbasePrimeException;
public interface TransactionsService {
- // Transactions - OpenAPI spec compliance
ListPortfolioTransactionsResponse listPortfolioTransactions(ListPortfolioTransactionsRequest request) throws CoinbaseClientException, CoinbasePrimeException;
GetTransactionResponse getTransaction(GetTransactionRequest request) throws CoinbaseClientException, CoinbasePrimeException;
CreateConversionResponse createConversion(CreateConversionRequest request) throws CoinbaseClientException, CoinbasePrimeException;
@@ -28,18 +27,6 @@ public interface TransactionsService {
ListWalletTransactionsResponse listWalletTransactions(ListWalletTransactionsRequest request) throws CoinbaseClientException, CoinbasePrimeException;
CreateWalletTransferResponse createWalletTransfer(CreateWalletTransferRequest request) throws CoinbaseClientException, CoinbasePrimeException;
CreateWalletWithdrawalResponse createWalletWithdrawal(CreateWalletWithdrawalRequest request) throws CoinbaseClientException, CoinbasePrimeException;
- ListAdvancedTransferTransactionsResponse listAdvancedTransferTransactions(ListAdvancedTransferTransactionsRequest request) throws CoinbaseClientException, CoinbasePrimeException;
-
- /**
- * Submit travel rule data for an existing deposit transaction.
- *
- * Beta: This endpoint is in Beta. Contact your account manager for more information.
- *
- * @param request The request containing portfolio ID, transaction ID, and travel rule data
- * @return Response indicating whether ownership verification is required
- * @throws CoinbaseClientException if there is a client-side error
- * @throws CoinbasePrimeException if there is a server-side error
- */
- SubmitDepositTravelRuleDataResponse submitDepositTravelRuleData(SubmitDepositTravelRuleDataRequest request) throws CoinbaseClientException, CoinbasePrimeException;
GetTransactionTravelRuleDataResponse getTransactionTravelRuleData(GetTransactionTravelRuleDataRequest request) throws CoinbaseClientException, CoinbasePrimeException;
+ SubmitDepositTravelRuleDataResponse submitDepositTravelRuleData(SubmitDepositTravelRuleDataRequest request) throws CoinbaseClientException, CoinbasePrimeException;
}
diff --git a/src/main/java/com/coinbase/prime/transactions/TransactionsServiceImpl.java b/src/main/java/com/coinbase/prime/transactions/TransactionsServiceImpl.java
index cc089c0d..e426d4a6 100644
--- a/src/main/java/com/coinbase/prime/transactions/TransactionsServiceImpl.java
+++ b/src/main/java/com/coinbase/prime/transactions/TransactionsServiceImpl.java
@@ -29,6 +29,16 @@ public TransactionsServiceImpl(CoinbasePrimeClient client) {
super(client);
}
+ @Override
+ public ListPortfolioTransactionsResponse listPortfolioTransactions(ListPortfolioTransactionsRequest request) throws CoinbasePrimeException {
+ return this.request(
+ HttpMethod.GET,
+ String.format("/portfolios/%s/transactions", request.getPortfolioId()),
+ request,
+ List.of(200),
+ new TypeReference() {});
+ }
+
@Override
public GetTransactionResponse getTransaction(GetTransactionRequest request) throws CoinbasePrimeException {
return this.request(
@@ -45,36 +55,32 @@ public CreateConversionResponse createConversion(CreateConversionRequest request
HttpMethod.POST,
String.format("/portfolios/%s/wallets/%s/conversion", request.getPortfolioId(), request.getWalletId()),
request,
- List.of(200),
+ List.of(201, 200),
new TypeReference() {});
}
@Override
- public ListPortfolioTransactionsResponse listPortfolioTransactions(ListPortfolioTransactionsRequest request)
- throws CoinbasePrimeException {
+ public CreateOnchainTransactionResponse createOnchainTransaction(CreateOnchainTransactionRequest request) throws CoinbasePrimeException {
return this.request(
- HttpMethod.GET,
- String.format("/portfolios/%s/transactions", request.getPortfolioId()),
+ HttpMethod.POST,
+ String.format("/portfolios/%s/wallets/%s/onchain_transaction", request.getPortfolioId(), request.getWalletId()),
request,
- List.of(200),
- new TypeReference() {});
+ List.of(201, 200),
+ new TypeReference() {});
}
@Override
- public ListWalletTransactionsResponse listWalletTransactions(ListWalletTransactionsRequest request)
- throws CoinbasePrimeException {
+ public ListWalletTransactionsResponse listWalletTransactions(ListWalletTransactionsRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
- String.format("/portfolios/%s/wallets/%s/transactions", request.getPortfolioId(),
- request.getWalletId()),
+ String.format("/portfolios/%s/wallets/%s/transactions", request.getPortfolioId(), request.getWalletId()),
request,
List.of(200),
new TypeReference() {});
}
@Override
- public CreateWalletTransferResponse createWalletTransfer(CreateWalletTransferRequest request)
- throws CoinbasePrimeException {
+ public CreateWalletTransferResponse createWalletTransfer(CreateWalletTransferRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.POST,
String.format("/portfolios/%s/wallets/%s/transfers", request.getPortfolioId(), request.getWalletId()),
@@ -84,8 +90,7 @@ public CreateWalletTransferResponse createWalletTransfer(CreateWalletTransferReq
}
@Override
- public CreateWalletWithdrawalResponse createWalletWithdrawal(CreateWalletWithdrawalRequest request)
- throws CoinbasePrimeException {
+ public CreateWalletWithdrawalResponse createWalletWithdrawal(CreateWalletWithdrawalRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.POST,
String.format("/portfolios/%s/wallets/%s/withdrawals", request.getPortfolioId(), request.getWalletId()),
@@ -95,55 +100,23 @@ public CreateWalletWithdrawalResponse createWalletWithdrawal(CreateWalletWithdra
}
@Override
- public CreateOnchainTransactionResponse createOnchainTransaction(CreateOnchainTransactionRequest request)
- throws CoinbasePrimeException {
- return this.request(
- HttpMethod.POST,
- String.format("/portfolios/%s/wallets/%s/onchain_transaction", request.getPortfolioId(),
- request.getWalletId()),
- request,
- List.of(200),
- new TypeReference() {});
- }
-
- @Override
- public ListAdvancedTransferTransactionsResponse listAdvancedTransferTransactions(ListAdvancedTransferTransactionsRequest request)
- throws CoinbasePrimeException {
+ public GetTransactionTravelRuleDataResponse getTransactionTravelRuleData(GetTransactionTravelRuleDataRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
- String.format("/portfolios/%s/advanced_transfers/%s/transactions", request.getPortfolioId(),
- request.getAdvancedTransferId()),
+ String.format("/portfolios/%s/transactions/%s/travel_rule", request.getPortfolioId(), request.getTransactionId()),
request,
List.of(200),
- new TypeReference() {});
+ new TypeReference() {});
}
- /**
- * Submit travel rule data for an existing deposit transaction.
- *
- * Beta: This endpoint is in Beta. Contact your account manager for more information.
- */
@Override
- public SubmitDepositTravelRuleDataResponse submitDepositTravelRuleData(SubmitDepositTravelRuleDataRequest request)
- throws CoinbasePrimeException {
+ public SubmitDepositTravelRuleDataResponse submitDepositTravelRuleData(SubmitDepositTravelRuleDataRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.POST,
- String.format("/portfolios/%s/transactions/%s/travel_rule/deposit", request.getPortfolioId(),
- request.getTransactionId()),
+ String.format("/portfolios/%s/transactions/%s/travel_rule/deposit", request.getPortfolioId(), request.getTransactionId()),
request,
- List.of(200),
+ List.of(201, 200),
new TypeReference() {});
}
- @Override
- public GetTransactionTravelRuleDataResponse getTransactionTravelRuleData(GetTransactionTravelRuleDataRequest request)
- throws CoinbasePrimeException {
- return this.request(
- HttpMethod.GET,
- String.format("/portfolios/%s/transactions/%s/travel_rule", request.getPortfolioId(),
- request.getTransactionId()),
- request,
- List.of(200),
- new TypeReference() {});
- }
}
diff --git a/src/main/java/com/coinbase/prime/users/ListEntityUsersRequest.java b/src/main/java/com/coinbase/prime/users/ListEntityUsersRequest.java
index 581aff22..7b9834c0 100644
--- a/src/main/java/com/coinbase/prime/users/ListEntityUsersRequest.java
+++ b/src/main/java/com/coinbase/prime/users/ListEntityUsersRequest.java
@@ -25,6 +25,9 @@
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Users
+ */
public class ListEntityUsersRequest extends PrimeListRequest {
@JsonProperty(required = true, value = "entity_id")
@JsonIgnore
@@ -47,18 +50,16 @@ public void setEntityId(String entityId) {
}
public static class Builder {
- private final String entityId;
+ private String entityId;
private String cursor;
private SortDirection sortDirection;
private Integer limit;
- public Builder(String entityId) {
- this.entityId = entityId;
+ public Builder() {
}
- public Builder pagination(Pagination pagination) {
- this.cursor = pagination.getNextCursor();
- this.sortDirection = pagination.getSortDirection();
+ public Builder entityId(String entityId) {
+ this.entityId = entityId;
return this;
}
@@ -67,8 +68,14 @@ public Builder limit(Integer limit) {
return this;
}
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
+ return this;
+ }
+
public ListEntityUsersRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListEntityUsersRequest(this);
}
@@ -78,4 +85,4 @@ private void validate() throws CoinbaseClientException {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/coinbase/prime/users/ListEntityUsersResponse.java b/src/main/java/com/coinbase/prime/users/ListEntityUsersResponse.java
index bc9b5ea1..d949b7a5 100644
--- a/src/main/java/com/coinbase/prime/users/ListEntityUsersResponse.java
+++ b/src/main/java/com/coinbase/prime/users/ListEntityUsersResponse.java
@@ -18,14 +18,16 @@
import com.coinbase.prime.model.EntityUser;
import com.coinbase.prime.common.Pagination;
+import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for listing all users associated with a specific entity.
+ * List Users
*/
public class ListEntityUsersResponse {
- /** Array of entity user information */
+ @JsonProperty("users")
private EntityUser[] users;
- /** Pagination information for the response */
+
+ @JsonProperty("pagination")
private Pagination pagination;
public ListEntityUsersResponse() {
diff --git a/src/main/java/com/coinbase/prime/users/ListPortfolioUsersRequest.java b/src/main/java/com/coinbase/prime/users/ListPortfolioUsersRequest.java
index 4b22ec3d..f1e6480e 100644
--- a/src/main/java/com/coinbase/prime/users/ListPortfolioUsersRequest.java
+++ b/src/main/java/com/coinbase/prime/users/ListPortfolioUsersRequest.java
@@ -25,6 +25,9 @@
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Portfolio Users
+ */
public class ListPortfolioUsersRequest extends PrimeListRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
@@ -52,30 +55,33 @@ public static class Builder {
private SortDirection sortDirection;
private Integer limit;
+ public Builder() {
+ }
+
public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
return this;
}
- public Builder pagination(Pagination pagination) {
- this.cursor = pagination.getNextCursor();
- this.sortDirection = pagination.getSortDirection();
+ public Builder limit(Integer limit) {
+ this.limit = limit;
return this;
}
- public Builder limit(Integer limit) {
- this.limit = limit;
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
return this;
}
public ListPortfolioUsersRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListPortfolioUsersRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("PortfolioId cannot be null");
+ throw new CoinbaseClientException("PortfolioId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/users/ListPortfolioUsersResponse.java b/src/main/java/com/coinbase/prime/users/ListPortfolioUsersResponse.java
index b594111a..f5ff17b3 100644
--- a/src/main/java/com/coinbase/prime/users/ListPortfolioUsersResponse.java
+++ b/src/main/java/com/coinbase/prime/users/ListPortfolioUsersResponse.java
@@ -16,21 +16,28 @@
package com.coinbase.prime.users;
-import com.coinbase.prime.model.EntityUser;
import com.coinbase.prime.common.Pagination;
+import com.coinbase.prime.model.PortfolioUser;
+import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * List Portfolio Users
+ */
public class ListPortfolioUsersResponse {
- private EntityUser[] users;
+ @JsonProperty("users")
+ private PortfolioUser[] users;
+
+ @JsonProperty("pagination")
private Pagination pagination;
public ListPortfolioUsersResponse() {
}
- public EntityUser[] getUsers() {
+ public PortfolioUser[] getUsers() {
return users;
}
- public void setUsers(EntityUser[] users) {
+ public void setUsers(PortfolioUser[] users) {
this.users = users;
}
diff --git a/src/main/java/com/coinbase/prime/users/ListUsersRequest.java b/src/main/java/com/coinbase/prime/users/ListUsersRequest.java
new file mode 100644
index 00000000..98bf1780
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/users/ListUsersRequest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.prime.users;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.common.PrimeListRequest;
+import com.coinbase.prime.common.Pagination;
+import com.coinbase.prime.model.enums.SortDirection;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+
+/**
+ * List Users
+ */
+public class ListUsersRequest extends PrimeListRequest {
+ @JsonProperty(required = true, value = "entity_id")
+ @JsonIgnore
+ private String entityId;
+
+ public ListUsersRequest() {
+ }
+
+ public ListUsersRequest(Builder builder) {
+ super(builder.cursor, builder.sortDirection, builder.limit);
+ this.entityId = builder.entityId;
+ }
+
+ public String getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(String entityId) {
+ this.entityId = entityId;
+ }
+
+ public static class Builder {
+ private String entityId;
+ private String cursor;
+ private SortDirection sortDirection;
+ private Integer limit;
+
+ public Builder() {
+ }
+
+ public Builder entityId(String entityId) {
+ this.entityId = entityId;
+ return this;
+ }
+
+ public Builder limit(Integer limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
+ return this;
+ }
+
+ public ListUsersRequest build() throws CoinbaseClientException {
+ validate();
+ return new ListUsersRequest(this);
+ }
+
+ private void validate() throws CoinbaseClientException {
+ if (isNullOrEmpty(this.entityId)) {
+ throw new CoinbaseClientException("EntityId is required");
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/coinbase/prime/users/ListUsersResponse.java b/src/main/java/com/coinbase/prime/users/ListUsersResponse.java
new file mode 100644
index 00000000..56f51e27
--- /dev/null
+++ b/src/main/java/com/coinbase/prime/users/ListUsersResponse.java
@@ -0,0 +1,52 @@
+/*
+ * 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.prime.users;
+
+import com.coinbase.prime.model.EntityUser;
+import com.coinbase.prime.common.Pagination;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * List Users
+ */
+public class ListUsersResponse {
+ @JsonProperty("users")
+ private EntityUser[] users;
+
+ @JsonProperty("pagination")
+ private Pagination pagination;
+
+ public ListUsersResponse() {
+ }
+
+ public EntityUser[] getUsers() {
+ return users;
+ }
+
+ public void setUsers(EntityUser[] users) {
+ this.users = users;
+ }
+
+ public Pagination getPagination() {
+ return pagination;
+ }
+
+ public void setPagination(Pagination pagination) {
+ this.pagination = pagination;
+ }
+
+}
diff --git a/src/main/java/com/coinbase/prime/users/UsersService.java b/src/main/java/com/coinbase/prime/users/UsersService.java
index 91d35676..25711387 100644
--- a/src/main/java/com/coinbase/prime/users/UsersService.java
+++ b/src/main/java/com/coinbase/prime/users/UsersService.java
@@ -20,7 +20,6 @@
import com.coinbase.prime.errors.CoinbasePrimeException;
public interface UsersService {
- // Users - OpenAPI spec compliance
ListEntityUsersResponse listEntityUsers(ListEntityUsersRequest request) throws CoinbaseClientException, CoinbasePrimeException;
ListPortfolioUsersResponse listPortfolioUsers(ListPortfolioUsersRequest request) throws CoinbaseClientException, CoinbasePrimeException;
}
diff --git a/src/main/java/com/coinbase/prime/users/UsersServiceImpl.java b/src/main/java/com/coinbase/prime/users/UsersServiceImpl.java
index 2f81cb78..1f6076d7 100644
--- a/src/main/java/com/coinbase/prime/users/UsersServiceImpl.java
+++ b/src/main/java/com/coinbase/prime/users/UsersServiceImpl.java
@@ -48,4 +48,5 @@ public ListPortfolioUsersResponse listPortfolioUsers(ListPortfolioUsersRequest r
List.of(200),
new TypeReference() {});
}
+
}
diff --git a/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressRequest.java b/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressRequest.java
index c2ae66e2..0eba9ba3 100644
--- a/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressRequest.java
@@ -22,13 +22,18 @@
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Wallet Deposit Address
+ */
public class CreateWalletDepositAddressRequest {
- @JsonProperty("portfolio_id")
+ @JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
- @JsonProperty("wallet_id")
+
+ @JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
@JsonProperty("network_id")
private String networkId;
@@ -89,20 +94,17 @@ public Builder networkId(String networkId) {
}
public CreateWalletDepositAddressRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new CreateWalletDepositAddressRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("Portfolio ID is required");
+ throw new CoinbaseClientException("PortfolioId is required");
}
if (isNullOrEmpty(this.walletId)) {
- throw new CoinbaseClientException("Wallet ID is required");
- }
- if (isNullOrEmpty(this.networkId)) {
- throw new CoinbaseClientException("Network ID is required");
+ throw new CoinbaseClientException("WalletId is required");
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressResponse.java b/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressResponse.java
index 8f5be147..bc75dcfa 100644
--- a/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/CreateWalletDepositAddressResponse.java
@@ -19,6 +19,9 @@
import com.coinbase.prime.model.Network;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * Create Wallet Deposit Address
+ */
public class CreateWalletDepositAddressResponse {
@JsonProperty("address")
private String address;
@@ -55,4 +58,5 @@ public Network getNetwork() {
public void setNetwork(Network network) {
this.network = network;
}
-}
\ No newline at end of file
+
+}
diff --git a/src/main/java/com/coinbase/prime/wallets/CreateWalletRequest.java b/src/main/java/com/coinbase/prime/wallets/CreateWalletRequest.java
index 561e2426..b3ee0369 100644
--- a/src/main/java/com/coinbase/prime/wallets/CreateWalletRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/CreateWalletRequest.java
@@ -17,23 +17,39 @@
package com.coinbase.prime.wallets;
import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.model.Network;
+import com.coinbase.prime.model.enums.NetworkFamily;
import com.coinbase.prime.model.enums.WalletType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Create Wallet
+ */
public class CreateWalletRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+ @JsonProperty("name")
private String name;
+ @JsonProperty("symbol")
private String symbol;
@JsonProperty("wallet_type")
- private WalletType type;
+ private WalletType walletType;
+
+ @JsonProperty("idempotency_key")
+ private String idempotencyKey;
+
+ @JsonProperty("network_family")
+ private NetworkFamily networkFamily;
+
+ @JsonProperty("network")
+ private Network network;
public CreateWalletRequest() {
}
@@ -42,7 +58,10 @@ public CreateWalletRequest(Builder builder) {
this.portfolioId = builder.portfolioId;
this.name = builder.name;
this.symbol = builder.symbol;
- this.type = builder.type;
+ this.walletType = builder.walletType;
+ this.idempotencyKey = builder.idempotencyKey;
+ this.networkFamily = builder.networkFamily;
+ this.network = builder.network;
}
public String getPortfolioId() {
@@ -69,19 +88,46 @@ public void setSymbol(String symbol) {
this.symbol = symbol;
}
- public WalletType getType() {
- return type;
+ public WalletType getWalletType() {
+ return walletType;
+ }
+
+ public void setWalletType(WalletType walletType) {
+ this.walletType = walletType;
+ }
+
+ public String getIdempotencyKey() {
+ return idempotencyKey;
+ }
+
+ public void setIdempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ }
+
+ public NetworkFamily getNetworkFamily() {
+ return networkFamily;
+ }
+
+ public void setNetworkFamily(NetworkFamily networkFamily) {
+ this.networkFamily = networkFamily;
}
- public void setType(WalletType type) {
- this.type = type;
+ public Network getNetwork() {
+ return network;
+ }
+
+ public void setNetwork(Network network) {
+ this.network = network;
}
public static class Builder {
private String portfolioId;
private String name;
private String symbol;
- private WalletType type;
+ private WalletType walletType;
+ private String idempotencyKey;
+ private NetworkFamily networkFamily;
+ private Network network;
public Builder() {
}
@@ -101,28 +147,34 @@ public Builder symbol(String symbol) {
return this;
}
- public Builder type(WalletType type) {
- this.type = type;
+ public Builder walletType(WalletType walletType) {
+ this.walletType = walletType;
+ return this;
+ }
+
+ public Builder idempotencyKey(String idempotencyKey) {
+ this.idempotencyKey = idempotencyKey;
+ return this;
+ }
+
+ public Builder networkFamily(NetworkFamily networkFamily) {
+ this.networkFamily = networkFamily;
+ return this;
+ }
+
+ public Builder network(Network network) {
+ this.network = network;
return this;
}
public CreateWalletRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new CreateWalletRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("Portfolio ID is required");
- }
- if (isNullOrEmpty(this.name)) {
- throw new CoinbaseClientException("Name is required");
- }
- if (isNullOrEmpty(this.symbol)) {
- throw new CoinbaseClientException("Symbol is required");
- }
- if (this.type == null) {
- throw new CoinbaseClientException("Type is required");
+ throw new CoinbaseClientException("PortfolioId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/wallets/CreateWalletResponse.java b/src/main/java/com/coinbase/prime/wallets/CreateWalletResponse.java
index f9af59ca..40447c8a 100644
--- a/src/main/java/com/coinbase/prime/wallets/CreateWalletResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/CreateWalletResponse.java
@@ -21,27 +21,21 @@
import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for the create wallet operation.
- *
- * Creates a new wallet for the specified portfolio. Note that the first ONCHAIN wallet
- * for each network family must be created through the Prime UI.
+ * Create Wallet
*/
public class CreateWalletResponse {
- /** The ID of the activity associated with the wallet creation */
@JsonProperty("activity_id")
private String activityId;
- /** The name of the wallet */
+ @JsonProperty("name")
private String name;
- /** The asset symbol stored in the wallet */
+ @JsonProperty("symbol")
private String symbol;
- /** The wallet type (VAULT, TRADING, or WALLET_TYPE_OTHER) */
@JsonProperty("wallet_type")
- private WalletType type;
+ private WalletType walletType;
- /** The network family for the wallet */
@JsonProperty("network_family")
private NetworkFamily networkFamily;
@@ -72,12 +66,12 @@ public void setSymbol(String symbol) {
this.symbol = symbol;
}
- public WalletType getType() {
- return type;
+ public WalletType getWalletType() {
+ return walletType;
}
- public void setType(WalletType type) {
- this.type = type;
+ public void setWalletType(WalletType walletType) {
+ this.walletType = walletType;
}
public NetworkFamily getNetworkFamily() {
@@ -87,4 +81,5 @@ public NetworkFamily getNetworkFamily() {
public void setNetworkFamily(NetworkFamily networkFamily) {
this.networkFamily = networkFamily;
}
+
}
diff --git a/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsRequest.java b/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsRequest.java
index ade61318..70979fe6 100644
--- a/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsRequest.java
@@ -18,12 +18,14 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.model.enums.WalletDepositInstructionType;
-import com.coinbase.prime.model.enums.NetworkType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Get Wallet Deposit Instructions
+ */
public class GetWalletDepositInstructionsRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
@@ -33,14 +35,14 @@ public class GetWalletDepositInstructionsRequest {
@JsonIgnore
private String walletId;
- @JsonProperty(required = true, value = "deposit_type")
+ @JsonProperty("deposit_type")
private WalletDepositInstructionType depositType;
@JsonProperty("network.id")
private String networkId;
@JsonProperty("network.type")
- private NetworkType networkType;
+ private String networkType;
public GetWalletDepositInstructionsRequest() {
}
@@ -85,11 +87,11 @@ public void setNetworkId(String networkId) {
this.networkId = networkId;
}
- public NetworkType getNetworkType() {
+ public String getNetworkType() {
return networkType;
}
- public void setNetworkType(NetworkType networkType) {
+ public void setNetworkType(String networkType) {
this.networkType = networkType;
}
@@ -98,52 +100,47 @@ public static class Builder {
private String walletId;
private WalletDepositInstructionType depositType;
private String networkId;
- private NetworkType networkType;
+ private String networkType;
public Builder() {
}
- public GetWalletDepositInstructionsRequest.Builder portfolioId(String portfolioId) {
+ public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
return this;
}
- public GetWalletDepositInstructionsRequest.Builder walletId(String walletId) {
+ public Builder walletId(String walletId) {
this.walletId = walletId;
return this;
}
- public GetWalletDepositInstructionsRequest.Builder depositType(WalletDepositInstructionType depositType) {
+ public Builder depositType(WalletDepositInstructionType depositType) {
this.depositType = depositType;
return this;
}
- public GetWalletDepositInstructionsRequest.Builder networkId(String networkId) {
+ public Builder networkId(String networkId) {
this.networkId = networkId;
return this;
}
- public GetWalletDepositInstructionsRequest.Builder networkType(NetworkType networkType) {
+ public Builder networkType(String networkType) {
this.networkType = networkType;
return this;
}
public GetWalletDepositInstructionsRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new GetWalletDepositInstructionsRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("PortfolioId cannot be null");
+ throw new CoinbaseClientException("PortfolioId is required");
}
-
if (isNullOrEmpty(this.walletId)) {
- throw new CoinbaseClientException("WalletId cannot be null");
- }
-
- if (this.depositType == null) {
- throw new CoinbaseClientException("DepositType cannot be null");
+ throw new CoinbaseClientException("WalletId is required");
}
}
}
diff --git a/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsResponse.java b/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsResponse.java
index 85da4e88..10594c4c 100644
--- a/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/GetWalletDepositInstructionsResponse.java
@@ -21,36 +21,32 @@
import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for retrieving wallet deposit instructions.
- *
- * Contains both crypto and fiat deposit instructions for the specified wallet,
- * providing the necessary information for users to deposit funds.
+ * Get Wallet Deposit Instructions
*/
public class GetWalletDepositInstructionsResponse {
- /** Cryptocurrency deposit instructions for the wallet */
@JsonProperty("crypto_instructions")
- WalletCryptoDepositInstructions cryptoDepositInstructions;
- /** Fiat currency deposit instructions for the wallet */
+ private WalletCryptoDepositInstructions cryptoInstructions;
+
@JsonProperty("fiat_instructions")
- WalletFiatDepositInstructions fiatDepositInstructions;
+ private WalletFiatDepositInstructions fiatInstructions;
public GetWalletDepositInstructionsResponse() {
}
- public WalletCryptoDepositInstructions getCryptoDepositInstructions() {
- return cryptoDepositInstructions;
+ public WalletCryptoDepositInstructions getCryptoInstructions() {
+ return cryptoInstructions;
}
- public void setCryptoDepositInstructions(WalletCryptoDepositInstructions cryptoDepositInstructions) {
- this.cryptoDepositInstructions = cryptoDepositInstructions;
+ public void setCryptoInstructions(WalletCryptoDepositInstructions cryptoInstructions) {
+ this.cryptoInstructions = cryptoInstructions;
}
- public WalletFiatDepositInstructions getFiatDepositInstructions() {
- return fiatDepositInstructions;
+ public WalletFiatDepositInstructions getFiatInstructions() {
+ return fiatInstructions;
}
- public void setFiatDepositInstructions(WalletFiatDepositInstructions fiatDepositInstructions) {
- this.fiatDepositInstructions = fiatDepositInstructions;
+ public void setFiatInstructions(WalletFiatDepositInstructions fiatInstructions) {
+ this.fiatInstructions = fiatInstructions;
}
}
diff --git a/src/main/java/com/coinbase/prime/wallets/GetWalletRequest.java b/src/main/java/com/coinbase/prime/wallets/GetWalletRequest.java
index 75aacce4..e36aeeea 100644
--- a/src/main/java/com/coinbase/prime/wallets/GetWalletRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/GetWalletRequest.java
@@ -20,12 +20,16 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * Get Wallet by Wallet ID
+ */
public class GetWalletRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+
@JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
@@ -55,16 +59,24 @@ public void setWalletId(String walletId) {
}
public static class Builder {
- private final String portfolioId;
- private final String walletId;
+ private String portfolioId;
+ private String walletId;
+
+ public Builder() {
+ }
- public Builder(String portfolioId, String walletId) {
+ public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
+ return this;
+ }
+
+ public Builder walletId(String walletId) {
this.walletId = walletId;
+ return this;
}
public GetWalletRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new GetWalletRequest(this);
}
diff --git a/src/main/java/com/coinbase/prime/wallets/GetWalletResponse.java b/src/main/java/com/coinbase/prime/wallets/GetWalletResponse.java
index b75bf849..dca74c31 100644
--- a/src/main/java/com/coinbase/prime/wallets/GetWalletResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/GetWalletResponse.java
@@ -17,14 +17,13 @@
package com.coinbase.prime.wallets;
import com.coinbase.prime.model.Wallet;
+import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for retrieving a specific wallet by Wallet ID.
- *
- * Contains the wallet information for the requested wallet.
+ * Get Wallet by Wallet ID
*/
public class GetWalletResponse {
- /** The wallet information */
+ @JsonProperty("wallet")
private Wallet wallet;
public GetWalletResponse() {
diff --git a/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesRequest.java b/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesRequest.java
index 317693f0..80cc6100 100644
--- a/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesRequest.java
@@ -18,19 +18,25 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.common.PrimeListRequest;
+import com.coinbase.prime.common.Pagination;
import com.coinbase.prime.model.enums.SortDirection;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Wallet Addresses
+ */
public class ListWalletAddressesRequest extends PrimeListRequest {
- @JsonProperty("portfolio_id")
+ @JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
- @JsonProperty("wallet_id")
+
+ @JsonProperty(required = true, value = "wallet_id")
@JsonIgnore
private String walletId;
+
@JsonProperty("network_id")
private String networkId;
@@ -68,14 +74,13 @@ public void setNetworkId(String networkId) {
this.networkId = networkId;
}
-
public static class Builder {
private String portfolioId;
private String walletId;
private String networkId;
private String cursor;
- private Integer limit;
private SortDirection sortDirection;
+ private Integer limit;
public Builder() {
}
@@ -95,32 +100,29 @@ public Builder networkId(String networkId) {
return this;
}
-
- public Builder cursor(String cursor) {
- this.cursor = cursor;
+ public Builder limit(Integer limit) {
+ this.limit = limit;
return this;
}
- public Builder sortDirection(SortDirection sortDirection) {
- this.sortDirection = sortDirection;
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
return this;
}
public ListWalletAddressesRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListWalletAddressesRequest(this);
}
private void validate() throws CoinbaseClientException {
if (isNullOrEmpty(this.portfolioId)) {
- throw new CoinbaseClientException("Portfolio ID is required");
+ throw new CoinbaseClientException("PortfolioId is required");
}
if (isNullOrEmpty(this.walletId)) {
- throw new CoinbaseClientException("Wallet ID is required");
- }
- if (isNullOrEmpty(this.networkId)) {
- throw new CoinbaseClientException("Network ID is required");
+ throw new CoinbaseClientException("WalletId is required");
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesResponse.java b/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesResponse.java
index 54688d67..3622b5d2 100644
--- a/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/ListWalletAddressesResponse.java
@@ -19,28 +19,25 @@
import com.coinbase.prime.model.BlockchainAddress;
import com.coinbase.prime.common.Pagination;
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
/**
- * Response object for listing all deposit addresses associated with a wallet.
- *
- * Returns all deposit addresses for the specified wallet along with pagination information.
+ * List Wallet Addresses
*/
public class ListWalletAddressesResponse {
- /** List of deposit addresses associated with the wallet */
@JsonProperty("addresses")
- private List addresses;
- /** Pagination information for the address listing */
+ private BlockchainAddress[] addresses;
+
+ @JsonProperty("pagination")
private Pagination pagination;
public ListWalletAddressesResponse() {
}
- public List getAddresses() {
+ public BlockchainAddress[] getAddresses() {
return addresses;
}
- public void setAddresses(List addresses) {
+ public void setAddresses(BlockchainAddress[] addresses) {
this.addresses = addresses;
}
@@ -51,4 +48,5 @@ public Pagination getPagination() {
public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
-}
\ No newline at end of file
+
+}
diff --git a/src/main/java/com/coinbase/prime/wallets/ListWalletsRequest.java b/src/main/java/com/coinbase/prime/wallets/ListWalletsRequest.java
index 85635288..65ea2e5c 100644
--- a/src/main/java/com/coinbase/prime/wallets/ListWalletsRequest.java
+++ b/src/main/java/com/coinbase/prime/wallets/ListWalletsRequest.java
@@ -19,29 +19,39 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.coinbase.prime.common.PrimeListRequest;
import com.coinbase.prime.common.Pagination;
-import com.coinbase.prime.model.enums.WalletType;
import com.coinbase.prime.model.enums.SortDirection;
+import com.coinbase.prime.model.enums.WalletType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
-import static com.coinbase.core.utils.Utils.*;
+import static com.coinbase.core.utils.Utils.isNullOrEmpty;
+/**
+ * List Portfolio Wallets
+ */
public class ListWalletsRequest extends PrimeListRequest {
@JsonProperty(required = true, value = "portfolio_id")
@JsonIgnore
private String portfolioId;
+ @JsonProperty("type")
private WalletType type;
+ @JsonProperty("symbols")
private String[] symbols;
- public ListWalletsRequest() {}
+ @JsonProperty("get_network_unified_wallets")
+ private Boolean getNetworkUnifiedWallets;
+
+ public ListWalletsRequest() {
+ }
public ListWalletsRequest(Builder builder) {
super(builder.cursor, builder.sortDirection, builder.limit);
this.portfolioId = builder.portfolioId;
this.type = builder.type;
this.symbols = builder.symbols;
+ this.getNetworkUnifiedWallets = builder.getNetworkUnifiedWallets;
}
public String getPortfolioId() {
@@ -68,15 +78,25 @@ public void setSymbols(String[] symbols) {
this.symbols = symbols;
}
+ public Boolean getGetNetworkUnifiedWallets() {
+ return getNetworkUnifiedWallets;
+ }
+
+ public void setGetNetworkUnifiedWallets(Boolean getNetworkUnifiedWallets) {
+ this.getNetworkUnifiedWallets = getNetworkUnifiedWallets;
+ }
+
public static class Builder {
private String portfolioId;
private WalletType type;
private String[] symbols;
+ private Boolean getNetworkUnifiedWallets;
private String cursor;
private SortDirection sortDirection;
private Integer limit;
- public Builder() {}
+ public Builder() {
+ }
public Builder portfolioId(String portfolioId) {
this.portfolioId = portfolioId;
@@ -93,9 +113,8 @@ public Builder symbols(String[] symbols) {
return this;
}
- public Builder pagination(Pagination pagination) {
- this.cursor = pagination.getNextCursor();
- this.sortDirection = pagination.getSortDirection();
+ public Builder getNetworkUnifiedWallets(Boolean getNetworkUnifiedWallets) {
+ this.getNetworkUnifiedWallets = getNetworkUnifiedWallets;
return this;
}
@@ -104,8 +123,14 @@ public Builder limit(Integer limit) {
return this;
}
+ public Builder pagination(Pagination pagination) {
+ this.cursor = pagination.getNextCursor();
+ this.sortDirection = pagination.getSortDirection();
+ return this;
+ }
+
public ListWalletsRequest build() throws CoinbaseClientException {
- this.validate();
+ validate();
return new ListWalletsRequest(this);
}
diff --git a/src/main/java/com/coinbase/prime/wallets/ListWalletsResponse.java b/src/main/java/com/coinbase/prime/wallets/ListWalletsResponse.java
index 5b8d87b9..f9263209 100644
--- a/src/main/java/com/coinbase/prime/wallets/ListWalletsResponse.java
+++ b/src/main/java/com/coinbase/prime/wallets/ListWalletsResponse.java
@@ -18,19 +18,20 @@
import com.coinbase.prime.common.Pagination;
import com.coinbase.prime.model.Wallet;
+import com.fasterxml.jackson.annotation.JsonProperty;
/**
- * Response object for listing all wallets associated with a portfolio.
- *
- * Contains an array of wallets and pagination information for the wallet listing.
+ * List Portfolio Wallets
*/
public class ListWalletsResponse {
- /** Array of wallets associated with the portfolio */
+ @JsonProperty("wallets")
private Wallet[] wallets;
- /** Pagination information for the wallet listing */
+
+ @JsonProperty("pagination")
private Pagination pagination;
- public ListWalletsResponse() {}
+ public ListWalletsResponse() {
+ }
public Wallet[] getWallets() {
return wallets;
@@ -47,4 +48,5 @@ public Pagination getPagination() {
public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
+
}
diff --git a/src/main/java/com/coinbase/prime/wallets/WalletsService.java b/src/main/java/com/coinbase/prime/wallets/WalletsService.java
index c221b2bd..030b9a2e 100644
--- a/src/main/java/com/coinbase/prime/wallets/WalletsService.java
+++ b/src/main/java/com/coinbase/prime/wallets/WalletsService.java
@@ -20,7 +20,6 @@
import com.coinbase.prime.errors.CoinbasePrimeException;
public interface WalletsService {
- // Wallets - OpenAPI spec compliance
ListWalletsResponse listWallets(ListWalletsRequest request) throws CoinbaseClientException, CoinbasePrimeException;
CreateWalletResponse createWallet(CreateWalletRequest request) throws CoinbaseClientException, CoinbasePrimeException;
GetWalletResponse getWallet(GetWalletRequest request) throws CoinbaseClientException, CoinbasePrimeException;
diff --git a/src/main/java/com/coinbase/prime/wallets/WalletsServiceImpl.java b/src/main/java/com/coinbase/prime/wallets/WalletsServiceImpl.java
index f543ffa3..f2a7b9a1 100644
--- a/src/main/java/com/coinbase/prime/wallets/WalletsServiceImpl.java
+++ b/src/main/java/com/coinbase/prime/wallets/WalletsServiceImpl.java
@@ -45,7 +45,7 @@ public CreateWalletResponse createWallet(CreateWalletRequest request) throws Coi
HttpMethod.POST,
String.format("/portfolios/%s/wallets", request.getPortfolioId()),
request,
- List.of(200),
+ List.of(201, 200),
new TypeReference() {});
}
@@ -60,36 +60,33 @@ public GetWalletResponse getWallet(GetWalletRequest request) throws CoinbasePrim
}
@Override
- public GetWalletDepositInstructionsResponse getWalletDepositInstructions(
- GetWalletDepositInstructionsRequest request) throws CoinbasePrimeException {
+ public ListWalletAddressesResponse listWalletAddresses(ListWalletAddressesRequest request) throws CoinbasePrimeException {
return this.request(
HttpMethod.GET,
- String.format("/portfolios/%s/wallets/%s/deposit_instructions", request.getPortfolioId(),
- request.getWalletId()),
+ String.format("/portfolios/%s/wallets/%s/addresses", request.getPortfolioId(), request.getWalletId()),
request,
List.of(200),
- new TypeReference() {});
+ new TypeReference() {});
}
@Override
- public ListWalletAddressesResponse listWalletAddresses(ListWalletAddressesRequest request)
- throws CoinbasePrimeException {
+ public CreateWalletDepositAddressResponse createWalletDepositAddress(CreateWalletDepositAddressRequest request) throws CoinbasePrimeException {
return this.request(
- HttpMethod.GET,
+ HttpMethod.POST,
String.format("/portfolios/%s/wallets/%s/addresses", request.getPortfolioId(), request.getWalletId()),
request,
- List.of(200),
- new TypeReference() {});
+ List.of(201, 200),
+ new TypeReference() {});
}
@Override
- public CreateWalletDepositAddressResponse createWalletDepositAddress(CreateWalletDepositAddressRequest request)
- throws CoinbasePrimeException {
+ public GetWalletDepositInstructionsResponse getWalletDepositInstructions(GetWalletDepositInstructionsRequest request) throws CoinbasePrimeException {
return this.request(
- HttpMethod.POST,
- String.format("/portfolios/%s/wallets/%s/addresses", request.getPortfolioId(), request.getWalletId()),
+ HttpMethod.GET,
+ String.format("/portfolios/%s/wallets/%s/deposit_instructions", request.getPortfolioId(), request.getWalletId()),
request,
List.of(200),
- new TypeReference() {});
+ new TypeReference() {});
}
+
}
diff --git a/src/test/java/com/coinbase/prime/activities/ActivitiesServiceSerializationTest.java b/src/test/java/com/coinbase/prime/activities/ActivitiesServiceSerializationTest.java
new file mode 100644
index 00000000..544e2b66
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/activities/ActivitiesServiceSerializationTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.prime.activities;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ActivitiesServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== GetActivity Tests ====================
+
+ @Test
+ public void testGetActivityRequestConstruction() {
+ GetActivityRequest request = new GetActivityRequest("activity-123");
+ assertNotNull(request);
+ assertEquals("activity-123", request.getActivityId());
+ }
+
+ @Test
+ public void testGetActivityResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity\":{"
+ + "\"id\":\"activity-123\","
+ + "\"category\":\"ACTIVITY_CATEGORY_ORDER\","
+ + "\"status\":\"ACTIVITY_STATUS_COMPLETED\""
+ + "}"
+ + "}";
+
+ GetActivityResponse response = objectMapper.readValue(json, GetActivityResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getActivity());
+ assertEquals("activity-123", response.getActivity().getId());
+ }
+
+ // ==================== GetPortfolioActivity Tests ====================
+
+ @Test
+ public void testGetPortfolioActivityRequestConstruction() throws CoinbaseClientException {
+ GetPortfolioActivityRequest request = new GetPortfolioActivityRequest.Builder()
+ .portfolioId("portfolio-123")
+ .activityId("activity-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("activity-456", request.getActivityId());
+ }
+
+ @Test
+ public void testGetPortfolioActivityRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPortfolioActivityRequest.Builder()
+ .activityId("activity-456")
+ .build());
+
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPortfolioActivityRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build());
+ }
+
+ @Test
+ public void testGetPortfolioActivityResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity\":{"
+ + "\"id\":\"activity-789\","
+ + "\"category\":\"ACTIVITY_CATEGORY_TRANSACTION\","
+ + "\"status\":\"ACTIVITY_STATUS_PROCESSING\""
+ + "}"
+ + "}";
+
+ GetPortfolioActivityResponse response = objectMapper.readValue(json, GetPortfolioActivityResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getActivity());
+ assertEquals("activity-789", response.getActivity().getId());
+ }
+
+ // ==================== ListPortfolioActivities Tests ====================
+
+ @Test
+ public void testListPortfolioActivitiesRequestConstruction() throws CoinbaseClientException {
+ ListPortfolioActivitiesRequest request = new ListPortfolioActivitiesRequest.Builder("portfolio-123")
+ .symbols(new String[]{"BTC", "ETH"})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testListPortfolioActivitiesRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListPortfolioActivitiesRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testListPortfolioActivitiesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activities\":["
+ + "{\"id\":\"act-1\",\"category\":\"ACTIVITY_CATEGORY_ORDER\"},"
+ + "{\"id\":\"act-2\",\"category\":\"ACTIVITY_CATEGORY_TRANSACTION\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"cursor-123\",\"has_next\":true}"
+ + "}";
+
+ ListPortfolioActivitiesResponse response = objectMapper.readValue(json, ListPortfolioActivitiesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getActivities());
+ assertEquals(2, response.getActivities().length);
+ assertEquals("act-1", response.getActivities()[0].getId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/activities/ListEntityActivitiesRequestTest.java b/src/test/java/com/coinbase/prime/activities/ListEntityActivitiesRequestTest.java
index 0a5e21fc..28224c52 100644
--- a/src/test/java/com/coinbase/prime/activities/ListEntityActivitiesRequestTest.java
+++ b/src/test/java/com/coinbase/prime/activities/ListEntityActivitiesRequestTest.java
@@ -32,7 +32,8 @@ public class ListEntityActivitiesRequestTest {
@Test
public void testBuilderWithEnums() throws CoinbaseClientException {
// Test that the builder accepts enum types
- ListEntityActivitiesRequest request = new ListEntityActivitiesRequest.Builder("entity-123")
+ ListEntityActivitiesRequest request = new ListEntityActivitiesRequest.Builder()
+ .entityId("entity-123")
.activityLevel(ActivityLevel.ACTIVITY_LEVEL_ALL)
.categories(new ActivityCategory[]{
ActivityCategory.ACTIVITY_CATEGORY_ORDER,
@@ -65,7 +66,8 @@ public void testBuilderWithEnums() throws CoinbaseClientException {
@Test
public void testSettersWithEnums() {
- ListEntityActivitiesRequest request = new ListEntityActivitiesRequest("entity-456");
+ ListEntityActivitiesRequest request = new ListEntityActivitiesRequest();
+ request.setEntityId("entity-456");
// Test that setters accept enum types
request.setActivityLevel(ActivityLevel.ACTIVITY_LEVEL_PORTFOLIO);
@@ -81,7 +83,8 @@ public void testSettersWithEnums() {
@Test
public void testJsonSerialization() throws Exception {
// Test that enums serialize correctly to JSON
- ListEntityActivitiesRequest request = new ListEntityActivitiesRequest.Builder("entity-789")
+ ListEntityActivitiesRequest request = new ListEntityActivitiesRequest.Builder()
+ .entityId("entity-789")
.activityLevel(ActivityLevel.ACTIVITY_LEVEL_ENTITY)
.categories(new ActivityCategory[]{ActivityCategory.ACTIVITY_CATEGORY_LENDING})
.statuses(new ActivityStatus[]{ActivityStatus.ACTIVITY_STATUS_CANCELLED})
@@ -99,11 +102,11 @@ public void testJsonSerialization() throws Exception {
public void testBuilderRequiresEntityId() {
// Test that validation works
assertThrows(CoinbaseClientException.class, () -> {
- new ListEntityActivitiesRequest.Builder(null).build();
+ new ListEntityActivitiesRequest.Builder().entityId(null).build();
});
assertThrows(CoinbaseClientException.class, () -> {
- new ListEntityActivitiesRequest.Builder("").build();
+ new ListEntityActivitiesRequest.Builder().entityId("").build();
});
}
}
diff --git a/src/test/java/com/coinbase/prime/addressbook/AddressBookServiceSerializationTest.java b/src/test/java/com/coinbase/prime/addressbook/AddressBookServiceSerializationTest.java
new file mode 100644
index 00000000..802e9db0
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/addressbook/AddressBookServiceSerializationTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.prime.addressbook;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AddressBookServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListAddressBook Tests ====================
+
+ @Test
+ public void testListAddressBookRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListAddressBookRequest request = new ListAddressBookRequest.Builder("portfolio-123")
+ .currencySymbol("BTC")
+ .search("my-address")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"currency_symbol\":\"BTC\""));
+ assertTrue(json.contains("\"search\":\"my-address\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testListAddressBookRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListAddressBookRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testListAddressBookResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"addresses\":["
+ + "{\"id\":\"addr-1\",\"address\":\"bc1qxy2kgdygjrsqtzq2n0yrf2\",\"currency_symbol\":\"BTC\"},"
+ + "{\"id\":\"addr-2\",\"address\":\"0xabc123\",\"currency_symbol\":\"ETH\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"cursor-abc\",\"has_next\":false}"
+ + "}";
+
+ ListAddressBookResponse response = objectMapper.readValue(json, ListAddressBookResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAddresses());
+ assertEquals(2, response.getAddresses().length);
+ assertEquals("addr-1", response.getAddresses()[0].getId());
+ }
+
+ // ==================== CreateAddressBookEntry Tests ====================
+
+ @Test
+ public void testCreateAddressBookEntryRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ CreateAddressBookEntryRequest request = new CreateAddressBookEntryRequest.Builder("portfolio-123")
+ .address("bc1qxy2kgdygjrsqtzq2n0yrf2")
+ .currencySymbol("BTC")
+ .name("My Bitcoin Address")
+ .accountIdentifier("memo-123")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"address\":\"bc1qxy2kgdygjrsqtzq2n0yrf2\""));
+ assertTrue(json.contains("\"currency_symbol\":\"BTC\""));
+ assertTrue(json.contains("\"name\":\"My Bitcoin Address\""));
+ assertTrue(json.contains("\"account_identifier\":\"memo-123\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testCreateAddressBookEntryRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new CreateAddressBookEntryRequest.Builder("portfolio-123")
+ .address("bc1q")
+ .currencySymbol("BTC")
+ .build());
+
+ assertThrows(CoinbaseClientException.class, () ->
+ new CreateAddressBookEntryRequest.Builder("portfolio-123")
+ .currencySymbol("BTC")
+ .name("name")
+ .build());
+ }
+
+ @Test
+ public void testCreateAddressBookEntryResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_type\":\"ACTIVITY_TYPE_ADDRESS_BOOK\","
+ + "\"num_approvals_remaining\":2,"
+ + "\"activity_id\":\"act-abc123\""
+ + "}";
+
+ CreateAddressBookEntryResponse response = objectMapper.readValue(json, CreateAddressBookEntryResponse.class);
+ assertNotNull(response);
+ assertEquals(2, response.getNumApprovalsRemaining());
+ assertEquals("act-abc123", response.getActivityId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/advancedtransfers/AdvancedTransfersServiceSerializationTest.java b/src/test/java/com/coinbase/prime/advancedtransfers/AdvancedTransfersServiceSerializationTest.java
new file mode 100644
index 00000000..2f7d1986
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/advancedtransfers/AdvancedTransfersServiceSerializationTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.prime.advancedtransfers;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AdvancedTransfersServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListAdvancedTransfers Tests ====================
+
+ @Test
+ public void testListAdvancedTransfersRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListAdvancedTransfersRequest request = new ListAdvancedTransfersRequest.Builder()
+ .portfolioId("portfolio-123")
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .referenceId("ref-abc")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"start_time\":\"2025-01-01T00:00:00Z\""));
+ assertTrue(json.contains("\"end_time\":\"2025-12-31T23:59:59Z\""));
+ assertTrue(json.contains("\"reference_id\":\"ref-abc\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testListAdvancedTransfersRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListAdvancedTransfersRequest.Builder().build());
+ }
+
+ @Test
+ public void testListAdvancedTransfersResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"advanced_transfers\":["
+ + "{\"id\":\"at-1\",\"state\":\"ADVANCED_TRANSFER_STATE_CREATED\"},"
+ + "{\"id\":\"at-2\",\"state\":\"ADVANCED_TRANSFER_STATE_DONE\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListAdvancedTransfersResponse response = objectMapper.readValue(json, ListAdvancedTransfersResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAdvancedTransfers());
+ assertEquals(2, response.getAdvancedTransfers().length);
+ }
+
+ // ==================== CreateAdvancedTransfer Tests ====================
+
+ @Test
+ public void testCreateAdvancedTransferRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ CreateAdvancedTransferRequest request = new CreateAdvancedTransferRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testCreateAdvancedTransferRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new CreateAdvancedTransferRequest.Builder().build());
+ }
+
+ @Test
+ public void testCreateAdvancedTransferResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"advanced_transfer\":{"
+ + "\"id\":\"at-new-1\","
+ + "\"state\":\"ADVANCED_TRANSFER_STATE_CREATED\","
+ + "\"type\":\"ADVANCED_TRANSFER_TYPE_BLIND_MATCH\","
+ + "\"amount\":\"1000.00\","
+ + "\"currency\":\"USD\""
+ + "}"
+ + "}";
+
+ CreateAdvancedTransferResponse response = objectMapper.readValue(json, CreateAdvancedTransferResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAdvancedTransfer());
+ assertEquals("at-new-1", response.getAdvancedTransfer().getId());
+ }
+
+ // ==================== CancelAdvancedTransfer Tests ====================
+
+ @Test
+ public void testCancelAdvancedTransferRequestConstruction() throws CoinbaseClientException {
+ CancelAdvancedTransferRequest request = new CancelAdvancedTransferRequest.Builder()
+ .portfolioId("portfolio-123")
+ .advancedTransferId("at-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("at-456", request.getAdvancedTransferId());
+ }
+
+ @Test
+ public void testCancelAdvancedTransferRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new CancelAdvancedTransferRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build());
+ assertThrows(CoinbaseClientException.class, () ->
+ new CancelAdvancedTransferRequest.Builder()
+ .advancedTransferId("at-456")
+ .build());
+ }
+
+ @Test
+ public void testCancelAdvancedTransferResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"advanced_transfer_id\":\"at-456\"}";
+ CancelAdvancedTransferResponse response = objectMapper.readValue(json, CancelAdvancedTransferResponse.class);
+ assertNotNull(response);
+ assertEquals("at-456", response.getAdvancedTransferId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/allocations/AllocationsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/allocations/AllocationsServiceSerializationTest.java
new file mode 100644
index 00000000..aa8136c0
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/allocations/AllocationsServiceSerializationTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.prime.allocations;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AllocationsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== CreateAllocation Tests ====================
+
+ @Test
+ public void testCreateAllocationRequestSerialization() throws JsonProcessingException {
+ CreateAllocationRequest request = new CreateAllocationRequest.Builder()
+ .allocationId("alloc-123")
+ .sourcePortfolioId("portfolio-source")
+ .productId("BTC-USD")
+ .orderIds(new String[]{"order-1", "order-2"})
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"allocation_id\":\"alloc-123\""));
+ assertTrue(json.contains("\"source_portfolio_id\":\"portfolio-source\""));
+ assertTrue(json.contains("\"product_id\":\"BTC-USD\""));
+ assertTrue(json.contains("\"order_ids\""));
+ }
+
+ @Test
+ public void testCreateAllocationResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"success\":true,"
+ + "\"allocation_id\":\"alloc-123\""
+ + "}";
+
+ CreateAllocationResponse response = objectMapper.readValue(json, CreateAllocationResponse.class);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ assertEquals("alloc-123", response.getAllocationId());
+ }
+
+ @Test
+ public void testCreateAllocationResponseFailureDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"success\":false,"
+ + "\"failure_reason\":\"Insufficient orders\""
+ + "}";
+
+ CreateAllocationResponse response = objectMapper.readValue(json, CreateAllocationResponse.class);
+ assertNotNull(response);
+ assertFalse(response.isSuccess());
+ assertEquals("Insufficient orders", response.getFailureReason());
+ }
+
+ // ==================== CreateNetAllocation Tests ====================
+
+ @Test
+ public void testCreateNetAllocationRequestSerialization() throws JsonProcessingException {
+ CreateNetAllocationRequest request = new CreateNetAllocationRequest.Builder()
+ .sourcePortfolioId("portfolio-source")
+ .productId("ETH-USD")
+ .nettingId("netting-456")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"source_portfolio_id\":\"portfolio-source\""));
+ assertTrue(json.contains("\"product_id\":\"ETH-USD\""));
+ assertTrue(json.contains("\"netting_id\":\"netting-456\""));
+ }
+
+ @Test
+ public void testCreateNetAllocationResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"success\":true,"
+ + "\"netting_id\":\"netting-456\","
+ + "\"buy_allocation_id\":\"buy-alloc-1\","
+ + "\"sell_allocation_id\":\"sell-alloc-2\""
+ + "}";
+
+ CreateNetAllocationResponse response = objectMapper.readValue(json, CreateNetAllocationResponse.class);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ assertEquals("netting-456", response.getNettingId());
+ assertEquals("buy-alloc-1", response.getBuyAllocationId());
+ assertEquals("sell-alloc-2", response.getSellAllocationId());
+ }
+
+ // ==================== GetAllocation Tests ====================
+
+ @Test
+ public void testGetAllocationRequestConstruction() throws CoinbaseClientException {
+ GetAllocationRequest request = new GetAllocationRequest.Builder("portfolio-123", "alloc-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("alloc-456", request.getAllocationId());
+ }
+
+ @Test
+ public void testGetAllocationResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"allocation\":{"
+ + "\"allocation_id\":\"alloc-456\","
+ + "\"product_id\":\"BTC-USD\""
+ + "}"
+ + "}";
+
+ GetAllocationResponse response = objectMapper.readValue(json, GetAllocationResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAllocation());
+ }
+
+ // ==================== ListAllocationsByNettingId Tests ====================
+
+ @Test
+ public void testListAllocationsByNettingIdRequestConstruction() throws CoinbaseClientException {
+ ListAllocationsByNettingIdRequest request = new ListAllocationsByNettingIdRequest.Builder("portfolio-123", "netting-789")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("netting-789", request.getNettingId());
+ }
+
+ @Test
+ public void testListAllocationsByNettingIdResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"allocations\":["
+ + "{\"allocation_id\":\"alloc-1\",\"product_id\":\"BTC-USD\"},"
+ + "{\"allocation_id\":\"alloc-2\",\"product_id\":\"BTC-USD\"}"
+ + "]"
+ + "}";
+
+ ListAllocationsByNettingIdResponse response = objectMapper.readValue(json, ListAllocationsByNettingIdResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAllocations());
+ assertEquals(2, response.getAllocations().length);
+ }
+
+ // ==================== ListPortfolioAllocations Tests ====================
+
+ @Test
+ public void testListPortfolioAllocationsRequestConstruction() throws CoinbaseClientException {
+ ListPortfolioAllocationsRequest request = new ListPortfolioAllocationsRequest.Builder("portfolio-123")
+ .productIds(new String[]{"BTC-USD", "ETH-USD"})
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testListPortfolioAllocationsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"allocations\":["
+ + "{\"allocation_id\":\"alloc-1\",\"product_id\":\"BTC-USD\"},"
+ + "{\"allocation_id\":\"alloc-2\",\"product_id\":\"ETH-USD\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"page-2\",\"has_next\":true}"
+ + "}";
+
+ ListPortfolioAllocationsResponse response = objectMapper.readValue(json, ListPortfolioAllocationsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAllocations());
+ assertEquals(2, response.getAllocations().length);
+ assertNotNull(response.getPagination());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/assets/AssetsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/assets/AssetsServiceSerializationTest.java
new file mode 100644
index 00000000..66385cc8
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/assets/AssetsServiceSerializationTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.prime.assets;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AssetsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ @Test
+ public void testListAssetsRequestConstruction() throws CoinbaseClientException {
+ ListAssetsRequest request = new ListAssetsRequest.Builder("entity-123").build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testListAssetsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListAssetsRequest.Builder(null).build());
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListAssetsRequest.Builder("").build());
+ }
+
+ @Test
+ public void testListAssetsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"assets\":["
+ + "{\"name\":\"Bitcoin\",\"symbol\":\"BTC\",\"decimal_precision\":\"8\"},"
+ + "{\"name\":\"Ethereum\",\"symbol\":\"ETH\",\"decimal_precision\":\"18\"}"
+ + "]"
+ + "}";
+
+ ListAssetsResponse response = objectMapper.readValue(json, ListAssetsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAssets());
+ assertEquals(2, response.getAssets().length);
+ assertEquals("BTC", response.getAssets()[0].getSymbol());
+ assertEquals("ETH", response.getAssets()[1].getSymbol());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/balances/BalancesServiceSerializationTest.java b/src/test/java/com/coinbase/prime/balances/BalancesServiceSerializationTest.java
new file mode 100644
index 00000000..75fb0440
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/balances/BalancesServiceSerializationTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.prime.balances;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BalancesServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListEntityBalances Tests ====================
+
+ @Test
+ public void testListEntityBalancesRequestSerialization() throws JsonProcessingException {
+ ListEntityBalancesRequest request = new ListEntityBalancesRequest.Builder()
+ .entityId("entity-123")
+ .symbols("BTC,ETH")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbols\":\"BTC,ETH\""));
+ assertFalse(json.contains("\"entity_id\""));
+ }
+
+ @Test
+ public void testListEntityBalancesRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListEntityBalancesRequest.Builder().build());
+ }
+
+ @Test
+ public void testListEntityBalancesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"balances\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"1.5\",\"holds\":\"0.0\"},"
+ + "{\"symbol\":\"ETH\",\"amount\":\"10.0\",\"holds\":\"0.5\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"cursor-xyz\",\"has_next\":false}"
+ + "}";
+
+ ListEntityBalancesResponse response = objectMapper.readValue(json, ListEntityBalancesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getBalances());
+ assertEquals(2, response.getBalances().length);
+ }
+
+ // ==================== ListPortfolioBalances Tests ====================
+
+ @Test
+ public void testListPortfolioBalancesRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListPortfolioBalancesRequest request = new ListPortfolioBalancesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbols(new String[]{"BTC", "ETH"})
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbols\""));
+ assertFalse(json.contains("\"portfolio_id\""));
+ }
+
+ @Test
+ public void testListPortfolioBalancesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"balances\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"2.0\",\"holds\":\"0.1\"}"
+ + "],"
+ + "\"type\":\"TRADING_BALANCES\","
+ + "\"trading_balances\":{\"total\":\"100000.00\",\"holds\":\"5000.00\"},"
+ + "\"vault_balances\":{\"total\":\"50000.00\",\"holds\":\"0.00\"}"
+ + "}";
+
+ ListPortfolioBalancesResponse response = objectMapper.readValue(json, ListPortfolioBalancesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getBalances());
+ assertEquals(1, response.getBalances().length);
+ assertNotNull(response.getTradingBalances());
+ assertNotNull(response.getVaultBalances());
+ }
+
+ // ==================== GetWalletBalance Tests ====================
+
+ @Test
+ public void testGetWalletBalanceRequestConstruction() throws CoinbaseClientException {
+ GetWalletBalanceRequest request = new GetWalletBalanceRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ }
+
+ @Test
+ public void testGetWalletBalanceRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetWalletBalanceRequest.Builder()
+ .walletId("wallet-456")
+ .build());
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetWalletBalanceRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build());
+ }
+
+ @Test
+ public void testGetWalletBalanceResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"balance\":{"
+ + "\"symbol\":\"BTC\","
+ + "\"amount\":\"0.5\","
+ + "\"holds\":\"0.0\""
+ + "}"
+ + "}";
+
+ GetWalletBalanceResponse response = objectMapper.readValue(json, GetWalletBalanceResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getBalance());
+ assertEquals("BTC", response.getBalance().getSymbol());
+ }
+
+ // ==================== ListOnchainWalletBalances Tests ====================
+
+ @Test
+ public void testListOnchainWalletBalancesRequestConstruction() throws CoinbaseClientException {
+ ListOnchainWalletBalancesRequest request = new ListOnchainWalletBalancesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ }
+
+ @Test
+ public void testListOnchainWalletBalancesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"balances\":["
+ + "{\"asset\":{\"name\":\"USDC\",\"symbol\":\"USDC\"},\"amount\":\"500.00\"},"
+ + "{\"asset\":{\"name\":\"Ether\",\"symbol\":\"ETH\"},\"amount\":\"2.5\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListOnchainWalletBalancesResponse response = objectMapper.readValue(json, ListOnchainWalletBalancesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getBalances());
+ assertEquals(2, response.getBalances().length);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/commission/CommissionServiceSerializationTest.java b/src/test/java/com/coinbase/prime/commission/CommissionServiceSerializationTest.java
new file mode 100644
index 00000000..a7de9999
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/commission/CommissionServiceSerializationTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.prime.commission;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CommissionServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ @Test
+ public void testGetPortfolioCommissionRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ GetPortfolioCommissionRequest request = new GetPortfolioCommissionRequest.Builder()
+ .portfolioId("portfolio-123")
+ .productId("BTC-USD")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"product_id\":\"BTC-USD\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testGetPortfolioCommissionRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPortfolioCommissionRequest.Builder().build());
+ }
+
+ @Test
+ public void testGetPortfolioCommissionResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"commission\":{"
+ + "\"type\":\"COMMISSION_TYPE_TAKER\","
+ + "\"rate\":\"0.0005\""
+ + "}"
+ + "}";
+
+ GetPortfolioCommissionResponse response = objectMapper.readValue(json, GetPortfolioCommissionResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getCommission());
+ assertEquals("0.0005", response.getCommission().getRate());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/financing/FinancingServiceSerializationTest.java b/src/test/java/com/coinbase/prime/financing/FinancingServiceSerializationTest.java
new file mode 100644
index 00000000..f31c2d40
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/financing/FinancingServiceSerializationTest.java
@@ -0,0 +1,454 @@
+/*
+ * 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.prime.financing;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class FinancingServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== CreateNewLocates Tests ====================
+
+ @Test
+ public void testCreateNewLocatesRequestSerialization() throws JsonProcessingException {
+ CreateNewLocatesRequest request = new CreateNewLocatesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbol("BTC")
+ .amount("1.0")
+ .locateDate("2025-01-15")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbol\":\"BTC\""));
+ assertTrue(json.contains("\"amount\":\"1.0\""));
+ assertTrue(json.contains("\"locate_date\":\"2025-01-15\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testCreateNewLocatesResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"locate_id\":\"locate-abc123\"}";
+ CreateNewLocatesResponse response = objectMapper.readValue(json, CreateNewLocatesResponse.class);
+ assertNotNull(response);
+ assertEquals("locate-abc123", response.getLocateId());
+ }
+
+ // ==================== GetCrossMarginOverview Tests ====================
+
+ @Test
+ public void testGetCrossMarginOverviewRequestConstruction() {
+ GetCrossMarginOverviewRequest request = new GetCrossMarginOverviewRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetCrossMarginOverviewResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"overview\":{"
+ + "\"equity\":\"50000.00\","
+ + "\"margin_used\":\"10000.00\""
+ + "}"
+ + "}";
+
+ GetCrossMarginOverviewResponse response = objectMapper.readValue(json, GetCrossMarginOverviewResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getOverview());
+ }
+
+ // ==================== GetEntityLocateAvailabilities Tests ====================
+
+ @Test
+ public void testGetEntityLocateAvailabilitiesRequestSerialization() throws JsonProcessingException {
+ GetEntityLocateAvailabilitiesRequest request = new GetEntityLocateAvailabilitiesRequest.Builder()
+ .entityId("entity-123")
+ .conversionDate("2025-01-15")
+ .locateDate("2025-01-15")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"conversion_date\":\"2025-01-15\""));
+ assertTrue(json.contains("\"locate_date\":\"2025-01-15\""));
+ }
+
+ @Test
+ public void testGetEntityLocateAvailabilitiesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"locates\":["
+ + "{\"symbol\":\"BTC\",\"available\":\"5.0\"},"
+ + "{\"symbol\":\"ETH\",\"available\":\"50.0\"}"
+ + "]"
+ + "}";
+
+ GetEntityLocateAvailabilitiesResponse response = objectMapper.readValue(json, GetEntityLocateAvailabilitiesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getLocates());
+ assertEquals(2, response.getLocates().length);
+ }
+
+ // ==================== GetMarginInformation Tests ====================
+
+ @Test
+ public void testGetMarginInformationRequestConstruction() {
+ GetMarginInformationRequest request = new GetMarginInformationRequest.Builder()
+ .entityId("entity-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-456", request.getEntityId());
+ }
+
+ @Test
+ public void testGetMarginInformationResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"margin_information\":{"
+ + "\"leverage\":\"3.0\","
+ + "\"margin_call_state\":\"FCM_MARGIN_CALL_STATE_OK\""
+ + "}"
+ + "}";
+
+ GetMarginInformationResponse response = objectMapper.readValue(json, GetMarginInformationResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getMarginInformation());
+ }
+
+ // ==================== GetPortfolioBuyingPower Tests ====================
+
+ @Test
+ public void testGetPortfolioBuyingPowerRequestSerialization() throws JsonProcessingException {
+ GetPortfolioBuyingPowerRequest request = new GetPortfolioBuyingPowerRequest.Builder()
+ .portfolioId("portfolio-123")
+ .baseCurrency("BTC")
+ .quoteCurrency("USD")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"base_currency\":\"BTC\""));
+ assertTrue(json.contains("\"quote_currency\":\"USD\""));
+ assertFalse(json.contains("\"portfolio_id\""));
+ }
+
+ @Test
+ public void testGetPortfolioBuyingPowerResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"buying_power\":{"
+ + "\"symbol\":\"BTC\","
+ + "\"quantity\":\"0.5\","
+ + "\"local_currency\":\"USD\","
+ + "\"local_currency_quantity\":\"25000.00\""
+ + "}"
+ + "}";
+
+ GetPortfolioBuyingPowerResponse response = objectMapper.readValue(json, GetPortfolioBuyingPowerResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getBuyingPower());
+ }
+
+ // ==================== GetPortfolioCreditInformation Tests ====================
+
+ @Test
+ public void testGetPortfolioCreditInformationRequestConstruction() {
+ GetPortfolioCreditInformationRequest request = new GetPortfolioCreditInformationRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testGetPortfolioCreditInformationResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"postTradeCredit\":{"
+ + "\"portfolio_id\":\"portfolio-123\","
+ + "\"currency\":\"USD\","
+ + "\"limit\":\"1000000.00\","
+ + "\"utilized\":\"50000.00\""
+ + "}"
+ + "}";
+
+ GetPortfolioCreditInformationResponse response = objectMapper.readValue(json, GetPortfolioCreditInformationResponse.class);
+ assertNotNull(response);
+ }
+
+ // ==================== GetPortfolioWithdrawalPower Tests ====================
+
+ @Test
+ public void testGetPortfolioWithdrawalPowerRequestSerialization() throws JsonProcessingException {
+ GetPortfolioWithdrawalPowerRequest request = new GetPortfolioWithdrawalPowerRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbol("BTC")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbol\":\"BTC\""));
+ }
+
+ @Test
+ public void testGetPortfolioWithdrawalPowerResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"withdrawal_power\":{"
+ + "\"symbol\":\"BTC\","
+ + "\"quantity\":\"0.25\""
+ + "}"
+ + "}";
+
+ GetPortfolioWithdrawalPowerResponse response = objectMapper.readValue(json, GetPortfolioWithdrawalPowerResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getWithdrawalPower());
+ }
+
+ // ==================== GetTradeFinanceTieredPricingFees Tests ====================
+
+ @Test
+ public void testGetTradeFinanceTieredPricingFeesRequestSerialization() throws JsonProcessingException {
+ GetTradeFinanceTieredPricingFeesRequest request = new GetTradeFinanceTieredPricingFeesRequest.Builder()
+ .entityId("entity-123")
+ .effectiveAt("2025-01-01T00:00:00Z")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"effective_at\":\"2025-01-01T00:00:00Z\""));
+ }
+
+ @Test
+ public void testGetTradeFinanceTieredPricingFeesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"fees\":["
+ + "{\"tier\":\"1\",\"rate\":\"0.01\"},"
+ + "{\"tier\":\"2\",\"rate\":\"0.008\"}"
+ + "]"
+ + "}";
+
+ GetTradeFinanceTieredPricingFeesResponse response = objectMapper.readValue(json, GetTradeFinanceTieredPricingFeesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getFees());
+ assertEquals(2, response.getFees().length);
+ }
+
+ // ==================== ListExistingLocates Tests ====================
+
+ @Test
+ public void testListExistingLocatesRequestSerialization() throws JsonProcessingException {
+ ListExistingLocatesRequest request = new ListExistingLocatesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .locateIds(new String[]{"locate-1", "locate-2"})
+ .locateDate("2025-01-15")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"locate_ids\""));
+ assertTrue(json.contains("\"locate_date\":\"2025-01-15\""));
+ }
+
+ @Test
+ public void testListExistingLocatesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"locates\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"1.0\",\"locate_date\":\"2025-01-15\"},"
+ + "{\"symbol\":\"ETH\",\"amount\":\"10.0\",\"locate_date\":\"2025-01-15\"}"
+ + "]"
+ + "}";
+
+ ListExistingLocatesResponse response = objectMapper.readValue(json, ListExistingLocatesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getLocates());
+ assertEquals(2, response.getLocates().length);
+ }
+
+ // ==================== ListFinancingEligibleAssets Tests ====================
+
+ @Test
+ public void testListFinancingEligibleAssetsRequestConstruction() {
+ ListFinancingEligibleAssetsRequest request = new ListFinancingEligibleAssetsRequest.Builder().build();
+ assertNotNull(request);
+ }
+
+ @Test
+ public void testListFinancingEligibleAssetsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"assets\":["
+ + "{\"symbol\":\"BTC\",\"name\":\"Bitcoin\"},"
+ + "{\"symbol\":\"ETH\",\"name\":\"Ethereum\"}"
+ + "]"
+ + "}";
+
+ ListFinancingEligibleAssetsResponse response = objectMapper.readValue(json, ListFinancingEligibleAssetsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAssets());
+ assertEquals(2, response.getAssets().size());
+ }
+
+ // ==================== ListInterestAccruals Tests ====================
+
+ @Test
+ public void testListInterestAccrualsRequestSerialization() throws JsonProcessingException {
+ ListInterestAccrualsRequest request = new ListInterestAccrualsRequest.Builder()
+ .entityId("entity-123")
+ .portfolioId("portfolio-456")
+ .startDate("2025-01-01")
+ .endDate("2025-01-31")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"portfolio_id\":\"portfolio-456\""));
+ assertTrue(json.contains("\"start_date\":\"2025-01-01\""));
+ assertTrue(json.contains("\"end_date\":\"2025-01-31\""));
+ }
+
+ @Test
+ public void testListInterestAccrualsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"total_notional_accrual\":\"150.00\","
+ + "\"accruals\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"100.00\"},"
+ + "{\"symbol\":\"ETH\",\"amount\":\"50.00\"}"
+ + "]"
+ + "}";
+
+ ListInterestAccrualsResponse response = objectMapper.readValue(json, ListInterestAccrualsResponse.class);
+ assertNotNull(response);
+ assertEquals("150.00", response.getTotalNotionalAccrual());
+ assertNotNull(response.getAccruals());
+ assertEquals(2, response.getAccruals().length);
+ }
+
+ // ==================== ListInterestAccrualsForPortfolio Tests ====================
+
+ @Test
+ public void testListInterestAccrualsForPortfolioResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"total_notional_accrual\":\"75.50\","
+ + "\"accruals\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"75.50\"}"
+ + "]"
+ + "}";
+
+ ListInterestAccrualsForPortfolioResponse response = objectMapper.readValue(json, ListInterestAccrualsForPortfolioResponse.class);
+ assertNotNull(response);
+ assertEquals("75.50", response.getTotalNotionalAccrual());
+ assertNotNull(response.getAccruals());
+ assertEquals(1, response.getAccruals().length);
+ }
+
+ // ==================== ListMarginCallSummaries Tests ====================
+
+ @Test
+ public void testListMarginCallSummariesRequestSerialization() throws JsonProcessingException {
+ ListMarginCallSummariesRequest request = new ListMarginCallSummariesRequest.Builder()
+ .entityId("entity-123")
+ .startDate("2025-01-01")
+ .endDate("2025-01-31")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"start_date\":\"2025-01-01\""));
+ assertTrue(json.contains("\"end_date\":\"2025-01-31\""));
+ }
+
+ @Test
+ public void testListMarginCallSummariesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"margin_summaries\":["
+ + "{\"date\":\"2025-01-10\",\"state\":\"FCM_MARGIN_CALL_STATE_OK\"},"
+ + "{\"date\":\"2025-01-11\",\"state\":\"FCM_MARGIN_CALL_STATE_CLOSED\"}"
+ + "]"
+ + "}";
+
+ ListMarginCallSummariesResponse response = objectMapper.readValue(json, ListMarginCallSummariesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getMarginSummaries());
+ assertEquals(2, response.getMarginSummaries().length);
+ }
+
+ // ==================== ListMarginConversions Tests ====================
+
+ @Test
+ public void testListMarginConversionsRequestSerialization() throws JsonProcessingException {
+ ListMarginConversionsRequest request = new ListMarginConversionsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .startDate("2025-01-01")
+ .endDate("2025-01-31")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"start_date\":\"2025-01-01\""));
+ assertTrue(json.contains("\"end_date\":\"2025-01-31\""));
+ }
+
+ @Test
+ public void testListMarginConversionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"conversions\":["
+ + "{\"id\":\"conv-1\",\"symbol\":\"BTC\",\"amount\":\"0.5\"},"
+ + "{\"id\":\"conv-2\",\"symbol\":\"ETH\",\"amount\":\"5.0\"}"
+ + "]"
+ + "}";
+
+ ListMarginConversionsResponse response = objectMapper.readValue(json, ListMarginConversionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getConversions());
+ assertEquals(2, response.getConversions().length);
+ }
+
+ // ==================== ListTfObligations Tests ====================
+
+ @Test
+ public void testListTfObligationsRequestConstruction() {
+ ListTfObligationsRequest request = new ListTfObligationsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testListTfObligationsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"obligations\":["
+ + "{\"id\":\"obl-1\",\"symbol\":\"BTC\",\"amount\":\"1.0\"},"
+ + "{\"id\":\"obl-2\",\"symbol\":\"ETH\",\"amount\":\"10.0\"}"
+ + "]"
+ + "}";
+
+ ListTfObligationsResponse response = objectMapper.readValue(json, ListTfObligationsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getObligations());
+ assertEquals(2, response.getObligations().size());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/futures/FuturesServiceSerializationTest.java b/src/test/java/com/coinbase/prime/futures/FuturesServiceSerializationTest.java
new file mode 100644
index 00000000..014d3dcb
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/futures/FuturesServiceSerializationTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.prime.futures;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class FuturesServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== SetAutoSweep Tests ====================
+
+ @Test
+ public void testSetAutoSweepRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ SetAutoSweepRequest request = new SetAutoSweepRequest.Builder()
+ .entityId("entity-123")
+ .autoSweep(true)
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"auto_sweep\":true"));
+ assertFalse(json.contains("entity_id"));
+ }
+
+ @Test
+ public void testSetAutoSweepResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"success\":true}";
+ SetAutoSweepResponse response = objectMapper.readValue(json, SetAutoSweepResponse.class);
+ assertNotNull(response);
+ assertTrue(response.getSuccess());
+ }
+
+ // ==================== GetEntityFcmBalance Tests ====================
+
+ @Test
+ public void testGetEntityFcmBalanceRequestConstruction() throws CoinbaseClientException {
+ GetEntityFcmBalanceRequest request = new GetEntityFcmBalanceRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetEntityFcmBalanceResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"portfolio_id\":\"portfolio-123\","
+ + "\"cfm_usd_balance\":\"100000.00\","
+ + "\"unrealized_pnl\":\"500.00\","
+ + "\"daily_realized_pnl\":\"250.00\","
+ + "\"excess_liquidity\":\"50000.00\","
+ + "\"futures_buying_power\":\"75000.00\","
+ + "\"initial_margin\":\"10000.00\","
+ + "\"maintenance_margin\":\"8000.00\","
+ + "\"clearing_account_id\":\"clr-abc\","
+ + "\"cfm_unsettled_accrued_funding_pnl\":\"10.00\""
+ + "}";
+
+ GetEntityFcmBalanceResponse response = objectMapper.readValue(json, GetEntityFcmBalanceResponse.class);
+ assertNotNull(response);
+ assertEquals("portfolio-123", response.getPortfolioId());
+ assertEquals("100000.00", response.getCfmUsdBalance());
+ assertEquals("500.00", response.getUnrealizedPnl());
+ assertEquals("250.00", response.getDailyRealizedPnl());
+ assertEquals("clr-abc", response.getClearingAccountId());
+ }
+
+ // ==================== GetPositions Tests ====================
+
+ @Test
+ public void testGetPositionsRequestConstruction() throws CoinbaseClientException {
+ GetPositionsRequest request = new GetPositionsRequest.Builder()
+ .entityId("entity-123")
+ .productId("BTC-PERP")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ assertEquals("BTC-PERP", request.getProductId());
+ }
+
+ @Test
+ public void testGetPositionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"positions\":["
+ + "{\"product_id\":\"BTC-PERP\",\"entry_price\":\"50000.00\",\"quantity\":\"0.1\"},"
+ + "{\"product_id\":\"ETH-PERP\",\"entry_price\":\"3000.00\",\"quantity\":\"1.0\"}"
+ + "],"
+ + "\"clearing_account_id\":\"clr-xyz\""
+ + "}";
+
+ GetPositionsResponse response = objectMapper.readValue(json, GetPositionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPositions());
+ assertEquals(2, response.getPositions().length);
+ assertEquals("clr-xyz", response.getClearingAccountId());
+ }
+
+ // ==================== ListEntityFuturesSweeps Tests ====================
+
+ @Test
+ public void testListEntityFuturesSweepsRequestConstruction() throws CoinbaseClientException {
+ ListEntityFuturesSweepsRequest request = new ListEntityFuturesSweepsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testListEntityFuturesSweepsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"sweeps\":["
+ + "{\"id\":\"sweep-1\",\"amount\":\"1000.00\",\"currency\":\"USD\"},"
+ + "{\"id\":\"sweep-2\",\"amount\":\"2000.00\",\"currency\":\"USD\"}"
+ + "],"
+ + "\"auto_sweep\":true"
+ + "}";
+
+ ListEntityFuturesSweepsResponse response = objectMapper.readValue(json, ListEntityFuturesSweepsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getSweeps());
+ assertEquals(2, response.getSweeps().length);
+ assertTrue(response.getAutoSweep());
+ }
+
+ // ==================== CancelEntityFuturesSweep Tests ====================
+
+ @Test
+ public void testCancelEntityFuturesSweepRequestConstruction() throws CoinbaseClientException {
+ CancelEntityFuturesSweepRequest request = new CancelEntityFuturesSweepRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testCancelEntityFuturesSweepResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"success\":true,\"request_id\":\"req-abc123\"}";
+ CancelEntityFuturesSweepResponse response = objectMapper.readValue(json, CancelEntityFuturesSweepResponse.class);
+ assertNotNull(response);
+ assertTrue(response.getSuccess());
+ assertEquals("req-abc123", response.getRequestId());
+ }
+
+ // ==================== ScheduleEntityFuturesSweep Tests ====================
+
+ @Test
+ public void testScheduleEntityFuturesSweepRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ScheduleEntityFuturesSweepRequest request = new ScheduleEntityFuturesSweepRequest();
+ request.setEntityId("entity-123");
+ request.setAmount("5000.00");
+ request.setCurrency("USD");
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"amount\":\"5000.00\""));
+ assertTrue(json.contains("\"currency\":\"USD\""));
+ assertFalse(json.contains("entity_id"));
+ }
+
+ @Test
+ public void testScheduleEntityFuturesSweepResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"success\":true,\"request_id\":\"req-xyz789\"}";
+ ScheduleEntityFuturesSweepResponse response = objectMapper.readValue(json, ScheduleEntityFuturesSweepResponse.class);
+ assertNotNull(response);
+ assertTrue(response.getSuccess());
+ assertEquals("req-xyz789", response.getRequestId());
+ }
+
+ // ==================== GetFcmMarginCallDetails Tests ====================
+
+ @Test
+ public void testGetFcmMarginCallDetailsRequestConstruction() throws CoinbaseClientException {
+ GetFcmMarginCallDetailsRequest request = new GetFcmMarginCallDetailsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetFcmMarginCallDetailsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"margin_call_details\":{"
+ + "\"state\":\"FCM_MARGIN_CALL_STATE_OK\","
+ + "\"portfolio_id\":\"portfolio-123\""
+ + "}"
+ + "}";
+
+ GetFcmMarginCallDetailsResponse response = objectMapper.readValue(json, GetFcmMarginCallDetailsResponse.class);
+ assertNotNull(response);
+ }
+
+ // ==================== GetFcmRiskLimits Tests ====================
+
+ @Test
+ public void testGetFcmRiskLimitsRequestConstruction() throws CoinbaseClientException {
+ GetFcmRiskLimitsRequest request = new GetFcmRiskLimitsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetFcmRiskLimitsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"cfm_risk_limit\":\"100000.00\","
+ + "\"cfm_risk_limit_utilization\":\"0.50\","
+ + "\"cfm_total_margin\":\"50000.00\","
+ + "\"cfm_delta_ote\":\"1000.00\","
+ + "\"cfm_unsettled_realized_pnl\":\"500.00\","
+ + "\"cfm_unsettled_accrued_funding_pnl\":\"25.00\""
+ + "}";
+
+ GetFcmRiskLimitsResponse response = objectMapper.readValue(json, GetFcmRiskLimitsResponse.class);
+ assertNotNull(response);
+ assertEquals("100000.00", response.getCfmRiskLimit());
+ assertEquals("0.50", response.getCfmRiskLimitUtilization());
+ assertEquals("50000.00", response.getCfmTotalMargin());
+ assertEquals("1000.00", response.getCfmDeltaOte());
+ assertEquals("500.00", response.getCfmUnsettledRealizedPnl());
+ assertEquals("25.00", response.getCfmUnsettledAccruedFundingPnl());
+ }
+
+ // ==================== GetFcmSettings Tests ====================
+
+ @Test
+ public void testGetFcmSettingsRequestConstruction() {
+ GetFcmSettingsRequest request = new GetFcmSettingsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetFcmSettingsResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"target_derivatives_excess\":\"10000.00\"}";
+ GetFcmSettingsResponse response = objectMapper.readValue(json, GetFcmSettingsResponse.class);
+ assertNotNull(response);
+ assertEquals("10000.00", response.getTargetDerivativesExcess());
+ }
+
+ // ==================== SetFcmSettings Tests ====================
+
+ @Test
+ public void testSetFcmSettingsRequestSerialization() throws JsonProcessingException {
+ SetFcmSettingsRequest request = new SetFcmSettingsRequest.Builder()
+ .entityId("entity-123")
+ .targetDerivativesExcess("15000.00")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"target_derivatives_excess\":\"15000.00\""));
+ assertFalse(json.contains("entity_id"));
+ }
+
+ @Test
+ public void testSetFcmSettingsResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"success\":true}";
+ SetFcmSettingsResponse response = objectMapper.readValue(json, SetFcmSettingsResponse.class);
+ assertNotNull(response);
+ assertTrue(response.getSuccess());
+ }
+
+ // ==================== GetFcmEquity Tests ====================
+
+ @Test
+ public void testGetFcmEquityRequestConstruction() {
+ GetFcmEquityRequest request = new GetFcmEquityRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testGetFcmEquityResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"available_to_sweep\":\"5000.00\","
+ + "\"current_excess_deficit\":\"2000.00\","
+ + "\"eod_account_equity\":\"105000.00\","
+ + "\"eod_unrealized_pnl\":\"500.00\""
+ + "}";
+
+ GetFcmEquityResponse response = objectMapper.readValue(json, GetFcmEquityResponse.class);
+ assertNotNull(response);
+ assertEquals("5000.00", response.getAvailableToSweep());
+ assertEquals("2000.00", response.getCurrentExcessDeficit());
+ assertEquals("105000.00", response.getEodAccountEquity());
+ assertEquals("500.00", response.getEodUnrealizedPnl());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/ActivitiesIT.java b/src/test/java/com/coinbase/prime/integration/ActivitiesIT.java
new file mode 100644
index 00000000..d6dbf937
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/ActivitiesIT.java
@@ -0,0 +1,86 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.activities.*;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.ActivityCategory;
+import com.coinbase.prime.model.enums.ActivityStatus;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class ActivitiesIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPortfolioActivities() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ ActivitiesService service = PrimeServiceFactory.createActivitiesService(client);
+ ListPortfolioActivitiesResponse response = service.listPortfolioActivities(
+ new ListPortfolioActivitiesRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioActivitiesWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ ActivitiesService service = PrimeServiceFactory.createActivitiesService(client);
+ ListPortfolioActivitiesResponse response = service.listPortfolioActivities(
+ new ListPortfolioActivitiesRequest.Builder()
+ .portfolioId(portfolioId)
+ .symbols(new String[]{"BTC", "ETH"})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListEntityActivities() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ ActivitiesService service = PrimeServiceFactory.createActivitiesService(client);
+ ListEntityActivitiesResponse response = service.listEntityActivities(
+ new ListEntityActivitiesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListEntityActivitiesWithOptionals() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ ActivitiesService service = PrimeServiceFactory.createActivitiesService(client);
+ ListEntityActivitiesResponse response = service.listEntityActivities(
+ new ListEntityActivitiesRequest.Builder()
+ .entityId(entityId)
+ .categories(new ActivityCategory[]{ActivityCategory.ACTIVITY_CATEGORY_ORDER})
+ .statuses(new ActivityStatus[]{ActivityStatus.ACTIVITY_STATUS_COMPLETED})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/AddressBookIT.java b/src/test/java/com/coinbase/prime/integration/AddressBookIT.java
new file mode 100644
index 00000000..22e1e1bf
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/AddressBookIT.java
@@ -0,0 +1,56 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.addressbook.AddressBookService;
+import com.coinbase.prime.addressbook.ListAddressBookRequest;
+import com.coinbase.prime.addressbook.ListAddressBookResponse;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class AddressBookIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListAddressBook() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ AddressBookService service = PrimeServiceFactory.createAddressBookService(client);
+ ListAddressBookResponse response = service.listAddressBook(
+ new ListAddressBookRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListAddressBookWithSearch() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ AddressBookService service = PrimeServiceFactory.createAddressBookService(client);
+ ListAddressBookResponse response = service.listAddressBook(
+ new ListAddressBookRequest.Builder()
+ .portfolioId(portfolioId)
+ .currencySymbol("BTC")
+ .search("")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/AdvancedTransferIT.java b/src/test/java/com/coinbase/prime/integration/AdvancedTransferIT.java
new file mode 100644
index 00000000..6fce7880
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/AdvancedTransferIT.java
@@ -0,0 +1,92 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.advancedtransfer.*;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class AdvancedTransferIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListAdvancedTransfers() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ AdvancedTransferService service = PrimeServiceFactory.createAdvancedTransferService(client);
+ ListAdvancedTransfersResponse response = service.listAdvancedTransfers(
+ new ListAdvancedTransfersRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListAdvancedTransfersWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ AdvancedTransferService service = PrimeServiceFactory.createAdvancedTransferService(client);
+ ListAdvancedTransfersResponse response = service.listAdvancedTransfers(
+ new ListAdvancedTransfersRequest.Builder()
+ .portfolioId(portfolioId)
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPortfolioCounterpartyId() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ AdvancedTransferService service = PrimeServiceFactory.createAdvancedTransferService(client);
+ GetPortfolioCounterpartyIdResponse response = service.getPortfolioCounterpartyId(
+ new GetPortfolioCounterpartyIdRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListAdvancedTransferTransactions() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+
+ AdvancedTransferService service = PrimeServiceFactory.createAdvancedTransferService(client);
+ ListAdvancedTransfersResponse transfers = service.listAdvancedTransfers(
+ new ListAdvancedTransfersRequest.Builder()
+ .portfolioId(portfolioId)
+ .limit(1)
+ .build());
+
+ assumeTrue(transfers != null && transfers.getAdvancedTransfers() != null
+ && transfers.getAdvancedTransfers().length > 0,
+ "Skipping: no advanced transfers found for portfolio");
+
+ String advancedTransferId = transfers.getAdvancedTransfers()[0].getId();
+
+ ListAdvancedTransferTransactionsResponse response = service.listAdvancedTransferTransactions(
+ new ListAdvancedTransferTransactionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .advancedTransferId(advancedTransferId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/AssetsIT.java b/src/test/java/com/coinbase/prime/integration/AssetsIT.java
new file mode 100644
index 00000000..ffef158e
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/AssetsIT.java
@@ -0,0 +1,41 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.assets.AssetsService;
+import com.coinbase.prime.assets.ListAssetsRequest;
+import com.coinbase.prime.assets.ListAssetsResponse;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class AssetsIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListAssets() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ AssetsService service = PrimeServiceFactory.createAssetsService(client);
+ ListAssetsResponse response = service.listAssets(
+ new ListAssetsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/BalancesIT.java b/src/test/java/com/coinbase/prime/integration/BalancesIT.java
new file mode 100644
index 00000000..1c089508
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/BalancesIT.java
@@ -0,0 +1,106 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.balances.*;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.WalletType;
+import com.coinbase.prime.wallets.ListWalletsRequest;
+import com.coinbase.prime.wallets.ListWalletsResponse;
+import com.coinbase.prime.wallets.WalletsService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class BalancesIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListEntityBalances() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ BalancesService service = PrimeServiceFactory.createBalancesService(client);
+ ListEntityBalancesResponse response = service.listEntityBalances(
+ new ListEntityBalancesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListEntityBalancesWithSymbols() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ BalancesService service = PrimeServiceFactory.createBalancesService(client);
+ ListEntityBalancesResponse response = service.listEntityBalances(
+ new ListEntityBalancesRequest.Builder()
+ .entityId(entityId)
+ .symbols(new String[]{"BTC", "ETH", "USDC"})
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioBalances() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ BalancesService service = PrimeServiceFactory.createBalancesService(client);
+ ListPortfolioBalancesResponse response = service.listPortfolioBalances(
+ new ListPortfolioBalancesRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioBalancesWithSymbols() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ BalancesService service = PrimeServiceFactory.createBalancesService(client);
+ ListPortfolioBalancesResponse response = service.listPortfolioBalances(
+ new ListPortfolioBalancesRequest.Builder()
+ .portfolioId(portfolioId)
+ .symbols(new String[]{"BTC", "ETH"})
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListOnchainWalletBalances() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+
+ WalletsService walletsService = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse wallets = walletsService.listWallets(
+ new ListWalletsRequest.Builder()
+ .portfolioId(portfolioId)
+ .type(WalletType.ONCHAIN)
+ .build());
+ assumeTrue(wallets != null && wallets.getWallets() != null && wallets.getWallets().length > 0,
+ "Skipping: no ONCHAIN wallets found for portfolio");
+
+ String walletId = wallets.getWallets()[0].getId();
+
+ BalancesService service = PrimeServiceFactory.createBalancesService(client);
+ ListOnchainWalletBalancesResponse response = service.listOnchainWalletBalances(
+ new ListOnchainWalletBalancesRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/BaseIntegrationTest.java b/src/test/java/com/coinbase/prime/integration/BaseIntegrationTest.java
new file mode 100644
index 00000000..c70a7d0b
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/BaseIntegrationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.client.CoinbasePrimeClient;
+import com.coinbase.prime.credentials.CoinbasePrimeCredentials;
+import org.junit.jupiter.api.BeforeEach;
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Base class for live integration tests. Tests are skipped automatically when
+ * COINBASE_PRIME_CREDENTIALS is not set in the environment.
+ *
+ * Required env vars:
+ * COINBASE_PRIME_CREDENTIALS - JSON with accessKey, passphrase, signingKey
+ * COINBASE_PRIME_PORTFOLIO_ID - portfolio UUID used in portfolio-scoped calls
+ * COINBASE_PRIME_ENTITY_ID - entity UUID used in entity-scoped calls
+ */
+public abstract class BaseIntegrationTest {
+
+ protected CoinbasePrimeClient client;
+ protected String portfolioId;
+ protected String entityId;
+
+ @BeforeEach
+ public void setUpBase() throws Exception {
+ String credentialsJson = System.getenv("COINBASE_PRIME_CREDENTIALS");
+ assumeTrue(credentialsJson != null && !credentialsJson.isEmpty(),
+ "Skipping integration test: COINBASE_PRIME_CREDENTIALS not set");
+
+ portfolioId = System.getenv("COINBASE_PRIME_PORTFOLIO_ID");
+ entityId = System.getenv("COINBASE_PRIME_ENTITY_ID");
+
+ CoinbasePrimeCredentials credentials = new CoinbasePrimeCredentials(credentialsJson);
+ client = new CoinbasePrimeClient(credentials);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/CommissionIT.java b/src/test/java/com/coinbase/prime/integration/CommissionIT.java
new file mode 100644
index 00000000..ebe737d9
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/CommissionIT.java
@@ -0,0 +1,54 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.commission.CommissionService;
+import com.coinbase.prime.commission.GetPortfolioCommissionRequest;
+import com.coinbase.prime.commission.GetPortfolioCommissionResponse;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class CommissionIT extends BaseIntegrationTest {
+
+ @Test
+ public void testGetPortfolioCommission() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ CommissionService service = PrimeServiceFactory.createCommissionService(client);
+ GetPortfolioCommissionResponse response = service.getPortfolioCommission(
+ new GetPortfolioCommissionRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPortfolioCommissionWithProductId() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ CommissionService service = PrimeServiceFactory.createCommissionService(client);
+ GetPortfolioCommissionResponse response = service.getPortfolioCommission(
+ new GetPortfolioCommissionRequest.Builder()
+ .portfolioId(portfolioId)
+ .productId("BTC-USD")
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/FinancingIT.java b/src/test/java/com/coinbase/prime/integration/FinancingIT.java
new file mode 100644
index 00000000..75cdc566
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/FinancingIT.java
@@ -0,0 +1,308 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.financing.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class FinancingIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListInterestAccruals() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListInterestAccrualsResponse response = service.listInterestAccruals(
+ new ListInterestAccrualsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListInterestAccrualsWithDateRange() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListInterestAccrualsResponse response = service.listInterestAccruals(
+ new ListInterestAccrualsRequest.Builder()
+ .entityId(entityId)
+ .startDate("2025-01-01")
+ .endDate("2025-12-31")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetCrossMarginOverview() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetCrossMarginOverviewResponse response = service.getCrossMarginOverview(
+ new GetCrossMarginOverviewRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetEntityLocateAvailabilities() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetEntityLocateAvailabilitiesResponse response = service.getEntityLocateAvailabilities(
+ new GetEntityLocateAvailabilitiesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetMarginInformation() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetMarginInformationResponse response = service.getMarginInformation(
+ new GetMarginInformationRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListMarginCallSummaries() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListMarginCallSummariesResponse response = service.listMarginCallSummaries(
+ new ListMarginCallSummariesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListMarginCallSummariesWithDateRange() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListMarginCallSummariesResponse response = service.listMarginCallSummaries(
+ new ListMarginCallSummariesRequest.Builder()
+ .entityId(entityId)
+ .startDate("2025-01-01")
+ .endDate("2025-12-31")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListTradeFinanceObligations() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListTradeFinanceObligationsResponse response = service.listTradeFinanceObligations(
+ new ListTradeFinanceObligationsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetTradeFinanceTieredPricingFees() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetTradeFinanceTieredPricingFeesResponse response = service.getTradeFinanceTieredPricingFees(
+ new GetTradeFinanceTieredPricingFeesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListFinancingEligibleAssets() throws Exception {
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ ListFinancingEligibleAssetsResponse response = service.listFinancingEligibleAssets();
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListInterestAccrualsForPortfolio() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ try {
+ ListInterestAccrualsForPortfolioResponse response = service.listInterestAccrualsForPortfolio(
+ new ListInterestAccrualsForPortfolioRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01")
+ .endDate("2025-12-31")
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("InvalidArgument") && !causeMsg.contains("not enabled")
+ && !causeMsg.contains("precondition"),
+ "Skipping: portfolio does not have access to interest accruals or date format unsupported");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testListInterestAccrualsForPortfolioWithDateRange() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ try {
+ ListInterestAccrualsForPortfolioResponse response = service.listInterestAccrualsForPortfolio(
+ new ListInterestAccrualsForPortfolioRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01")
+ .endDate("2025-06-30")
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("InvalidArgument") && !causeMsg.contains("not enabled")
+ && !causeMsg.contains("precondition"),
+ "Skipping: portfolio does not have access to interest accruals or date format unsupported");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testGetPortfolioBuyingPower() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetPortfolioBuyingPowerResponse response = service.getPortfolioBuyingPower(
+ new GetPortfolioBuyingPowerRequest.Builder()
+ .portfolioId(portfolioId)
+ .baseCurrency("BTC")
+ .quoteCurrency("USD")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPortfolioCreditInformation() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetPortfolioCreditInformationResponse response = service.getPortfolioCreditInformation(
+ new GetPortfolioCreditInformationRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListExistingLocates() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ try {
+ ListExistingLocatesResponse response = service.listExistingLocates(
+ new ListExistingLocatesRequest.Builder()
+ .portfolioId(portfolioId)
+ .locateDate("2025-01-01")
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("precondition") && !causeMsg.contains("not enabled")
+ && !causeMsg.contains("missing") && !causeMsg.contains("Invalid"),
+ "Skipping: portfolio does not have access to locates");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testListMarginConversions() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ try {
+ ListMarginConversionsResponse response = service.listMarginConversions(
+ new ListMarginConversionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01")
+ .endDate("2025-12-31")
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("InvalidArgument") && !causeMsg.contains("not enabled")
+ && !causeMsg.contains("precondition"),
+ "Skipping: portfolio does not have access to margin conversions or date format unsupported");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testListMarginConversionsWithDateRange() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ try {
+ ListMarginConversionsResponse response = service.listMarginConversions(
+ new ListMarginConversionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01")
+ .endDate("2025-06-30")
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("InvalidArgument") && !causeMsg.contains("not enabled")
+ && !causeMsg.contains("precondition"),
+ "Skipping: portfolio does not have access to margin conversions or date format unsupported");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testGetPortfolioWithdrawalPower() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetPortfolioWithdrawalPowerResponse response = service.getPortfolioWithdrawalPower(
+ new GetPortfolioWithdrawalPowerRequest.Builder()
+ .portfolioId(portfolioId)
+ .symbol("BTC")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPortfolioWithdrawalPowerWithSymbol() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ FinancingService service = PrimeServiceFactory.createFinancingService(client);
+ GetPortfolioWithdrawalPowerResponse response = service.getPortfolioWithdrawalPower(
+ new GetPortfolioWithdrawalPowerRequest.Builder()
+ .portfolioId(portfolioId)
+ .symbol("BTC")
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/FuturesIT.java b/src/test/java/com/coinbase/prime/integration/FuturesIT.java
new file mode 100644
index 00000000..32fd29e3
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/FuturesIT.java
@@ -0,0 +1,124 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.futures.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class FuturesIT extends BaseIntegrationTest {
+
+ @Test
+ public void testGetEntityFcmBalance() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetEntityFcmBalanceResponse response = service.getEntityFcmBalance(
+ new GetEntityFcmBalanceRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetFcmMarginCallDetails() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetFcmMarginCallDetailsResponse response = service.getFcmMarginCallDetails(
+ new GetFcmMarginCallDetailsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPositions() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetPositionsResponse response = service.getPositions(
+ new GetPositionsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPositionsWithProductId() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetPositionsResponse response = service.getPositions(
+ new GetPositionsRequest.Builder()
+ .entityId(entityId)
+ .productId("BTC-28MAR25-CDE")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetFcmRiskLimits() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetFcmRiskLimitsResponse response = service.getFcmRiskLimits(
+ new GetFcmRiskLimitsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetFcmSettings() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetFcmSettingsResponse response = service.getFcmSettings(
+ new GetFcmSettingsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListEntityFuturesSweeps() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ ListEntityFuturesSweepsResponse response = service.listEntityFuturesSweeps(
+ new ListEntityFuturesSweepsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetFcmEquity() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ FuturesService service = PrimeServiceFactory.createFuturesService(client);
+ GetFcmEquityResponse response = service.getFcmEquity(
+ new GetFcmEquityRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/InvoiceIT.java b/src/test/java/com/coinbase/prime/integration/InvoiceIT.java
new file mode 100644
index 00000000..34536723
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/InvoiceIT.java
@@ -0,0 +1,58 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.invoice.InvoiceService;
+import com.coinbase.prime.invoice.ListInvoicesRequest;
+import com.coinbase.prime.invoice.ListInvoicesResponse;
+import com.coinbase.prime.model.enums.InvoiceState;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class InvoiceIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListInvoices() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ InvoiceService service = PrimeServiceFactory.createInvoiceService(client);
+ ListInvoicesResponse response = service.listInvoices(
+ new ListInvoicesRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListInvoicesWithOptionals() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ InvoiceService service = PrimeServiceFactory.createInvoiceService(client);
+ ListInvoicesResponse response = service.listInvoices(
+ new ListInvoicesRequest.Builder()
+ .entityId(entityId)
+ .states(new InvoiceState[]{InvoiceState.INVOICE_STATE_BILLED})
+ .billingYear(2025)
+ .billingMonth(1)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/OnchainAddressBookIT.java b/src/test/java/com/coinbase/prime/integration/OnchainAddressBookIT.java
new file mode 100644
index 00000000..30e99143
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/OnchainAddressBookIT.java
@@ -0,0 +1,41 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.onchainaddressbook.ListOnchainAddressGroupsRequest;
+import com.coinbase.prime.onchainaddressbook.ListOnchainAddressGroupsResponse;
+import com.coinbase.prime.onchainaddressbook.OnchainAddressBookService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class OnchainAddressBookIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListOnchainAddressGroups() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OnchainAddressBookService service = PrimeServiceFactory.createOnchainAddressBookService(client);
+ ListOnchainAddressGroupsResponse response = service.listOnchainAddressGroups(
+ new ListOnchainAddressGroupsRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/OrdersIT.java b/src/test/java/com/coinbase/prime/integration/OrdersIT.java
new file mode 100644
index 00000000..6f8846a5
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/OrdersIT.java
@@ -0,0 +1,115 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.OrderSide;
+import com.coinbase.prime.model.enums.OrderStatus;
+import com.coinbase.prime.orders.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class OrdersIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPortfolioOrders() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListPortfolioOrdersResponse response = service.listPortfolioOrders(
+ new ListPortfolioOrdersRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioOrdersWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListPortfolioOrdersResponse response = service.listPortfolioOrders(
+ new ListPortfolioOrdersRequest.Builder()
+ .portfolioId(portfolioId)
+ .orderStatuses(new OrderStatus[]{OrderStatus.FILLED})
+ .productIds(new String[]{"BTC-USD"})
+ .orderSide(OrderSide.BUY)
+ .startDate("2025-01-01T00:00:00Z")
+ .endDate("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListOpenOrders() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListOpenOrdersResponse response = service.listOpenOrders(
+ new ListOpenOrdersRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListOpenOrdersWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListOpenOrdersResponse response = service.listOpenOrders(
+ new ListOpenOrdersRequest.Builder()
+ .portfolioId(portfolioId)
+ .productIds(new String[]{"BTC-USD", "ETH-USD"})
+ .orderSide(OrderSide.BUY)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioFills() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListPortfolioFillsResponse response = service.listPortfolioFills(
+ new ListPortfolioFillsRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01T00:00:00Z")
+ .endDate("2025-12-31T23:59:59Z")
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioFillsWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ OrdersService service = PrimeServiceFactory.createOrdersService(client);
+ ListPortfolioFillsResponse response = service.listPortfolioFills(
+ new ListPortfolioFillsRequest.Builder()
+ .portfolioId(portfolioId)
+ .startDate("2025-01-01T00:00:00Z")
+ .endDate("2025-06-30T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/PaymentMethodsIT.java b/src/test/java/com/coinbase/prime/integration/PaymentMethodsIT.java
new file mode 100644
index 00000000..cd9db110
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/PaymentMethodsIT.java
@@ -0,0 +1,41 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.paymentmethods.ListPaymentMethodsRequest;
+import com.coinbase.prime.paymentmethods.ListPaymentMethodsResponse;
+import com.coinbase.prime.paymentmethods.PaymentMethodsService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class PaymentMethodsIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPaymentMethods() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ PaymentMethodsService service = PrimeServiceFactory.createPaymentMethodsService(client);
+ ListPaymentMethodsResponse response = service.listPaymentMethods(
+ new ListPaymentMethodsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/PortfoliosIT.java b/src/test/java/com/coinbase/prime/integration/PortfoliosIT.java
new file mode 100644
index 00000000..c6686992
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/PortfoliosIT.java
@@ -0,0 +1,47 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.portfolios.GetPortfolioRequest;
+import com.coinbase.prime.portfolios.GetPortfolioResponse;
+import com.coinbase.prime.portfolios.ListPortfoliosResponse;
+import com.coinbase.prime.portfolios.PortfoliosService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class PortfoliosIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPortfolios() throws Exception {
+ PortfoliosService service = PrimeServiceFactory.createPortfoliosService(client);
+ ListPortfoliosResponse response = service.listPortfolios();
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetPortfolio() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ PortfoliosService service = PrimeServiceFactory.createPortfoliosService(client);
+ GetPortfolioResponse response = service.getPortfolio(
+ new GetPortfolioRequest.Builder().portfolioId(portfolioId).build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/PositionsIT.java b/src/test/java/com/coinbase/prime/integration/PositionsIT.java
new file mode 100644
index 00000000..81647438
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/PositionsIT.java
@@ -0,0 +1,77 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.positions.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class PositionsIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPositions() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ PositionsService service = PrimeServiceFactory.createPositionsService(client);
+ ListPositionsResponse response = service.listPositions(
+ new ListPositionsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPositionsWithPagination() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ PositionsService service = PrimeServiceFactory.createPositionsService(client);
+ ListPositionsResponse response = service.listPositions(
+ new ListPositionsRequest.Builder()
+ .entityId(entityId)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListAggregatePositions() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ PositionsService service = PrimeServiceFactory.createPositionsService(client);
+ ListAggregatePositionsResponse response = service.listAggregatePositions(
+ new ListAggregatePositionsRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListAggregatePositionsWithPagination() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ PositionsService service = PrimeServiceFactory.createPositionsService(client);
+ ListAggregatePositionsResponse response = service.listAggregatePositions(
+ new ListAggregatePositionsRequest.Builder()
+ .entityId(entityId)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/ProductsIT.java b/src/test/java/com/coinbase/prime/integration/ProductsIT.java
new file mode 100644
index 00000000..15bec5a1
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/ProductsIT.java
@@ -0,0 +1,69 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.CandlesGranularity;
+import com.coinbase.prime.products.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class ProductsIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPortfolioProducts() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ ProductsService service = PrimeServiceFactory.createProductsService(client);
+ ListPortfolioProductsResponse response = service.listPortfolioProducts(
+ new ListPortfolioProductsRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioProductsWithPagination() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ ProductsService service = PrimeServiceFactory.createProductsService(client);
+ ListPortfolioProductsResponse response = service.listPortfolioProducts(
+ new ListPortfolioProductsRequest.Builder()
+ .portfolioId(portfolioId)
+ .limit(10)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetCandles() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ ProductsService service = PrimeServiceFactory.createProductsService(client);
+ GetCandlesResponse response = service.getCandles(
+ new GetCandlesRequest.Builder()
+ .portfolioId(portfolioId)
+ .productId("BTC-USD")
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-01-02T00:00:00Z")
+ .granularity(CandlesGranularity.ONE_HOUR)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/StakingIT.java b/src/test/java/com/coinbase/prime/integration/StakingIT.java
new file mode 100644
index 00000000..fa2bb765
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/StakingIT.java
@@ -0,0 +1,99 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.errors.CoinbasePrimeException;
+import com.coinbase.prime.model.enums.WalletType;
+import com.coinbase.prime.staking.GetStakingStatusRequest;
+import com.coinbase.prime.staking.GetStakingStatusResponse;
+import com.coinbase.prime.staking.GetUnstakingStatusRequest;
+import com.coinbase.prime.staking.GetUnstakingStatusResponse;
+import com.coinbase.prime.staking.StakingService;
+import com.coinbase.prime.wallets.ListWalletsRequest;
+import com.coinbase.prime.wallets.ListWalletsResponse;
+import com.coinbase.prime.wallets.WalletsService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class StakingIT extends BaseIntegrationTest {
+
+ /**
+ * Finds a VAULT-type wallet (most likely to support staking).
+ */
+ private String resolveStakingWalletId() throws Exception {
+ WalletsService walletsService = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse wallets = walletsService.listWallets(
+ new ListWalletsRequest.Builder()
+ .portfolioId(portfolioId)
+ .type(WalletType.VAULT)
+ .build());
+ if (wallets == null || wallets.getWallets() == null || wallets.getWallets().length == 0) {
+ return null;
+ }
+ return wallets.getWallets()[0].getId();
+ }
+
+ @Test
+ public void testGetStakingStatus() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveStakingWalletId();
+ assumeTrue(walletId != null, "Skipping: no VAULT wallets found for portfolio");
+
+ StakingService service = PrimeServiceFactory.createStakingService(client);
+ try {
+ GetStakingStatusResponse response = service.getStakingStatus(
+ new GetStakingStatusRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("not supported for staking"),
+ "Skipping: wallet currency does not support staking status via Prime API");
+ throw e;
+ }
+ }
+
+ @Test
+ public void testGetUnstakingStatus() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveStakingWalletId();
+ assumeTrue(walletId != null, "Skipping: no VAULT wallets found for portfolio");
+
+ StakingService service = PrimeServiceFactory.createStakingService(client);
+ try {
+ GetUnstakingStatusResponse response = service.getUnstakingStatus(
+ new GetUnstakingStatusRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ } catch (CoinbaseClientException e) {
+ String causeMsg = e.getCause() != null ? e.getCause().getMessage() : "";
+ assumeTrue(!causeMsg.contains("not supported for unstaking") && !causeMsg.contains("not supported for staking"),
+ "Skipping: wallet currency does not support unstaking status via Prime API");
+ throw e;
+ }
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/TransactionsIT.java b/src/test/java/com/coinbase/prime/integration/TransactionsIT.java
new file mode 100644
index 00000000..3de9d645
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/TransactionsIT.java
@@ -0,0 +1,108 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.TransactionType;
+import com.coinbase.prime.transactions.*;
+import com.coinbase.prime.wallets.ListWalletsRequest;
+import com.coinbase.prime.wallets.ListWalletsResponse;
+import com.coinbase.prime.wallets.WalletsService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class TransactionsIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListPortfolioTransactions() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ TransactionsService service = PrimeServiceFactory.createTransactionsService(client);
+ ListPortfolioTransactionsResponse response = service.listPortfolioTransactions(
+ new ListPortfolioTransactionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioTransactionsWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ TransactionsService service = PrimeServiceFactory.createTransactionsService(client);
+ ListPortfolioTransactionsResponse response = service.listPortfolioTransactions(
+ new ListPortfolioTransactionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .symbols(new String[]{"BTC", "ETH"})
+ .types(new TransactionType[]{TransactionType.WITHDRAWAL, TransactionType.DEPOSIT})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListWalletTransactions() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+
+ WalletsService walletsService = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse wallets = walletsService.listWallets(
+ new ListWalletsRequest.Builder().portfolioId(portfolioId).build());
+ assumeTrue(wallets != null && wallets.getWallets() != null && wallets.getWallets().length > 0,
+ "Skipping: no wallets found for portfolio");
+
+ String walletId = wallets.getWallets()[0].getId();
+
+ TransactionsService service = PrimeServiceFactory.createTransactionsService(client);
+ ListWalletTransactionsResponse response = service.listWalletTransactions(
+ new ListWalletTransactionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListWalletTransactionsWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+
+ WalletsService walletsService = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse wallets = walletsService.listWallets(
+ new ListWalletsRequest.Builder().portfolioId(portfolioId).build());
+ assumeTrue(wallets != null && wallets.getWallets() != null && wallets.getWallets().length > 0,
+ "Skipping: no wallets found for portfolio");
+
+ String walletId = wallets.getWallets()[0].getId();
+
+ TransactionsService service = PrimeServiceFactory.createTransactionsService(client);
+ ListWalletTransactionsResponse response = service.listWalletTransactions(
+ new ListWalletTransactionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .types(new TransactionType[]{TransactionType.WITHDRAWAL})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/UsersIT.java b/src/test/java/com/coinbase/prime/integration/UsersIT.java
new file mode 100644
index 00000000..f3b16790
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/UsersIT.java
@@ -0,0 +1,77 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.users.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class UsersIT extends BaseIntegrationTest {
+
+ @Test
+ public void testListEntityUsers() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ UsersService service = PrimeServiceFactory.createUsersService(client);
+ ListEntityUsersResponse response = service.listEntityUsers(
+ new ListEntityUsersRequest.Builder()
+ .entityId(entityId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListEntityUsersWithPagination() throws Exception {
+ assumeTrue(entityId != null && !entityId.isEmpty(),
+ "Skipping: COINBASE_PRIME_ENTITY_ID not set");
+ UsersService service = PrimeServiceFactory.createUsersService(client);
+ ListEntityUsersResponse response = service.listEntityUsers(
+ new ListEntityUsersRequest.Builder()
+ .entityId(entityId)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioUsers() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ UsersService service = PrimeServiceFactory.createUsersService(client);
+ ListPortfolioUsersResponse response = service.listPortfolioUsers(
+ new ListPortfolioUsersRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListPortfolioUsersWithPagination() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ UsersService service = PrimeServiceFactory.createUsersService(client);
+ ListPortfolioUsersResponse response = service.listPortfolioUsers(
+ new ListPortfolioUsersRequest.Builder()
+ .portfolioId(portfolioId)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/integration/WalletsIT.java b/src/test/java/com/coinbase/prime/integration/WalletsIT.java
new file mode 100644
index 00000000..e9e83bd4
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/integration/WalletsIT.java
@@ -0,0 +1,132 @@
+/*
+ * 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.prime.integration;
+
+import com.coinbase.prime.factory.PrimeServiceFactory;
+import com.coinbase.prime.model.enums.WalletDepositInstructionType;
+import com.coinbase.prime.model.enums.WalletType;
+import com.coinbase.prime.wallets.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class WalletsIT extends BaseIntegrationTest {
+
+ private String resolveWalletId() throws Exception {
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse wallets = service.listWallets(
+ new ListWalletsRequest.Builder().portfolioId(portfolioId).build());
+ if (wallets == null || wallets.getWallets() == null || wallets.getWallets().length == 0) {
+ return null;
+ }
+ return wallets.getWallets()[0].getId();
+ }
+
+ @Test
+ public void testListWallets() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse response = service.listWallets(
+ new ListWalletsRequest.Builder()
+ .portfolioId(portfolioId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListWalletsWithOptionals() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ ListWalletsResponse response = service.listWallets(
+ new ListWalletsRequest.Builder()
+ .portfolioId(portfolioId)
+ .type(WalletType.TRADING)
+ .symbols(new String[]{"BTC", "ETH", "USDC"})
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetWallet() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveWalletId();
+ assumeTrue(walletId != null, "Skipping: no wallets found for portfolio");
+
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ GetWalletResponse response = service.getWallet(
+ new GetWalletRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListWalletAddresses() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveWalletId();
+ assumeTrue(walletId != null, "Skipping: no wallets found for portfolio");
+
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ ListWalletAddressesResponse response = service.listWalletAddresses(
+ new ListWalletAddressesRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testListWalletAddressesWithPagination() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveWalletId();
+ assumeTrue(walletId != null, "Skipping: no wallets found for portfolio");
+
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ ListWalletAddressesResponse response = service.listWalletAddresses(
+ new ListWalletAddressesRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .limit(5)
+ .build());
+ assertNotNull(response);
+ }
+
+ @Test
+ public void testGetWalletDepositInstructions() throws Exception {
+ assumeTrue(portfolioId != null && !portfolioId.isEmpty(),
+ "Skipping: COINBASE_PRIME_PORTFOLIO_ID not set");
+ String walletId = resolveWalletId();
+ assumeTrue(walletId != null, "Skipping: no wallets found for portfolio");
+
+ WalletsService service = PrimeServiceFactory.createWalletsService(client);
+ GetWalletDepositInstructionsResponse response = service.getWalletDepositInstructions(
+ new GetWalletDepositInstructionsRequest.Builder()
+ .portfolioId(portfolioId)
+ .walletId(walletId)
+ .depositType(WalletDepositInstructionType.CRYPTO)
+ .build());
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/invoice/InvoiceServiceSerializationTest.java b/src/test/java/com/coinbase/prime/invoice/InvoiceServiceSerializationTest.java
new file mode 100644
index 00000000..87879d59
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/invoice/InvoiceServiceSerializationTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.prime.invoice;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.model.enums.InvoiceState;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class InvoiceServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ @Test
+ public void testListInvoicesRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListInvoicesRequest request = new ListInvoicesRequest.Builder("entity-123")
+ .states(new InvoiceState[]{InvoiceState.INVOICE_STATE_BILLED, InvoiceState.INVOICE_STATE_PAID})
+ .billingMonth(1)
+ .billingYear(2025)
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"states\""));
+ assertTrue(json.contains("\"billing_month\":1"));
+ assertTrue(json.contains("\"billing_year\":2025"));
+ assertFalse(json.contains("entity_id"));
+ }
+
+ @Test
+ public void testListInvoicesRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListInvoicesRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testListInvoicesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"invoices\":["
+ + "{\"id\":\"inv-1\",\"state\":\"INVOICE_STATE_BILLED\",\"billing_month\":1,\"billing_year\":2025},"
+ + "{\"id\":\"inv-2\",\"state\":\"INVOICE_STATE_PAID\",\"billing_month\":12,\"billing_year\":2024}"
+ + "]"
+ + "}";
+
+ ListInvoicesResponse response = objectMapper.readValue(json, ListInvoicesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getInvoices());
+ assertEquals(2, response.getInvoices().length);
+ assertEquals("inv-1", response.getInvoices()[0].getId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/onchainaddressbook/OnchainAddressBookServiceSerializationTest.java b/src/test/java/com/coinbase/prime/onchainaddressbook/OnchainAddressBookServiceSerializationTest.java
new file mode 100644
index 00000000..024e70ff
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/onchainaddressbook/OnchainAddressBookServiceSerializationTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.prime.onchainaddressbook;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class OnchainAddressBookServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== CreateOnchainAddressBookEntry Tests ====================
+
+ @Test
+ public void testCreateOnchainAddressBookEntryRequestSerialization() throws JsonProcessingException {
+ CreateOnchainAddressBookEntryRequest request = new CreateOnchainAddressBookEntryRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testCreateOnchainAddressBookEntryResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_type\":\"ACTIVITY_TYPE_ADDRESS_BOOK\","
+ + "\"num_approvals_remaining\":1,"
+ + "\"activity_id\":\"act-abc123\""
+ + "}";
+
+ CreateOnchainAddressBookEntryResponse response = objectMapper.readValue(json, CreateOnchainAddressBookEntryResponse.class);
+ assertNotNull(response);
+ assertEquals(1, response.getNumApprovalsRemaining());
+ assertEquals("act-abc123", response.getActivityId());
+ }
+
+ // ==================== DeleteOnchainAddressGroup Tests ====================
+
+ @Test
+ public void testDeleteOnchainAddressGroupRequestConstruction() {
+ DeleteOnchainAddressGroupRequest request = new DeleteOnchainAddressGroupRequest.Builder()
+ .portfolioId("portfolio-123")
+ .addressGroupId("group-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("group-456", request.getAddressGroupId());
+ }
+
+ @Test
+ public void testDeleteOnchainAddressGroupResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_type\":\"ACTIVITY_TYPE_ADDRESS_BOOK\","
+ + "\"num_approvals_remaining\":2,"
+ + "\"activity_id\":\"act-def456\""
+ + "}";
+
+ DeleteOnchainAddressGroupResponse response = objectMapper.readValue(json, DeleteOnchainAddressGroupResponse.class);
+ assertNotNull(response);
+ assertEquals(2, response.getNumApprovalsRemaining());
+ assertEquals("act-def456", response.getActivityId());
+ }
+
+ // ==================== ListOnchainAddressGroups Tests ====================
+
+ @Test
+ public void testListOnchainAddressGroupsRequestConstruction() throws CoinbaseClientException {
+ ListOnchainAddressGroupsRequest request = new ListOnchainAddressGroupsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testListOnchainAddressGroupsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListOnchainAddressGroupsRequest.Builder().build());
+ }
+
+ @Test
+ public void testListOnchainAddressGroupsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"address_groups\":["
+ + "{\"id\":\"group-1\",\"name\":\"Hot Wallets\"},"
+ + "{\"id\":\"group-2\",\"name\":\"Cold Wallets\"}"
+ + "]"
+ + "}";
+
+ ListOnchainAddressGroupsResponse response = objectMapper.readValue(json, ListOnchainAddressGroupsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getAddressGroups());
+ assertEquals(2, response.getAddressGroups().size());
+ }
+
+ // ==================== UpdateOnchainAddressBookEntry Tests ====================
+
+ @Test
+ public void testUpdateOnchainAddressBookEntryRequestSerialization() throws JsonProcessingException {
+ UpdateOnchainAddressBookEntryRequest request = new UpdateOnchainAddressBookEntryRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testUpdateOnchainAddressBookEntryResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_type\":\"ACTIVITY_TYPE_ADDRESS_BOOK\","
+ + "\"num_approvals_remaining\":0,"
+ + "\"activity_id\":\"act-ghi789\""
+ + "}";
+
+ UpdateOnchainAddressBookEntryResponse response = objectMapper.readValue(json, UpdateOnchainAddressBookEntryResponse.class);
+ assertNotNull(response);
+ assertEquals(0, response.getNumApprovalsRemaining());
+ assertEquals("act-ghi789", response.getActivityId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/orders/OrdersServiceSerializationTest.java b/src/test/java/com/coinbase/prime/orders/OrdersServiceSerializationTest.java
index 8d17503f..c36e6439 100644
--- a/src/test/java/com/coinbase/prime/orders/OrdersServiceSerializationTest.java
+++ b/src/test/java/com/coinbase/prime/orders/OrdersServiceSerializationTest.java
@@ -261,7 +261,7 @@ public void testCancelOrderRequestCreation() throws CoinbaseClientException {
@Test
public void testCancelOrderResponseDeserialization() throws JsonProcessingException {
- String json = "{\"order_id\":\"order-789\"}";
+ String json = "{\"id\":\"order-789\"}";
CancelOrderResponse response = objectMapper.readValue(json, CancelOrderResponse.class);
@@ -468,7 +468,7 @@ public void testListOrderEditHistoryResponseDeserialization() throws JsonProcess
assertNotNull(response);
assertEquals("order-456", response.getOrderId());
assertNotNull(response.getEditHistory());
- assertEquals(2, response.getEditHistory().size());
+ assertEquals(2, response.getEditHistory().length);
}
// ==================== Builder Validation Tests ====================
diff --git a/src/test/java/com/coinbase/prime/paymentmethods/PaymentMethodsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/paymentmethods/PaymentMethodsServiceSerializationTest.java
new file mode 100644
index 00000000..3ff080c4
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/paymentmethods/PaymentMethodsServiceSerializationTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.prime.paymentmethods;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class PaymentMethodsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListPaymentMethods Tests ====================
+
+ @Test
+ public void testListPaymentMethodsRequestConstruction() throws CoinbaseClientException {
+ ListPaymentMethodsRequest request = new ListPaymentMethodsRequest.Builder("entity-123").build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testListPaymentMethodsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListPaymentMethodsRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testListPaymentMethodsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"payment_methods\":["
+ + "{\"id\":\"pm-1\",\"name\":\"Wire Transfer\",\"currency\":\"USD\"},"
+ + "{\"id\":\"pm-2\",\"name\":\"ACH Transfer\",\"currency\":\"USD\"}"
+ + "]"
+ + "}";
+
+ ListPaymentMethodsResponse response = objectMapper.readValue(json, ListPaymentMethodsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPaymentMethods());
+ assertEquals(2, response.getPaymentMethods().length);
+ assertEquals("pm-1", response.getPaymentMethods()[0].getId());
+ }
+
+ // ==================== GetPaymentMethodDetails Tests ====================
+
+ @Test
+ public void testGetPaymentMethodDetailsRequestConstruction() throws CoinbaseClientException {
+ GetPaymentMethodDetailsRequest request = new GetPaymentMethodDetailsRequest.Builder()
+ .entityId("entity-123")
+ .paymentMethodId("pm-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ assertEquals("pm-456", request.getPaymentMethodId());
+ }
+
+ @Test
+ public void testGetPaymentMethodDetailsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPaymentMethodDetailsRequest.Builder()
+ .paymentMethodId("pm-456")
+ .build());
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPaymentMethodDetailsRequest.Builder()
+ .entityId("entity-123")
+ .build());
+ }
+
+ @Test
+ public void testGetPaymentMethodDetailsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"details\":{"
+ + "\"id\":\"pm-456\","
+ + "\"name\":\"Wire Transfer\","
+ + "\"currency\":\"USD\","
+ + "\"verified\":true"
+ + "}"
+ + "}";
+
+ GetPaymentMethodDetailsResponse response = objectMapper.readValue(json, GetPaymentMethodDetailsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getDetails());
+ assertEquals("pm-456", response.getDetails().getId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/portfolios/PortfoliosServiceSerializationTest.java b/src/test/java/com/coinbase/prime/portfolios/PortfoliosServiceSerializationTest.java
new file mode 100644
index 00000000..a38b02aa
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/portfolios/PortfoliosServiceSerializationTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.prime.portfolios;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class PortfoliosServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListPortfolios Tests ====================
+
+ @Test
+ public void testListPortfoliosRequestConstruction() {
+ ListPortfoliosRequest request = new ListPortfoliosRequest.Builder().build();
+ assertNotNull(request);
+ }
+
+ @Test
+ public void testListPortfoliosResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"portfolios\":["
+ + "{\"id\":\"port-1\",\"name\":\"Trading Portfolio\",\"entity_id\":\"entity-abc\"},"
+ + "{\"id\":\"port-2\",\"name\":\"Vault Portfolio\",\"entity_id\":\"entity-abc\"}"
+ + "]"
+ + "}";
+
+ ListPortfoliosResponse response = objectMapper.readValue(json, ListPortfoliosResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPortfolios());
+ assertEquals(2, response.getPortfolios().length);
+ assertEquals("port-1", response.getPortfolios()[0].getId());
+ }
+
+ // ==================== GetPortfolio Tests ====================
+
+ @Test
+ public void testGetPortfolioRequestConstruction() throws CoinbaseClientException {
+ GetPortfolioRequest request = new GetPortfolioRequest.Builder("portfolio-123").build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testGetPortfolioRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetPortfolioRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testGetPortfolioResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"portfolio\":{"
+ + "\"id\":\"portfolio-123\","
+ + "\"name\":\"My Portfolio\","
+ + "\"entity_id\":\"entity-xyz\""
+ + "}"
+ + "}";
+
+ GetPortfolioResponse response = objectMapper.readValue(json, GetPortfolioResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPortfolio());
+ assertEquals("portfolio-123", response.getPortfolio().getId());
+ assertEquals("My Portfolio", response.getPortfolio().getName());
+ }
+
+ // ==================== GetPortfolioCounterpartyId Tests ====================
+
+ @Test
+ public void testGetPortfolioCounterpartyIdRequestConstruction() throws CoinbaseClientException {
+ GetPortfolioCounterpartyIdRequest request = new GetPortfolioCounterpartyIdRequest.Builder("portfolio-123").build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testGetPortfolioCounterpartyIdResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"counterparty\":{"
+ + "\"counterparty_id\":\"cpty-abc123\","
+ + "\"name\":\"Counterparty Name\""
+ + "}"
+ + "}";
+
+ GetPortfolioCounterpartyIdResponse response = objectMapper.readValue(json, GetPortfolioCounterpartyIdResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getCounterparty());
+ assertEquals("cpty-abc123", response.getCounterparty().getCounterpartyId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/positions/PositionsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/positions/PositionsServiceSerializationTest.java
new file mode 100644
index 00000000..2c98e2bb
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/positions/PositionsServiceSerializationTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.prime.positions;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class PositionsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListAggregatePositions Tests ====================
+
+ @Test
+ public void testListAggregatePositionsRequestConstruction() throws CoinbaseClientException {
+ ListAggregatePositionsRequest request = new ListAggregatePositionsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getId());
+ }
+
+ @Test
+ public void testListAggregatePositionsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListAggregatePositionsRequest.Builder().build());
+ }
+
+ @Test
+ public void testListAggregatePositionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"positions\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"0.5\",\"cost_basis\":\"25000.00\"},"
+ + "{\"symbol\":\"ETH\",\"amount\":\"5.0\",\"cost_basis\":\"15000.00\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListAggregatePositionsResponse response = objectMapper.readValue(json, ListAggregatePositionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPositions());
+ assertEquals(2, response.getPositions().length);
+ }
+
+ // ==================== ListPositions Tests ====================
+
+ @Test
+ public void testListPositionsRequestConstruction() throws CoinbaseClientException {
+ ListPositionsRequest request = new ListPositionsRequest.Builder()
+ .entityId("entity-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getId());
+ }
+
+ @Test
+ public void testListPositionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"positions\":["
+ + "{\"symbol\":\"BTC\",\"amount\":\"1.0\",\"cost_basis\":\"50000.00\"},"
+ + "{\"symbol\":\"ETH\",\"amount\":\"10.0\",\"cost_basis\":\"30000.00\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"pos-cursor\",\"has_next\":true}"
+ + "}";
+
+ ListPositionsResponse response = objectMapper.readValue(json, ListPositionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getPositions());
+ assertEquals(2, response.getPositions().length);
+ assertNotNull(response.getPagination());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/products/ProductsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/products/ProductsServiceSerializationTest.java
new file mode 100644
index 00000000..b87a0865
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/products/ProductsServiceSerializationTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.prime.products;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.coinbase.prime.model.enums.CandlesGranularity;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ProductsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListCandles Tests ====================
+
+ @Test
+ public void testListCandlesRequestSerialization() throws JsonProcessingException {
+ ListCandlesRequest request = new ListCandlesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .productId("BTC-USD")
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-01-02T00:00:00Z")
+ .granularity(CandlesGranularity.ONE_DAY)
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"product_id\":\"BTC-USD\""));
+ assertTrue(json.contains("\"start_time\":\"2025-01-01T00:00:00Z\""));
+ assertTrue(json.contains("\"end_time\":\"2025-01-02T00:00:00Z\""));
+ assertTrue(json.contains("\"granularity\":\"ONE_DAY\""));
+ assertFalse(json.contains("\"portfolio_id\""));
+ }
+
+ @Test
+ public void testListCandlesRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListCandlesRequest.Builder()
+ .productId("BTC-USD")
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-01-02T00:00:00Z")
+ .granularity(CandlesGranularity.ONE_DAY)
+ .build());
+
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListCandlesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-01-02T00:00:00Z")
+ .granularity(CandlesGranularity.ONE_DAY)
+ .build());
+ }
+
+ @Test
+ public void testListCandlesResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"candles\":["
+ + "{\"start\":\"1704067200\",\"high\":\"50000.00\",\"low\":\"48000.00\",\"open\":\"49000.00\",\"close\":\"49500.00\",\"volume\":\"1000.0\"},"
+ + "{\"start\":\"1704153600\",\"high\":\"51000.00\",\"low\":\"49000.00\",\"open\":\"49500.00\",\"close\":\"50500.00\",\"volume\":\"1200.0\"}"
+ + "]"
+ + "}";
+
+ ListCandlesResponse response = objectMapper.readValue(json, ListCandlesResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getCandles());
+ assertEquals(2, response.getCandles().size());
+ }
+
+ // ==================== ListPortfolioProducts Tests ====================
+
+ @Test
+ public void testListPortfolioProductsRequestConstruction() throws CoinbaseClientException {
+ ListPortfolioProductsRequest request = new ListPortfolioProductsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testListPortfolioProductsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListPortfolioProductsRequest.Builder().build());
+ }
+
+ @Test
+ public void testListPortfolioProductsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"products\":["
+ + "{\"id\":\"BTC-USD\",\"base_currency\":\"BTC\",\"quote_currency\":\"USD\"},"
+ + "{\"id\":\"ETH-USD\",\"base_currency\":\"ETH\",\"quote_currency\":\"USD\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListPortfolioProductsResponse response = objectMapper.readValue(json, ListPortfolioProductsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getProducts());
+ assertEquals(2, response.getProducts().length);
+ assertEquals("BTC-USD", response.getProducts()[0].getId());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/serialization/RequestSerializationTest.java b/src/test/java/com/coinbase/prime/serialization/RequestSerializationTest.java
index e2aa0af2..1e251dc9 100644
--- a/src/test/java/com/coinbase/prime/serialization/RequestSerializationTest.java
+++ b/src/test/java/com/coinbase/prime/serialization/RequestSerializationTest.java
@@ -32,7 +32,6 @@
import com.coinbase.prime.paymentmethods.GetPaymentMethodDetailsRequest;
import com.coinbase.prime.paymentmethods.ListPaymentMethodsRequest;
import com.coinbase.prime.portfolios.GetPortfolioRequest;
-import com.coinbase.prime.portfolios.ListPortfoliosRequest;
import com.coinbase.prime.transactions.GetTransactionRequest;
import com.coinbase.prime.transactions.ListPortfolioTransactionsRequest;
import com.coinbase.prime.wallets.GetWalletRequest;
@@ -50,197 +49,199 @@ public class RequestSerializationTest {
@Test
public void testListPortfolioActivitiesRequestConstruction() {
- ListPortfolioActivitiesRequest request = new ListPortfolioActivitiesRequest.Builder("portfolio-123")
- .build();
+ ListPortfolioActivitiesRequest request =
+ new ListPortfolioActivitiesRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetActivityRequestConstruction() {
- GetActivityRequest request = new GetActivityRequest("activity-456");
+ public void testGetActivityRequestConstruction() throws Exception {
+ GetActivityRequest request =
+ new GetActivityRequest.Builder().activityId("activity-456").build();
assertNotNull(request);
assertEquals("activity-456", request.getActivityId());
}
@Test
- public void testListPortfolioAllocationsRequestConstruction() {
- ListPortfolioAllocationsRequest request = new ListPortfolioAllocationsRequest.Builder("portfolio-789")
- .build();
+ public void testListPortfolioAllocationsRequestConstruction() throws Exception {
+ ListPortfolioAllocationsRequest request =
+ new ListPortfolioAllocationsRequest.Builder().portfolioId("portfolio-789").build();
assertNotNull(request);
assertEquals("portfolio-789", request.getPortfolioId());
}
@Test
- public void testGetAllocationRequestConstruction() {
- GetAllocationRequest request = new GetAllocationRequest.Builder("portfolio-123", "allocation-456")
- .build();
+ public void testGetAllocationRequestConstruction() throws Exception {
+ GetAllocationRequest request =
+ new GetAllocationRequest.Builder()
+ .portfolioId("portfolio-123")
+ .allocationId("allocation-456")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
assertEquals("allocation-456", request.getAllocationId());
}
@Test
- public void testListAssetsRequestConstruction() {
- ListAssetsRequest request = new ListAssetsRequest.Builder("entity-123")
- .build();
+ public void testListAssetsRequestConstruction() throws Exception {
+ ListAssetsRequest request = new ListAssetsRequest.Builder().entityId("entity-123").build();
assertNotNull(request);
assertEquals("entity-123", request.getEntityId());
}
@Test
- public void testListPortfolioBalancesRequestConstruction() {
- ListPortfolioBalancesRequest request = new ListPortfolioBalancesRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testListPortfolioBalancesRequestConstruction() throws Exception {
+ ListPortfolioBalancesRequest request =
+ new ListPortfolioBalancesRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetWalletBalanceRequestConstruction() {
- GetWalletBalanceRequest request = new GetWalletBalanceRequest.Builder()
- .portfolioId("portfolio-123")
- .walletId("wallet-456")
- .build();
+ public void testGetWalletBalanceRequestConstruction() throws Exception {
+ GetWalletBalanceRequest request =
+ new GetWalletBalanceRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
assertEquals("wallet-456", request.getWalletId());
}
@Test
- public void testGetPortfolioCommissionRequestConstruction() {
- GetPortfolioCommissionRequest request = new GetPortfolioCommissionRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testGetPortfolioCommissionRequestConstruction() throws Exception {
+ GetPortfolioCommissionRequest request =
+ new GetPortfolioCommissionRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetMarginInformationRequestConstruction() {
- GetMarginInformationRequest request = new GetMarginInformationRequest.Builder()
- .entityId("entity-123")
- .build();
+ public void testGetMarginInformationRequestConstruction() throws Exception {
+ GetMarginInformationRequest request =
+ new GetMarginInformationRequest.Builder().entityId("entity-123").build();
assertNotNull(request);
assertEquals("entity-123", request.getEntityId());
}
@Test
- public void testListExistingLocatesRequestConstruction() {
- ListExistingLocatesRequest request = new ListExistingLocatesRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testListExistingLocatesRequestConstruction() throws Exception {
+ ListExistingLocatesRequest request =
+ new ListExistingLocatesRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetEntityFcmBalanceRequestConstruction() {
- GetEntityFcmBalanceRequest request = new GetEntityFcmBalanceRequest.Builder()
- .entityId("entity-123")
- .build();
+ public void testGetEntityFcmBalanceRequestConstruction() throws Exception {
+ GetEntityFcmBalanceRequest request =
+ new GetEntityFcmBalanceRequest.Builder().entityId("entity-123").build();
assertNotNull(request);
assertEquals("entity-123", request.getEntityId());
}
@Test
- public void testListOpenOrdersRequestConstruction() {
- ListOpenOrdersRequest request = new ListOpenOrdersRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testListOpenOrdersRequestConstruction() throws Exception {
+ ListOpenOrdersRequest request =
+ new ListOpenOrdersRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetOrderByOrderIdRequestConstruction() {
- GetOrderByOrderIdRequest request = new GetOrderByOrderIdRequest.Builder()
- .portfolioId("portfolio-123")
- .orderId("order-456")
- .build();
+ public void testGetOrderByOrderIdRequestConstruction() throws Exception {
+ GetOrderByOrderIdRequest request =
+ new GetOrderByOrderIdRequest.Builder()
+ .portfolioId("portfolio-123")
+ .orderId("order-456")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
assertEquals("order-456", request.getOrderId());
}
@Test
- public void testListPaymentMethodsRequestConstruction() {
- ListPaymentMethodsRequest request = new ListPaymentMethodsRequest.Builder("entity-123")
- .build();
+ public void testListPaymentMethodsRequestConstruction() throws Exception {
+ ListPaymentMethodsRequest request =
+ new ListPaymentMethodsRequest.Builder().entityId("entity-123").build();
assertNotNull(request);
assertEquals("entity-123", request.getEntityId());
}
@Test
- public void testGetPaymentMethodDetailsRequestConstruction() {
- GetPaymentMethodDetailsRequest request = new GetPaymentMethodDetailsRequest.Builder()
- .entityId("entity-123")
- .paymentMethodId("payment-456")
- .build();
+ public void testGetPaymentMethodDetailsRequestConstruction() throws Exception {
+ GetPaymentMethodDetailsRequest request =
+ new GetPaymentMethodDetailsRequest.Builder()
+ .entityId("entity-123")
+ .paymentMethodId("payment-456")
+ .build();
assertNotNull(request);
assertEquals("entity-123", request.getEntityId());
assertEquals("payment-456", request.getPaymentMethodId());
}
@Test
- public void testListPortfoliosRequestConstruction() {
- ListPortfoliosRequest request = new ListPortfoliosRequest.Builder().build();
- assertNotNull(request);
- }
-
- @Test
- public void testGetPortfolioRequestConstruction() {
- GetPortfolioRequest request = new GetPortfolioRequest.Builder("portfolio-123")
- .build();
+ public void testGetPortfolioRequestConstruction() throws Exception {
+ GetPortfolioRequest request =
+ new GetPortfolioRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetTransactionRequestConstruction() {
- GetTransactionRequest request = new GetTransactionRequest.Builder("portfolio-123", "txn-456")
- .build();
+ public void testGetTransactionRequestConstruction() throws Exception {
+ GetTransactionRequest request =
+ new GetTransactionRequest.Builder()
+ .portfolioId("portfolio-123")
+ .transactionId("txn-456")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
assertEquals("txn-456", request.getTransactionId());
}
@Test
- public void testListPortfolioTransactionsRequestConstruction() {
- ListPortfolioTransactionsRequest request = new ListPortfolioTransactionsRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testListPortfolioTransactionsRequestConstruction() throws Exception {
+ ListPortfolioTransactionsRequest request =
+ new ListPortfolioTransactionsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testListWalletsRequestConstruction() {
- ListWalletsRequest request = new ListWalletsRequest.Builder()
- .portfolioId("portfolio-123")
- .build();
+ public void testListWalletsRequestConstruction() throws Exception {
+ ListWalletsRequest request =
+ new ListWalletsRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
}
@Test
- public void testGetWalletRequestConstruction() {
- GetWalletRequest request = new GetWalletRequest.Builder("portfolio-123", "wallet-456")
- .build();
+ public void testGetWalletRequestConstruction() throws Exception {
+ GetWalletRequest request =
+ new GetWalletRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
assertEquals("wallet-456", request.getWalletId());
}
@Test
- public void testRequestWithOptionalFieldsBuildsSuccessfully() {
- // Verify requests can be built without setting optional fields
- ListPortfolioActivitiesRequest request = new ListPortfolioActivitiesRequest.Builder("portfolio-123")
- .build();
+ public void testRequestWithOptionalFieldsBuildsSuccessfully() throws Exception {
+ ListPortfolioActivitiesRequest request =
+ new ListPortfolioActivitiesRequest.Builder().portfolioId("portfolio-123").build();
assertNotNull(request);
assertEquals("portfolio-123", request.getPortfolioId());
- // Optional fields are null or have default values, which is expected
}
}
diff --git a/src/test/java/com/coinbase/prime/staking/StakingServiceSerializationTest.java b/src/test/java/com/coinbase/prime/staking/StakingServiceSerializationTest.java
new file mode 100644
index 00000000..be980879
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/staking/StakingServiceSerializationTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.prime.staking;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class StakingServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ClaimRewards Tests ====================
+
+ @Test
+ public void testClaimRewardsRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ClaimRewardsRequest request = new ClaimRewardsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .idempotencyKey("idem-key-abc")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"idempotency_key\":\"idem-key-abc\""));
+ assertFalse(json.contains("portfolio_id"));
+ assertFalse(json.contains("wallet_id"));
+ }
+
+ @Test
+ public void testClaimRewardsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ClaimRewardsRequest.Builder()
+ .walletId("wallet-456")
+ .idempotencyKey("idem-key")
+ .build());
+ }
+
+ @Test
+ public void testClaimRewardsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"wallet_id\":\"wallet-456\","
+ + "\"transaction_id\":\"txn-abc123\","
+ + "\"activity_id\":\"act-xyz789\""
+ + "}";
+
+ ClaimRewardsResponse response = objectMapper.readValue(json, ClaimRewardsResponse.class);
+ assertNotNull(response);
+ assertEquals("wallet-456", response.getWalletId());
+ assertEquals("txn-abc123", response.getTransactionId());
+ assertEquals("act-xyz789", response.getActivityId());
+ }
+
+ // ==================== CreateStake Tests ====================
+
+ @Test
+ public void testCreateStakeRequestSerialization() throws JsonProcessingException {
+ CreateStakeRequest request = new CreateStakeRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .idempotencyKey("idem-key-def")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"idempotency_key\":\"idem-key-def\""));
+ assertFalse(json.contains("portfolio_id"));
+ assertFalse(json.contains("wallet_id"));
+ }
+
+ @Test
+ public void testCreateStakeResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"wallet_id\":\"wallet-456\","
+ + "\"transaction_id\":\"txn-stake-1\","
+ + "\"activity_id\":\"act-stake-1\""
+ + "}";
+
+ CreateStakeResponse response = objectMapper.readValue(json, CreateStakeResponse.class);
+ assertNotNull(response);
+ assertEquals("wallet-456", response.getWalletId());
+ assertEquals("txn-stake-1", response.getTransactionId());
+ assertEquals("act-stake-1", response.getActivityId());
+ }
+
+ // ==================== CreateUnstake Tests ====================
+
+ @Test
+ public void testCreateUnstakeResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"wallet_id\":\"wallet-456\","
+ + "\"transaction_id\":\"txn-unstake-1\","
+ + "\"activity_id\":\"act-unstake-1\""
+ + "}";
+
+ CreateUnstakeResponse response = objectMapper.readValue(json, CreateUnstakeResponse.class);
+ assertNotNull(response);
+ assertEquals("wallet-456", response.getWalletId());
+ assertEquals("txn-unstake-1", response.getTransactionId());
+ assertEquals("act-unstake-1", response.getActivityId());
+ }
+
+ // ==================== PortfolioStakingInitiate Tests ====================
+
+ @Test
+ public void testPortfolioStakingInitiateRequestSerialization() throws JsonProcessingException {
+ PortfolioStakingInitiateRequest request = new PortfolioStakingInitiateRequest.Builder()
+ .portfolioId("portfolio-123")
+ .idempotencyKey("idem-init-1")
+ .currencySymbol("ETH")
+ .amount("10.0")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"idempotency_key\":\"idem-init-1\""));
+ assertTrue(json.contains("\"currency_symbol\":\"ETH\""));
+ assertTrue(json.contains("\"amount\":\"10.0\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testPortfolioStakingInitiateResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-init-1\","
+ + "\"transaction_id\":\"txn-init-1\""
+ + "}";
+
+ PortfolioStakingInitiateResponse response = objectMapper.readValue(json, PortfolioStakingInitiateResponse.class);
+ assertNotNull(response);
+ assertEquals("act-init-1", response.getActivityId());
+ assertEquals("txn-init-1", response.getTransactionId());
+ }
+
+ // ==================== PortfolioStakingUnstake Tests ====================
+
+ @Test
+ public void testPortfolioStakingUnstakeRequestSerialization() throws JsonProcessingException {
+ PortfolioStakingUnstakeRequest request = new PortfolioStakingUnstakeRequest.Builder()
+ .portfolioId("portfolio-123")
+ .idempotencyKey("idem-unstake-1")
+ .currencySymbol("ETH")
+ .amount("5.0")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"idempotency_key\":\"idem-unstake-1\""));
+ assertTrue(json.contains("\"currency_symbol\":\"ETH\""));
+ assertTrue(json.contains("\"amount\":\"5.0\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testPortfolioStakingUnstakeResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-unstake-1\","
+ + "\"transaction_id\":\"txn-unstake-1\""
+ + "}";
+
+ PortfolioStakingUnstakeResponse response = objectMapper.readValue(json, PortfolioStakingUnstakeResponse.class);
+ assertNotNull(response);
+ assertEquals("act-unstake-1", response.getActivityId());
+ assertEquals("txn-unstake-1", response.getTransactionId());
+ }
+
+ // ==================== PreviewUnstake Tests ====================
+
+ @Test
+ public void testPreviewUnstakeRequestSerialization() throws JsonProcessingException {
+ PreviewUnstakeRequest request = new PreviewUnstakeRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .amount("5.0")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"amount\":\"5.0\""));
+ assertFalse(json.contains("portfolio_id"));
+ assertFalse(json.contains("wallet_id"));
+ }
+
+ @Test
+ public void testPreviewUnstakeResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"estimated_amount\":\"4.9\"}";
+ PreviewUnstakeResponse response = objectMapper.readValue(json, PreviewUnstakeResponse.class);
+ assertNotNull(response);
+ assertEquals("4.9", response.getEstimatedAmount());
+ }
+
+ // ==================== GetStakingStatus Tests ====================
+
+ @Test
+ public void testGetStakingStatusRequestConstruction() {
+ GetStakingStatusRequest request = new GetStakingStatusRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ }
+
+ @Test
+ public void testGetStakingStatusResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"portfolio_id\":\"portfolio-123\","
+ + "\"wallet_id\":\"wallet-456\","
+ + "\"wallet_address\":\"0xabc123\","
+ + "\"current_timestamp\":\"2025-01-01T00:00:00Z\","
+ + "\"validators\":["
+ + "{\"public_key\":\"0xpubkey1\",\"status\":\"active\"}"
+ + "]"
+ + "}";
+
+ GetStakingStatusResponse response = objectMapper.readValue(json, GetStakingStatusResponse.class);
+ assertNotNull(response);
+ assertEquals("portfolio-123", response.getPortfolioId());
+ assertEquals("wallet-456", response.getWalletId());
+ assertEquals("0xabc123", response.getWalletAddress());
+ assertNotNull(response.getValidators());
+ assertEquals(1, response.getValidators().size());
+ }
+
+ // ==================== GetUnstakingStatus Tests ====================
+
+ @Test
+ public void testGetUnstakingStatusRequestConstruction() {
+ GetUnstakingStatusRequest request = new GetUnstakingStatusRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ }
+
+ @Test
+ public void testGetUnstakingStatusResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"portfolio_id\":\"portfolio-123\","
+ + "\"wallet_id\":\"wallet-456\","
+ + "\"wallet_address\":\"0xabc123\","
+ + "\"current_timestamp\":\"2025-01-01T00:00:00Z\","
+ + "\"validators\":["
+ + "{\"public_key\":\"0xpubkey1\",\"exit_epoch\":\"100\"}"
+ + "]"
+ + "}";
+
+ GetUnstakingStatusResponse response = objectMapper.readValue(json, GetUnstakingStatusResponse.class);
+ assertNotNull(response);
+ assertEquals("portfolio-123", response.getPortfolioId());
+ assertEquals("wallet-456", response.getWalletId());
+ assertNotNull(response.getValidators());
+ assertEquals(1, response.getValidators().size());
+ }
+
+ // ==================== ListTransactionValidators Tests ====================
+
+ @Test
+ public void testListTransactionValidatorsRequestSerialization() throws JsonProcessingException {
+ ListTransactionValidatorsRequest request = new ListTransactionValidatorsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .transactionIds(java.util.Arrays.asList("txn-1", "txn-2"))
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"transaction_ids\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testListTransactionValidatorsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"transaction_validators\":["
+ + "{\"transaction_id\":\"txn-1\",\"validators\":[{\"public_key\":\"0xpub1\"}]},"
+ + "{\"transaction_id\":\"txn-2\",\"validators\":[{\"public_key\":\"0xpub2\"}]}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListTransactionValidatorsResponse response = objectMapper.readValue(json, ListTransactionValidatorsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getTransactionValidators());
+ assertEquals(2, response.getTransactionValidators().size());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/transactions/TransactionsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/transactions/TransactionsServiceSerializationTest.java
new file mode 100644
index 00000000..8ab58850
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/transactions/TransactionsServiceSerializationTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.prime.transactions;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TransactionsServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListPortfolioTransactions Tests ====================
+
+ @Test
+ public void testListPortfolioTransactionsRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListPortfolioTransactionsRequest request = new ListPortfolioTransactionsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbols(new String[]{"BTC", "ETH"})
+ .startTime("2025-01-01T00:00:00Z")
+ .endTime("2025-12-31T23:59:59Z")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbols\""));
+ assertTrue(json.contains("\"start_time\":\"2025-01-01T00:00:00Z\""));
+ assertTrue(json.contains("\"end_time\":\"2025-12-31T23:59:59Z\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testListPortfolioTransactionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"transactions\":["
+ + "{\"id\":\"txn-1\",\"type\":\"DEPOSIT\",\"status\":\"TRANSACTION_DONE\"},"
+ + "{\"id\":\"txn-2\",\"type\":\"WITHDRAWAL\",\"status\":\"TRANSACTION_PROCESSING\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"txn-cursor\",\"has_next\":true}"
+ + "}";
+
+ ListPortfolioTransactionsResponse response = objectMapper.readValue(json, ListPortfolioTransactionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getTransactions());
+ assertEquals(2, response.getTransactions().length);
+ assertNotNull(response.getPagination());
+ }
+
+ // ==================== GetTransaction Tests ====================
+
+ @Test
+ public void testGetTransactionRequestConstruction() throws CoinbaseClientException {
+ GetTransactionRequest request = new GetTransactionRequest.Builder("portfolio-123", "txn-456").build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("txn-456", request.getTransactionId());
+ }
+
+ @Test
+ public void testGetTransactionResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"transaction\":{"
+ + "\"id\":\"txn-456\","
+ + "\"type\":\"DEPOSIT\","
+ + "\"status\":\"TRANSACTION_DONE\","
+ + "\"amount\":\"1.5\","
+ + "\"symbol\":\"BTC\""
+ + "}"
+ + "}";
+
+ GetTransactionResponse response = objectMapper.readValue(json, GetTransactionResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getTransaction());
+ assertEquals("txn-456", response.getTransaction().getId());
+ }
+
+ // ==================== CreateConversion Tests ====================
+
+ @Test
+ public void testCreateConversionRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ CreateConversionRequest request = new CreateConversionRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .amount("100.0")
+ .destination("wallet-789")
+ .sourceSymbol("USD")
+ .destinationSymbol("USDC")
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"amount\":\"100.0\""));
+ assertTrue(json.contains("\"destination\":\"wallet-789\""));
+ assertTrue(json.contains("\"source_symbol\":\"USD\""));
+ assertTrue(json.contains("\"destination_symbol\":\"USDC\""));
+ assertFalse(json.contains("\"portfolio_id\""));
+ assertFalse(json.contains("\"wallet_id\""));
+ }
+
+ @Test
+ public void testCreateConversionResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-conv-1\","
+ + "\"source_symbol\":\"USD\","
+ + "\"destination_symbol\":\"USDC\","
+ + "\"amount\":\"100.0\","
+ + "\"destination\":\"wallet-789\","
+ + "\"source\":\"wallet-456\","
+ + "\"transaction_id\":\"txn-conv-1\""
+ + "}";
+
+ CreateConversionResponse response = objectMapper.readValue(json, CreateConversionResponse.class);
+ assertNotNull(response);
+ assertEquals("act-conv-1", response.getActivityId());
+ assertEquals("USD", response.getSourceSymbol());
+ assertEquals("USDC", response.getDestinationSymbol());
+ assertEquals("txn-conv-1", response.getTransactionId());
+ }
+
+ // ==================== CreateOnchainTransaction Tests ====================
+
+ @Test
+ public void testCreateOnchainTransactionRequestSerialization() throws JsonProcessingException, com.coinbase.core.errors.CoinbaseClientException {
+ com.coinbase.prime.model.RpcConfig rpc = new com.coinbase.prime.model.RpcConfig.Builder()
+ .url("https://rpc.example.com")
+ .build();
+ CreateOnchainTransactionRequest request = new CreateOnchainTransactionRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .rawUnsignedTxn("0xrawunsignedtxn")
+ .rpc(rpc)
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"raw_unsigned_txn\":\"0xrawunsignedtxn\""));
+ assertFalse(json.contains("\"portfolio_id\""));
+ assertFalse(json.contains("\"wallet_id\""));
+ }
+
+ @Test
+ public void testCreateOnchainTransactionResponseDeserialization() throws JsonProcessingException {
+ String json = "{\"transaction_id\":\"txn-onchain-1\"}";
+ CreateOnchainTransactionResponse response = objectMapper.readValue(json, CreateOnchainTransactionResponse.class);
+ assertNotNull(response);
+ assertEquals("txn-onchain-1", response.getTransactionId());
+ }
+
+ // ==================== ListWalletTransactions Tests ====================
+
+ @Test
+ public void testListWalletTransactionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"transactions\":["
+ + "{\"id\":\"txn-w1\",\"type\":\"DEPOSIT\",\"status\":\"TRANSACTION_DONE\"},"
+ + "{\"id\":\"txn-w2\",\"type\":\"WITHDRAWAL\",\"status\":\"TRANSACTION_PROCESSING\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListWalletTransactionsResponse response = objectMapper.readValue(json, ListWalletTransactionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getTransactions());
+ assertEquals(2, response.getTransactions().length);
+ }
+
+ // ==================== CreateWalletTransfer Tests ====================
+
+ @Test
+ public void testCreateWalletTransferResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-transfer-1\","
+ + "\"source_symbol\":\"BTC\","
+ + "\"destination_symbol\":\"BTC\","
+ + "\"amount\":\"0.5\","
+ + "\"destination\":\"wallet-dest\","
+ + "\"source\":\"wallet-src\","
+ + "\"transaction_id\":\"txn-transfer-1\""
+ + "}";
+
+ CreateWalletTransferResponse response = objectMapper.readValue(json, CreateWalletTransferResponse.class);
+ assertNotNull(response);
+ assertEquals("act-transfer-1", response.getActivityId());
+ assertEquals("txn-transfer-1", response.getTransactionId());
+ }
+
+ // ==================== CreateWalletWithdrawal Tests ====================
+
+ @Test
+ public void testCreateWalletWithdrawalResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-withdraw-1\","
+ + "\"source_symbol\":\"BTC\","
+ + "\"destination_symbol\":\"BTC\","
+ + "\"amount\":\"0.25\","
+ + "\"destination\":\"bc1qexternal\","
+ + "\"source\":\"wallet-src\","
+ + "\"transaction_id\":\"txn-withdraw-1\""
+ + "}";
+
+ CreateWalletWithdrawalResponse response = objectMapper.readValue(json, CreateWalletWithdrawalResponse.class);
+ assertNotNull(response);
+ assertEquals("act-withdraw-1", response.getActivityId());
+ assertEquals("txn-withdraw-1", response.getTransactionId());
+ }
+
+ // ==================== ListAdvancedTransferTransactions Tests ====================
+
+ @Test
+ public void testListAdvancedTransferTransactionsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"transactions\":["
+ + "{\"id\":\"txn-at1\",\"type\":\"WITHDRAWAL\",\"status\":\"TRANSACTION_DONE\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListAdvancedTransferTransactionsResponse response = objectMapper.readValue(json, ListAdvancedTransferTransactionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getTransactions());
+ assertEquals(1, response.getTransactions().length);
+ }
+
+ // ==================== GetTransactionTravelRuleData Tests ====================
+
+ @Test
+ public void testGetTransactionTravelRuleDataResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"originator_name\":\"John Doe\","
+ + "\"originator_account_number\":\"acc-123\","
+ + "\"beneficiary_name\":\"Jane Smith\","
+ + "\"beneficiary_account_number\":\"acc-456\""
+ + "}";
+
+ GetTransactionTravelRuleDataResponse response = objectMapper.readValue(json, GetTransactionTravelRuleDataResponse.class);
+ assertNotNull(response);
+ }
+
+ // ==================== SubmitDepositTravelRuleData Tests ====================
+
+ @Test
+ public void testSubmitDepositTravelRuleDataResponseDeserialization() throws JsonProcessingException {
+ String json = "{}";
+ SubmitDepositTravelRuleDataResponse response = objectMapper.readValue(json, SubmitDepositTravelRuleDataResponse.class);
+ assertNotNull(response);
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/users/UsersServiceSerializationTest.java b/src/test/java/com/coinbase/prime/users/UsersServiceSerializationTest.java
new file mode 100644
index 00000000..0cb89572
--- /dev/null
+++ b/src/test/java/com/coinbase/prime/users/UsersServiceSerializationTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.prime.users;
+
+import com.coinbase.core.errors.CoinbaseClientException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UsersServiceSerializationTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ public void setUp() {
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ // ==================== ListEntityUsers Tests ====================
+
+ @Test
+ public void testListEntityUsersRequestConstruction() throws CoinbaseClientException {
+ ListEntityUsersRequest request = new ListEntityUsersRequest.Builder("entity-123").build();
+ assertNotNull(request);
+ assertEquals("entity-123", request.getEntityId());
+ }
+
+ @Test
+ public void testListEntityUsersRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListEntityUsersRequest.Builder(null).build());
+ }
+
+ @Test
+ public void testListEntityUsersResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"users\":["
+ + "{\"id\":\"user-1\",\"name\":\"Alice Smith\",\"role\":\"ADMIN\"},"
+ + "{\"id\":\"user-2\",\"name\":\"Bob Jones\",\"role\":\"AUDITOR\"}"
+ + "],"
+ + "\"pagination\":{\"has_next\":false}"
+ + "}";
+
+ ListEntityUsersResponse response = objectMapper.readValue(json, ListEntityUsersResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getUsers());
+ assertEquals(2, response.getUsers().length);
+ assertEquals("user-1", response.getUsers()[0].getId());
+ }
+
+ // ==================== ListPortfolioUsers Tests ====================
+
+ @Test
+ public void testListPortfolioUsersRequestConstruction() throws CoinbaseClientException {
+ ListPortfolioUsersRequest request = new ListPortfolioUsersRequest.Builder()
+ .portfolioId("portfolio-123")
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ }
+
+ @Test
+ public void testListPortfolioUsersRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListPortfolioUsersRequest.Builder().build());
+ }
+
+ @Test
+ public void testListPortfolioUsersResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"users\":["
+ + "{\"id\":\"user-3\",\"name\":\"Carol White\",\"role\":\"TRADER\"},"
+ + "{\"id\":\"user-4\",\"name\":\"Dave Brown\",\"role\":\"AUDITOR\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"user-cursor\",\"has_next\":true}"
+ + "}";
+
+ ListPortfolioUsersResponse response = objectMapper.readValue(json, ListPortfolioUsersResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getUsers());
+ assertEquals(2, response.getUsers().length);
+ assertNotNull(response.getPagination());
+ }
+}
diff --git a/src/test/java/com/coinbase/prime/wallets/WalletsServiceSerializationTest.java b/src/test/java/com/coinbase/prime/wallets/WalletsServiceSerializationTest.java
index 05a706ec..1854e4bc 100644
--- a/src/test/java/com/coinbase/prime/wallets/WalletsServiceSerializationTest.java
+++ b/src/test/java/com/coinbase/prime/wallets/WalletsServiceSerializationTest.java
@@ -18,6 +18,7 @@
import com.coinbase.core.errors.CoinbaseClientException;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -33,7 +34,7 @@ public class WalletsServiceSerializationTest {
@BeforeEach
public void setUp() {
- objectMapper = new ObjectMapper();
+ objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
// ==================== CreateWalletDepositAddress Tests ====================
@@ -145,4 +146,181 @@ public void testListWalletAddressesResponseDeserialization() throws JsonProcessi
assertEquals("0xabc123", response.getAddresses().get(0).getAddress());
assertEquals("0xdef456", response.getAddresses().get(1).getAddress());
}
+
+ // ==================== ListWallets Tests ====================
+
+ @Test
+ public void testListWalletsRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ ListWalletsRequest request = new ListWalletsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbols(new String[]{"BTC", "ETH"})
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"symbols\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testListWalletsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new ListWalletsRequest.Builder().build());
+ }
+
+ @Test
+ public void testListWalletsResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"wallets\":["
+ + "{\"id\":\"wallet-1\",\"name\":\"BTC Trading\",\"symbol\":\"BTC\",\"type\":\"TRADING\"},"
+ + "{\"id\":\"wallet-2\",\"name\":\"ETH Vault\",\"symbol\":\"ETH\",\"type\":\"VAULT\"}"
+ + "],"
+ + "\"pagination\":{\"next_cursor\":\"wallet-cursor\",\"has_next\":true}"
+ + "}";
+
+ ListWalletsResponse response = objectMapper.readValue(json, ListWalletsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getWallets());
+ assertEquals(2, response.getWallets().length);
+ assertEquals("wallet-1", response.getWallets()[0].getId());
+ assertNotNull(response.getPagination());
+ }
+
+ // ==================== CreateWallet Tests ====================
+
+ @Test
+ public void testCreateWalletRequestSerialization() throws CoinbaseClientException, JsonProcessingException {
+ CreateWalletRequest request = new CreateWalletRequest.Builder()
+ .portfolioId("portfolio-123")
+ .name("My BTC Wallet")
+ .symbol("BTC")
+ .type(com.coinbase.prime.model.enums.WalletType.VAULT)
+ .build();
+
+ String json = objectMapper.writeValueAsString(request);
+ assertNotNull(json);
+ assertTrue(json.contains("\"name\":\"My BTC Wallet\""));
+ assertTrue(json.contains("\"symbol\":\"BTC\""));
+ assertTrue(json.contains("\"wallet_type\":\"VAULT\""));
+ assertFalse(json.contains("portfolio_id"));
+ }
+
+ @Test
+ public void testCreateWalletRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new CreateWalletRequest.Builder()
+ .portfolioId("portfolio-123")
+ .symbol("BTC")
+ .type(com.coinbase.prime.model.enums.WalletType.VAULT)
+ .build());
+
+ assertThrows(CoinbaseClientException.class, () ->
+ new CreateWalletRequest.Builder()
+ .portfolioId("portfolio-123")
+ .name("My Wallet")
+ .type(com.coinbase.prime.model.enums.WalletType.VAULT)
+ .build());
+ }
+
+ @Test
+ public void testCreateWalletResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"activity_id\":\"act-wallet-1\","
+ + "\"name\":\"My BTC Wallet\","
+ + "\"symbol\":\"BTC\","
+ + "\"wallet_type\":\"VAULT\","
+ + "\"network_family\":\"NETWORK_FAMILY_EVM\""
+ + "}";
+
+ CreateWalletResponse response = objectMapper.readValue(json, CreateWalletResponse.class);
+ assertNotNull(response);
+ assertEquals("act-wallet-1", response.getActivityId());
+ assertEquals("My BTC Wallet", response.getName());
+ assertEquals("BTC", response.getSymbol());
+ }
+
+ // ==================== GetWallet Tests ====================
+
+ @Test
+ public void testGetWalletRequestConstruction() throws CoinbaseClientException {
+ GetWalletRequest request = new GetWalletRequest.Builder("portfolio-123", "wallet-456").build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ }
+
+ @Test
+ public void testGetWalletResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"wallet\":{"
+ + "\"id\":\"wallet-456\","
+ + "\"name\":\"My BTC Wallet\","
+ + "\"symbol\":\"BTC\","
+ + "\"type\":\"VAULT\""
+ + "}"
+ + "}";
+
+ GetWalletResponse response = objectMapper.readValue(json, GetWalletResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getWallet());
+ assertEquals("wallet-456", response.getWallet().getId());
+ }
+
+ // ==================== GetWalletDepositInstructions Tests ====================
+
+ @Test
+ public void testGetWalletDepositInstructionsRequestConstruction() throws CoinbaseClientException {
+ GetWalletDepositInstructionsRequest request = new GetWalletDepositInstructionsRequest.Builder()
+ .portfolioId("portfolio-123")
+ .walletId("wallet-456")
+ .depositType(com.coinbase.prime.model.enums.WalletDepositInstructionType.CRYPTO)
+ .build();
+ assertNotNull(request);
+ assertEquals("portfolio-123", request.getPortfolioId());
+ assertEquals("wallet-456", request.getWalletId());
+ assertEquals(com.coinbase.prime.model.enums.WalletDepositInstructionType.CRYPTO, request.getDepositType());
+ }
+
+ @Test
+ public void testGetWalletDepositInstructionsRequestBuilderValidation() {
+ assertThrows(CoinbaseClientException.class, () ->
+ new GetWalletDepositInstructionsRequest.Builder()
+ .walletId("wallet-456")
+ .depositType(com.coinbase.prime.model.enums.WalletDepositInstructionType.CRYPTO)
+ .build());
+ }
+
+ @Test
+ public void testGetWalletDepositInstructionsCryptoResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"crypto_instructions\":{"
+ + "\"address\":\"0xabc123\","
+ + "\"account_identifier\":\"memo-xyz\","
+ + "\"destination_tag\":\"12345\","
+ + "\"network\":{\"id\":\"ethereum\",\"type\":\"mainnet\"}"
+ + "}"
+ + "}";
+
+ GetWalletDepositInstructionsResponse response = objectMapper.readValue(json, GetWalletDepositInstructionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getCryptoDepositInstructions());
+ assertEquals("0xabc123", response.getCryptoDepositInstructions().getAddress());
+ assertNull(response.getFiatDepositInstructions());
+ }
+
+ @Test
+ public void testGetWalletDepositInstructionsFiatResponseDeserialization() throws JsonProcessingException {
+ String json = "{"
+ + "\"fiat_instructions\":{"
+ + "\"account_number\":\"123456789\","
+ + "\"routing_number\":\"021000021\","
+ + "\"reference\":\"REF-001\""
+ + "}"
+ + "}";
+
+ GetWalletDepositInstructionsResponse response = objectMapper.readValue(json, GetWalletDepositInstructionsResponse.class);
+ assertNotNull(response);
+ assertNotNull(response.getFiatDepositInstructions());
+ assertNull(response.getCryptoDepositInstructions());
+ }
}
diff --git a/tools/model-generator/README.md b/tools/model-generator/README.md
index 5e4e90ef..f8f263e3 100644
--- a/tools/model-generator/README.md
+++ b/tools/model-generator/README.md
@@ -1,94 +1,65 @@
-# Model Generator
+# Prime Java SDK generator
-Generates Java model classes and enums from the OpenAPI specification.
+Generates the majority of the Prime Java SDK from the published OpenAPI spec, aligned with `prime-sdk-dotnet` (shared JSON config in `config/`).
-## Purpose
+## What it generates
-Generates domain models (`com.coinbase.prime.model`) and enums (`com.coinbase.prime.model.enums`) from the OpenAPI spec. Request/Response classes are excluded and maintained separately in service packages.
+1. **Models & enums** — OpenAPI Generator (`OpenApiGenerator`) → `PostProcessor` → `com.coinbase.prime.model` / `model.enums`
+2. **Client surface** — `SdkGeneratorMain` / `ClientSurfacePhase`: per-operation `*Request`, `*Response`, `*Service`, `*ServiceImpl` under `com.coinbase.prime./`
+3. **Factory** — `com.coinbase.prime.factory.PrimeServiceFactory`
-## Architecture
+Hand-written samples live under `com.coinbase.examples`; the generator does not create or update them.
-The generator implements a three-stage pipeline:
+## Configuration
-1. **OpenAPI Generator** (`OpenApiGenerator.java`) - Generates raw POJOs from the OpenAPI spec using the OpenAPI Generator library
-2. **Post-Processor** (`PostProcessor.java`) - Applies transformations (Web3→Onchain renaming, schema filtering, file routing)
-3. **Model Transformer** (`ModelTransformer.java`) - Uses JavaPoet to generate final models with builder pattern, license headers, and proper annotations
+| File | Purpose |
+|------|---------|
+| `config/generator-config.json` | `specUrl`, renames, acronym mappings, `tagToFolderOverrides`, `serviceMethodOrderOverrides`, `statusCodeOverrides` (HTTP names: `OK`, `CREATED`, …) |
+| `config/operations-overrides.json` | Per-`operationId` overrides: `sdkMethod`, `service`, `omitRequest`, `forcePaginated`, `paramTypeOverrides` (Java types) |
+| [`../.openapi-generator-ignore`](.openapi-generator-ignore) | OpenAPI Generator ignore patterns for the **model** pass (under `model/`) |
## Usage
-### Prerequisites
-
-- Java 11+
-- Maven 3.6+
-- Network access to fetch the OpenAPI spec from `https://api.prime.coinbase.com/v1/openapi.yaml`
-
-### Build
-
```bash
cd tools/model-generator
-mvn clean package
+mvn -q -Pgenerate
+# optional:
+mvn -q compile exec:java@generate-models -Dgenerator.args=--dry-run
+mvn -q compile exec:java@generate-models -Dgenerator.args=--diff
```
-### Generate Models
+Or from the **repository root**:
```bash
mvn -Pgenerate
+mvn -Pgenerate -Dgenerator.args=--diff
```
-Or using the JAR directly:
+The root profile runs `mvn` in this module with `-Pgenerate` and forwards `generator.args`.
-```bash
-java -jar target/model-generator-1.0.0.jar
-```
+**Wrapper:** `./tools/model-generator/generate.sh` (builds then runs the generator entrypoint).
-## Generated Code
+## Entry points
-Models:
-- Apache 2.0 license headers
-- Jackson `@JsonProperty` annotations
-- Builder pattern
-- Standard getters/setters (`is` prefix for booleans)
-- No-arg and builder constructors
+- **`com.coinbase.tools.sdkgenerator.SdkGeneratorMain`** — default `main` for the shaded JAR and `exec-maven-plugin`
+- **`com.coinbase.tools.modelgenerator.Main`** — delegates to `SdkGeneratorMain` (legacy)
-Enums: `UPPERCASE_WITH_UNDERSCORES` naming.
-
-## Configuration
+## Phases (conceptual)
-### Input
-- OpenAPI spec: Fetched automatically from `https://api.prime.coinbase.com/v1/openapi.yaml` during generation
+1. Download spec to `../generated/openapi.yaml`
+2. Parse YAML to `JsonNode` (`SpecParser`); build operation bindings (`OperationBindingGenerator` + `operations-overrides.json`)
+3. Run model/enum generation unless `--dry-run` / `--diff`
+4. Emit request/response/service and write files (`ClientSurfacePhase`)
+5. Regenerate `PrimeServiceFactory` (`FactoryPhase`)
-### Output
-- Models: `src/main/java/com/coinbase/prime/model/`
-- Enums: `src/main/java/com/coinbase/prime/model/enums/`
+## Tests
-### Filtering
-
-The `.openapi-generator-ignore` file excludes:
-- `*Request.java` - service-specific requests
-- `*Response.java` - service-specific responses
-- `Google*.java` - infrastructure types
-- `RFQ.java` - inline schemas
-- `*AllOf*.java` - composition artifacts
-
-## Technical Details
-
-### Dependencies
-- OpenAPI Generator 7.x
-- Palantir JavaPoet (code generation)
-- Jackson (JSON annotations)
-- SnakeYAML (spec parsing)
-
-### Transformations
-
-The post-processor applies:
-- **Web3→Onchain**: Renames classes/fields containing "Web3" to "Onchain" while preserving `@JsonProperty` mappings
-- **Schema filtering**: Skips schemas matching ignore patterns
-- **Package routing**: Places enums in `model/enums/`, models in `model/`
-- **Full regeneration**: Processes all models from the OpenAPI spec, updating existing files and creating new ones as needed
+```bash
+mvn test
+```
-## Workflow
+## Dependencies
-1. Run generator: `mvn -Pgenerate`
-2. Review generated files
-3. Compile: `mvn clean install`
-4. Commit changes
+- OpenAPI Generator 7.x (models)
+- Jackson (YAML + JSON for config and spec)
+- JUnit 5 (tests)
diff --git a/tools/model-generator/config/generator-config.json b/tools/model-generator/config/generator-config.json
new file mode 100644
index 00000000..264a8ba4
--- /dev/null
+++ b/tools/model-generator/config/generator-config.json
@@ -0,0 +1,98 @@
+{
+ "specUrl": "https://api.prime.coinbase.com/v1/openapi.yaml",
+ "filePathReplacements": {
+ "CoinbaseCustodyApiActivityType": "CustodyActivityType",
+ "CoinbasePublicRestApiActivityType": "PrimeActivityType",
+ "rFQ": "RFQ",
+ "FcmFuturesSweep": "FuturesSweep"
+ },
+ "contentReplacements": {
+ "coinbaseCustodyApiActivityType": "CustodyActivityType",
+ "coinbasePublicRestApiActivityType": "PrimeActivityType",
+ "CoinbaseCustodyApiActivityType": "CustodyActivityType",
+ "CoinbasePublicRestApiActivityType": "PrimeActivityType",
+ "CoinbasePublicRestApi": "",
+ "coinbasePublicRestApi": "",
+ "PrimeRESTAPI": "",
+ "primeRESTAPI": "",
+ "CoinbaseCustodyApi": "",
+ "coinbaseCustodyApi": "",
+ "CoinbaseBrokerageProxyEventsMaterializedApi": "",
+ "coinbaseBrokerageProxyEventsMaterializedApi": "",
+ "publicRestApi": "",
+ "PublicRestApi": "",
+ "CreateOnchainTransactionRequestEvmParams": "EvmParams",
+ "FcmFuturesSweepRequestAmount": "SweepAmount",
+ "FcmFuturesSweep": "FuturesSweep",
+ "EvmParam ": "EvmParams ",
+ "EvmParam?": "EvmParams?",
+ "EvmParam>": "EvmParams>",
+ "RPC ": "RpcConfig ",
+ "RPC?": "RpcConfig?",
+ "RPC>": "RpcConfig>",
+ "PaginatedResponse": "Pagination",
+ "GoogleTypeDate": "DateOfBirth"
+ },
+ "acronymMappings": [
+ { "acronym": "TF", "normalized": "Tf" },
+ { "acronym": "FCM", "normalized": "Fcm" },
+ { "acronym": "XML", "normalized": "Xml" },
+ { "acronym": "XM", "normalized": "Xm" },
+ { "acronym": "PM", "normalized": "Pm" },
+ { "acronym": "RFQ", "normalized": "Rfq" },
+ { "acronym": "NFT", "normalized": "Nft" },
+ { "acronym": "EVM", "normalized": "Evm" },
+ { "acronym": "VASP", "normalized": "Vasp" }
+ ],
+ "enumNameMappings": {
+ "ActivityType": "PrimeActivityType"
+ },
+ "tagToFolderOverrides": {
+ "Travel Rule": "transactions"
+ },
+ "serviceMethodOrderOverrides": {
+ "activities": [ "ListPortfolioActivities", "ListEntityActivities", "GetActivity", "GetPortfolioActivity" ],
+ "addressbook": [ "ListAddressBook", "CreateAddressBookEntry" ],
+ "advancedtransfer": [ "ListAdvancedTransfers", "CreateAdvancedTransfer", "CancelAdvancedTransfer", "ListAdvancedTransferTransactions" ],
+ "allocations": [ "CreateAllocation", "CreateNetAllocation", "ListPortfolioAllocations", "ListAllocationsByNettingId", "GetAllocation" ],
+ "assets": [ "ListAssets" ],
+ "balances": [ "ListEntityBalances", "ListPortfolioBalances", "GetWalletBalance", "ListOnchainWalletBalances" ],
+ "commission": [ "GetPortfolioCommission" ],
+ "financing": [ "ListInterestAccruals", "GetCrossMarginOverview", "GetEntityLocateAvailabilities", "GetMarginInformation", "ListMarginCallSummaries", "ListTradeFinanceObligations", "GetTradeFinanceTieredPricingFees", "ListFinancingEligibleAssets", "ListInterestAccrualsForPortfolio", "GetPortfolioBuyingPower", "GetPortfolioCreditInformation", "ListExistingLocates", "CreateNewLocates", "ListMarginConversions", "GetPortfolioWithdrawalPower" ],
+ "futures": [ "SetAutoSweep", "GetEntityFcmBalance", "GetFcmMarginCallDetails", "GetPositions", "GetFcmRiskLimits", "GetFcmSettings", "SetFcmSettings", "ListEntityFuturesSweeps", "ScheduleEntityFuturesSweep", "CancelEntityFuturesSweep", "GetFcmEquity" ],
+ "invoice": [ "ListInvoices" ],
+ "onchainaddressbook": [ "UpdateOnchainAddressBookEntry", "CreateOnchainAddressBookEntry", "DeleteOnchainAddressGroup", "ListOnchainAddressGroups" ],
+ "orders": [ "AcceptQuote", "ListPortfolioFills", "ListOpenOrders", "CreateOrder", "GetOrderPreview", "ListPortfolioOrders", "GetOrderByOrderId", "CancelOrder", "EditOrder", "ListOrderEditHistory", "ListOrderFills", "CreateQuote" ],
+ "paymentmethods": [ "ListPaymentMethods", "GetPaymentMethodDetails" ],
+ "portfolios": [ "ListPortfolios", "GetPortfolio", "GetPortfolioCounterpartyId" ],
+ "positions": [ "ListAggregatePositions", "ListPositions" ],
+ "products": [ "ListPortfolioProducts", "GetCandles" ],
+ "staking": [ "ClaimRewards", "CreateStake", "CreateUnstake", "ListTransactionValidators", "PortfolioStakingInitiate", "PortfolioStakingUnstake", "GetStakingStatus", "GetUnstakingStatus", "PreviewUnstake" ],
+ "transactions": [ "ListPortfolioTransactions", "GetTransaction", "CreateConversion", "CreateOnchainTransaction", "ListWalletTransactions", "CreateWalletTransfer", "CreateWalletWithdrawal", "GetTransactionTravelRuleData", "SubmitDepositTravelRuleData" ],
+ "users": [ "ListEntityUsers", "ListPortfolioUsers" ],
+ "wallets": [ "ListWallets", "CreateWallet", "GetWallet", "ListWalletAddresses", "CreateWalletDepositAddress", "GetWalletDepositInstructions" ]
+ },
+ "statusCodeOverrides": {
+ "CreateOrder": [ "CREATED", "OK" ],
+ "CreateQuote": [ "CREATED", "OK" ],
+ "CreateWallet": [ "CREATED", "OK" ],
+ "CreateWalletDepositAddress": [ "CREATED", "OK" ],
+ "CreateConversion": [ "CREATED", "OK" ],
+ "CreateTransfer": [ "CREATED", "OK" ],
+ "CreateWithdrawal": [ "CREATED", "OK" ],
+ "CreateOnchainTransaction": [ "CREATED", "OK" ],
+ "PreviewUnstake": [ "CREATED", "OK" ],
+ "SubmitDepositTravelRuleData": [ "CREATED", "OK" ],
+ "CreateAllocation": [ "CREATED", "OK" ],
+ "CreateNetAllocation": [ "CREATED", "OK" ],
+ "CreateAddressBookEntry": [ "CREATED", "OK" ],
+ "CreateAdvancedTransfer": [ "CREATED", "OK" ],
+ "CreateNewLocates": [ "CREATED", "OK" ],
+ "PortfolioStakingInitiate": [ "CREATED", "OK" ],
+ "PortfolioStakingUnstake": [ "CREATED", "OK" ],
+ "CreateStake": [ "CREATED", "OK" ],
+ "CreateUnstake": [ "CREATED", "OK" ],
+ "ClaimRewards": [ "CREATED", "OK" ],
+ "CreateOnchainAddressBookEntry": [ "CREATED", "OK" ]
+ }
+}
diff --git a/tools/model-generator/config/operations-overrides.json b/tools/model-generator/config/operations-overrides.json
new file mode 100644
index 00000000..c621ac2d
--- /dev/null
+++ b/tools/model-generator/config/operations-overrides.json
@@ -0,0 +1,166 @@
+[
+ {
+ "operationId": "PrimeRESTAPI_CancelFuturesSweep",
+ "sdkMethod": "CancelEntityFuturesSweep"
+ },
+ {
+ "operationId": "PrimeRESTAPI_CreateOnchainAddressGroup",
+ "sdkMethod": "CreateOnchainAddressBookEntry"
+ },
+ {
+ "operationId": "PrimeRESTAPI_CreatePortfolioAddressBookEntry",
+ "sdkMethod": "CreateAddressBookEntry"
+ },
+ {
+ "operationId": "PrimeRESTAPI_CreateQuoteRequest",
+ "sdkMethod": "CreateQuote"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetAllocationsByClientNettingId",
+ "sdkMethod": "ListAllocationsByNettingId"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetEntityActivities",
+ "paramTypeOverrides": {
+ "categories": "ActivityCategory[]",
+ "statuses": "ActivityStatus[]",
+ "activity_level": "ActivityLevel"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetEntityAssets",
+ "sdkMethod": "ListAssets"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetEntityPaymentMethodDetails",
+ "sdkMethod": "GetPaymentMethodDetails"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetEntityPaymentMethods",
+ "sdkMethod": "ListPaymentMethods"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetFcmBalance",
+ "sdkMethod": "GetEntityFcmBalance"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetFuturesSweeps",
+ "sdkMethod": "ListEntityFuturesSweeps"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetLocateAvailabilities",
+ "sdkMethod": "GetEntityLocateAvailabilities"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetMarginSummaries",
+ "sdkMethod": "ListMarginCallSummaries"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetOpenOrders",
+ "paramTypeOverrides": {
+ "order_type": "OrderType",
+ "order_side": "OrderSide"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetOrders",
+ "sdkMethod": "ListPortfolioOrders",
+ "paramTypeOverrides": {
+ "order_type": "OrderType",
+ "order_side": "OrderSide"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetOrder",
+ "sdkMethod": "GetOrderByOrderId"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioActivities",
+ "paramTypeOverrides": {
+ "categories": "ActivityCategory[]",
+ "statuses": "ActivityStatus[]"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioAddressBook",
+ "sdkMethod": "ListAddressBook"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioAllocations",
+ "paramTypeOverrides": {
+ "order_side": "OrderSide"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioBalances",
+ "forcePaginated": true,
+ "paramTypeOverrides": {
+ "balance_type": "PortfolioBalanceType",
+ "symbols": "String[]"
+ }
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioCounterpartyID",
+ "sdkMethod": "GetPortfolioCounterpartyId"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPortfolioInterestAccruals",
+ "sdkMethod": "ListInterestAccrualsForPortfolio"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetPostTradeCredit",
+ "sdkMethod": "GetPortfolioCreditInformation"
+ },
+ {
+ "operationId": "PrimeRESTAPI_GetTFTieredPricingFees",
+ "sdkMethod": "GetTradeFinanceTieredPricingFees"
+ },
+ {
+ "operationId": "PrimeRESTAPI_ListAdvancedTransferTransactions",
+ "service": "advancedtransfer"
+ },
+ {
+ "operationId": "PrimeRESTAPI_ListAggregateEntityPositions",
+ "sdkMethod": "ListAggregatePositions"
+ },
+ {
+ "operationId": "PrimeRESTAPI_ListEntityPositions",
+ "sdkMethod": "ListPositions"
+ },
+ {
+ "operationId": "PrimeRESTAPI_ListTFObligations",
+ "sdkMethod": "ListTradeFinanceObligations"
+ },
+ {
+ "operationId": "PrimeRESTAPI_OrderPreview",
+ "sdkMethod": "GetOrderPreview"
+ },
+ {
+ "operationId": "PrimeRESTAPI_PortfolioStakingInitiate",
+ "sdkMethod": "PortfolioStakingInitiate"
+ },
+ {
+ "operationId": "PrimeRESTAPI_PortfolioStakingUnstake",
+ "sdkMethod": "PortfolioStakingUnstake"
+ },
+ {
+ "operationId": "PrimeRESTAPI_ScheduleFuturesSweep",
+ "sdkMethod": "ScheduleEntityFuturesSweep"
+ },
+ {
+ "operationId": "PrimeRESTAPI_StakingClaimRewards",
+ "sdkMethod": "ClaimRewards"
+ },
+ {
+ "operationId": "PrimeRESTAPI_StakingInitiate",
+ "sdkMethod": "CreateStake"
+ },
+ {
+ "operationId": "PrimeRESTAPI_StakingUnstake",
+ "sdkMethod": "CreateUnstake"
+ },
+ {
+ "operationId": "PrimeRESTAPI_UpdateOnchainAddressGroup",
+ "sdkMethod": "UpdateOnchainAddressBookEntry"
+ }
+]
diff --git a/tools/model-generator/generate.sh b/tools/model-generator/generate.sh
new file mode 100644
index 00000000..7ae2d20f
--- /dev/null
+++ b/tools/model-generator/generate.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT/tools/model-generator"
+mvn -q -DskipTests package
+mvn -q -DskipTests exec:java@generate-models
diff --git a/tools/model-generator/pom.xml b/tools/model-generator/pom.xml
index d0630c32..6d7d31cb 100644
--- a/tools/model-generator/pom.xml
+++ b/tools/model-generator/pom.xml
@@ -21,6 +21,7 @@
2.16.1
1.13.0
2.2
+
@@ -49,6 +50,11 @@
jackson-dataformat-yaml
${jackson.version}
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${jackson.version}
+
@@ -75,6 +81,12 @@
slf4j-simple
2.0.9
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.0
+ test
+
@@ -88,6 +100,11 @@
11
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M5
+
org.apache.maven.plugins
maven-shade-plugin
@@ -101,7 +118,7 @@
- com.coinbase.tools.modelgenerator.Main
+ com.coinbase.tools.sdkgenerator.SdkGeneratorMain
@@ -120,7 +137,8 @@
java
- com.coinbase.tools.modelgenerator.Main
+ com.coinbase.tools.sdkgenerator.SdkGeneratorMain
+ ${generator.args}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/Main.java b/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/Main.java
index 4b86e3b2..a7226302 100644
--- a/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/Main.java
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/Main.java
@@ -16,75 +16,14 @@
package com.coinbase.tools.modelgenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import com.coinbase.tools.sdkgenerator.SdkGeneratorMain;
+/**
+ * @deprecated Use {@link SdkGeneratorMain}. Kept for compatibility with older scripts.
+ */
public class Main {
- private static final Logger logger = LoggerFactory.getLogger(Main.class);
-
- public static void main(String[] args) {
- try {
- logger.info("Coinbase Prime Java SDK Model Generator");
- logger.info("========================================");
- logger.info("Mode: FULL GENERATION (regenerates all models from OpenAPI spec)");
- logger.info("");
-
- // Find project root
- Path projectRoot = findProjectRoot();
- String specUrl = args.length > 0 ? args[0] : "https://api.prime.coinbase.com/v1/openapi.yaml";
- Path outputDir = projectRoot.resolve("src/main/java/com/coinbase/prime/model");
- Path enumsDir = outputDir.resolve("enums");
- Path tempDir = projectRoot.resolve("generated");
-
- logger.info("Project Root: {}", projectRoot);
- logger.info("OpenAPI Spec: {}", specUrl);
- logger.info("Output Directory: {}", outputDir);
- logger.info("Enums Directory: {}", enumsDir);
- logger.info("Temp Directory: {}", tempDir);
-
- // Phase 1: Generate raw models using OpenAPI Generator
- logger.info("\nPhase 1: Generating raw models with OpenAPI Generator...");
- OpenApiGenerator generator = new OpenApiGenerator(specUrl, tempDir);
- generator.generateModels();
-
- // Phase 2: Post-process models to match existing patterns
- logger.info("\nPhase 2: Post-processing models...");
- PostProcessor postProcessor = new PostProcessor(tempDir, outputDir, enumsDir);
- postProcessor.processModels();
-
- logger.info("\nModel generation completed successfully!");
- logger.info("\nGenerated models are in: {}", outputDir);
- logger.info("Generated enums are in: {}", enumsDir);
- logger.info("\nNext steps:");
- logger.info("1. Review the generated models");
- logger.info("2. Run tests to ensure everything works");
- logger.info("3. Build the project: mvn clean install");
-
- } catch (Exception e) {
- logger.error("Error during model generation", e);
- System.exit(1);
- }
- }
- private static Path findProjectRoot() {
- Path current = Paths.get(System.getProperty("user.dir"));
- while (current != null) {
- if (current.resolve("pom.xml").toFile().exists() &&
- current.resolve("src/main/java/com/coinbase/prime").toFile().exists()) {
- return current;
- }
- current = current.getParent();
- }
-
- // If we can't find it, check if we're in the tools directory
- Path toolsPath = Paths.get(System.getProperty("user.dir"));
- if (toolsPath.toString().contains("tools/model-generator")) {
- return toolsPath.getParent().getParent();
- }
-
- throw new RuntimeException("Could not find project root (looking for pom.xml and src/main/java/com/coinbase/prime)");
+ public static void main(String[] args) throws Exception {
+ SdkGeneratorMain.main(args);
}
-}
\ No newline at end of file
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/PostProcessor.java b/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/PostProcessor.java
index f370d809..d86a194d 100644
--- a/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/PostProcessor.java
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/modelgenerator/PostProcessor.java
@@ -278,6 +278,7 @@ private void processFile(Path file, Path targetDir, boolean isEnum) throws IOExc
content = applyBooleanPrimitiveConversion(content);
}
+ content = com.coinbase.tools.sdkgenerator.CopyrightHelper.applyCopyrightYear(outputPath, content);
Files.writeString(outputPath, content);
if (!existsBefore) {
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/CopyrightHelper.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/CopyrightHelper.java
new file mode 100644
index 00000000..31de24b6
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/CopyrightHelper.java
@@ -0,0 +1,176 @@
+/*
+ * 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.sdkgenerator;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Copyright year handling: resolves years from Git first-commit dates where possible, and
+ * normalizes generated SDK file content via {@link #applyCopyrightYear(Path, String)}.
+ *
+ * Mirrors the behavior of the .NET generator's CopyrightHelper. Newly emitted files use
+ * the year of the first commit that added the generator anchor file ({@code RequestPhase.java}).
+ * Files that already exist on disk preserve their existing on-disk year.
+ */
+public final class CopyrightHelper {
+
+ private static final Pattern COPYRIGHT_YEAR_PATTERN =
+ Pattern.compile("Copyright (\\d{4})-present");
+ private static final Pattern COPYRIGHT_REPLACE_PATTERN =
+ Pattern.compile("Copyright \\d{4}-present");
+
+ private static int sdkEmittedCopyrightYear;
+
+ private CopyrightHelper() {}
+
+ /**
+ * Calendar year used in newly emitted Java file headers (requests, responses, services,
+ * factory). Set by {@link #initializeSdkEmittedCopyrightYear(Path)} from Git; falls back to
+ * UTC year if unset.
+ */
+ public static int getSdkEmittedCopyrightYear() {
+ return sdkEmittedCopyrightYear > 0
+ ? sdkEmittedCopyrightYear
+ : ZonedDateTime.now(ZoneOffset.UTC).getYear();
+ }
+
+ /**
+ * Resolves the year from the first commit that added
+ * {@code tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/RequestPhase.java},
+ * used as the copyright year for all holistically generated Java surface files.
+ */
+ public static void initializeSdkEmittedCopyrightYear(Path projectRoot) {
+ Path anchor =
+ projectRoot
+ .resolve("tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/RequestPhase.java");
+ sdkEmittedCopyrightYear = getFirstCommitYearForPath(projectRoot, anchor);
+ }
+
+ /**
+ * Returns the commit year (UTC) of the first commit that added {@code absolutePath}, or the
+ * current UTC year if Git is unavailable or the file is not yet tracked.
+ */
+ public static int getFirstCommitYearForPath(Path gitRepositoryRoot, Path absolutePath) {
+ try {
+ Path full = absolutePath.toAbsolutePath().normalize();
+ Path root = gitRepositoryRoot.toAbsolutePath().normalize();
+ if (!full.startsWith(root)) {
+ return ZonedDateTime.now(ZoneOffset.UTC).getYear();
+ }
+ String relPosix = root.relativize(full).toString().replace('\\', '/');
+
+ ProcessBuilder pb =
+ new ProcessBuilder(
+ "git",
+ "log",
+ "--diff-filter=A",
+ "--format=%cs",
+ "--reverse",
+ "--",
+ relPosix)
+ .directory(root.toFile())
+ .redirectErrorStream(false);
+ Process proc = pb.start();
+ String output;
+ try (var in = proc.getInputStream()) {
+ output = new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ proc.waitFor();
+ String firstLine =
+ output.lines()
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .findFirst()
+ .orElse(null);
+ if (firstLine == null || firstLine.length() < 4) {
+ return ZonedDateTime.now(ZoneOffset.UTC).getYear();
+ }
+ try {
+ return Integer.parseInt(firstLine.substring(0, 4));
+ } catch (NumberFormatException ex) {
+ return ZonedDateTime.now(ZoneOffset.UTC).getYear();
+ }
+ } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ return ZonedDateTime.now(ZoneOffset.UTC).getYear();
+ }
+ }
+
+ /**
+ * Standard Apache 2.0 comment block for generated Java files using the given copyright year.
+ */
+ public static String javaFileHeader(int year) {
+ return "/*\n"
+ + " * Copyright "
+ + year
+ + "-present Coinbase Global, Inc.\n"
+ + " *\n"
+ + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ + " * you may not use this file except in compliance with the License.\n"
+ + " * You may obtain a copy of the License at\n"
+ + " *\n"
+ + " * http://www.apache.org/licenses/LICENSE-2.0\n"
+ + " *\n"
+ + " * Unless required by applicable law or agreed to in writing, software\n"
+ + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ + " * See the License for the specific language governing permissions and\n"
+ + " * limitations under the License.\n"
+ + " */\n\n";
+ }
+
+ /** Convenience wrapper using the SDK-emitted copyright year. */
+ public static String javaFileHeader() {
+ return javaFileHeader(getSdkEmittedCopyrightYear());
+ }
+
+ /**
+ * Replaces the hardcoded copyright year in {@code content} with:
+ *
+ *
+ * - The existing year from the on-disk file at {@code outputPath} (if it exists), or
+ *
- {@link #getSdkEmittedCopyrightYear()} for brand-new generated files.
+ *
+ */
+ public static String applyCopyrightYear(Path outputPath, String content) {
+ String targetYear;
+ if (outputPath != null && Files.exists(outputPath)) {
+ try {
+ String onDisk = Files.readString(outputPath);
+ Matcher m = COPYRIGHT_YEAR_PATTERN.matcher(onDisk);
+ targetYear =
+ m.find() ? m.group(1) : Integer.toString(getSdkEmittedCopyrightYear());
+ } catch (IOException ex) {
+ targetYear = Integer.toString(getSdkEmittedCopyrightYear());
+ }
+ } else {
+ targetYear = Integer.toString(getSdkEmittedCopyrightYear());
+ }
+ return COPYRIGHT_REPLACE_PATTERN
+ .matcher(content)
+ .replaceAll("Copyright " + targetYear + "-present");
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/GeneratorPaths.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/GeneratorPaths.java
new file mode 100644
index 00000000..6051e3ad
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/GeneratorPaths.java
@@ -0,0 +1,53 @@
+/*
+ * 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.sdkgenerator;
+
+import java.nio.file.Path;
+
+public final class GeneratorPaths {
+
+ private GeneratorPaths() {}
+
+ public static Path findProjectRoot() {
+ Path current = Path.of(System.getProperty("user.dir"));
+ for (int i = 0; i < 20; i++) {
+ if (current.resolve("pom.xml").toFile().exists()
+ && current.resolve("src/main/java/com/coinbase/prime").toFile().exists()) {
+ return current;
+ }
+ Path parent = current.getParent();
+ if (parent == null) {
+ break;
+ }
+ current = parent;
+ }
+ String env = System.getProperty("user.dir", "");
+ if (env.contains("tools/model-generator")) {
+ return Path.of(env).getParent().getParent();
+ }
+ throw new IllegalStateException(
+ "Could not find project root (pom.xml and src/main/java/com/coinbase/prime).");
+ }
+
+ public static Path configDirectory(Path projectRoot) {
+ return projectRoot.resolve("tools/model-generator/config");
+ }
+
+ public static Path toolDirectory(Path projectRoot) {
+ return projectRoot.resolve("tools/model-generator");
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/SdkGeneratorMain.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/SdkGeneratorMain.java
new file mode 100644
index 00000000..d35e6527
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/SdkGeneratorMain.java
@@ -0,0 +1,109 @@
+package com.coinbase.tools.sdkgenerator;
+
+import com.coinbase.tools.sdkgenerator.phases.ClientSurfacePhase;
+import com.coinbase.tools.sdkgenerator.phases.FactoryPhase;
+import com.coinbase.tools.sdkgenerator.phases.ModelEnumPhase;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.OperationBindingValidator;
+import com.coinbase.tools.sdkgenerator.processing.SharedTransforms;
+import com.coinbase.tools.sdkgenerator.processing.SpecAnalyzer;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import com.coinbase.tools.sdkgenerator.spec.SpecParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Holistic Prime Java SDK generator: models, enums, client surface, factory.
+ */
+public final class SdkGeneratorMain {
+
+ private static final Logger log = LoggerFactory.getLogger(SdkGeneratorMain.class);
+
+ private SdkGeneratorMain() {}
+
+ public static void main(String[] args) throws Exception {
+ boolean dryRun = java.util.Arrays.asList(args).contains("--dry-run");
+ boolean diffMode = java.util.Arrays.asList(args).contains("--diff");
+
+ log.info("Coinbase Prime Java SDK Generator");
+ log.info("==================================");
+ if (dryRun) {
+ log.info("Mode: DRY-RUN (no files written)");
+ } else if (diffMode) {
+ log.info("Mode: DIFF (compare to existing files)");
+ } else {
+ log.info("Mode: WRITE (full generation)");
+ }
+
+ Path projectRoot = GeneratorPaths.findProjectRoot();
+ CopyrightHelper.initializeSdkEmittedCopyrightYear(projectRoot);
+ GeneratorConfiguration cfg = GeneratorConfiguration.load(projectRoot);
+ SharedTransforms transforms = new SharedTransforms(cfg);
+
+ Path specPath = projectRoot.resolve("generated").resolve("openapi.yaml");
+ Files.createDirectories(specPath.getParent());
+ log.info("Downloading OpenAPI spec from {} to {}...", cfg.getSpecUrl(), specPath);
+ HttpClient http = HttpClient.newHttpClient();
+ HttpRequest req =
+ HttpRequest.newBuilder(URI.create(cfg.getSpecUrl()))
+ .GET()
+ .build();
+ HttpResponse res = http.send(req, HttpResponse.BodyHandlers.ofString());
+ Files.writeString(specPath, res.body());
+
+ log.info("Parsing OpenAPI YAML for client surface...");
+ ParsedOpenApiDocument document = SpecParser.load(specPath);
+ SpecAnalyzer.apply(document, cfg, log);
+
+ if (!dryRun && !diffMode) {
+ ModelEnumPhase.run(log, projectRoot, specPath.toString());
+ } else {
+ log.info("Skipping model/enum CLI phase (--dry-run or --diff).");
+ }
+
+ GeneratorConfiguration.OperationBindingMergeResult merge =
+ GeneratorConfiguration.mergeOperationBindings(document, cfg, projectRoot);
+ OperationBindingValidator.validateOperationBindings(log, document, merge);
+ List operations = merge.getMerged();
+
+ Path primeRoot = projectRoot.resolve("src/main/java/com/coinbase/prime");
+ ClientSurfacePhase client =
+ new ClientSurfacePhase(log, document, cfg, transforms, primeRoot);
+ client.run(operations, dryRun, diffMode);
+
+ String factory = FactoryPhase.emit(cfg);
+ Path factoryPath =
+ projectRoot.resolve("src/main/java/com/coinbase/prime/factory/PrimeServiceFactory.java");
+ factory = CopyrightHelper.applyCopyrightYear(factoryPath, factory);
+ if (diffMode) {
+ if (!Files.exists(factoryPath)) {
+ log.info("DIFF missing factory would be created: {}", factoryPath);
+ } else {
+ String existing = Files.readString(factoryPath).replace("\r\n", "\n");
+ if (!existing.equals(factory.replace("\r\n", "\n"))) {
+ log.warn("DIFF differs: {}", factoryPath);
+ }
+ }
+ } else if (dryRun) {
+ log.info("DRY-RUN would write {} ({} chars)", factoryPath, factory.length());
+ } else {
+ Files.createDirectories(factoryPath.getParent());
+ Files.writeString(factoryPath, factory);
+ }
+
+ log.info("");
+ log.info("Holistic generation finished.");
+ if (!dryRun && !diffMode) {
+ log.info("Next: mvn clean install (from repository root)");
+ }
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ClientSurfacePhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ClientSurfacePhase.java
new file mode 100644
index 00000000..d787c642
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ClientSurfacePhase.java
@@ -0,0 +1,180 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.sdkgenerator.CopyrightHelper;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.NamingResolver;
+import com.coinbase.tools.sdkgenerator.processing.ServiceDefinition;
+import com.coinbase.tools.sdkgenerator.processing.SharedTransforms;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOperation;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class ClientSurfacePhase {
+
+ private final Logger log;
+ private final ParsedOpenApiDocument doc;
+ private final GeneratorConfiguration cfg;
+ private final SharedTransforms transforms;
+ private final Path primeSrcRoot;
+
+ public ClientSurfacePhase(
+ Logger log,
+ ParsedOpenApiDocument doc,
+ GeneratorConfiguration cfg,
+ SharedTransforms transforms,
+ Path primeSrcRoot) {
+ this.log = log;
+ this.doc = doc;
+ this.cfg = cfg;
+ this.transforms = transforms;
+ this.primeSrcRoot = primeSrcRoot;
+ }
+
+ public void run(
+ List bindings, boolean dryRun, boolean diffMode) throws IOException {
+ Map> byService = new LinkedHashMap<>();
+ for (SdkOperationBinding b : bindings) {
+ ParsedOperation op = doc.getOperationsById().get(b.getOperationId());
+ if (op == null) {
+ log.warn("OpenAPI spec missing operationId {}; skipping.", b.getOperationId());
+ continue;
+ }
+ byService.computeIfAbsent(b.getService(), k -> new ArrayList<>())
+ .add(new ServicePhase.SdkOpPair(b, op));
+ }
+ for (String key : new ArrayList<>(byService.keySet())) {
+ byService.put(key, sortOperationsForService(key, byService.get(key)));
+ }
+
+ for (Map.Entry> e :
+ byService.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .collect(Collectors.toList())) {
+ String serviceKey = e.getKey();
+ List ops = e.getValue();
+ ServiceDefinition svc = NamingResolver.requireService(cfg, serviceKey);
+ Path folder = primeSrcRoot.resolve(svc.getFolder());
+ for (ServicePhase.SdkOpPair pair : ops) {
+ SdkOperationBinding b = pair.binding;
+ if (!b.isOmitRequest()) {
+ String req = RequestPhase.emitRequest(doc, cfg, transforms, b, pair.op);
+ writeOrDiff(
+ folder.resolve(b.getSdkMethod() + "Request.java"), req, dryRun, diffMode);
+ } else {
+ Path stale = folder.resolve(b.getSdkMethod() + "Request.java");
+ if (Files.exists(stale) && !dryRun && !diffMode) {
+ Files.delete(stale);
+ log.info("REQUEST deleted stale (omitRequest): {}", stale);
+ } else if (Files.exists(stale) && (dryRun || diffMode)) {
+ log.info("REQUEST stale (omitRequest) would delete: {}", stale);
+ }
+ }
+ String resp =
+ ResponsePhase.emitResponse(
+ doc, cfg, transforms, b, pair.op);
+ writeOrDiff(
+ folder.resolve(b.getSdkMethod() + "Response.java"), resp, dryRun, diffMode);
+ }
+ }
+
+ for (Map.Entry> e :
+ byService.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .collect(Collectors.toList())) {
+ ServiceDefinition svc = NamingResolver.requireService(cfg, e.getKey());
+ Path folder = primeSrcRoot.resolve(svc.getFolder());
+ String iface = ServicePhase.emitInterface(svc, e.getValue());
+ String impl = ServicePhase.emitServiceImpl(cfg, svc, e.getValue());
+ writeOrDiff(folder.resolve(svc.getJavaInterfaceName() + ".java"), iface, dryRun, diffMode);
+ writeOrDiff(
+ folder.resolve(svc.getJavaServiceImplName() + ".java"), impl, dryRun, diffMode);
+ }
+ }
+
+ private void writeOrDiff(Path path, String content, boolean dryRun, boolean diffMode)
+ throws IOException {
+ content = CopyrightHelper.applyCopyrightYear(path, content).replace("\r\n", "\n");
+ if (diffMode) {
+ if (!Files.exists(path)) {
+ log.info("DIFF missing file would be created: {}", path);
+ return;
+ }
+ String existing = Files.readString(path).replace("\r\n", "\n");
+ if (!existing.equals(content)) {
+ log.warn("DIFF differs: {}", path);
+ }
+ return;
+ }
+ if (dryRun) {
+ log.info("DRY-RUN would write {} ({} chars)", path, content.length());
+ return;
+ }
+ Files.createDirectories(path.getParent());
+ Files.writeString(path, content);
+ }
+
+ private static int httpVerbRank(String m) {
+ switch (m.toUpperCase(Locale.ROOT)) {
+ case "GET":
+ return 0;
+ case "POST":
+ return 1;
+ case "PUT":
+ return 2;
+ case "PATCH":
+ return 3;
+ case "DELETE":
+ return 4;
+ default:
+ return 5;
+ }
+ }
+
+ private static int pathDepth(String path) {
+ if (path == null) {
+ return 0;
+ }
+ return (int) path.chars().filter(c -> c == '/').count();
+ }
+
+ private List sortOperationsForService(
+ String serviceKey, List ops) {
+ List order = cfg.getServiceMethodOrderOverrides().get(serviceKey);
+ if (order != null && !order.isEmpty()) {
+ Map rank = new LinkedHashMap<>();
+ for (int i = 0; i < order.size(); i++) {
+ rank.put(order.get(i), i);
+ }
+ return ops.stream()
+ .sorted(
+ Comparator.comparing(
+ (ServicePhase.SdkOpPair x) ->
+ rank.getOrDefault(
+ x.binding.getSdkMethod(),
+ Integer.MAX_VALUE))
+ .thenComparing(x -> x.binding.getSdkMethod()))
+ .collect(Collectors.toList());
+ }
+ return ops.stream()
+ .sorted(
+ Comparator.comparingInt(
+ (ServicePhase.SdkOpPair x) -> httpVerbRank(x.op.getHttpMethod()))
+ .thenComparingInt(
+ (ServicePhase.SdkOpPair x) -> pathDepth(x.op.getPath()))
+ .thenComparing((ServicePhase.SdkOpPair x) -> x.op.getPath())
+ .thenComparing((ServicePhase.SdkOpPair x) -> x.binding.getSdkMethod()))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/FactoryPhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/FactoryPhase.java
new file mode 100644
index 00000000..6951a36a
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/FactoryPhase.java
@@ -0,0 +1,47 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.sdkgenerator.CopyrightHelper;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.ServiceDefinition;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/** Emits {@code com.coinbase.prime.factory.PrimeServiceFactory}. */
+public final class FactoryPhase {
+
+ private FactoryPhase() {}
+
+ public static String emit(GeneratorConfiguration cfg) {
+ List defs =
+ cfg.getServices().values().stream()
+ .sorted(Comparator.comparing(ServiceDefinition::getFolder))
+ .collect(Collectors.toCollection(ArrayList::new));
+ StringBuilder s = new StringBuilder();
+ s.append(CopyrightHelper.javaFileHeader());
+ s.append("package com.coinbase.prime.factory;\n\n");
+ for (ServiceDefinition d : defs) {
+ s.append("import com.coinbase.prime.").append(d.getFolder()).append(".").append(d.getJavaInterfaceName()).append(";\n");
+ s.append("import com.coinbase.prime.").append(d.getFolder()).append(".").append(d.getJavaServiceImplName()).append(";\n");
+ }
+ s.append("import com.coinbase.prime.client.CoinbasePrimeClient;\n\n");
+ s.append("public class PrimeServiceFactory {\n");
+ for (ServiceDefinition d : defs) {
+ String method = "create" + d.getPascalName() + "Service";
+ s.append(" public static ")
+ .append(d.getJavaInterfaceName())
+ .append(" ")
+ .append(method)
+ .append("(CoinbasePrimeClient client) {\n");
+ s.append(" return new ")
+ .append(d.getJavaServiceImplName())
+ .append("(client);\n");
+ s.append(" }\n\n");
+ }
+ s.append("}\n");
+ return s.toString();
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ModelEnumPhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ModelEnumPhase.java
new file mode 100644
index 00000000..8cd518f7
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ModelEnumPhase.java
@@ -0,0 +1,26 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.modelgenerator.OpenApiGenerator;
+import com.coinbase.tools.modelgenerator.PostProcessor;
+import org.slf4j.Logger;
+
+import java.nio.file.Path;
+
+/**
+ * Runs OpenAPI Generator for models/enums then post-processes into the SDK tree.
+ */
+public final class ModelEnumPhase {
+
+ private ModelEnumPhase() {}
+
+ public static void run(Logger log, Path projectRoot, String specPath) throws Exception {
+ Path generated = projectRoot.resolve("generated");
+ Path modelDir = projectRoot.resolve("src/main/java/com/coinbase/prime/model");
+ Path enumsDir = modelDir.resolve("enums");
+ log.info("Phase: model/enum OpenAPI generation into {}", modelDir);
+ OpenApiGenerator generator = new OpenApiGenerator(specPath, generated);
+ generator.generateModels();
+ PostProcessor postProcessor = new PostProcessor(generated, modelDir, enumsDir);
+ postProcessor.processModels();
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/RequestPhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/RequestPhase.java
new file mode 100644
index 00000000..cbe53ddb
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/RequestPhase.java
@@ -0,0 +1,366 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.sdkgenerator.CopyrightHelper;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.NamingResolver;
+import com.coinbase.tools.sdkgenerator.processing.OpenApiSchemaCodegen;
+import com.coinbase.tools.sdkgenerator.processing.OpenApiSchemaCodegen.JavaTypeResult;
+import com.coinbase.tools.sdkgenerator.processing.SchemaProperty;
+import com.coinbase.tools.sdkgenerator.processing.ServiceDefinition;
+import com.coinbase.tools.sdkgenerator.processing.SharedTransforms;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOperation;
+import com.coinbase.tools.sdkgenerator.spec.ParsedParameter;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+/**
+ * Emits per-operation *Request.java (builder style matching hand-written Prime SDK).
+ */
+public final class RequestPhase {
+
+ private RequestPhase() {}
+
+ public static String emitRequest(
+ ParsedOpenApiDocument doc,
+ GeneratorConfiguration cfg,
+ SharedTransforms transforms,
+ SdkOperationBinding b,
+ ParsedOperation op) {
+ ServiceDefinition svc = NamingResolver.requireService(cfg, b.getService());
+ String pkg = svc.getJavaPackage();
+ String sdk = b.getSdkMethod();
+ String className = sdk + "Request";
+
+ List pathParams =
+ op.getParameters().stream()
+ .filter(p -> "path".equals(p.getIn()))
+ .collect(Collectors.toList());
+ List queryParams =
+ op.getParameters().stream()
+ .filter(p -> "query".equals(p.getIn()))
+ .collect(Collectors.toList());
+ List bodyProps =
+ op.getRequestBodyJsonSchema() != null
+ ? OpenApiSchemaCodegen.listProperties(
+ doc.getRoot(), op.getRequestBodyJsonSchema(), transforms)
+ : new ArrayList<>();
+
+ boolean paginated =
+ b.isForcePaginated()
+ || queryParams.stream()
+ .anyMatch(
+ p ->
+ "cursor".equals(p.getName())
+ || "sort_direction".equals(p.getName()));
+
+ List queryParamsForMembers =
+ paginated
+ ? queryParams.stream()
+ .filter(
+ p ->
+ !"cursor".equals(p.getName())
+ && !"limit".equals(p.getName())
+ && !"sort_direction"
+ .equals(p.getName()))
+ .collect(Collectors.toList())
+ : queryParams;
+
+ TreeSet modelImports = new TreeSet<>();
+ TreeSet enumImports = new TreeSet<>();
+ for (SchemaProperty p : bodyProps) {
+ modelImports.addAll(p.getReferencedModelTypeNames());
+ enumImports.addAll(p.getReferencedEnumTypeNames());
+ }
+ for (ParsedParameter p : pathParams) {
+ JavaTypeResult tr =
+ OpenApiSchemaCodegen.toJavaType(doc.getRoot(), p.getSchema(), transforms);
+ modelImports.addAll(tr.getModelTypeNames());
+ enumImports.addAll(tr.getEnumTypeNames());
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ JavaTypeResult tr =
+ OpenApiSchemaCodegen.toJavaType(doc.getRoot(), p.getSchema(), transforms);
+ modelImports.addAll(tr.getModelTypeNames());
+ enumImports.addAll(tr.getEnumTypeNames());
+ }
+ if (paginated) {
+ enumImports.add("SortDirection");
+ }
+
+ StringBuilder i = new StringBuilder();
+ i.append(CopyrightHelper.javaFileHeader());
+ i.append("package ").append(pkg).append(";\n\n");
+ i.append("import com.coinbase.core.errors.CoinbaseClientException;\n");
+ if (paginated) {
+ i.append("import com.coinbase.prime.common.PrimeListRequest;\n");
+ i.append("import com.coinbase.prime.common.Pagination;\n");
+ }
+ for (String m : modelImports) {
+ i.append("import com.coinbase.prime.model.").append(m).append(";\n");
+ }
+ for (String e : enumImports) {
+ i.append("import com.coinbase.prime.model.enums.").append(e).append(";\n");
+ }
+ i.append("import com.fasterxml.jackson.annotation.JsonIgnore;\n");
+ i.append("import com.fasterxml.jackson.annotation.JsonProperty;\n");
+ i.append("\nimport static com.coinbase.core.utils.Utils.isNullOrEmpty;\n\n");
+
+ if (op.getSummary() != null && !op.getSummary().isEmpty()) {
+ i.append("/**\n * ").append(escapeJavadoc(op.getSummary())).append("\n */\n");
+ }
+
+ if (paginated) {
+ i.append("public class ").append(className).append(" extends PrimeListRequest {\n");
+ } else {
+ i.append("public class ").append(className).append(" {\n");
+ }
+
+ for (ParsedParameter p : pathParams) {
+ String jt = mapPathParamJava(doc, transforms, b, p);
+ String prop = OpenApiSchemaCodegen.toPascalCase(p.getName());
+ i.append(" @JsonProperty");
+ if (p.isRequired()) {
+ i.append("(required = true, value = \"").append(p.getName()).append("\")");
+ } else {
+ i.append("(\"").append(p.getName()).append("\")");
+ }
+ i.append("\n");
+ i.append(" @JsonIgnore\n");
+ i.append(" private ").append(jt).append(" ").append(camelize(prop)).append(";\n\n");
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ String jt = mapQueryParamJava(doc, transforms, b, p);
+ i.append(" @JsonProperty(\"").append(p.getName()).append("\")\n");
+ i.append(" private ")
+ .append(jt)
+ .append(" ")
+ .append(camelize(OpenApiSchemaCodegen.toPascalCase(p.getName())))
+ .append(";\n\n");
+ }
+ for (SchemaProperty p : bodyProps) {
+ i.append(" @JsonProperty(\"").append(p.getJsonName()).append("\")\n");
+ i.append(" private ")
+ .append(p.getJavaType())
+ .append(" ")
+ .append(camelize(p.getJavaName()))
+ .append(";\n\n");
+ }
+
+ i.append(" public ").append(className).append("() {\n }\n\n");
+
+ if (paginated) {
+ i.append(" public ").append(className).append("(Builder builder) {\n");
+ i.append(" super(builder.cursor, builder.sortDirection, builder.limit);\n");
+ for (ParsedParameter p : pathParams) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ for (SchemaProperty p : bodyProps) {
+ String c = camelize(p.getJavaName());
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ i.append(" }\n\n");
+ } else {
+ i.append(" public ").append(className).append("(Builder builder) {\n");
+ for (ParsedParameter p : pathParams) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ for (SchemaProperty p : bodyProps) {
+ String c = camelize(p.getJavaName());
+ i.append(" this.").append(c).append(" = builder.").append(c).append(";\n");
+ }
+ i.append(" }\n\n");
+ }
+
+ for (ParsedParameter p : pathParams) {
+ appendAccessorPair(
+ i,
+ mapPathParamJava(doc, transforms, b, p),
+ OpenApiSchemaCodegen.toPascalCase(p.getName()),
+ camelize(OpenApiSchemaCodegen.toPascalCase(p.getName())));
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ appendAccessorPair(
+ i,
+ mapQueryParamJava(doc, transforms, b, p),
+ OpenApiSchemaCodegen.toPascalCase(p.getName()),
+ camelize(OpenApiSchemaCodegen.toPascalCase(p.getName())));
+ }
+ for (SchemaProperty p : bodyProps) {
+ appendAccessorPair(
+ i, p.getJavaType(), p.getJavaName(), camelize(p.getJavaName()));
+ }
+
+ i.append(" public static class Builder {\n");
+ for (ParsedParameter p : pathParams) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ String jt = mapPathParamJava(doc, transforms, b, p);
+ i.append(" private ").append(jt).append(" ").append(c).append(";\n");
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ String jt = mapQueryParamJava(doc, transforms, b, p);
+ i.append(" private ").append(jt).append(" ").append(c).append(";\n");
+ }
+ for (SchemaProperty p : bodyProps) {
+ String c = camelize(p.getJavaName());
+ i.append(" private ").append(p.getJavaType()).append(" ").append(c).append(";\n");
+ }
+ if (paginated) {
+ i.append(" private String cursor;\n");
+ i.append(" private SortDirection sortDirection;\n");
+ i.append(" private Integer limit;\n");
+ }
+
+ i.append("\n public Builder() {\n }\n\n");
+
+ for (ParsedParameter p : pathParams) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ String g = OpenApiSchemaCodegen.toPascalCase(p.getName());
+ String jt = mapPathParamJava(doc, transforms, b, p);
+ i.append(" public Builder ")
+ .append(camelize(g))
+ .append("(")
+ .append(jt)
+ .append(" ")
+ .append(c)
+ .append(") {\n");
+ i.append(" this.").append(c).append(" = ").append(c).append(";\n");
+ i.append(" return this;\n");
+ i.append(" }\n\n");
+ }
+ for (ParsedParameter p : queryParamsForMembers) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ String g = OpenApiSchemaCodegen.toPascalCase(p.getName());
+ String jt = mapQueryParamJava(doc, transforms, b, p);
+ i.append(" public Builder ")
+ .append(camelize(g))
+ .append("(")
+ .append(jt)
+ .append(" ")
+ .append(c)
+ .append(") {\n");
+ i.append(" this.").append(c).append(" = ").append(c).append(";\n");
+ i.append(" return this;\n");
+ i.append(" }\n\n");
+ }
+ for (SchemaProperty p : bodyProps) {
+ String c = camelize(p.getJavaName());
+ i.append(" public Builder ")
+ .append(c)
+ .append("(")
+ .append(p.getJavaType())
+ .append(" ")
+ .append(c)
+ .append(") {\n");
+ i.append(" this.").append(c).append(" = ").append(c).append(";\n");
+ i.append(" return this;\n");
+ i.append(" }\n\n");
+ }
+ if (paginated) {
+ i.append(" public Builder limit(Integer limit) {\n");
+ i.append(" this.limit = limit;\n");
+ i.append(" return this;\n");
+ i.append(" }\n\n");
+ i.append(" public Builder pagination(Pagination pagination) {\n");
+ i.append(" this.cursor = pagination.getNextCursor();\n");
+ i.append(" this.sortDirection = pagination.getSortDirection();\n");
+ i.append(" return this;\n");
+ i.append(" }\n\n");
+ }
+
+ i.append(" public ").append(className).append(" build() throws CoinbaseClientException {\n");
+ i.append(" validate();\n");
+ i.append(" return new ").append(className).append("(this);\n");
+ i.append(" }\n\n");
+ i.append(" private void validate() throws CoinbaseClientException {\n");
+ for (ParsedParameter p : pathParams) {
+ if (p.isRequired() && p.getSchema() != null) {
+ String jt = mapPathParamJava(doc, transforms, b, p);
+ if ("String".equals(jt)) {
+ String c = camelize(OpenApiSchemaCodegen.toPascalCase(p.getName()));
+ String g = OpenApiSchemaCodegen.toPascalCase(p.getName());
+ i.append(" if (isNullOrEmpty(this.")
+ .append(c)
+ .append(")) {\n");
+ i.append(" throw new CoinbaseClientException(\"")
+ .append(g)
+ .append(" is required\");\n");
+ i.append(" }\n");
+ }
+ }
+ }
+ i.append(" }\n");
+ i.append(" }\n");
+ i.append("}\n");
+ return i.toString();
+ }
+
+ private static void appendAccessorPair(
+ StringBuilder i, String javaType, String pascalName, String fieldCamel) {
+ i.append(" public ")
+ .append(javaType)
+ .append(" get")
+ .append(pascalName)
+ .append("() {\n");
+ i.append(" return ").append(fieldCamel).append(";\n");
+ i.append(" }\n\n");
+ i.append(" public void set")
+ .append(pascalName)
+ .append("(")
+ .append(javaType)
+ .append(" ")
+ .append(fieldCamel)
+ .append(") {\n");
+ i.append(" this.").append(fieldCamel).append(" = ").append(fieldCamel).append(";\n");
+ i.append(" }\n\n");
+ }
+
+ private static String camelize(String pascal) {
+ if (pascal == null || pascal.isEmpty()) {
+ return pascal;
+ }
+ return pascal.substring(0, 1).toLowerCase(Locale.ROOT) + pascal.substring(1);
+ }
+
+ private static String escapeJavadoc(String s) {
+ return s.replace("*/", "* /");
+ }
+
+ private static String mapPathParamJava(
+ ParsedOpenApiDocument doc, SharedTransforms transforms, SdkOperationBinding b, ParsedParameter p) {
+ if (b.getParamTypeOverrides().containsKey(p.getName()) && b.getParamTypeOverrides().get(p.getName()) != null) {
+ String o = b.getParamTypeOverrides().get(p.getName()).trim();
+ if (!o.isEmpty()) {
+ return o;
+ }
+ }
+ return OpenApiSchemaCodegen.toJavaType(doc.getRoot(), p.getSchema(), transforms).getJavaType();
+ }
+
+ private static String mapQueryParamJava(
+ ParsedOpenApiDocument doc, SharedTransforms transforms, SdkOperationBinding b, ParsedParameter p) {
+ if (b.getParamTypeOverrides().containsKey(p.getName()) && b.getParamTypeOverrides().get(p.getName()) != null) {
+ String o = b.getParamTypeOverrides().get(p.getName()).trim();
+ if (!o.isEmpty()) {
+ return o;
+ }
+ }
+ return OpenApiSchemaCodegen.toJavaType(doc.getRoot(), p.getSchema(), transforms).getJavaType();
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ResponsePhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ResponsePhase.java
new file mode 100644
index 00000000..87bf9cf8
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ResponsePhase.java
@@ -0,0 +1,116 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.sdkgenerator.CopyrightHelper;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.NamingResolver;
+import com.coinbase.tools.sdkgenerator.processing.OpenApiSchemaCodegen;
+import com.coinbase.tools.sdkgenerator.processing.SchemaProperty;
+import com.coinbase.tools.sdkgenerator.processing.ServiceDefinition;
+import com.coinbase.tools.sdkgenerator.processing.SharedTransforms;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOperation;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import com.coinbase.tools.sdkgenerator.spec.SpecRef;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.List;
+import java.util.TreeSet;
+
+public final class ResponsePhase {
+
+ private ResponsePhase() {}
+
+ public static String emitResponse(
+ ParsedOpenApiDocument doc,
+ GeneratorConfiguration cfg,
+ SharedTransforms transforms,
+ SdkOperationBinding b,
+ ParsedOperation op) {
+ ServiceDefinition svc = NamingResolver.requireService(cfg, b.getService());
+ String pkg = svc.getJavaPackage();
+ String name = b.getSdkMethod() + "Response";
+ StringBuilder o = new StringBuilder();
+ o.append(CopyrightHelper.javaFileHeader());
+ o.append("package ").append(pkg).append(";\n\n");
+
+ if (op.getSuccessResponseSchemaRef() == null || op.getSuccessResponseSchemaRef().isEmpty()) {
+ o.append("/**\n * Response for ").append(b.getSdkMethod()).append(".\n */\n");
+ o.append("public class ").append(name).append(" {\n");
+ o.append(" public ").append(name).append("() {\n }\n");
+ o.append("}\n");
+ return o.toString();
+ }
+
+ JsonNode schema = SpecRef.resolveRef(doc.getRoot(), op.getSuccessResponseSchemaRef());
+ if (schema == null) {
+ o.append("public class ").append(name).append(" {\n");
+ o.append(" public ").append(name).append("() {\n }\n");
+ o.append("}\n");
+ return o.toString();
+ }
+ List props = OpenApiSchemaCodegen.listProperties(doc.getRoot(), schema, transforms);
+ TreeSet modelImports = new TreeSet<>();
+ TreeSet enumImports = new TreeSet<>();
+ for (SchemaProperty p : props) {
+ modelImports.addAll(p.getReferencedModelTypeNames());
+ enumImports.addAll(p.getReferencedEnumTypeNames());
+ }
+ for (String m : modelImports) {
+ if ("Pagination".equals(m)) {
+ o.append("import com.coinbase.prime.common.Pagination;\n");
+ } else {
+ o.append("import com.coinbase.prime.model.").append(m).append(";\n");
+ }
+ }
+ for (String e : enumImports) {
+ o.append("import com.coinbase.prime.model.enums.").append(e).append(";\n");
+ }
+ o.append("import com.fasterxml.jackson.annotation.JsonProperty;\n\n");
+
+ if (op.getSummary() != null && !op.getSummary().isEmpty()) {
+ o.append("/**\n * ").append(escape(op.getSummary())).append("\n */\n");
+ }
+ o.append("public class ").append(name).append(" {\n");
+ for (SchemaProperty p : props) {
+ o.append(" @JsonProperty(\"").append(p.getJsonName()).append("\")\n");
+ o.append(" private ")
+ .append(p.getJavaType())
+ .append(" ")
+ .append(camelize(p.getJavaName()))
+ .append(";\n\n");
+ }
+ o.append(" public ").append(name).append("() {\n }\n\n");
+ for (SchemaProperty p : props) {
+ String c = camelize(p.getJavaName());
+ o.append(" public ")
+ .append(p.getJavaType())
+ .append(" get")
+ .append(p.getJavaName())
+ .append("() {\n");
+ o.append(" return ").append(c).append(";\n");
+ o.append(" }\n\n");
+ o.append(" public void set")
+ .append(p.getJavaName())
+ .append("(")
+ .append(p.getJavaType())
+ .append(" ")
+ .append(c)
+ .append(") {\n");
+ o.append(" this.").append(c).append(" = ").append(c).append(";\n");
+ o.append(" }\n\n");
+ }
+ o.append("}\n");
+ return o.toString();
+ }
+
+ private static String camelize(String pascal) {
+ if (pascal == null || pascal.isEmpty()) {
+ return pascal;
+ }
+ return pascal.substring(0, 1).toLowerCase() + pascal.substring(1);
+ }
+
+ private static String escape(String s) {
+ return s.replace("*/", "* /");
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ServicePhase.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ServicePhase.java
new file mode 100644
index 00000000..a4b3692a
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/phases/ServicePhase.java
@@ -0,0 +1,255 @@
+package com.coinbase.tools.sdkgenerator.phases;
+
+import com.coinbase.tools.sdkgenerator.CopyrightHelper;
+import com.coinbase.tools.sdkgenerator.processing.GeneratorConfiguration;
+import com.coinbase.tools.sdkgenerator.processing.ServiceDefinition;
+import com.coinbase.tools.sdkgenerator.processing.OpenApiSchemaCodegen;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOperation;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public final class ServicePhase {
+
+ private static final Pattern PATH_PARAM = Pattern.compile("\\{([^}]+)}");
+
+ private ServicePhase() {}
+
+ public static String emitInterface(
+ ServiceDefinition svc, List operations) {
+ StringBuilder s = new StringBuilder();
+ s.append(CopyrightHelper.javaFileHeader());
+ s.append("package ").append(svc.getJavaPackage()).append(";\n\n");
+ s.append("import com.coinbase.core.errors.CoinbaseClientException;\n");
+ s.append("import com.coinbase.prime.errors.CoinbasePrimeException;\n\n");
+ s.append("public interface ").append(svc.getJavaInterfaceName()).append(" {\n");
+ for (SdkOpPair pair : operations) {
+ if (pair.op.getSummary() != null && !pair.op.getSummary().isEmpty()) {
+ s.append(" /** ").append(escapeJavadoc(pair.op.getSummary())).append(" */\n");
+ }
+ if (pair.binding.isOmitRequest()) {
+ s.append(" ")
+ .append(pair.binding.getSdkMethod())
+ .append("Response ")
+ .append(camelizeMethod(pair.binding.getSdkMethod()))
+ .append("()")
+ .append(" throws CoinbaseClientException, CoinbasePrimeException;\n");
+ } else {
+ s.append(" ")
+ .append(pair.binding.getSdkMethod())
+ .append("Response ")
+ .append(camelizeMethod(pair.binding.getSdkMethod()))
+ .append("(")
+ .append(pair.binding.getSdkMethod())
+ .append("Request request)")
+ .append(" throws CoinbaseClientException, CoinbasePrimeException;\n");
+ }
+ }
+ s.append("}\n");
+ return s.toString();
+ }
+
+ public static String emitServiceImpl(
+ GeneratorConfiguration cfg, ServiceDefinition svc, List operations) {
+ StringBuilder s = new StringBuilder();
+ s.append(CopyrightHelper.javaFileHeader());
+ s.append("package ").append(svc.getJavaPackage()).append(";\n\n");
+ s.append("import com.coinbase.core.common.HttpMethod;\n");
+ s.append("import com.coinbase.core.service.CoinbaseServiceImpl;\n");
+ s.append("import com.coinbase.prime.client.CoinbasePrimeClient;\n");
+ s.append("import com.coinbase.prime.errors.CoinbasePrimeException;\n");
+ s.append("import com.fasterxml.jackson.core.type.TypeReference;\n\n");
+ s.append("import java.util.List;\n\n");
+ s.append("public class ")
+ .append(svc.getJavaServiceImplName())
+ .append(" extends CoinbaseServiceImpl implements ")
+ .append(svc.getJavaInterfaceName())
+ .append(" {\n");
+ s.append(" public ")
+ .append(svc.getJavaServiceImplName())
+ .append("(CoinbasePrimeClient client) {\n");
+ s.append(" super(client);\n }\n\n");
+
+ for (SdkOpPair pair : operations) {
+ String m = pair.binding.getSdkMethod();
+ s.append(" @Override\n");
+ s.append(" public ")
+ .append(m)
+ .append("Response ")
+ .append(camelizeMethod(m))
+ .append("(");
+ if (!pair.binding.isOmitRequest()) {
+ s.append(m).append("Request request");
+ }
+ s.append(") throws CoinbasePrimeException {\n");
+ s.append(" return this.request(\n");
+ s.append(" HttpMethod.").append(httpMethodConst(pair.op.getHttpMethod())).append(",\n");
+ s.append(" ")
+ .append(pathExpression(cfg, pair.binding, pair.op))
+ .append(",\n");
+ s.append(" ")
+ .append(requestBodyArg(pair.binding, pair.op))
+ .append(",\n");
+ s.append(" List.of(")
+ .append(
+ javaStatusList(cfg, pair.binding.getSdkMethod(), pair.op).stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(", ")))
+ .append("),\n");
+ s.append(" new TypeReference<")
+ .append(m)
+ .append("Response>() {});\n");
+ s.append(" }\n\n");
+ }
+ s.append("}\n");
+ return s.toString();
+ }
+
+ public static String requestBodyArg(SdkOperationBinding b, ParsedOperation op) {
+ if (b.isOmitRequest()) {
+ return "null";
+ }
+ return "request";
+ }
+
+ public static String pathExpression(
+ GeneratorConfiguration cfg, SdkOperationBinding b, ParsedOperation op) {
+ if (b.isOmitRequest()) {
+ if (PATH_PARAM.matcher(op.getPath()).find()) {
+ throw new IllegalStateException(
+ "omitRequest operations cannot include path template parameters.");
+ }
+ return stringLiteral(trimApiPrefix(op.getPath()));
+ }
+ String p = trimApiPrefix(op.getPath());
+ List paramNames = new ArrayList<>();
+ Matcher m = PATH_PARAM.matcher(p);
+ while (m.find()) {
+ paramNames.add(m.group(1));
+ }
+ if (paramNames.isEmpty()) {
+ return stringLiteral(p);
+ }
+ String formatStr = p.replaceAll("\\{[^}]+}", "%s");
+ StringBuilder sb = new StringBuilder("String.format(\"" + escapeForJavaString(formatStr) + "\"");
+ for (String raw : paramNames) {
+ String prop = OpenApiSchemaCodegen.toPascalCase(raw);
+ sb.append(", request.get").append(prop).append("()");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private static String stringLiteral(String path) {
+ return "\"" + escapeForJavaString(path) + "\"";
+ }
+
+ private static String escapeForJavaString(String s) {
+ return s.replace("\\", "\\\\").replace("\"", "\\\"");
+ }
+
+ private static String trimApiPrefix(String openApiPath) {
+ if (openApiPath.startsWith("/v1/")) {
+ return openApiPath.substring(3);
+ }
+ return openApiPath;
+ }
+
+ private static String httpMethodConst(String method) {
+ switch (method.toUpperCase()) {
+ case "GET":
+ return "GET";
+ case "POST":
+ return "POST";
+ case "PUT":
+ return "PUT";
+ case "PATCH":
+ return "PATCH";
+ case "DELETE":
+ return "DELETE";
+ default:
+ return "GET";
+ }
+ }
+
+ public static List javaStatusList(
+ GeneratorConfiguration cfg, String sdkMethod, ParsedOperation op) {
+ List codes;
+ if (cfg.getStatusCodeOverrides().containsKey(sdkMethod)
+ && cfg.getStatusCodeOverrides().get(sdkMethod) != null
+ && !cfg.getStatusCodeOverrides().get(sdkMethod).isEmpty()) {
+ codes =
+ cfg.getStatusCodeOverrides().get(sdkMethod).stream()
+ .map(ServicePhase::mapStatusToken)
+ .collect(Collectors.toCollection(ArrayList::new));
+ } else if (op.getSuccessStatusCodes().isEmpty()) {
+ codes = new ArrayList<>();
+ codes.add(200);
+ } else {
+ codes = new ArrayList<>(op.getSuccessStatusCodes());
+ }
+ codes.sort((a, b) -> compareSuccessStatus(a, b));
+ return codes;
+ }
+
+ private static int mapStatusToken(String token) {
+ if (token == null) {
+ return 200;
+ }
+ switch (token.toUpperCase()) {
+ case "OK":
+ return 200;
+ case "CREATED":
+ return 201;
+ case "ACCEPTED":
+ return 202;
+ case "NO_CONTENT":
+ return 204;
+ default:
+ try {
+ return Integer.parseInt(token);
+ } catch (NumberFormatException e) {
+ return 200;
+ }
+ }
+ }
+
+ private static int compareSuccessStatus(int a, int b) {
+ if (a == b) {
+ return 0;
+ }
+ if (a == 201 && b == 200) {
+ return -1;
+ }
+ if (a == 200 && b == 201) {
+ return 1;
+ }
+ return Integer.compare(a, b);
+ }
+
+ private static String escapeJavadoc(String s) {
+ return s == null ? "" : s.replace("*/", "* /");
+ }
+
+ private static String camelizeMethod(String pascal) {
+ if (pascal == null || pascal.isEmpty()) {
+ return pascal;
+ }
+ return pascal.substring(0, 1).toLowerCase() + pascal.substring(1);
+ }
+
+ public static final class SdkOpPair {
+ public final SdkOperationBinding binding;
+ public final ParsedOperation op;
+
+ public SdkOpPair(SdkOperationBinding binding, ParsedOperation op) {
+ this.binding = binding;
+ this.op = op;
+ }
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/GeneratorConfiguration.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/GeneratorConfiguration.java
new file mode 100644
index 00000000..dbb5b2ec
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/GeneratorConfiguration.java
@@ -0,0 +1,270 @@
+package com.coinbase.tools.sdkgenerator.processing;
+
+import com.coinbase.tools.sdkgenerator.GeneratorPaths;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GeneratorConfiguration {
+ @JsonProperty("specUrl")
+ private String specUrl = "https://api.prime.coinbase.com/v1/openapi.yaml";
+
+ @JsonProperty("filePathReplacements")
+ private Map filePathReplacements = new LinkedHashMap<>();
+
+ @JsonProperty("contentReplacements")
+ private Map contentReplacements = new LinkedHashMap<>();
+
+ @JsonProperty("acronymMappings")
+ private List acronymMappings = new ArrayList<>();
+
+ @JsonProperty("enumNameMappings")
+ private Map enumNameMappings = new LinkedHashMap<>();
+
+ @JsonProperty("tagToFolderOverrides")
+ private Map tagToFolderOverrides = new LinkedHashMap<>();
+
+ @JsonIgnore
+ private Map tagToFolder = new LinkedHashMap<>();
+
+ @JsonIgnore
+ private Map services = new LinkedHashMap<>();
+
+ @JsonProperty("serviceMethodOrderOverrides")
+ private Map> serviceMethodOrderOverrides = new LinkedHashMap<>();
+
+ @JsonProperty("statusCodeOverrides")
+ private Map> statusCodeOverrides = new LinkedHashMap<>();
+
+ public String getSpecUrl() {
+ return specUrl;
+ }
+
+ public Map getFilePathReplacements() {
+ return filePathReplacements;
+ }
+
+ public Map getContentReplacements() {
+ return contentReplacements;
+ }
+
+ public List getAcronymMappings() {
+ return acronymMappings;
+ }
+
+ public Map getEnumNameMappings() {
+ return enumNameMappings;
+ }
+
+ public Map getTagToFolderOverrides() {
+ return tagToFolderOverrides;
+ }
+
+ public Map getTagToFolder() {
+ return tagToFolder;
+ }
+
+ public void setTagToFolder(Map tagToFolder) {
+ this.tagToFolder = tagToFolder;
+ }
+
+ public Map getServices() {
+ return services;
+ }
+
+ public void setServices(Map services) {
+ this.services = services;
+ }
+
+ public Map> getServiceMethodOrderOverrides() {
+ return serviceMethodOrderOverrides;
+ }
+
+ public Map> getStatusCodeOverrides() {
+ return statusCodeOverrides;
+ }
+
+ public static GeneratorConfiguration load(Path projectRoot) throws IOException {
+ Path path = GeneratorPaths.configDirectory(projectRoot).resolve("generator-config.json");
+ ObjectMapper om =
+ new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ return om.readValue(path.toFile(), GeneratorConfiguration.class);
+ }
+
+ public static List loadOperationBindingOverrides(Path projectRoot)
+ throws IOException {
+ Path path = GeneratorPaths.configDirectory(projectRoot).resolve("operations-overrides.json");
+ if (!path.toFile().exists()) {
+ return new ArrayList<>();
+ }
+ ObjectMapper om = new ObjectMapper();
+ return om.readValue(
+ path.toFile(),
+ new TypeReference>() {});
+ }
+
+ public static OperationBindingMergeResult mergeOperationBindings(
+ ParsedOpenApiDocument document, GeneratorConfiguration cfg, Path projectRoot)
+ throws IOException {
+ List derived = OperationBindingGenerator.deriveAll(document, cfg);
+ List patches = loadOperationBindingOverrides(projectRoot);
+ Map mergedById = new LinkedHashMap<>();
+ for (SdkOperationBinding b : derived) {
+ mergedById.put(b.getOperationId(), cloneBinding(b));
+ }
+ for (SdkOperationBindingPatch patch : patches) {
+ SdkOperationBinding binding = mergedById.get(patch.getOperationId());
+ if (binding == null) {
+ throw new IllegalStateException(
+ "operations-overrides.json references operationId '"
+ + patch.getOperationId()
+ + "' that is not in the OpenAPI spec.");
+ }
+ applyOperationBindingPatch(binding, patch);
+ }
+ List merged =
+ mergedById.values().stream()
+ .sorted(
+ (a, b) ->
+ a.getOperationId()
+ .compareTo(b.getOperationId()))
+ .collect(Collectors.toList());
+ return new OperationBindingMergeResult(derived, merged, patches);
+ }
+
+ private static SdkOperationBinding cloneBinding(SdkOperationBinding b) {
+ SdkOperationBinding c = new SdkOperationBinding();
+ c.setOperationId(b.getOperationId());
+ c.setSdkMethod(b.getSdkMethod());
+ c.setService(b.getService());
+ c.setOmitRequest(b.isOmitRequest());
+ c.setForcePaginated(b.isForcePaginated());
+ c.setParamTypeOverrides(new HashMap<>(b.getParamTypeOverrides()));
+ return c;
+ }
+
+ private static void applyOperationBindingPatch(
+ SdkOperationBinding binding, SdkOperationBindingPatch patch) {
+ if (patch.getSdkMethod() != null) {
+ binding.setSdkMethod(patch.getSdkMethod());
+ }
+ if (patch.getService() != null) {
+ binding.setService(patch.getService());
+ }
+ if (patch.getOmitRequest() != null) {
+ binding.setOmitRequest(patch.getOmitRequest());
+ }
+ if (patch.getForcePaginated() != null) {
+ binding.setForcePaginated(patch.getForcePaginated());
+ }
+ if (patch.getParamTypeOverrides() != null) {
+ for (Map.Entry e : patch.getParamTypeOverrides().entrySet()) {
+ binding.getParamTypeOverrides().put(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ public static final class AcronymMappingEntry {
+ @JsonProperty("acronym")
+ private String acronym = "";
+
+ @JsonProperty("normalized")
+ private String normalized = "";
+
+ public String getAcronym() {
+ return acronym;
+ }
+
+ public String getNormalized() {
+ return normalized;
+ }
+ }
+
+ public static class SdkOperationBindingPatch {
+ @JsonProperty("operationId")
+ private String operationId = "";
+
+ @JsonProperty("sdkMethod")
+ private String sdkMethod;
+
+ @JsonProperty("service")
+ private String service;
+
+ @JsonProperty("omitRequest")
+ private Boolean omitRequest;
+
+ @JsonProperty("forcePaginated")
+ private Boolean forcePaginated;
+
+ @JsonProperty("paramTypeOverrides")
+ private Map paramTypeOverrides;
+
+ public String getOperationId() {
+ return operationId;
+ }
+
+ public String getSdkMethod() {
+ return sdkMethod;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public Boolean getOmitRequest() {
+ return omitRequest;
+ }
+
+ public Boolean getForcePaginated() {
+ return forcePaginated;
+ }
+
+ public Map getParamTypeOverrides() {
+ return paramTypeOverrides;
+ }
+ }
+
+ public static final class OperationBindingMergeResult {
+ private final List derived;
+ private final List merged;
+ private final List patches;
+
+ public OperationBindingMergeResult(
+ List derived,
+ List merged,
+ List patches) {
+ this.derived = derived;
+ this.merged = merged;
+ this.patches = patches;
+ }
+
+ public List getDerived() {
+ return derived;
+ }
+
+ public List getMerged() {
+ return merged;
+ }
+
+ public List getPatches() {
+ return patches;
+ }
+ }
+
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/NamingResolver.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/NamingResolver.java
new file mode 100644
index 00000000..157b25c2
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/NamingResolver.java
@@ -0,0 +1,14 @@
+package com.coinbase.tools.sdkgenerator.processing;
+
+public final class NamingResolver {
+
+ private NamingResolver() {}
+
+ public static ServiceDefinition requireService(GeneratorConfiguration cfg, String serviceKey) {
+ ServiceDefinition def = cfg.getServices().get(serviceKey);
+ if (def == null) {
+ throw new IllegalStateException("Unknown service key '" + serviceKey + "' in operation bindings.");
+ }
+ return def;
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OpenApiSchemaCodegen.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OpenApiSchemaCodegen.java
new file mode 100644
index 00000000..3b82f0a1
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OpenApiSchemaCodegen.java
@@ -0,0 +1,300 @@
+package com.coinbase.tools.sdkgenerator.processing;
+
+import com.coinbase.tools.sdkgenerator.spec.SpecRef;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * OpenAPI JSON Schema → Java type strings, mirroring the .NET OpenApiSchemaCodegen.
+ */
+public final class OpenApiSchemaCodegen {
+
+ public static class JavaTypeResult {
+ private final String javaType;
+ private final boolean needsModel;
+ private final boolean needsEnum;
+ private final Set modelTypeNames;
+ private final Set enumTypeNames;
+
+ public JavaTypeResult(
+ String javaType,
+ boolean needsModel,
+ boolean needsEnum,
+ Set modelTypeNames,
+ Set enumTypeNames) {
+ this.javaType = javaType;
+ this.needsModel = needsModel;
+ this.needsEnum = needsEnum;
+ this.modelTypeNames = Set.copyOf(modelTypeNames);
+ this.enumTypeNames = Set.copyOf(enumTypeNames);
+ }
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public boolean isNeedsModel() {
+ return needsModel;
+ }
+
+ public boolean isNeedsEnum() {
+ return needsEnum;
+ }
+
+ public Set getModelTypeNames() {
+ return modelTypeNames;
+ }
+
+ public Set getEnumTypeNames() {
+ return enumTypeNames;
+ }
+ }
+
+ private OpenApiSchemaCodegen() {}
+
+ public static JavaTypeResult toJavaType(
+ JsonNode documentRoot, JsonNode node, SharedTransforms transforms) {
+ Set models = new HashSet<>();
+ Set enums = new HashSet<>();
+ String t = mapType(documentRoot, node, transforms, models, enums);
+ return new JavaTypeResult(t, !models.isEmpty(), !enums.isEmpty(), models, enums);
+ }
+
+ private static String mapType(
+ JsonNode documentRoot,
+ JsonNode node,
+ SharedTransforms transforms,
+ Set modelTypes,
+ Set enumTypes) {
+ if (node == null || node.isNull()) {
+ return "Object";
+ }
+ if (!node.isObject()) {
+ return "Object";
+ }
+ if (node.has("$ref") && node.get("$ref").isTextual()) {
+ String r = node.get("$ref").asText();
+ JsonNode resolved = SpecRef.resolveRef(documentRoot, r);
+ String refName = SpecRef.getRefName(r);
+ if (resolved == null || refName == null) {
+ return "Object";
+ }
+ if (resolved.has("enum") && resolved.get("enum").isArray()) {
+ String t = transforms.transformSchemaRefToJavaName(refName);
+ enumTypes.add(t);
+ return t;
+ }
+ String t = transforms.transformSchemaRefToJavaName(refName);
+ modelTypes.add(t);
+ return t;
+ }
+ if (node.has("type")) {
+ String type = node.get("type").asText();
+ if ("array".equals(type) && node.has("items")) {
+ JsonNode items = node.get("items");
+ if (items != null && items.isObject()) {
+ String matched = findMatchingNamedEnum(documentRoot, (ObjectNode) items, transforms);
+ if (matched != null) {
+ enumTypes.add(matched);
+ return matched + "[]";
+ }
+ }
+ String inner = mapType(documentRoot, items, transforms, modelTypes, enumTypes);
+ return normalizeArrayElementType(inner) + "[]";
+ }
+ if ("string".equals(type)
+ && node.has("enum")
+ && node.get("enum").isArray()) {
+ String matched = findMatchingNamedEnum(documentRoot, (ObjectNode) node, transforms);
+ if (matched != null) {
+ enumTypes.add(matched);
+ return matched;
+ }
+ }
+ return mapPrimitive((ObjectNode) node, type);
+ }
+ if (node.has("oneOf") || node.has("anyOf")) {
+ return "Object";
+ }
+ return "Object";
+ }
+
+ private static Set getInlineEnumValues(ObjectNode node) {
+ if (!node.has("enum") || !node.get("enum").isArray()) {
+ return new HashSet<>();
+ }
+ Set s = new HashSet<>();
+ for (JsonNode n : node.get("enum")) {
+ if (n != null && n.isTextual()) {
+ s.add(n.asText());
+ }
+ }
+ return s;
+ }
+
+ private static String findMatchingNamedEnum(
+ JsonNode documentRoot, ObjectNode inlineNode, SharedTransforms transforms) {
+ Set inlineValues = getInlineEnumValues(inlineNode);
+ if (inlineValues.isEmpty()) {
+ return null;
+ }
+ if (!documentRoot.has("components") || !documentRoot.get("components").isObject()) {
+ return null;
+ }
+ JsonNode schemas = documentRoot.get("components").path("schemas");
+ if (!schemas.isObject()) {
+ return null;
+ }
+ Iterator it = ((ObjectNode) schemas).fieldNames();
+ while (it.hasNext()) {
+ String key = it.next();
+ JsonNode schema = schemas.get(key);
+ if (schema == null
+ || !schema.isObject()
+ || !schema.has("enum")
+ || !schema.get("enum").isArray()) {
+ continue;
+ }
+ Set schemaValues = getInlineEnumValues((ObjectNode) schema);
+ if (schemaValues.containsAll(inlineValues)) {
+ return transforms.transformSchemaRefToJavaName(key);
+ }
+ }
+ return null;
+ }
+
+ private static String mapPrimitive(ObjectNode mm, String type) {
+ switch (type) {
+ case "string":
+ return "String";
+ case "integer":
+ if (mm.has("format")
+ && "int64".equals(mm.get("format").asText())) {
+ return "Long";
+ }
+ return "Integer";
+ case "number":
+ return "String";
+ case "boolean":
+ return "Boolean";
+ case "object":
+ return "Object";
+ default:
+ return "String";
+ }
+ }
+
+ public static List listProperties(
+ JsonNode documentRoot, JsonNode schema, SharedTransforms transforms) {
+ List list = new ArrayList<>();
+ if (schema == null
+ || !schema.isObject()
+ || !schema.has("properties")
+ || !schema.get("properties").isObject()) {
+ return list;
+ }
+ ObjectNode schemaObj = (ObjectNode) schema;
+ ObjectNode props = (ObjectNode) schema.get("properties");
+ Set required = new HashSet<>();
+ if (schemaObj.has("required") && schemaObj.get("required").isArray()) {
+ for (JsonNode r : schemaObj.get("required")) {
+ if (r != null && r.isTextual()) {
+ required.add(r.asText());
+ }
+ }
+ }
+ Iterator> it = props.fields();
+ while (it.hasNext()) {
+ Map.Entry e = it.next();
+ String jsonName = e.getKey();
+ JsonNode pnode = e.getValue();
+ String javaName = toPascalCase(jsonName);
+ Set model = new HashSet<>();
+ Set enums = new HashSet<>();
+ String jt = mapType(documentRoot, pnode, transforms, model, enums);
+ boolean isReq = required.contains(jsonName);
+ boolean usesEnum = !enums.isEmpty();
+ if (!isReq && usesEnum && !jt.endsWith("[]")) {
+ jt = optionalScalarEnumJava(jt);
+ }
+ list.add(
+ new SchemaProperty(
+ jsonName,
+ javaName,
+ jt,
+ isReq,
+ !model.isEmpty(),
+ usesEnum,
+ model,
+ enums));
+ }
+ return list;
+ }
+
+ public static String toPascalCase(String snakeOrDotted) {
+ if (snakeOrDotted == null) {
+ return "";
+ }
+ String[] segments = snakeOrDotted.split("\\.");
+ StringBuilder sb = new StringBuilder();
+ for (String segment : segments) {
+ if (segment == null || segment.isEmpty()) {
+ continue;
+ }
+ String[] parts = segment.split("_");
+ for (String p : parts) {
+ if (p == null || p.isEmpty()) {
+ continue;
+ }
+ sb.append(Character.toUpperCase(p.charAt(0)));
+ if (p.length() > 1) {
+ sb.append(p, 1, p.length());
+ }
+ }
+ }
+ String s = sb.toString();
+ return s.isEmpty() ? snakeOrDotted : s;
+ }
+
+ private static String normalizeArrayElementType(String element) {
+ if ("String".equals(element)) {
+ return "String";
+ }
+ return element;
+ }
+
+ private static String optionalScalarEnumJava(String jt) {
+ if (jt.endsWith("?")) {
+ return jt;
+ }
+ return jt; // Java: reference type already nullable; primitives need wrapper — enums are refs
+ }
+
+ public static boolean typeIsEnumRef(JsonNode documentRoot, JsonNode node) {
+ if (node == null) {
+ return false;
+ }
+ if (node.isObject() && node.has("type")) {
+ String type = node.get("type").asText();
+ if ("array".equals(type) && node.has("items")) {
+ return typeIsEnumRef(documentRoot, node.get("items"));
+ }
+ if ("string".equals(type) && node.has("enum") && node.get("enum").isArray()) {
+ return true;
+ }
+ }
+ if (node.isObject() && node.has("$ref") && node.get("$ref").isTextual()) {
+ String r = node.get("$ref").asText();
+ JsonNode resolved = SpecRef.resolveRef(documentRoot, r);
+ return resolved != null && resolved.isObject() && resolved.has("enum");
+ }
+ return false;
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingGenerator.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingGenerator.java
new file mode 100644
index 00000000..51bc70a3
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingGenerator.java
@@ -0,0 +1,150 @@
+package com.coinbase.tools.sdkgenerator.processing;
+
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.ParsedOperation;
+import com.coinbase.tools.sdkgenerator.spec.ParsedParameter;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import com.coinbase.tools.sdkgenerator.spec.SpecResponseSchema;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class OperationBindingGenerator {
+
+ private static final String OPERATION_ID_PREFIX = "PrimeRESTAPI_";
+
+ private OperationBindingGenerator() {}
+
+ public static List deriveAll(
+ ParsedOpenApiDocument doc, GeneratorConfiguration cfg) {
+ SharedTransforms transforms = new SharedTransforms(cfg);
+ List sorted = new ArrayList<>(doc.getOperationsById().values());
+ sorted.sort(Comparator.comparing(ParsedOperation::getOperationId));
+ List list = new ArrayList<>();
+ for (ParsedOperation op : sorted) {
+ list.add(deriveOne(doc.getRoot(), cfg, transforms, op));
+ }
+ return list;
+ }
+
+ private static SdkOperationBinding deriveOne(
+ JsonNode root,
+ GeneratorConfiguration cfg,
+ SharedTransforms transforms,
+ ParsedOperation op) {
+ String sdkMethod = deriveSdkMethod(op, transforms);
+ String service = resolveServiceKey(cfg, op);
+ boolean omitRequest = deriveOmitRequest(op);
+ Map paramOverrides = deriveParamTypeOverrides(root, transforms, op);
+ boolean forcePaginated = deriveForcePaginated(root, op);
+
+ SdkOperationBinding b = new SdkOperationBinding();
+ b.setOperationId(op.getOperationId());
+ b.setSdkMethod(sdkMethod);
+ b.setService(service);
+ b.setOmitRequest(omitRequest);
+ b.setForcePaginated(forcePaginated);
+ b.getParamTypeOverrides().putAll(paramOverrides);
+ return b;
+ }
+
+ private static String deriveSdkMethod(ParsedOperation op, SharedTransforms transforms) {
+ if (op.getExtensionSdkMethodName() != null
+ && !op.getExtensionSdkMethodName().trim().isEmpty()) {
+ String ext = op.getExtensionSdkMethodName().trim();
+ ext = transforms.normalizeAcronyms(ext);
+ return transforms.applyWeb3ToOnchainName(ext);
+ }
+ String name = op.getOperationId();
+ if (name.startsWith(OPERATION_ID_PREFIX)) {
+ name = name.substring(OPERATION_ID_PREFIX.length());
+ }
+ name = transforms.normalizeAcronyms(name);
+ name = transforms.applyWeb3ToOnchainName(name);
+ if ("GET".equals(op.getHttpMethod())
+ && op.getSummary() != null
+ && op.getSummary().startsWith("List ")
+ && name.startsWith("Get")) {
+ name = "List" + name.substring(3);
+ }
+ name = applyPortfolioPathPrefix(name, op);
+ return name;
+ }
+
+ private static final java.util.Set PORTFOLIO_SCOPED_GET_SUFFIXES =
+ new java.util.HashSet<>(java.util.Arrays.asList("BuyingPower", "WithdrawalPower"));
+
+ private static String applyPortfolioPathPrefix(String name, ParsedOperation op) {
+ String path = op.getPath();
+ if (path == null
+ || (!path.contains("{portfolio_id}") && !path.contains("/portfolios/"))) {
+ return name;
+ }
+ if (!name.startsWith("Get")
+ || name.contains("Portfolio")
+ || name.length() <= 3) {
+ return name;
+ }
+ String rest = name.substring(3);
+ if (!PORTFOLIO_SCOPED_GET_SUFFIXES.contains(rest)) {
+ return name;
+ }
+ return "GetPortfolio" + rest;
+ }
+
+ private static String resolveServiceKey(GeneratorConfiguration cfg, ParsedOperation op) {
+ for (String tag : op.getTags()) {
+ if (cfg.getTagToFolder().containsKey(tag)) {
+ String folder = cfg.getTagToFolder().get(tag);
+ if (cfg.getServices().containsKey(folder)) {
+ return folder;
+ }
+ }
+ }
+ throw new IllegalStateException(
+ "Operation '"
+ + op.getOperationId()
+ + "' has no tag mapped in tagToFolder (tags: "
+ + String.join(", ", op.getTags())
+ + ").");
+ }
+
+ private static boolean deriveOmitRequest(ParsedOperation op) {
+ return op.getParameters().isEmpty() && op.getRequestBodyJsonSchema() == null;
+ }
+
+ private static boolean deriveForcePaginated(JsonNode root, ParsedOperation op) {
+ return "GET".equals(op.getHttpMethod())
+ && SpecResponseSchema.responseSchemaSuggestsPagination(root, op);
+ }
+
+ private static Map deriveParamTypeOverrides(
+ JsonNode root, SharedTransforms transforms, ParsedOperation op) {
+ Map result = new HashMap<>();
+ for (ParsedParameter p : op.getParameters()) {
+ if (!"query".equals(p.getIn())) {
+ continue;
+ }
+ if (!"symbols".equals(p.getName())) {
+ continue;
+ }
+ JsonNode schema = p.getSchema();
+ if (schema == null || !schema.isObject()) {
+ continue;
+ }
+ if (!schema.has("type") || !"string".equals(schema.get("type").asText())) {
+ continue;
+ }
+ OpenApiSchemaCodegen.JavaTypeResult def =
+ OpenApiSchemaCodegen.toJavaType(root, schema, transforms);
+ if (!"String[]".equals(def.getJavaType())) {
+ result.put("symbols", "String[]");
+ }
+ }
+ return result;
+ }
+}
diff --git a/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingValidator.java b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingValidator.java
new file mode 100644
index 00000000..aa8d22fe
--- /dev/null
+++ b/tools/model-generator/src/main/java/com/coinbase/tools/sdkgenerator/processing/OperationBindingValidator.java
@@ -0,0 +1,95 @@
+package com.coinbase.tools.sdkgenerator.processing;
+
+import com.coinbase.tools.sdkgenerator.spec.ParsedOpenApiDocument;
+import com.coinbase.tools.sdkgenerator.spec.SdkOperationBinding;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public final class OperationBindingValidator {
+
+ private OperationBindingValidator() {}
+
+ public static void validateOperationBindings(
+ Logger logger,
+ ParsedOpenApiDocument document,
+ GeneratorConfiguration.OperationBindingMergeResult merge) {
+ List merged = merge.getMerged();
+ List missing =
+ merged.stream()
+ .filter(b -> !document.getOperationsById().containsKey(b.getOperationId()))
+ .map(SdkOperationBinding::getOperationId)
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ if (!missing.isEmpty()) {
+ throw new IllegalStateException(
+ "Merged operation bindings reference operationId values that are not in the OpenAPI spec: "
+ + String.join(", ", missing));
+ }
+ Map