Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
21477bf
chore: version packages to 1.62.12-tron-support.0
NeOMakinG Nov 26, 2025
6b22beb
chore: temporarily skip build for publish
NeOMakinG Nov 26, 2025
5b368ce
chore: version packages to 1.62.12-tron-alpha.0
NeOMakinG Nov 26, 2025
b7fa895
chore: version packages to 1.62.12-tron-alpha.1
NeOMakinG Nov 26, 2025
6624af5
fix: rename adapter to tronAdapter to avoid property collision with S…
NeOMakinG Nov 26, 2025
844e640
chore: version packages to 1.62.14-tron-adapter-fix.0
NeOMakinG Nov 26, 2025
4536ed9
fix: resolve TypeScript error in GridPlus ethereum transaction data
NeOMakinG Nov 26, 2025
8dec9a0
fix: restore proper build scripts in package.json files
NeOMakinG Nov 26, 2025
6fc3d0b
chore: version packages to 1.62.14-tron-adapter-fix.1
NeOMakinG Nov 26, 2025
0791b70
chore: fix clean scripts and version to 1.62.14-tron-adapter-fix.2
NeOMakinG Nov 26, 2025
2011f88
chore: update gitHead in package.json files
NeOMakinG Nov 26, 2025
516d886
fix: convert Uint8Array to Buffer before toString in TRON address der…
NeOMakinG Nov 26, 2025
b1bf1e9
fix: use slice(1) instead of slice(2) for TRON public key
NeOMakinG Nov 26, 2025
cdfb59c
chore: bump version to 1.62.14-tron-adapter-fix.4
NeOMakinG Nov 26, 2025
4119922
fix: lint
NeOMakinG Nov 27, 2025
d71a3fe
fix: reset packages
NeOMakinG Nov 27, 2025
f441d6e
fix: reset gridplus
NeOMakinG Nov 27, 2025
99f5a85
fix: reset yarn
NeOMakinG Nov 27, 2025
822f49a
chore: bump version to 1.62.15-tron.1
gomesalexandre Nov 27, 2025
5cd6b3e
chore: reset package.json and lerna.json to origin/master
gomesalexandre Nov 27, 2025
6ff6cda
Merge remote-tracking branch 'origin/master' into tron-support-native
gomesalexandre Nov 27, 2025
caa3b9d
chore(release): publish 1.62.14
gomesalexandre Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/hdwallet-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./terra";
export * from "./thorchain";
export * from "./solana";
export * from "./transport";
export * from "./tron";
export * from "./utils";
export * from "./utxoUtils";
export * from "./wallet";
87 changes: 87 additions & 0 deletions packages/hdwallet-core/src/tron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { addressNListToBIP32, slip44ByCoin } from "./utils";
import { BIP32Path, HDWallet, HDWalletInfo, PathDescription } from "./wallet";

export interface TronGetAddress {
addressNList: BIP32Path;
showDisplay?: boolean;
pubKey?: string;
}

export interface TronSignTx {
addressNList: BIP32Path;
/** Raw transaction data in hex format */
rawDataHex: string;
}

export interface TronSignedTx {
serialized: string;
signature: string;
}

export interface TronTxSignature {
signature: string;
}

export interface TronGetAccountPaths {
accountIdx: number;
}

export interface TronAccountPath {
addressNList: BIP32Path;
}

export interface TronWalletInfo extends HDWalletInfo {
readonly _supportsTronInfo: boolean;

/**
* Returns a list of bip32 paths for a given account index in preferred order
* from most to least preferred.
*/
tronGetAccountPaths(msg: TronGetAccountPaths): Array<TronAccountPath>;

/**
* Returns the "next" account path, if any.
*/
tronNextAccountPath(msg: TronAccountPath): TronAccountPath | undefined;
}

