GIP-0046: L2 Transfer Tools

GIP: 0046
Title: L2 Transfer Tools
Authors: Pablo Carranza Vélez <>, Ariel Barmat <>, Tomás Migone <>
Created: 2023-02-13
Updated: 2023-03-02
Stage: Draft
Category: Protocol Logic, Protocol Interfaces
Depends-On: GIP-0031, GIP-0040
Audits: TODO


We present transfer tools to allow Indexers, Delegators, Subgraph Owners and Curators to move their tokens and state to the Arbitrum One network. These tools require upgrades to the Staking and GNS contracts on L1 and L2, and will allow transferring with minimal friction and low cost. Even though they’re not part of the core protocol, the case of vesting contracts is also addressed by providing special functions in the Staking contract to transfer stake and delegation for accounts that are vesting contracts.


Since GIP-0031 / GIP-0040 have been deployed, The Graph now runs the protocol on both Ethereum Mainnet (L1) and Arbitrum One (L2). Participants are free to perform actions on either chain, and as GIP-0037 is rolled out, indexing rewards in L2 will make this chain more attractive for Indexers.

Without this improvement, people that want to move their activities to L2 would have to manually unsignal / unstake / undelegate and bridge the tokens using the Arbitrum GRT Bridge. This would require several transactions, going through thawing periods and potentially paying protocol taxes. Given the benefits of the low gas on Arbitrum would affect all protocol participants, we believe it’s desirable to ease this transition by providing a way to do this with lower cost and less transactions.

Prior Art

We’ve used Livepeer as an inspiration for previous L2 GIPs; in this case they also have a relevant example in their Migration Tool.

High Level Description

This proposal includes transfer tools for subgraphs, curation, Indexer stake, and delegation. In the following sections we’ll describe the user flow when using these helpers for each case.

Subgraph transfer to L2

When a Subgraph Owner (i.e. the owner of the subgraph NFT corresponding to a subgraph ID) wants to transfer a subgraph to L2, they would have a CTA (call to action) to start this process on Explorer (and possibly Studio). This should open a transfer tool page. In this page, they can optionally set the desired address for the owner of the subgraph in L2. In case the owner is using an EOA, this can be the same address for L2 so it can be set as default. If the owner is using a contract wallet (e.g. a multisig), then a different address must be set, as most probably the contract does not exist in L2.

The Subgraph Owner confirms the first transaction, and the following will happen:

  • The subgraph will be immediately deprecated in L1
  • The curation signal owned by the Subgraph Owner will be burnt, and the resulting tokens will be sent to the Arbitrum GRT Bridge
  • Together with the tokens, the L1 GNS will encode a callhook for L2 GNS to re-create the subgraph.

The Subgraph Owner must then wait for the transaction to propagate to L2, which can take about 20 minutes. The subgraph will be created in L2 in a disabled state: like a deprecated subgraph, it will not have a subgraph deployment associated to it and curating to it will not be possible yet. The Subgraph Owner then performs a second transaction to finalize the transfer, using the wallet that was specified as the L2 owner of the subgraph. This second transaction includes the subgraph deployment ID and corresponding metadata for L2GNS to publish the subgraph.

Once this second transaction succeeds, the subgraph will be published in L2 and the owner will have an amount of signal corresponding to the amount of GRT that was sent from L1 (note this is different from the signal in L1 due to GIP-0039). No curation tax will be charged when minting this signal.

The subgraph in L2 will have a subgraph ID that is different from L1, but that can be computed deterministically from the L1 subgraph ID, similarly to how Arbitrum does address aliasing.

Curation transfer to L2

Once a Subgraph Owner transfers a subgraph to L2, the L1 subgraph will appear as deprecated, but the L1 GNS will also record that it was transferred to L2.

