Signing a Transaction
BitBadges Transaction Signing and Generation Guide
This guide explains how to create, fund, and sign transactions for the BitBadges blockchain using Ethereum-compatible wallets and the BitBadges SDK.
Prerequisites
npm install @cosmjs/crypto @cosmjs/proto-signing @cosmjs/stargate
npm install bitbadgesjs-sdk ethers
Step 1: Create ETH Wallet
There are several ways to create an Ethereum wallet for BitBadges transactions:
Option A: Create Random Wallet
import { ethers } from 'ethers';
// Create a random wallet
const ethWallet = ethers.Wallet.createRandom();
console.log('Address:', ethWallet.address);
console.log('Private Key:', ethWallet.privateKey);
console.log('Mnemonic:', ethWallet.mnemonic.phrase);
Option B: From Existing Mnemonic
import { ethers } from 'ethers';
const mnemonic = "your twelve word mnemonic phrase here";
const ethWallet = ethers.Wallet.fromMnemonic(mnemonic);
Option C: From Private Key
import { ethers } from 'ethers';
const privateKey = "0x..."; // Your private key
const ethWallet = new ethers.Wallet(privateKey);
Step 2: Fund the Wallet
Option A: Using Cosmos SDK Direct Transfer (Requires Funded Account)
The easiest way to do this is through the BitBadges website. If you want to do it programmatically, you can do so via the snippet below.
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { SigningStargateClient } from '@cosmjs/stargate';
import { convertToBitBadgesAddress } from 'bitbadgesjs-sdk';
// If you have a funded mnemonic account
const fromMnemonic = "your funded account mnemonic";
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(fromMnemonic);
const [firstAccount] = await wallet.getAccounts();
// Connect to BitBadges RPC
const rpcUrl = "https://rpc.bitbadges.io"; // or your preferred RPC
const signingClient = await SigningStargateClient.connectWithSigner(rpcUrl, wallet);
// Transfer tokens to your new wallet
const amount = {
denom: 'ubadge', // BitBadges native token
amount: '1000000' // Amount in micro-units
};
const fee = {
amount: [{ denom: 'ubadge', amount: '5000' }],
gas: '200000'
};
const result = await signingClient.sendTokens(
firstAccount.address,
convertToBitBadgesAddress(ethWallet.address),
[amount],
fee
);
Step 3: Get Current Account Number and Sequence
Option A: Using Cosmos RPC Query
import { SigningStargateClient } from '@cosmjs/stargate';
import { convertToBitBadgesAddress } from 'bitbadgesjs-sdk';
const rpcUrl = "https://rpc.bitbadges.io";
const client = await SigningStargateClient.connect(rpcUrl);
const bitbadgesAddress = convertToBitBadgesAddress(ethWallet.address);
const account = await client.getAccount(bitbadgesAddress);
if (!account) {
throw new Error('Account not found - ensure it has been funded');
}
console.log('Account Number:', account.accountNumber);
console.log('Sequence:', account.sequence);
Option B: Direct REST API Query
const bitbadgesAddress = convertToBitBadgesAddress(ethWallet.address);
const restUrl = `https://lcd.bitbadges.io/cosmos/auth/v1beta1/accounts/${bitbadgesAddress}`;
const response = await fetch(restUrl);
const data = await response.json();
const accountNumber = data.account.account_number;
const sequence = data.account.sequence;
Step 4: Generate Transaction Payload
Get Public Key First
For Ethereum signatures, this is automatically detected via the SDK, so you do not need to pre-generate.
const base64PubKey = '';
Create Transaction Context
import { createTransactionPayload, convertToBitBadgesAddress, Numberify } from 'bitbadgesjs-sdk';
const sender = {
address: ethWallet.address, // Ethereum address - not BitBadges address
sequence: sequence,
accountNumber: Numberify(accountNumber),
pubkey: base64PubKey,
publicKey: base64PubKey
};
const txContext = {
testnet: false,
sender,
memo: 'My BitBadges transaction',
fee: {
denom: 'ubadge',
amount: '5000',
gas: '200000'
}
};
Create Transaction Messages
Refer to the proto definitions for all transaction types available. Standard Cosmos messages are also supported.
https://github.com/BitBadges/bitbadgeschain/tree/master/proto
https://bitbadges.github.io/bitbadgesjs/modules/proto.html
import { badges } from 'bitbadgesjs-sdk';
// Example: Create a badge collection
const createCollectionMsg = new badges.MsgUniversalUpdateCollection({
creator: convertToBitBadgesAddress(ethWallet.address),
collectionId: '0', // 0 for new collection
badgesToCreate: [
{
amount: '100',
badgeIds: [{ start: '1', end: '100' }]
}
],
managerTimeline: [
{
manager: convertToBitBadgesAddress(ethWallet.address),
timelineTimes: [{ start: '1', end: Number.MAX_SAFE_INTEGER.toString() }]
}
],
updateManagerTimeline: true,
updateBadgeMetadataTimeline: true,
updateCollectionMetadataTimeline: true
});
// Example: Transfer badges
const transferMsg = new badges.MsgTransferBadges({
creator: convertToBitBadgesAddress(ethWallet.address),
collectionId: '1',
transfers: [
{
from: convertToBitBadgesAddress(ethWallet.address),
toAddresses: ['bb1recipient...'],
balances: [
{
amount: '1',
badgeIds: [{ start: '1', end: '1' }],
ownershipTimes: [{ start: '1', end: '18446744073709551615' }]
}
]
}
]
});
const messages = [createCollectionMsg]; // Add your messages here
Generate Transaction Payload
const txPayload = createTransactionPayload(txContext, messages);
if (!txPayload.txnString) {
throw new Error('Failed to generate transaction payload');
}
console.log('Transaction to sign:', txPayload.txnString);
Step 5: Simulate Transaction
import { createTxBroadcastBody } from 'bitbadgesjs-sdk';
const simulationBroadcastBody = createTxBroadcastBody(txContext, messages, '');
Option A: Using BitBadges API Simulation Endpoint
const simulateUrl = "https://api.bitbadges.io/api/v0/simulate";
const simulationResponse = await fetch(simulateUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tx_bytes: simulationBroadcastBody.tx_bytes,
// Some APIs accept the full broadcast body for simulation
...simulationBroadcastBody
})
});
const simulationResult = await simulationResponse.json();
if (simulationResult.error) {
throw new Error(`Simulation failed: ${simulationResult.error}`);
}
// Extract gas used from simulation
const gasUsed = simulationResult.gas_info?.gas_used || simulationResult.gasUsed;
const gasWanted = simulationResult.gas_info?.gas_wanted || simulationResult.gasWanted;
console.log('Simulation successful!');
console.log('Gas used:', gasUsed);
console.log('Gas wanted:', gasWanted);
Option B: Using Cosmos REST API Simulation
const restSimulateUrl = "https://rest.bitbadges.io/cosmos/tx/v1beta1/simulate";
const restSimulationResponse = await fetch(restSimulateUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tx_bytes: simulationBroadcastBody.tx_bytes
})
});
const restSimulationResult = await restSimulationResponse.json();
if (restSimulationResult.code && restSimulationResult.code !== 0) {
throw new Error(`Simulation failed: ${restSimulationResult.raw_log}`);
}
const gasInfo = restSimulationResult.gas_info;
console.log('Gas used:', gasInfo.gas_used);
console.log('Gas wanted:', gasInfo.gas_wanted);
Step 6: Sign and Broadcast Transaction
Note: Before signing and broadcasting, you will want to fix errors from the simulation and most likely adjust gas usage dynamically.
Sign the Transaction
import { createTxBroadcastBody } from 'bitbadgesjs-sdk';
// Sign the transaction string with your Ethereum wallet
const signature = await ethWallet.signMessage(txPayload.txnString);
// Create the broadcast body
const broadcastBody = createTxBroadcastBody(txContext, messages, signature);
Broadcast the Transaction
// Option A: Using BitBadges API
const broadcastUrl = "https://api.bitbadges.io/api/v0/broadcast";
const response = await fetch(broadcastUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(broadcastBody)
});
const result = await response.json();
console.log('Transaction hash:', result.txhash);
// Option B: Using Cosmos RPC directly
const rpcBroadcastUrl = "https://rpc.bitbadges.io/broadcast_tx_sync";
const rpcResponse = await fetch(rpcBroadcastUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'broadcast_tx_sync',
params: {
tx: broadcastBody.tx_bytes
}
})
});
Important Notes
Testnet vs Mainnet: Ensure you're using the correct RPC URLs and chain IDs
Gas Estimation: Always estimate gas properly for complex transactions
Sequence Management: Increment sequence for each transaction from the same account
Error Handling: Always implement proper error handling for network calls
Security: Never expose private keys or mnemonics in production code
Common Issues
Account not found: Ensure the account has been funded at least once
Sequence mismatch: Query the latest sequence before each transaction
Insufficient gas: Increase gas limit for complex transactions
Invalid signature: Ensure the public key extraction and signing process is correct
Last updated