export interface TronWallet extends TronWalletInfo, HDWallet {
readonly _supportsTron: boolean;

tronGetAddress(msg: TronGetAddress): Promise<string | null>;
tronGetAddresses?(msgs: TronGetAddress[]): Promise<string[]>;
tronSignTx(msg: TronSignTx): Promise<TronSignedTx | null>;
tronSendTx?(msg: TronSignTx): Promise<TronTxSignature | null>;
}

export function tronDescribePath(path: BIP32Path): PathDescription {
const pathStr = addressNListToBIP32(path);
const unknown: PathDescription = {
verbose: pathStr,
coin: "Tron",
isKnown: false,
};

if (path.length != 5) return unknown;
if (path[0] != 0x80000000 + 44) return unknown;
if (path[1] != 0x80000000 + slip44ByCoin("Tron")) return unknown;
if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;
if (path[3] !== 0) return unknown;
if (path[4] !== 0) return unknown;

const index = path[2] & 0x7fffffff;
return {
verbose: `Tron Account #${index}`,
accountIdx: index,
wholeAccount: true,
coin: "Tron",
isKnown: true,
};
}

// The standard BIP44 derivation path for Tron is: m/44'/195'/<account>'/0/0
Comment thread
gomesalexandre marked this conversation as resolved.
// https://github.com/tronprotocol/tips/issues/102
export function tronGetAccountPaths(msg: TronGetAccountPaths): Array<TronAccountPath> {
const slip44 = slip44ByCoin("Tron");
return [{ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0] }];
}
1 change: 1 addition & 0 deletions packages/hdwallet-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const slip44Table = Object.freeze({
Terra: 330,
Kava: 459,
Solana: 501,
Tron: 195,
// EVM chains all use the same SLIP44
Ethereum: 60,
Avalanche: 60,
Expand Down
9 changes: 9 additions & 0 deletions packages/hdwallet-core/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SolanaWallet, SolanaWalletInfo } from "./solana";
import { TerraWallet, TerraWalletInfo } from "./terra";
import { ThorchainWallet, ThorchainWalletInfo } from "./thorchain";
import { Transport } from "./transport";
import { TronWallet, TronWalletInfo } from "./tron";

export type BIP32Path = Array<number>;

Expand Down Expand Up @@ -257,6 +258,14 @@ export function infoSolana(info: HDWalletInfo): info is SolanaWalletInfo {
return isObject(info) && (info as any)._supportsSolanaInfo;
}

export function supportsTron(wallet: HDWallet): wallet is TronWallet {
return isObject(wallet) && (wallet as any)._supportsTron;
}

export function infoTron(info: HDWalletInfo): info is TronWalletInfo {
return isObject(info) && (info as any)._supportsTronInfo;
}

export function supportsDebugLink(wallet: HDWallet): wallet is DebugLinkWallet {
return isObject(wallet) && (wallet as any)._supportsDebugLink;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as Binance } from "./binance";
export { default as Cosmos } from "./cosmos";
export { default as CosmosDirect } from "./cosmosDirect";
export { default as Solana } from "./solana";
export { default as Tron } from "./tron";
69 changes: 69 additions & 0 deletions packages/hdwallet-native/src/crypto/isolation/adapters/tron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { keccak256 } from "@ethersproject/keccak256";
import * as core from "@shapeshiftoss/hdwallet-core";
import * as bs58check from "bs58check";

import { Isolation } from "../..";
import { SecP256K1 } from "../core";

const TRON_ADDRESS_PREFIX = 0x41;

export class TronAdapter {
protected readonly nodeAdapter: Isolation.Adapters.BIP32;

constructor(nodeAdapter: Isolation.Adapters.BIP32) {
this.nodeAdapter = nodeAdapter;
}

/**
* Get TRON address from BIP32 path
* Address generation:
* 1. Get uncompressed public key (65 bytes)
* 2. Remove first byte (0x04)
* 3. Hash with Keccak256
* 4. Take last 20 bytes
* 5. Prepend 0x41
* 6. Base58check encode
*/
async getAddress(addressNList: core.BIP32Path): Promise<string> {
const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList));
const publicKey = SecP256K1.UncompressedPoint.from(nodeAdapter.getPublicKey());

// Remove the 0x04 prefix from uncompressed public key
const publicKeyBytes = publicKey.slice(1);

// Hash with Keccak256
const hash = keccak256("0x" + Buffer.from(publicKeyBytes).toString("hex"));

// Take last 20 bytes and prepend 0x41 (TRON prefix)
const addressBytes = Buffer.concat([Buffer.from([TRON_ADDRESS_PREFIX]), Buffer.from(hash.slice(-40), "hex")]);

// Base58check encode
return bs58check.encode(addressBytes);
}

