Prechádzať zdrojové kódy

feat: add Pulse Solidity SDK

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>
Devin AI 6 mesiacov pred
rodič
commit
893222ab34

+ 140 - 0
target_chains/ethereum/pulse_sdk/solidity/IScheduler.sol

@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
+import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
+import "./SchedulerEvents.sol";
+import "./SchedulerStructs.sol";
+
+interface IScheduler is SchedulerEvents {
+    /**
+     * @notice Creates a new subscription
+     * @dev Requires msg.value to be at least the minimum balance for the subscription (calculated by getMinimumBalance()).
+     * @param subscriptionParams The parameters for the subscription
+     * @return subscriptionId The ID of the newly created subscription
+     */
+    function createSubscription(
+        SchedulerStructs.SubscriptionParams calldata subscriptionParams
+    ) external payable returns (uint256 subscriptionId);
+
+    /**
+     * @notice Gets a subscription's parameters and status
+     * @param subscriptionId The ID of the subscription
+     * @return params The subscription parameters
+     * @return status The subscription status
+     */
+    function getSubscription(
+        uint256 subscriptionId
+    )
+        external
+        view
+        returns (
+            SchedulerStructs.SubscriptionParams memory params,
+            SchedulerStructs.SubscriptionStatus memory status
+        );
+
+    /**
+     * @notice Updates an existing subscription
+     * @dev You can activate or deactivate a subscription by setting isActive to true or false. Reactivating a subscription
+     *      requires the subscription to hold at least the minimum balance (calculated by getMinimumBalance()).
+     * @dev Any Ether sent with this call (`msg.value`) will be added to the subscription's balance before processing the update.
+     * @param subscriptionId The ID of the subscription to update
+     * @param newSubscriptionParams The new parameters for the subscription
+     */
+    function updateSubscription(
+        uint256 subscriptionId,
+        SchedulerStructs.SubscriptionParams calldata newSubscriptionParams
+    ) external payable;
+
+    /**
+     * @notice Updates price feeds for a subscription.
+     * @dev The updateData must contain all price feeds for the subscription, not a subset or superset.
+     * @dev Internally, the updateData is verified using the Pyth contract and validates update conditions.
+     *      The call will only succeed if the update conditions for the subscription are met.
+     * @param subscriptionId The ID of the subscription
+     * @param updateData The price update data from Pyth
+     */
+    function updatePriceFeeds(
+        uint256 subscriptionId,
+        bytes[] calldata updateData
+    ) external;
+
+    /** @notice Returns the price of a price feed without any sanity checks.
+     * @dev This function returns the most recent price update in this contract without any recency checks.
+     * This function is unsafe as the returned price update may be arbitrarily far in the past.
+     *
+     * Users of this function should check the `publishTime` in the price to ensure that the returned price is
+     * sufficiently recent for their application. If you are considering using this function, it may be
+     * safer / easier to use `getPriceNoOlderThan`.
+     * @return prices - please read the documentation of PythStructs.Price to understand how to use this safely.
+     */
+    function getPricesUnsafe(
+        uint256 subscriptionId,
+        bytes32[] calldata priceIds
+    ) external view returns (PythStructs.Price[] memory prices);
+
+    /**
+     * @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks.
+     * @dev This function returns the same price as `getEmaPrice` in the case where the price is available.
+     * However, if the price is not recent this function returns the latest available price.
+     *
+     * The returned price can be from arbitrarily far in the past; this function makes no guarantees that
+     * the returned price is recent or useful for any particular application.
+     *
+     * Users of this function should check the `publishTime` in the price to ensure that the returned price is
+     * sufficiently recent for their application. If you are considering using this function, it may be
+     * safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`.
+     * @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
+     */
+    function getEmaPriceUnsafe(
+        uint256 subscriptionId,
+        bytes32[] calldata priceIds
+    ) external view returns (PythStructs.Price[] memory price);
+
+    /**
+     * @notice Adds funds to a subscription's balance
+     * @param subscriptionId The ID of the subscription
+     */
+    function addFunds(uint256 subscriptionId) external payable;
+
+    /**
+     * @notice Withdraws funds from a subscription's balance.
+     * @dev A minimum balance must be maintained for active subscriptions. To withdraw past
+     * the minimum balance limit, deactivate the subscription first.
+     * @param subscriptionId The ID of the subscription
+     * @param amount The amount to withdraw
+     */
+    function withdrawFunds(uint256 subscriptionId, uint256 amount) external;
+
+    /**
+     * @notice Returns the minimum balance an active subscription of a given size needs to hold.
+     * @param numPriceFeeds The number of price feeds in the subscription.
+     */
+    function getMinimumBalance(
+        uint8 numPriceFeeds
+    ) external view returns (uint256 minimumBalanceInWei);
+
+    /**
+     * @notice Gets all active subscriptions with their parameters, paginated.
+     * @dev This function has no access control to allow keepers to discover active subscriptions.
+     * @dev Note that the order of subscription IDs returned may not be sequential and can change
+     *      when subscriptions are deactivated or reactivated.
+     * @param startIndex The starting index within the list of active subscriptions (NOT the subscription ID).
+     * @param maxResults The maximum number of results to return starting from startIndex.
+     * @return subscriptionIds Array of active subscription IDs
+     * @return subscriptionParams Array of subscription parameters for each active subscription
+     * @return totalCount Total number of active subscriptions
+     */
+    function getActiveSubscriptions(
+        uint256 startIndex,
+        uint256 maxResults
+    )
+        external
+        view
+        returns (
+            uint256[] memory subscriptionIds,
+            SchedulerStructs.SubscriptionParams[] memory subscriptionParams,
+            uint256 totalCount
+        );
+}

