UNPKG

@parifi/synthetix-sdk-ts

Version:

A Typescript SDK for interactions with the Synthetix protocol

1,302 lines (1,286 loc) 216 kB
// src/utils/common.ts import { formatEther, maxUint128, parseEther } from "viem"; import { mainnet, base, optimism, arbitrum, baseSepolia, arbitrumSepolia } from "viem/chains"; import { randomBytes } from "crypto"; // src/constants/partner.ts var DEFAULT_TRACKING_CODE = "0x5052460000000000000000000000000000000000000000000000000000000000"; var DEFAULT_REFERRER = "0x32A74B2cE1a0783A182f607D29207e571a1cDD4D"; // src/constants/protocol.ts var DEFAULT_DECIMALS = 18; var PUBLIC_PYTH_ENDPOINT = "https://hermes.pyth.network"; var DEFAULT_PYTH_TIMEOUT = 15e3; // src/constants/common.ts import { zeroAddress } from "viem"; var ZERO_ADDRESS = zeroAddress; var SIG_ORACLE_DATA_REQUIRED = "0xcf2cabdf"; var SIG_FEE_REQUIRED = "0x0e7186fb"; var SIG_ERRORS = "0x0b42fd17"; var MAX_ERC7412_RETRIES = 80; var DISABLED_MARKETS = { 84532: [3, 6300], 8453: [6300] }; var publicRpcEndpoints = { 8453: "https://base.llamarpc.com", 84532: "https://sepolia.base.org", 42161: "https://arbitrum.llamarpc.com", 421614: "https://sepolia-rollup.arbitrum.io/rpc" }; var DEFAULT_LOGGER_LEVEL = 4; var CUSTOM_DECIMALS = { [421614 /* ARBITUM_SEPOLIA */]: { 2: 6, USDC: 6 }, [42161 /* ARBITRUM */]: { 2: 6, USDC: 6 }, [84532 /* BASE_SEPOLIA */]: { 1: 6, USDC: 6 }, [8453 /* BASE */]: { 1: 6, USDC: 6 } }; // src/constants/pythPriceIds.ts var SYMBOL_TO_PYTH_FEED = /* @__PURE__ */ new Map([ // Markets ["BTC", "0xc9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33"], ["SOL", "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"], ["WIF", "0x4ca4beeca86f0d164160323817a4e42b10010a724c2217c6ee41b54cd4cc61fc"], ["ETH", "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], ["AAVE", "0x2b9ab1e972a281585084148ba1389800799bd4be63b957507db1349314e47445"], ["ADA", "0x2a01deaec9e51a579277b34b122399984d0bbf57e2458a7e42fecd2829867a0d"], ["ARB", "0x3fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf5"], ["AVAX", "0x93da3352f9f1d105fdfe4971cfa80e9dd777bfc5d0f683ebb6e1294b92137bb7"], ["BCH", "0x3dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3"], ["BNB", "0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f"], ["CRV", "0xa19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8"], ["DOGE", "0xdcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c"], ["DYDX", "0x6489800bb8974169adfe35937bf6736507097d13c190d760c557108c7e93a81b"], ["GMX", "0xb962539d0fcb272a494d65ea56f94851c2bcf8823935da05bd628916e2e9edbf"], ["LINK", "0x8ac0c70fff57e9aefdf5edf44b51d62c2d433653cbb2cf5cc06bb115af04d221"], ["LTC", "0x6e3f3fa8253588df9326580180233eb791e03b443a3ba7a1d892e73874e19a54"], ["MKR", "0x9375299e31c0deb9c6bc378e6329aab44cb48ec655552a70d4b9050346a30378"], ["NEAR", "0xc415de8d2eba7db216527dff4b60e8f3a5311c740dadb233e13e12547e226750"], ["OP", "0x385f64d993f7b77d8182ed5003d97c60aa3361f3cecfe711544d2d59165e9bdf"], ["ORDI", "0x193c739db502aadcef37c2589738b1e37bdb257d58cf1ab3c7ebc8e6df4e3ec0"], ["PEPE", "0xd69731a2e74ac1ce884fc3890f7ee324b6deb66147055249568869ed700882e4"], ["POL", "0xffd11c5a1cfd42f80afb2df4d9f264c15f956d68153335374ec10722edd70472"], ["PYTH", "0x0bbf28e9a841a1cc788f6a361b17ca072d0ea3098a1e5df1c3922d06719579ff"], ["RUNE", "0x5fcf71143bb70d41af4fa9aa1287e2efd3c5911cee59f909f915c9f61baacb1e"], ["SHIB", "0xf0d57deca57b3da2fe63a493f4c25925fdfd8edf834b20f93e1f84dbd1504d4a"], ["STX", "0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17"], ["TIA", "0x09f7c1d7dfbb7df2b8fe3d3d87ee94a2259d212da4f30c1f0540d066dfa44723"], ["UNI", "0x78d185a741d07edb3412b09008b7c5cfb9bbbd7d568bf00ba737b456ba171501"], ["XLM", "0xb7a8eba68a997cd0210c2e1e4ee811ad2d174b3611c22d9ebf16f4cb7e9ba850"], ["XRP", "0xec5d399846a9209f3fe5881d70aae9268c94339ff9817e8d18ff19fa05eea1c8"], ["ATOM", "0xb00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819"], ["BLUR", "0x856aac602516addee497edf6f50d39e8c95ae5fb0da1ed434a8c2ab9c3e877e9"], ["BOME", "0x30e4780570973e438fdb3f1b7ad22618b2fc7333b65c7853a7ca144c39052f7a"], ["BONK", "0x72b021217ca3fe68922a19aaf990109cb9d84e9ad004b4d2025ad6f529314419"], ["COMP", "0x4a8e42861cabc5ecb50996f92e7cfa2bce3fd0a2423b0c44c9b423fb2bd25478"], ["DOT", "0xca3eed9b267293f6595901c734c7525ce8ef49adafe8284606ceb307afa2ca5b"], ["EIGEN", "0xc65db025687356496e8653d0d6608eec64ce2d96e2e28c530e574f0e4f712380"], ["ENA", "0xb7910ba7322db020416fcac28b48c01212fd9cc8fbcbaf7d30477ed8605f6bd4"], ["EOS", "0x06ade621dbc31ed0fc9255caaab984a468abe84164fb2ccc76f02a4636d97e31"], ["ETC", "0x7f5cc8d963fc5b3d2ae41fe5685ada89fd4f14b435f8050f28c7fd409f40c2d8"], ["ETHBTC", "0xc96458d393fe9deb7a7d63a0ac41e2898a67a7750dbd166673279e06c868df0a"], ["ETHFI", "0xb27578a9654246cb0a2950842b92330e9ace141c52b63829cc72d5c45a5a595a"], ["FIL", "0x150ac9b959aee0051e4091f0ef5216d941f590e1c5e7f91cf7635b5c11628c0e"], ["FLOW", "0x2fb245b9a84554a0f15aa123cbb5f64cd263b59e9a87d80148cbffab50c69f30"], ["FTM", "0x5c6c0d2386e3352356c3ab84434fafb5ea067ac2678a38a338c4a69ddc4bdb0c"], ["FXS", "0x735f591e4fed988cd38df74d8fcedecf2fe8d9111664e0fd500db9aa78b316b1"], ["GALA", "0x0781209c28fda797616212b7f94d77af3a01f3e94a5d421760aef020cf2bcb51"], ["GRT", "0x4d1f8dae0d96236fb98e8f47471a366ec3b1732b47041781934ca3a9bb2f35e7"], ["ICP", "0xc9907d786c5821547777780a1e4f89484f3417cb14dd244f2b0a34ea7a554d67"], ["IMX", "0x941320a8989414874de5aa2fc340a75d5ed91fdff1613dd55f83844d52ea63a2"], ["INJ", "0x7a5bc1d2b56ad029048cd63964b3ad2776eadf812edc1a43a31406cb54bff592"], ["IO", "0x82595d1509b770fa52681e260af4dda9752b87316d7c048535d8ead3fa856eb1"], ["JTO", "0xb43660a5f790c69354b0729a5ef9d50d68f1df92107540210b9cccba1f947cc2"], ["JUP", "0x0a0408d619e9380abad35060f9192039ed5042fa6f82301d0e48bb52be830996"], ["LDO", "0xc63e2a7f37a04e5e614c07238bedb25dcc38927fba8fe890597a593c0b2fa4ad"], ["MEME", "0xcd2cee36951a571e035db0dfad138e6ecdb06b517cc3373cd7db5d3609b7927c"], ["MEW", "0x514aed52ca5294177f20187ae883cec4a018619772ddce41efcc36a6448f5d5d"], ["NOT", "0x75ec6f04d4bded6afdc1440689be4402dd1e23d2ff2c21e081871eb2739ceb36"], ["PENDLE", "0x9a4df90b25497f66b1afb012467e316e801ca3d839456db028892fe8c70c8016"], ["PEOPLE", "0xb7fe919d83815ca6074c82a3286b1cd6ffb7d3136e323cd2b1ef706cfc7e5945"], ["POPCAT", "0xb9312a7ee50e189ef045aa3c7842e099b061bd9bdc99ac645956c3b660dc8cce"], ["RENDER", "0x3d4a2bd9535be6ce8059d75eadeba507b043257321aa544717c56fa19b49e35d"], ["SATS", "0x40440d18fb5ad809e2825ce7dfc035cfa57135c13062a04addafe0c7f54425e0"], ["SEI", "0x53614f1cb0c031d4af66c04cb9c756234adad0e1cee85303795091499a4084eb"], ["SNX", "0x39d020f60982ed892abbcd4a06a276a9f9b7bfbce003204c110b6e488f502da3"], ["STRK", "0x6a182399ff70ccf3e06024898942028204125a819e519a335ffa4579e66cd870"], ["SUI", "0x23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744"], ["SUSHI", "0x26e4f737fde0263a9eea10ae63ac36dcedab2aaf629261a994e1eeb6ee0afe53"], ["TAO", "0x410f41de235f2db824e562ea7ab2d3d3d4ff048316c61d629c0b93f58584e1af"], ["TON", "0x8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac3026"], ["TRX", "0x67aed5a24fdad045475e7195c98a98aea119c763f272d4523f5bac93a4f33c2b"], ["W", "0xeff7446475e218517566ea99e72a4abec2e1bd8498b43b7d8331e29dcb059389"], ["WLD", "0xd6835ad1f773de4a378115eb6824bd0c0e42d84d1c84d9750e853fb6b6c7794a"], ["YFI", "0x425f4b198ab2504936886c1e93511bb6720fbcf2045a4f3c0723bb213846022f"], ["ZRO", "0x3bd860bea28bf982fa06bcf358118064bb114086cc03993bd76197eaab0b8018"], ["ALGO", "0xfa17ceaf30d19ba51112fdcc750cc83454776f47fb0112e4af07f15f4bb1ebc0"], ["APT", "0x03ae4db29ed4ae33d323568895aa00337e658e348b37509f5372ae51f0af00d5"], ["ARKM", "0x7677dd124dee46cfcd46ff03cf405fb0ed94b1f49efbea3444aadbda939a7ad3"], ["AXL", "0x60144b1d5c9e9851732ad1d9760e3485ef80be39b984f6bf60f82b28a2b7f126"], ["AXS", "0xb7e3904c08ddd9c0c10c6d207d390fd19e87eb6aab96304f571ed94caebdefa0"], ["BAL", "0x07ad7b4a7662d19a6bc675f6b467172d2f3947fa653ca97555a9b20236406628"], ["GOAT", "0xf7731dc812590214d3eb4343bfb13d1b4cfa9b1d4e020644b5d5d8e07d60c66c"], ["MOODENG", "0xffff73128917a90950cd0473fd2551d7cd274fd5a6cc45641881bbcc6ee73417"], ["SAFE", "0x7b3576858506a94fad3a9cc55e32934f0c3931150fe3a3c7b83558dbae5b8e38"], ["PNUT", "0x116da895807f81f6b5c5f01b109376e7f6834dc8b51365ab7cdfa66634340e54"], ["CAT", "0xdf7b724fae4b9ecfd6081effb1ea3bb3b5c250c3641cf903b4d8fb10f4f452df"], ["DEGEN", "0x9c93e4a22c56885af427ac4277437e756e7ec403fbc892f975d497383bb33560"], ["SLERF", "0x1a483c4a63876d286991ac0d6e090298db42e88c3826b6e0cff89daca498eed5"], ["AERO", "0x9db37f4d5654aad3e37e2e14ffd8d53265fb3026d1d8f91146539eebaa2ef45f"], ["CHILLGUY", "0xd98869edbb4a0d2803dc1054405bceb1ddc546bfc9a3d0e31bb0e0448e6181e1"], ["MORPHO", "0x5b2a4c542d4a74dd11784079ef337c0403685e3114ba0d9909b5c7a7e06fdc42"], ["SOLETH", "0xde87506dabfadbef89af2d5d796ebae80ddaea240fc7667aa808fce3629cd8fb"], // Collaterals ["USDC", "0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a"], ["USDE", "0x6ec879b1e9963de5ee97e9c8710b742d6228252a5e2ca12d4ae81d7fe5ee8c5d"], ["DAI", "0xb0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd"] ]); // src/contracts/addreses/zap.ts import { zeroAddress as zeroAddress2 } from "viem"; var ZAP_BY_CHAIN = { [8453 /* BASE */]: "0x459Bbb4231f0a606DC514BacD5712A5922CBe6c8", [84532 /* BASE_SEPOLIA */]: zeroAddress2, [42161 /* ARBITRUM */]: "0x9ec181B2E69fB36C50031F0c87Bc0749b766A9f4", [421614 /* ARBITUM_SEPOLIA */]: "0xb569ed692206a1d73996088ae646333b1d59d9c5" }; var SYNTHETIX_ZAP = { [84532 /* BASE_SEPOLIA */]: "0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9", [8453 /* BASE */]: "0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9" }; // src/utils/common.ts function getPublicRpcEndpoint(chainId) { return publicRpcEndpoints[chainId]; } function getChain(chainId) { const chains = [mainnet, base, optimism, arbitrum, baseSepolia, arbitrumSepolia]; for (const chain of Object.values(chains)) { if (chain.id === chainId) { return chain; } } throw new Error(`Chain with id ${chainId} not found`); } function convertWeiToEther(amountInWei) { if (amountInWei == void 0) { throw new Error("Invalid amount received during conversion: undefined"); } if (typeof amountInWei == "bigint") { return Number(formatEther(amountInWei)); } else if (typeof amountInWei == "string") { return Number(formatEther(BigInt(amountInWei))); } else { throw new Error("Expected string or bigint for conversion"); } } function convertEtherToWei(amount) { if (amount == void 0) { throw new Error("Invalid amount received during conversion: undefined"); } if (typeof amount == "number") { return parseEther(amount.toString()); } else if (typeof amount == "string") { return parseEther(amount); } else { throw new Error("Expected string or bigint for conversion"); } } function sleep(seconds) { return new Promise((resolve) => setTimeout(resolve, seconds * 1e3)); } function generateRandomAccountId() { const maxUint128Half = maxUint128 / BigInt(2); const buffer = randomBytes(8); const randomAccountId = BigInt("0x" + buffer.toString("hex")); if (randomAccountId > maxUint128Half) { throw new Error("Account ID greater than Maxuint128"); } return randomAccountId; } var batchArray = (arr, batchSize) => { return arr.reduce((acc, _, i) => i % batchSize ? acc : [...acc, arr.slice(i, i + batchSize)], []); }; var fetcher = (url, init = { headers: { "Content-Type": "application/json" } }) => fetch(url, { ...init, body: JSON.stringify(init.body), headers: init.headers, method: init.body && !init.method ? "POST" : init.method }); var odoFetcher = async (url, options) => { return fetcher(`https://api.odos.xyz/sor${url}`, options); }; var assemblePath = async (user, pathId) => { const response = await odoFetcher(`/assemble`, { body: { userAddr: user, pathId }, headers: { "Content-Type": "application/json" } }); if (!response.ok) return ""; const data = await response.json(); return data.transaction.data; }; var getOdosPath = async (quoteParams) => { if (!quoteParams.fromToken || !quoteParams.toToken || !quoteParams.fromAmount) { throw new Error("Missing required parameters for Odos path generation"); } const data = { chainId: quoteParams.fromChain, inputTokens: [{ tokenAddress: quoteParams.fromToken, amount: quoteParams.fromAmount }], outputTokens: [{ tokenAddress: quoteParams.toToken, proportion: 1 }], userAddr: ZAP_BY_CHAIN[quoteParams.fromChain] }; const response = await odoFetcher("/quote/v2", { body: data, headers: { "Content-Type": "application/json" } }); if (!response.ok) { console.error(`Failed to get Odos quote: ${response.status} ${response.statusText}`); return { path: "" }; } const quote = await response.json(); if (!quote?.pathId) { console.error("Invalid response from Odos API: missing pathId"); return { path: "" }; } const quoteId = quote.pathId; const path = await assemblePath(data.userAddr, quoteId); if (!path) { console.error("Failed to assemble path from pathId"); } return { path }; }; // src/utils/index.ts import { decodeAbiParameters, decodeErrorResult, decodeFunctionResult, encodeAbiParameters, encodeFunctionData, parseAbiParameters } from "viem"; // src/contracts/abis/IERC7412.ts var IERC7412Abi = [ { type: "function", name: "fulfillOracleQuery", inputs: [{ name: "signedOffchainData", type: "bytes", internalType: "bytes" }], outputs: [], stateMutability: "payable" }, { type: "function", name: "oracleId", inputs: [], outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }], stateMutability: "view" }, { type: "error", name: "FeeRequired", inputs: [{ name: "feeAmount", type: "uint256", internalType: "uint256" }] }, { type: "error", name: "OracleDataRequired", inputs: [ { name: "oracleContract", type: "address", internalType: "address" }, { name: "oracleQuery", type: "bytes", internalType: "bytes" } ] }, { type: "error", name: "Errors", inputs: [ { type: "bytes[]", name: "revertReasons" } ] } ]; // src/utils/parseError.ts function parseError(error) { try { if (error.cause?.data) { return error.cause?.data; } if (error.cause?.cause?.data) { return error.cause?.cause?.data; } if (error.cause?.cause?.cause?.data) { return error.cause?.cause?.cause?.data; } if (error.cause?.cause?.error?.data) { return error.cause?.cause?.error?.data; } if (error.cause?.cause?.cause?.error?.data) { return error.cause?.cause?.cause?.error?.data; } } catch (err) { console.log("=== exception in erc7412 error parser:", err); } console.log("=== a", JSON.stringify(error, null, 2)); console.error("=== got unknown error in erc7412 parse", error); return "0x"; } // src/contracts/addreses/oracleProxy.ts var ORACLE_PROXY_BY_CHAIN = { [421614 /* ARBITUM_SEPOLIA */]: "0x59d6ec32e05900949d7fff679a4adc7f94f0208c", [42161 /* ARBITRUM */]: "0xe87ceB87b63267ef925E2897B629052eb815bB7d", [8453 /* BASE */]: "0xa03A0Be2f3B85B76765A442683d62E992972d816", [84532 /* BASE_SEPOLIA */]: "0x4Bc47c016EE6Ead253152CCfe6427D0bC06F544c" }; // src/utils/index.ts var Utils = class { constructor(synthetixSdk) { this.sdk = synthetixSdk; } async isOracleCall(data) { return Object.values(ORACLE_PROXY_BY_CHAIN).map((a) => a.toLowerCase()).includes(typeof data === "string" ? data.toLowerCase() : data.to.toLowerCase()); } /** * Returns the Pyth price ids array * @param oracleQueryData * @returns Pyth Price IDs array */ getPythPriceIdsFromOracleQuery(oracleQueryData) { const values = decodeAbiParameters(parseAbiParameters("uint8, uint64, bytes32[]"), oracleQueryData); return values[2]; } /** * Decodes the response from a Smart contract function call * @param abi Contract ABI * @param functionName Function name to decode * @param result Response from function call that needs to be decoded * @returns Decoded result */ decodeResponse(abi, functionName, result) { const decodedResult = decodeFunctionResult({ abi, functionName, data: result }); return decodedResult; } /** * Determines the type of Error emitted by ERC7412 contract and prepares signed update data * @param data Error data emitted from ERC7412 contract * @returns Encoded data for oracle price update transaction */ async fetchOracleUpdateData(data) { const [updateType] = decodeAbiParameters([{ name: "updateType", type: "uint8" }], data); if (updateType === 1) { const [updateType2, stalenessOrTime, priceIds] = decodeAbiParameters( [ { name: "updateType", type: "uint8" }, { name: "stalenessTolerance", type: "uint64" }, { name: "priceIds", type: "bytes32[]" } ], data ); const stalenessTolerance = stalenessOrTime; const updateData = await this.sdk.pyth.pythConnection.getPriceFeedsUpdateData( priceIds ); return encodeAbiParameters( [ { type: "uint8", name: "updateType" }, { type: "uint64", name: "stalenessTolerance" }, { type: "bytes32[]", name: "priceIds" }, { type: "bytes[]", name: "updateData" } ], [updateType2, stalenessTolerance, priceIds, updateData] ); } else if (updateType === 2) { const [updateType2, requestedTime, priceId] = decodeAbiParameters( [ { name: "updateType", type: "uint8" }, { name: "requestedTime", type: "uint64" }, { name: "priceIds", type: "bytes32" } ], data ); this.sdk.logger.info("Update type: ", updateType2); this.sdk.logger.info("priceIds: ", priceId); const [priceFeedUpdateVaa] = await this.sdk.pyth.pythConnection.getVaa( priceId, Number(requestedTime.toString()) ); const priceFeedUpdate = "0x" + Buffer.from(priceFeedUpdateVaa, "base64").toString("hex"); return encodeAbiParameters( [ { type: "uint8", name: "updateType" }, { type: "uint64", name: "timestamp" }, { type: "bytes32[]", name: "priceIds" }, { type: "bytes[]", name: "updateData" } ], [updateType2, requestedTime, [priceId], [priceFeedUpdate]] ); } else { throw new Error(`Error encoding/decoding data`); } } async handleOracleDataRequiredError(parsedError) { const err = decodeErrorResult({ abi: IERC7412Abi, data: parsedError }); const oracleAddress = err.args[0]; const oracleQuery = err.args[1]; const signedRequiredData = await this.fetchOracleUpdateData(oracleQuery); const dataVerificationTx = this.generateDataVerificationTx(oracleAddress, signedRequiredData); return dataVerificationTx; } /** * Handles ERC7412 error by creating a price update tx which is prepended to the existing calls * @param error Error thrown by the call function * @param calls Multicall call data * @returns call array with ERC7412 fulfillOracleQuery transaction */ async handleErc7412Error(parsedError) { let err; try { err = decodeErrorResult({ abi: IERC7412Abi, data: parsedError }); } catch (decodeErr) { this.sdk.logger.error("Decode Error: ", decodeErr); throw new Error("Handle ERC7412 error"); } if (!["OracleDataRequired", "FeeRequired", "Errors"].includes(err?.errorName)) throw new Error("Handle ERC7412 error"); if (err?.errorName === "Errors") { const oracleErrors = err.args[0]; let oracleCalls = []; const batchedOracleErrors = batchArray(oracleErrors, 10); for (const oracleErrors2 of batchedOracleErrors) { const promiseOracleCalls = oracleErrors2.map((oracleError) => { return this.handleErc7412Error(oracleError); }); const resolvedCalls = await Promise.all(promiseOracleCalls); oracleCalls = [...oracleCalls, ...resolvedCalls.flat()]; } return oracleCalls.flat(); } if (err?.errorName === "OracleDataRequired") { return [await this.handleOracleDataRequiredError(parsedError)]; } return []; } /** * Generates Call3Value tx for Price update data of Oracle * @param oracleContract Oracle Contract address * @param signedRequiredData Encoded Price Update data * @returns Transaction Request for Oracle price update */ generateDataVerificationTx(_oracleContract, signedRequiredData) { const priceUpdateCall = { target: ORACLE_PROXY_BY_CHAIN[this.sdk.rpcConfig.chainId], callData: encodeFunctionData({ abi: IERC7412Abi, functionName: "fulfillOracleQuery", args: [signedRequiredData] }), value: 0n, requireSuccess: false }; return priceUpdateCall; } /** * Calls the `functionName` on `contractAddress` target using the Multicall contract. If the call requires * a price update, ERC7412 price update tx is prepended to the tx. * @param contractAddress Target contract address for the call * @param abi Contract ABI * @param functionName Function to be called on the contract * @param args Arguments list for the function call * @param calls Array of Call3Value calls for Multicall contract * @returns Response from the contract function call */ async callErc7412({ contractAddress, abi, args, functionName, calls = [] }) { const multicallInstance = await this.sdk.contracts.getMulticallInstance(); const currentCall = { target: contractAddress, callData: encodeFunctionData({ abi, functionName, args }), value: 0n, requireSuccess: true }; calls.push(currentCall); const publicClient = this.sdk.getPublicClient(); const oracleCalls = await this.getMissingOracleCalls(calls); const multicallData = encodeFunctionData({ abi: multicallInstance.abi, functionName: "aggregate3Value", args: [[...oracleCalls, ...calls]] }); const totalValue = [...oracleCalls, ...calls].reduce((acc, tx) => { return acc + (tx.value || 0n); }, 0n); const finalTx = { account: this.sdk.accountAddress, to: multicallInstance.address, data: multicallData, value: totalValue }; const response = await publicClient.call(finalTx); this.sdk.logger.info("=== response", response); if (!response.data) throw new Error("Error decoding call data"); const multicallResult = this.decodeResponse( multicallInstance.abi, "aggregate3Value", response.data ); const returnData = multicallResult.at(-1); if (!returnData?.success) throw new Error("Error decoding call data"); const decodedResult = this.decodeResponse(abi, functionName, returnData.returnData); return decodedResult; } /** * Calls the `functionName` on `contractAddress` target using the Multicall contract. If the call requires * a price update, ERC7412 price update tx is prepended to the tx. * @param contractAddress Target contract address for the call * @param abi Contract ABI * @param functionName Function to be called on the contract * @param argsList Array of arguments list for the function call * @param calls Array of Call3Value calls for Multicall contract * @returns Array of responses from the contract function call for the multicalls */ async multicallErc7412({ contractAddress, abi, functionName, args: argsList, calls = [] }) { const multicallInstance = await this.sdk.contracts.getMulticallInstance(); argsList = argsList.map((args) => Array.isArray(args) ? args : [args]); argsList.forEach((args) => { const currentCall = { target: contractAddress, callData: encodeFunctionData({ abi, functionName, args }), value: 0n, requireSuccess: true }; calls.push(currentCall); }); calls = calls.map((call) => { return { ...call, requireSuccess: call.value === 0n ? true : false }; }); const oracleCalls = await this.getMissingOracleCalls(calls); const numCalls = calls.length - oracleCalls.length; const publicClient = this.sdk.getPublicClient(); const totalValue = [...oracleCalls, ...calls].reduce((acc, tx) => { return acc + (tx.value || 0n); }, 0n); const multicallData = encodeFunctionData({ abi: multicallInstance.abi, functionName: "aggregate3Value", args: [[...oracleCalls, ...calls]] }); const finalTx = { account: this.sdk.accountAddress, to: multicallInstance.address, data: multicallData, value: totalValue }; const response = await publicClient.call(finalTx); const multicallResult = this.decodeResponse( multicallInstance.abi, "aggregate3Value", response.data ); const callsToDecode = multicallResult.slice(-numCalls); const decodedResult = callsToDecode.map((result) => this.decodeResponse(abi, functionName, result.returnData)); return decodedResult; } isWriteContractParams(data) { return data.contractAddress !== void 0; } /** * Simulates the `functionName` on `contractAddress` target using the Multicall contract and returns the * final transaction call with ERC7412 price update data if required * @param contractAddress Target contract address for the call * @param abi Contract ABI * @param functionName Function to be called on the contract * @param args Arguments list for the function call * @param calls Array of Call3Value calls for Multicall contract * @param override Override parameters for the transaction * @returns Final transaction call with ERC7412 price update data if required */ async writeErc7412(data, override = { shouldRevertOnTxFailure: true }) { const calls = data.calls ?? []; const multicallInstance = await this.sdk.contracts.getMulticallInstance(); if (this.isWriteContractParams(data)) { const { contractAddress, abi, functionName, args } = data; const currentCall = { target: contractAddress, callData: encodeFunctionData({ abi, functionName, args }), value: 0n, requireSuccess: true }; calls.push(currentCall); } const multicallData = encodeFunctionData({ abi: multicallInstance.abi, functionName: "aggregate3Value", args: [calls] }); let totalValue = 0n; for (const tx of calls) { totalValue += tx.value || 0n; } const finalTx = { account: override.account || this.sdk.accountAddress, to: multicallInstance.address, data: multicallData, value: totalValue }; const publicClient = this.sdk.getPublicClient(); if (override.shouldRevertOnTxFailure) await publicClient.call(finalTx); return this._fromCallDataToTransactionData(finalTx); } /** * Calls the `functionName` on `contractAddress` target using the Multicall contract. If the call requires * a price update, ERC7412 price update tx is prepended to the tx. * @param contractAddress Target contract address for the call * @param abi Contract ABI * @param functionNames Function to be called on the contract * @param argsList Array of arguments list for the function call * @param calls Array of Call3Value calls for Multicall contract * @returns Array of responses from the contract function call for the multicalls */ async multicallMultifunctionErc7412({ contractAddress, abi, functionNames, args: argsList, calls = [] }, override = {}) { const multicallInstance = await this.sdk.contracts.getMulticallInstance(); argsList = argsList.map((args) => Array.isArray(args) ? args : [args]); if (argsList.length != functionNames.length) { throw new Error("Inconsistent data: args and functionName don't match"); } argsList.forEach((args, index) => { const currentCall = { target: contractAddress, callData: encodeFunctionData({ abi, functionName: functionNames[index], args }), value: 0n, requireSuccess: true }; calls.push(currentCall); }); const publicClient = this.sdk.getPublicClient(); const oracleCalls = await this.getMissingOracleCalls(calls); const multicallData = encodeFunctionData({ abi: multicallInstance.abi, functionName: "aggregate3Value", args: [[...oracleCalls, ...calls]] }); let totalValue = 0n; for (const tx of [...oracleCalls, ...calls]) { totalValue += tx.value || 0n; } const finalTx = { account: override.account || this.sdk.accountAddress, to: multicallInstance.address, data: multicallData, value: totalValue }; const response = await publicClient.call(finalTx); this.sdk.logger.info("=== response", response); const multicallResult = this.decodeResponse( multicallInstance.abi, "aggregate3Value", response.data ); const callsToDecode = multicallResult.slice(-oracleCalls.length); const decodedResult = callsToDecode.map( (result, idx) => this.decodeResponse(abi, functionNames[idx], result.returnData) ); return decodedResult; } async getMissingOracleCalls(calls, oracleCalls = [], { attemps = MAX_ERC7412_RETRIES, account, stateOverride } = {}) { const publicClient = this.sdk.getPublicClient(); const totalValue = [...oracleCalls, ...calls].reduce((acc, tx) => { return acc + (tx.value || 0n); }, 0n); this.sdk.logger.info("=== init data", { oracleCalls, calls, attemps }); const multicallInstance = await this.sdk.contracts.getMulticallInstance(); const multicallData = encodeFunctionData({ abi: multicallInstance.abi, functionName: "aggregate3Value", args: [[...oracleCalls, ...calls]] }); const parsedTx = { account: account || this.sdk.accountAddress, to: multicallInstance.address, data: multicallData, value: totalValue, stateOverride }; try { await publicClient.call(parsedTx); return oracleCalls; } catch (error) { const parsedError = parseError(error); this.sdk.logger.error("=== parsedError in getMissingOracleCalls", parsedError); const shouldRetry = this.shouldRetryLogic(error, attemps); console.log("=== shouldRetry", { shouldRetry, error, attemps, parsedError }); if (!shouldRetry) return oracleCalls; const data = await this.handleErc7412Error(parsedError); return await this.getMissingOracleCalls(calls, [...oracleCalls, ...data], { account, attemps: attemps - 1, stateOverride }); } } _fromCall3ToTransactionData(call) { return { to: call.target, data: call.callData, value: call?.value?.toString() ?? "0" }; } _fromTransactionDataToCall3(data, requireSuccess = true) { return { target: data.to, callData: data.data, value: BigInt(data.value || 0), requireSuccess }; } _fromCallDataToTransactionData(calls) { return { to: calls.to, data: calls.data, value: calls?.value?.toString() ?? "0" }; } _fromTransactionDataToCallData(data) { return { account: this.sdk.accountAddress, to: data.to, data: data.data, value: BigInt(data.value || 0) }; } async processTransactions(data, override) { const useOracleCall = override.useOracleCalls ?? true; const oracleCalls = useOracleCall ? await this.getMissingOracleCalls(data, void 0, { account: override.account, stateOverride: override?.stateOverride?.reduce( (acc, state) => { const includedIndex = acc.findIndex((s) => s.address === state.address); const included = override?.stateOverride?.[includedIndex]; if (!included && state.stateDiff) acc.push(state); if (included) { acc[includedIndex] = { address: state.address, stateDiff: [...state.stateDiff, ...included.stateDiff] }; } return acc; }, [] ) }) ?? void 0 : []; const txs = [ ...!override.useMultiCall ? (override.prepend || []).map((a) => this._fromTransactionDataToCall3(a, true)) : [], ...oracleCalls, ...data ]; if (!override.useMultiCall && !override.submit) return txs.map(this.sdk.utils._fromCall3ToTransactionData); const tx = await this.sdk.utils.writeErc7412({ calls: txs }, override); if (!override.submit) return [...override.prepend || [], tx]; return this.sdk.executeTransaction(this.sdk.utils._fromTransactionDataToCallData(tx)); } shouldRetryLogic(error, attemps = 0) { const parsedError = parseError(error); const isErc7412Error = this.isErc7412Error(parsedError); this.sdk.logger.error("=== parsedError in processTransactions ", { parsedError, isErc7412Error }); if (!attemps && !isErc7412Error) return false; if (!isErc7412Error) { return false; } return true; } isErc7412Error(parsedError) { return parsedError.startsWith(SIG_ORACLE_DATA_REQUIRED) || parsedError.startsWith(SIG_FEE_REQUIRED) || parsedError.startsWith(SIG_ERRORS); } }; // src/core/index.ts import { encodeFunctionData as encodeFunctionData2, formatEther as formatEther2, parseUnits } from "viem"; var Core = class { constructor(synthetixSdk) { this.sdk = synthetixSdk; this.accountIds = []; } async initCore() { await this.getAccountIds(); } /** * Returns the Owner wallet address for an account ID * @param accountId - Account ID * @returns string - Address of the account owning the accountId */ async getAccountOwner(accountId) { const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const response = await this.sdk.utils.callErc7412({ contractAddress: coreProxy.address, abi: coreProxy.abi, functionName: "getAccountOwner", args: [accountId] }); this.sdk.logger.info(`Core account Owner for id ${accountId} is ${response}`); return response; } /** * Get the address of the USD stablecoin token * * @returns Address of the USD stablecoin token */ async getUsdToken() { const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const response = await this.sdk.utils.callErc7412({ contractAddress: coreProxy.address, abi: coreProxy.abi, functionName: "getUsdToken", args: [] }); this.sdk.logger.info("USD Token address: ", response); return response; } /** * Get the core account IDs owned by an address. * Fetches the account IDs for the given address by checking the balance of * the AccountProxy contract, which is an NFT owned by the address. * If no address is provided, uses the connected wallet address. * @param address The address to get accounts for. Uses connected address if not provided. * @param defaultAccountId The default account ID to set after fetching. * @returns A list of account IDs owned by the address */ async getAccountIds({ address: accountAddress = this.sdk.accountAddress || ZERO_ADDRESS, accountId: defaultAccountId = void 0 } = {}) { if (accountAddress == ZERO_ADDRESS) { throw new Error("Invalid address"); } const accountProxy = await this.sdk.contracts.getAccountProxyInstance(); const balance = await accountProxy.read.balanceOf([accountAddress]); this.sdk.logger.info("balance", balance); const argsList = []; for (let index = 0; index < Number(balance); index++) { argsList.push([accountAddress, index]); } const accountIds = await this.sdk.utils.multicallErc7412({ contractAddress: accountProxy.address, abi: accountProxy.abi, functionName: "tokenOfOwnerByIndex", args: argsList }); this.accountIds = accountIds; this.sdk.logger.info("accountIds", accountIds); if (defaultAccountId) { this.defaultAccountId = defaultAccountId; } else if (this.accountIds.length > 0) { this.defaultAccountId = this.accountIds[0]; this.sdk.logger.info("Using default account id as ", this.defaultAccountId); } return accountIds; } /** * Get the available collateral for an account for a specified collateral type * of ``token_address`` * Fetches the amount of undelegated collateral available for withdrawal * for a given token and account. * @param tokenAddress The address of the collateral token * @param accountId The ID of the account to check. Uses default if not provided. * @returns The available collateral as an ether value. */ async getAvailableCollateral({ tokenAddress, accountId = this.defaultAccountId }) { if (!accountId) throw new Error("Invalid account ID"); const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const availableCollateral = await this.sdk.utils.callErc7412({ contractAddress: coreProxy.address, abi: coreProxy.abi, functionName: "getAccountAvailableCollateral", args: [accountId, tokenAddress] }); return formatEther2(availableCollateral); } /** * Retrieves the unique system preferred pool * @returns poolId The id of the pool that is currently set as preferred in the system. */ async getPreferredPool() { const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const preferredPool = await this.sdk.utils.callErc7412({ contractAddress: coreProxy.address, abi: coreProxy.abi, functionName: "getPreferredPool", args: [] }); this.sdk.logger.info(preferredPool); return preferredPool; } async createAccount(accountId, override = { shouldRevertOnTxFailure: false }) { const txArgs = []; if (accountId != void 0) { txArgs.push(accountId); } const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const createAccountTx = { target: coreProxy.address, callData: encodeFunctionData2({ abi: coreProxy.abi, functionName: "createAccount", args: txArgs }), value: 0n, requireSuccess: true }; const txs = [createAccountTx]; return this.sdk.utils.processTransactions(txs, { ...override, useOracleCalls: false }); } async deposit({ tokenAddress, amount, decimals = 18, accountId = this.defaultAccountId }, override = { shouldRevertOnTxFailure: false }) { if (!accountId) throw new Error("Invalid account ID"); const amountInWei = parseUnits(amount.toString(), decimals); const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const depositTx = { target: coreProxy.address, callData: encodeFunctionData2({ abi: coreProxy.abi, functionName: "deposit", args: [accountId, tokenAddress, amountInWei] }), value: 0n, requireSuccess: true }; const txs = [depositTx]; return this.sdk.utils.processTransactions(txs, override); } async withdraw({ tokenAddress, amount, decimals = 18, accountId = this.defaultAccountId }, override = { shouldRevertOnTxFailure: false }) { if (!accountId) throw new Error("Account ID is required for withdrawal"); const amountInWei = parseUnits(amount.toString(), decimals); const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const withdrawTx = { target: coreProxy.address, callData: encodeFunctionData2({ abi: coreProxy.abi, functionName: "withdraw", args: [accountId, tokenAddress, amountInWei] }), value: 0n, requireSuccess: true }; const txs = [withdrawTx]; return this.sdk.utils.processTransactions(txs, override); } async delegateCollateral({ tokenAddress, amount, poolId, leverage, accountId = this.defaultAccountId }, override = { shouldRevertOnTxFailure: false }) { if (!accountId) throw new Error("Invalid account ID"); const amountInWei = parseUnits(amount.toString(), 18); const leverageInWei = parseUnits(leverage.toString(), 18); const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const delegateCollateralTx = { target: coreProxy.address, callData: encodeFunctionData2({ abi: coreProxy.abi, functionName: "delegateCollateral", args: [accountId, poolId, tokenAddress, amountInWei, leverageInWei] }), value: 0n, requireSuccess: true }; const txs = [delegateCollateralTx]; return this.sdk.utils.processTransactions(txs, override); } async mintUsd({ tokenAddress, amount, poolId, accountId = this.defaultAccountId }, override = { shouldRevertOnTxFailure: false }) { if (!accountId) throw new Error("Invalid account ID"); const amountInWei = parseUnits(amount.toString(), 18); const coreProxy = await this.sdk.contracts.getCoreProxyInstance(); const mintUsdTx = { target: coreProxy.address, callData: encodeFunctionData2({ abi: coreProxy.abi, functionName: "mintUsd", args: [accountId, poolId, tokenAddress, amountInWei] }), value: 0n, requireSuccess: true }; const txs = [mintUsdTx]; return this.sdk.utils.processTransactions(txs, override); } }; // src/index.ts import { createPublicClient, createWalletClient, encodeFunctionData as encodeFunctionData5, erc20Abi as erc20Abi5, getContract as getContract3, http, maxUint256 as maxUint2562, webSocket } from "viem"; // src/contracts/v3-contracts/42161/index.ts import * as ARBTokenMain from "@synthetixio/v3-contracts/42161-main/ARBToken.json"; import * as AccountProxyMain from "@synthetixio/v3-contracts/42161-main/AccountProxy.json"; import * as AllErrorsMain from "@synthetixio/v3-contracts/42161-main/AllErrors.json"; import * as CollateralToken_ARBMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_ARB.json"; import * as CollateralToken_USDCMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_USDC.json"; import * as CollateralToken_USDeMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_USDe.json"; import * as CollateralToken_WETHMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_WETH.json"; import * as CollateralToken_sETHMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_sETH.json"; import * as CollateralToken_stBTCMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_stBTC.json"; import * as CollateralToken_tBTCMain from "@synthetixio/v3-contracts/42161-main/CollateralToken_tBTC.json"; import * as CoreProxyMain from "@synthetixio/v3-contracts/42161-main/CoreProxy.json"; import * as OracleManagerProxyMain from "@synthetixio/v3-contracts/42161-main/OracleManagerProxy.json"; import * as PerpsAccountProxyMain from "@synthetixio/v3-contracts/42161-main/PerpsAccountProxy.json"; import * as PerpsMarketProxyMain from "@synthetixio/v3-contracts/42161-main/PerpsMarketProxy.json"; import * as PythERC7412WrapperMain from "@synthetixio/v3-contracts/42161-main/PythERC7412Wrapper.json"; import * as RewardsDistributor_1_ARB_ARBMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_ARB_ARB.json"; import * as RewardsDistributor_1_USDC_ARBMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_USDC_ARB.json"; import * as RewardsDistributor_1_USDe_ARBMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_USDe_ARB.json"; import * as RewardsDistributor_1_WETH_ARBMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_WETH_ARB.json"; import * as RewardsDistributor_1_sETHMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_sETH.json"; import * as RewardsDistributor_1_stBTCMain from "@synthetixio/v3-contracts/42161-main/RewardsDistributor_1_stBTC.json"; import * as SpotMarketProxyMain from "@synthetixio/v3-contracts/42161-main/SpotMarketProxy.json"; import * as SynthToken_sETHMain from "@synthetixio/v3-contracts/42161-main/SynthToken_sETH.json"; import * as SynthToken_sUSDCMain from "@synthetixio/v3-contracts/42161-main/SynthToken_sUSDC.json"; import * as SynthToken_stBTCMain from "@synthetixio/v3-contracts/42161-main/SynthToken_stBTC.json"; import * as SynthUSDCTokenMain from "@synthetixio/v3-contracts/42161-main/SynthUSDCToken.json"; import * as TrustedMulticallForwarderMain from "@synthetixio/v3-contracts/42161-main/TrustedMulticallForwarder.json"; import * as USDCTokenMain from "@synthetixio/v3-contracts/42161-main/USDCToken.json"; import * as USDProxyMain from "@synthetixio/v3-contracts/42161-main/USDProxy.json"; import * as WETHTokenMain from "@synthetixio/v3-contracts/42161-main/WETHToken.json"; import * as CannonMain from "@synthetixio/v3-contracts/42161-main/cannon.json"; import * as CollateralTokensMain from "@synthetixio/v3-contracts/42161-main/collateralTokens.json"; import * as ExtrasMain from "@synthetixio/v3-contracts/42161-main/extras.json"; import * as mainMeta from "@synthetixio/v3-contracts/42161-main/meta.json"; import * as MintableTokensMain from "@synthetixio/v3-contracts/42161-main/mintableTokens.json"; import * as RewardsDistributorsMain from "@synthetixio/v3-contracts/42161-main/rewardsDistributors.json"; import * as SynthTokensMain from "@synthetixio/v3-contracts/42161-main/synthTokens.json"; import * as SystemTokenMain from "@synthetixio/v3-contracts/42161-main/systemToken.json"; // src/contracts/abis/zap.ts var ZAP_ABI = [ { inputs: [ { internalType: "address", name: "_usdc", type: "address" }, { internalType: "address", name: "_usdx", type: "address" }, { internalType: "address", name: "_spotMarket", type: "address" }, { internalType: "address", name: "_perpsMarket", type: "address" }, { internalType: "address", name: "_referrer", type: "address" }, { internalType: "uint128", name: "_susdcSpotId", type: "uint128" }, { internalType: "address", name: "_aave", type: "address" }, { internalType: "address", name: "_router", type: "address" }, { internalType: "address", name: "_quoter", type: "address" } ], stateMutability: "nonpayable", type: "constructor" }, { inputs: [{ internalType: "string", name: "reason", type: "string" }], name: "BuyFailed", type: "error" }, { inputs: [], name: "NotPermitted", type: "error" }, { inputs: [{ internalType: "address", name: "caller", type: "address" }], name: "OnlyAave", type: "error" }, { inputs: [], name: "OnlyPlumber", type: "error" }, { inputs: [{ internalType: "bytes", name: "reason", type: "bytes" }], name: "PullFailed", type: "error" }, { inputs: [{ internalType: "bytes", name: "reason", type: "bytes" }], name: "PushFailed", type: "error" }, { inputs: [ { internalType: "enum Reentrancy.Stage", name: "actual", type: "uint8" }, { internalType: "enum Reentrancy.Stage", name: "expected", type: "uint8" } ], name: "ReentrancyDetected", type: "error" }, { inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" }, { inputs: [{ internalType: "address", name: "token", type: "address" }], name: "SafeERC20FailedOperation", type: "error" }, { inputs: [{ internalType: "string", name: "reason", type: "string" }], name: "SellFailed", type: "error" }, { inputs: [{ internalType: "string", name: "reason", type: "string" }], name: "SwapFailed", type: "error" }, { inputs: [{ internalType: "string", name: "reason", type: "string" }], name: "UnwrapFailed", type: "error" }, { inputs: [{ internalType: "string", name: "reason", type: "string" }], name: "WrapFailed", type: "error" }, { anonymous: false, inputs: [ { indexed: false, internalType: "address", name: "plumber", type: "address" } ], name: "PlumberDesignated", type: "event" }, { inputs: [], name: "AAVE", outputs: [{ internalType: "address", name: "", type: "address" }], stateMutability: "view", type: "function" }, { inputs: [], name: "BURN_PERMISSION", outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], stateMutability: "view", type: "function" }, { inputs: [], name: "FEE_TIER", outputs: [{ internalType: "uint24", name: "", type: "uint24" }], stateMutability: "view", type: "function" }, { inputs: [], name: "MODIFY_PERMISSION", outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], stateMutability: "view", type: "function" }, { inputs: [], name: "PERPS_MARKET", outputs: [{ internalType: "address", name: "", type: "address" }], stateMutability: "view", type: "function" }, { inputs: [], name: "PLUMBER", outputs: [{ internalType: "address", name: "", type: "address" }], stateMutability: "view", type: "function" }, { inputs: [], name: "QUOTER", outputs: [{ internalType: "address", name: "", type: "address" }], stateMutability: "view", type: "function" }, { inputs: [], name: "REFERRAL_CODE", outputs: [{ internalType: "uint16", name: "", type: "uint16" }], stateMutability: "view", type: "function" }, { inputs: [],