Curators can therefore choose whether to burn and withdraw their signal in L1 (as currently happens when a subgraph is deprecated), or to send the signal to L2. This can be shown as a CTA in Explorer as well. When sending their signal to L2, they can specify an L2 Curator address, especially necessary if the L1 Curator is a contract (e.g. a multisig). A single transaction is needed in this case, and this will trigger a bridge transaction also including a message from L1 GNS to its L2 counterpart. Once the message propagates to L2 (again ~20 minutes), the tokens corresponding to the L1 signal will be used to mint signal in the transferred L2 subgraph (using the flat curve from GIP-0039), without charging a curation tax and without the need for any further user interaction.

Indexer stake transfer to L2

Considering that indexing rewards will slowly grow in L2 while they are reduced in L1, it’s likely that Indexers will want to progressively transfer their stake to L2. The proposed transfer tool will therefore allow sending parts or all of the Indexer’s L1 stake to an L2 beneficiary.

An Indexer wishing to transfer their stake would find a CTA to do this on Explorer. This would open a pane / modal where the Indexer can specify their L2 address.

Once they confirm the transaction, this will trigger a bridge callhook just like in the other helpers, and once the ticket is executed in L2 the Indexer will be staked and ready to allocate.

Since the Indexer must be over the minimum stake in L2, and there is no way to verify if the Indexer has already staked in L2, Indexers must send at least the minimum (100k GRT) when calling this function for the first time.

If the Indexer is not sending all of their stake, the contract will also validate that at least the minimum 100k is left behind in L1, and that the remaining stake and delegation (taking delegation ratio into account) is enough to cover the open allocations. So the Indexer might have to close some allocations before going through this process - the transfer tool UI will make this evident.

If the Indexer is sending all of their stake, then they must close all their allocations before going through this process.

If the Indexer uses the helper several times, they must always specify the same L2 beneficiary.

Delegation transfer to L2

Once an Indexer has transferred at least part of their stake to L2, the Indexer’s associated Delegators should be able to also transfer their delegated GRT to the same Indexer in L2. (Note that if the Indexer transfers all of their stake, Delegators will not accrue rewards anymore until they transfer).

If the Delegator does not want to transfer to L2, they can undelegate and withdraw without waiting for the undelegating period only if the Indexer has transferred the full stake. The UI can allow doing this in a single transaction making use of the multicall functionality in the Staking contract; under the hood this will be done by:

  • Undelegating, which locks the tokens for withdrawal.
  • Calling a new unlockDelegationToTransferredIndexer function which will unlock the tokens for withdrawal if the conditions are met, and
  • Withdrawing the delegation now that it’s unlocked.

If the Indexer has only transferred part of their stake, so is still operating in L1, and the Delegator wants to undelegate, they must wait for the undelegating period as usual.

If instead the Delegator does want to transfer to L2, they can also do this through the Explorer UI, which uses a callhook like the other helpers, and also requires specifying an L2 beneficiary (in case the Delegator address does not exist in L2).

The delegation transfer tool only allows sending the full delegated amount to L2.

Special case: vesting contracts

Vesting contracts from The Graph’s token-distribution repo are in use by many protocol participants to stake or delegate. While these contracts are not part of the core protocol, and therefore work outside the GIP process, it is important to provide a way for these participants to transfer to L2 as well. As things stand, they wouldn’t be able to use the proposed stake and delegation transfer tools, as this would allow them to escape the vesting lock in L2.

The full spec for the changes needed on the vesting contracts for them to transfer to L2 is out of scope for this GIP, but we will outline the high level overview and how we can support them with minimal modifications in the Staking contract.

Vesting contracts are instances (or more precisely, proxies) of a contract called GraphTokenLockWallet. These smart contract wallets allow releasing funds as the vesting periods pass, and they also allow certain functions in the protocol to be called, using an allowlist and the fallback function. This fallback function does not forward ETH from msg.value, so the contracts can’t directly pay for the L2 retryable tickets.

For vesting contracts to transfer to L2, we propose deploying an L1GraphTokenLockTransferTool contract, that can be called by the GraphTokenLockWallet to deposit an arbitrary amount of GRT into a counterpart vesting contract in L2. This counterpart will be created through a callhook on the bridge that calls an L2GraphTokenLockManager contract on Arbitrum. This GIP therefore proposes allowing this transfer tool contract to be added to the bridge callhook allowlist (see GIP-0031 for details on how the callhooks work).