+ 109 - 0
target_chains/ethereum/pulse_sdk/solidity/README.md

@@ -0,0 +1,109 @@
+# Pyth Pulse Solidity SDK
+
+The Pyth Pulse Solidity SDK allows you to interact with the Pyth Pulse protocol, which automatically pushes Pyth price updates to on-chain contracts based on configurable conditions. This SDK provides the interfaces and data structures needed to integrate with the Pulse service.
+
+## Install
+
+### Truffle/Hardhat
+
+If you are using Truffle or Hardhat, simply install the NPM package:
+
+```bash
+npm install @pythnetwork/pulse-sdk-solidity
+```
+
+### Foundry
+
+If you are using Foundry, you will need to create an NPM project if you don't already have one.
+From the root directory of your project, run:
+
+```bash
+npm init -y
+npm install @pythnetwork/pulse-sdk-solidity
+```
+
+Then add the following line to your `remappings.txt` file:
+
+```text
+@pythnetwork/pulse-sdk-solidity/=node_modules/@pythnetwork/pulse-sdk-solidity
+```
+
+## Usage
+
+To use the SDK, you need the address of a Pulse contract on your blockchain.
+
+```solidity
+import "@pythnetwork/pulse-sdk-solidity/IScheduler.sol";
+import "@pythnetwork/pulse-sdk-solidity/SchedulerStructs.sol";
+
+IScheduler pulse = IScheduler(<address>);
+```
+
+## Key Data Structures
+
+### SubscriptionParams
+
+This struct defines the parameters for a Pulse subscription:
+
+```solidity
+struct SubscriptionParams {
+    bytes32[] priceIds;                // Array of Pyth price feed IDs to subscribe to
+    address[] readerWhitelist;         // Optional array of addresses allowed to read prices
+    bool whitelistEnabled;             // Whether to enforce whitelist or allow anyone to read
+    bool isActive;                     // Whether the subscription is active
+    bool isPermanent;                  // Whether the subscription can be updated
+    UpdateCriteria updateCriteria;     // When to update the price feeds
+}
+```
+
+### UpdateCriteria
+
+This struct defines when price feeds should be updated:
+
+```solidity
+struct UpdateCriteria {
+    bool updateOnHeartbeat;           // Update based on time elapsed
+    uint32 heartbeatSeconds;          // Time interval for heartbeat updates
+    bool updateOnDeviation;           // Update based on price deviation
+    uint32 deviationThresholdBps;     // Price deviation threshold in basis points
+}
+```
+
+## Creating a Subscription
+
+```solidity
+SchedulerStructs.SubscriptionParams memory params = SchedulerStructs.SubscriptionParams({
+    priceIds: new bytes32[](1),
+    readerWhitelist: new address[](1),
+    whitelistEnabled: true,
+    isActive: true,
+    isPermanent: false,
+    updateCriteria: SchedulerStructs.UpdateCriteria({
+        updateOnHeartbeat: true,
+        heartbeatSeconds: 60,
+        updateOnDeviation: true,
+        deviationThresholdBps: 100
+    })
+});
+
+params.priceIds[0] = bytes32(...);  // Pyth price feed ID
+params.readerWhitelist[0] = address(...);  // Allowed reader
+
+uint256 minBalance = pulse.getMinimumBalance(uint8(params.priceIds.length));
+uint256 subscriptionId = pulse.createSubscription{value: minBalance}(params);
+```
+
+## Reading Price Feeds
+
+```solidity
+bytes32[] memory priceIds = new bytes32[](1);
+priceIds[0] = bytes32(...);  // Pyth price feed ID
+
+PythStructs.Price[] memory prices = pulse.getPricesUnsafe(subscriptionId, priceIds);
+
+// Access price data
+int64 price = prices[0].price;
+uint64 conf = prices[0].conf;
+int32 expo = prices[0].expo;
+uint publishTime = prices[0].publishTime;
+```

