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:
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.
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.
You can use the SDK method computeMPCWallet
to do the above two steps in a single call, but the proxy server is more secure.
// 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)