For contracts that are fully-vested (i.e. their endTime is in the past), and that haven’t previously created a counterpart L2 lock, the transfer tool will allow setting an arbitrary L2 counterpart address, so that beneficiaries can use a non-vesting-contract setup in L2.

To prevent (non-fully-vested) beneficiaries from escaping the vesting lock, the L2 vesting counterparts (L2GraphTokenLockWallet) will not allow releasing funds in L2 until the full vesting timeline is over; but beneficiaries will be able to bridge tokens back to the L1 vesting contract to release them there.

As mentioned above, vesting lock contracts do not forward ETH in their function calls, so it is necessary for the transfer tools to pull the ETH for the L2 ticket gas from somewhere else. We propose adding the ability for users to deposit the ETH into the L1GraphTokenLockTransferTool, and then the Staking contract can pull the ETH from there.

The only change needed in the core protocol, then, is for the Staking contract to allow transferring stake and delegation to L2 but restricting the L2 beneficiary to be the L2 vesting counterpart if the caller is a vesting contract, and pull the ETH from the transfer tool contract. For this to work, there will be separate functions exposed in the Staking contract (transferLockedDelegationToL2 and transferLockedStakeToL2) that will query the L1GraphTokenLockTransferTool to get the L2 vesting contract address for the caller, and perform the same transfer process described above but using this L2 address as beneficiary.

As a result, vesting contract beneficiaries that are still vesting, and want to transfer to L2 would have to:

  1. Deposit some ETH into the transfer tool contract (UI can help estimate a reasonable amount)
  2. Send some locked GRT through the transfer tool contract, to L2 to initialize the L2 vesting lock. This will also set their L2 beneficiary address.
  3. Send their stake/delegation to L2 through the “locked” transfer tool functions in the L1Staking contract.
  4. Withdraw any remaining ETH from the transfer tool contract

For those that are fully-vested, the process is similar:

  1. Deposit some ETH into the transfer tool contract (UI can help estimate a reasonable amount)
  2. Set their L2 address with a call to the transfer tool contract
  3. Send their stake/delegation to L2 through the “locked” transfer tool functions in the L1Staking contract.
  4. Withdraw any remaining ETH from the transfer tool contract

These actions can also be surfaced in the Explorer UI when connected with a vesting contract wallet.

Detailed Specification

L1GNS spec

The GNS contract on L1 will be upgraded to a new L1GNS contract, that inherits from the original GNS but adds the following external functions:

  • sendSubgraphToL2(): this function can be called by a Subgraph Owner. They must specify a subgraph ID and an address for the Subgraph Owner in L2, together with gas parameters for the L2 retryable ticket. This function will mark the subgraph as transferred and disable it. It will burn all the signal and send the GRT corresponding to the owner’s share, together with the subgraph’s information, to L2 through the L1GraphTokenGateway. The remaining GRT will be set as withdrawable so that Curators can withdraw them (or send them to L2).
  • sendCuratorBalanceToBeneficiaryOnL2(): this function can be called by Curators that have signal deposited on a subgraph that was transferred to L2. They must specify the (L1) subgraph ID and the L2 beneficiary that will own the signal on the transferred subgraph. They must also specify gas parameters for the L2 retryable ticket. The function sends the GRT corresponding to the Curator’s share of the subgraph signal to L2 through the L1GraphTokenGateway. It reduces the subgraph’s withdrawable GRT accordingly.

L2GNS spec

