1- import { Address , AddressType } from './Address' ;
2- import { OpaqueString , assertIsBech32WithPrefix , typedBech32 } from '@cardano-sdk/util' ;
1+ import * as BaseEncoding from '@scure/base' ;
2+ import { Address , AddressType , Credential , CredentialType } from './Address' ;
3+ import { Hash28ByteBase16 } from '@cardano-sdk/crypto' ;
4+ import { OpaqueString , typedBech32 } from '@cardano-sdk/util' ;
5+
6+ const MAX_BECH32_LENGTH_LIMIT = 1023 ;
7+ const CIP_105_DREP_ID_LENGTH = 28 ;
8+ const CIP_129_DREP_ID_LENGTH = 29 ;
9+
310/** DRepID as bech32 string */
411export type DRepID = OpaqueString < 'DRepID' > ;
512
6- /**
7- * @param {string } value DRepID as bech32 string
8- * @throws InvalidStringError
9- */
10- export const DRepID = ( value : string ) : DRepID => typedBech32 ( value , [ 'drep' ] ) ;
13+ // CIP-105 is deprecated, however we still need to support it since several providers and tooling
14+ // stills uses this format.
15+ export const DRepID = ( value : string ) : DRepID => {
16+ try {
17+ return typedBech32 ( value , [ 'drep' ] , 47 ) ;
18+ } catch {
19+ return typedBech32 ( value , [ 'drep' , 'drep_script' ] , 45 ) ;
20+ }
21+ } ;
1122
1223DRepID . isValid = ( value : string ) : boolean => {
1324 try {
14- assertIsBech32WithPrefix ( value , 'drep' ) ;
25+ DRepID ( value ) ;
1526 return true ;
1627 } catch {
1728 return false ;
@@ -25,3 +36,73 @@ DRepID.canSign = (value: string): boolean => {
2536 return false ;
2637 }
2738} ;
39+
40+ DRepID . cip105FromCredential = ( credential : Credential ) : DRepID => {
41+ let prefix = 'drep' ;
42+ if ( credential . type === CredentialType . ScriptHash ) {
43+ prefix = 'drep_script' ;
44+ }
45+
46+ const words = BaseEncoding . bech32 . toWords ( Buffer . from ( credential . hash , 'hex' ) ) ;
47+
48+ return BaseEncoding . bech32 . encode ( prefix , words , MAX_BECH32_LENGTH_LIMIT ) as DRepID ;
49+ } ;
50+
51+ DRepID . cip129FromCredential = ( credential : Credential ) : DRepID => {
52+ // The CIP-129 header is defined by 2 nibbles, where the first 4 bits represent the kind of governance credential
53+ // (CC Hot, CC Cold and DRep), and the last 4 bits are the credential type (offset by 2 to ensure that governance
54+ // identifiers remain distinct and are not inadvertently processed as addresses).
55+ let header = '22' ; // DRep-PubKeyHash header in hex [00100010]
56+ if ( credential . type === CredentialType . ScriptHash ) {
57+ header = '23' ; // DRep-ScriptHash header in hex [00100011]
58+ }
59+
60+ const cip129payload = `${ header } ${ credential . hash } ` ;
61+ const words = BaseEncoding . bech32 . toWords ( Buffer . from ( cip129payload , 'hex' ) ) ;
62+
63+ return BaseEncoding . bech32 . encode ( 'drep' , words , MAX_BECH32_LENGTH_LIMIT ) as DRepID ;
64+ } ;
65+
66+ DRepID . toCredential = ( drepId : DRepID ) : Credential => {
67+ const { words } = BaseEncoding . bech32 . decode ( drepId , MAX_BECH32_LENGTH_LIMIT ) ;
68+ const payload = BaseEncoding . bech32 . fromWords ( words ) ;
69+
70+ if ( payload . length !== CIP_105_DREP_ID_LENGTH && payload . length !== CIP_129_DREP_ID_LENGTH ) {
71+ throw new Error ( 'Invalid DRepID payload' ) ;
72+ }
73+
74+ if ( payload . length === CIP_105_DREP_ID_LENGTH ) {
75+ const isScriptHash = drepId . includes ( 'drep_script' ) ;
76+
77+ return {
78+ hash : Hash28ByteBase16 ( Buffer . from ( payload ) . toString ( 'hex' ) ) ,
79+ type : isScriptHash ? CredentialType . ScriptHash : CredentialType . KeyHash
80+ } ;
81+ }
82+
83+ // CIP-129
84+ const header = payload [ 0 ] ;
85+ const hash = payload . slice ( 1 ) ;
86+ const isDrepGovCred = ( header & 0x20 ) === 0x20 ; // 0b00100000
87+ const isScriptHash = ( header & 0x03 ) === 0x03 ; // 0b00000011
88+
89+ if ( ! isDrepGovCred ) {
90+ throw new Error ( 'Invalid governance credential type' ) ;
91+ }
92+
93+ return {
94+ hash : Hash28ByteBase16 ( Buffer . from ( hash ) . toString ( 'hex' ) ) ,
95+ type : isScriptHash ? CredentialType . ScriptHash : CredentialType . KeyHash
96+ } ;
97+ } ;
98+
99+ // Use these if you need to ensure the ID is in a specific format.
100+ DRepID . toCip105DRepID = ( drepId : DRepID ) : DRepID => {
101+ const credential = DRepID . toCredential ( drepId ) ;
102+ return DRepID . cip105FromCredential ( credential ) ;
103+ } ;
104+
105+ DRepID . toCip129DRepID = ( drepId : DRepID ) : DRepID => {
106+ const credential = DRepID . toCredential ( drepId ) ;
107+ return DRepID . cip129FromCredential ( credential ) ;
108+ } ;
0 commit comments