+ 38 - 0
target_chains/ethereum/pulse_sdk/solidity/SchedulerErrors.sol

@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+// Authorization errors
+error Unauthorized();
+
+// Subscription state errors
+error InactiveSubscription();
+error InsufficientBalance();
+error CannotUpdatePermanentSubscription();
+
+// Price feed errors
+error InvalidPriceId(bytes32 providedPriceId, bytes32 expectedPriceId);
+error InvalidPriceIdsLength(uint256 providedLength, uint256 expectedLength);
+error EmptyPriceIds();
+error TooManyPriceIds(uint256 provided, uint256 maximum);
+error DuplicatePriceId(bytes32 priceId);
+error PriceSlotMismatch();
+
+// Update criteria errors
+error InvalidUpdateCriteria();
+error UpdateConditionsNotMet();
+error TimestampTooOld(
+    uint256 providedUpdateTimestamp,
+    uint256 currentTimestamp
+);
+error TimestampOlderThanLastUpdate(
+    uint256 providedUpdateTimestamp,
+    uint256 lastUpdatedAt
+);
+
+// Whitelist errors
+error TooManyWhitelistedReaders(uint256 provided, uint256 maximum);
+error DuplicateWhitelistAddress(address addr);
+
+// Payment errors
+error KeeperPaymentFailed();

+ 15 - 0
target_chains/ethereum/pulse_sdk/solidity/SchedulerEvents.sol

@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "./SchedulerStructs.sol";
+
+interface SchedulerEvents {
+    event SubscriptionCreated(
+        uint256 indexed subscriptionId,
+        address indexed manager
+    );
+    event SubscriptionUpdated(uint256 indexed subscriptionId);
+    event SubscriptionDeactivated(uint256 indexed subscriptionId);
+    event SubscriptionActivated(uint256 indexed subscriptionId);
+    event PricesUpdated(uint256 indexed subscriptionId, uint256 timestamp);
+}

+ 51 - 0
target_chains/ethereum/pulse_sdk/solidity/SchedulerStructs.sol

