Skip to content

Commit a447d05

Browse files
committed
improved Address module: refactored address/crossAccountId extraction
1 parent cba1942 commit a447d05

8 files changed

Lines changed: 394 additions & 311 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ import {Utf8, Utf16, HexString} from '@unique-nft/utils/string'
4848
// import {StringUtils} from '@unique-nft/utils'
4949

5050
Utf8.stringToU8a('a 🌷') // Uint8Array [97, 32, 240, 159, 140, 183]
51-
Utf8.u8aToString([97, 32, 240, 159, 140, 183]) // "a 🌷"
51+
Utf8.u8aToString([97, 32, 240, 159, 140, 183]) // "a 🌷" // it takes usual number arrays too
52+
Utf8.hexStringToString('0x6120f09f8cb7') // "a 🌷"
53+
Utf8.stringToHexString('a 🌷') // "0x6120f09f8cb7"
5254
Utf8.lengthInBytes('a 🌷') // 6
5355

5456
Utf16.stringToU16a('a 🌷') // Uint16Array [97, 32, 55356, 57143]

docs/Address.md

Lines changed: 120 additions & 241 deletions
Large diffs are not rendered by default.

src/Address/crossAccountId.ts

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,51 @@
11
import {normalizeEthereumAddress} from './ethereum'
22
import {normalizeSubstrateAddress} from './substrate'
33
import {CrossAccountId} from '../types'
4-
import {is, mirror, normalize} from './index'
4+
import {is, mirror, normalize, validate} from './index'
55

