Create SubWallet On Enterprise Profile
POST /v1/trovo-api/users/subwallet
Json Body
{
WalletType int `json:"walletType"` //0=normal, 1= assetIssuing (if issuing wallet, then linkedWalletPublicKey is required). Required for initial Call
SubwalletPublicKey string `json:"subwalletPublicKey"` //required for Initial Call. public key of the subwallet being created
WalletTag string `json:"walletTag"` //required for initial Call. tag for the subwallet. If an issuing wallet that is to be used to issue a token like BRT then let the tag be brtissuer.
Alias string `json:"alias"` // this is the friendly wallet alias generated by the api, that can be used to reference this wallet directly when interacting with it for payment.
Transaction string `json:"transaction"` //base64Txn string
PrimarySignature string `json:"primarySignature"` //signature obtained by signing the transaction with the primary wallet ( the enterprise profile wallet). This allows for fees and activation tokens to be deducted from the enterprise wallet and used to activate the subwallet and any possible linked wallets.
SubWalletMustSign int `json:"subWalletMustSign"`// when set to 1, then the subwallet secret key must be used to sign the transaction object.
SubWalletSignature string `json:"subWalletSignature"` //signature obtained by signing the transaction with the secret key of the subwallet.
TransactionID string `json:"transactionId"`
NetworkPassPhrase string `json:"networkPassPhrase"`
Messages []string `json:"messages"` //messages returned containing useful information the enterprise needs to be aware of.
FeeAmount string `json:"feeAmount"`
FeeCode string `json:"feeCode"` //asset code for the fee that will be charged for this creation of subwallet
LinkedWalletMustSign int `json:"linkedWalletMustSign"` //0 = no linked wallet, 1= linked wallet must provide signature too for authentication.
LinkedWalletPublicKey string `json:"linkedWalletPublicKey"` //required for initial call only if wallet type is 1 (issuing wallet)
LinkedWalletSignature string `json:"linkedWalletSignature"` // only if linkedWalletMustSign = 1. This is the signature obtained from signing the transaction object with the linked wallet secret key.
}
INITIAL CALL REQUIRED PARAMETERS:
walletTypesubwalletPublicKeywalletTaglinkedWalletPublicKey(only if the wallet type is 1)
SECOND CALL: ALL needed signatures must be returned with the response object.
Options
ISSUING WALLET: This is the wallet type option that will be used when you intend to create a wallet for minting/issuing a token. The wallet type value to be given is 1. When this wallet type is specified, there must be a wallet public key of a linked distribution wallet that is specified as the linked wallet public key. The Issuing wallet requires generation of two separate wallets. First is for the issuer and then the second is for the linked distribution wallet. Any change made to the permissions of the issuing wallet is also mirrored in the distribution wallet also. When an issuing wallet is created, then there must also be a signature for the distribution wallet that is submitted on the second call.
STANDARD WALLET: This is a standard wallet that is generally used for performing transactions. Wallet type value should be specified as 0 and there is no linked wallet needed for this.
Sample Code - Signing Function
- NodeJS
- Go
- Python
//signSubwalletBase64Txn.js
import { Keypair, Transaction, Utils } from "stellar-sdk";
/**
* Sign a Stellar Transaction XDR (base64) with 3 separate secret keys
* and return ONLY the base64-encoded signatures (not the signed envelope)
*
* @param {string} primarySecretKey
* @param {string} subWalletSecretKey
* @param {string} linkedWalletSecret
* @param {string} base64Txn
* @param {string} networkPassPhrase
* @returns {Promise<{primarySignature: string, subWalletSignature: string, linkedWalletSignature: string}>}
*/
export async function SignSubwalletBase64Txn(
primarySecretKey,
subWalletSecretKey,
linkedWalletSecret,
base64Txn,
networkPassPhrase
) {
try {
// ---- equivalent of keypair.ParseFull ----
const primaryKP = Keypair.fromSecret(primarySecretKey);
const subWalletKP = Keypair.fromSecret(subWalletSecretKey);
const linkedWalletKP = Keypair.fromSecret(linkedWalletSecret);
// ---- parse transaction from base64 XDR ----
const tx = new Transaction(base64Txn, networkPassPhrase);
// ---- calculate tx hash ----
const txHash = tx.hash(); // returns Buffer of the transaction hash
// ---- sign using raw hash (Go version signs the hash, not the envelope) ----
const primarySignature = primaryKP.sign(txHash).toString("base64");
const subWalletSignature = subWalletKP.sign(txHash).toString("base64");
const linkedWalletSignature = linkedWalletKP
.sign(txHash)
.toString("base64");
return {
primarySignature,
subWalletSignature,
linkedWalletSignature,
};
} catch (err) {
throw err;
}
}
package main
import (
"encoding/base64"
"fmt"
"github.com/stellar/go/keypair"
"github.com/stellar/go/txnbuild"
)
// SignSubwalletBase64Txn signs a Stellar Transaction XDR (base64) with 3 separate secret keys
// and returns ONLY the base64-encoded signatures
func SignSubwalletBase64Txn(
primarySecretKey string,
subWalletSecretKey string,
linkedWalletSecret string,
base64Txn string,
networkPassPhrase string,
) (primarySig, subWalletSig, linkedWalletSig string, err error) {
// Parse the secret keys
primaryKP, err := keypair.ParseFull(primarySecretKey)
if err != nil {
return "", "", "", fmt.Errorf("failed to parse primary key: %w", err)
}
subWalletKP, err := keypair.ParseFull(subWalletSecretKey)
if err != nil {
return "", "", "", fmt.Errorf("failed to parse subwallet key: %w", err)
}
linkedWalletKP, err := keypair.ParseFull(linkedWalletSecret)
if err != nil {
return "", "", "", fmt.Errorf("failed to parse linked wallet key: %w", err)
}
// Parse the transaction from base64 XDR
var tx txnbuild.GenericTransaction
err = txnbuild.UnmarshalBase64(base64Txn, &tx, networkPassPhrase)
if err != nil {
return "", "", "", fmt.Errorf("failed to unmarshal transaction: %w", err)
}
// Get the transaction hash
txHash, err := tx.Hash(networkPassPhrase)
if err != nil {
return "", "", "", fmt.Errorf("failed to hash transaction: %w", err)
}
// Sign the transaction hash with each keypair
primarySigBytes, err := primaryKP.Sign(txHash[:])
if err != nil {
return "", "", "", fmt.Errorf("failed to sign with primary key: %w", err)
}
subWalletSigBytes, err := subWalletKP.Sign(txHash[:])
if err != nil {
return "", "", "", fmt.Errorf("failed to sign with subwallet key: %w", err)
}
linkedWalletSigBytes, err := linkedWalletKP.Sign(txHash[:])
if err != nil {
return "", "", "", fmt.Errorf("failed to sign with linked wallet key: %w", err)
}
// Encode signatures to base64
primarySig = base64.StdEncoding.EncodeToString(primarySigBytes)
subWalletSig = base64.StdEncoding.EncodeToString(subWalletSigBytes)
linkedWalletSig = base64.StdEncoding.EncodeToString(linkedWalletSigBytes)
return primarySig, subWalletSig, linkedWalletSig, nil
}
from stellar_sdk import Keypair, TransactionEnvelope
import base64
def sign_subwallet_base64_txn(
primary_secret_key: str,
sub_wallet_secret_key: str,
linked_wallet_secret: str,
base64_txn: str,
network_passphrase: str
) -> dict:
"""
Sign a Stellar Transaction XDR (base64) with 3 separate secret keys
and return ONLY the base64-encoded signatures (not the signed envelope)
Args:
primary_secret_key: Primary wallet secret key
sub_wallet_secret_key: Subwallet secret key
linked_wallet_secret: Linked wallet secret key
base64_txn: Base64-encoded transaction XDR
network_passphrase: Network passphrase
Returns:
Dictionary with primarySignature, subWalletSignature, linkedWalletSignature
"""
try:
# Parse the secret keys
primary_kp = Keypair.from_secret(primary_secret_key)
sub_wallet_kp = Keypair.from_secret(sub_wallet_secret_key)
linked_wallet_kp = Keypair.from_secret(linked_wallet_secret)
# Parse the transaction from base64 XDR
tx_envelope = TransactionEnvelope.from_xdr(base64_txn, network_passphrase)
# Get the transaction hash
tx_hash = tx_envelope.hash()
# Sign the transaction hash with each keypair
primary_signature = base64.b64encode(primary_kp.sign(tx_hash)).decode('utf-8')
sub_wallet_signature = base64.b64encode(sub_wallet_kp.sign(tx_hash)).decode('utf-8')
linked_wallet_signature = base64.b64encode(linked_wallet_kp.sign(tx_hash)).decode('utf-8')
return {
"primarySignature": primary_signature,
"subWalletSignature": sub_wallet_signature,
"linkedWalletSignature": linked_wallet_signature
}
except Exception as err:
raise Exception(f"Error signing transaction: {str(err)}")
Sample Code - Full Implementation
- NodeJS
- Go
- Python
// subwallet.js
import axios from "axios";
import { Keypair, Networks, Transaction } from "stellar-sdk";
async function createSubwallet() {
try {
// === 1️⃣ CONFIGURATION ===
const API_URL = BASE_URL + "/v1/trovo-api/users/subwallet"; // replace with real API endpoint
const WALLET_TYPE = 1; //create an issuing wallet
// subwallet (the wallet that is being created under the enterprise)
const subwalletKeypair = Keypair.random();
const subwalletPublicKey = subwalletKeypair.publicKey();
const linkedWalletKeypair = Keypair.random();
const linkedWalletPublicKey = linkedWalletKeypair.publicKey();
// Primary Secret key (the account that is creating the subwallet)
const PRIMARY_SECRET = getFromEnvVariable(); // inject the secret from ENV variable. This is the signer of your enterprise primary wallet.
const primaryKeypair = Keypair.fromSecret(PRIMARY_SECRET);
// === 2️⃣ INITIAL REQUEST (Create unsigned transaction) ===
const subwalletRequestBody = {
walletType: walletType,
subwalletPublicKey: subwalletPublicKey,
walletTag: walletTag,
linkedWalletPublicKey: linkedWalletPublicKey,
};
console.log("🔹 Sending initial subwallet creation request...");
const { data: unsignedTxResponse } = await axios.post(
API_URL,
subwalletRequestBody
);
// The response should contain: Transaction (XDR) and NetworkPassPhrase
const { Transaction: base64Txn, NetworkPassPhrase: networkPassphrase } =
unsignedTxResponse;
if (!base64Txn || !networkPassphrase) {
throw new Error(
"Invalid response from endpoint — missing transaction or networkPassPhrase"
);
}
console.log("✅ Received unsigned transaction from server.");
// === 3️⃣ SIGN THE TRANSACTION ===
const result = await SignSubwalletBase64Txn(
primaryKeypair.secretKey(),
subWalletKeypair.secretKey(),
linkedWalletKeypair.secretKey(),
base64Txn,
networkPassPhrase
);
// === 4️⃣ SECOND CALL — Send back signed transaction ===
const signedBody = {
...unsignedTxResponse,
primarySignature: result.primarySignature,
subwalletSignature: result.subWalletSignature,
linkedWalletSignature: result.linkedWalletSignature,
};
console.log("🔹 Sending signed transaction back for mint commit...");
const { data: finalResponse } = await axios.post(API_URL, signedBody);
// === 5️⃣ SUCCESS RESPONSE ===
console.log("✅ SubwalletCreation completed successfully!");
console.log("Transaction ID:", finalResponse.TransactionID);
console.log("Full response:", finalResponse);
} catch (error) {
console.error(
"❌ Error creating subwallet:",
error.response?.data || error.message
);
}
}
// Run the function
createSubwallet();
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/stellar/go/keypair"
)
type SubwalletRequest struct {
WalletType int `json:"walletType"`
SubwalletPublicKey string `json:"subwalletPublicKey"`
WalletTag string `json:"walletTag"`
LinkedWalletPublicKey string `json:"linkedWalletPublicKey,omitempty"`
}
type SubwalletResponse struct {
Transaction string `json:"transaction"`
NetworkPassPhrase string `json:"networkPassPhrase"`
TransactionID string `json:"transactionId"`
Messages []string `json:"messages"`
}
type SignedSubwalletRequest struct {
SubwalletResponse
PrimarySignature string `json:"primarySignature"`
SubWalletSignature string `json:"subWalletSignature"`
LinkedWalletSignature string `json:"linkedWalletSignature"`
}
func createSubwallet() error {
// === 1️⃣ CONFIGURATION ===
apiURL := os.Getenv("BASE_URL") + "/v1/trovo-api/users/subwallet"
walletType := 1 // create an issuing wallet
// Generate subwallet keypair
subwalletKP, err := keypair.Random()
if err != nil {
return fmt.Errorf("failed to generate subwallet keypair: %w", err)
}
// Generate linked wallet keypair
linkedWalletKP, err := keypair.Random()
if err != nil {
return fmt.Errorf("failed to generate linked wallet keypair: %w", err)
}
// Primary Secret key (from environment variable)
primarySecret := os.Getenv("PRIMARY_SECRET")
primaryKP, err := keypair.ParseFull(primarySecret)
if err != nil {
return fmt.Errorf("failed to parse primary secret: %w", err)
}
// === 2️⃣ INITIAL REQUEST (Create unsigned transaction) ===
reqBody := SubwalletRequest{
WalletType: walletType,
SubwalletPublicKey: subwalletKP.Address(),
WalletTag: "mysubwallet",
LinkedWalletPublicKey: linkedWalletKP.Address(),
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
fmt.Println("🔹 Sending initial subwallet creation request...")
resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
var unsignedTxResp SubwalletResponse
err = json.Unmarshal(body, &unsignedTxResp)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
if unsignedTxResp.Transaction == "" || unsignedTxResp.NetworkPassPhrase == "" {
return fmt.Errorf("invalid response - missing transaction or networkPassPhrase")
}
fmt.Println("✅ Received unsigned transaction from server.")
// === 3️⃣ SIGN THE TRANSACTION ===
primarySig, subWalletSig, linkedWalletSig, err := SignSubwalletBase64Txn(
primaryKP.Seed(),
subwalletKP.Seed(),
linkedWalletKP.Seed(),
unsignedTxResp.Transaction,
unsignedTxResp.NetworkPassPhrase,
)
if err != nil {
return fmt.Errorf("failed to sign transaction: %w", err)
}
// === 4️⃣ SECOND CALL — Send back signed transaction ===
signedReq := SignedSubwalletRequest{
SubwalletResponse: unsignedTxResp,
PrimarySignature: primarySig,
SubWalletSignature: subWalletSig,
LinkedWalletSignature: linkedWalletSig,
}
signedData, err := json.Marshal(signedReq)
if err != nil {
return fmt.Errorf("failed to marshal signed request: %w", err)
}
fmt.Println("🔹 Sending signed transaction back for commit...")
resp2, err := http.Post(apiURL, "application/json", bytes.NewBuffer(signedData))
if err != nil {
return fmt.Errorf("failed to send signed request: %w", err)
}
defer resp2.Body.Close()
body2, err := io.ReadAll(resp2.Body)
if err != nil {
return fmt.Errorf("failed to read final response: %w", err)
}
var finalResp SubwalletResponse
err = json.Unmarshal(body2, &finalResp)
if err != nil {
return fmt.Errorf("failed to unmarshal final response: %w", err)
}
// === 5️⃣ SUCCESS RESPONSE ===
fmt.Println("✅ SubwalletCreation completed successfully!")
fmt.Println("Transaction ID:", finalResp.TransactionID)
fmt.Printf("Full response: %+v\n", finalResp)
return nil
}
func main() {
if err := createSubwallet(); err != nil {
fmt.Printf("❌ Error creating subwallet: %v\n", err)
os.Exit(1)
}
}
import os
import requests
from stellar_sdk import Keypair
def create_subwallet():
"""Create a subwallet on the enterprise profile"""
try:
# === 1️⃣ CONFIGURATION ===
api_url = os.getenv("BASE_URL") + "/v1/trovo-api/users/subwallet"
wallet_type = 1 # create an issuing wallet
# Generate subwallet keypair
subwallet_kp = Keypair.random()
subwallet_public_key = subwallet_kp.public_key
# Generate linked wallet keypair
linked_wallet_kp = Keypair.random()
linked_wallet_public_key = linked_wallet_kp.public_key
# Primary Secret key (from environment variable)
primary_secret = os.getenv("PRIMARY_SECRET")
primary_kp = Keypair.from_secret(primary_secret)
# === 2️⃣ INITIAL REQUEST (Create unsigned transaction) ===
subwallet_request_body = {
"walletType": wallet_type,
"subwalletPublicKey": subwallet_public_key,
"walletTag": "mysubwallet",
"linkedWalletPublicKey": linked_wallet_public_key,
}
print("🔹 Sending initial subwallet creation request...")
response = requests.post(api_url, json=subwallet_request_body)
response.raise_for_status()
unsigned_tx_response = response.json()
# The response should contain: Transaction (XDR) and NetworkPassPhrase
base64_txn = unsigned_tx_response.get("transaction")
network_passphrase = unsigned_tx_response.get("networkPassPhrase")
if not base64_txn or not network_passphrase:
raise ValueError("Invalid response - missing transaction or networkPassPhrase")
print("✅ Received unsigned transaction from server.")
# === 3️⃣ SIGN THE TRANSACTION ===
signatures = sign_subwallet_base64_txn(
primary_kp.secret,
subwallet_kp.secret,
linked_wallet_kp.secret,
base64_txn,
network_passphrase
)
# === 4️⃣ SECOND CALL — Send back signed transaction ===
signed_body = {
**unsigned_tx_response,
"primarySignature": signatures["primarySignature"],
"subWalletSignature": signatures["subWalletSignature"],
"linkedWalletSignature": signatures["linkedWalletSignature"],
}
print("🔹 Sending signed transaction back for commit...")
final_response = requests.post(api_url, json=signed_body)
final_response.raise_for_status()
final_data = final_response.json()
# === 5️⃣ SUCCESS RESPONSE ===
print("✅ SubwalletCreation completed successfully!")
print(f"Transaction ID: {final_data.get('transactionId')}")
print(f"Full response: {final_data}")
except requests.exceptions.RequestException as error:
print(f"❌ Error creating subwallet: {error}")
except Exception as error:
print(f"❌ Error creating subwallet: {error}")
if __name__ == "__main__":
create_subwallet()