@gearbox-protocol/sdk 17.10.1 → 17.12.0

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.
Files changed (95) hide show
  1. package/dist/cjs/offchain/AbstractOffchainNamespace.js +31 -8
  2. package/dist/cjs/permissionless/api/GearboxPermissionless.js +79 -0
  3. package/dist/cjs/permissionless/api/chains/ChainsNamespace.js +180 -0
  4. package/dist/cjs/permissionless/api/chains/index.js +4 -0
  5. package/dist/cjs/permissionless/api/chains/schemas.js +34 -0
  6. package/dist/cjs/permissionless/api/chains/types.js +1 -0
  7. package/dist/cjs/permissionless/api/deploy/PermissionlessDeploy.js +152 -0
  8. package/dist/cjs/permissionless/api/deploy/effective-staleness.js +46 -0
  9. package/dist/cjs/permissionless/api/deploy/index.js +8 -0
  10. package/dist/cjs/permissionless/api/deploy/types.js +1 -0
  11. package/dist/cjs/permissionless/api/errors.js +82 -0
  12. package/dist/cjs/permissionless/api/index.js +28 -0
  13. package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +84 -0
  14. package/dist/cjs/permissionless/api/oracles/index.js +6 -0
  15. package/dist/cjs/permissionless/api/oracles/schemas.js +51 -0
  16. package/dist/cjs/permissionless/api/oracles/types.js +1 -0
  17. package/dist/cjs/permissionless/api/schemas.js +28 -0
  18. package/dist/cjs/permissionless/api/transactions/InstanceOwnerTransactions.js +234 -0
  19. package/dist/cjs/permissionless/api/transactions/index.js +4 -0
  20. package/dist/cjs/permissionless/api/transactions/lp-price-feeds.js +86 -0
  21. package/dist/cjs/permissionless/api/transactions/types.js +1 -0
  22. package/dist/cjs/permissionless/api/types.js +1 -0
  23. package/dist/cjs/permissionless/bindings/cross-chain-multisig.js +1 -1
  24. package/dist/cjs/permissionless/bindings/index.js +3 -3
  25. package/dist/cjs/permissionless/bindings/instance-manager.js +3 -3
  26. package/dist/cjs/permissionless/bindings/price-feed-store.js +0 -2
  27. package/dist/cjs/permissionless/index.js +31 -8
  28. package/dist/cjs/permissionless/utils/governance/timelock-txs.js +1 -1
  29. package/dist/cjs/permissionless/utils/index.js +4 -4
  30. package/dist/cjs/permissionless/utils/price-update/get-price-update-tx.js +1 -1
  31. package/dist/cjs/permissionless/utils/price-update/index.js +1 -1
  32. package/dist/esm/offchain/AbstractOffchainNamespace.js +31 -8
  33. package/dist/esm/permissionless/api/GearboxPermissionless.js +78 -0
  34. package/dist/esm/permissionless/api/chains/ChainsNamespace.js +179 -0
  35. package/dist/esm/permissionless/api/chains/index.js +3 -0
  36. package/dist/esm/permissionless/api/chains/schemas.js +30 -0
  37. package/dist/esm/permissionless/api/chains/types.js +1 -0
  38. package/dist/esm/permissionless/api/deploy/PermissionlessDeploy.js +149 -0
  39. package/dist/esm/permissionless/api/deploy/effective-staleness.js +46 -0
  40. package/dist/esm/permissionless/api/deploy/index.js +4 -0
  41. package/dist/esm/permissionless/api/deploy/types.js +1 -0
  42. package/dist/esm/permissionless/api/errors.js +77 -0
  43. package/dist/esm/permissionless/api/index.js +13 -0
  44. package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +81 -0
  45. package/dist/esm/permissionless/api/oracles/index.js +3 -0
  46. package/dist/esm/permissionless/api/oracles/schemas.js +47 -0
  47. package/dist/esm/permissionless/api/oracles/types.js +1 -0
  48. package/dist/esm/permissionless/api/schemas.js +25 -0
  49. package/dist/esm/permissionless/api/transactions/InstanceOwnerTransactions.js +233 -0
  50. package/dist/esm/permissionless/api/transactions/index.js +3 -0
  51. package/dist/esm/permissionless/api/transactions/lp-price-feeds.js +84 -0
  52. package/dist/esm/permissionless/api/transactions/types.js +1 -0
  53. package/dist/esm/permissionless/api/types.js +1 -0
  54. package/dist/esm/permissionless/bindings/cross-chain-multisig.js +1 -1
  55. package/dist/esm/permissionless/bindings/index.js +3 -3
  56. package/dist/esm/permissionless/bindings/instance-manager.js +3 -3
  57. package/dist/esm/permissionless/bindings/price-feed-store.js +0 -2
  58. package/dist/esm/permissionless/index.js +17 -9
  59. package/dist/esm/permissionless/utils/governance/timelock-txs.js +1 -1
  60. package/dist/esm/permissionless/utils/index.js +4 -4
  61. package/dist/esm/permissionless/utils/price-update/get-price-update-tx.js +1 -1
  62. package/dist/esm/permissionless/utils/price-update/index.js +1 -1
  63. package/dist/types/offchain/AbstractOffchainNamespace.d.ts +12 -0
  64. package/dist/types/permissionless/api/GearboxPermissionless.d.ts +71 -0
  65. package/dist/types/permissionless/api/chains/ChainsNamespace.d.ts +99 -0
  66. package/dist/types/permissionless/api/chains/index.d.ts +3 -0
  67. package/dist/types/permissionless/api/chains/schemas.d.ts +49 -0
  68. package/dist/types/permissionless/api/chains/types.d.ts +58 -0
  69. package/dist/types/permissionless/api/deploy/PermissionlessDeploy.d.ts +69 -0
  70. package/dist/types/permissionless/api/deploy/effective-staleness.d.ts +19 -0
  71. package/dist/types/permissionless/api/deploy/index.d.ts +4 -0
  72. package/dist/types/permissionless/api/deploy/types.d.ts +87 -0
  73. package/dist/types/permissionless/api/errors.d.ts +63 -0
  74. package/dist/types/permissionless/api/index.d.ts +17 -0
  75. package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +64 -0
  76. package/dist/types/permissionless/api/oracles/index.d.ts +3 -0
  77. package/dist/types/permissionless/api/oracles/schemas.d.ts +74 -0
  78. package/dist/types/permissionless/api/oracles/types.d.ts +51 -0
  79. package/dist/types/permissionless/api/schemas.d.ts +22 -0
  80. package/dist/types/permissionless/api/transactions/InstanceOwnerTransactions.d.ts +68 -0
  81. package/dist/types/permissionless/api/transactions/index.d.ts +3 -0
  82. package/dist/types/permissionless/api/transactions/lp-price-feeds.d.ts +17 -0
  83. package/dist/types/permissionless/api/transactions/types.d.ts +58 -0
  84. package/dist/types/permissionless/api/types.d.ts +12 -0
  85. package/dist/types/permissionless/bindings/factory/credit-factory.d.ts +1 -1
  86. package/dist/types/permissionless/bindings/factory/loss-policy-factory.d.ts +1 -1
  87. package/dist/types/permissionless/bindings/factory/pool-factory.d.ts +1 -1
  88. package/dist/types/permissionless/bindings/factory/price-oracle-factory.d.ts +1 -1
  89. package/dist/types/permissionless/bindings/index.d.ts +1 -1
  90. package/dist/types/permissionless/core/index.d.ts +1 -1
  91. package/dist/types/permissionless/core/pricefeed.d.ts +0 -1
  92. package/dist/types/permissionless/index.d.ts +23 -10
  93. package/dist/types/permissionless/utils/governance/index.d.ts +1 -1
  94. package/dist/types/permissionless/utils/index.d.ts +1 -1
  95. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ import { parseAbi } from "viem";
