store.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import {
  2. AptosChain,
  3. Chain,
  4. CosmWasmChain,
  5. StarknetChain,
  6. EvmChain,
  7. FuelChain,
  8. GlobalChain,
  9. SuiChain,
  10. TonChain,
  11. NearChain,
  12. IotaChain,
  13. } from "./chains";
  14. import {
  15. AptosPriceFeedContract,
  16. AptosWormholeContract,
  17. CosmWasmPriceFeedContract,
  18. CosmWasmWormholeContract,
  19. EvmEntropyContract,
  20. EvmPriceFeedContract,
  21. EvmWormholeContract,
  22. SuiPriceFeedContract,
  23. SuiWormholeContract,
  24. FuelWormholeContract,
  25. WormholeContract,
  26. FuelPriceFeedContract,
  27. EvmExpressRelayContract,
  28. TonPriceFeedContract,
  29. TonWormholeContract,
  30. IotaWormholeContract,
  31. IotaPriceFeedContract,
  32. EvmPulseContract,
  33. } from "./contracts";
  34. import { Token } from "./token";
  35. import { PriceFeedContract, Storable } from "./base";
  36. import { parse, stringify } from "yaml";
  37. import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
  38. import { Vault } from "./governance";
  39. import {
  40. StarknetPriceFeedContract,
  41. StarknetWormholeContract,
  42. } from "./contracts/starknet";
  43. import { NearPriceFeedContract, NearWormholeContract } from "./contracts/near";
  44. export class Store {
  45. public chains: Record<string, Chain> = { global: new GlobalChain() };
  46. public contracts: Record<string, PriceFeedContract> = {};
  47. public entropy_contracts: Record<string, EvmEntropyContract> = {};
  48. public pulse_contracts: Record<string, EvmPulseContract> = {};
  49. public wormhole_contracts: Record<string, WormholeContract> = {};
  50. public express_relay_contracts: Record<string, EvmExpressRelayContract> = {};
  51. public tokens: Record<string, Token> = {};
  52. public vaults: Record<string, Vault> = {};
  53. constructor(public path: string) {
  54. this.loadAllChains();
  55. this.loadAllContracts();
  56. this.loadAllTokens();
  57. this.loadAllVaults();
  58. }
  59. static serialize(obj: Storable) {
  60. return stringify([obj.toJson()]);
  61. }
  62. getYamlFiles(path: string) {
  63. const walk = function (dir: string) {
  64. let results: string[] = [];
  65. const list = readdirSync(dir);
  66. list.forEach(function (file) {
  67. file = dir + "/" + file;
  68. const stat = statSync(file);
  69. if (stat && stat.isDirectory()) {
  70. // Recurse into a subdirectory
  71. results = results.concat(walk(file));
  72. } else {
  73. // Is a file
  74. results.push(file);
  75. }
  76. });
  77. return results;
  78. };
  79. return walk(path).filter((file) => file.endsWith(".yaml"));
  80. }
  81. loadAllChains() {
  82. const allChainClasses = {
  83. [CosmWasmChain.type]: CosmWasmChain,
  84. [SuiChain.type]: SuiChain,
  85. [EvmChain.type]: EvmChain,
  86. [AptosChain.type]: AptosChain,
  87. [FuelChain.type]: FuelChain,
  88. [StarknetChain.type]: StarknetChain,
  89. [TonChain.type]: TonChain,
  90. [NearChain.type]: NearChain,
  91. [SuiChain.type]: SuiChain,
  92. [IotaChain.type]: IotaChain,
  93. };
  94. this.getYamlFiles(`${this.path}/chains/`).forEach((yamlFile) => {
  95. const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
  96. for (const parsed of parsedArray) {
  97. if (allChainClasses[parsed.type] === undefined) {
  98. throw new Error(
  99. `No chain class found for chain type: ${parsed.type}`
  100. );
  101. }
  102. const chain = allChainClasses[parsed.type].fromJson(parsed);
  103. if (this.chains[chain.getId()])
  104. throw new Error(`Multiple chains with id ${chain.getId()} found`);
  105. this.chains[chain.getId()] = chain;
  106. }
  107. });
  108. }
  109. saveAllContracts() {
  110. const contractsByType: Record<string, Storable[]> = {};
  111. const contracts: Storable[] = Object.values(this.contracts);
  112. contracts.push(...Object.values(this.entropy_contracts));
  113. contracts.push(...Object.values(this.wormhole_contracts));
  114. for (const contract of contracts) {
  115. if (!contractsByType[contract.getType()]) {
  116. contractsByType[contract.getType()] = [];
  117. }
  118. contractsByType[contract.getType()].push(contract);
  119. }
  120. for (const [type, contracts] of Object.entries(contractsByType)) {
  121. writeFileSync(
  122. `${this.path}/contracts/${type}s.yaml`,
  123. stringify(contracts.map((c) => c.toJson()))
  124. );
  125. }
  126. }
  127. saveAllChains() {
  128. const chainsByType: Record<string, Chain[]> = {};
  129. for (const chain of Object.values(this.chains)) {
  130. if (!chainsByType[chain.getType()]) {
  131. chainsByType[chain.getType()] = [];
  132. }
  133. chainsByType[chain.getType()].push(chain);
  134. }
  135. for (const [type, chains] of Object.entries(chainsByType)) {
  136. writeFileSync(
  137. `${this.path}/chains/${type}s.yaml`,
  138. stringify(chains.map((c) => c.toJson()))
  139. );
  140. }
  141. }
  142. loadAllContracts() {
  143. const allContractClasses = {
  144. [CosmWasmPriceFeedContract.type]: CosmWasmPriceFeedContract,
  145. [CosmWasmWormholeContract.type]: CosmWasmWormholeContract,
  146. [SuiPriceFeedContract.type]: SuiPriceFeedContract,
  147. [SuiWormholeContract.type]: SuiWormholeContract,
  148. [EvmPriceFeedContract.type]: EvmPriceFeedContract,
  149. [AptosPriceFeedContract.type]: AptosPriceFeedContract,
  150. [AptosWormholeContract.type]: AptosWormholeContract,
  151. [EvmEntropyContract.type]: EvmEntropyContract,
  152. [EvmExpressRelayContract.type]: EvmExpressRelayContract,
  153. [EvmWormholeContract.type]: EvmWormholeContract,
  154. [FuelPriceFeedContract.type]: FuelPriceFeedContract,
  155. [FuelWormholeContract.type]: FuelWormholeContract,
  156. [StarknetPriceFeedContract.type]: StarknetPriceFeedContract,
  157. [StarknetWormholeContract.type]: StarknetWormholeContract,
  158. [TonPriceFeedContract.type]: TonPriceFeedContract,
  159. [TonWormholeContract.type]: TonWormholeContract,
  160. [NearPriceFeedContract.type]: NearPriceFeedContract,
  161. [NearWormholeContract.type]: NearWormholeContract,
  162. [IotaPriceFeedContract.type]: IotaPriceFeedContract,
  163. [IotaWormholeContract.type]: IotaWormholeContract,
  164. };
  165. this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
  166. const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
  167. for (const parsed of parsedArray) {
  168. if (allContractClasses[parsed.type] === undefined) return;
  169. if (!this.chains[parsed.chain])
  170. throw new Error(`Chain ${parsed.chain} not found`);
  171. const chain = this.chains[parsed.chain];
  172. const chainContract = allContractClasses[parsed.type].fromJson(
  173. chain,
  174. parsed
  175. );
  176. if (
  177. this.contracts[chainContract.getId()] ||
  178. this.entropy_contracts[chainContract.getId()] ||
  179. this.wormhole_contracts[chainContract.getId()]
  180. )
  181. throw new Error(
  182. `Multiple contracts with id ${chainContract.getId()} found`
  183. );
  184. if (chainContract instanceof EvmEntropyContract) {
  185. this.entropy_contracts[chainContract.getId()] = chainContract;
  186. } else if (chainContract instanceof EvmExpressRelayContract) {
  187. this.express_relay_contracts[chainContract.getId()] = chainContract;
  188. } else if (chainContract instanceof WormholeContract) {
  189. this.wormhole_contracts[chainContract.getId()] = chainContract;
  190. } else {
  191. this.contracts[chainContract.getId()] = chainContract;
  192. }
  193. }
  194. });
  195. }
  196. loadAllTokens() {
  197. this.getYamlFiles(`${this.path}/tokens/`).forEach((yamlFile) => {
  198. const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
  199. for (const parsed of parsedArray) {
  200. if (parsed.type !== Token.type) return;
  201. const token = Token.fromJson(parsed);
  202. if (this.tokens[token.getId()])
  203. throw new Error(`Multiple tokens with id ${token.getId()} found`);
  204. this.tokens[token.getId()] = token;
  205. }
  206. });
  207. }
  208. loadAllVaults() {
  209. this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => {
  210. const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
  211. for (const parsed of parsedArray) {
  212. if (parsed.type !== Vault.type) return;
  213. const vault = Vault.fromJson(parsed);
  214. if (this.vaults[vault.getId()])
  215. throw new Error(`Multiple vaults with id ${vault.getId()} found`);
  216. this.vaults[vault.getId()] = vault;
  217. }
  218. });
  219. }
  220. /**
  221. * Returns the chain with the given ID, or throws an error if it doesn't exist or is not of the specified type.
  222. * @param chainId The unique identifier of the chain to retrieve
  223. * @param ChainClass Optional class to validate the chain type.
  224. * @returns The chain instance of type T
  225. * @throws Error if chain doesn't exist or is not of the specified type
  226. * @template T Type of chain to return, extends base Chain class
  227. */
  228. getChainOrThrow<T extends Chain>(
  229. chainId: string,
  230. ChainClass?: { new (...args: any[]): T; type: string } // eslint-disable-line @typescript-eslint/no-explicit-any
  231. ): T {
  232. const chain = this.chains[chainId];
  233. if (!chain) {
  234. throw new Error(`Chain with ID '${chainId}' does not exist.`);
  235. }
  236. if (ChainClass && !(chain instanceof ChainClass)) {
  237. throw new Error(
  238. `Chain with ID '${chainId}' is not of type ${ChainClass.type}.`
  239. );
  240. }
  241. return chain as T;
  242. }
  243. }
  244. /**
  245. * DefaultStore loads all the contracts and chains from the store directory and provides a single point of access to them.
  246. */
  247. export const DefaultStore = new Store(`${__dirname}/../store`);