/**
* Sign TRON transaction
* Transaction signing:
* 1. Get raw transaction hex
* 2. Hash with SHA256 (NOT Keccak256)
* 3. Sign with secp256k1
* 4. Return hex signature
*/
async signTransaction(rawDataHex: string, addressNList: core.BIP32Path): Promise<string> {
const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList));
const txBuf = Buffer.from(rawDataHex, "hex");

// TRON uses SHA256 for transaction signing
const recoverableSig = await SecP256K1.RecoverableSignature.signCanonically(nodeAdapter.node, "sha256", txBuf);

const sig = SecP256K1.RecoverableSignature.sig(recoverableSig);
const recoveryParam = SecP256K1.RecoverableSignature.recoveryParam(recoverableSig);

// Concatenate signature (64 bytes) + recovery param (1 byte)
const fullSig = core.compatibleBufferConcat([sig, Buffer.from([recoveryParam])]);

return fullSig.toString("hex");
}
}

export default TronAdapter;
34 changes: 23 additions & 11 deletions packages/hdwallet-native/src/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MixinNativeSecretWallet, MixinNativeSecretWalletInfo } from "./secret";
import { MixinNativeSolanaWallet, MixinNativeSolanaWalletInfo } from "./solana";
import { MixinNativeTerraWallet, MixinNativeTerraWalletInfo } from "./terra";
import { MixinNativeThorchainWallet, MixinNativeThorchainWalletInfo } from "./thorchain";
import { MixinNativeTronWallet, MixinNativeTronWalletInfo } from "./tron";