@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+// This contract holds the Scheduler structs
+contract SchedulerStructs {
+    /// Maximum number of price feeds per subscription
+    uint8 public constant MAX_PRICE_IDS_PER_SUBSCRIPTION = 255;
+    /// Maximum number of addresses in the reader whitelist
+    uint8 public constant MAX_READER_WHITELIST_SIZE = 255;
+
+    /// Maximum time in the past (relative to current block timestamp)
+    /// for which a price update timestamp is considered valid
+    /// when validating the update conditions.
+    /// @dev Note: We don't use this when parsing update data from the Pyth contract
+    /// because don't want to reject update data if it contains a price from a market
+    /// that closed a few days ago, since it will contain a timestamp from the last
+    /// trading period. We enforce this value ourselves against the maximum
+    /// timestamp in the provided update data.
+    uint64 public constant PAST_TIMESTAMP_MAX_VALIDITY_PERIOD = 1 hours;
+
+    /// Maximum time in the future (relative to current block timestamp)
+    /// for which a price update timestamp is considered valid
+    uint64 public constant FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD = 10 seconds;
+    /// Fixed gas overhead component used in keeper fee calculation.
+    /// This is a rough estimate of the tx overhead for a keeper to call updatePriceFeeds.
+    uint256 public constant GAS_OVERHEAD = 30000;
+
+    struct SubscriptionParams {
+        bytes32[] priceIds;
+        address[] readerWhitelist;
+        bool whitelistEnabled;
+        bool isActive;
+        bool isPermanent;
+        UpdateCriteria updateCriteria;
+    }
+
+    struct SubscriptionStatus {
+        uint256 priceLastUpdatedAt;
+        uint256 balanceInWei;
+        uint256 totalUpdates;
+        uint256 totalSpent;
+    }
+
+    struct UpdateCriteria {
+        bool updateOnHeartbeat;
+        uint32 heartbeatSeconds;
+        bool updateOnDeviation;
+        uint32 deviationThresholdBps;
+    }
+}

+ 252 - 0
target_chains/ethereum/pulse_sdk/solidity/abis/IScheduler.json

@@ -0,0 +1,252 @@
+[
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "manager",
+        "type": "address"
+      }
+    ],
+    "name": "SubscriptionCreated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionUpdated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionDeactivated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionActivated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "timestamp",
+        "type": "uint256"
+      }
+    ],
+    "name": "PricesUpdated",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          {
+            "internalType": "bytes32[]",
+            "name": "priceIds",
+            "type": "bytes32[]"
+          },
+          {
+            "internalType": "address[]",
+            "name": "readerWhitelist",
+            "type": "address[]"
+          },
+          {
+            "internalType": "bool",
+            "name": "whitelistEnabled",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isActive",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isPermanent",
+            "type": "bool"
+          },
+          {
+            "components": [
+              {
+                "internalType": "bool",
+                "name": "updateOnHeartbeat",
+                "type": "bool"
+              },
+              {
+                "internalType": "uint32",
+                "name": "heartbeatSeconds",
+                "type": "uint32"
+              },
+              {
+                "internalType": "bool",
+                "name": "updateOnDeviation",
+                "type": "bool"
+              },
+              {
+                "internalType": "uint32",
+                "name": "deviationThresholdBps",
+                "type": "uint32"
+              }
+            ],
+            "internalType": "struct SchedulerStructs.UpdateCriteria",
+            "name": "updateCriteria",
+            "type": "tuple"
+          }
+        ],
+        "internalType": "struct SchedulerStructs.SubscriptionParams",
+        "name": "subscriptionParams",
+        "type": "tuple"
+      }
+    ],
+    "name": "createSubscription",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "getSubscription",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "bytes32[]",
+            "name": "priceIds",
+            "type": "bytes32[]"
+          },
+          {
+            "internalType": "address[]",
+            "name": "readerWhitelist",
+            "type": "address[]"
+          },
+          {
+            "internalType": "bool",
+            "name": "whitelistEnabled",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isActive",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isPermanent",
+            "type": "bool"
+          },
+          {
+            "components": [
+              {
+                "internalType": "bool",
+                "name": "updateOnHeartbeat",
+                "type": "bool"
+              },
+              {
+                "internalType": "uint32",
+                "name": "heartbeatSeconds",
+                "type": "uint32"
+              },
+              {
+                "internalType": "bool",
+                "name": "updateOnDeviation",
+                "type": "bool"
+              },
+              {
+                "internalType": "uint32",
+                "name": "deviationThresholdBps",
+                "type": "uint32"
+              }
+            ],
+            "internalType": "struct SchedulerStructs.UpdateCriteria",
+            "name": "updateCriteria",
+            "type": "tuple"
+          }
+        ],
+        "internalType": "struct SchedulerStructs.SubscriptionParams",
+        "name": "params",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          {
+            "internalType": "uint256",
+            "name": "priceLastUpdatedAt",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "balanceInWei",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "totalUpdates",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "totalSpent",
+            "type": "uint256"
+          }
+        ],
+        "internalType": "struct SchedulerStructs.SubscriptionStatus",
+        "name": "status",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 165 - 0
target_chains/ethereum/pulse_sdk/solidity/abis/SchedulerErrors.json

