From d51afbb48abe42056f8ee6096cde15a6175fe6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Mon, 4 May 2026 18:47:49 -0700 Subject: [PATCH 1/5] docs: add 'Register via DAO governance' article for generic-custom gateway Documents the standardized token registration template for cases where the parent chain token can't self-register (non-upgradeable, missing ICustomToken). Covers the RegisterAndSetArbCustomGatewayAction contract, the calldata generator script, Tally proposal submission, and the post-execution sequence. Cross-links from setup-generic-custom-gateway.mdx (Step 1 prerequisite mention and FAQ entry). New sidebar entry under Configure token bridging. Sources: - https://github.com/OffchainLabs/governance/blob/main/src/gov-action-contracts/token-bridge/RegisterAndSetArbCustomGatewayAction.sol - https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764 - https://gist.github.com/gzeoneth/439dedc7bdd971345657a04c266daf00 --- .../register-via-dao-governance.mdx | 177 ++++++++++++++++++ .../setup-generic-custom-gateway.mdx | 6 +- sidebars.js | 5 + 3 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx new file mode 100644 index 0000000000..6340cf74b9 --- /dev/null +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx @@ -0,0 +1,177 @@ +--- +title: 'Register a custom gateway token via Arbitrum DAO governance' +sidebar_label: 'Register via DAO governance' +description: "Guide to registering a token in Arbitrum's generic-custom gateway when self-service registration isn't possible. Uses the standardized RegisterAndSetArbCustomGatewayAction template and a DAO proposal." +user_story: 'As a token issuer whose parent chain token cannot self-register with the generic-custom gateway, I want to register the token through an Arbitrum DAO governance proposal.' +content_type: how-to +displayed_sidebar: buildAppsSidebar +--- + +[Self-service registration](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) requires the parent chain token to implement [`ICustomToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) and call `registerCustomL2Token` and `setGateway` itself. When the parent chain token is non-upgradeable, immutable, or otherwise can't make those calls, registration must go through Arbitrum DAO governance using the privileged `forceRegisterTokenToL2` and `setGateways` paths. + +Offchain Labs publishes a standardized calldata template — `RegisterAndSetArbCustomGatewayAction` — together with a payload generator script. Using the template is a [helpful utility, not a requirement](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764), but it reduces manual debugging and gives reviewers a known-safe shape to verify against. + +## When to use this path + +Use this article if: + +- Your parent chain token is already deployed and **can't be upgraded** to add the `ICustomToken` methods. +- You'd rather not [wrap the token](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx#step-1-review-the-prerequisites) and register the wrapper instead. +- A child chain counterpart implementing [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) is already deployed. + +If your parent chain token is upgradeable or you're building it from scratch, follow the [self-service generic-custom gateway setup](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) instead — it's faster and doesn't require a governance vote. + +## How the action contract works + +`RegisterAndSetArbCustomGatewayAction` is a one-shot action contract executed by the DAO's `UpgradeExecutor` via the L1 timelock. It performs the two registration calls in a single privileged transaction: + +```solidity +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +contract RegisterAndSetArbCustomGatewayAction { + IL1AddressRegistry public immutable addressRegistry; + + function perform( + address[] memory _l1Tokens, + address[] memory _l2Tokens, + uint256 _maxGasForRegister, + uint256 _gasPriceBidForRegister, + uint256 _maxSubmissionCostForRegister, + uint256 _maxGasForSetGateway, + uint256 _gasPriceBidForSetGateway, + uint256 _maxSubmissionCostForSetGateway + ) external payable { + // 1. forceRegisterTokenToL2 on the L1 generic-custom gateway + // 2. setGateways on the L1 gateway router (pointing _l1Tokens at the generic-custom gateway) + } +} +``` + +Source: [`RegisterAndSetArbCustomGatewayAction.sol`](https://github.com/OffchainLabs/governance/blob/main/src/gov-action-contracts/token-bridge/RegisterAndSetArbCustomGatewayAction.sol). + +The two calls each emit a retryable ticket from the parent chain to the child chain. Both retryables are auto-redeemed when the action contract supplies enough submission cost, after which the token is fully registered on both chains. + +## Prerequisites + +Before generating a proposal, make sure: + +- The parent chain ERC-20 token is **already deployed**. +- The child chain token contract implementing [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) is **already deployed**. +- Both addresses are final and immutable — once registered, the mapping cannot be changed. +- You have [Foundry](https://book.getfoundry.sh/getting-started/installation) installed locally (the payload generator uses `cast`). + +## Step 1: Generate the proposal calldata + +Save the following script as `reg-arb-custom.sh`. Set `L1_TOKEN_ADDRESS` and `L2_TOKEN_ADDRESS` to your token's parent and child chain addresses. Leave the other constants alone — they reference live governance contracts on Ethereum and the canonical action address. + +```shell +#!/usr/bin/env bash +set -euo pipefail + +# Token addresses (modify these) +L1_TOKEN_ADDRESS="0x000000000000000000000000000000000000dead" +L2_TOKEN_ADDRESS="0x000000000000000000000000000000000000dead" + +# Governance constants (do not modify) +readonly L1_ACTION_ADDRESS="0x997668Ee3C575dC060F80B06db0a8B04C9558969" +readonly L1_UPGRADE_EXECUTOR="0x3ffFbAdAF827559da092217e474760E2b2c3CeDd" +readonly L1_TIMELOCK="0xE6841D92B0C345144506576eC13ECf5103aC7f49" +readonly MAX_SUBMISSION_FEE="0.0005" +readonly TOTAL_VALUE="0.001" +readonly DELAY_SECONDS=259200 + +L1CALL=$(cast calldata \ + "perform(address[],address[],uint256,uint256,uint256,uint256,uint256,uint256)" \ + "[$L1_TOKEN_ADDRESS]" \ + "[$L2_TOKEN_ADDRESS]" \ + 0 \ + 0 \ + "$(cast to-wei "$MAX_SUBMISSION_FEE")" \ + 0 \ + 0 \ + "$(cast to-wei "$MAX_SUBMISSION_FEE")") +L1CALLVALUE=$(cast to-wei "$TOTAL_VALUE") +L2CALL=$(cast calldata \ + "execute(address,bytes)" \ + "$L1_ACTION_ADDRESS" \ + "$L1CALL") +PREDECESSOR=$(cast to-bytes32 0x00) +SALT=$(cast keccak \ + "$(cast abi-encode \ + "a(uint256[],address[])" \ + "[1]" \ + "[$L1_ACTION_ADDRESS]")") +FINAL_CALLDATA=$(cast calldata \ + "scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256)" \ + "[$L1_UPGRADE_EXECUTOR]" \ + "[$L1CALLVALUE]" \ + "[$L2CALL]" \ + "$PREDECESSOR" \ + "$SALT" \ + "$DELAY_SECONDS") + +echo "===== Proposal =====" +echo "Target Contract: 0x0000000000000000000000000000000000000064" +echo "Value: 0" +echo "arbSysSendTxToL1Args.l1Timelock: " $L1_TIMELOCK +echo "arbSysSendTxToL1Args.calldata:" +echo "$FINAL_CALLDATA" +``` + +Run it: + +```shell +chmod +x reg-arb-custom.sh +./reg-arb-custom.sh +``` + +The script prints the four values you'll paste into the Tally proposal: the target contract (`ArbSys` precompile at `0x...0064`), the value (always `0`), the L1 Timelock address, and the encoded calldata that schedules the batched call on the L1 timelock. + +For a fully worked example, see the [BORING token registration payload gist](https://gist.github.com/hajnalben/b12cfae78ec88259be8c396c25bab1c2). + +## Step 2: Submit the Tally proposal + +Open a new proposal in [Tally](https://www.tally.xyz/gov/arbitrum) targeting the Arbitrum Core governance contract: + +- **Target contract:** `0x0000000000000000000000000000000000000064` (the `ArbSys` precompile) +- **Value:** `0` +- **Function:** `sendTxToL1(address destination, bytes data)` + - `destination`: the L1 Timelock address printed by the script + - `data`: the calldata printed by the script + +Before publishing, post a draft on the [Arbitrum Foundation forum](https://forum.arbitrum.foundation/) for community review. Token registrations have historically passed without controversy, but the off-chain feedback step catches encoding mistakes. + +## Step 3: After the proposal passes + +Once the proposal succeeds, the execution sequence is: + +1. The Arbitrum Core governance contract calls `ArbSys.sendTxToL1`, queuing a message from the child chain to the parent chain. +2. After the standard withdrawal delay, the L1 outbox executes the message, calling `scheduleBatch` on the L1 Timelock. +3. After the timelock's `DELAY_SECONDS` (3 days) elapses, anyone can call `executeBatch`, which has the `UpgradeExecutor` invoke `RegisterAndSetArbCustomGatewayAction.perform`. +4. The action contract calls `forceRegisterTokenToL2` on the parent chain generic-custom gateway and `setGateways` on the parent chain gateway router. Each call sends a retryable ticket to the child chain. +5. Both retryables auto-redeem (they're funded by the `MAX_SUBMISSION_FEE` constants), updating the L2 mappings. + +When all retryables are redeemed, the token is registered. Deposits and withdrawals through the generic-custom gateway will work normally from that point on. + +## Frequently asked questions + +### What if I get the L2 address wrong in the proposal? + +Registration is one-time and irreversible per (parent token) address — `forceRegisterTokenToL2` reverts on a second attempt. If a bad mapping is registered, recovery requires a second governance proposal with a new action contract that reregisters via different methods. Validate addresses carefully before submitting. + +### Why does the proposal target the `ArbSys` precompile (`0x...0064`)? + +Arbitrum DAO proposals execute on the child chain, but the registration calls happen on the parent chain. `ArbSys.sendTxToL1` is the precompile that creates outbound messages from the child chain to the parent chain. The proposal's child chain transaction queues the parent chain call; the L1 Timelock + UpgradeExecutor then dispatch it. + +### Is the standardized template mandatory? + +No. Per the [Foundation announcement](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764), the template is a helpful utility, not a requirement. Custom proposals that achieve the same registration result are valid, but reviewers and the Foundation will scrutinize them more closely. + +## Resources + +- [Forum announcement: Standardized Token Registrations Template](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764) +- [`RegisterAndSetArbCustomGatewayAction.sol`](https://github.com/OffchainLabs/governance/blob/main/src/gov-action-contracts/token-bridge/RegisterAndSetArbCustomGatewayAction.sol) (action contract source) +- [Payload generator gist](https://gist.github.com/gzeoneth/439dedc7bdd971345657a04c266daf00) (script used in Step 1) +- [Self-service generic-custom gateway setup](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) +- [Token bridging concept](/how-arbitrum-works/deep-dives/token-bridging.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx index b64e01eb81..57a23d0b2e 100644 --- a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx @@ -23,8 +23,8 @@ First of all, the **parent chain counterpart of the token** must conform to the These methods are needed to register the token via the gateway contract. If your parent chain contract does not include these methods and it is not upgradeable, you could register in one of these ways: -- As a chain owner, register via an [Arbitrum DAO](https://forum.arbitrum.foundation/) proposal. -- By wrapping your parent chain token and registering the wrapped version of your token. +- Submit an [Arbitrum DAO proposal using the standardized token registrations template](/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx). +- Wrap your parent chain token and register the wrapped version of your token. Note that registration is a one-time event. @@ -368,7 +368,7 @@ No, you can only register once a child chain token for the same parent chain tok ### What can I do if my parent chain token is not upgradable? -As mentioned on the concept page, token registration can also be completed as a chain owner registration via an **[Arbitrum DAO](https://forum.arbitrum.foundation/)** proposal. +Token registration can be completed through an Arbitrum DAO proposal using the standardized template. See [Register a custom gateway token via Arbitrum DAO governance](/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx) for the full process. ### Can I set up the generic-custom gateway after a standard **ERC-20** token has been deployed on the child chain? diff --git a/sidebars.js b/sidebars.js index e9a2e3c460..f4124807fc 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1337,6 +1337,11 @@ const sidebars = { label: 'Set up standard gateway', id: 'build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway', }, + { + type: 'doc', + label: 'Register via DAO governance', + id: 'build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance', + }, ], }, ], From 89136b428d8bfdb39f415e579ed3307ce4495755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Tue, 12 May 2026 20:43:14 -0700 Subject: [PATCH 2/5] remove duplicate content, this belongs in the AF repository --- .../register-via-dao-governance.mdx | 177 ------------------ 1 file changed, 177 deletions(-) delete mode 100644 docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx deleted file mode 100644 index 6340cf74b9..0000000000 --- a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: 'Register a custom gateway token via Arbitrum DAO governance' -sidebar_label: 'Register via DAO governance' -description: "Guide to registering a token in Arbitrum's generic-custom gateway when self-service registration isn't possible. Uses the standardized RegisterAndSetArbCustomGatewayAction template and a DAO proposal." -user_story: 'As a token issuer whose parent chain token cannot self-register with the generic-custom gateway, I want to register the token through an Arbitrum DAO governance proposal.' -content_type: how-to -displayed_sidebar: buildAppsSidebar ---- - -[Self-service registration](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) requires the parent chain token to implement [`ICustomToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) and call `registerCustomL2Token` and `setGateway` itself. When the parent chain token is non-upgradeable, immutable, or otherwise can't make those calls, registration must go through Arbitrum DAO governance using the privileged `forceRegisterTokenToL2` and `setGateways` paths. - -Offchain Labs publishes a standardized calldata template — `RegisterAndSetArbCustomGatewayAction` — together with a payload generator script. Using the template is a [helpful utility, not a requirement](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764), but it reduces manual debugging and gives reviewers a known-safe shape to verify against. - -## When to use this path - -Use this article if: - -- Your parent chain token is already deployed and **can't be upgraded** to add the `ICustomToken` methods. -- You'd rather not [wrap the token](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx#step-1-review-the-prerequisites) and register the wrapper instead. -- A child chain counterpart implementing [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) is already deployed. - -If your parent chain token is upgradeable or you're building it from scratch, follow the [self-service generic-custom gateway setup](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) instead — it's faster and doesn't require a governance vote. - -## How the action contract works - -`RegisterAndSetArbCustomGatewayAction` is a one-shot action contract executed by the DAO's `UpgradeExecutor` via the L1 timelock. It performs the two registration calls in a single privileged transaction: - -```solidity -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.16; - -contract RegisterAndSetArbCustomGatewayAction { - IL1AddressRegistry public immutable addressRegistry; - - function perform( - address[] memory _l1Tokens, - address[] memory _l2Tokens, - uint256 _maxGasForRegister, - uint256 _gasPriceBidForRegister, - uint256 _maxSubmissionCostForRegister, - uint256 _maxGasForSetGateway, - uint256 _gasPriceBidForSetGateway, - uint256 _maxSubmissionCostForSetGateway - ) external payable { - // 1. forceRegisterTokenToL2 on the L1 generic-custom gateway - // 2. setGateways on the L1 gateway router (pointing _l1Tokens at the generic-custom gateway) - } -} -``` - -Source: [`RegisterAndSetArbCustomGatewayAction.sol`](https://github.com/OffchainLabs/governance/blob/main/src/gov-action-contracts/token-bridge/RegisterAndSetArbCustomGatewayAction.sol). - -The two calls each emit a retryable ticket from the parent chain to the child chain. Both retryables are auto-redeemed when the action contract supplies enough submission cost, after which the token is fully registered on both chains. - -## Prerequisites - -Before generating a proposal, make sure: - -- The parent chain ERC-20 token is **already deployed**. -- The child chain token contract implementing [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) is **already deployed**. -- Both addresses are final and immutable — once registered, the mapping cannot be changed. -- You have [Foundry](https://book.getfoundry.sh/getting-started/installation) installed locally (the payload generator uses `cast`). - -## Step 1: Generate the proposal calldata - -Save the following script as `reg-arb-custom.sh`. Set `L1_TOKEN_ADDRESS` and `L2_TOKEN_ADDRESS` to your token's parent and child chain addresses. Leave the other constants alone — they reference live governance contracts on Ethereum and the canonical action address. - -```shell -#!/usr/bin/env bash -set -euo pipefail - -# Token addresses (modify these) -L1_TOKEN_ADDRESS="0x000000000000000000000000000000000000dead" -L2_TOKEN_ADDRESS="0x000000000000000000000000000000000000dead" - -# Governance constants (do not modify) -readonly L1_ACTION_ADDRESS="0x997668Ee3C575dC060F80B06db0a8B04C9558969" -readonly L1_UPGRADE_EXECUTOR="0x3ffFbAdAF827559da092217e474760E2b2c3CeDd" -readonly L1_TIMELOCK="0xE6841D92B0C345144506576eC13ECf5103aC7f49" -readonly MAX_SUBMISSION_FEE="0.0005" -readonly TOTAL_VALUE="0.001" -readonly DELAY_SECONDS=259200 - -L1CALL=$(cast calldata \ - "perform(address[],address[],uint256,uint256,uint256,uint256,uint256,uint256)" \ - "[$L1_TOKEN_ADDRESS]" \ - "[$L2_TOKEN_ADDRESS]" \ - 0 \ - 0 \ - "$(cast to-wei "$MAX_SUBMISSION_FEE")" \ - 0 \ - 0 \ - "$(cast to-wei "$MAX_SUBMISSION_FEE")") -L1CALLVALUE=$(cast to-wei "$TOTAL_VALUE") -L2CALL=$(cast calldata \ - "execute(address,bytes)" \ - "$L1_ACTION_ADDRESS" \ - "$L1CALL") -PREDECESSOR=$(cast to-bytes32 0x00) -SALT=$(cast keccak \ - "$(cast abi-encode \ - "a(uint256[],address[])" \ - "[1]" \ - "[$L1_ACTION_ADDRESS]")") -FINAL_CALLDATA=$(cast calldata \ - "scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256)" \ - "[$L1_UPGRADE_EXECUTOR]" \ - "[$L1CALLVALUE]" \ - "[$L2CALL]" \ - "$PREDECESSOR" \ - "$SALT" \ - "$DELAY_SECONDS") - -echo "===== Proposal =====" -echo "Target Contract: 0x0000000000000000000000000000000000000064" -echo "Value: 0" -echo "arbSysSendTxToL1Args.l1Timelock: " $L1_TIMELOCK -echo "arbSysSendTxToL1Args.calldata:" -echo "$FINAL_CALLDATA" -``` - -Run it: - -```shell -chmod +x reg-arb-custom.sh -./reg-arb-custom.sh -``` - -The script prints the four values you'll paste into the Tally proposal: the target contract (`ArbSys` precompile at `0x...0064`), the value (always `0`), the L1 Timelock address, and the encoded calldata that schedules the batched call on the L1 timelock. - -For a fully worked example, see the [BORING token registration payload gist](https://gist.github.com/hajnalben/b12cfae78ec88259be8c396c25bab1c2). - -## Step 2: Submit the Tally proposal - -Open a new proposal in [Tally](https://www.tally.xyz/gov/arbitrum) targeting the Arbitrum Core governance contract: - -- **Target contract:** `0x0000000000000000000000000000000000000064` (the `ArbSys` precompile) -- **Value:** `0` -- **Function:** `sendTxToL1(address destination, bytes data)` - - `destination`: the L1 Timelock address printed by the script - - `data`: the calldata printed by the script - -Before publishing, post a draft on the [Arbitrum Foundation forum](https://forum.arbitrum.foundation/) for community review. Token registrations have historically passed without controversy, but the off-chain feedback step catches encoding mistakes. - -## Step 3: After the proposal passes - -Once the proposal succeeds, the execution sequence is: - -1. The Arbitrum Core governance contract calls `ArbSys.sendTxToL1`, queuing a message from the child chain to the parent chain. -2. After the standard withdrawal delay, the L1 outbox executes the message, calling `scheduleBatch` on the L1 Timelock. -3. After the timelock's `DELAY_SECONDS` (3 days) elapses, anyone can call `executeBatch`, which has the `UpgradeExecutor` invoke `RegisterAndSetArbCustomGatewayAction.perform`. -4. The action contract calls `forceRegisterTokenToL2` on the parent chain generic-custom gateway and `setGateways` on the parent chain gateway router. Each call sends a retryable ticket to the child chain. -5. Both retryables auto-redeem (they're funded by the `MAX_SUBMISSION_FEE` constants), updating the L2 mappings. - -When all retryables are redeemed, the token is registered. Deposits and withdrawals through the generic-custom gateway will work normally from that point on. - -## Frequently asked questions - -### What if I get the L2 address wrong in the proposal? - -Registration is one-time and irreversible per (parent token) address — `forceRegisterTokenToL2` reverts on a second attempt. If a bad mapping is registered, recovery requires a second governance proposal with a new action contract that reregisters via different methods. Validate addresses carefully before submitting. - -### Why does the proposal target the `ArbSys` precompile (`0x...0064`)? - -Arbitrum DAO proposals execute on the child chain, but the registration calls happen on the parent chain. `ArbSys.sendTxToL1` is the precompile that creates outbound messages from the child chain to the parent chain. The proposal's child chain transaction queues the parent chain call; the L1 Timelock + UpgradeExecutor then dispatch it. - -### Is the standardized template mandatory? - -No. Per the [Foundation announcement](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764), the template is a helpful utility, not a requirement. Custom proposals that achieve the same registration result are valid, but reviewers and the Foundation will scrutinize them more closely. - -## Resources - -- [Forum announcement: Standardized Token Registrations Template](https://forum.arbitrum.foundation/t/announcement-of-standardized-token-registrations-template/29764) -- [`RegisterAndSetArbCustomGatewayAction.sol`](https://github.com/OffchainLabs/governance/blob/main/src/gov-action-contracts/token-bridge/RegisterAndSetArbCustomGatewayAction.sol) (action contract source) -- [Payload generator gist](https://gist.github.com/gzeoneth/439dedc7bdd971345657a04c266daf00) (script used in Step 1) -- [Self-service generic-custom gateway setup](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) -- [Token bridging concept](/how-arbitrum-works/deep-dives/token-bridging.mdx) From 593c84cb7b961d883043269a0f62a8299d5ad5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Tue, 12 May 2026 20:44:14 -0700 Subject: [PATCH 3/5] add external link to AF docs --- sidebars.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sidebars.js b/sidebars.js index 9dc465d1af..f46a397b27 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1343,9 +1343,9 @@ const sidebars = { id: 'build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway', }, { - type: 'doc', + type: 'link', label: 'Register via DAO governance', - id: 'build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance', + id: 'https://docs.arbitrum.foundation/how-tos/register-token-via-dao-governance', }, ], }, From 4e139ebfe8eded2b1d46decb173744604e853f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Wed, 13 May 2026 10:59:53 -0700 Subject: [PATCH 4/5] point DAO token registration to AF docs --- .../setup-generic-custom-gateway.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx index 57a23d0b2e..9d3fb9a7da 100644 --- a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx @@ -23,10 +23,9 @@ First of all, the **parent chain counterpart of the token** must conform to the These methods are needed to register the token via the gateway contract. If your parent chain contract does not include these methods and it is not upgradeable, you could register in one of these ways: -- Submit an [Arbitrum DAO proposal using the standardized token registrations template](/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx). +- Submit an [Arbitrum DAO proposal using the standardized token registrations template](https://docs.arbitrum.foundation/how-tos/register-token-via-dao-governance). - Wrap your parent chain token and register the wrapped version of your token. - -Note that registration is a one-time event. + Note that registration is a one-time event. Also, the **child chain counterpart of the token** must conform to the [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) interface, meaning that: From 0af4c21126ad51b768c82d2f1c543c89c1feeb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Wed, 13 May 2026 11:03:15 -0700 Subject: [PATCH 5/5] point second DAO token registration link to the AF doc --- .../configure-token-bridging/setup-generic-custom-gateway.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx index 9d3fb9a7da..040c54c39d 100644 --- a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx @@ -367,7 +367,7 @@ No, you can only register once a child chain token for the same parent chain tok ### What can I do if my parent chain token is not upgradable? -Token registration can be completed through an Arbitrum DAO proposal using the standardized template. See [Register a custom gateway token via Arbitrum DAO governance](/build-decentralized-apps/token-bridging/configure-token-bridging/register-via-dao-governance.mdx) for the full process. +Token registration can be completed through an Arbitrum DAO proposal using the standardized template. See [Register a custom gateway token via Arbitrum DAO governance](https://docs.arbitrum.foundation/how-tos/register-token-via-dao-governance) for the full process. ### Can I set up the generic-custom gateway after a standard **ERC-20** token has been deployed on the child chain?