Skip to main content

How to Create an MPCWallet

This guide explains how to create a fully-fledged MPC crypto wallet with WaaS.

1. Create Pool with Client Library

An MPC Wallet requires a parent Pool, which segregates funds. Create a Pool from your proxy server with the following call:

tip

Alternatively, you can use the SDK method createPool, but the proxy server is more secure.

// Go code in your proxy server

import (
"context"
"log"

"github.com/coinbase/cloud/waas-client-library-go/auth"
"github.com/coinbase/cloud/waas-client-library-go/clients"
v1clients "github.com/coinbase/cloud/waas-client-library-go/clients/v1"
pools "github.com/coinbase/cloud/waas-client-library-go/gen/go/coinbase/cloud/pools/v1"
)

...
ctx := context.Background()

authOpt := clients.WithAPIKey(&auth.APIKey{
Name: apiKeyName,
PrivateKey: apiKeyPrivateKey,
})

poolServiceClient, err := v1clients.NewPoolServiceClient(ctx, authOpt)
if err != nil {
log.Fatalf("error instantiating pool service client: %v", err)
}

pool, err := poolServiceClient.CreatePool(ctx, &pools.CreatePoolRequest{
Pool: &pools.Pool{
DisplayName: "My First Pool",
}
})

if err != nil {
log.Fatalf("error creating pool: %v", err)
}

2. Bootstrap and Register Device

To participate in MPC, your device must be bootstrapped and registered with WaaS servers. You can do so with a combination of the WaaS SDK and the client libraries.

Bootstrap your device and get the registration data for your device with the following calls into the WaaS SDK:

// React Native code in your app
await bootstrapDevice(passcode);
let registrationData = await getRegistrationData()

Pass the registrationData to your proxy server, and from your proxy server, register your device.

tip

Alternatively, you can use the SDK method registerDevice, but the proxy server is more secure.

import mpcKeys "github.com/coinbase/cloud/waas-client-library-go/gen/go/coinbase/cloud/mpc_keys/v1"

...
mpcKeyServiceClient, err := v1clients.NewMPCKeyServiceClient(ctx, authOpt)
if err != nil {
log.Fatalf("error instantiating mpc key service client: %v", err)
}


req := &mpcKeys.RegisterDeviceRequest{
RegistrationData: []byte("registration data"),
}

device, err := mpcKeyServiceClient.RegisterDevice(ctx, req)

if err != nil {
log.Fatalf("error registering device: %v", err)
}

3. Initiate MPCWallet Creation

Creating an MPCWallet creates a DeviceGroup under the hood, and requires MPC participation. Initiate this process with the [CreateMPCWallet] () API. The operation’s metadata will contain the DeviceGroup you can use to poll with ListMPCOperations.

// Go code in your Proxy server
import mpcWallets "github.com/coinbase/cloud/waas-client-library-go/gen/go/coinbase/cloud/mpc_wallets/v1"

...
mpcWalletServiceClient, err := v1clients.NewMPCWalletServiceClient(ctx, authOpt)
if err != nil {
log.Fatalf("error instantiating mpc wallet service client: %v", err)
}

req := &mpcWallets.CreateMPCWalletRequest{
Parent: pool.GetName(),
MpcWallet: &mpcWallets.MPCWallet{},
Device: device.GetName(),
}

op, err := mpcWalletServiceClient.CreateMPCWallet(ctx, req)

if err != nil {
log.Fatalf("error initiating wallet creation: %v", err)
}

metadata, _ := op.Metadata()

deviceGroupName := metadata.GetDeviceGroup()

4. Poll and Compute MPCOperation

The DeviceGroup creation is now underway, and requires MPC participation from your device. Poll for your pending DeviceGroup via your proxy server using ListMPCOperations.

To finalize your MPC wallet, poll for your created DeviceGroup again and compute the device archive using computePrepareDeviceArchiveMPCOperation in the WaaS SDK.

// Go code in your Proxy server

// pollMPCOperations polls for pending MPCOperations of the given type. It returns the MPC Operation data to process.
func pollMPCOperations(
deviceGroupName string,
pollChan chan bool,
pollInterval int64,
) (string, error) {
if pollInterval == 0 {
pollInterval = 200
}

timeout := time.After(time.Minute)
ticker := time.NewTicker(time.Duration(pollInterval) * time.Millisecond)

var response []*mpcKeys.MPCOperation
var retErr error

POLL_LOOP:
for {
select {
case <-pollChan:
break POLL_LOOP
case <-timeout:
retErr = fmt.Errorf("timed out polling for device group %s", deviceGroupName)
break POLL_LOOP
case <-ticker.C:
req := &mpcKeys.ListMPCOperationsRequest{
Parent: deviceGroupName,
}
listMpcOperationsResp, err := mpcKeyServiceClient.ListMPCOperations(context.Background(), req)
if err != nil {
if clients.HTTPCode(err) == http.StatusNotFound {
// Continue polling if no MPC operation was found.
continue POLL_LOOP
}

retErr = err

break POLL_LOOP
}

if len(listMpcOperationsResp.GetMpcOperations()) == 0 {
// Continue polling if there were no MPC operations.
continue POLL_LOOP
}

mpcOp := listMPCOperationsResp.GetMpcOperations()[0]

if len(response) > 0 {
return mpcOp.GetMpcData(), nil
}
}
}

return "", retErr
}

Now use the MPC data in the MPC operation to compute the MPC operation for DeviceGroup in the WaaS SDK.

// React Native code in your app
computeMPCOperation(mpcData)

Then, repeat the pollMPCOperations in your Proxy server and compute the prepare device archive.

// React Native code in your app
computePrepareDeviceArchiveMPCOperation(mpcData)

5. Generate Address

Shortly after you compute the MPC operation, the original operation returned from CreateMPCWallet should complete. You can now use the MPCWallet to generate an Address.

// Go code in your Proxy server
mpcWallet, _ := op.Wait(ctx)

req := &mpcwallets.GenerateAddressRequest{
Parent: "networks/ethereum-mainnet",
MpcWallet: mpcWallet.GetName()
}

address, _ := mpcWalletServiceClient.GenerateAddress(ctx, req)

Was this helpful?