export enum NativeEvents {
MNEMONIC_REQUIRED = "MNEMONIC_REQUIRED",
Expand Down Expand Up @@ -126,12 +127,14 @@ class NativeHDWalletInfo
MixinNativeCosmosWalletInfo(
MixinNativeBinanceWalletInfo(
MixinNativeSolanaWalletInfo(
MixinNativeThorchainWalletInfo(
MixinNativeMayachainWalletInfo(
MixinNativeSecretWalletInfo(
MixinNativeTerraWalletInfo(
MixinNativeKavaWalletInfo(
MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase))
MixinNativeTronWalletInfo(
MixinNativeThorchainWalletInfo(
MixinNativeMayachainWalletInfo(
MixinNativeSecretWalletInfo(
MixinNativeTerraWalletInfo(
MixinNativeKavaWalletInfo(
MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase))
)
)
)
)
Expand All @@ -149,6 +152,7 @@ class NativeHDWalletInfo
core.CosmosWalletInfo,
core.BinanceWalletInfo,
core.SolanaWalletInfo,
core.TronWalletInfo,
core.ThorchainWalletInfo,
core.MayachainWalletInfo,
core.SecretWalletInfo,
Expand Down Expand Up @@ -203,6 +207,9 @@ class NativeHDWalletInfo
return core.osmosisDescribePath(msg.path);
case "arkeo":
return core.arkeoDescribePath(msg.path);
case "tron":
case "trx":
return core.tronDescribePath(msg.path);
default:
throw new Error("Unsupported path");
}
Expand All @@ -215,11 +222,13 @@ export class NativeHDWallet
MixinNativeCosmosWallet(
MixinNativeBinanceWallet(
MixinNativeSolanaWallet(
MixinNativeThorchainWallet(
MixinNativeMayachainWallet(
MixinNativeSecretWallet(
MixinNativeTerraWallet(
MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo)))
MixinNativeTronWallet(
MixinNativeThorchainWallet(
MixinNativeMayachainWallet(
MixinNativeSecretWallet(
MixinNativeTerraWallet(
MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo)))
)
)
)
)
Expand All @@ -236,6 +245,7 @@ export class NativeHDWallet
core.CosmosWallet,
core.BinanceWallet,
core.SolanaWallet,
core.TronWallet,
core.ThorchainWallet,
core.MayachainWallet,
core.SecretWallet,
Expand Down Expand Up @@ -359,6 +369,7 @@ export class NativeHDWallet
super.cosmosInitializeWallet(secp256k1MasterKey),
super.osmosisInitializeWallet(secp256k1MasterKey),
super.binanceInitializeWallet(secp256k1MasterKey),
super.tronInitializeWallet(secp256k1MasterKey),
super.thorchainInitializeWallet(secp256k1MasterKey),
super.mayachainInitializeWallet(secp256k1MasterKey),
super.secretInitializeWallet(secp256k1MasterKey),
Expand Down Expand Up @@ -412,6 +423,7 @@ export class NativeHDWallet
super.cosmosWipe();
super.osmosisWipe();
super.binanceWipe();
super.tronWipe();
super.thorchainWipe();
super.mayachainWipe();
super.secretWipe();
Expand Down
67 changes: 67 additions & 0 deletions packages/hdwallet-native/src/tron.ts
Comment thread
gomesalexandre marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as core from "@shapeshiftoss/hdwallet-core";

import { Isolation } from "./crypto";
import { TronAdapter } from "./crypto/isolation/adapters/tron";
import { NativeHDWalletBase } from "./native";

export function MixinNativeTronWalletInfo<TBase extends core.Constructor<core.HDWalletInfo>>(Base: TBase) {
// eslint-disable-next-line @typescript-eslint/no-shadow
return class MixinNativeTronWalletInfo extends Base implements core.TronWalletInfo {
readonly _supportsTronInfo = true;

tronGetAccountPaths(msg: core.TronGetAccountPaths): Array<core.TronAccountPath> {
return core.tronGetAccountPaths(msg);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
tronNextAccountPath(msg: core.TronAccountPath): core.TronAccountPath | undefined {
throw new Error("Method not implemented");
}
};
}

export function MixinNativeTronWallet<TBase extends core.Constructor<NativeHDWalletBase>>(Base: TBase) {
// eslint-disable-next-line @typescript-eslint/no-shadow
return class MixinNativeTronWallet extends Base {
readonly _supportsTron = true;

tronAdapter: TronAdapter | undefined;

async tronInitializeWallet(masterKey: Isolation.Core.BIP32.Node): Promise<void> {
const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey);
this.tronAdapter = new TronAdapter(nodeAdapter);
}

tronWipe() {
this.tronAdapter = undefined;
}

async tronGetAddress(msg: core.TronGetAddress): Promise<string | null> {
return this.needsMnemonic(!!this.tronAdapter, () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.tronAdapter!.getAddress(msg.addressNList);
});
}

async tronSignTx(msg: core.TronSignTx): Promise<core.TronSignedTx | null> {
return this.needsMnemonic(!!this.tronAdapter, async () => {
const address = await this.tronGetAddress({
addressNList: msg.addressNList,
showDisplay: false,
});

if (!address) throw new Error("Failed to get TRON address");

const signature = await this.tronAdapter!.signTransaction(msg.rawDataHex, msg.addressNList);

// Serialized transaction = rawDataHex + signature
const serialized = msg.rawDataHex + signature;

return {
serialized,
signature,
};
});
}
};
}
Loading