The GNS contract on L2 will be upgraded to a new L2GNS contract, that inherits from the original GNS but adds the following external functions and modifications:

  • onTokenTransfer(): to conform to the bridge’s callhook interface (see GIP-0031), this function can only be called by the L2GraphTokenGateway, and validates that the L1 sender is the L1GNS. It accepts two types of encoded messages, identified by a uint8 code in the ABI-encoded callhook data:
    • RECEIVE_SUBGRAPH_CODE will receive a subgraph ID and owner address and create the subgraph in a disabled state. The subgraph will not have a deployment ID or any metadata at this point, and the received GRT will not be used yet (but the amount will be recorded in storage, to be used in finishSubgraphTransferFromL1). The subgraph ID in L2 will be aliased from the L1 subgraph ID.
    • RECEIVE_CURATOR_BALANCE_CODE will receive a subgraph ID and beneficiary address, and use the received GRT to mint signal on the subgraph for the beneficiary. Signal will be minted without charging curation tax. If the subgraph transfer to L2 has not been finalized (i.e. the Subgraph Owner never called finishSubgraphTransferFromL1, or for whatever reason the subgraph was never received in L2), the tokens will instead be returned to the beneficiary.
  • finishSubgraphTransferFromL1(): this function must be called by a Subgraph Owner after the subgraph was received from L1 through onTokenTransfer. The caller must specify the subgraph deployment ID, subgraph metadata and version metadata to publish the subgraph. The tokens received in the callhook will be used to mint signal on the subgraph deployment, initializing the (flat) curation pool for the subgraph. The corresponding subgraph signal will be owned by the Subgraph Owner. Since the curve is flat, there is no problem if someone has pre-curated on the subgraph deployment directly through the Curation contract (unlike in L1, where such a scenario would revert to prevent frontrunning).
  • publishNewVersion(): this function is overridden from the original GNS implementation to allow updating to a pre-curated subgraph deployment. This reverted in the original implementation to avoid frontrunning; the flat curve implemented in GIP-0039 means we don’t need to prevent this scenario anymore.

L1Staking spec

The Staking contract on L1 will be upgraded to a new L1Staking contract, that inherits from the original base Staking but adds the following external functions:

  • transferStakeToL2(): This function takes an L2 beneficiary address and an amount, and sends this amount from the caller’s Indexer stake to be staked for the beneficiary in L2. The caller must also specify gas parameters for the L2 retryable ticket.
  • transferLockedStakeToL2(): equivalent to transferStakeToL2, but queries the L2 beneficiary from an external L1GraphTokenLockTransferTool address configured on storage; it will revert if the transfer tool returns a zero address for the beneficiary (meaning the caller is not a GraphTokenLockWallet that has transferred to L2). It will pull the ETH for the retryable ticket from the L1GraphTokenLockTransferTool using a call to pullETH(), checking that the transfer tool transferred the ETH to the Staking contract after the call.
  • transferDelegationToL2(): takes an Indexer address and an L2 beneficiary. Sends the caller’s delegation assigned to the specified Indexer through the bridge, to be delegated to the corresponding Indexer in L2. The Indexer must have previously transferred stake using one of the stake transfer tool functions. The caller must also specify gas parameters for the L2 retryable ticket.
  • transferLockedDelegationToL2(): equivalent to transferDelegationToL2, but queries the L2 beneficiary from an external L1GraphTokenLockTransferTool address configured on storage; it will revert if the transfer tool returns a zero address for the beneficiary (meaning the caller is not a GraphTokenLockWallet that has transferred to L2). It will pull the ETH for the retryable ticket from the L1GraphTokenLockTransferTool using a call to pullETH(), checking that the transfer tool transferred the ETH to the Staking contract after the call.
  • setL1GraphTokenLockTransferTool(): can only be called by the governor (i.e. the Council). Sets the address of the L1GraphTokenLockTransferTool that will be queried by transferLockedDelegationToL2 and transferLockedStakeToL2.
  • unlockDelegationToTransferredIndexer(): this function can only be called by Delegators that are delegated to an Indexer that has transferred to L2 and has no stake left in L1. The Delegator must have previously locked tokens for undelegation using undelegate (though his could have been done in the same transaction using multicall). The function will set this delegation’s tokensLockedUntil to the current epoch, so that the Delegator can withdraw the locked tokens immediately after calling this (again, potentially on the same transaction using multicall).

