@wormhole-foundation/sdk-base 0.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/finality.ts +8 -0
- package/__tests__/layout.ts +173 -0
- package/package.json +15 -0
- package/src/constants/chains.ts +104 -0
- package/src/constants/circle.ts +119 -0
- package/src/constants/contracts/circle.ts +99 -0
- package/src/constants/contracts/core.ts +125 -0
- package/src/constants/contracts/index.ts +15 -0
- package/src/constants/contracts/nftBridge.ts +68 -0
- package/src/constants/contracts/relayer.ts +40 -0
- package/src/constants/contracts/tokenBridge.ts +117 -0
- package/src/constants/decimals.ts +18 -0
- package/src/constants/explorer.ts +326 -0
- package/src/constants/finality.ts +46 -0
- package/src/constants/index.ts +12 -0
- package/src/constants/networks.ts +9 -0
- package/src/constants/platforms.ts +128 -0
- package/src/constants/protocols.ts +36 -0
- package/src/constants/rpc.ts +48 -0
- package/src/index.ts +2 -0
- package/src/utils/array.ts +102 -0
- package/src/utils/hexstring.ts +62 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/layout/deserialize.ts +177 -0
- package/src/utils/layout/discriminate.ts +464 -0
- package/src/utils/layout/fixedDynamic.ts +122 -0
- package/src/utils/layout/index.ts +26 -0
- package/src/utils/layout/layout.ts +140 -0
- package/src/utils/layout/serialize.ts +191 -0
- package/src/utils/layout/utils.ts +23 -0
- package/src/utils/mapping.ts +426 -0
- package/src/utils/metaprogramming.ts +60 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.esm.json +8 -0
- package/typedoc.json +4 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ChainName } from "./chains";
|
|
2
|
+
import { Network } from "./networks";
|
|
3
|
+
import { RoArray, ToMapping, column, constMap } from "../utils";
|
|
4
|
+
|
|
5
|
+
const platformAndChainsEntries = [
|
|
6
|
+
[
|
|
7
|
+
"Evm",
|
|
8
|
+
[
|
|
9
|
+
"Ethereum",
|
|
10
|
+
"Bsc",
|
|
11
|
+
"Polygon",
|
|
12
|
+
"Avalanche",
|
|
13
|
+
"Oasis",
|
|
14
|
+
"Aurora",
|
|
15
|
+
"Fantom",
|
|
16
|
+
"Karura",
|
|
17
|
+
"Acala",
|
|
18
|
+
"Klaytn",
|
|
19
|
+
"Celo",
|
|
20
|
+
"Moonbeam",
|
|
21
|
+
"Neon",
|
|
22
|
+
"Arbitrum",
|
|
23
|
+
"Optimism",
|
|
24
|
+
"Gnosis",
|
|
25
|
+
"Base",
|
|
26
|
+
"Sepolia",
|
|
27
|
+
],
|
|
28
|
+
],
|
|
29
|
+
["Solana", ["Solana", "Pythnet"]],
|
|
30
|
+
["Cosmwasm", ["Terra", "Terra2", "Injective", "Xpla", "Sei"]],
|
|
31
|
+
["Btc", ["Btc"]],
|
|
32
|
+
["Algorand", ["Algorand"]],
|
|
33
|
+
["Sui", ["Sui"]],
|
|
34
|
+
["Aptos", ["Aptos"]],
|
|
35
|
+
["Osmosis", ["Osmosis"]],
|
|
36
|
+
["Wormchain", ["Wormchain"]],
|
|
37
|
+
["Near", ["Near"]],
|
|
38
|
+
] as const satisfies RoArray<readonly [string, RoArray<ChainName>]>;
|
|
39
|
+
|
|
40
|
+
export const platforms = column(platformAndChainsEntries, 0);
|
|
41
|
+
export type PlatformName = (typeof platforms)[number];
|
|
42
|
+
|
|
43
|
+
export const platformToChains = constMap(platformAndChainsEntries);
|
|
44
|
+
export const chainToPlatform = constMap(platformAndChainsEntries, [1, 0]);
|
|
45
|
+
|
|
46
|
+
export const isPlatform = (platform: string): platform is PlatformName =>
|
|
47
|
+
platformToChains.has(platform);
|
|
48
|
+
|
|
49
|
+
export type PlatformToChains<P extends PlatformName> = ReturnType<
|
|
50
|
+
typeof platformToChains<P>
|
|
51
|
+
>[number];
|
|
52
|
+
export type ChainToPlatform<C extends ChainName> = ReturnType<
|
|
53
|
+
typeof chainToPlatform<C>
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
const networkChainEvmCIdEntries = [
|
|
57
|
+
[
|
|
58
|
+
"Mainnet",
|
|
59
|
+
[
|
|
60
|
+
["Ethereum", 1n],
|
|
61
|
+
// TODO: forced to add this to match other list
|
|
62
|
+
["Sepolia", 0n],
|
|
63
|
+
["Bsc", 56n],
|
|
64
|
+
["Polygon", 137n],
|
|
65
|
+
["Avalanche", 43114n],
|
|
66
|
+
["Oasis", 42262n],
|
|
67
|
+
["Aurora", 1313161554n],
|
|
68
|
+
["Fantom", 250n],
|
|
69
|
+
["Karura", 686n],
|
|
70
|
+
["Acala", 787n],
|
|
71
|
+
["Klaytn", 8217n],
|
|
72
|
+
["Celo", 42220n],
|
|
73
|
+
["Moonbeam", 1284n],
|
|
74
|
+
["Neon", 245022934n],
|
|
75
|
+
["Arbitrum", 42161n],
|
|
76
|
+
["Optimism", 10n],
|
|
77
|
+
["Gnosis", 100n],
|
|
78
|
+
["Base", 8453n],
|
|
79
|
+
],
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"Testnet",
|
|
83
|
+
[
|
|
84
|
+
["Ethereum", 5n], //goerli
|
|
85
|
+
["Sepolia", 11155111n], //actually just another ethereum testnet...
|
|
86
|
+
["Bsc", 97n],
|
|
87
|
+
["Polygon", 80001n], //mumbai
|
|
88
|
+
["Avalanche", 43113n], //fuji
|
|
89
|
+
["Oasis", 42261n],
|
|
90
|
+
["Aurora", 1313161555n],
|
|
91
|
+
["Fantom", 4002n],
|
|
92
|
+
["Karura", 596n],
|
|
93
|
+
["Acala", 597n],
|
|
94
|
+
["Klaytn", 1001n], //baobab
|
|
95
|
+
["Celo", 44787n], //alfajores
|
|
96
|
+
["Moonbeam", 1287n], //moonbase alpha
|
|
97
|
+
["Neon", 245022940n],
|
|
98
|
+
["Arbitrum", 421613n], //arbitrum goerli
|
|
99
|
+
["Optimism", 420n],
|
|
100
|
+
["Gnosis", 77n],
|
|
101
|
+
["Base", 84531n],
|
|
102
|
+
],
|
|
103
|
+
],
|
|
104
|
+
] as const satisfies RoArray<
|
|
105
|
+
readonly [Network, RoArray<readonly [PlatformToChains<"Evm">, bigint]>]
|
|
106
|
+
>;
|
|
107
|
+
|
|
108
|
+
export const evmChainIdToNetworkChainPair = constMap(
|
|
109
|
+
networkChainEvmCIdEntries,
|
|
110
|
+
[2, [0, 1]]
|
|
111
|
+
);
|
|
112
|
+
export const evmNetworkChainToEvmChainId = constMap(networkChainEvmCIdEntries);
|
|
113
|
+
|
|
114
|
+
const networkChainSolanaGenesisHashes = [
|
|
115
|
+
["Mainnet", [["Solana", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d"]]],
|
|
116
|
+
["Testnet", [["Solana", "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"]]], // Note: this is referred to as `devnet` in sol
|
|
117
|
+
] as const satisfies RoArray<
|
|
118
|
+
readonly [Network, RoArray<readonly [ChainName, string]>]
|
|
119
|
+
>;
|
|
120
|
+
|
|
121
|
+
export const solGenesisHashToNetworkChainPair = constMap(
|
|
122
|
+
networkChainSolanaGenesisHashes,
|
|
123
|
+
[2, [0, 1]]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
export const solNetworkChainToGenesisHash = constMap(
|
|
127
|
+
networkChainSolanaGenesisHashes
|
|
128
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* TODO:
|
|
2
|
+
* governance actions have a module parameter:
|
|
3
|
+
* - "Core" - https://github.com/wormhole-foundation/wormhole/blob/9e61d151c61bedb18ab1d4ca6ffb1c6c91b108f0/ethereum/contracts/Governance.sol#L21
|
|
4
|
+
* - "TokenBridge" - https://github.com/wormhole-foundation/wormhole/blob/9e61d151c61bedb18ab1d4ca6ffb1c6c91b108f0/ethereum/contracts/bridge/BridgeGovernance.sol#L24
|
|
5
|
+
* - "NFTBridge" - https://github.com/wormhole-foundation/wormhole/blob/9e61d151c61bedb18ab1d4ca6ffb1c6c91b108f0/ethereum/contracts/nft/NFTBridgeGovernance.sol#L23
|
|
6
|
+
* - "WormholeRelayer" - https://github.com/wormhole-foundation/wormhole/blob/9e61d151c61bedb18ab1d4ca6ffb1c6c91b108f0/ethereum/contracts/relayer/wormholeRelayer/WormholeRelayerGovernance.sol#L43
|
|
7
|
+
* while the core contract is actually called "Wormhole" in most platforms
|
|
8
|
+
* - though in solana it's called bridge - https://github.com/wormhole-foundation/wormhole/tree/main/solana/bridge
|
|
9
|
+
* - and in algorand it seems to be called wormhole_core - https://github.com/wormhole-foundation/wormhole/blob/main/algorand/wormhole_core.py
|
|
10
|
+
* - and in EVM it resides directly in the contracts directory
|
|
11
|
+
* the naming of the token bridge and the nft bridge seem to be a lot more consistent, though:
|
|
12
|
+
* - for EVM the TokenBridge and NFTbridge reside in the "bridge" and "nft" directory respectively: https://github.com/wormhole-foundation/wormhole/tree/main/ethereum/contracts)
|
|
13
|
+
* the WormholeRelayer resides in relayer/wormholeRelayer (only built for EVM so far)
|
|
14
|
+
*
|
|
15
|
+
* Within the solana directory, only the token bridge and the nft bridge are considered modules
|
|
16
|
+
* (i.e. are in the modules directory: https://github.com/wormhole-foundation/wormhole/tree/main/solana/modules).
|
|
17
|
+
* While in the JS SDK, the core bridge functionality is in the "bridge" directory
|
|
18
|
+
* (notice the clash with EVM where bridge refers to the token bridge...).
|
|
19
|
+
*
|
|
20
|
+
* With all of this in mind: What should we name modules here?
|
|
21
|
+
* My preferred choice would be ["CoreBridge", "TokenBridge", "NftBridge", "Relayer"]
|
|
22
|
+
* but ["Core", "TokenBridge", "NFTBridge", "WormholeRelayer"] seems to be more consistent given
|
|
23
|
+
* current naming "conventions"
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const protocols = [
|
|
27
|
+
"CoreBridge",
|
|
28
|
+
"TokenBridge",
|
|
29
|
+
"NftBridge",
|
|
30
|
+
"Relayer",
|
|
31
|
+
"CCTP",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export type ProtocolName = (typeof protocols)[number];
|
|
35
|
+
export const isProtocolName = (protocol: string): protocol is ProtocolName =>
|
|
36
|
+
protocols.includes(protocol as ProtocolName);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { constMap, RoArray } from "../utils";
|
|
2
|
+
import { Network } from "./networks";
|
|
3
|
+
import { ChainName } from "./chains";
|
|
4
|
+
|
|
5
|
+
const rpcConfig = [
|
|
6
|
+
[
|
|
7
|
+
"Mainnet",
|
|
8
|
+
[
|
|
9
|
+
["Ethereum", "https://rpc.ankr.com/eth"],
|
|
10
|
+
["Solana", "https://api.mainnet-beta.solana.com"],
|
|
11
|
+
["Polygon", "https://rpc.ankr.com/polygon"],
|
|
12
|
+
["Bsc", "https://bscrpc.com"],
|
|
13
|
+
["Avalanche", "https://rpc.ankr.com/avalanche"],
|
|
14
|
+
["Fantom", "https://rpc.ankr.com/fantom"],
|
|
15
|
+
["Celo", "https://rpc.ankr.com/celo"],
|
|
16
|
+
["Moonbeam", "https://rpc.ankr.com/moonbeam"],
|
|
17
|
+
["Sui", "https://rpc.mainnet.sui.io"],
|
|
18
|
+
["Aptos", "https://fullnode.mainnet.aptoslabs.com/v1"],
|
|
19
|
+
["Arbitrum", "https://arb1.arbitrum.io/rpc"],
|
|
20
|
+
["Optimism", "https://mainnet.optimism.io"],
|
|
21
|
+
["Sei", ""], // TODO
|
|
22
|
+
],
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"Testnet",
|
|
26
|
+
[
|
|
27
|
+
["Ethereum", "https://rpc.ankr.com/eth_goerli"],
|
|
28
|
+
["Polygon", "https://polygon-mumbai.blockpi.network/v1/rpc/public"],
|
|
29
|
+
["Bsc", "https://data-seed-prebsc-1-s3.binance.org:8545"],
|
|
30
|
+
["Avalanche", "https://api.avax-test.network/ext/bc/C/rpc"],
|
|
31
|
+
["Fantom", "https://rpc.ankr.com/fantom_testnet"],
|
|
32
|
+
["Celo", "https://alfajores-forno.celo-testnet.org"],
|
|
33
|
+
["Solana", "https://api.devnet.solana.com"],
|
|
34
|
+
["Moonbeam", "https://rpc.api.moonbase.moonbeam.network"],
|
|
35
|
+
["Sui", "https://fullnode.testnet.sui.io"],
|
|
36
|
+
["Aptos", "https://fullnode.testnet.aptoslabs.com/v1"],
|
|
37
|
+
["Sei", "https://rpc.atlantic-2.seinetwork.io"],
|
|
38
|
+
["Arbitrum", "https://arbitrum-goerli.publicnode.com"],
|
|
39
|
+
["Optimism", "https://optimism-goerli.publicnode.com"],
|
|
40
|
+
],
|
|
41
|
+
],
|
|
42
|
+
] as const satisfies RoArray<
|
|
43
|
+
readonly ["Mainnet" | "Testnet", RoArray<readonly [ChainName, string]>]
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
const rpc = constMap(rpcConfig);
|
|
47
|
+
export const rpcAddress = (network: Network, chain: ChainName) =>
|
|
48
|
+
network === "Devnet" ? undefined : rpc.get(network, chain);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ParseNumber, RoArray, RoArray2D } from "./metaprogramming";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//TODO the intent here is that number represents a number literal, but strictly speaking
|
|
5
|
+
// the type allows for unions of number literals (and an array of such unions)
|
|
6
|
+
export type IndexEs = number | RoArray<number>;
|
|
7
|
+
|
|
8
|
+
export const range = (length: number) => [...Array(length).keys()];
|
|
9
|
+
|
|
10
|
+
export type Entries<T extends RoArray> =
|
|
11
|
+
readonly [...{ [K in keyof T]: K extends `${number}` ? [T[K], ParseNumber<K>] : never }];
|
|
12
|
+
|
|
13
|
+
export type Flatten<T extends RoArray> =
|
|
14
|
+
T extends readonly [infer Head, ...infer Tail]
|
|
15
|
+
? Head extends RoArray
|
|
16
|
+
? [...Head, ...Flatten<Tail>]
|
|
17
|
+
: [Head, ...Flatten<Tail>]
|
|
18
|
+
: [];
|
|
19
|
+
|
|
20
|
+
export type InnerFlatten<T extends RoArray> =
|
|
21
|
+
[...{ [K in keyof T]:
|
|
22
|
+
K extends `${number}`
|
|
23
|
+
? T[K] extends RoArray
|
|
24
|
+
? Flatten<T[K]>
|
|
25
|
+
: T[K]
|
|
26
|
+
: never
|
|
27
|
+
}];
|
|
28
|
+
|
|
29
|
+
export type IsFlat<T extends RoArray> =
|
|
30
|
+
T extends readonly [infer Head, ...infer Tail]
|
|
31
|
+
? Head extends RoArray
|
|
32
|
+
? false
|
|
33
|
+
: IsFlat<Tail>
|
|
34
|
+
: true;
|
|
35
|
+
|
|
36
|
+
export type Unflatten<T extends RoArray> =
|
|
37
|
+
[...{ [K in keyof T]: K extends `${number}` ? readonly [T[K]] : never }];
|
|
38
|
+
|
|
39
|
+
export type AllSameLength<T extends RoArray2D, L extends number> =
|
|
40
|
+
T extends readonly [infer Head extends RoArray, ...infer Tail extends RoArray2D]
|
|
41
|
+
? Head["length"] extends L
|
|
42
|
+
? AllSameLength<Tail, L>
|
|
43
|
+
: false
|
|
44
|
+
: true;
|
|
45
|
+
|
|
46
|
+
export type IsRectangular<T extends RoArray> =
|
|
47
|
+
T extends RoArray2D
|
|
48
|
+
? T extends readonly [infer Head extends RoArray, ...infer Tail extends RoArray2D]
|
|
49
|
+
? AllSameLength<Tail, Head["length"]>
|
|
50
|
+
: true //empty array is rectangular
|
|
51
|
+
: IsFlat<T>; //1d array is rectangular
|
|
52
|
+
|
|
53
|
+
export type Column<A extends RoArray2D, I extends number> =
|
|
54
|
+
[...{ [K in keyof A]: K extends `${number}` ? A[K][I] : never }];
|
|
55
|
+
|
|
56
|
+
export const column = <A extends RoArray2D, I extends number>(tupArr: A, index: I) =>
|
|
57
|
+
tupArr.map((tuple) => tuple[index]) as Column<A, I>;
|
|
58
|
+
|
|
59
|
+
export type Zip<A extends RoArray2D> =
|
|
60
|
+
//TODO remove, find max length, and return undefined for elements in shorter arrays
|
|
61
|
+
IsRectangular<A> extends true
|
|
62
|
+
? A[0] extends infer Head extends RoArray
|
|
63
|
+
? [...{ [K in keyof Head]:
|
|
64
|
+
K extends `${number}`
|
|
65
|
+
? readonly [...{ [K2 in keyof A]: K extends keyof A[K2] ? A[K2][K] : never }]
|
|
66
|
+
: never
|
|
67
|
+
}]
|
|
68
|
+
: []
|
|
69
|
+
: never
|
|
70
|
+
|
|
71
|
+
export const zip = <const Args extends RoArray2D>(arr: Args) =>
|
|
72
|
+
range(arr[0].length).map(col =>
|
|
73
|
+
range(arr.length).map(row => arr[row][col])
|
|
74
|
+
) as unknown as ([Zip<Args>] extends [never] ? RoArray2D : Zip<Args>);
|
|
75
|
+
|
|
76
|
+
export type OnlyIndexes<E extends RoArray, I extends IndexEs> =
|
|
77
|
+
I extends number
|
|
78
|
+
? OnlyIndexes<E, [I]>
|
|
79
|
+
: I extends readonly [infer Head extends number, ...infer Tail extends RoArray<number>]
|
|
80
|
+
? E[Head] extends undefined
|
|
81
|
+
? OnlyIndexes<E, Tail>
|
|
82
|
+
: [E[Head], ...OnlyIndexes<E, Tail>]
|
|
83
|
+
: [];
|
|
84
|
+
|
|
85
|
+
type ExcludeIndexesImpl<T extends RoArray, C extends number> =
|
|
86
|
+
T extends readonly [infer Head, ...infer Tail]
|
|
87
|
+
? Head extends readonly [infer V, infer I extends number]
|
|
88
|
+
? I extends C
|
|
89
|
+
? ExcludeIndexesImpl<Tail, C>
|
|
90
|
+
: [V, ...ExcludeIndexesImpl<Tail, C>]
|
|
91
|
+
: never
|
|
92
|
+
: [];
|
|
93
|
+
|
|
94
|
+
export type ExcludeIndexes<T extends RoArray, C extends IndexEs> =
|
|
95
|
+
ExcludeIndexesImpl<Entries<T>, C extends RoArray<number> ? ParseNumber<keyof IndexEs> : C>;
|
|
96
|
+
|
|
97
|
+
export type Cartesian<L, R> =
|
|
98
|
+
L extends RoArray
|
|
99
|
+
? Flatten<[...{ [K in keyof L]: K extends `${number}` ? Cartesian<L[K], R> : never }]>
|
|
100
|
+
: R extends RoArray
|
|
101
|
+
? [...{ [K in keyof R]: K extends `${number}` ? readonly [L, R[K]] : never }]
|
|
102
|
+
: [L, R];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const stripPrefix = (prefix: string, str: string): string =>
|
|
2
|
+
str.startsWith(prefix) ? str.slice(prefix.length) : str;
|
|
3
|
+
|
|
4
|
+
const tryAsciiHexCharToNumber = (str: string, index: number): number => {
|
|
5
|
+
//we could use parseInt(char, 16) but given that we are doing this for every single char and
|
|
6
|
+
// parseInt has to check for optional "0x" prefix and other conversion stuff, let's just be
|
|
7
|
+
// explicit and not overly wasteful with performance
|
|
8
|
+
const charCode = str.charCodeAt(index);
|
|
9
|
+
switch (charCode) {
|
|
10
|
+
case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57:
|
|
11
|
+
return charCode - 48; //ascii 0-9 is 48-57
|
|
12
|
+
case 65: case 66: case 67: case 68: case 69: case 70:
|
|
13
|
+
return charCode - 65 + 10; //ascii A-F is 65-70
|
|
14
|
+
case 97: case 98: case 99: case 100: case 101: case 102:
|
|
15
|
+
return charCode - 97 + 10; //ascii a-f is 97-102
|
|
16
|
+
default:
|
|
17
|
+
return Number.NaN;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const asciiHexCharToNumber = (str: string, index: number): number => {
|
|
22
|
+
const val = tryAsciiHexCharToNumber(str, index);
|
|
23
|
+
if (Number.isNaN(val))
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Character ${str.charAt(index)} at position ${index} in ${str} is not a hex char`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return val;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const isHexByteString = (str: string, expectedBytes?: number): boolean => {
|
|
32
|
+
let i = ((str.length > 1 && str[1] == "x") ? 2 : 0);
|
|
33
|
+
if ((str.length % 2 !== 0) ||
|
|
34
|
+
(expectedBytes !== undefined && str.length - i !== 2 * expectedBytes))
|
|
35
|
+
return false;
|
|
36
|
+
|
|
37
|
+
for (; i < str.length; ++i)
|
|
38
|
+
if (Number.isNaN(tryAsciiHexCharToNumber(str, i)))
|
|
39
|
+
return false;
|
|
40
|
+
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//TODO naming: arrayify (ethers), toBytes (solana)
|
|
45
|
+
export const hexByteStringToUint8Array = (str: string): Uint8Array => {
|
|
46
|
+
if (str.length % 2 !== 0)
|
|
47
|
+
throw new Error(`hex byte string has odd length: ${str}`);
|
|
48
|
+
|
|
49
|
+
const prefixOffset = str.length > 2 && str[1] === "x" ? 2 : 0;
|
|
50
|
+
const ret = new Uint8Array((str.length - prefixOffset) / 2);
|
|
51
|
+
for (let i = prefixOffset; i < str.length; i += 2)
|
|
52
|
+
ret[(i - prefixOffset) / 2] =
|
|
53
|
+
asciiHexCharToNumber(str, i) * 16 +
|
|
54
|
+
asciiHexCharToNumber(str, i + 1);
|
|
55
|
+
|
|
56
|
+
return ret;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//TODO naming: hexlify (ethers)
|
|
60
|
+
export const uint8ArrayToHexByteString = (arr: Uint8Array, addPrefix = true): string =>
|
|
61
|
+
(addPrefix ? "0x" : "") +
|
|
62
|
+
Array.from(arr).map(byte => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Layout,
|
|
3
|
+
LayoutItem,
|
|
4
|
+
LayoutToType,
|
|
5
|
+
LayoutItemToType,
|
|
6
|
+
FixedPrimitiveBytesLayoutItem,
|
|
7
|
+
FixedValueBytesLayoutItem,
|
|
8
|
+
CustomConversion,
|
|
9
|
+
UintSizeToPrimitive,
|
|
10
|
+
numberMaxSize,
|
|
11
|
+
} from "./layout";
|
|
12
|
+
|
|
13
|
+
import { checkUint8ArrayDeeplyEqual, checkUintEquals } from "./utils";
|
|
14
|
+
|
|
15
|
+
export function deserializeLayout<const L extends Layout>(
|
|
16
|
+
layout: L,
|
|
17
|
+
encoded: Uint8Array,
|
|
18
|
+
offset?: number,
|
|
19
|
+
consumeAll?: true,
|
|
20
|
+
): LayoutToType<L>;
|
|
21
|
+
|
|
22
|
+
export function deserializeLayout<const L extends Layout>(
|
|
23
|
+
layout: L,
|
|
24
|
+
encoded: Uint8Array,
|
|
25
|
+
offset?: number,
|
|
26
|
+
consumeAll?: false,
|
|
27
|
+
): readonly [LayoutToType<L>, number];
|
|
28
|
+
|
|
29
|
+
export function deserializeLayout<const L extends Layout>(
|
|
30
|
+
layout: L,
|
|
31
|
+
encoded: Uint8Array,
|
|
32
|
+
offset = 0,
|
|
33
|
+
consumeAll = true,
|
|
34
|
+
): LayoutToType<L> | readonly [LayoutToType<L>, number] {
|
|
35
|
+
const [decoded, finalOffset] = internalDeserializeLayout(layout, encoded, offset);
|
|
36
|
+
|
|
37
|
+
if (consumeAll && finalOffset !== encoded.length)
|
|
38
|
+
throw new Error(`encoded data is longer than expected: ${encoded.length} > ${finalOffset}`);
|
|
39
|
+
|
|
40
|
+
return consumeAll ? decoded as LayoutToType<L> : [decoded as LayoutToType<L>, finalOffset];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function internalDeserializeLayout(
|
|
44
|
+
layout: Layout,
|
|
45
|
+
encoded: Uint8Array,
|
|
46
|
+
offset: number,
|
|
47
|
+
): [any, number] {
|
|
48
|
+
let decoded = {} as any;
|
|
49
|
+
for (const item of layout)
|
|
50
|
+
[((item as any).omit ? {} : decoded)[item.name], offset] =
|
|
51
|
+
deserializeLayoutItem(item, encoded, offset);
|
|
52
|
+
|
|
53
|
+
return [decoded, offset];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function updateOffset (
|
|
57
|
+
encoded: Uint8Array,
|
|
58
|
+
offset: number,
|
|
59
|
+
size: number
|
|
60
|
+
): number {
|
|
61
|
+
const newOffset = offset + size;
|
|
62
|
+
if (newOffset > encoded.length)
|
|
63
|
+
throw new Error(`encoded data is shorter than expected: ${encoded.length} < ${newOffset}`);
|
|
64
|
+
|
|
65
|
+
return newOffset;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function deserializeUint<S extends number>(
|
|
69
|
+
encoded: Uint8Array,
|
|
70
|
+
offset: number,
|
|
71
|
+
size: S,
|
|
72
|
+
): readonly [UintSizeToPrimitive<S>, number] {
|
|
73
|
+
let value = 0n;
|
|
74
|
+
for (let i = 0; i < size; ++i)
|
|
75
|
+
value += BigInt(encoded[offset + i]) << BigInt((size - 1 - i) * 8);
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
((size > numberMaxSize) ? value : Number(value)) as UintSizeToPrimitive<S>,
|
|
79
|
+
updateOffset(encoded, offset, size)
|
|
80
|
+
] as const;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function deserializeLayoutItem(
|
|
84
|
+
item: LayoutItem,
|
|
85
|
+
encoded: Uint8Array,
|
|
86
|
+
offset: number,
|
|
87
|
+
): readonly [any, number] {
|
|
88
|
+
try {
|
|
89
|
+
switch (item.binary) {
|
|
90
|
+
case "object": {
|
|
91
|
+
return internalDeserializeLayout(item.layout, encoded, offset);
|
|
92
|
+
}
|
|
93
|
+
case "array": {
|
|
94
|
+
let ret = [] as LayoutToType<typeof item.layout>[];
|
|
95
|
+
if (item.lengthSize !== undefined) {
|
|
96
|
+
const [length, newOffset] = deserializeUint(encoded, offset, item.lengthSize);
|
|
97
|
+
offset = newOffset;
|
|
98
|
+
for (let i = 0; i < length; ++i)
|
|
99
|
+
[ret[i], offset] = internalDeserializeLayout(item.layout, encoded, offset);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
while (offset < encoded.length)
|
|
103
|
+
[ret[ret.length], offset] = internalDeserializeLayout(item.layout, encoded, offset);
|
|
104
|
+
}
|
|
105
|
+
return [ret, offset];
|
|
106
|
+
}
|
|
107
|
+
case "bytes": {
|
|
108
|
+
let newOffset;
|
|
109
|
+
let fixedFrom;
|
|
110
|
+
let fixedTo;
|
|
111
|
+
if (item.custom !== undefined) {
|
|
112
|
+
if (item.custom instanceof Uint8Array)
|
|
113
|
+
fixedFrom = item.custom;
|
|
114
|
+
else if (item.custom.from instanceof Uint8Array) {
|
|
115
|
+
fixedFrom = item.custom.from;
|
|
116
|
+
fixedTo = item.custom.to;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (fixedFrom !== undefined)
|
|
121
|
+
newOffset = updateOffset(encoded, offset, fixedFrom.length);
|
|
122
|
+
else {
|
|
123
|
+
item = item as
|
|
124
|
+
Exclude<typeof item, FixedPrimitiveBytesLayoutItem | FixedValueBytesLayoutItem>;
|
|
125
|
+
if ("size" in item && item.size !== undefined)
|
|
126
|
+
newOffset = updateOffset(encoded, offset, item.size);
|
|
127
|
+
else if (item.lengthSize !== undefined) {
|
|
128
|
+
let length;
|
|
129
|
+
[length, offset] = deserializeUint(encoded, offset, item.lengthSize);
|
|
130
|
+
newOffset = updateOffset(encoded, offset, length);
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
newOffset = encoded.length;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const value = encoded.slice(offset, newOffset);
|
|
137
|
+
if (fixedFrom !== undefined) {
|
|
138
|
+
checkUint8ArrayDeeplyEqual(fixedFrom, value);
|
|
139
|
+
return [fixedTo ?? fixedFrom, newOffset];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type narrowedCustom = CustomConversion<Uint8Array, any>;
|
|
143
|
+
return [
|
|
144
|
+
item.custom !== undefined ? (item.custom as narrowedCustom).to(value) : value,
|
|
145
|
+
newOffset
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
case "uint": {
|
|
149
|
+
const [value, newOffset] = deserializeUint(encoded, offset, item.size);
|
|
150
|
+
|
|
151
|
+
if (item.custom !== undefined) {
|
|
152
|
+
if (typeof item.custom === "number" || typeof item.custom === "bigint") {
|
|
153
|
+
checkUintEquals(item.custom, value);
|
|
154
|
+
return [item.custom, newOffset];
|
|
155
|
+
}
|
|
156
|
+
else if (typeof item.custom.from === "number" || typeof item.custom.from === "bigint") {
|
|
157
|
+
checkUintEquals(item.custom.from, value);
|
|
158
|
+
return [item.custom.to, newOffset];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//narrowing to CustomConver<number | bigint, any> is a bit hacky here, since the true type
|
|
163
|
+
// would be CustomConver<number, any> | CustomConver<bigint, any>, but then we'd have to
|
|
164
|
+
// further tease that apart still for no real gain...
|
|
165
|
+
type narrowedCustom = CustomConversion<number | bigint, any>;
|
|
166
|
+
return [
|
|
167
|
+
item.custom !== undefined ? (item.custom as narrowedCustom).to(value) : value,
|
|
168
|
+
newOffset
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
(e as Error).message = `when deserializing item '${item.name}': ${(e as Error).message}`;
|
|
175
|
+
throw e;
|
|
176
|
+
}
|
|
177
|
+
}
|