6-
export const guessAddressAndExtractItNormalizedSafe = (address: string | object): string | null => {
7-
if (typeof address === 'object') {
8-
if (is.substrateAddressObject(address)) return normalize.substrateAddress(address.Substrate)
9-
else if (is.substrateAddressObjectUncapitalized(address)) return normalize.substrateAddress(address.substrate)
10-
else if (is.ethereumAddressObject(address)) return normalizeEthereumAddress(address.Ethereum)
11-
else if (is.ethereumAddressObjectUncapitalized(address)) return normalizeEthereumAddress(address.ethereum)
12-
else return null
13-
}
14-
if (typeof address === 'string') {
15-
if (is.substrateAddress(address)) return normalizeSubstrateAddress(address)
16-
else if (is.ethereumAddress(address)) return normalizeEthereumAddress(address)
17-
else return null
18-
}
19-
20-
return null
21-
}
6+
export const guessAddressAndExtractCrossAccountIdUnsafe = (rawAddress: string | object, normalize: boolean = false): CrossAccountId => {
7+
const address = rawAddress as any
228

23-
export const guessAddressAndExtractItNormalized = (address: string | object): string => {
24-
const result = guessAddressAndExtractItNormalizedSafe(address)
25-
if (!result) {
26-
throw new Error(`Passed address is not a valid address string or object: ${JSON.stringify(address).slice(0, 100)}`)
9+
if (typeof address === 'object') {
10+
if (address.hasOwnProperty('Substrate')) {
11+
validate.substrateAddress(address.Substrate)
12+
return {Substrate: normalize ? normalizeSubstrateAddress(address.Substrate) : address.Substrate}
13+
} else if (address.hasOwnProperty('substrate')) {
14+
validate.substrateAddress(address.substrate)
15+
return {Substrate: normalize ? normalizeSubstrateAddress(address.substrate) : address.substrate}
16+
} else if (address.hasOwnProperty('Ethereum')) {
17+
validate.ethereumAddress(address.Ethereum)
18+
return {Ethereum: normalize ? normalizeEthereumAddress(address.Ethereum) : address.Ethereum}
19+
} else if (address.hasOwnProperty('ethereum')) {
20+
validate.ethereumAddress(address.ethereum)
21+
return {Ethereum: normalize ? normalizeEthereumAddress(address.ethereum) : address.ethereum}
22+
} else {
23+
throw new Error(`Address ${address} is not a valid crossAccountId object (should contain "Substrate"/"substrate" or "Ethereum"/"ethereum" field)`)
24+
}
2725
}
28-
return result
29-
}
3026

31-
export const addressToCrossAccountId = (address: string): CrossAccountId => {
32-
if (is.substrateAddress(address)) {
33-
return {Substrate: address}
34-
} else if (is.ethereumAddress(address)) {
35-
return {Ethereum: address}
27+
if (typeof address === 'string') {
28+
if (is.substrateAddress(address)) return {Substrate: normalize ? normalizeSubstrateAddress(address) : address}
29+
else if (is.ethereumAddress(address)) return {Ethereum: normalize ? normalizeEthereumAddress(address) : address}
30+
else {
31+
throw new Error(`Address ${address} is not a valid Substrate or Ethereum address`)
32+
}
3633
}
3734

38-
throw new Error(`Passed address ${address} is not substrate nor ethereum address`)
35+
throw new Error(`Address ${address} is not a string or object: ${typeof address}`)
3936
}
4037

41-
export const addressToCrossAccountIdNormalized = (address: string): CrossAccountId => {
42-
if (is.substrateAddress(address)) {
43-
return {Substrate: normalize.substrateAddress(address)}
44-
} else if (is.ethereumAddress(address)) {
45-
return {Ethereum: normalize.ethereumAddress(address)}
38+
export const guessAddressAndExtractCrossAccountIdSafe = (address: string | object, normalize: boolean = false): CrossAccountId | null => {
39+
try {
40+
return guessAddressAndExtractCrossAccountIdUnsafe(address, normalize)
41+
} catch {
42+
return null
4643
}
47-
48-
throw new Error(`Passed address ${address} is not substrate nor ethereum address`)
4944
}
5045

51-
export const substrateNormalizedWithMirrorIfEthereum = (address: string): string => {
52-
const addressObject = addressToCrossAccountId(address)
46+
export const substrateOrMirrorIfEthereum = (address: string | object, normalize: boolean = false): string => {
47+
const addressObject = guessAddressAndExtractCrossAccountIdUnsafe(address, normalize)
5348
return addressObject.Substrate
54-
? normalizeSubstrateAddress(addressObject.Substrate)
49+
? addressObject.Substrate
5550
: mirror.ethereumToSubstrate(addressObject.Ethereum as string)
5651
}

src/Address/ethereum.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const normalizeEthereumAddress = (address: string) => {
2424
}
2525

2626
type EthAddressObj = { Ethereum: string }
27-
export const compareEthereumAddresses = (address1: string | EthAddressObj | object, address2: string | EthAddressObj | object): boolean => {
27+
export const compareEthereumAddresses = (address1: string | object, address2: string | object): boolean => {
2828
const addr1 = typeof address1 === 'string'
2929
? address1
3030
: ((address1 as EthAddressObj).Ethereum || (address1 as any).ethereum) as string | undefined

src/Address/index.ts

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,24 @@ import {
2020
SubAddressObj, SubAddressObjUncapitalized,
2121
} from '../types'
2222
import {
23-
substrateNormalizedWithMirrorIfEthereum,
24-
addressToCrossAccountId, addressToCrossAccountIdNormalized,
25-
guessAddressAndExtractItNormalized,
26-
guessAddressAndExtractItNormalizedSafe
23+
guessAddressAndExtractCrossAccountIdSafe,
24+
guessAddressAndExtractCrossAccountIdUnsafe,
25+
substrateOrMirrorIfEthereum
2726
} from './crossAccountId'
2827

2928
import * as algorithms from './imports'
3029
import * as constants from './constants'
30+
import {add} from "@noble/hashes/_u64";
3131

3232
export {constants, algorithms}
3333

3434
const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
3535

3636
export type DecodeSubstrateAddressResult = {
37-
u8a: Uint8Array,
38-
hex: string,
37+
u8a: Uint8Array
38+
hex: string
3939
bigint: bigint
40+
ss58Prefix: number
4041
}
4142

4243
export const validate = {
@@ -131,20 +132,62 @@ export const nesting = {
131132
idsToAddress: collectionIdAndTokenIdToNestingAddress,
132133
addressToIds: nestingAddressToCollectionIdAndTokenId,
133134
}
134-
export const to = {
135-
crossAccountId: addressToCrossAccountId,
136-
crossAccountIdNormalized: addressToCrossAccountIdNormalized,
137-
substrateNormalizedOrMirrorIfEthereum: substrateNormalizedWithMirrorIfEthereum,
138-
}
139135

140136
export const extract = {
141-
normalizedAddressFromObject: guessAddressAndExtractItNormalized,
142-
normalizedAddressFromObjectSafe: guessAddressAndExtractItNormalizedSafe,
143-
crossAccountIdFromObject: (obj: any): CrossAccountId => {
144-
return addressToCrossAccountId(guessAddressAndExtractItNormalized(obj))
137+
address: (addressOrCrossAccountId: string | object): string => {
138+
const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId)
139+
return (crossAccountId.Substrate || crossAccountId.Ethereum) as string
140+
},
141+
addressSafe: (addressOrCrossAccountId: string | object): string | null => {
142+
const crossAccountId = guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId)
143+
return crossAccountId ? (crossAccountId.Substrate || crossAccountId.Ethereum) as string : null
144+
},
145+
146+
addressNormalized: (addressOrCrossAccountId: string | object): string => {
147+
const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true)
148+
return (crossAccountId.Substrate || crossAccountId.Ethereum) as string
149+
},
150+
addressNormalizedSafe: (addressOrCrossAccountId: string | object): string | null => {
151+
const crossAccountId = guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId, true)
152+
return crossAccountId ? (crossAccountId.Substrate || crossAccountId.Ethereum) as string : null
153+
},
154+
155+
156+
crossAccountId: (addressOrCrossAccountId: string | object): CrossAccountId => {
157+
return guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId)
158+
},
159+
crossAccountIdSafe: (addressOrCrossAccountId: string | object): CrossAccountId | null => {
160+
return guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId)
161+
},
162+
163+
crossAccountIdNormalized: (addressOrCrossAccountId: string | object): CrossAccountId => {
164+
return guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true)
165+
},
166+
crossAccountIdNormalizedSafe: (addressOrCrossAccountId: string | object): CrossAccountId | null => {
167+
return guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId, true)
145168
},
146-
crossAccountIdFromObjectNormalized: (obj: any): CrossAccountId => {
147-
return addressToCrossAccountId(guessAddressAndExtractItNormalized(obj))
169+
170+
171+
substrateOrMirrorIfEthereum: (addressOrCrossAccountId: string | object): string => {
172+
return substrateOrMirrorIfEthereum(addressOrCrossAccountId)
173+
},
174+
substrateOrMirrorIfEthereumSafe: (addressOrCrossAccountId: string | object): string | null => {
175+
try {
176+
return substrateOrMirrorIfEthereum(addressOrCrossAccountId)
177+
} catch {
178+
return null
179+
}
180+
},
181+
182+
substrateOrMirrorIfEthereumNormalized: (addressOrCrossAccountId: string | object): string => {
183+
return substrateOrMirrorIfEthereum(addressOrCrossAccountId, true)
184+
},
185+
substrateOrMirrorIfEthereumNormalizedSafe: (addressOrCrossAccountId: string | object): string | null => {
186+
try {
187+
return substrateOrMirrorIfEthereum(addressOrCrossAccountId, true)
188+
} catch {
189+
return null
190+
}
148191
},
149192
}
150193

@@ -176,7 +219,6 @@ export const Address = {
176219
validate,
177220
collection,
178221
nesting,
179-
to,
180222
extract,
181223
mirror,
182224
normalize,

src/Address/substrate.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const sshash = (data: Uint8Array): Uint8Array => {
3333
return blake2AsU8a(u8aConcat([SS58_PREFIX, data]), 64);
3434
}
3535

36-
const checkAddressChecksum = (decoded: Uint8Array): [boolean, number, number, number] => {
36+
const checkAddressChecksum = (decoded: Uint8Array, ignoreChecksum: boolean = false): [boolean, number, number, number] => {
3737
const ss58Length = (decoded[0] & 0b0100_0000) ? 2 : 1;
3838
const ss58Decoded = ss58Length === 1
3939
? decoded[0]
@@ -43,13 +43,17 @@ const checkAddressChecksum = (decoded: Uint8Array): [boolean, number, number, nu
4343
const isPublicKey = [34 + ss58Length, 35 + ss58Length].includes(decoded.length);
4444
const length = decoded.length - (isPublicKey ? 2 : 1);
4545

46-
// calculate the hash and do the checksum byte checks
47-
const hash = sshash(decoded.subarray(0, length));
48-
const isValid = (decoded[0] & 0x80) === 0 && ![46, 47].includes(decoded[0]) && (
49-
isPublicKey
50-
? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1]
51-
: decoded[decoded.length - 1] === hash[0]
52-
);
46+
let isValid = false
47+
48+
if (!ignoreChecksum) {
49+
// calculate the hash and do the checksum byte checks
50+
const hash = sshash(decoded.subarray(0, length));
51+
isValid = (decoded[0] & 0x80) === 0 && ![46, 47].includes(decoded[0]) && (
52+
isPublicKey
53+
? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1]
54+
: decoded[decoded.length - 1] === hash[0]
55+
)
56+
}
5357

5458
return [isValid, length, ss58Length, ss58Decoded];
5559
}
@@ -105,7 +109,7 @@ export function decodeSubstrateAddress(address: string, ignoreChecksum?: boolean
105109
throw realError
106110
}
107111

108-
const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded)
112+
const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded, ignoreChecksum)
109113

110114
if (!ignoreChecksum && !isValid) {
111115
realError = new Error(`Invalid decoded address checksum`)
@@ -122,7 +126,8 @@ export function decodeSubstrateAddress(address: string, ignoreChecksum?: boolean
122126
return {
123127
u8a: publicKey,
124128
hex,
125-
bigint: BigInt(hex)
129+
bigint: BigInt(hex),
130+
ss58Prefix: ss58Decoded,
126131
}
127132
} catch (error) {
128133
throw realError
@@ -132,7 +137,7 @@ export function decodeSubstrateAddress(address: string, ignoreChecksum?: boolean
132137
}
133138

134139
type SubAddressObj = { Substrate: string }
135-
export const compareSubstrateAddresses = (address1: string | SubAddressObj | object, address2: string | SubAddressObj | object): boolean => {
140+
export const compareSubstrateAddresses = (address1: string | object, address2: string | object): boolean => {
136141
const addr1 = typeof address1 === 'string'
137142
? address1
138143
: ((address1 as SubAddressObj).Substrate || (address1 as any).substrate) as string | undefined

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ export {
77
StringUtils,
88
constants,
99
}
10+
11+
export * from './types'

0 commit comments

Comments
 (0)