diff --git a/modules/sdk-coin-stx/src/lib/utils.ts b/modules/sdk-coin-stx/src/lib/utils.ts
index c82fc52498..9e56737e3f 100644
--- a/modules/sdk-coin-stx/src/lib/utils.ts
+++ b/modules/sdk-coin-stx/src/lib/utils.ts
@@ -21,8 +21,10 @@ import {
deserializeTransaction,
PubKeyEncoding,
publicKeyFromSignature,
+ serializeCV,
signWithKey,
StacksTransaction,
+ standardPrincipalCV,
TransactionVersion,
validateStacksAddress,
} from '@stacks/transactions';
@@ -505,6 +507,33 @@ export function getAddressVersion(address: string): AddressVersion {
return createAddress(baseAddress).version;
}
+/**
+ * Encode a Stacks *standard* principal as the Clarity SIP-005 hex wire format
+ * — a 22-byte blob:
+ *
+ * `0x05` || version (1 byte) || hash160 (20 bytes)
+ *
+ * Contract principals (`
.`) and addresses with a
+ * `?memoId=…` suffix are rejected. The Clarity type byte (`0x05`) is set
+ * by `serializeCV` from `@stacks/transactions`, so this helper does not
+ * hand-roll any byte concatenation.
+ *
+ * @param {string} principal a Stacks standard principal (e.g. `SP…` / `ST…`)
+ * @returns {string} hex-encoded 22-byte Clarity standard principal
+ */
+export function getEncodedPrincipal(principal: string): string {
+ if (principal.includes('?')) {
+ throw new UtilsError(`principal must not include a query string: ${principal}`);
+ }
+ if (principal.includes('.')) {
+ throw new UtilsError(`contract principals are not supported, expected a standard principal: ${principal}`);
+ }
+ if (!isValidAddress(principal)) {
+ throw new UtilsError(`invalid Stacks address in principal: ${principal}`);
+ }
+ return serializeCV(standardPrincipalCV(principal)).toString('hex');
+}
+
/**
* Returns a STX pub key from an xpub
*
diff --git a/modules/sdk-coin-stx/test/unit/util.ts b/modules/sdk-coin-stx/test/unit/util.ts
index ef4fe40954..f26fdff1ea 100644
--- a/modules/sdk-coin-stx/test/unit/util.ts
+++ b/modules/sdk-coin-stx/test/unit/util.ts
@@ -323,6 +323,32 @@ describe('Stx util library', function () {
});
});
+ describe('getEncodedPrincipal', function () {
+ it('should encode a testnet standard principal as the 22-byte Clarity blob', function () {
+ Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C').should.equal(
+ '051ad206838b7981a116c334e8cb1b950afb73eb54a5'
+ );
+ });
+
+ it('should reject contract principals', function () {
+ should.throws(
+ () => Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C.my-contract'),
+ /contract principals are not supported/
+ );
+ });
+
+ it('should reject addresses with a memoId suffix', function () {
+ should.throws(
+ () => Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C?memoId=0'),
+ /must not include a query string/
+ );
+ });
+
+ it('should reject invalid addresses', function () {
+ should.throws(() => Utils.getEncodedPrincipal('not-a-stacks-address'), /invalid Stacks address/);
+ });
+ });
+
describe('xpubToSTXPubkey', function () {
it('should succeed to convert for valid xpubs', function () {
Utils.xpubToSTXPubkey(