2
+ //#region src/permissionless/api/deploy/effective-staleness.ts
3
+ const abi = parseAbi(["function stalenessPeriod() view returns (uint32)", "function latestRoundData() view returns (uint80,int256,uint256,uint256,uint80)"]);
4
+ /**
5
+ * Bound for a leg that recomputes its answer on every read and stamps it with
6
+ * the current block (exchange-rate adapters, NAV feeds, Pendle oracles,
7
+ * constant feeds). One second is the tightest value that still satisfies
8
+ * `block.timestamp < updatedAt + stalenessPeriod`.
9
+ **/
10
+ const CURRENT_BLOCK_STALENESS = 1;
11
+ /**
12
+ * The staleness a deployed price feed enforces on the price it reports.
13
+ *
14
+ * This is deliberately not the value the feed is registered with in the
15
+ * PriceFeedStore — derived feeds must be registered with 0 there, and the store
16
+ * rejects anything else for them with `IncorrectParameterException()`. It is
17
+ * the value a parent feed needs when it re-checks the leg's `updatedAt`
18
+ * itself.
19
+ *
20
+ * Returns `null` when it cannot be determined. Callers must surface that
21
+ * rather than substitute a bound: a leg constructed with a staleness its
22
+ * underlying oracle can never satisfy produces a feed that reverts on every
23
+ * read and can never be added to the store.
24
+ **/
25
+ async function deriveEffectiveStalenessPeriod(client, address) {
26
+ try {
27
+ const own = await client.readContract({
28
+ address,
29
+ abi,
30
+ functionName: "stalenessPeriod"
31
+ });
32
+ if (Number(own) > 0) return Number(own);
33
+ } catch {}
34
+ try {
35
+ const [block, round] = await Promise.all([client.getBlock(), client.readContract({
36
+ address,
37
+ abi,
38
+ functionName: "latestRoundData"
39
+ })]);
40
+ const updatedAt = Number(round[3]);
41
+ if (updatedAt > 0 && updatedAt >= Number(block.timestamp)) return CURRENT_BLOCK_STALENESS;
42
+ } catch {}
43
+ return null;
44
+ }
45
+ //#endregion
46
+ export { deriveEffectiveStalenessPeriod };
@@ -0,0 +1,4 @@
1
+ import { deriveEffectiveStalenessPeriod } from "./effective-staleness.js";
2
+ import { DEFAULT_DEPLOY_SALT, PermissionlessDeploy, priceFeedSalt } from "./PermissionlessDeploy.js";
3
+ import "./types.js";
4
+ export { DEFAULT_DEPLOY_SALT, PermissionlessDeploy, deriveEffectiveStalenessPeriod, priceFeedSalt };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { BaseError } from "viem";
2
+ //#region src/permissionless/api/errors.ts
3
+ /**
4
+ * Thrown when anything is read off {@link ChainsNamespace} before
5
+ * {@link GearboxPermissionless.attach} has completed.
6
+ *
7
+ * The chain list is the backend's answer, so there is nothing to report until
8
+ * it arrives. Answering with an empty list instead would be indistinguishable
9
+ * from a backend that serves no chains at all.
10
+ **/
11
+ var PermissionlessNotAttachedError = class extends BaseError {
12
+ name = "PermissionlessNotAttachedError";
13
+ constructor() {
14
+ super("The client has not attached yet.", { metaMessages: ["Await `attach()` before reading chains off the client."] });
15
+ }
16
+ };
17
+ /**
18
+ * Thrown when a call has to read a chain the client holds no viem client for.
19
+ *
20
+ * Either the backend does not serve the chain, or it serves one this SDK
21
+ * carries no definition for: endpoints come from the chain definition, since
22
+ * the backend's own urls carry provider keys and are never handed out.
23
+ **/
24
+ var PermissionlessChainUnreachableError = class extends BaseError {
25
+ name = "PermissionlessChainUnreachableError";
26
+ /** Chain that was asked for. */
27
+ chainId;
28
+ constructor(chainId) {
29
+ super(`Chain ${chainId} cannot be read directly.`, { metaMessages: ["The backend either does not serve it, or serves a chain this SDK has no definition for."] });
30
+ this.chainId = chainId;
31
+ }
32
+ };
33
+ /**
34
+ * Thrown when a rename names a feed the PriceFeedStore does not carry. A name
35
+ * is part of what the store holds, so there is nothing to change on a feed
36
+ * that never reached it.
37
+ **/
38
+ var PriceFeedNotInStoreError = class extends BaseError {
39
+ name = "PriceFeedNotInStoreError";
40
+ /** Feed that was named. */
41
+ priceFeed;
42
+ chainId;
43
+ constructor(priceFeed, chainId) {
44
+ super(`Price feed ${priceFeed} is not in the PriceFeedStore of chain ${chainId}.`, { metaMessages: ["Only a feed the store already carries can be renamed; add it first."] });
45
+ this.priceFeed = priceFeed;
46
+ this.chainId = chainId;
47
+ }
48
+ };
49
+ /**
50
+ * Thrown when a composite feed names a leg whose own staleness bound cannot be
51
+ * read off the chain.
52
+ *
53
+ * Deploying with a placeholder is not an option: a composite whose check can
54
+ * never pass reverts on every read, and the store will not take it. The bound
55
+ * has to be set on the leg first.
56
+ **/
57
+ var PriceFeedStalenessUnknownError = class extends BaseError {
58
+ name = "PriceFeedStalenessUnknownError";
59
+ /** Leg whose bound could not be determined. */
60
+ priceFeed;
61
+ constructor(priceFeed) {
62
+ super(`Cannot determine the staleness period of price feed ${priceFeed}.`, { metaMessages: ["Deploying with a placeholder would make this composite permanently stale.", "Set the staleness period on the underlying feed first."] });
63
+ this.priceFeed = priceFeed;
64
+ }
65
+ };
66
+ /**
67
+ * Thrown when a price feed deploy names neither a salt nor the name one would
68
+ * be derived from, leaving CREATE2 nothing to distinguish it by.
69
+ **/
70
+ var PriceFeedSaltMissingError = class extends BaseError {
71
+ name = "PriceFeedSaltMissingError";
72
+ constructor() {
73
+ super("A price feed deploy needs either `salt` or `name`.", { metaMessages: ["`name` is hashed into the salt, which is what keeps two feeds of the", "same type and arguments from landing on the same address."] });
74
+ }
75
+ };
76
+ //#endregion
77
+ export { PermissionlessChainUnreachableError, PermissionlessNotAttachedError, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError };
@@ -0,0 +1,13 @@
1
+ import { PermissionlessChainUnreachableError, PermissionlessNotAttachedError, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError } from "./errors.js";
2
+ import { ChainsNamespace } from "./chains/ChainsNamespace.js";
3
+ import "./chains/index.js";
4
+ import { deriveEffectiveStalenessPeriod } from "./deploy/effective-staleness.js";
5
+ import { DEFAULT_DEPLOY_SALT, PermissionlessDeploy, priceFeedSalt } from "./deploy/PermissionlessDeploy.js";
6
+ import "./deploy/index.js";
7
+ import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./oracles/PermissionlessOracles.js";
8
+ import { InstanceOwnerTransactions } from "./transactions/InstanceOwnerTransactions.js";
9
+ import { GearboxPermissionless } from "./GearboxPermissionless.js";
10
+ import "./oracles/index.js";
11
+ import "./transactions/index.js";
12
+ import "./types.js";
13
+ export { ChainsNamespace, DEFAULT_DEPLOY_SALT, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, GearboxPermissionless, InstanceOwnerTransactions, PermissionlessChainUnreachableError, PermissionlessDeploy, PermissionlessNotAttachedError, PermissionlessOracles, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError, deriveEffectiveStalenessPeriod, priceFeedSalt };
@@ -0,0 +1,81 @@
1
+ import { AbstractOffchainNamespace } from "../../../offchain/AbstractOffchainNamespace.js";
2
+ import { getPrices } from "../../utils/price-update/get-prices.js";
3
+ import { priceFeedStoreSchema, pricesSchema } from "./schemas.js";
4
+ //#region src/permissionless/api/oracles/PermissionlessOracles.ts
5
+ const GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = { 143: 100000000n };
6
+ const GET_PRICES_CHUNK_SIZE = 200;
7
+ /**
8
+ * The price feeds of one chain: the PriceFeedStore as the backend has indexed
9
+ * it, and the prices behind it.
10
+ *
11
+ * ```ts
12
+ * const store = await permissionless.oracles.store({ chainId: 1 });
13
+ * const { prices } = await permissionless.oracles.prices({ chainId: 1 });
14
+ * ```
15
+ *
16
+ * Read-only. What the store carries is decided on chain, so putting a feed
17
+ * there is a batch the instance owner executes — see
18
+ * {@link InstanceOwnerTransactions} — not a row a client writes here.
19
+ **/
20
+ var PermissionlessOracles = class extends AbstractOffchainNamespace {
21
+ #chains;
22
+ constructor(options, chains) {
23
+ super("PermissionlessOracles", {
24
+ ...options,
25
+ chainIds: []
26
+ });
27
+ this.#chains = chains;
28
+ }
29
+ /**
30
+ * The store with the assets and feeds of one chain.
31
+ **/
32
+ async store({ chainId }) {
33
+ return this.getData({
34
+ path: `/pfs/${chainId}`,
35
+ schema: priceFeedStoreSchema
36
+ });
37
+ }
38
+ /**
39
+ * A price per feed, and a market cap per feed and asset. The feeds the
40
+ * indexer has not seen are read off the chain by the backend, so the store
41
+ * is priced whole.
42
+ **/
43
+ async prices({ chainId }) {
44
+ return this.getData({
45
+ path: `/pfs/${chainId}/prices`,
46
+ schema: pricesSchema
47
+ });
48
+ }
49
+ /**
50
+ * The price each of the named feeds reports, read off the chain.
51
+ *
52
+ * Where {@link prices} answers for the store as a whole and at whatever
53
+ * moment the backend priced it, this prices exactly the feeds asked for, at
54
+ * the current block. A feed the store does not carry prices the same way,
55
+ * which is what makes this the call to reach for after a deploy, or while a
56
+ * form is still choosing between candidates.
57
+ *
58
+ * Feeds needing a fresh answer before they can be read are updated inside
59
+ * the same simulation, so a push-based feed prices as readily as a
60
+ * pull-based one. `null` is a feed whose read reverted, not a zero.
61
+ *
62
+ * ```ts
63
+ * const prices = await permissionless.oracles.getPrices({
64
+ * chainId: 1,
65
+ * priceFeeds: [feed],
66
+ * });
67
+ * ```
68
+ *
69
+ * @returns A price per feed, in the 8 decimals every Gearbox feed reports.
70
+ **/
71
+ async getPrices({ chainId, priceFeeds }) {
72
+ return getPrices({
73
+ client: this.#chains.client(chainId),
74
+ priceFeeds,
75
+ chunkSize: 200,
76
+ gasLimit: GET_PRICES_GAS_LIMIT_BY_CHAIN_ID[chainId]
77
+ });
78
+ }
79
+ };
80
+ //#endregion
81
+ export { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles };
@@ -0,0 +1,3 @@
1
+ import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
2
+ import "./types.js";
3
+ export { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles };
@@ -0,0 +1,47 @@
1
+ import { addressRecord, addressSchema } from "../schemas.js";
2
+ import { z } from "zod/v4";
3
+ //#region src/permissionless/api/oracles/schemas.ts
4
+ /**
5
+ * Runtime schemas for {@link ./types.js}.
6
+ *
7
+ * The objects are loose: the backend reads most of these rows straight off the
8
+ * chain through the SDK's own bindings, which attach fields the hand-written
9
+ * types do not declare, and dropping them would make this client lossier than
10
+ * the payload it decodes.
11
+ **/
12
+ /** {@link PriceFeed}, as the backend serves it. */
13
+ const priceFeedSchema = z.looseObject({
14
+ address: addressSchema,
15
+ contractType: z.string(),
16
+ version: z.number(),
17
+ deployedBy: addressSchema,
18
+ stalenessPeriod: z.number(),
19
+ name: z.string()
20
+ });
21
+ /** {@link PermissionlessAsset} */
22
+ const assetSchema = z.looseObject({
23
+ address: addressSchema,
24
+ symbol: z.string(),
25
+ name: z.string(),
26
+ decimals: z.number(),
27
+ owner: addressSchema,
28
+ priceFeeds: z.array(priceFeedSchema)
29
+ });
30
+ /** {@link PermissionlessPriceFeedStore} */
31
+ const priceFeedStoreSchema = z.looseObject({
32
+ chainId: z.number(),
33
+ name: z.string(),
34
+ isPublic: z.boolean(),
35
+ isActivated: z.boolean(),
36
+ explorerUrl: z.string(),
37
+ assets: z.array(assetSchema),
38
+ priceFeeds: z.array(priceFeedSchema)
39
+ });
40
+ /** {@link PermissionlessPrices} */
41
+ const pricesSchema = z.looseObject({
42
+ prices: addressRecord(z.string().nullable()),
43
+ marketCaps: addressRecord(addressRecord(z.string().nullable())),
44
+ time: z.number()
45
+ });
46
+ //#endregion
47
+ export { assetSchema, priceFeedSchema, priceFeedStoreSchema, pricesSchema };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { isAddress, isHex } from "viem";
2
+ import { z } from "zod/v4";
3
+ //#region src/permissionless/api/schemas.ts
4
+ /**
5
+ * Runtime schemas shared by the {@link GearboxPermissionless} namespaces.
6
+ *
7
+ * Addresses are validated but never rewritten: the backend serves them in the
8
+ * case its database holds, and its payloads key records by address, so
9
+ * checksumming here would silently break a lookup by the key it answered with.
10
+ * A caller that needs one form converts with viem's `getAddress`.
11
+ **/
12
+ /** An address, in whatever case the backend serves it. */
13
+ const addressSchema = z.custom((value) => typeof value === "string" && isAddress(value, { strict: false }), { message: "invalid address" });
14
+ /** A `0x`-prefixed hex string, as viem's `Hex`. */
15
+ const hexSchema = z.custom((value) => typeof value === "string" && isHex(value), { message: "invalid hex string" });
16
+ /**
17
+ * A record keyed by address. Spelled out here because zod types a record key
18
+ * by the schema's output, which the branded `Address` is not inferred as.
19
+ **/
20
+ function addressRecord(value) {
21
+ const key = z.string().refine((v) => isAddress(v, { strict: false }), { message: "invalid address" });
22
+ return z.record(key, value);
23
+ }
24
+ //#endregion
25
+ export { addressRecord, addressSchema, hexSchema };
@@ -0,0 +1,233 @@
1
+ import { createRawTx } from "../../../onchain/utils/createRawTx.js";
2
+ import { getNetworkType } from "../../../onchain/chain/chains.js";
3
+ import { AP_PRICE_FEED_COMPRESSOR } from "../../../onchain/constants/address-provider.js";
4
+ import { VERSION_RANGE_310 } from "../../../onchain/constants/versions.js";
5
+ import { ChainContractsRegister } from "../../../onchain/base/ChainContractsRegister.js";
6
+ import { OnchainSDK } from "../../../onchain/OnchainSDK.js";
7
+ import "../../../onchain/index.js";
8
+ import { PriceFeedNotInStoreError } from "../errors.js";
9
+ import { INSTANCE_MANAGER } from "../../utils/literals.js";
10
+ import { PriceFeedStoreContract } from "../../bindings/price-feed-store.js";
11
+ import "../../deployment/addresses.js";
12
+ import { AddressProviderContract } from "../../bindings/address-provider.js";
13
+ import { InstanceManagerContract } from "../../bindings/instance-manager.js";
14
+ import { convertRawTxToSafeMultisigTx, getSafeBatch } from "../../utils/governance/batch.js";
15
+ import { getUpdatablePriceFeeds } from "../../utils/price-update/get-updatable-feeds.js";
16
+ import { getFeedsToUpdate, updateBounds } from "./lp-price-feeds.js";
17
+ //#region src/permissionless/api/transactions/InstanceOwnerTransactions.ts
18
+ const lower = (value) => value.toLowerCase();
19
+ /**
20
+ * The Safe batches an instance owner executes to apply what a curator
21
+ * prepared: adding their private feeds to the PriceFeedStore, renaming feeds,
22
+ * changing staleness periods, forbidding feeds and refreshing LP bounds.
23
+ *
24
+ * Every batch is wrapped with `wrapConfigureLocal`, so the InstanceManager is
25
+ * the only caller the PriceFeedStore ever sees.
26
+ *
27
+ * Backend-free: a batch is built off the chain it will be executed on, and
28
+ * the store it reads is the one {@link PermissionlessOracles} already
29
+ * fetched. Nothing is sent either — a curator never executes these
30
+ * themselves. They prepare a batch and hand it to the instance owner, who
31
+ * reviews and executes it from their Safe.
32
+ *
33
+ * ```ts
34
+ * const txs = await permissionless.transactions.addFeeds({
35
+ * chainId: 1,
36
+ * owner: curator,
37
+ * feeds: [feed],
38
+ * pairs: [{ asset, priceFeed: feed.address }],
39
+ * });
40
+ * ```
41
+ **/
42
+ var InstanceOwnerTransactions = class {
43
+ #chains;
44
+ #oracles;
45
+ constructor(chains, oracles) {
46
+ this.#chains = chains;
47
+ this.#oracles = oracles;
48
+ }
49
+ /**
50
+ * The batch that adds the named feeds to the store and allows the named
51
+ * edges on it.
52
+ *
53
+ * Both lists come from the caller. Which feeds are private, and which asset
54
+ * each is meant to price, is held wherever the curator prepared them — the
55
+ * backend indexes the store, so a feed that has not reached it yet is not
56
+ * something this client can discover.
57
+ *
58
+ * Only feeds that actually carry an edge are added: a feed nobody priced
59
+ * would land in the store doing nothing.
60
+ **/
61
+ async addFeeds({ chainId, owner, feeds, pairs }) {
62
+ const batch = await this.#batchContext(chainId);
63
+ const connected = new Set(pairs.map((pair) => lower(pair.priceFeed)));
64
+ const feedsToAdd = feeds.filter((feed) => connected.has(lower(feed.address)));
65
+ const txs = [...feedsToAdd.map((feed) => batch.store.addPriceFeedTx(feed.address, feed.stalenessPeriod, feed.name)), ...pairs.map((pair) => batch.store.allowPriceFeedTx(pair.asset, pair.priceFeed))];
66
+ return this.#envelope({
67
+ batch,
68
+ author: owner,
69
+ name: "pfs-updates",
70
+ txs,
71
+ touchedFeeds: feedsToAdd.map((feed) => feed.address)
72
+ });
73
+ }
74
+ /**
75
+ * The batch that renames feeds already in the store. A name is part of what
76
+ * the store holds, so changing one is a remove and a fresh add, followed by
77
+ * allowing the feed again on every asset that priced it.
78
+ **/
79
+ async changeNames({ chainId, owner, names }) {
80
+ const [batch, store] = await Promise.all([this.#batchContext(chainId), this.#oracles.store({ chainId })]);
81
+ const feedToAssets = await this.#feedToAssets(batch.store);
82
+ const inStore = new Map(store.priceFeeds.map((feed) => [lower(feed.address), feed]));
83
+ const txs = [];
84
+ for (const [address, name] of Object.entries(names)) {
85
+ const feed = inStore.get(lower(address));
86
+ if (!feed) throw new PriceFeedNotInStoreError(address, chainId);
87
+ txs.push(batch.store.removePriceFeedTx(feed.address), batch.store.addPriceFeedTx(feed.address, feed.stalenessPeriod, name));
88
+ for (const asset of feedToAssets.get(lower(address)) ?? []) txs.push(batch.store.allowPriceFeedTx(asset, feed.address));
89
+ }
90
+ return this.#envelope({
91
+ batch,
92
+ author: owner,
93
+ name: "pricefeed-name-changes",
94
+ txs,
95
+ touchedFeeds: Object.keys(names)
96
+ });
97
+ }
98
+ /**
99
+ * The batch that changes how long each feed's price stays valid.
100
+ **/
101
+ async changeStalenessPeriods({ chainId, owner, periods }) {
102
+ const batch = await this.#batchContext(chainId);
103
+ const entries = Object.entries(periods);
104
+ const txs = entries.map(([address, period]) => batch.store.setStalenessPeriodTx(address, period));
105
+ return this.#envelope({
106
+ batch,
107
+ author: owner,
108
+ name: "pricefeed-staleness-changes",
109
+ txs,
110
+ touchedFeeds: entries.map(([address]) => address)
111
+ });
112
+ }
113
+ /**
114
+ * The batch that forbids feeds on the assets they were allowed for.
115
+ **/
116
+ async forbidFeeds({ chainId, owner, tokens }) {
117
+ const batch = await this.#batchContext(chainId);
118
+ const txs = Object.entries(tokens).flatMap(([token, feeds]) => feeds.map((feed) => createRawTx(batch.store.address, {
119
+ abi: batch.store.abi,
120
+ functionName: "forbidPriceFeed",
121
+ args: [token, feed]
122
+ })));
123
+ return this.#envelope({
124
+ batch,
125
+ author: owner,
126
+ name: "forbid-feeds",
127
+ txs,
128
+ touchedFeeds: []
129
+ });
130
+ }
131
+ /**
132
+ * The batch that pulls every drifted LP feed's lower bound back under its
133
+ * exchange rate. Unlike the others this takes no input: the whole store is
134
+ * scanned and only the feeds that drifted produce a transaction, so this
135
+ * one is slow.
136
+ **/
137
+ async updateBounds({ chainId, owner }) {
138
+ const batch = await this.#batchContext(chainId);
139
+ const pfCompressor = await this.#priceFeedCompressor(batch.client);
140
+ const known = await batch.store.getKnownPriceFeeds();
141
+ const feeds = await getFeedsToUpdate(batch.client, pfCompressor, known);
142
+ const configureTxs = [];
143
+ for (const feed of feeds) configureTxs.push(...await updateBounds(batch.client, feed));
144
+ const txs = [batch.store.configurePriceFeeds(configureTxs.map((tx) => ({
145
+ target: tx.to,
146
+ callData: tx.callData
147
+ })))];
148
+ return this.#envelope({
149
+ batch,
150
+ author: owner,
151
+ name: "update-bounds",
152
+ txs,
153
+ touchedFeeds: []
154
+ });
155
+ }
156
+ /**
157
+ * The contracts every batch of one chain is built against.
158
+ *
159
+ * Nothing here checks that the instance is activated: that is a backend
160
+ * read, and an instance without a deployed InstanceManager fails on the
161
+ * owner lookup below anyway.
162
+ **/
163
+ async #batchContext(chainId) {
164
+ const client = this.#chains.client(chainId);
165
+ const addressProvider = new AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client);
166
+ const instanceManager = new InstanceManagerContract(await addressProvider.getAddressOrRevert(INSTANCE_MANAGER), new ChainContractsRegister(client));
167
+ return {
168
+ chainId,
169
+ client,
170
+ store: new PriceFeedStoreContract("0x74A868AC479EE145029bB80827BB77F7B7c441cB", client),
171
+ instanceManager,
172
+ instanceOwner: await instanceManager.getOwner()
173
+ };
174
+ }
175
+ /** The common shape every batch is returned in. */
176
+ async #envelope(args) {
177
+ const { batch, author, name, txs, touchedFeeds } = args;
178
+ const { chainId, client, instanceManager, instanceOwner } = batch;
179
+ const wrapped = txs.map((tx) => instanceManager.wrapConfigureLocal(tx));
180
+ const safeBatch = getSafeBatch({
181
+ chainId,
182
+ safeAddress: instanceOwner,
183
+ name,
184
+ txs: wrapped.map((tx) => convertRawTxToSafeMultisigTx(tx))
185
+ });
186
+ const [block, updatableFeeds] = await Promise.all([client.getBlock(), this.#updatableFeeds(touchedFeeds, client)]);
187
+ return {
188
+ chainId,
189
+ author,
190
+ instanceManager: instanceManager.address,
191
+ batches: [safeBatch.transactions],
192
+ createdAtBlock: Number(block.number),
193
+ updatableFeeds: [updatableFeeds]
194
+ };
195
+ }
196
+ /** Which of the touched feeds the executor has to push a price update for. */
197
+ async #updatableFeeds(touchedFeeds, client) {
198
+ if (touchedFeeds.length === 0) return [];
199
+ const sdk = await this.#sdk(client);
200
+ return (await getUpdatablePriceFeeds({
201
+ sdk,
202
+ client,
203
+ pfCompressor: this.#compressorFrom(sdk),
204
+ priceFeeds: touchedFeeds
205
+ })).map((contract) => contract.address);
206
+ }
207
+ async #priceFeedCompressor(client) {
208
+ return this.#compressorFrom(await this.#sdk(client));
209
+ }
210
+ #compressorFrom(sdk) {
211
+ const [compressor] = sdk.addressProvider.mustGetLatest(AP_PRICE_FEED_COMPRESSOR, VERSION_RANGE_310);
212
+ return compressor;
213
+ }
214
+ async #sdk(client) {
215
+ const chainId = client.chain?.id ?? await client.getChainId();
216
+ const sdk = new OnchainSDK(getNetworkType(chainId), { client });
217
+ await sdk.attach({ marketConfigurators: [] });
218
+ return sdk;
219
+ }
220
+ /** Every asset each feed is currently allowed on, keyed by lowercase feed. */
221
+ async #feedToAssets(store) {
222
+ const byFeed = /* @__PURE__ */ new Map();
223
+ for (const { token, priceFeeds } of await store.getTokenPriceFeedsMap()) for (const feed of priceFeeds) {
224
+ const key = lower(feed);
225
+ const assets = byFeed.get(key);
226
+ if (assets) assets.push(token.toLowerCase());
227
+ else byFeed.set(key, [token.toLowerCase()]);
228
+ }
229
+ return byFeed;
230
+ }
231
+ };
232
+ //#endregion
233
+ export { InstanceOwnerTransactions };
@@ -0,0 +1,3 @@
1
+ import { InstanceOwnerTransactions } from "./InstanceOwnerTransactions.js";
2
+ import "./types.js";
3
+ export { InstanceOwnerTransactions };
@@ -0,0 +1,84 @@
1
+ import { createRawTx } from "../../../onchain/utils/createRawTx.js";
2
+ import { simulateMulticall } from "../../../onchain/utils/viem/simulateMulticall.js";
3
+ import { priceFeedCompressorAbi } from "../../../abi/compressors/priceFeedCompressor.js";
4
+ import "../../../onchain/index.js";
5
+ import { decodeFunctionResult, encodeFunctionData, parseAbi } from "viem";
6
+ //#region src/permissionless/api/transactions/lp-price-feeds.ts
7
+ const LOWER_BOUND_FACTOR = 999n;
8
+ const ilpPriceFeedAbi = parseAbi([
9
+ "function contractType() external view returns (bytes32)",
10
+ "function setLimiter(uint256 newLimiter) external",
11
+ "function getLPExchangeRate() public view returns (uint256 exchangeRate)",
12
+ "function lowerBound() external view returns (uint256)",
13
+ "function upperBound() external view returns (uint256)"
14
+ ]);
15
+ function currentLowerBound(client, address) {
16
+ return client.readContract({
17
+ abi: ilpPriceFeedAbi,
18
+ address,
19
+ functionName: "lowerBound"
20
+ });
21
+ }
22
+ function getValue(client, address) {
23
+ return client.readContract({
24
+ abi: ilpPriceFeedAbi,
25
+ address,
26
+ functionName: "getLPExchangeRate"
27
+ });
28
+ }
29
+ function toLowerBound(value) {
30
+ return value * LOWER_BOUND_FACTOR / 1000n;
31
+ }
32
+ /**
33
+ * Moves an LP feed's lower bound back under its current exchange rate, when
34
+ * the rate has outgrown the bound or drifted far enough from it to be worth a
35
+ * transaction. Returns nothing when the bound still fits.
36
+ **/
37
+ async function updateBounds(client, address) {
38
+ const value = await getValue(client, address);
39
+ const currentLowerBoundValue = await currentLowerBound(client, address);
40
+ const lowerBound = toLowerBound(value);
41
+ const deviationFromValue = Math.floor(Number(10000n * (value - currentLowerBoundValue) / currentLowerBoundValue)) / 1e4;
42
+ const deviationFromBound = Math.floor(Number(10000n * (lowerBound - currentLowerBoundValue) / currentLowerBoundValue)) / 1e4;
43
+ const txs = [];
44
+ if (deviationFromValue < 0 || Math.abs(deviationFromBound) > .005) txs.push(createRawTx(address, {
45
+ abi: ilpPriceFeedAbi,
46
+ functionName: "setLimiter",
47
+ args: [lowerBound]
48
+ }, `[${address}].setLimiter(${lowerBound}); // deviationFromValue: ${(100 * deviationFromValue).toFixed(2)}%`));
49
+ return txs;
50
+ }
51
+ /**
52
+ * Of the whole feed tree behind `feeds`, the ones that are LP feeds: the
53
+ * probe keeps whatever answers `getLPExchangeRate`.
54
+ **/
55
+ async function getFeedsToUpdate(client, pfCompressor, feeds) {
56
+ const updateableFeeds = /* @__PURE__ */ new Set();
57
+ const { data } = await client.call({
58
+ to: pfCompressor,
59
+ data: encodeFunctionData({
60
+ abi: priceFeedCompressorAbi,
61
+ functionName: "loadPriceFeedTree",
62
+ args: [feeds]
63
+ }),
64
+ gas: 200000000n
65
+ });
66
+ const trees = decodeFunctionResult({
67
+ abi: priceFeedCompressorAbi,
68
+ functionName: "loadPriceFeedTree",
69
+ data: data ?? "0x"
70
+ });
71
+ for (const tree of trees) updateableFeeds.add(tree.baseParams.addr.toLowerCase());
72
+ const candidates = Array.from(updateableFeeds);
73
+ const { results } = await simulateMulticall(client, {
74
+ contracts: candidates.map((feed) => ({
75
+ address: feed,
76
+ abi: ilpPriceFeedAbi,
77
+ functionName: "getLPExchangeRate"
78
+ })),
79
+ allowFailure: true
80
+ });
81
+ return candidates.filter((_, index) => results[index]?.status === "success");
82
+ }
83
+ //#endregion
84
+ export { getFeedsToUpdate, updateBounds };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -3,9 +3,9 @@ import { BaseContract } from "../../onchain/base/BaseContract.js";
3
3
  import "../../onchain/index.js";
