Skip to content

Commit 426ff1c

Browse files
authored
feat(sdk-coin-stx): add getEncodedPrincipal util for Clarity standard principles
2 parents 9b182de + c531a39 commit 426ff1c

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

modules/sdk-coin-stx/src/lib/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import {
2121
deserializeTransaction,
2222
PubKeyEncoding,
2323
publicKeyFromSignature,
24+
serializeCV,
2425
signWithKey,
2526
StacksTransaction,
27+
standardPrincipalCV,
2628
TransactionVersion,
2729
validateStacksAddress,
2830
} from '@stacks/transactions';
@@ -505,6 +507,33 @@ export function getAddressVersion(address: string): AddressVersion {
505507
return createAddress(baseAddress).version;
506508
}
507509

510+
/**
511+
* Encode a Stacks *standard* principal as the Clarity SIP-005 hex wire format
512+
* — a 22-byte blob:
513+
*
514+
* `0x05` || version (1 byte) || hash160 (20 bytes)
515+
*
516+
* Contract principals (`<address>.<contract-name>`) and addresses with a
517+
* `?memoId=…` suffix are rejected. The Clarity type byte (`0x05`) is set
518+
* by `serializeCV` from `@stacks/transactions`, so this helper does not
519+
* hand-roll any byte concatenation.
520+
*
521+
* @param {string} principal a Stacks standard principal (e.g. `SP…` / `ST…`)
522+
* @returns {string} hex-encoded 22-byte Clarity standard principal
523+
*/
524+
export function getEncodedPrincipal(principal: string): string {
525+
if (principal.includes('?')) {
526+
throw new UtilsError(`principal must not include a query string: ${principal}`);
527+
}
528+
if (principal.includes('.')) {
529+
throw new UtilsError(`contract principals are not supported, expected a standard principal: ${principal}`);
530+
}
531+
if (!isValidAddress(principal)) {
532+
throw new UtilsError(`invalid Stacks address in principal: ${principal}`);
533+
}
534+
return serializeCV(standardPrincipalCV(principal)).toString('hex');
535+
}
536+
508537
/**
509538
* Returns a STX pub key from an xpub
510539
*

modules/sdk-coin-stx/test/unit/util.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,32 @@ describe('Stx util library', function () {
323323
});
324324
});
325325

326+
describe('getEncodedPrincipal', function () {
327+
it('should encode a testnet standard principal as the 22-byte Clarity blob', function () {
328+
Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C').should.equal(
329+
'051ad206838b7981a116c334e8cb1b950afb73eb54a5'
330+
);
331+
});
332+
333+
it('should reject contract principals', function () {
334+
should.throws(
335+
() => Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C.my-contract'),
336+
/contract principals are not supported/
337+
);
338+
});
339+
340+
it('should reject addresses with a memoId suffix', function () {
341+
should.throws(
342+
() => Utils.getEncodedPrincipal('ST390D0WBF60T25P36KMCP6WN1BXQ7TTMMQKNV15C?memoId=0'),
343+
/must not include a query string/
344+
);
345+
});
346+
347+
it('should reject invalid addresses', function () {
348+
should.throws(() => Utils.getEncodedPrincipal('not-a-stacks-address'), /invalid Stacks address/);
349+
});
350+
});
351+
326352
describe('xpubToSTXPubkey', function () {
327353
it('should succeed to convert for valid xpubs', function () {
328354
Utils.xpubToSTXPubkey(

0 commit comments

Comments
 (0)