Skip to content

Commit

Permalink
BATM-6210 Coinbase CDP authorization support (#974)
Browse files Browse the repository at this point in the history
* BATM-7108 Add Mockito
* BATM-7108 Implement new CDP api key authorization
* BATM-7108 Fix CoinbaseCdpDigest overriding wrong class
* BATM-7123 Create a wrapper for CoinbaseExchange API calls
* BATM-7123 Remove CoinbaseApiWrapper#getApiKey
* BATM-7123 Rename ICoinbaseV2API to ICoinbaseV2APILegacy
* BATM-7123 Create a wrapper for CoinbaseWalletV2 API calls
* BATM-7123 Add missing Copyright
* BATM-7110 Create a factory for creating API proxies
* BATM-7110 Pass api proxy to wrappers directly
* BATM-7110 Make methods in CoinbaseApiFactory static
* BATM-7110 Use api wrapper in CoinbaseExchange
* BATM-7110 Rename apiWrapper field to api
* BATM-7110 Add validation for credentials in CoinbaseV2ApiWrapperLegacy
* BATM-7110 Use api wrapper in coinbase wallets
* BATM-7110 Use api wrapper in coinbase rate source
* BATM-7110 Fix wrong getter
* BATM-7107 Prepare API interfaces and DTOs
* BATM-7107 Improve CoinbaseApiException#getMessage and add tests
* BATM-7107 Remove CoinbaseCredentials from this PR, it is unused
* BATM-7107 Delete ICoinbaseLegacyApi
* BATM-7107 getPrice currency pair as single parameter
* BATM-7107 Rename API interfaces for backward compatibility
* BATM-7107 Add some missing fields to CoinbaseAccount
* BATM-7107 Add a missing field to CoinbaseServerTime
* BATM-7107 Add the rest of needed endpoints to ICoinbaseV3Api
* BATM-7109 Create a mapper between v2 and v3 api dtos
* BATM-7109 Rename mapper to CoinbaseV2ApiMapper
* BATM-7109 CoinbaseApiFactory add method to create v3 api proxy
* BATM-7109 Implement v2 api wrapper for new cdp api
* BATM-7109 Create mapper between v1 and v3 api dtos
* BATM-7109 Make fields private in CoinbaseAddress dto
* BATM-7109 Add private constructors to utility methods
* BATM-7109 Remove unused dto
* BATM-7109 Fix transaction amount dtos - different name for value field
* BATM-7109 Idem in send coins request must be a UUIDv4
* BATM-7109 Prevent possible NPE
* BATM-7109 Remove TODOs
* BATM-7109 Add error response to order response
* BATM-7109 Assert the expected value in test
* BATM-7109 Implement cdp wrapper for v1 api
* BATM-7109 Add logs to v2 cdp api wrapper
* BATM-7124 Rename coinbase params in crypto settings help
* BATM-7124 Add new wallets and exchange for cdp api keys
* BATM-7111 Use new api wrappers in exchange and wallets
  • Loading branch information
d0by1 authored Feb 12, 2025
1 parent 00fd8f6 commit fb98953
Show file tree
Hide file tree
Showing 67 changed files with 7,500 additions and 121 deletions.
79 changes: 79 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,14 @@
<sha256 value="2b5352d674ebc2c3f09b1e83b5a4caed224da16dd71cf41afb77af2b53a39be5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.nimbusds" name="nimbus-jose-jwt" version="9.40">
<artifact name="nimbus-jose-jwt-9.40.jar">
<sha256 value="77128ed53756421bf59d2fc7f31554da29ea81cad8a5345977275adb7c5254c8" origin="Generated by Gradle"/>
</artifact>
<artifact name="nimbus-jose-jwt-9.40.pom">
<sha256 value="e675362773bdc550ad64869450b0aa7215c4419cac32cb9b5102d0cee46f69c1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.onfido" name="onfido-api-java" version="2.3.1">
<artifact name="onfido-api-java-2.3.1.jar">
<sha256 value="8b31a855c87e93fb9ba0f4831edd69ce7f4c52761484576f729a2697f1f1e013" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -1197,6 +1205,48 @@
<sha256 value="c68defdedaaaeae1432e12a5302bf2bfa05057d8b5acc65aaa3f3d9853ff40d6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy" version="1.12.19">
<artifact name="byte-buddy-1.12.19.jar">
<sha256 value="030704139e46f32c38d27060edee9e0676b0a0fff8a8be53461515154ba8a7be" origin="Generated by Gradle"/>
</artifact>
<artifact name="byte-buddy-1.12.19.pom">
<sha256 value="435fb8664aa9b7e120c8dd6c707d4eafa642fa262dff6d5e3f71dc25c69e89eb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy" version="1.15.11">
<artifact name="byte-buddy-1.15.11.jar">
<sha256 value="fa08998aae1e7bdae83bde0712c50e8444d71c0e0c196bb2247ade8d4ad0eb90" origin="Generated by Gradle"/>
</artifact>
<artifact name="byte-buddy-1.15.11.pom">
<sha256 value="205b8b254196717e81dad672bb869a719afc96df297f97d811effe1f43653dae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-agent" version="1.12.19">
<artifact name="byte-buddy-agent-1.12.19.jar">
<sha256 value="3a70240de7cdcde04e7c504c2327d7035b9c25ae0206881e3bf4e6798a273ed8" origin="Generated by Gradle"/>
</artifact>
<artifact name="byte-buddy-agent-1.12.19.pom">
<sha256 value="b5a2cff643681de1687fc40acd251feefdfca23e673e049fb3c3692b53526d4b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-agent" version="1.15.11">
<artifact name="byte-buddy-agent-1.15.11.jar">
<sha256 value="316d2c0795c2a4d4c4756f2e6f9349837c7430ac34e0477ead874d05f5cc19e5" origin="Generated by Gradle"/>
</artifact>
<artifact name="byte-buddy-agent-1.15.11.pom">
<sha256 value="b5fa1396f14797b8d808827e7743ba8a5f203b4889be32e4963d44bd5ed759a8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-parent" version="1.12.19">
<artifact name="byte-buddy-parent-1.12.19.pom">
<sha256 value="72ab6fef409e812921f4728b3c4b6ef4fa53bc25fabb0488fc2cae367368b54d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-parent" version="1.15.11">
<artifact name="byte-buddy-parent-1.15.11.pom">
<sha256 value="8dc519d7a3e792112a7cd841eafbec4f00dbc633d085add20c45f0ab476ca496" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.i2p.crypto" name="eddsa" version="0.3.0">
<artifact name="eddsa-0.3.0.jar">
<sha256 value="4dda1120db856640dbec04140ed23242215a075fe127bdefa0dcfa29fb31267d" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -1864,6 +1914,35 @@
<sha256 value="04a6645cab0953b1239c692bf3561ca6f90828df33134253dd0e59d950e63dbb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mockito" name="mockito-core" version="4.11.0">
<artifact name="mockito-core-4.11.0.jar">
<sha256 value="4b909690cab288c761eb94c0bf0e814496cf3921d8affac84cd87774530351e5" origin="Generated by Gradle"/>
</artifact>
<artifact name="mockito-core-4.11.0.pom">
<sha256 value="2bc2e3c8c8b313fdac231f23822225b07831dc899d067072f49d5f8ae37e739a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mockito" name="mockito-inline" version="4.11.0">
<artifact name="mockito-inline-4.11.0.jar">
<sha256 value="ee52e1c299a632184fba274a9370993e09140429f5e516e6c5570fd6574b297f" origin="Generated by Gradle"/>
</artifact>
<artifact name="mockito-inline-4.11.0.pom">
<sha256 value="83e63e9c1c688ad0522146b4aa77948c9973ac01829e10336462369561a80344" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.objenesis" name="objenesis" version="3.3">
<artifact name="objenesis-3.3.jar">
<sha256 value="02dfd0b0439a5591e35b708ed2f5474eb0948f53abf74637e959b8e4ef69bfeb" origin="Generated by Gradle"/>
</artifact>
<artifact name="objenesis-3.3.pom">
<sha256 value="ba0c40da2669a048b6e24ef7066a471f0fbcbfcc509e6a3e856ca4ddfa614ad3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.objenesis" name="objenesis-parent" version="3.3">
<artifact name="objenesis-parent-3.3.pom">
<sha256 value="305c384aa2f1e1c7fe53a96da41c3ec35243b97d428d24a8f779818cc10be4ff" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2" name="ow2" version="1.5">
<artifact name="ow2-1.5.pom">
<sha256 value="0f8a1b116e760b8fe6389c51b84e4b07a70fc11082d4f936e453b583dd50b43b" origin="Generated by Gradle"/>
Expand Down
4 changes: 4 additions & 0 deletions server_extensions_extra/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ dependencies {
implementation("javax.xml.bind:jaxb-api:2.3.1")
implementation("javax.annotation:javax.annotation-api:1.3.2")
implementation("com.sun.xml.bind:jaxb-impl:2.3.9")
implementation("org.bouncycastle:bcpkix-jdk15on:1.63")
implementation("com.nimbusds:nimbus-jose-jwt:9.40")

runtimeOnly("com.google.protobuf:protobuf-java:3.13.0")

Expand All @@ -102,6 +104,8 @@ dependencies {
testImplementation("ch.qos.logback:logback-core:1.2.9")
testImplementation("org.assertj:assertj-core:3.19.0")
testImplementation('org.glassfish.jersey.core:jersey-common:2.25.1')
testImplementation("org.mockito:mockito-core:4.11.0")
testImplementation("org.mockito:mockito-inline:4.11.0")
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,24 @@

import com.generalbytes.batm.common.currencies.CryptoCurrency;
import com.generalbytes.batm.common.currencies.FiatCurrency;
import com.generalbytes.batm.server.extensions.*;
import com.generalbytes.batm.server.extensions.AbstractExtension;
import com.generalbytes.batm.server.extensions.ExtensionsUtil;
import com.generalbytes.batm.server.extensions.FixPriceRateSource;
import com.generalbytes.batm.server.extensions.ICryptoAddressValidator;
import com.generalbytes.batm.server.extensions.ICryptoCurrencyDefinition;
import com.generalbytes.batm.server.extensions.IExchange;
import com.generalbytes.batm.server.extensions.IExtensionContext;
import com.generalbytes.batm.server.extensions.IPaperWalletGenerator;
import com.generalbytes.batm.server.extensions.IPaymentProcessor;
import com.generalbytes.batm.server.extensions.IRateSource;
import com.generalbytes.batm.server.extensions.IWallet;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseApiFactory;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseApiWrapper;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseApiWrapperCdp;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseApiWrapperLegacy;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseV2ApiWrapperCdp;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.CoinbaseV2ApiWrapperLegacy;
import com.generalbytes.batm.server.extensions.extra.bitcoin.coinbase.api.ICoinbaseV3Api;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.binance.BinanceComExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.binance.BinanceUsExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitbuy.BitbuyExchange;
Expand All @@ -30,12 +46,13 @@
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitstamp.BitstampExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bittrex.BittrexExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.coinbase.CoinbaseExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.coinbase.ICoinbaseAPI;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.coinbasepro.CoinbaseProExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.coingi.CoingiExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.coinzix.CoinZixExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.dvchain.DVChainExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.hitbtc.HitbtcExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.enigma.EnigmaExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.hitbtc.HitbtcExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.poloniex.PoloniexExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.stillmandigital.StillmanDigitalExchange;
import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.bitcoinpay.BitcoinPayPP;
Expand All @@ -54,6 +71,7 @@
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinbase.v2.CoinbaseV2RateSource;
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinbase.v2.CoinbaseWalletV2;
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinbase.v2.CoinbaseWalletV2WithUniqueAddresses;
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinbase.v2.ICoinbaseV2API;
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.CryptXWallet;
import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.CryptXWithUniqueAddresses;
import com.generalbytes.batm.server.extensions.extra.ethereum.UsdcDefinition;
Expand All @@ -62,12 +80,17 @@

import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;

import static com.generalbytes.batm.common.currencies.CryptoCurrency.USDT;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitflyer.BitFlyerExchange.BITFLYER_COM_BASE_URL;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.exchanges.bitflyer.BitFlyerExchange.BITFLYER_JP_BASE_URL;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.ICryptXAPI.*;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.ICryptXAPI.PRIORITY_CUSTOM;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.ICryptXAPI.PRIORITY_HIGH;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.ICryptXAPI.PRIORITY_LOW;
import static com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.cryptx.v2.ICryptXAPI.PRIORITY_MEDIUM;

public class BitcoinExtension extends AbstractExtension {
private IExtensionContext ctx;
Expand Down Expand Up @@ -124,23 +147,19 @@ public IExchange createExchange(String paramString) {
} else if ("coinbaseexchange".equalsIgnoreCase(prefix)) {
String apiKey = paramTokenizer.nextToken().trim();
String secretKey = paramTokenizer.nextToken().trim();

String accountName = null;
String preferedFiatCurrency = null;
String paymentMethodName = null;

if(paramTokenizer.hasMoreTokens()) {
accountName = paramTokenizer.nextToken().trim();
}

if(paramTokenizer.hasMoreTokens()) {
preferedFiatCurrency = paramTokenizer.nextToken().toUpperCase().trim();
}

if(paramTokenizer.hasMoreTokens()) {
paymentMethodName = paramTokenizer.nextToken().trim();
}
return new CoinbaseExchange(apiKey, secretKey, accountName, preferedFiatCurrency, paymentMethodName);
CoinbaseExchangeParameters parameters = getCoinbaseExchangeParameters(paramTokenizer);

ICoinbaseAPI api = CoinbaseApiFactory.createCoinbaseApiLegacy();
CoinbaseApiWrapper apiWrapper = new CoinbaseApiWrapperLegacy(api, apiKey, secretKey);
return new CoinbaseExchange(apiWrapper, parameters.accountName, parameters.preferredFiatCurrency, parameters.paymentMethodName);
} else if ("coinbaseexchange2".equalsIgnoreCase(prefix)) {
String privateKey = paramTokenizer.nextToken().trim();
String keyName = paramTokenizer.nextToken().trim();
CoinbaseExchangeParameters parameters = getCoinbaseExchangeParameters(paramTokenizer);

ICoinbaseV3Api api = CoinbaseApiFactory.createCoinbaseV3Api();
CoinbaseApiWrapperCdp apiWrapper = new CoinbaseApiWrapperCdp(api, privateKey, keyName);
return new CoinbaseExchange(apiWrapper, parameters.accountName, parameters.preferredFiatCurrency, parameters.paymentMethodName);
} else if ("coinbasepro".equalsIgnoreCase(prefix)) {
String preferredFiatCurrency = FiatCurrency.USD.getCode();
String key = paramTokenizer.nextToken();
Expand Down Expand Up @@ -237,6 +256,38 @@ public IExchange createExchange(String paramString) {
return null;
}

private CoinbaseExchangeParameters getCoinbaseExchangeParameters(StringTokenizer paramTokenizer) {
String accountName = null;
String preferredFiatCurrency = null;
String paymentMethodName = null;

if (paramTokenizer.hasMoreTokens()) {
accountName = paramTokenizer.nextToken().trim();
}

if (paramTokenizer.hasMoreTokens()) {
preferredFiatCurrency = paramTokenizer.nextToken().toUpperCase().trim();
}

if (paramTokenizer.hasMoreTokens()) {
paymentMethodName = paramTokenizer.nextToken().trim();
}

return new CoinbaseExchangeParameters(accountName, preferredFiatCurrency, paymentMethodName);
}

private static class CoinbaseExchangeParameters {
private final String accountName;
private final String preferredFiatCurrency;
private final String paymentMethodName;

public CoinbaseExchangeParameters(String accountName, String preferredFiatCurrency, String paymentMethodName) {
this.accountName = accountName;
this.preferredFiatCurrency = preferredFiatCurrency;
this.paymentMethodName = paymentMethodName;
}
}

@Override
public IPaymentProcessor createPaymentProcessor(String paymentProcessorLogin) {
if (paymentProcessorLogin != null && !paymentProcessorLogin.trim().isEmpty()) {
Expand Down Expand Up @@ -354,22 +405,28 @@ public IWallet createWallet(String walletLogin, String tunnelPassword) {

return new BitgoWallet(scheme, host, port, token, walletId, walletPassphrase, numBlocks, feeRate, maxFeeRate);

} else if ("coinbasewallet2".equalsIgnoreCase(walletType)
|| "coinbasewallet2noforward".equalsIgnoreCase(walletType)) {
} else if ("coinbasewallet2".equalsIgnoreCase(walletType) || "coinbasewallet2noforward".equalsIgnoreCase(walletType)) {
String apiKey = st.nextToken();
String secretKey = st.nextToken();
String accountName = getCoinbaseWalletAccountNameParameter(st);

String accountName = null;
if (st.hasMoreTokens()) {
accountName = st.nextToken();
if (accountName.trim().isEmpty()) {
accountName = null;
}
}
ICoinbaseV2API api = CoinbaseApiFactory.createCoinbaseV2ApiLegacy();
CoinbaseV2ApiWrapperLegacy apiWrapper = new CoinbaseV2ApiWrapperLegacy(api, apiKey, secretKey);
if ("coinbasewallet2noforward".equalsIgnoreCase(walletType)) {
return new CoinbaseWalletV2WithUniqueAddresses(apiKey, secretKey, accountName);
return new CoinbaseWalletV2WithUniqueAddresses(apiWrapper, accountName);
}
return new CoinbaseWalletV2(apiWrapper, accountName);
} else if ("coinbasewallet3".equalsIgnoreCase(walletType) || "coinbasewallet3noforward".equalsIgnoreCase(walletType)) {
String privateKey = st.nextToken();
String keyName = st.nextToken();
String accountName = getCoinbaseWalletAccountNameParameter(st);

ICoinbaseV3Api api = CoinbaseApiFactory.createCoinbaseV3Api();
CoinbaseV2ApiWrapperCdp apiWrapper = new CoinbaseV2ApiWrapperCdp(api, privateKey, keyName);
if ("coinbasewallet3noforward".equalsIgnoreCase(walletType)) {
return new CoinbaseWalletV2WithUniqueAddresses(apiWrapper, accountName);
}
return new CoinbaseWalletV2(apiKey, secretKey, accountName);
return new CoinbaseWalletV2(apiWrapper, accountName);
} else if ("cryptx".equalsIgnoreCase(walletType) || "cryptxnoforward".equalsIgnoreCase(walletType)) {

String first = st.nextToken();
Expand Down Expand Up @@ -444,6 +501,16 @@ public IWallet createWallet(String walletLogin, String tunnelPassword) {
return null;
}

private String getCoinbaseWalletAccountNameParameter(StringTokenizer stringTokenizer) {
if (stringTokenizer.hasMoreTokens()) {
String accountName = stringTokenizer.nextToken();
if (!accountName.trim().isEmpty()) {
return accountName;
}
}
return null;
}

@Override
public ICryptoAddressValidator createAddressValidator(String cryptoCurrency) {
if (CryptoCurrency.BNB.getCode().equalsIgnoreCase(cryptoCurrency)) {
Expand Down Expand Up @@ -520,7 +587,10 @@ public IRateSource createRateSource(String sourceLogin) {
if (st.hasMoreTokens()) {
preferredFiatCurrency = st.nextToken().toUpperCase();
}
return new CoinbaseV2RateSource(preferredFiatCurrency);
// Rate Source doesn't need credentials
ICoinbaseV2API api = CoinbaseApiFactory.createCoinbaseV2ApiLegacy();
CoinbaseV2ApiWrapperLegacy apiWrapper = new CoinbaseV2ApiWrapperLegacy(api, null, null);
return new CoinbaseV2RateSource(preferredFiatCurrency, apiWrapper);
} else if ("coinbasepro".equalsIgnoreCase(rsType)) {
String preferredFiatCurrency = FiatCurrency.USD.getCode();
if (st.hasMoreTokens()) {
Expand Down
Loading

0 comments on commit fb98953

Please sign in to comment.