4
4
  import { CROSS_CHAIN_MULTISIG } from "../utils/literals.js";
5
5
  import { normalizeSignature } from "../utils/signature.js";
6
- import { crossChainMultisigAbi } from "../../abi/310/crossChainMultisig.js";
7
6
  import "../deployment/addresses.js";
8
7
  import { InstanceManagerContract } from "./instance-manager.js";
8
+ import { crossChainMultisigAbi } from "../../abi/310/crossChainMultisig.js";
9
9
  import { MarketConfiguratorFactoryContract } from "./market-configurator-factory.js";
10
10
  import { parseAbi, parseEventLogs, recoverTypedDataAddress, zeroAddress } from "viem";
11
11
  //#region src/permissionless/bindings/cross-chain-multisig.ts
@@ -1,10 +1,10 @@
1
- import { AddressProviderContract } from "./address-provider.js";
2
1
  import { BytecodeRepositoryContract } from "./bytecode-repository.js";
2
+ import { priceFeedSetupParams } from "./pricefeeds/index.js";
3
+ import { PriceFeedStoreContract } from "./price-feed-store.js";
4
+ import { AddressProviderContract } from "./address-provider.js";
3
5
  import { TokenCompressorContract } from "./compressors/token-compressor.js";
4
6
  import { WithdrawalCompressorContract } from "./compressors/withdrawal-compressor.js";
5
7
  import "./compressors/index.js";
6
- import { priceFeedSetupParams } from "./pricefeeds/index.js";
7
- import { PriceFeedStoreContract } from "./price-feed-store.js";
8
8
  import { RoutingManagerContract } from "./router/routing-manager.js";
9
9
  import "./router/index.js";
10
10
  import { TreasurySplitterContract } from "./treasury-splitter.js";