@@ -0,0 +1,165 @@
+[
+  {
+    "inputs": [],
+    "name": "Unauthorized",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "InactiveSubscription",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "InsufficientBalance",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "CannotUpdatePermanentSubscription",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "providedPriceId",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "expectedPriceId",
+        "type": "bytes32"
+      }
+    ],
+    "name": "InvalidPriceId",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "providedLength",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "expectedLength",
+        "type": "uint256"
+      }
+    ],
+    "name": "InvalidPriceIdsLength",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "EmptyPriceIds",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "provided",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "maximum",
+        "type": "uint256"
+      }
+    ],
+    "name": "TooManyPriceIds",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "priceId",
+        "type": "bytes32"
+      }
+    ],
+    "name": "DuplicatePriceId",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "PriceSlotMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "InvalidUpdateCriteria",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "UpdateConditionsNotMet",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "providedUpdateTimestamp",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "currentTimestamp",
+        "type": "uint256"
+      }
+    ],
+    "name": "TimestampTooOld",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "providedUpdateTimestamp",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "lastUpdatedAt",
+        "type": "uint256"
+      }
+    ],
+    "name": "TimestampOlderThanLastUpdate",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "provided",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "maximum",
+        "type": "uint256"
+      }
+    ],
+    "name": "TooManyWhitelistedReaders",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "addr",
+        "type": "address"
+      }
+    ],
+    "name": "DuplicateWhitelistAddress",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "KeeperPaymentFailed",
+    "type": "error"
+  }
+]

+ 79 - 0
target_chains/ethereum/pulse_sdk/solidity/abis/SchedulerEvents.json

@@ -0,0 +1,79 @@
+[
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "manager",
+        "type": "address"
+      }
+    ],
+    "name": "SubscriptionCreated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionUpdated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionDeactivated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      }
+    ],
+    "name": "SubscriptionActivated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "subscriptionId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "timestamp",
+        "type": "uint256"
+      }
+    ],
+    "name": "PricesUpdated",
+    "type": "event"
+  }
+]

+ 67 - 0
target_chains/ethereum/pulse_sdk/solidity/abis/SchedulerStructs.json

@@ -0,0 +1,67 @@
+[
+  {
+    "inputs": [],
+    "name": "MAX_PRICE_IDS_PER_SUBSCRIPTION",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "MAX_READER_WHITELIST_SIZE",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "PAST_TIMESTAMP_MAX_VALIDITY_PERIOD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "GAS_OVERHEAD",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 37 - 0
target_chains/ethereum/pulse_sdk/solidity/package.json

@@ -0,0 +1,37 @@
+{
+  "name": "@pythnetwork/pulse-sdk-solidity",
+  "version": "1.0.0",
+  "description": "Automatically update price feeds with Pyth Pulse",
+  "type": "module",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain",
+    "directory": "target_chains/ethereum/pulse_sdk/solidity"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "scripts": {
+    "test:format": "prettier --check .",
+    "fix:format": "prettier --write .",
+    "build": "generate-abis IScheduler SchedulerEvents SchedulerErrors SchedulerStructs",
+    "test": "git diff --exit-code abis"
+  },
+  "keywords": [
+    "pyth",
+    "solidity",
+    "price feed",
+    "pulse"
+  ],
+  "author": "Douro Labs",
+  "license": "Apache-2.0",
+  "bugs": {
+    "url": "https://github.com/pyth-network/pyth-crosschain/issues"
+  },
+  "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/pulse_sdk/solidity",
+  "devDependencies": {
+    "abi_generator": "workspace:*",
+    "prettier": "catalog:",
+    "prettier-plugin-solidity": "catalog:"
+  }
+}