@bubolabs/wallet-rn-sdk
v0.2.14
Published
React Native smoke SDK for bubo wallet UniFFI bridge
Downloads
2,620
Readme
@bubolabs/wallet-rn-sdk
React Native SDK for the Bubo wallet UniFFI bridge.
This package uses standard RN native-module layout and supports autolinking:
android/React Native Android library moduleios/Pod sources + vendoredBuboWalletFFI.xcframeworkreact-native.config.jsfor Android package registration
Install
npm install @bubolabs/wallet-rn-sdkiOS:
cd ios && pod installAPI Overview
Public API is chain-first. Generic chainId-routed aggregators are not exported
from package entry (deriveAddress*, deriveWallet*, buildAndSign*,
signMessage*, signBytes*, signTypedData*).
Preferred (chain namespace API):
import { btc, eth, solana } from "@bubolabs/wallet-rn-sdk";
await btc.signMessage({
mnemonic,
account: 0,
index: 0,
message: "hello",
mode: "bip322-simple",
});
await eth.signTypedData({
mnemonic,
account: 0,
index: 0,
typedData: JSON.stringify(typedData),
});
await solana.signTx({
unsignedTx,
unsignedTxEncoding: "base64",
privateKey,
keyEncoding: "base58",
});Legacy flat function exports are still fully supported:
import {
btc,
core,
eth,
solana,
btcBuildAndSign,
btcBuildAndSignFromPrivateKey,
btcBuildPsbt,
btcBuildTx,
btcDeriveAddress,
btcDeriveAddressFromPrivateKey,
btcDeriveWallet,
btcDeriveWalletFromPrivateKey,
btcFrostDkgFinalize,
btcFrostDkgRound1,
btcFrostDkgRound2,
btcFrostAggregateSignature,
btcFrostBuildSigningPackage,
btcFrostRound1Commit,
btcFrostRound2SignShare,
btcFrostTaprootComputeTweak,
btcFrostNonceSessionScope,
btcFrostResetNonceGuard,
btcFrostTrustedDealerKeygen,
createBtcFrostDkgSession,
createBtcFrostSigningSession,
runBtcFrostDkgSession,
runBtcFrostSigningSession,
btcInspectPsbt,
btcPartialSignPsbt,
btcSignBytes,
btcSignBytesFromPrivateKey,
btcSignMessage,
btcSignMessageFromPrivateKey,
btcSignPsbt,
btcSignPsbtWithSigners,
btcSignTx,
btcVerifyPsbtFinalized,
ethBuildAndSign,
ethBuildAndSignFromPrivateKey,
ethBuildTx,
ethDeriveAddress,
ethDeriveAddressFromPrivateKey,
ethDeriveWallet,
ethDeriveWalletFromPrivateKey,
ethBuildEip1271IsValidSignatureCall,
ethBuildEip1271RpcCall,
ethDecodeSignedTx,
generateMnemonic,
validateMnemonic,
init,
listChainExtensions,
listSupportedChains,
ethEcrecover,
ethRecoverTypedDataSigner,
ethSignMessage,
ethSignMessageFromPrivateKey,
ethSignTx,
ethSignTypedData,
ethSignTypedDataFromPrivateKey,
ethVerifyEip1271IsValidSignatureResult,
generateMnemonic,
init,
listChainExtensions,
listSupportedChains,
solanaBuildAndSign,
solanaBuildAndSignFromPrivateKey,
solanaBuildTx,
solanaDeriveAddress,
solanaDeriveAddressFromPrivateKey,
solanaDeriveWallet,
solanaDeriveWalletFromPrivateKey,
solanaInspectSignedTx,
solanaSignBytes,
solanaSignBytesFromPrivateKey,
solanaSignMessage,
solanaSignMessageFromPrivateKey,
solanaSignTx,
validateMnemonic,
} from "@bubolabs/wallet-rn-sdk";- Shared discovery + lifecycle:
- namespaced:
core.init,core.listSupportedChains,core.listChainExtensions - flat:
init,listSupportedChains,listChainExtensions
- namespaced:
- BTC core API:
- wallet management:
btcDeriveAddress,btcDeriveWallet,btcDeriveAddressFromPrivateKey,btcDeriveWalletFromPrivateKey - transaction signing:
btcBuildAndSign,btcBuildAndSignFromPrivateKey - transaction lifecycle:
btcBuildTx,btcSignTx(PSBT-backed) - message/bytes signing:
btcSignMessage,btcSignMessageFromPrivateKey,btcSignBytes,btcSignBytesFromPrivateKey - PSBT flow:
btcBuildPsbt,btcSignPsbt,btcSignPsbtWithSigners,btcPartialSignPsbt,btcVerifyPsbtFinalized,btcInspectPsbt - FROST key setup:
btcFrostTrustedDealerKeygen,btcFrostDkgRound1,btcFrostDkgRound2,btcFrostDkgFinalize - FROST share lifecycle helpers:
btcFrostRefreshRound1,btcFrostRefreshRound2,btcFrostRefreshFinalize,btcFrostReshareRound1,btcFrostReshareRound2,btcFrostReshareFinalize - FROST public-state guards:
btcFrostValidateShare,btcFrostValidatePublicState,btcFrostCompareGroupPublicKey - FROST threshold signing:
btcFrostRound1Commit,btcFrostBuildSigningPackage,btcFrostRound2SignShare,btcFrostAggregateSignature,btcFrostTaprootComputeTweak,btcFrostVerifySignatureShare - FROST nonce lifecycle guard:
btcFrostNonceSessionScope,btcFrostResetNonceGuard - FROST Taproot PSBT bridge:
btcFrostBuildTaprootPsbtSigningInputs,btcFrostFinalizeTaprootPsbt - FROST offline orchestration helpers:
createBtcFrostDkgSession,runBtcFrostDkgSession,createBtcFrostSigningSession,runBtcFrostSigningSession
- wallet management:
- ETH core API:
- wallet management:
ethDeriveAddress,ethDeriveWallet,ethDeriveAddressFromPrivateKey,ethDeriveWalletFromPrivateKey - transaction signing:
ethBuildAndSign,ethBuildAndSignFromPrivateKey - transaction lifecycle:
ethBuildTx,ethSignTx - message/typed-data signing:
ethSignMessage,ethSignMessageFromPrivateKey,ethSignTypedData,ethSignTypedDataFromPrivateKey - recovery/verification:
ethEcrecover,ethRecoverTypedDataSigner,ethDecodeSignedTx - contract-wallet helpers (EIP-1271):
ethBuildEip1271IsValidSignatureCall,ethBuildEip1271RpcCall,ethVerifyEip1271IsValidSignatureResult,ethBuildEip1271RpcCallAndVerify - app-layer orchestration reference:
docs/eth-eip1271-consumer-template.md
- wallet management:
- SOL core API:
- wallet management:
solanaDeriveAddress,solanaDeriveWallet,solanaDeriveAddressFromPrivateKey,solanaDeriveWalletFromPrivateKey - transaction signing:
solanaBuildAndSign,solanaBuildAndSignFromPrivateKey - transaction lifecycle:
solanaBuildTx,solanaSignTx,solanaInspectSignedTx(supportsversioned_messagepayload path and multi-signer progress inspection) - message/bytes signing:
solanaSignMessage,solanaSignMessageFromPrivateKey,solanaSignBytes,solanaSignBytesFromPrivateKey - app-layer multi-signer orchestration reference:
docs/solana-multisigner-consumer-template.md
- wallet management:
- Low-level fallback:
invokeChainExtensionis kept only as escape hatch for chain-specific experiments.
Migration Guide (0.1.14)
Starting from 0.1.14, app code should use chain-specific core APIs by default.
Recommended migration:
| Previous style | Chain-specific style |
| --- | --- |
| deriveAddress({ chainId: "btc", ... }) | btc.deriveAddress({ ... }) (or btcDeriveAddress) |
| deriveWallet({ chainId: "btc", ... }) | btc.deriveWallet({ ... }) (or btcDeriveWallet) |
| deriveAddressFromPrivateKey({ chainId: "eth", ... }) | eth.deriveAddressFromPrivateKey({ ... }) (or ethDeriveAddressFromPrivateKey) |
| buildAndSign({ chainId: "solana", ... }) | solana.buildAndSign({ ... }) (or solanaBuildAndSign) |
| signMessage({ chainId: "eth", ... }) | eth.signMessage({ ... }) (or ethSignMessage) |
| signBytes({ chainId: "btc", ... }) | btc.signBytes({ ... }) (or btcSignBytes) |
| signTypedData({ chainId: "eth", ... }) | eth.signTypedData({ ... }) (or ethSignTypedData) |
Before:
await signMessage({
chainId: "btc",
mnemonic,
account: 0,
index: 0,
message: "hello",
});After:
await btc.signMessage({
mnemonic,
account: 0,
index: 0,
message: "hello",
});invokeChainExtension is still supported, but treat it as low-level fallback, not the
primary wallet integration path.
Chain Capability Matrix
The matrix is generated from chain adapter source and validated in CI. Sync command:
node ./scripts/check-capability-matrix.mjs --write| chainId | deriveAddress | deriveAddressFromPrivateKey | deriveWallet | deriveWalletFromPrivateKey | buildAndSign | buildAndSignFromPrivateKey | signBytes | signBytesFromPrivateKey | signMessage | signMessageFromPrivateKey | signTypedData | signTypedDataFromPrivateKey | buildTx | signTx | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | btc | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | | eth | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes | Yes | Yes | Yes | Yes | | solana | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | No | Yes | Yes |
Signing Semantics By Chain
signMessage, signBytes, and signTypedData are chain-specific. Do not assume one chain's signing
semantics on another chain.
| chainId | signMessage | signBytes | signTypedData |
| --- | --- | --- | --- |
| eth | EIP-191 style message hash signing (hash_message) | Not supported | EIP-712 typed data signing. typedData must be valid EIP-712 JSON |
| btc | Supports mode: "ecdsa" \| "bip322-simple" (ecdsa default) | Signs provided raw bytes with BTC secp256k1 flow | Not supported |
| solana | Signs UTF-8 text message with ed25519 (messageEncoding fixed to utf8) | Signs provided raw bytes with ed25519 | Not supported |
For btc and solana, signatures depend on exact input bytes. Keep serialization deterministic
across client/server.
For solana, use solanaSignBytes* for binary payloads (hex/base64); solanaSignMessage* is
reserved for human-readable UTF-8 message text.
Chain-Specific Core API
The SDK is now organized by chain core capabilities instead of forcing everything into homogeneous APIs.
- BTC:
- account/address:
btcDeriveAddress,btcDeriveWallet,btcDeriveAddressFromPrivateKey,btcDeriveWalletFromPrivateKey - signing:
btcBuildAndSign,btcBuildAndSignFromPrivateKey,btcBuildTx,btcSignTx,btcSignMessage*,btcSignBytes* - PSBT:
btcBuildPsbt,btcSignPsbt,btcSignPsbtWithSigners,btcPartialSignPsbt,btcVerifyPsbtFinalized,btcInspectPsbt - FROST key setup:
btcFrostTrustedDealerKeygen,btcFrostDkgRound1,btcFrostDkgRound2,btcFrostDkgFinalize - FROST share lifecycle helpers:
btcFrostRefreshRound1,btcFrostRefreshRound2,btcFrostRefreshFinalize,btcFrostReshareRound1,btcFrostReshareRound2,btcFrostReshareFinalize - FROST public-state guards:
btcFrostValidateShare,btcFrostValidatePublicState,btcFrostCompareGroupPublicKey - FROST threshold signing:
btcFrostRound1Commit,btcFrostBuildSigningPackage,btcFrostRound2SignShare,btcFrostAggregateSignature,btcFrostTaprootComputeTweak,btcFrostVerifySignatureShare - FROST nonce lifecycle guard:
btcFrostNonceSessionScope,btcFrostResetNonceGuard - FROST Taproot PSBT bridge:
btcFrostBuildTaprootPsbtSigningInputs,btcFrostFinalizeTaprootPsbt - FROST offline orchestration:
createBtcFrostDkgSession,runBtcFrostDkgSession,createBtcFrostSigningSession,runBtcFrostSigningSession,createBtcFrostRefreshSession,runBtcFrostRefreshSession,createBtcFrostReshareSession,runBtcFrostReshareSession
- account/address:
- ETH:
- account/address:
ethDeriveAddress,ethDeriveWallet,ethDeriveAddressFromPrivateKey,ethDeriveWalletFromPrivateKey - signing:
ethBuildAndSign,ethBuildAndSignFromPrivateKey,ethBuildTx,ethSignTx,ethSignMessage*,ethSignTypedData* - verification/recovery:
ethEcrecover,ethRecoverTypedDataSigner,ethDecodeSignedTx - contract-wallet helpers (EIP-1271):
ethBuildEip1271IsValidSignatureCall,ethBuildEip1271RpcCall,ethVerifyEip1271IsValidSignatureResult,ethBuildEip1271RpcCallAndVerify - app-layer orchestration reference:
docs/eth-eip1271-consumer-template.md
- account/address:
- SOL:
- account/address:
solanaDeriveAddress,solanaDeriveWallet,solanaDeriveAddressFromPrivateKey,solanaDeriveWalletFromPrivateKey - signing:
solanaBuildAndSign,solanaBuildAndSignFromPrivateKey,solanaBuildTx,solanaSignTx,solanaInspectSignedTx(incl.versioned_messagepath + multi-signer state),solanaSignMessage*,solanaSignBytes* - app-layer multi-signer orchestration reference:
docs/solana-multisigner-consumer-template.md
- account/address:
invokeChainExtension remains available as a low-level escape hatch, but app code should
prefer chain-specific core APIs.
BTC FROST DKG Flow
The SDK supports two key-setup modes for BTC FROST Taproot:
- Trusted dealer setup:
btcFrostTrustedDealerKeygen
- Decentralized DKG setup:
btcFrostDkgRound1 -> btcFrostDkgRound2 -> btcFrostDkgFinalize
Recommended coordinator process for DKG:
- each participant runs
btcFrostDkgRound1; coordinator collectscoefficientCommitmentsHex. - each participant runs
btcFrostDkgRound2; coordinator routesshares[].toIdentifier. - each participant runs
btcFrostDkgFinalizewith full round1 commitments + routed shares. - coordinator verifies all finalize outputs have identical
groupPublicKeyXOnlyHex.
Production offline orchestration helper:
createBtcFrostDkgSession(config)gives a stateful session object with:- round request builders (
buildRound1Request,buildRound2Request,buildFinalizeRequest) - per-round replay protection (
acceptRound1Result,acceptRound2Result,acceptFinalizeResult) - persistence hooks (
toState,BtcFrostDkgSession.fromState)
- round request builders (
runBtcFrostDkgSession(config)runs round1/round2/finalize end-to-end using SDK executor defaults.
Coordinator helper example:
const dkg = await runBtcFrostDkgSession({
threshold: 2,
participants: 3,
});
// dkg.groupPublicKeyXOnlyHex
// dkg.participants[i].secretShareHex (store per participant securely)Refresh/Reshare boundary helpers (0.2.8+ API surface):
btcFrostRefreshRound1/2/FinalizebtcFrostReshareRound1/2/FinalizebtcFrostValidateSharebtcFrostValidatePublicStatebtcFrostCompareGroupPublicKey- session helpers:
createBtcFrostRefreshSession,runBtcFrostRefreshSessioncreateBtcFrostReshareSession,runBtcFrostReshareSession
Signing orchestration helpers (0.2.14+ API surface):
createBtcFrostSigningSession:- stateful signing session with replay protection
- supports
toState/BtcFrostSigningSession.fromState - explicit steps: round1 commit -> build signing package -> round2 shares -> aggregate
runBtcFrostSigningSession:- one-call offline runner for the full signing path
- consumes participant identifiers + per-participant secret shares
These APIs are offline primitives for consumer-side orchestration. Coordinator policy (refresh cadence, roster governance, approval flow, transport) stays in app/backend layer.
After setup (trusted dealer or DKG), run threshold signing with:
btcFrostRound1CommitbtcFrostBuildSigningPackagebtcFrostRound2SignSharebtcFrostAggregateSignature
Nonce lifecycle guard:
btcFrostRound2SignSharenow enforces one-time use for eachnonceSecretHexwithin process lifecycle; nonce reuse will be rejected.
For Taproot output-key context, apply:
btcFrostTaprootComputeTweak
FROST offline verification + PSBT bridge:
btcFrostVerifySignatureShare(single-share verification / blame attribution)btcFrostBuildTaprootPsbtSigningInputs(extract per-input Taproot key-spend message hashes)btcFrostFinalizeTaprootPsbt(inject aggregated Schnorr signatures into PSBT inputs)
Types
type SupportedChainInfo = {
chainId: string;
capabilities: string[];
};
type ChainExtensionInfo = {
chainId: string;
methods: string[];
};
type DeriveAddressRequest = {
chainId: string;
mnemonic: string;
account: number; // uint32
index: number; // uint32
passphrase?: string | null;
};
type DeriveAddressFromPrivateKeyRequest = {
chainId: string;
privateKey: string;
keyEncoding?: "utf8" | "hex" | "base64" | "wif"; // default: hex
};
type DerivedWallet = {
address: string;
privateKey: string;
privateKeyEncoding: "hex" | "base58" | "wif";
publicKey: string;
publicKeyEncoding: "hex" | "base58" | "wif";
};
type BuildAndSignRequest = {
chainId: string;
mnemonic: string;
txPayload: string;
txEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type BuildAndSignResult = {
chainId: string;
signedTxHex: string;
};
type BuildAndSignFromPrivateKeyRequest = {
chainId: string;
privateKey: string;
keyEncoding?: "utf8" | "hex" | "base64" | "wif"; // default: hex
txPayload: string;
txEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type BuildAndSignFromPrivateKeyResult = BuildAndSignResult;
type BtcSignMessageMode = "ecdsa" | "bip322-simple";
type SignMessageRequest = {
chainId: string;
mnemonic: string;
account: number;
index: number;
passphrase?: string | null;
message: string;
messageEncoding?: "utf8" | "hex" | "base64"; // default: utf8
mode?: BtcSignMessageMode; // btc only, default: ecdsa
};
type SignMessageFromPrivateKeyRequest = {
chainId: string;
privateKey: string;
keyEncoding?: "utf8" | "hex" | "base64" | "wif"; // default: hex
message: string;
messageEncoding?: "utf8" | "hex" | "base64"; // default: utf8
mode?: BtcSignMessageMode; // btc only, default: ecdsa
};
type SignBytesRequest = {
chainId: string;
mnemonic: string;
account: number;
index: number;
passphrase?: string | null;
bytes: string;
bytesEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type SignBytesFromPrivateKeyRequest = {
chainId: string;
privateKey: string;
keyEncoding?: "utf8" | "hex" | "base64" | "wif"; // default: hex
bytes: string;
bytesEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type SignTypedDataRequest = {
chainId: string;
mnemonic: string;
account: number;
index: number;
passphrase?: string | null;
typedData: string;
typedDataEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type SignTypedDataFromPrivateKeyRequest = {
chainId: string;
privateKey: string;
keyEncoding?: "utf8" | "hex" | "base64" | "wif"; // default: hex
typedData: string;
typedDataEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type SignResult = {
chainId: string;
signatureHex: string;
};
type InvokeChainExtensionRequest = {
chainId: string;
method: string;
payload: string;
payloadEncoding?: "utf8" | "hex" | "base64"; // default: utf8
};
type EthEcrecoverRequest = {
message: string;
messageEncoding?: "utf8" | "hex" | "base64"; // default: utf8
signature: string;
signatureEncoding?: "hex" | "base64"; // default: hex
};
type BtcNetwork = "mainnet" | "testnet" | "signet" | "regtest";
type BtcAddressType = "p2pkh" | "p2sh-p2wpkh" | "p2wpkh" | "p2tr";
type BtcDeriveAddressRequest = {
mnemonic: string;
account?: number; // default: 0
change?: number; // default: 0
index?: number; // default: 0
passphrase?: string | null;
network?: BtcNetwork; // default: mainnet
addressType?: BtcAddressType; // default: p2pkh
};
type BtcDeriveAddressFromPrivateKeyRequest = {
privateKey: string;
keyEncoding?: "utf8" | "hex" | "wif"; // default: wif
network?: BtcNetwork; // default: mainnet
addressType?: BtcAddressType; // default: p2pkh
};
type BtcDerivedWallet = DerivedWallet & {
network: BtcNetwork;
addressType: BtcAddressType;
};BTC API Notes
btcDeriveAddress/btcDeriveWalletsupport:network:mainnet,testnet,signet,regtest(defaultmainnet)addressType:p2pkh,p2sh-p2wpkh,p2wpkh,p2tr(defaultp2pkh)
btcDeriveWalletreturns:privateKeyin WIF (privateKeyEncoding: "wif")- compressed
publicKeyin hex (publicKeyEncoding: "hex")
btcDeriveWalletFromPrivateKeyaccepts both:- WIF private key (
keyEncoding: "wif") - hex private key (
keyEncoding: "hex")
- WIF private key (
btcSignPsbtis strict finalize path:- uses one private key
- requires all inputs to be finalizable in one call
btcSignPsbtWithSignerssupports production multi-signer orchestration:- accepts multiple signer keys in one call
- optional
targetInputIndexesper signer - supports
finalize: falsefor round-based signing andfinalize: truefor strict completion
btcBuildPsbt/btcBuildTxsupport both:- single-input fallback (
prevTxid,prevVout,prevScriptPubkeyHex,prevValueSat) - multi-UTXO mode (
inputs[]with per-inputtxid,vout,scriptPubkeyHex,valueSat, optional derivation path selectors)
- single-input fallback (
btcPartialSignPsbtis non-finalizing friendly:- signs only inputs controlled by the provided key
- returns
finalized+missingFinalizedInputsso multi-party rounds can continue
- DKG orchestration helpers (
0.2.7+):createBtcFrostDkgSession: stateful coordinator with replay protection andtoState/fromStaterunBtcFrostDkgSession: round1/round2/finalize convenience runner for offline coordinator workflows
- signing orchestration helpers (
0.2.14+):createBtcFrostSigningSession: stateful signing coordinator with replay protection andtoState/fromStaterunBtcFrostSigningSession: round1/build/round2/aggregate convenience runner for offline coordinator workflows
- SDK public API is chain-first:
- prefer
btc.*namespace (orbtcXxxflat chain-specific exports) - do not route wallet operations through generic
chainId-based aggregator methods
- prefer
Example
import {
btcBuildPsbt,
btcBuildTx,
btcDeriveAddress,
btcDeriveWallet,
btcDeriveAddressFromPrivateKey,
btcDeriveWalletFromPrivateKey,
btcPartialSignPsbt,
btcSignPsbt,
btcSignPsbtWithSigners,
btcSignTx,
btcVerifyPsbtFinalized,
ethBuildAndSignFromPrivateKey,
ethDeriveAddressFromPrivateKey,
ethSignMessage,
generateMnemonic,
validateMnemonic,
init,
listSupportedChains,
ethEcrecover,
} from "@bubolabs/wallet-rn-sdk";
const mnemonic = await generateMnemonic();
const isValid = await validateMnemonic(mnemonic);
if (!isValid) throw new Error("generated mnemonic must be valid");
await init();
const chains = await listSupportedChains();
const address = await btcDeriveAddress({
mnemonic,
account: 0,
change: 0,
index: 0,
passphrase: null,
});
const wallet = await btcDeriveWallet({
mnemonic,
account: 0,
change: 0,
index: 0,
});
// btc privateKey is WIF
const restoredBtc = await btcDeriveWalletFromPrivateKey({
privateKey: wallet.privateKey,
keyEncoding: "wif",
});
const btcTestnetTaproot = await btcDeriveWallet({
mnemonic,
account: 0,
change: 0,
index: 0,
network: "testnet",
addressType: "p2tr",
});
const btcNestedSegwitAddr = await btcDeriveAddressFromPrivateKey({
privateKey: wallet.privateKey,
keyEncoding: "wif",
network: "mainnet",
addressType: "p2sh-p2wpkh",
});
const signed = await btcBuildAndSign({
mnemonic,
txPayload: "<tx payload>",
txEncoding: "utf8",
});
const signedFromPk = await ethBuildAndSignFromPrivateKey({
privateKey: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
keyEncoding: "hex",
txPayload: "<tx payload>",
txEncoding: "utf8",
});
const messageSig = await ethSignMessage({
mnemonic,
account: 0,
index: 0,
passphrase: null,
message: "hello from bubo",
messageEncoding: "utf8",
});
const restored = await ethDeriveAddressFromPrivateKey({
privateKey: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
keyEncoding: "hex",
});
const recovered = await ethEcrecover({
message: "hello from bubo",
messageEncoding: "utf8",
signature: messageSig.signatureHex,
signatureEncoding: "hex",
});
const builtPsbt = await btcBuildPsbt({
mnemonic,
account: 0,
addressIndex: 0,
changeIndex: 0,
network: "testnet",
addressType: "p2wpkh",
prevTxid: "6000000000000000000000000000000000000000000000000000000000000006",
prevVout: 0,
prevScriptPubkeyHex: "0014d85c2b71d0060b09c9886aeb815e50991dda124d",
prevValueSat: 100000,
toAddress: "tb1q4ryc5g5q2sz66mh58f9x7w6j5xw5lf7k0k4a2s",
amountSat: 10000,
feeSat: 1000,
});
const builtPsbtMultiInput = await btcBuildPsbt({
mnemonic,
account: 0,
addressIndex: 0,
changeIndex: 0,
network: "testnet",
addressType: "p2wpkh",
inputs: [
{
txid: "7000000000000000000000000000000000000000000000000000000000000007",
vout: 0,
scriptPubkeyHex: "0014d85c2b71d0060b09c9886aeb815e50991dda124d",
valueSat: 60000,
},
{
txid: "8000000000000000000000000000000000000000000000000000000000000008",
vout: 1,
scriptPubkeyHex: "0014d85c2b71d0060b09c9886aeb815e50991dda124d",
valueSat: 50000,
},
],
toAddress: "tb1q4ryc5g5q2sz66mh58f9x7w6j5xw5lf7k0k4a2s",
amountSat: 100000,
feeSat: 1000,
});
const signedPsbt = await btcSignPsbt({
psbt: builtPsbt.psbtHex,
privateKey: wallet.privateKey, // WIF
keyEncoding: "wif",
network: "testnet",
});
const signedByMultiSigners = await btcSignPsbtWithSigners({
psbt: builtPsbtMultiInput.psbtHex,
network: "testnet",
finalize: false,
signers: [
{
privateKey: wallet.privateKey, // WIF
keyEncoding: "wif",
targetInputIndexes: [0],
},
],
});
const partiallySigned = await btcPartialSignPsbt({
psbt: builtPsbt.psbtHex,
privateKey: wallet.privateKey, // WIF
keyEncoding: "wif",
network: "testnet",
});
const builtTx = await btcBuildTx({
mnemonic,
account: 0,
addressIndex: 0,
changeIndex: 0,
network: "testnet",
addressType: "p2wpkh",
prevTxid: "6000000000000000000000000000000000000000000000000000000000000006",
prevVout: 0,
prevScriptPubkeyHex: "0014d85c2b71d0060b09c9886aeb815e50991dda124d",
prevValueSat: 100000,
toAddress: "tb1q4ryc5g5q2sz66mh58f9x7w6j5xw5lf7k0k4a2s",
amountSat: 10000,
feeSat: 1000,
});
const signedTx = await btcSignTx({
psbt: builtTx.psbtHex,
privateKey: wallet.privateKey, // WIF
keyEncoding: "wif",
network: "testnet",
});
const verifiedPsbt = await btcVerifyPsbtFinalized({
psbt: signedTx.psbtHex,
});txPayload JSON Examples
txEncoding uses "utf8" by default, so pass JSON text directly.
For full canonical schemas (required/optional/default fields), see
repo doc: docs/chain-payload-schemas.md.
BTC:
{
"account": 0,
"address_index": 0,
"change_index": 0,
"network": "mainnet",
"address_type": "p2pkh",
"prev_txid": "0000000000000000000000000000000000000000000000000000000000000001",
"prev_vout": 0,
"prev_script_pubkey_hex": "76a914d986ed01b7a22225a70edbf2ba7cfb63a15cb3aa88ac",
"prev_value_sat": 100000,
"to_address": "1BoatSLRHtKNngkdXEeobR76b53LETtpyT",
"amount_sat": 10000,
"fee_sat": 1000,
"lock_time": 0,
"sequence": 4294967295
}address_type supports p2pkh, p2sh-p2wpkh, p2wpkh, p2tr (default p2pkh).
ETH (legacy transfer):
{
"account": 0,
"index": 0,
"chain_id": 1,
"nonce": 0,
"gas_price_wei": "1000000000",
"gas_limit": 21000,
"to_address": "0x1111111111111111111111111111111111111111",
"value_wei": "1000000000000000"
}ETH (EIP-1559 transfer):
{
"account": 0,
"index": 0,
"chain_id": 1,
"nonce": 0,
"max_fee_per_gas_wei": "1000000000",
"max_priority_fee_per_gas_wei": "100000000",
"gas_limit": 21000,
"to_address": "0x1111111111111111111111111111111111111111",
"value_wei": "1000000000000000"
}For ETH tx payload, use either legacy (gas_price_wei) or EIP-1559
(max_fee_per_gas_wei + max_priority_fee_per_gas_wei), not both.
ETH build-path APIs (ethBuildTx, ethBuildAndSign*, and generic
buildAndSign* with chainId: "eth") run adapter preflight checks for
txEncoding: "utf8" JSON payloads:
- required fields:
nonce,gas_limit,to_address - fee-model exclusivity: legacy vs EIP-1559 cannot be mixed
- EIP-1559 constraint:
max_priority_fee_per_gas_wei <= max_fee_per_gas_wei - supports camelCase aliases and normalizes to snake_case before native call:
chainId,gasLimit,toAddress,gasPriceWei,maxFeePerGasWei,maxPriorityFeePerGasWei,valueWei,dataHex
Solana (system transfer):
{
"tx_kind": "system_transfer",
"account": 0,
"index": 0,
"to_address": "11111111111111111111111111111111",
"lamports": 1000,
"message_version": "legacy",
"recent_blockhash": "11111111111111111111111111111111"
}Solana (SPL token transfer, transferChecked):
{
"tx_kind": "spl_token_transfer",
"account": 0,
"index": 0,
"source_token_account": "11111111111111111111111111111111",
"destination_token_account": "11111111111111111111111111111111",
"mint_address": "11111111111111111111111111111111",
"amount": 1000,
"decimals": 6,
"message_version": "legacy",
"recent_blockhash": "11111111111111111111111111111111"
}Solana (high-level v0 compile with ALT lookup tables):
{
"tx_kind": "system_transfer",
"account": 0,
"index": 0,
"to_address": "11111111111111111111111111111111",
"lamports": 1000,
"message_version": "v0",
"address_lookup_tables": [
{
"table_address": "11111111111111111111111111111111",
"addresses": ["11111111111111111111111111111111"]
}
],
"recent_blockhash": "11111111111111111111111111111111"
}Solana (precompiled versioned message, including ALT-compiled message bytes):
{
"tx_kind": "versioned_message",
"versioned_message": "<base64-serialized-VersionedMessage>",
"versioned_message_encoding": "base64"
}For Solana tx payload:
tx_kinddefaults tosystem_transferwhen omitted.spl_token_transferuses SPL Token program transferChecked (amountin raw token units).- optional
token_program_idcan override the default SPL Token program id. message_versionsupportslegacy(default) andv0.address_lookup_tablescan be provided whenmessage_version=v0; each table hastable_address+addresses[].versioned_messagelets app/backend provide precompiled message bytes (ALT flows included).- for
versioned_message,recent_blockhashis not required in payload (already embedded in message bytes). - for
solanaBuildTx+versioned_message, signer fields are optional; for other Solana tx kinds signer is required.
Chain Feature Notes
- Published package content depends on Rust feature set at build time.
- When
BUBO_RUST_FEATURESis not set, packaging auto-enables allchain-*features fromcrates/ffi/Cargo.toml. signMessage*is implemented foreth,btc, andsolana.signBytes*is implemented forbtcandsolana.signTypedData*is implemented only foreth.- You can still override for focused builds, for example:
BUBO_RUST_FEATURES=chain-eth,chain-btc ./scripts/build-rn-sdk-native-artifacts.shCompatibility Smoke API
Legacy smoke APIs remain available for regression checks:
runSmoke()runBtcSmoke()
type WalletSmokeResult = {
supportedChains: Array<{ chainId: string; capabilities: string[] }>;
deriveChainId: string;
derivedAddress: string;
signChainId: string | null;
signedTxHex: string | null;
};Distribution Scripts
cd ../..
./scripts/package-rn-sdk.shBuilds a publishable tarball and verifies runtime assets are present/synced.
./scripts/verify-rn-sdk-distribution.shInstalls the tarball into example/ and verifies iOS/Android native builds.
./scripts/publish-rn-sdk.sh --dry-run
./scripts/publish-rn-sdk.sh --publish --tag latestPublish wrapper:
- default is dry-run
- real publish requires
--publish
