lido
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: 7002, Oracle --- ![image](https://hackmd.io/_uploads/H1n5Mao9A.png) # Triggerable Withdrawals [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) fixes the principal-agent problem in delegated staking—where stakers must trust validator node operators to pre-sign exit messages, or honor future exit requests—by introducing a new voluntary exit operation that can be triggered with a validator’s withdrawal credential. This empowers stakers to withdraw staked ETH without relying on the entity holding the validator’s signing key (i.e., the staking service in a delegated staking setup) to process withdrawals. ## **Table of Content** [ToC] # General ## Motivation There are four main reasons Lido protocol should support Triggerable Withdrawals (TW): - Minimize trusted entities (Node Operators voluntary validator exits); - Unlock "truly" permissionless Staking Modules. A malicious node operator can't take the ETH hostage; - Fallback safety option for all node operator that lost their validator's secret key; - Unlock DualGov by allowing stETH holders to withdraw their funds permissionlessly; ## Abstractly about eip-7002 and eip-7685 [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) describes general framework for storing contract-triggered requests. When developing a Withdrawal Credentials contract, it is important to consider that other types of requests may appear in the future (like consolidation in [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251)). [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) key feature is the introduction of a stateful validator exit precompile that maintains a queue of validator exit messages originating from the execution layer. Exit messages are regular Ethereum transactions with the validator precompile address as the target and indicate intent to exit a validator (identified by its public key). A validator exit message is valid if (a) it is signed by the Ethereum address referenced in the validator’s execution-layer (0x01) withdrawal credential (b) the validator to be exited is active on the Beacon Chain. These checks are executed by the consensus layer after the exit message makes its way to the Beacon Chain; the validator exit precompile only confirms if an exit transaction pays enough fee at the time the exit precompile is called by a withdrawal credential address. ## Lido specific requirements ### ⚖ DualGov Dual Governance allows stETH holders, in the worst case, to delay any proposed changes to the Lido protocol until they have fully exited (withdrawn their ETH from the protocol). Currently, the withdrawal process depends on the AO report (finalizing withdrawal requests), VEBO (requesting to exit a sufficient number of validators), and NOs (sending the exit message). By making the triggerable flow permissionless, we can eliminate the influence of VEBO and NOs, making withdrawals more secure and trustless. #### Summary - Reduce trust assumptions on Oracles and NOs; - The protocol should allow Dual Governance to generate exit events in VEBO, enabling permissionless ejection from the EL side; [DualGov overview.](https://blog.lido.fi/dual-governance-overview/) ### 🌍 CSM (and other permissionless Staking Modules) CSM is a permissionless module that compensates for reduced trust assumptions in delegated staking via bonds. There are several cases when CSM might require usage of the EL triggerable exits: - To trigger exits for validators that were requested to exit by VEBO but were not exited in time; - To trigger exits for validators demonstrating unacceptable performance for a long period of time; - To allow CSM Node Operators to trigger exits for their own validators in a case when validators' private keys were lost; #### Summary - The protocol should allow CSM to request exits for any validator in the staking module; - The protocol should manage exit triggering for validators that were not exited voluntarily after the request. - The protocol component responsible for exit triggering should inform CSM about triggered exits. [CSM V2 EIP-7002 expectations](https://hackmd.io/@lido/HJrMPHUt0#EIP-7002-support) ### 👔 DAO Gov Lido governance should be able to do anything, unless it doesn't break DualGov assumptions. #### Summury - The protocol should allow the Lido DAO Governance to eject any validators from the protocol, considering rational limits; # 🧰 Technical part To understand the following section, it is recommended to familiarize yourself with the operation of VEBO Oracles, particularly the process of submitting and unpacking hashes ([Oracle phases](https://docs.lido.fi/guides/oracle-operator-manual/#oracle-phases)). Globally, the scope of changes will be as follows: - New trusted entities will be able to submit reports to VEB and thereby emit exit events. - Reports from these entities will be processed within the VEBO Oracle frame and will be limited by current sanity checks. Oracles reporting at the end of the frame will only be able to use the remaining frame's exit quota ([exit limits](https://docs.lido.fi/guides/oracle-spec/validator-exit-bus/#report-limits)). - TW in the protocol will allow anyone to provide proof of a validator's exit request and trigger the exit through the EL using the TW precompiled contract. - Anyone can prepare proof and deliver validator's state from CL to the VEB to notify Staking Modules about exit status. - The Accounting Oracle will no longer be responsible for reporting stuck keys ([Accounting extra data details](https://docs.lido.fi/contracts/accounting-oracle/#extra-data)). This responsibility will now be handled by a dedicated bot that uses permissionless methods in VEB to deliver proofs that the validator was delinquent. - Update Staking Module interface and implement bonds in Curated Modules to correctly TW actions. ## Affected protocol parts The following parts will be affected: - Onchain - [Withdrawal Vault Contract](https://docs.lido.fi/contracts/withdrawal-vault) - [AO contract](https://docs.lido.fi/contracts/accounting-oracle) - [VEBO Contract](https://docs.lido.fi/contracts/validators-exit-bus-oracle) - [CSM contracts](https://docs.lido.fi/staking-modules/csm/intro) - [Staking Router](https://docs.lido.fi/contracts/staking-router) - [Curated registry contracts](https://docs.lido.fi/contracts/node-operators-registry) - Off-chain - [Oracles](https://docs.lido.fi/guides/oracle-spec/validator-exit-bus) - [Ejector](https://docs.lido.fi/guides/validator-ejector-guide) - (New) Trigger Withdrawals tool - (New) Delayed keys report tool - Specs - [Penalties spec](https://docs.lido.fi/guides/oracle-spec/penalties) ### New concepts **VEB** - Validator Exit Bus Contract. Contains the following logic: - Submitting and storing report hashes; - Emitting validator exit events; - Triggering validator exits on the EL through the WC contract; - Reporting delinquent keys; **VEBO** - Validator Exit Bus Oracle Contract. Contains next logic: - Storing frame's report hash. - Contains Oracles specific submit and unpack report hashes. **Delinqued key/validator** - a key/validator that was unable to exit on time. Delinquent keys do not lose their status even if they are eventually exited afterward. ## VEB specification ### Emiting exit events Events emiting in VEBO takes two steps: - First phase - **Submit Report Hash** Trusted entities build a report, calculate the report hash, and send it (e.g., Lido Oracles, Dual Governance, Governance, Staking Modules). Report hashes are saved to the VEB. - Second phase - **Deliver Report Data** Submit report data and emit exit events, taking exit limits into account. ![Triggerable Withdrawals (1)](https://hackmd.io/_uploads/BJcAZ03VJx.jpg) ## First Phase - Submit report hash The first phase involves submitting and saving the hash in the contract. ### Lido Oracles The first phase of the Oracle process won't change. Oracles deliver report hashes to the Hash Consensus contract. When a quorum is reached, the report hash is delivered to VEBO via `submitReport` method. The report hash from Oracles can only be used to emit events by the Oracles themselves. ```solidity= function submitReport(uint256 slot, bytes32 report, uint256 consensusVersion) external; ``` ### Trusted entities Trusted entities submit a report hash to the VEB contract via the `submitReportHash` method. This hash is instantly saved to the VEB (in the `reportHashes` mapping) and can be used by anyone to deliver report data during the second phase. ```solidity= function submitReportHash(bytes32 reportHash) external; ``` `reportHashes` mapping will have next signature: ```solidity= struct DeliverHistory { // Block number of deliver transaction uint32 blockNumber; // Last index of the key for which the exit event was emitted uint32 lastDeliveredKeyIndex; }; struct ReportStatus { // Total items count in report (on creation 0) uint32 totalItemsCount; // Total processed items in report (by default 0) uint32 deliveredItemsCount; // Contract version on the moment of creation uint32 contractVersion; // Data delivery history DeliveryHistory[] deliverHistory; }; mapping (bytes32 => ReportStatus) public reportHashes; ``` ### Invalidating report hashes The `ReportStatus` structure includes a contractVersion field, indicating the contract version at the time the hash was submitted. This field is required to prevent the delivery of report data if the report hash was submitted under a previous contract version, thereby avoiding unpredictable behavior in the VEB. ### Schema ![Triggerable Withdrawals (2)](https://hackmd.io/_uploads/Hk7tMC3VJe.jpg) ## Second Phase - Deliver report data and Emit exit events ### Oracles flow ```solidity= // contract VEBO function submitReportData(ReportData calldata data, uint256 contractVersion) ``` The Oracle unpacks the full report and emits events for all items in the report. Oracle exit limits are calculated dynamically and depend on the number of events emitted in the reference frame ([details](#Exit-limits)). During this phase, the `ReportStatus` record is added to the `reportHashes` mapping. ![Triggerable Withdrawals (3)](https://hackmd.io/_uploads/Sk-QB0nVke.jpg) ### Trustless flow ```solidity= function emitExitEvents(ReportData calldata data, uint256 contractVersion) ``` Anyone can emit exit events by providing report data, the hash of which is stored in the VEB. If `reportsHashes.totalItemsCount` is `0` update and save the report length. Starting from `deliveredItemsCount` index, emit events for keys from the report. Once all keys are processed or the number of exits in the current frame reaches the exit limits from the `OracleSanityCheck` contract, stop emitting events and save `last proceed key index + 1` into `deliveredItemsCount`. Assumptions: - An actor cannot deliver specific keys; all events are emitted for keys one by one in the order specified in the report data; - An actor cannot choose how many keys to emit events for; exit events are emitted up to the exit limit or until the key list ends; - An actor cannot emit events for a report hash that was submitted with an outdated `contractVersion`; ![image](https://hackmd.io/_uploads/Hy4EjC2V1g.png) ### Exit limits For security reasons, the Lido protocol cannot create more exit requests in a single frame than specified in the `OracleReportSanityChecker` contract. This means that keys presented in the report but lacking an exit event should not be used as indicators to exit a validator and cannot be used to exit it via a TW. Each frame has an exit quota that cannot be exceeded. Therefore, the sum of emitted exit events in a frame and the Oracle's report for that frame must not exceed this quota. The Offchain Oracle's report is considered the final report for the frame it is based on, even if the transaction is included in the next frame. #### Examples For next examples assume that: - A report frame consists of 4 epochs; - The exit limit for one frame is 10 validators; In the next example, Frame 2 contains two deliver report transactions: - 5 exit events were emitted for the `report #1`, so #1 was fully delivered (5 exit quota left); - 5 exit events were emitted for the `report #2`, it were partially delivered, because was limited by exit quota (0 exit quota left); - The Oracle report will contain no validators, as the entire exit quota was used; ![Triggerable Withdrawals (18)](https://hackmd.io/_uploads/rJnMOmRI1g.jpg) In `Frame 3`, exit events were emitted for all remaining keys from `report #2`. The Oracle can report up to 5 keys for exit. ![Triggerable Withdrawals (14)](https://hackmd.io/_uploads/S1__DXC81l.jpg) ## Validator's exit flow The main operations involving validators are as follows: ![image](https://hackmd.io/_uploads/S1y2kjnNyx.png) Once a validator is marked for exit in the VEB, the Node Operator (NO) should send an exit message on the CL. Simultaneously, anyone can trigger the withdrawal on the EL. Anyone can provide proof that a validator did not exit on time, and this information will be relayed to the Staking Module. All further decisions regarding penalties are made by the Staking Module. ### Classic CL exit Exits using voluntary messages on the CL remain our primary method for withdrawing validators. NO hosts [validator ejector](https://docs.lido.fi/guides/validator-ejector-guide) that sending exit message as soon as it sees event in VEB contract. ### Triggerable exits in VEB The following function verifies that the hash of the provided report data exists in the mapping and that events for the keys have been emitted. It then triggers exits on the EL via the Withdrawal Vault contract. ```solidity= function triggerExits(ReportData calldata data, uint32[] keyIndexes,) ``` This function contains next steps: - Verifies that the provided data hash exists in the `reportHash` mapping; - Confirms that events were emitted for the provided key indexes beforehand; - Sends a request to the Withdrawal Vault contract to trigger exits for the provided keys; - Sends notification to Staking Router contract with keys data that were triggered to exit. ![Triggerable Withdrawals (5)](https://hackmd.io/_uploads/Hkn82A24yx.jpg) ## Delinquent keys report Delinquent keys are keys for which validators did not start exiting before the end of the voluntary exit period. Delinquent keys can be permissionlessly reported to the VEB contract with corresponding proof that the required time has passed. Those keys should be penalized by Staking Modules. Our current [penalties concept](https://docs.lido.fi/guides/oracle-spec/penalties/) is specific for permissionfull staking modules. Anyone can deliver proof that validator was delayed: ```solidity= function submitProofOfDelinquent(ReportData calldata data, Witness[] witness, BeaconBlock beconBlock) external; ``` This method will support batch proof delivery to optimize gas costs. ![Triggerable Withdrawals (6)](https://hackmd.io/_uploads/Hk1th03Nyg.jpg) ### Delinquent hook to module After receiving and verifying the proof, the VEB notifies the Staking Router about the delinquent validators. This data is then provided to the Staking Modules. Essentially, the module notification consists of the validator's identifier within the Lido protocol, the time when its exit was requested, and the time it remained active. Staking modules use the time difference between the exit event and the timestamp when the validator was not in an exiting status to determine whether the node operator should be penalized. Staking Modules should take into account that some validators could be not able to exit because of CL restrictions. ```solidity= struct DelinquentStatus { // Validators indetification uint32 stakingModuleId; uint32 nodeOperatorId; uint32 keyIndex; // Timestamp when validator was activated uint32 activatedTs; // Timestamp when exit event was emited uint32 exitedTs; // Timestamp of block on which validator were still active uint32 activeTs; } ``` ## Direct Triggerable Exit Trusted entities will be able to directly emit exit events and request validators through the TW to exit them without delivering hashes and any proving. This type of TW should also comply with the sanity report limits. ```solidity= function triggerExitsDirectly(ValidatorsData calldata data) external; ``` The flow is as follows: - Verify that the provided validator count fits within the limits; - Provide the validator list to the Withdrawal Vault TW method; ![Triggerable Withdrawals (7)](https://hackmd.io/_uploads/B1IClJaVye.jpg) ## Offchain tooling ### Lido Oracles ### AO Since stuck keys are deprecated, all functionality related to this feature should be removed from AO. ### VEBO - Remove the delayed predicate; - Remove the weight predicate; - Restructure VEBO limits so that all entities share common limits for a frame; - Remove the last NO exited index; Oralce will fetch exit events only for the last 28 days to track which validators have already been requested for exit. If this period has already passed, it is possible that the same validator might be requested to exit again. However, this scenario is unlikely because in case of stuck validators they will be exited using TW. ### Delinquent Deliver bot / CLI A bot that will generate proof that a validator was delinquent and deliver it to the protocol. An analog of the [CSM Prover tool](https://github.com/lidofinance/csm-prover-tool). ### Exit Trigger bot The Exit Trigger bot should be accessible for anyone to use to exit validators. It should feature a straightforward interface that allows for flexible exiting of any validators. Goals: - Develop a trigger exit policy; - Create a bot capable of delivering WE proofs with various fee strategies; - Make this an open-source project and include a CLI within the bot to facilitate the creation of selected Triggerable Withdrawal requests; ### Ejector allowlist #### Purpose of protection Generally, what is this protection for: If a Node Operator (NO) relies on third-party providers for the ejector, these providers could potentially return falsified data, such as fake events. To address this security risk, the ejector verifies that the signatures associated with these events were created using Oracle accounts. For example, if a malicious provider generates 10 fake events, they will be ignored by the ejector because they would be signed with a non-Oracle private key. #### After TW implementation After TE, emitting exit events can be performed by anyone since it is permissionless, and transaction signature verification cannot be done. To mitigate this security issue, all Node Operators (NOs) should run their ejectors on their own local nodes. # Useful links - [Spec from stETH team](https://hackmd.io/btui1496SqaAxu7lL7Zs7A?view) - [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) - [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) - [Fist draft of working group goals](https://hackmd.io/i_6EYi7sSf-hB_aGQ3_izw?both) - [Summary requirements from CSM](https://hackmd.io/-2_u2peQRuiumZHE_B0vhA?view#Summary) - [Miro board](https://miro.com/app/board/uXjVMo0ffqM=/) - [TE KRs](https://www.notion.so/The-MVP-version-of-on-chain-code-of-the-Priority-Exit-Bus-and-Triggerable-Exits-implementation-is-re-13cab345f67e4a3697220708e2a82663?pvs=4) - [EIPs For Nerds #2: EIP-7002](https://ethereum2077.substack.com/p/eip-7002-execution-layer-exits) - [TW timelines](https://miro.com/app/board/uXjVLIpBo8o=/) - [IStakingModule interface](/TVzBJS38RT-mC8uwJePqew) - [ADR: Curated Module Implementation](/WH_60K4RQIaUZz-BwXMIWg) - [ADR: Withdrawal Credentials Contract](https://hackmd.io/@lido/ryTE7SPXJl) <!-- ### Replace stuck keys with new delayed mechanism The new solution completely removes functionality related to stuck keys. As a result: - The Accounting Oracle will no longer report stuck keys in Phase 3. - Methods related to stuck keys will be marked as deprecated. - The staking module interface will be updated to support integration with the new Validator Exit Bus contract. -->

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully