Creating Vault account {"message":"Unauthorized user","code":1040} error

With this setup, I am getting an unauthorized user error when trying to create a vault account with my key and secret. Why? And how can I correct it?

type ApiTokenProvider struct {
	privateKey *rsa.PrivateKey
	apiKey     string
}

func NewApiTokenProvider(privateKeyPath, apiKey string) (*ApiTokenProvider, error) {
	privateKeyBytes, err := os.ReadFile(privateKeyPath)
	if err != nil {
		return nil, fmt.Errorf("error reading private key from %s: %w", privateKeyPath, err)
	}

	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
	if err != nil {
		return nil, fmt.Errorf("error parsing RSA private key: %w", err)
	}

	return &ApiTokenProvider{
		privateKey: privateKey,
		apiKey:     apiKey,
	}, nil
}

func (a *ApiTokenProvider) SignJwt(path string, bodyJson interface{}) (string, error) {
	nonce := uuid.New().String()
	now := time.Now().Unix()
	expiration := now + 55 // Consider making this configurable

	bodyBytes, err := json.Marshal(bodyJson)
	if err != nil {
		return "", fmt.Errorf("error marshaling JSON: %w", err)
	}

	h := sha256.New()
	h.Write(bodyBytes)
	hashed := h.Sum(nil)

	claims := jwt.MapClaims{
		"uri":      path,
		"nonce":    nonce,
		"iat":      now,
		"exp":      expiration,
		"sub":      a.apiKey,
		"bodyHash": hex.EncodeToString(hashed),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	tokenString, err := token.SignedString(a.privateKey)
	if err != nil {
		return "", fmt.Errorf("error signing token: %w", err)
	}

	return tokenString, nil
}

var httpClient = &http.Client{} // Reuse HTTP client

func makeAPIRequest(method, path string, body interface{}, tokenProvider *ApiTokenProvider) ([]byte, error) {
	var url = "https://sandbox-api.fireblocks.io" + path

	var reqBodyBytes []byte
	if body != nil {
		var err error
		reqBodyBytes, err = json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("error marshaling request body: %w", err)
		}
	}

	token, err := tokenProvider.SignJwt(path, body)
	if err != nil {
		return nil, fmt.Errorf("error signing JWT: %w", err)
	}

	req, err := http.NewRequest(method, url, bytes.NewBuffer(reqBodyBytes))
	if err != nil {
		return nil, fmt.Errorf("error creating HTTP request: %w", err)
	}

	if method == "POST" {
		req.Header.Set("Content-Type", "application/json")
	}

	log.Println("the token: ", token)

	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("X-API-KEY", tokenProvider.apiKey)

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error sending HTTP request: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response body: %w", err)
	}

	return respBody, nil
}

// createAccount makes a POST request to create a new account
func createAccount(tokenProvider *ApiTokenProvider) error {
	path := "/v1/vault/accounts"
	body := map[string]interface{}{
		"name":          "MyGoVault",
		"hiddenOnUI":    false,
		"customerRefId": "testing-123",
		"autoFuel":      true,
	}

	respBody, err := makeAPIRequest("POST", path, body, tokenProvider)
	if err != nil {
		return fmt.Errorf("error making POST request to create account: %w", err)
	}

	fmt.Printf("Response body for createAccount: %s\n", string(respBody))
	return nil
}

Hey @kjasuquo can you please share the first 5 chars of your API key?

Hi @SlavaSereb here you go 54619

Hey @kjasuquo,

I can see that this API key has the “NCW_ADMIN” role which is not allowed to create vault accounts in your workspace but only Non Custodial Wallets.

For creating Vault Accounts (Self Custodial Wallets), you need to have either EDITOR or NON_SIGNING_ADMIN roles.

Let me know if you have any further questions.

Thank you @SlavaSereb but I still have a blocker.
I have used it for NCW wallet and it works
but I called the endpoint /v1/wallets/{walletId}/setup_status it gives me a response of {"status":"INCOMPLETE","deviceSetupStatus":[]}

For more context I called the add-asset endpoint /v1/ncw/wallets/{walletId}/accounts/{accountId}/assets/BTC_TEST" to add an asset to my created account and wallet and I got the response {"message":"Wallet setup is incomplete","code":1000}

Please what is missing and what can I do?

can you share with me the docs or how to complete the the wallet set_up in order to enable me add assets?

while you respond, Please can you help me with the go-SDK for fireblocks?
or is there a http endpoint for generating MPC Key for NCW Wallets? Please share @SlavaSereb

Hey @kjasuquo

Sorry for the delayed response here.

Generating an MPC key for your Non Custodial Wallets is an essential part of the process.

You can check the step by step process of initializing our Web/Mobile SDKs with detailed explanation about every step in here:

The API reference for the NCW SDK can be found here:

Hi @SlavaSereb I have generated the MPC key and I got this…

{"method":"get_service_certificates","headers":{"sdkVersion":"12.1.2","mpcVersion":6,"physicalDeviceId":"12ade3da-45a8-4dd8-8103-73f1604cd412","platformType":"Web"},"params":[{"names":["nckms","signing_service","zona_service","policy_service"]}]}

from the docs I need to make a call verify it or something like that POST/v1/ncw/wallets/{walletID}/devices/{deviceID}/invoke msg

Here is my code snippet

func makeAPIRequest(method, path string, body interface{}, tokenProvider *ApiTokenProvider) ([]byte, error) {
	var url = "https://sandbox-api.fireblocks.io" + path

	var reqBodyBytes []byte
	if body != nil {
		var err error
		reqBodyBytes, err = json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("error marshaling request body: %w", err)
		}
	}

	token, err := tokenProvider.SignJwt(path, body)
	if err != nil {
		return nil, fmt.Errorf("error signing JWT: %w", err)
	}

	req, err := http.NewRequest(method, url, bytes.NewBuffer(reqBodyBytes))
	if err != nil {
		return nil, fmt.Errorf("error creating HTTP request: %w", err)
	}

	if method == "POST" {
		req.Header.Set("Content-Type", "application/json")
	}

	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("X-API-KEY", tokenProvider.apiKey)

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error sending HTTP request: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response body: %w", err)
	}

	return respBody, nil
}

type RequestPayload struct {
	Method  string                `json:"method"`
	Headers map[string]string     `json:"headers"`
	Params  []map[string][]string `json:"params"`
}


func verifyMPCKeys(tokenProvider *ApiTokenProvider, walletID, deviceID string) ([]byte, error) {
	path := "/v1/ncw/wallets/" + walletID + "/devices/" + deviceID + "/invoke"

	msg := RequestPayload{
		Method: "get_service_certificates",
		Headers: map[string]string{
			"sdkVersion":       "12.1.2",
			"mpcVersion":       "6",
			"physicalDeviceId": "12ade3da-45a8-4dd8-8103-73f1604cd412",
			"platformType":     "Web",
		},
		Params: []map[string][]string{
			{
				"names": {"nckms", "signing_service", "zona_service", "policy_service"},
			},
		},
	}

	return makeAPIRequest("POST", path, msg, tokenProvider)
}

But I am getting this error message

{"error":"Bad Request","message":"Couldn't complete the operation. Please contact support if the problem persists."}

What am I doing wrong?