Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Proxy Server Interoperability #66

Merged
merged 2 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ This repository provides an example app that demonstrates how the APIs should be
> To run the example app, populate, or replace, this file with the Cloud API Key file provided to you
> by Coinbase.

### Running in Proxy-Mode (recommended)

To run the example app using `proxy-mode`:

1. Ensure the proxy server is correctly set up and active at the designated endpoint. The default endpoint is `localhost:8091`.
2. If your proxy server endpoint differs from the default, update the `proxyUrl` in the `config.json` file accordingly.
3. On app startup, choose `Proxy Mode` from the Mode Selection screen.

### Running in Direct-Mode (not recommended)

To run the example app using `direct-mode`:

1. Populate the `.coinbase_cloud_api_key.json` file with your personal API credentials.
2. On app startup, select `Direct Mode` from the Mode Selection screen.

### iOS
Ensure you have XCode open and run the following from the root directory of the repository:

Expand Down Expand Up @@ -103,6 +118,8 @@ yarn example start # Start the Metro server
yarn example android # Build and start the app on Android emulator
```

By following the above steps, you should be able to run the example app either in proxy-mode or direct-mode based on your preference and setup.

## Recommended Architecture

Broadly speaking, there are two possible approaches to using the WaaS SDK:
Expand Down Expand Up @@ -132,6 +149,25 @@ The methods from the WaaS SDK which are _required_ to be used for participation
3. `getRegistrationData`
4. `computeMPCOperation`

## Proxy-Mode vs. Direct-Mode

Users can switch between two distinct operating modes: `proxy-mode` and `direct-mode`.

### Proxy-Mode (recommended)

**Proxy-mode** allows the application to connect to the Coinbase WaaS API through a proxy server. The primary features of this mode include:

- Initiate the SDK without needing the Coinbase Cloud API key details in the app itself. Communication will be authenticated during Proxy-Server <> WaaS API.
- The SDK, by default, points to a proxy server endpoint at `localhost:8091`. Update this `proxyUrl` in the `config.json` file.
- Cloud credentials are stored in the proxy server, eliminating the need to have them on the application side.

### Direct-Mode (not recommended)

**Direct-mode** enables the app to connect directly to the Coinbase WaaS API, bypassing the need for a proxy server. Key aspects of this mode are:

- The app communicates directly with the Coinbase WaaS API, with no involvement of a proxy server.
- Users must provide the API Key and private key for communication, retrieved from the `.coinbase_cloud_api_key.json` file.

# Native Waas SDK

We expose a Java 8+, `java.util.concurrent.Future`-based SDK for use with Java/Kotlin. An example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,36 @@
*/
public class MPCKeyService {

// The URL of the MPCKeyService.
public static final String mpcKeyServiceUrl = "https://api.developer.coinbase.com/waas/mpc_keys";

// The URL of the MPCKeyService when running in "direct mode".
public static final String mpcKeyServiceWaaSUrl = "https://api.developer.coinbase.com/waas/mpc_keys";
// The handle to the Go MPCKeyService client.
com.waassdkinternal.v1.MPCKeyService keyClient;

ExecutorService executor;

/**
* Initializes the MPCKeyService, for a given apiKey / privateKey.
* Initializes the MPCKeyService with the given Cloud API Key parameters or proxy URL.
* Utilizes `proxyUrl` and operates in insecure mode if either `apiKeyName` or `privateKey` is missing.
* Uses direct WaaS URL with the API keys if both are provided.
* NOTE: You should almost never include this statically in your code, and you should
* call our endpoints via a proxy service. This API will change in the future
* to accommodate proxy services better.
* call our endpoints via a proxy service. This API will change in the future
* to accommodate proxy services better.
*/
public MPCKeyService(String apiKeyName, String privateKey, ExecutorService executor) throws WaasException {
public MPCKeyService(String apiKeyName, String privateKey, String proxyUrl, ExecutorService executor) throws WaasException {
Bool insecure;

String mpcKeyServiceUrl;

if (apiKeyName == "" && privateKey == "") {
mpcKeyServiceUrl = proxyUrl;
insecure = true;
} else {
mpcKeyServiceUrl = mpcKeyServiceWaaSUrl;
insecure = false;
}

try {
keyClient = newMPCKeyService(mpcKeyServiceUrl, apiKeyName, privateKey);
keyClient = newMPCKeyService(mpcKeyServiceUrl, apiKeyName, privateKey, insecure);
this.executor = executor;
} catch (Exception e) {
throw new WaasException("Error initializing mpckey-service: ", e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
* Methods for creating and managing MPC wallets with Coinbase.
*/
public class MPCWalletService {
public static final String mpcWalletServiceUrl = "https://api.developer.coinbase.com/waas/mpc_wallets";
// The URL of the MPCWalletService when running in "direct mode".
public static final String mpcWalletServiceWaaSUrl = "https://api.developer.coinbase.com/waas/mpc_wallets";

com.waassdkinternal.v1.MPCWalletService walletsClient;

Expand All @@ -27,15 +28,27 @@ private <T> Future<T> call(Callable<T> callable) {
return executor.submit(callable);
}


/**
* Initializes the MPCWalletService with the given Cloud API Key parameters.
* NOTE: You should almost-never include these credentials in your app, and instead
* delegate to a proxy server to perform these calls.
* Initializes the MPCWalletService with the given Cloud API Key parameters or proxy URL.
* Utilizes `proxyUrl` and operates in insecure mode if either `apiKeyName` or `privateKey` is missing.
* NOTE: You should almost never include these credentials in your app, and instead
* delegate to a proxy server to perform these calls.
*/
public MPCWalletService(String apiKeyName, String privateKey, ExecutorService executor) throws WaasException {
public MPCWalletService(String apiKeyName, String privateKey, String proxyUrl, ExecutorService executor) throws WaasException {
Bool insecure;

String mpcWalletServiceUrl;

if (apiKeyName == "" && privateKey == "") {
mpcWalletServiceUrl = proxyUrl;
insecure = true;
} else {
mpcWalletServiceUrl = mpcWalletServiceWaaSUrl;
insecure = false;
}

try {
walletsClient = newMPCWalletService(mpcWalletServiceUrl, apiKeyName, privateKey);
walletsClient = newMPCWalletService(mpcWalletServiceUrl, apiKeyName, privateKey, insecure);
this.executor = executor;
} catch (Exception e) {
throw new WaasException("initialize MPC wallet service failed : ", e.getMessage());
Expand Down
28 changes: 21 additions & 7 deletions android-native/src/main/java/com/coinbase/waassdk/PoolService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,35 @@
* A pool represents a group of related wallets.
*/
public class PoolService {
// The URL of the PoolService.
public static String poolServiceUrl = "https://api.developer.coinbase.com/waas/pools";

// The URL of the PoolService when running in "direct mode".
public static final String poolServiceWaaSUrl = "https://api.developer.coinbase.com/waas/pools";
// The handle to the Go PoolService client.
com.waassdkinternal.v1.PoolService poolClient;
ExecutorService executor;

/**
* Initializes the PoolService with the given Cloud API Key parameters. Resolves with the string "success" on success;
* rejects with an error otherwise.
* Initializes the PoolService with the given Cloud API Key parameters or proxy URL.
* Utilizes `proxyUrl` and operates in insecure mode if either `apiKeyName` or `privateKey` is missing.
* Uses direct WaaS URL with the API keys if both are provided.
* Resolves with the string "success" on success; rejects with an error otherwise.
*/
public PoolService(String apiKeyName, String privateKey, ExecutorService executor) throws WaasException {
public PoolService(String apiKeyName, String privateKey, String proxyUrl, ExecutorService executor) throws WaasException {
this.executor = executor;

Bool insecure;

String poolServiceUrl;

if (apiKeyName == "" && privateKey == "") {
poolServiceUrl = proxyUrl;
insecure = true;
} else {
poolServiceUrl = poolServiceWaaSUrl;
insecure = false;
}

try {
poolClient = newPoolService(poolServiceUrl, apiKeyName, privateKey);
poolClient = newPoolService(poolServiceUrl, apiKeyName, privateKey, insecure);
} catch (Exception e) {
throw new WaasException("initialize pool failed : ", e.getMessage());
}
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ WaasSdkReactNative_minSdkVersion=21
WaasSdkReactNative_targetSdkVersion=31
WaasSdkReactNative_compileSdkVersion=31
WaasSdkReactNative_ndkversion=21.4.7075529
WaasSdkReactNative_goInternalSdkVersion=0.1.1
WaasSdkReactNative_goInternalSdkVersion=0.1.8
WaasSdkReactNative_mpcSdkVersion=0.0.2
android.useAndroidX=true
kotlinCompilerVersion=1.8.0
48 changes: 36 additions & 12 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';

import React, { useState, useEffect, useContext } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { HomeScreen } from './screens/HomeScreen';
Expand All @@ -11,25 +10,42 @@ import { MPCKeyExportDemo } from './screens/MPCKeyExportDemo';
import AppContext from './components/AppContext';
import { DeviceBackupDemo } from './screens/DeviceBackupDemo';
import { DeviceAdditionDemo } from './screens/DeviceAdditionDemo';
import { ModeSelectionScreen } from './screens/ModeSelectionScreen';
import { ModeContext } from './utils/ModeProvider';
import { ModeProvider } from './utils/ModeProvider';

/** The navigation stack. */
const Stack = createNativeStackNavigator();

const cloudAPIKey = require('./.coinbase_cloud_api_key.json');
function SetupApp() {
const [apiKeyData, setApiKeyData] = useState<any>({});
const [proxyUrlData, setProxyUrlData] = useState<string>('');
const { selectedMode } = useContext(ModeContext);

export default function App() {
const apiKeyName = cloudAPIKey.name;
const privateKey = cloudAPIKey.privateKey;
useEffect(() => {
if (selectedMode === 'direct-mode') {
const cloudAPIKey = require('./.coinbase_cloud_api_key.json');

setApiKeyData(cloudAPIKey);
} else {
const config = require('./config.json');

const userCredentials = {
apiKeyName,
privateKey,
};
setProxyUrlData(config.proxyUrl);
}
}, [selectedMode]);

const apiKeyName = apiKeyData.name || '';
const privateKey = apiKeyData.privateKey || '';
const proxyUrl = proxyUrlData || '';

return (
<AppContext.Provider value={userCredentials}>
<AppContext.Provider value={{ apiKeyName, privateKey, proxyUrl }}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Navigator initialRouteName="ModeSelectionScreen">
<Stack.Screen
name="ModeSelectionScreen"
component={ModeSelectionScreen}
/>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="PoolServiceDemo" component={PoolServiceDemo} />
<Stack.Screen
Expand All @@ -52,3 +68,11 @@ export default function App() {
</AppContext.Provider>
);
}

export default function App() {
return (
<ModeProvider>
<SetupApp />
</ModeProvider>
);
}
1 change: 1 addition & 0 deletions example/src/components/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
export type Context = {
apiKeyName?: string;
privateKey?: string;
proxyUrl?: string;
};

const AppContext = React.createContext<Context>({});
Expand Down
3 changes: 3 additions & 0 deletions example/src/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"proxyUrl": "http://localhost:8091"
}
4 changes: 4 additions & 0 deletions example/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// The operating mode where the app connects directly to the WaaS API.
export const directMode: string = 'direct-mode';
// The operating mode where the app connects to the WaaS API via a proxy server.
export const proxyMode: string = 'proxy-mode';
35 changes: 25 additions & 10 deletions example/src/screens/DeviceAdditionDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { InputText } from '../components/InputText';
import { PageTitle } from '../components/PageTitle';
import AppContext from '../components/AppContext';
import { Note } from '../components/Note';
import { ModeContext } from '../utils/ModeProvider';
import { directMode, proxyMode } from '../constants';

export const DeviceAdditionDemo = () => {
const [deviceGroupName, setDeviceGroupName] = React.useState<string>('');
Expand All @@ -36,29 +38,40 @@ export const DeviceAdditionDemo = () => {
const [showStep6, setShowStep6] = React.useState<boolean>();
const [showError, setShowError] = React.useState<boolean>();

const credentials = React.useContext(AppContext);
const apiKeyName = credentials.apiKeyName as string;
const privateKey = credentials.privateKey as string;
const { selectedMode } = React.useContext(ModeContext);
const {
apiKeyName: apiKeyName,
privateKey: privateKey,
proxyUrl: proxyUrl,
} = React.useContext(AppContext) as {
apiKeyName: string;
privateKey: string;
proxyUrl: string;
};

// Runs the DeviceAdditionDemo.
React.useEffect(
() => {
let demoFn = async function () {
if (!showStep2 || showStep6 || deviceGroupName) {
return;
}

if (
deviceGroupName === '' ||
apiKeyName === '' ||
privateKey === '' ||
!showStep2 ||
showStep6
selectedMode === directMode &&
(apiKeyName === '' || privateKey === '')
) {
return;
}

try {
const apiKey = selectedMode === proxyMode ? '' : apiKeyName;
const privKey = selectedMode === proxyMode ? '' : privateKey;

// Initialize the MPCKeyService and MPCSdk.
await initMPCSdk(true);
await initMPCKeyService(apiKeyName, privateKey);
await initMPCWalletService(apiKeyName, privateKey);
await initMPCKeyService(apiKey, privKey, proxyUrl);
await initMPCWalletService(apiKey, privKey, proxyUrl);

if (!showStep5) {
const operationName = await addDevice(deviceGroupName, deviceName);
Expand Down Expand Up @@ -99,10 +112,12 @@ export const DeviceAdditionDemo = () => {
deviceGroupName,
apiKeyName,
privateKey,
proxyUrl,
showStep2,
deviceName,
deviceBackup,
passcode,
selectedMode,
]
);

Expand Down
Loading