L2Staking spec

The Staking contract on L2 will be upgraded to a new L2Staking contract, that inherits from the original Staking but adds the following external function:

  • onTokenTransfer() : to conform to the bridge’s callhook interface (see GIP-0031), this function can only be called by the L2GraphTokenGateway, and validates that the L1 sender is the L1Staking. It accepts two types of encoded messages, identified by a uint8 code in the ABI-encoded callhook data:
    • RECEIVE_INDEXER_STAKE_CODE is used to receive an Indexer’s stake from L1. This function will stake the received tokens to the specified Indexer address.
    • RECEIVE_DELEGATION_CODE is used to receive delegation for a specific Indexer. An ABI-encoded struct contains the Indexer and Delegator’s addresses. The received tokens are delegated to the specified Indexer on behalf of the specified Delegator, without charging delegation tax.

L2Curation spec

In order to support minting signal without charging curation tax (needed by L2GNS when receiving tokens for a Subgraph Owner or Curator from L1), the Curation contract on L2 will be upgraded to a new L2Curation contract. Since this contract can be simplified (see below on the removal of the BancorFormula calls), the contract does not inherit from the original Curation but instead copies most of it, removing the unnecessary parts.

The contract adds the following external function:

  • mintTaxFree(): can only be called by the L2GNS contract. This function behaves like the mint() function, minting signal for a subgraph deployment, but skips the curation tax. As the bonding curve is flat, this function does not include slippage protection, as it is not necessary.

BancorFormula removal in GNS and L2Curation

Since GIP-0039 has flattened the bonding curve in L2, there is no need to use the BancorFormula contract in the L2 Curation contract. Therefore, as part of the implementation of the GNS transfer tool, we propose removing the calls to this contract, to simplify the L2 Curation contract and make it more efficient.

Similarly, the GNS contracts on both L1 and L2 currently include the BancorFormula calls too, even though their internal bonding curve is also flat (it was included in the original protocol deployment in case this would change in the future). Since at this point it seems extremely unlikely that the protocol would re-introduce bonding curves at the GNS level, we also propose optimizing by removing these calls.

These two changes are part of the implementation PRs for this GIP.

Bridge callhook allowlist changes

  • L1GNS must be added to the bridge callhook allowlist.
  • L1Staking must be added to the bridge callhook allowlist.
  • L1GraphTokenLockTransferTool from the token-distribution repo must be added to the bridge callhook allowlist.

Changes to base Staking contract

The Staking contract is currently very close to the 24kB contract size limit on mainnet. To be able to deploy the new L1Staking, we will need to take some actions to keep the contract size within limits:

  • Several functions, including all delegation-related functions and storage getters, are moved to a StakingExtension contract, called via delegatecall through a fallback.
  • closeAllocationMany and closeAndAllocate are removed. Users can use multicall to get the same behavior.

To minimize divergence and keep the contracts more maintainable, this is done at the base Staking contract level, so the L2Staking will include the same changes.

To support the addition of the StakingExtension contract, a new setExtensionImpl function is added for the Council to set the address of the StakingExtension.

Finally, a setCounterpartAddress() is added as well; it allows the governor (Council) to set the address of the L2 Staking contract to which to send messages (in the L1Staking case) or the L1 Staking contract from which messages are received (in the L2Staking case).

Arbitration Charter change

Transferring stake could provide a way for indexers to avoid slashing, by sending their stake to L2 after performing a slashable offense. As part of this GIP, we propose modifying the Arbitration Charter to note that L1 offenses are slashable in L2 if the indexer transfers stake after the offense and before the slashing decision.

Subgraph ID aliasing

To prevent conflicts with subgraphs created in L2, subgraph IDs from L1 will be aliased by adding a constant:

uint256 public constant SUBGRAPH_ID_ALIAS_OFFSET =

This constant will be added to the L1 subgraph ID when receiving the subgraph in L2GNS, using addition in a way that wraps around zero when overflowing (note this is Solidity 0.7 addition):

l2SubgraphId = l1SubgraphID + SUBGRAPH_ID_ALIAS_OFFSET

Additionally, to make subgraph IDs unique cross-chain going forward, after this update new IDs will use the chain ID as part of the hash input.

Backwards Compatibility

  • closeAllocationMany and closeAndAllocate are removed from the Staking contract. Users can use multicall to obtain the same behavior. This breaks compatibility with older versions of Indexer Agent, but newer versions should already use multicall to perform these actions.


  • Depends on GIP-0031 and GIP-0040 (already deployed)
  • GIP-0039 means curves in L2 are flat (already deployed)
  • Related to GIP-0037 (but not directly dependent)
  • Related to GIP-0043 (but not directly dependent)
  • The implementation for the stake and delegation helpers also implements GIP-0044 (allocating requires being over minimum stake).

Risks and Security Considerations

Adding more contracts to the bridge callhook allowlist naturally increases the bridge’s attack surface, though the bridge has been designed to minimize the associated risk. It is particularly important that callhook receivers validate that the L1 sender address is the expected one, to mitigate the impact of a compromised L1 callhook sender. See GIP-0031 for more details.

Some other relevant risks are identified in the following risk register, together with our proposed mitigation approach:

Risk register

Risk Impact Likelihood Severity Mitigation
Transfer tool callhook reverts continuosly Transferred signal / subgraph / stake / delegation is lost Low/Medium? Critical Audits should reduce likelihood of this. Monitoring to alert on reverted bridge transactions. Relevant contracts can be upgraded with a fix if needed, and we can keep the retryable tickets alive if needed while implementing the fix. As a last resort, an upgrade could add a governance function to restore any lost assets.
Contract or bridge vulnerability allows a malicious actor to transfer someone else’s assets to themselves Transferred signal / subgraph / stake / delegation is stolen Low Critical Audits should reduce likelihood of this. For some cases, a protocol pause and update should allow fixing and returning the assets to their rightful owner, as any malicious activity should be evident on chain.
Curation signal is transferred, but the subgraph was never received / published in L2 Signal tokens locked in the GNS contract. Medium High Mitigated through code: the L2GNS contract will return the tokens to the beneficiary. The UI will also not allow transferring signal until the subgraph transfer is finalized.
Delegation is received in L2, but the Indexer transfer never succeeded in L2 Delegation is locked on a non-existent Indexer until the Delegator undelegates. Medium Low Avoiding failed callhooks (see “Transfer tool callhook reverts continuosly” above) should mitigate this scenario as well. The UI should not allow transferring delegation until the Indexer transfer is complete.


  • Audits for all relevant PRs.
  • Testing the deployment plan for all contract upgrades on testnet.
  • Testing all transfer processes on testnet.

Rationale and Alternatives

The main design decision in this GIP involves how to treat GRT from some of the participants, namely Curators and Delegators. One possible approach was to send all the GRT signaled on a subgraph when the subgraph is transferred, and all the delegation for an Indexer when the Indexer’s stake is transferred. However, we believe the approach proposed here, with each participant deciding when to transfer their GRT (or whether to stay in L1) is better, as it doesn’t force anyone to move to L2 if they don’t want to. The other approach would also still require each participant to “claim” their balance in L2, so it wouldn’t save them from having to perform a manual action. Moreover, in the case of curation, moving all Curators from the L1 bonding curve to a flat one could potentially benefit some Curators at the expense of others, so we also believe the proposed approach is fairer and doesn’t introduce any economic risks for participants that they haven’t already accepted (as subgraphs can already be deprecated, and the behavior from transfer is identical to deprecation).

Copyright Waiver

Copyright and related rights waived via CC0.


Pull Request added to the GitHub repo: GIP-0046: L2 Migration Helpers by pcarranzav · Pull Request #3 · graphprotocol/graph-improvement-proposals · GitHub

1 Like

Many GraphTokenLock contracts have vested a long time ago, and their owners are free to remove their funds from them. However, many choose not to do so because these contracts are currently used for Indexer stake, Delegation, or Curation, and there is no easy way to exit funds out of the contract without disrupting indexer operation.

While I appreciate the proposed migration helper that offers a path to migrate GRT held through these contracts, the method is cumbersome. Indexers, for instance, will have to wait for the migration contract to become available to deploy to Arbitrum, because the indexer address will be the counterpart vesting lock contract. Indexers may want to deploy immediately to support the L2 migration early, but are prevented of doing so. Additionally, even if the original contract is fully vested, the contract owners will have to use an L2 Token Lock contract with all the problems it entails. (See this post for the difficulties it causes: GIP-0045: Enable beneficiary change for indexers' tokenlock smart contracts)

Therefore, I would like to suggest a second migration path for TokenLock holders that are fully vested. and are not encumbered with any restrictions (past endTime). The migration would then be identical to the standard case.


Hi @ellipfra these are good points.

I think for Indexers that are using non-fully-vested locks there’s not much more we can do without breaking the vesting contracts, but for the ones that are past the endTime, the token lock manager could actually allowlist the regular migration helpers so they can set any L2 beneficiary.

We were looking at this the other day and I think it would require changing the token lock manager for these contracts to a different one, so that only the ones that are fully vested can use the updated allowlist. But it sounds feasible.


In our case, we use a multi-sig as our indexer wallet. We run into a similar situation as mentioned in the subgraph migration that our arb indexer address will differ from the eth indexer address. We also have a company multisig set as the reward destination and then self delegate after receiving funds. My concern is we will lose a significant portion of our delegation if they attempt to migrate and it retains our existing address.

One possible solution that would be pretty elegant and could resolve @ellipfra 's issue as well as our issue would be to allow for indexers to state an migration address. Then when an indexer migrates stake, it instead applies the stake to the stated address:

  • This could help the indexer get out of a fully vested GraphTokenLock contract and instead use their preferred address.
  • When delegators go to migrate it would look up the stored migration address and delegate to that address.
  • Delegators may also need the ability to migrate delegation to a new address (as in the case of our mainnet multi-sig)

Edit: unless this is what the setCounterpartAddress() resolves. At which point my immediate concern is handled :slight_smile:


Hi @DataNexus yes that’s exactly the plan.
It’s not setCounterpartAddress, but instead the indexer specifies an l2Beneficiary parameter when calling the migration helper. Any delegator who wants to migrate would automatically have their delegation sent to the address that the indexer had specified as their beneficiary.

(And in case the delegators are contracts too, they can also specify an L2 address for themselves)


I just realized there’s a part of the vesting contract migration plan that won’t work as described here, because token lock wallets don’t forward ETH in fallback calls (and we need to send some ETH to pay for L2 gas in all the migration helpers).

We can fix it by requiring vesting contract beneficiaries to deposit some ETH into the L1GraphTokenLockMigrator before calling the helpers - I’m already implementing this and I’ll update the GIP next week to describe this in more detail.


Thank you for this proposal @Pablo. The suggested processes for each protocol participant outline a clear view on how to execute L2 migration in a user-friendly way.

I am curious if we can convey a gas cost-comparison for each participant at this point vis-a-vis a manual migration. For example, as a curator, would I anticipate total gas costs of migrating to L2 via migration helper to be higher than, less than or equal to the costs of doing it myself directly?

My question is more centered on the transaction complexity comparison between the two different approaches, knowing that there may be other associated costs of manual migration for protocol participants as described in the GIP (I.e. thawing period for delegators, incurring new curation tax for curators, etc).

1 Like

@Oliver good question, we don’t have those numbers yet but I’ll report back here after running some tests.

I would suspect gas costs won’t be hugely different and the main benefits would be the other things (UX, thawing periods and taxes) rather than gas, but we’ll see.

1 Like

Hello all,

In today’s Indexer Office Hours (IOH), we hosted a conversation on this topic. Please check out the link below if you’d like to watch the section of the recording relating to this topic:

1 Like

I’ve just edited the GIP to incorporate the changes I mentioned above: vesting lock beneficiaries would have to deposit ETH into the L1GraphTokenLockMigrator before calling any of the migration helpers. The implementation PRs already include these changes too.

1 Like

Just want to make sure:
How the process would look like for fully-vested tokenlock contracts?
The same way as for other indexers (with an ability to change the beneficiary address on Arbitrum)?
Or like for non-fully-vested tokenlock contracts (as far as I understand the beneficiary remains the same)

Can you, please, specify it in GIP itself and not in the comments section, so that would not slip away during implementation of the GIP

1 Like

@toxf1 yeah, for fully-vested locks we would ideally allow specifying any L2 beneficiary. We’re just working out the details to make sure this is feasible and doesn’t break anything else.

Today I realized calling the regular migration helpers wouldn’t work though, because the vesting contracts can’t forward ETH needed for the L2 retryable ticket (as mentioned above, which is why the vesting migration helpers include a deposit ETH feature). I think we have another solution but we need to flesh it out a bit more to make sure it works and doesn’t introduce any security issues. I’ll add it to the GIP once it’s confirmed that we can make it work.


Some quick updates on this, and I will update the GIP accordingly soon:

  • To avoid collisions, Subgraphs that are migrated to L2 won’t keep their subgraphID but will instead have it aliased (with a deterministic formula so you can compute the L2 subgraphID from the L1 subgraphID and vice-versa). This change has been implemented and has been reviewed by our auditors (OpenZeppelin). So the subgraphs and curation migration helpers will be ready for deployment after completing the testing plan on testnet (and getting approval from the Council).
  • For fully vested token locks, we’ve added a change in the L1GraphTokenLockMigrator contract that allows the vesting contract to set the L2 destination address manually. So instead of using the “regular” migration helpers, fully-vested beneficiaries will still use the “special” migration helpers, but will be able to send their stake/delegation to an arbitrary address instead of an L2 vesting lock. This will be reviewed by the auditors soon (we’re going through the audit for the stake/delegation/vesting changes now).

Two questions:

Will any delegation wallet be able to designate a different L2 wallet to be the L2 or only multisig wallets (wallets without an associated L2 address)?

And will you be able to migrate in increments (essentially send a test transaction first to make sure the L2 wallet is set up correctly?

Yes, all Delegators will have the option to set a different L2 address.

And will you be able to migrate in increments (essentially send a test transaction first to make sure the L2 wallet is set up correctly?

Not for delegators, as mentioned in the GIP: “The delegation migration helper only allows sending the full delegated amount to L2.”
So if in doubt I would recommend making sure that you can use the L2 wallet some other way, e.g. sending a small transaction from it on Arbitrum. Or if you want to test the migration itself, you can delegate a small amount from a different address, and send it to the same L2 wallet?

Rewards are already present on L2, many capable indexers are locked behind due to unavailability of migration smartcontracts. When?

1 Like

Fully agree with your concern here. The migration helpers really need to be ready before any further indexing rewards are migrated to L2.

1 Like

I agree it’s a very valid concern, and others have raised it on Discord this week.
The subgraph/curation migration helper contracts have been audited and there’s ongoing work to get the UI and other components ready.
The stake/delegation/vesting migration helpers are in the audit mitigation stage, so the contracts will be ready by end of this month, and UI work is starting next week. So we’re aiming for some time in May* for getting these on mainnet, and the upcoming GIP on increasing L2 rewards will have this as a hard requirement.

*as usual, please take the ETA with a grain of salt; there’s lots of moving pieces and it’s hard to estimate a precise date, so things could move.

1 Like

Are migrations indexer dependent? For example, could I migrate an entire delegation from one indexer, but choose not to migrate my delegation from another indexer? Or could I migrate my delegation to one indexer to L2 Wallet #1 and migrate my delegation to another indexer to L2 Wallet #2?

The reason I am asking is because I would like to delegate a small amount of GRT to a new indexer, so that I can use that indexer to do a test migration to make sure my L2 wallet is set up properly and working well.

1 Like