├── .gitignore ├── README.md ├── Share ├── AMA │ ├── DA_overview.md │ └── OdysseyPreheating.md ├── Articles │ ├── ArbitrumGas.md │ ├── ArbitrumNodeMap.md │ ├── AssertioTree.md │ ├── Inbox.md │ ├── README.md │ ├── RollupsContract.md │ ├── inboxMessage.md │ ├── l1ToL2Msg.md │ └── sequencerMessage.md ├── guide │ ├── 2_dimensional_fee.md │ └── bridgeUi.md ├── img │ ├── AssertionTree.png │ ├── arch_overview.png │ ├── bridgeUi │ │ ├── addNetwork.gif │ │ ├── claim.png │ │ ├── dapplist.gif │ │ ├── deposit.gif │ │ ├── login.gif │ │ ├── network.gif │ │ ├── recommand.gif │ │ ├── selectToken.gif │ │ ├── status.gif │ │ ├── withdraw.gif │ │ └── withdrawStart.gif │ ├── delayedInbox.png │ ├── nova.png │ └── retryable-lifecycle.JPG └── news │ ├── nitro_upgrade.md │ ├── nitro_upgrade_instructions.md │ ├── nitro_upgraded.md │ └── nova.md ├── TodoList ├── NonTech.md ├── Tech.md ├── Tech │ ├── AddNewTutorial.md │ └── RunArbTutorial.md └── TechAribiter.md ├── arbitrum-tutorials ├── .eslintrc.js ├── .pre-commit-config.yaml ├── .prettierrc.js ├── README.md ├── assets │ └── offchain_labs_logo.png ├── package.json ├── packages │ ├── address-table │ │ ├── .env-sample │ │ ├── README.md │ │ ├── contracts │ │ │ └── ArbitrumVIP.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── arb-shared-dependencies │ │ ├── hardhat.config.js │ │ ├── index.js │ │ └── package.json │ ├── custom-token-bridging │ │ ├── .env-sample │ │ ├── README.md │ │ ├── contracts │ │ │ ├── ICustomToken.sol │ │ │ ├── L1Token.sol │ │ │ └── L2Token.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── demo-dapp-election │ │ ├── .env-sample │ │ ├── .gitignore │ │ ├── README.md │ │ ├── contracts │ │ │ └── Election.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── demo-dapp-pet-shop │ │ ├── .env-sample │ │ ├── README.md │ │ ├── contracts │ │ │ └── Adoption.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── eth-deposit │ │ ├── .env-sample │ │ ├── .gitignore │ │ ├── README.md │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── eth-withdraw │ │ ├── .env-sample │ │ ├── .gitignore │ │ ├── README.md │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── greeter │ │ ├── .env-sample │ │ ├── .gitignore │ │ ├── README.md │ │ ├── contracts │ │ │ ├── Greeter.sol │ │ │ ├── arbitrum │ │ │ │ └── GreeterL2.sol │ │ │ └── ethereum │ │ │ │ └── GreeterL1.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── outbox-execute │ │ ├── .env-sample │ │ ├── README.md │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ ├── redeem-failed-retryable │ │ ├── .env-sample │ │ ├── .gitignore │ │ ├── README.md │ │ ├── contracts │ │ │ ├── Greeter.sol │ │ │ ├── arbitrum │ │ │ │ └── GreeterL2.sol │ │ │ └── ethereum │ │ │ │ └── GreeterL1.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ ├── exec-createFailedRetryable.js │ │ │ └── exec-redeem.js │ ├── token-deposit │ │ ├── .env-sample │ │ ├── README.md │ │ ├── contracts │ │ │ └── DappToken.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ │ └── exec.js │ └── token-withdraw │ │ ├── .env-sample │ │ ├── README.md │ │ ├── contracts │ │ └── DappToken.sol │ │ ├── hardhat.config.js │ │ ├── package.json │ │ └── scripts │ │ └── exec.js └── yarn.lock └── tutorials ├── README.md ├── img ├── datahub │ ├── datahub_1.png │ ├── datahub_2.png │ ├── datahub_3.png │ ├── datahub_4.png │ └── datahub_5.png ├── getBlock │ ├── getblock_1.png │ ├── getblock_2.png │ ├── getblock_3.png │ ├── getblock_4.png │ ├── getblock_5.png │ └── getblock_6.png ├── moralis │ ├── moralis_1.png │ ├── moralis_2.png │ ├── moralis_3.png │ ├── moralis_4.png │ ├── moralis_5.png │ ├── moralis_6.png │ └── moralis_7.png └── quicknode │ ├── quicknode_1.png │ ├── quicknode_2.png │ ├── quicknode_3.png │ ├── quicknode_4.png │ ├── quicknode_5.png │ └── quicknode_6.png └── tutorials ├── datahub.md ├── getBlock.md ├── moralis.md └── quicknode.md /.gitignore: -------------------------------------------------------------------------------- 1 | TodoList/Developer.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum-Library 2 | 一个专为中文区Arbitrum技术、生态爱好者提供的平台。 3 | 4 | 英文Twitter:@arbitrum \ 5 | 中文Twitter:@arbitrum_cn \ 6 | 中文技术社区: 添加微信Ethereum_wan加入 7 | 8 | ## 作为开发者,我该如何开始? 9 | 10 | 1. 学习 [Solidity](https://docs.soliditylang.org/en) 11 | 2. 了解 [Layer2](https://ethereum.org/zh/layer-2/) 12 | 3. 阅读以下技术资料(有视频或文章方式) 13 | 4. 深入开发或部署项目 14 | 15 | ## Arbitrum中文技术资料 16 | 17 | ### 视频分享 18 | - [Arbitrum中的可重试票据p1](https://www.youtube.com/watch?v=sYo8DXvysJI) 19 | 20 | - [Arbitrum中的可重试票据p2](https://www.youtube.com/watch?v=l_wt3L2k4dc) 21 | 22 | - [layer2:以太坊扩容方案](https://b23.tv/Vjn521b) 23 | 24 | ### 文章分享 25 | 26 | - [文章分享界面](./Share/Articles/README.md) 27 | 28 | ### Tutorial 29 | 30 | - [Arbitrum开发者教程](arbitrum-tutorials/README.md) 31 | - [第三方rpc设置教程](tutorials/README.md) 32 | 33 | ### 研究论坛 34 | 我们会在这里发布一些我们对于rollup技术路线的想法,欢迎点击[此处](https://research.arbitrum.io/)查看,如果你也有想法,同样欢迎提出! 35 | 36 | 37 | 40 |

41 | 42 |

-------------------------------------------------------------------------------- /Share/AMA/DA_overview.md: -------------------------------------------------------------------------------- 1 | ## DA AMA 2 | 3 | ### JasonWan: 4 | 5 | --- 6 | 7 | 大家好,我是JasonWan,是Arbitrum的集成工程师,目前负责Arbitrum亚太区的中文开发者社区,今天很高兴来和大家一起探讨数据可用性(D/A)的话题,D/A的话题最近被广泛提及,今天我们邀请了业内的几位嘉宾一起来探讨一下什么是D/A以及D/A有什么作用和好处。 8 | 9 | ### Nina: 10 | 大家好,我是Nina,是Arbitrum亚太区的生态负责人,各位嘉宾中我与潘老师比较熟悉,今天主要是和各位开发者一起来学习D/A。 11 | 12 | ### 潘致雄: 13 | 14 | --- 15 | 16 | 谢谢Jason,大家好,我是潘致雄,平时比较关注区块链的话题。之前很少讨论D/A,我对D/A的理解也不一定深,如果有什么问题的话,希望和大家一起来探讨。 17 | 18 | ### QiZhou: 19 | 20 | --- 21 | 22 | 大家好,我是周齐,是Web3Q和QuarkChain的创始人,一直以来专注于web3的基础设施,包括分片和数据可用性。最近和以太坊团队在D/A的一些标准(如EIP-4844)等方面有所交流,希望能和大家一起来聊聊数据可用性是怎么一回事,谢谢大家! 23 | 24 | ### cyberorange: 25 | 26 | --- 27 | 28 | 大家好,我是cyberorange,现在在Unipass做研究员,Unipass是一个低门槛的智能钱包,大家可以用邮箱管理和控制自己的资产,我平时也会利用空余时间去研究一些以太坊的技术进展,曾在dapplearning也做过一次D/A的分享。 29 | 30 | ### 话题一: 31 | 32 | 现在D/A的概念很火,请cyberorange来解释一下D/A的概念吧! 33 | 34 | ### cyberorange: 35 | 36 | --- 37 | 38 | 要讲什么是D/A,我们可以先回顾一下以太坊之前的扩容方案,以太坊之前是使用分片进行扩容的,所谓的分片,就是把资源拆分开到多台机器上去执行,有执行分片,状态分片,网络分片等类型,它们都代表着某一资源的拆分,但是后来发现,这一条路径是很难走的,它的设计过于复杂。 39 | 40 | 然后就出现了Plasma,但是plasma也存在问题,在挑战期可能会出现数据扣留,最后大家在plasma上面退了一步,设计出了rollup,rollup实现了将执行层从以太坊剥离,这么做虽然可以起到一定的扩容,但是扩容效果依然无法实现大规模采用,因为rollup需要把所有的交易信息以calldata的形式传到Layer1上,无论是压缩交易或者是zk-rollup的不将签名放到链上,交易花销终究都是和交易大小成正比,而以太坊本身的数据吞吐能力是较低的,这样就使得rollup扩容在当前的状态下天花板十分明显。但是又不能不提交数据,因为像op-rollup如果没有交易数据就无法验证一笔交易的正确,也无法对错误交易发起挑战。而zk-rollup如果没有数据,则无法构造当前状态,定序器就可以审查你的交易,比如在状态树里面删除了你的账户,虽然它无法构造错误的交易,但是你的钱就永远无法取出,所以说,以太坊除了在Rollup方向进行扩容,还需要在数据方面扩容,即D/A 。 41 | 42 | 它是一种保证数据可用性的能力,并没有计算能力。你可以把它理解为一个告示板,它可以在告示板上面贴数据,任何对数据感兴趣的,都可以去看,去读取数据。然后你根据内容重建某个应用上你感兴趣的状态,所以说D/A最重要的是,无需许可的重建状态。很多人会把D/A 和存储混淆,但是其实是不一样的,D/A并不保证存储数据一直有效,只是作为一个告示版,像EIP-4844,它提供了1M的Blobs空间供数据存储使用,但是会在一个月以后删除,所以他们的目的是不一样的。目前D/A所采用的技术主要是纠删码(RS纠删码)和数据采样(DAS),这是在18年的某篇论文中就定下来的技术方案。通过纠删码你可以做到某个节点只存一部分数据,然后任意一定比例的节点在线都可以把数据完整恢复出来。比如你有100份数据,通过纠删码编码300份,分散到300个节点上面,只要任意100个节点在线你就能把数据完整恢复出来。采样可以让一个节点不下载数据,只需要抽取部分数据片段就以极高的概率确定这个数据在当前网络是否可用。大概估计一下你只要抽十几个数据片就可以接近100%(99.9999……%)的概率来保证这个数据是可用的。 43 | 44 | 回过头来看,当你把D/A和rollup结合,就会发现实现了最初的资源拆分,D/A拆分了存储,Rollup拆分了执行和计算,最终的效果是类似的,但是设计哲学是不一样的。 45 | 46 | ### JasonWan: 47 | 48 | --- 49 | 50 | cyberorange的介绍十分详细,不但介绍了什么是D/A,还介绍了D/A的诞生的发展,也提到了plasma技术和EIP-4844所要使用到的技术,非常感谢cyberorange!第一个话题结束,相信大家已经有所了解,接下来我们聊第二个话题。 51 | 52 | ### 话题二: 53 | 54 | --- 55 | 56 | D/A由于需要存放数据,目前对于D/A的存储有很多方法,请嘉宾来聊聊对D/A的一些看法。 57 | 58 | ### QiZhou: 59 | 60 | --- 61 | 62 | 的确如cyberorange给大家讲的D/A的历史和发展一样,一开始D/A的数据路线是从以太坊对Rollup的需求开始的,将以太坊作为Rollup未来的D/A层是未来以太坊的技术方向。在这方面,EIP-4844的设计目标是让Beacon Chain Node(信标链节点)能够携带1-2M的Blobs的区块大小,2M除以12s的时间大概就能就能算出吞吐量。The Merge之后想要解决的一个问题是把效率提高,使用到的一个核心的技术方向就是DAS,它的思想就是只用采样很小的一部分数据,比如说整个数据的万分之几,在概率层面上可以确定数据是在网络中被存储和恢复的,这里有个前提就是其他所有的Validator都得做类似的随机采样,是诚实的验证节点,这样才能保证数据可用,才可以通过很小的采样数据确保数据在全网可用,不用去下载全部数据验证。通过这个技术的设计,可以达到32M的存储来提高吞吐速率,32M除以12s大概可以得到2.6M/s左右的吞吐量。在这里面,还有很多D/A的选择,在最早的时候,Vitalik也提过使用类似BCH那样的大区块技术来做D/A层,BCH和不同的分叉版本计划将原来比特币1M的区块扩大到32M甚至128M的大小,但是相对于比特币的出块时间来看速率也还是比较低的。它的机制也是采用P2P广播机制,这套机制的技术是比较落后的,需要广播数据到全网来下载区块数据,以太坊意识到在这方面的问题,所以下决心去使用自己的方案来解决D/A的问题。 63 | 64 | 除了上传之外还有一个保存的问题,现在有一些保存的方案,但是现在来说还不是最重要的重点。除此之外,还有celestia、avail这样的方案,据我所知,Arbitrum也在做一些Layer2的D/A方案。 65 | 66 | ### JasonWan: 67 | 68 | --- 69 | 70 | 感谢周齐老师详细的从D/A的关键数据参数如出块时间,区块大小等方面来对D/A进行了解读,请潘老师来发言谈谈看法。 71 | 72 | ### 潘致雄: 73 | 74 | --- 75 | 76 | 谢谢Jason,我做一些补充,角度不太一样,我觉得D/A是从blocks base衍生出的话题,我觉得是行业对区块链的理解逐渐在加深,区块链的集成度是比较高的,比如说包含共识,计算,存储,P2P网络等,一个大的趋势是大家都想解耦这些功能,把它分为某些小的部分来单独的优化和处理,所以以太坊才会在未来把D/A单独的做一层,独立于共识,虚拟机的部分,按照现在来看,D/A主要是通过区块空间来实现,但是未来会有区块链来专门提供D/A,这是对于layer1来说,但是对于很多Layer2,会采用自建D/A的方式。所以说,我觉得整体的路线是从泛用型区块链变成了专用型的D/A解决方案。在未来,D/A会成为垂直型的功能,大家会对它进行探索,无论是它的吞吐量,有效期,性能,采样安全等,都会做的更专业。 77 | 78 | ### JasonWant: 79 | 80 | --- 81 | 82 | 潘老师从模块化区块链的角度对D/A进行了补充,又从垂直领域对D/A进行了介绍,感谢潘老师!现在我们进入第三个问题。 83 | 84 | ### 话题三: 85 | 86 | 现在D/A的分类有很多,比如侧链,新公链和rollup都有自己的D/A方案,也提到了各自方案的优点,请嘉宾来聊聊对这些D/A方案的优缺点。 87 | 88 | ### cyberorange: 89 | 90 | --- 91 | 92 | 目前确实有挺多的D/A的尝试,事实上,所以的layer1都承载了D/A的功能,这里我们只谈单独做D/A的方案。 93 | 94 | 目前看到的D/A方案都是和对应的需求有关,比如说做游戏这类弱金融和弱审查的应用,像Starkware和Arbitrum都会采用委员会做节点的方式来做D/A ,它的去中心化程度和抗审查能力会比较低,使用这些D/A方案的一般做资产结算不太适合,但是适合用来做社交,链游这类对结算要求较低的应用。 95 | 96 | 然后就是celestia,他们创始人是最早发了DAS的那篇论文,还有Polygon Avail,他们的解决方案比较类似,区别在于celestia使用的是纠删码和错误编码证明,Avail使用的是Danksharding的KZG,他们的主要区别是:错误编码证明在于出块人出块以后这个块可能是错误的,然后其他人收到这个块以后可以重新编码,如果编码出来不一样,他们可以提供你编码错误的声明,在全网广播。Avail使用的KZG在于不使用错误编码证明,因为它直接约束了正确的编码。这两个区别就比较类似op-rollup和zk-rollup的区别。 97 | 98 | 当然了,layer2项目翘首以盼的是以太坊推出自己的D/A ,以前以太坊的路线是推出64个分片,但是现在采用了Danksharding方案,虽然说设计更为简化,但是在短期也无法推出,因为涉及到一些暂时无法很好解决的问题,比如说PBS,但是呢,以太坊基金会也准备了EIP-4844和EIP-4488,这是Danksharding的前置步骤Proto-Danksharding,为rollup降低了费用,先进行扩容解决燃眉之急。我觉得The Merge以后的最近一两次硬分叉就会把这两个EIP加入进去。他们的区别是用链原生的D/A安全假设更低、更优雅,抗审查能力也会更强。但如果是结算层用以太坊,D/A用celestia或者Avail的话,以太坊是无法对上面的D/A进行采样的,所以说它可能会带来一些安全模型上面的问题,比如说,celestia的验证人想欺骗轻节点,他们可以出一个块,放到以太坊上,然后对轻节点在网络上进行日蚀攻击,他就有可能欺骗某些应用,而轻节点是没法提供挑战或其他方式去证明它欺诈了你的。 99 | 100 | 总结一下,刚刚说了三种类型的D/A:原生D/A,celestia,Avail这样的独立D/A,委员会D/A。他们的去中心化程度和抗审查能力在逐步降低,实现难度也是越来越低,所以我们可能会先用到委员会D/A,像Arbitrum Nova,然后再是Celestia,最后才会使用到以太坊的原生D/A。 101 | 102 | ### JasonWan: 103 | 104 | --- 105 | 106 | 非常感谢cyberorange从D/A使用情况和实现难度来进行分析,接下来请潘老师来分享一下。 107 | 108 | ### 潘致雄: 109 | 110 | --- 111 | 112 | cyberorange所提到的有一点,我可以进行补充,像celestia、polygon Avail,还有以太坊未来的Proto-Danksharding,我记得他们最核心的区别在于,celestia使用的是欺诈证明,而另外两个方案使用的是KZG,这是最核心的差异之一。对于现行的D/A方案,我觉得侧链和新兴的以太坊范式的区块链,他们如果想做D/A的话,最核心的缺点是共识得从零开始,这是难度最大的地方。但是D/A技术层面选择上和以太坊是没有太大的差别的。现在以太坊还有很多的Layer2,想要跳开以太坊,将数据放在自己的服务上,我觉得这种方案比较折中且能兼顾安全性和效率,如果出现了问题,这些方案能够回退到以太坊来提供安全保障,Arbitrum的Nova链在委员会失效以后也是能够回退到Rollup模式的。如果发现committee(委员会)里面的某些验证者有问题了,那可以及时退出确保安全性,这样的方案相比侧链的方案也会更安全些。由于以太坊最初并不是为D/A设计的,当时是一个泛用型解决方案,如果说现在以以太坊作为D/A的话,它的成本是比较高的,没有考虑到很多特殊的场景,也没有考虑到数据的有效期的问题,如果能在上海分叉或者The Merge以后的升级上线Proto-Danksharding的话,还是能够给Layer2的专用D/A层带来新的方案和设计思路的。 113 | 114 | 另外一个就是专为D/A设计的新公链了,包括celestia,对于Avail我不知道是否会脱离开以太坊独立,应该也会,这些公链的D/A层,优缺点还不是很明确,但是或许可以在D/A技术的选择上诞生更有意思的新技术,当然他们也推动了DAS,KZG这些技术的进步。但是从共识上来说,因为是比较新的方案,所以不如以太坊来做D/A层更安全。 115 | 116 | ### JasonWan: 117 | 118 | --- 119 | 120 | 感谢潘老师从技术生态等方面分析了D/A ,总结的很全面。请周齐老师分享一下看法。 121 | 122 | ### QiZhou: 123 | 124 | --- 125 | 126 | 大家刚刚讨论的都比较细,从侧链,新公链,以太坊本身等的角度进行了剖析,我从其他不同的方面来聊聊我对D/A的分析。 127 | 128 | 我觉得在很多方案里面,把大量的Data Blobs上传过程的设计是不一样的,对安全性,去中心化,节点数目的要求是完全不一样的。 129 | 130 | 首先最简单的方式是大家都去广播区块,比如比特币,大区块的比特币,包括侧链和celestia都是使用Tendermint引擎作为共识引擎出块,整个思路还是直接进行广播,这样的话会存在一个缺点就是受制于性能上全节点的数目尤其是出块节点的数目不能太多,因为太多的话会形成网络延迟问题。在这方面,我看到两个方向来解决这个问题,一个是以太坊的DAS技术和PBS技术,核心的思想是所有的验证者(可以很多,上万个)不需要使用P2P网络进行数据的广播和上传,这个方面有很多的技术挑战,包括对网络架构的调整,因为会有很多的子网络不是像现在的D/A方案都是通过P2P网络进行数据广播,它是通过数据可用性采样(DAS)和子网的方式来让所有的验证者节点和普通节点在不下载全部数据的情况下确认数据被完整的上传到了整个网络里,这个是比较有意思的方案,它的去中心化程度和上传的数据都比较高。 131 | 132 | 另外的话我们可以看到aptos和sui这样的新公链方案,使用一些类似传统的P2P网络,但是会更加高效的利用mempool的信息去提高他们整个网络的上传速率,我看到最新的上传速率在10M每秒左右。这个是从网络的上传角度来说的一些比较有意思的点。 133 | 134 | ### JasonWan: 135 | 136 | --- 137 | 138 | 感谢周齐老师的补充,给到了数据上传相对准确的数据,谢谢各位嘉宾。对于D/A,侧链,rollups,新公链都有自己的D/A方案,我再介绍一下Arbitrum的D/A方案。 139 | 140 | Arbitrum One的D/A 是rollup的方案,数据的可用性在以太坊主网,calldata的数据会上传到Inbox合约。最近大家看到我们发布了Arbitrum Nova链,Nova链采用的就是刚刚cyberorange所提到的委员会D/A方案。虽然我们采用委员会的方案,但并不是说我们把系统的安全性都交给了委员会,即使委员会出现了问题,我们也能使系统保持安全状态,也就是Fallback to Rollup。如果网络都处于安全状态,没有委员会成员作恶的话,我们就会采用委员会来保存DA。如果使用过Nova的话大家会发现Nova的费用特别低,低至美分级别的gas。其实刚刚潘老师也提到了,在选择DA的时候会考虑到生态的问题,我们Nova链刚上线的第一天已经达到了20多万的地址,我们同时也和Reddit进行了合作,是web2领域顶流的媒体,Arbitrum Nova我们准备的十分充分,欢迎大家来开发体验。谢谢各位!我们进入第四个话题! 141 | 142 | ### 话题四: 143 | 144 | 嘉宾对于D/A的理想实现方案是哪种? 145 | 146 | ### 潘老师: 147 | 148 | --- 149 | 150 | 我比较关注Danksharding方案,虽然和celestia共享了DAS、RS纠删码的技术,他们是共同的作者,其实这是一个比较老的技术的新型采用,但是celestia选择的欺诈证明和其他方案有些差别,但是数据可用性采用的很多东西都是类似的,能通过这两个技术降低验证数据可用的成本。同时将节点分为了三类,全节点能提供一个普适的服务,在一定时间内存储数据;归档节点存储了全量的数据,未来以太坊无状态以后,数据对于全节点而言是不用全部存储的,到了一个月或者是一定的时间,数据会丢掉,但是不影响到安全性,但是要回溯历史的话,还需要归档节点来提供历史数据,然后就是轻节点,虽然现在的轻节点可以通过RPC找全节点拿数据,但是以后的验证者节点就可以自己对数据进行采样来确保数据在全网可用。所以我觉得这三类节点的划分也提高了网络中数据可用性的安全保障。 151 | 152 | ### JasonWan: 153 | 154 | --- 155 | 156 | 潘老师可以说一下在实现轻节点的方面有没有技术难点需要开发者共同来解决的? 157 | 158 | ### 潘致雄: 159 | 160 | --- 161 | 162 | 技术难点对我来说比较难评估,周齐可以进行评估一下,但是我看到以太坊基金会官方也在和第三方合作来进行Proto-danksharding和Danksharding的开发,我觉得这里面还是有很多的难点,虽然DAS里面的Erasure Code是CDG时代就有的技术,就是做多倍冗余,在丢失一小部分数据以后仍然能恢复全部数据,十几年前民用级的压缩软件就采用了这些技术,从概念上技术没有那么新,但是应用在区块链上面还是有很多共识上的挑战的,包括结构和网络分发等,所以我觉得工程难度会比较大。 163 | 164 | ### JasonWan: 165 | 166 | --- 167 | 168 | 谢谢潘老师,潘老师认为在工程实现上难度会比较大,那么请周齐老师来聊聊看法。 169 | 170 | ### QiZhou: 171 | 172 | --- 173 | 174 | 对的,刚刚潘老师大致已经把困难和问题介绍清楚了。我来介绍一些大家比较关注的实现难点。 175 | 176 | 这方面我们和以太坊的核心开发者有面对面的沟通他们在DAS的设计方案、问题以及解决方案。可以看到要解决D/A,以太坊的第一步使用的是EIP-4844,其采用的还是传统的Gossip这样的协议去广播消息,到了接下来的Danksharding以后才会使用DAS和RS纠删码来提高数据冗余和吞吐。这里面还是有很多的技术挑战,举个例子来说,虽然Danksharding极大简化了设计,让分片只关注在数据分片上面,但是对于builder的要求还是非常高的,要求builder在12S的时间内构造一个128M(32*4)的数据矩阵,并且要在12S内将矩阵数据广播到整个网络的所有validator那里,它的广播不是传统的Gossip广播,而是多子网的广播。我问了他们对于builder的优化方案,但是目前还处于一个比较早期的阶段,有一些早期的数据来支撑在硬件(如GPU)和库的优化支持下能够达到这样的一个出块速率。同时也要让所有的验证节点比较快速的去检查所有的样本所对应的数据的正确性,他们在这方面也开发了一些密码学的方案,我看到他们最近在做的是在采样了多个样本的情况下(10个或者100个),只需要multi-prove的计算过程就可以把所有样本的正确性都进行验证。 177 | 178 | 另外的话,整个网络的架构也是一个不太确定的问题。因为它和我们广泛的比较成熟的区块链的P2P网络的架构不太一样,包括子网的设计,validator的分配,validator在共谋的情况下(藏匿数据)怎么去检测和重构,这里面涉及到很多的网络设计方案和研究话题。 179 | 180 | 我觉得从短期来说,以太坊EIP-4844从工程角度来说是一个比较容易实现的方案,再接下来,怎么迁移到后面的danksharding,包括一些如EIP-4488这样临时的方案去降低上传的成本,我觉得会有一些新的想法会出现,这是我对以太坊技术路线的观察和思考。 181 | 182 | ### JasonWan: 183 | 184 | --- 185 | 186 | 谢谢周齐老师刚刚从工程的角度为我们解读了目前以太坊D/A实现的困难和潜在的方案,请cyberorange来进行补充一下。 187 | 188 | ### cyberorange: 189 | 190 | --- 191 | 192 | 我觉得理想的D/A要像BitTorrent一样,任何人都可以为网络做贡献,随机存储数据,而且我觉得理想的D/A是不需要出块人的,因为D/A的数据不用管数据的先后顺序和逻辑关联,只需要关注数据是不是可用的,所有我觉得理想的DA是有这样一个网络,当你想让你的某个数据做到D/A的状态的话,你只需要把DA上传到这个网络,你只需要监听你的交易数据有没有被广播,我觉得KZG承诺这些都可以放到客户端。我觉得目前做DA最大的难点就是绝大部分的网络根本没有那么多分散的节点,因为前面做了很多的假设都是有成千上万个独立的分散节点,你把数据都存放到上面,哪怕有50%的节点宕机了,你存储的数据也是有效的。但是事实上,现实情况是很多时候一台电脑就运行了很多的节点,这些节点可能20%在AWS上面,剩下的在某些云服务上面,这种其实是DA的一种很大的风险隐患。因为对于DA来说,任何一个片段的数据丢失了,对于某个应用或者rollup来说都是比较灾难的后果,你认为这个数据是可得到的,但是有一段时间它丢失了。 193 | 194 | 还有一个比较难的点在于,要做很多节点的D/A,每个节点在为D/A投票的时候要用到签名,签名的验证,聚合广播也是很消耗资源的,在节点很多的情况下,可能签名就把整个网络占据了。 195 | 196 | 其他就是D/A还带来了一些风险,模块化区块链把风险也模块化了,以前我们只要考虑在Layer1上面是否被审查,现在你首先得考虑在D/A层上面是否被审查,然后是执行层和共识层的审查。整个风险面被放大了。 197 | 198 | D/A需要的网络层可能现在都是不一样的,以前都是全广播,现在是采样,使用采样可能对网络层的攻击会增加,比如女巫攻击,日蚀攻击这样的经典的P2P网络攻击方式,所以说,要实现这样的一个网络层还是很难的,我看Polygon Avail和celestia都是直接复用了IPFS的一些网络基础设施,我大概就说这么多。 199 | 200 | ### JasonWan: 201 | 202 | --- 203 | 204 | 感谢cyberorange的解读,其中特别重要的一点就是我们最近常提的审查,新的方案会在抗审查方面带来一些比较困难的点。 205 | 206 | ### 观众开放问答: 207 | 208 | Settlement Layer和Execution Layer到底有什么细微的区别: 209 | 210 | ### cyberorange: 211 | 212 | --- 213 | 214 | 你可以理解为执行在任何一台机器都能执行,比如说你现在在以太坊发起了一笔交易,你可以在你的电脑上面执行这笔交易,但是它是不被这个网络认可的,因为它没有被结算。结算就是你知道一笔交易是不可能篡改,不可能回滚了,这就叫结算;而执行就是字面意义,就是你把这笔交易执行了,你可以在任何地方执行,但是只要没有被结算,就不被整个网络所认可。 215 | 216 | ### JasonWan: 217 | 218 | --- 219 | 220 | 感谢各位嘉宾和听众,今天的AMA到此结束了,希望大家关注各位嘉宾老师进行更多的信息获取,多多关注参与Arbitrum的中文AMA,我们每周都会为大家带来精彩的内容分享。我们下一时间再见! 221 | 222 | (感谢@0xCryptoLee的记录!) 223 | 224 | [原文链接](https://www.notion.so/941ddb1f60c64ef1a198c4284670de27) -------------------------------------------------------------------------------- /Share/AMA/OdysseyPreheating.md: -------------------------------------------------------------------------------- 1 | ## 6.14 Arbitrum中文社区AMA会议纪要 2 | 3 | Nina:大家好,我是Arbitrum亚太区的生态和社区负责人,我叫Nina,相信不少小伙伴已经通过我们的社区以及各种推特的活动认识到我了,今天非常有幸,我们Arbitrum中文推特的第一期AMA,邀请到了4位非常重磅的嘉宾。同时借助今天的AMA向大家宣布几件关于Arbitrum比较大的动态更新,一个是Arbitrum中文区未来的发展,以及大家都非常期待的奥德赛的活动;再就是我们今天的这4位嘉宾将会在奥德赛活动期间以及未来如何跟Arbitrum的生态进行交互与合作。 4 | 5 | 干货满满的一期,话不多说,先请我们的嘉宾自我介绍一下吧。Alfred先来? 6 | 7 | Alfred Xu(Multichain联合创始人之一):大家好,很高兴能参与到Arbitrum第一期的space voices,我是Multichain的Co-founder,简单介绍一下Multichain,我们是一个专注跨链交互的基础设施建设项目,项目启动于2020年7月,我们的愿景是服务于多链生态并成为web3的终极路由。我们的技术主要是基于SMPC所实现的多链节点网络,我们已经实现了对于ECDSA、EDDSA的分布式签名支持,在这个基础上我们推出了公共的、开放的和去中心化的数字资产互操作协议,包括点对点的跨链桥以及点对多点的跨链路由,这些产品都支持同质化的token和NFT资产在不同链之间的跨链交互。截至目前为止Multichain已经支持了54条EVM兼容和非EVM兼容的blockchains,支持将近2400种数字资产,累计为接近66万用户完成了超过357万笔的跨链操作。目前我们在第三方跨链协议之间是比较领先的,第三方网站统计的DEFI排名中(包括DEX、跨链等等应用)我们是排在第10名左右。除了作为跨链交互的基础设施之外,对于DEFI应用的项目方而言Multichain也是项目方对接用户和资金流量的通道。我们很高兴可以参与进Arbitrum Odyssey活动中,我们已经开通了ETH、BSC、Fantom、polygon和AVAX到Arbitrum的跨链通道,支持包括稳定币和主流币在内的50多种数字资产的跨链。这是我对于项目以及我个人的介绍,谢谢大家。 8 | 9 | Nina:谢谢Alfred的介绍,接下来yond? 10 | 11 | yond(izumi finance市场经理):谢谢Nina和Arbitrum中文社区的邀请,我是izumi的市场经理。在座许多小伙伴可能认识我,因为我是twitter space的常客,经常出没于各种space活动。izumi finance是一个提供一站式流动性服务的管理平台,我们目前提供两项服务,第一项是基于uniswap V3的流动性管理协议liquidBox,第二项服务是基于BNB chain的DEX叫iZiswap。我们的宗旨是为各种代币提供持续且高效的链上流动性,目前我们已经为超过10个区块链协议提供了流动性服务,目前独立LP以及机构LP加起来超过了8000名,izumi旗下管理的流动性资产额大概有6000万美元左右。目前我们部署在ETH、polygon以及Arbitrum,近期会部署在BNB chain。以上是我们项目目前的基本情况。非常高兴可以参与到今晚Odyssey的预热活动中来,我们本身也是week5的入围产品,非常乐意借助这个契机,向大家分享我们根据Odyssey活动做出的一系列搭配活动。 12 | 13 | Nina:感谢yond的分享,我们一起做过几期AMA,算是老朋友了。Kiko?在场嘉宾中唯一的女生,你说说吧? 14 | 15 | Kiko(imtoken市场部marketing manager):大家好我是Kiko,对于imtoken我想大家都不陌生,我们成立于2016年,目前已经累计为全球150多个国家和地区的1400万用户提供了安全可信赖的数字资产管理服务,目前除了支持12条主流公链外,我们还支持大多数主流Layer2方案,为了提供更好的Layer2体验,我们深度集成了Arbitrum。欢迎大家来imtoken体验,大家参与Odyssey的时候也可以多多使用imtoken来完成活动,谢谢。 16 | 17 | Nina:谢谢Kiko。最后一位,来自Aboard Exchange的J。 18 | 19 | J(Aboard市场策略研究):大家好,我负责Aboard Exchange的市场策略和研究。简单介绍一下Aboard项目:Aboard现阶段是一个使用订单簿的去中心化衍生品交易所,我们主要提供代币永续合约和指数永续合约,我们的愿景是想改善现有DEX的交易体验,为用户提供更专业的交易和管理工具。 20 | 21 | Nina:谢谢J。欢迎大家来到我们第一次的AMA,这里我要向大家介绍我的同事Jason,他是我们Arbitrum国内唯一的一位工程师,他会在技术上帮助在场的项目方以及生态开发者们,也希望大家可以借助这个机会认识一下他。 22 | 23 | Jason(Arbitrum亚太区集成开发工程师):各位朋友大家好,我是一名Arbitrum的工程师,我现在也负责国内的一些开发者社区,如果大家对开发感兴趣的话,可以到我的推特@Jason_Wan123空间,第二条推特有如何加入Arbitrum中文开发社区的通道,欢迎大家加入,谢谢。 24 | 25 | Nina:好的谢谢Jason。我简单跟大家介绍一下Arbitrum中文区的一些规划。Arbitrum去年8月上线以来中文社区就一直很活跃,很多小伙伴也参加过我们之前举办的线下活动,收到过我们的周边,关注了我们的推特,未来我们也希望可以更多地和中文区的小伙伴进行互动。首先我们会有长期的固定的AMA计划,每周二晚上8点我们都会固定和大家在这里相见,我们会邀请生态里的项目方、一些加密圈的好朋友,一起来探讨Layer2相关的一些话题,以及Arbitrum生态内部的一些话题,尽量给大家提供一个参与Layer2发展的渠道,希望大家多多关注我们的推特,这是一方面。另一方面我们针对开发者也会有越来越多的激励计划,如果今天在座的有我们生态内部的开发者的话,可以注意到最近我们有举办了ETH社区内的课程,大家可以关注我们的Layer2相关的线上课程,包括黑客松等等一系列激励计划都会陆续推出。 26 | 27 | 有社区小伙伴在问什么是Odyssey,这是我们的重头戏,简单跟大家介绍一下这个活动:Odyssey是Arbitrum第一次全生态范围内的交互活动,此次活动是Arbitrum和Galaxy Project的一次大型合作尝试。今年4月份我们向社区发起了一项投票,从生态中诸多项目里选出了14个最受社区欢迎的项目参与进Odyssey的正式活动中来。目前活动还没有开始,如果你错过了最初的投票环节,没有关系,它丝毫不会影响你接下来的参与。6月中旬我们将会发布活动上线的准确时间,这是一个长达2个月的活动,活动形式非常简单,你在链上完成指定的任务,就可以获取指定的NFT。 28 | 29 | 第一周是跨链周,我们会邀请生态内指定的一些跨链桥合作伙伴,如果你使用这些跨链桥将ETH(注意,只能是ETH)从任何的非Arbitrum公链跨入Arbitrum之内,你就完成了跨链周任务。本次活动的所有NFT都是由知名加密艺术家Ratwell&Sugoi设计的,是Arbitrum回馈社区参与热情的象征。具体有哪些跨链桥参与到本次活动,要请大家关注今明两天发布的新公告。关于跨链周还有一个小彩蛋,关于最受用户欢迎的跨链桥(跨链周过后我们会统计哪个跨链桥将最多的ETH跨入了Arbitrum),如果你使用的正好是该款跨链桥产品,恭喜你,你将获得一枚额外的NFT,所以跨链周大家最多是可以获得2枚NFT的。 30 | 31 | 跨链周过后就是14个被社区选中项目的交互过程,一共7周,每周会发布2个项目相关的任务,每个任务的完成时间都是7天,逾期不候,不能补做,一旦错过也就无法获得该项目对应的NFT。跨链周加上7周的项目任务,一共16枚NFT,如果你获得了其中的13枚以上,你就可以在整个活动结束后通过Galaxy网站向Arbitrum官方兑换终极NFT大奖,见证你的Odyssey之旅完美结束。 32 | 33 | 今天邀请到的嘉宾会在Odyssey活动期间为我们的社区小伙伴们准备一些小惊喜,请各位嘉宾和我们分享一下吧。 34 | 35 | Alfred:Multichain会在跨链周提供ETH的免费跨链服务,希望大家多多体验Multichain。 36 | 37 | yond:izumi finance会在week5和Yin finance一起进行Odyssey活动,我们和Yin finance会有一个联合的流动性挖矿激励,欢迎大家届时参与。 38 | 39 | Kiko:imtoken为Odyssey活动进行了Arbitrum网络优化,欢迎大家活动期间多多使用imtoken来完成任务,获得奖励。 40 | 41 | Nina:接下来进入对生态内项目方的采访环节,相信大家也比较感兴趣,我们生态内部的合作项目方未来的发展路线是怎样的。 42 | 43 | Alfred:跨链桥和公链之间是共生共赢的关系,Multichain从未来的发展方向来看,我们始终坚持着跨链交互基础设施的发展定位。近期来讲,我们会在MultiDAO的建设以及新产品方面有更多的举措。MultiDAO的建设主要体现在治理和激励两方面,我们在上个月开启了veMULTI计划,首批veMULTI用户将分享来自第一季度Multichain跨链手续费收益的45%,合计超过390万USD,将以USDC的方式分发给veMULTI持有者。这是MultiDAO建设的起步,治理方面我们会继续推动MultiDAO的社区建设,尽可能覆盖社区各类用户群体。产品技术方面我们今年的两个重要产品是Anycall和fastMPC,Anycall是一个综合消息跨链交互协议,其设计初衷并不仅是为了满足数字资产跨链交互的单一功能,而是能够实现链间DEX新应用形态的构建,我们之前已经和Curve合作在ETH和Fantom之间构建了一个统一流动性池的激励方案,关于这点会在我们的document以及为Anycall发布的白皮书里有详细介绍。第二个产品是fastMPC,这是对我们现有的SMPC network的升级,最终我们很有可能将其从一个分布式系统发展成一条链,这条链不是设计用来承载具体的DEFI应用,而是专门打造成为为区块链提供跨链信任机制的基础设施。我们会大力推动围绕这些新产品的生态建设,今年我们也会在MultiDAO设立基金来升级并且支持基于Anycall和fastMPC的各类应用。 44 | 45 | yond:izumi在今年Q2刚刚上线了我们今年最重要的一款产品iZiswap,Q3我们会跟更多的项目进行合作,拓展流动性,成立更加完整的生态系统,Q4我们会上线izumiDAO的治理,届时会有基于Curve的ve机制和uniswap V3的生态系统,会带给大家一个比较创新的治理模型。目前熊市阶段流动性、资金纷纷撤离,作为金融产品我们希望可以提高资金效率,尽量去帮助一些项目去省钱,在不缺失流动性的前提下提高他们的资本效率。这是我们一直秉承的使命,即便身处熊市,我们依旧build。 46 | 47 | Kiko:imtoken的未来发展一直都是围绕着安全和ETH扩容这两个出发点展开的,我们第一步是支持optimistic rollup,之后是zk rollup。第二个方向是改进用户钱包密钥管理的门槛,尽量从降低用户进入区块链世界的门槛角度出发。imtoken的愿景就是打造安全、放心、简单、易用的数字钱包,让每个人可以平等自由地享受有意义的数字生活。在rollup方案中我们会优先从optimistic rollup出发,optimistic rollup兼容EVM的特性使得它在当前的阶段脱颖而出,成为最先落地的的扩容方案并成为了主网用户的首选,这也是我们深度集成Arbitrum的原因。 48 | 49 | J:Aboard Exchange会在未来提供更多交易对,包括更多代币以及指数合约,随后我们会上线非线性收益产品,随着产品以及交易品种的逐渐完善,我们计划在2023年上旬上线一个资管类产品,为投资人以及管理人提供去中心化的资管服务,我们的Adviser protocol免去了传统金融所需的第三方机构,比如法律顾问甚至合同印刷等等第三方机构,我们通过Blockchain技术可以为大家提供更直接更高效可能性更多的管理平台,我们的目标是为用户提供交易品种繁多、既可以直接交易,也可以购买资管产品的服务。以上是我们的Roadmap,从实际交易的角度来说我们也在积极开发更多的交易和风控工具,在市场波动很大的情况下(比如现在,行情确实不好),一个衍生品作为风控工具也是具有很大实用效应的,不仅仅是用来赌方向,本质上的作用是帮助大家控制风险、降低风险。 50 | 51 | Nina:接下来回答一些大家都比较感兴趣的事情吧,我们的项目方为Odyssey活动都准备了一些各自的福利,各个项目带大家了解一下吧。 52 | 53 | Alfred:Multichain会为Odyssey跨链周的使用用户提供免费服务。在跨链周期间,使用Multichain做任务是免除手续费的,鼓励大家多多体验Multichain产品。 54 | 55 | Nina:看到社区有小伙伴在问,跨链周对于ETH的跨链金额有没有限制,这里统一回复一下,没有限制,只要你跨进来就有效,哪怕只有0.00001ETH也是可以的。不过为了保证后续7周活动的顺利完成,还是建议大家保证足够的资金。 56 | 57 | Kiko:imtoken联合Galaxy举办了Learn and earn活动,可以从我们的官方页面进入参与,有许多关于Layer2和其他链的知识问答,用户通过答题可以获得NFT。该活动的NFT是在Arbitrum网络上铸造的,通过Galaxy发布链接领取,我们希望通过这样的活动让大家更加深入了解Layer2及其生态内项目。另外也准备了一个类似于Odyssey这样的活动,活动名称是伊利亚特,我们联系了几个项目方,活动的大概形式是用户通过imtoken实现与Arbitrum的生态交互,从而得到token及NFT奖励。伊利亚特活动的NFT也是在Arbitrum网络铸造的。大家可以关注我们的推特以及官网,获取活动的最新消息。 58 | 59 | J:Aboard在活动期间也为大家准备了福利,包括本周我们会开启一个测试网交易大赛,第一名可以赢得1500USDC的奖金以及永久交易费用减免的福利,其他用户包括最后一名也有机会获得USDC,举办这个活动的目的是邀请大家来Aboard Exchange,尝试交易,体验产品的同时获得奖励。活动开始前和活动期间都可以报名,但是想要获得排名需要满足一定的交易需求,具体请参考我们的活动说明,也希望大家在参与测试网之后,通过社区向我们多多反馈意见。正式活动开始后Aboard可能会和其他项目联名给大家发送一些福利,也会有我们自己推出的额外奖品(Galaxy NFT等等),请大家多多关注我们的推特和discord社区。 60 | 61 | yond:izumi在week5会和Yin finance一起推出一个共同的流动性激励池,大家可以去挖一下,有高额的激励补贴,我们还在进一步探讨对于完成了Odyssey任务的用户,我们会对符合条件的地址进行快照,会随机抽取一些地址进行空投,这是我们提供给Arbitrum社区用户的福利。 62 | 63 | Nina:太惊喜了,izumi这个福利连我都不知道,可见Odyssey活动带动了我们的整个生态,各个项目都在借着这个机会和我们的社区及用户产生更多的交集。大家要多关注我们的生态项目,他们可能随时会带给大家你们想象不到的福利和alpha。流动性碎片化是现在整个行业都在面对的问题,我们已经不可避免地走向了多链和多层的时代,izumi在这个过程中准备如何去更好地正视行业现状,服务用户呢?我们知道izumi是基于uniswap V3生态的,为什么会选择uniswap V3,izumi如何看待流动性碎片化问题呢? 64 | 65 | yond:Nina的问题非常专业。关于流动性碎片化的问题,我们可以观察到很多大的协议,它们的流动性从V2的DEX中迁移出来,这就是一种征兆了。我们是基于uniswap V3做的流动性协议,我们鼓励更多的协议把他们的流动性从V2迁移出来(比如sushi、pancake等等),转移到V3中来,我们izumi作为流动性管理者可以帮你管理。在这波大跌以前,我们从一些数据分析网站(dune等)可以看到稳定币对的交易量(USDC、USDT、DAI等等)在uniswap V3的交易量是不输给Curve多少的(不到4个点左右)。uniswap V3对于协议及用户是更为友好的,izumi的流动性管理协议是liquidBox,拥有三种不同的流动性挖矿模型,根据协议及用户的需求可以选择其中的一种去管理各自的流动性,三种模型是固定范围的Fixed Range、单向的One Side以及动态的Dynamic Range,这三种模型分别是给不同特性的币去做的,像一些稳定币以及较为稳定的资产可以使用Fixed Range模型从中选择一些比较狭窄的区间,比如稳定币可以把价格区间设置在0.997~1.003之间,在这个范围内可以收取流动性及手续费奖励。一些较为稳定的资产使用Fixed Range可以使用较为广泛的价格区间,在享受uniswap V3高速交易的同时也不用过分担心由于市场价格波动导致的无常损失。One Side模型更适合一些Gamefi及web3项目,这些项目的token价格波动性会比较大,One Side可以提供单面的无常损失保护,增强向下的流动性深度。第三种模型Dynamic Range可以为那些大型项目服务,这种模型可以为LP提供者在当前的价格区间提供奖励,也许此种模型下无常损失不可避免,但是给予的流动性补偿是会大于流动性损失的。流动性碎片化不可避免,但是我们相信uniswap V3才是用户体验的destination。 66 | 67 | Nina:感谢yond专业的讲解,有些问题可能我还需要和你再单独多交流学习,通过你的讲解大家也更加了解了uniswap V3的机制以及选择izumi三种模型方案的原因,很受用。接下来我们知道imtoken上个月刚刚过完6周岁的生日,也知道你们最近一直在致力于layer2的兼容,大家都说今年是layer2的元年,在这样一个时期imtoken会扮演一个什么样的角色呢,imtoken有些什么样的致力于用户体验优化的行动可以和大家分享一下吗? 68 | 69 | Kiko:从2016年到2022年,我们已经6岁了,imtoken作为ETH生态第一个移动端钱包已经伴随着区块链的发展经历了许多不同的时代,作为用户进入web3世界的入口,钱包很大程度上决定了用户的体验感和对安全等级的判定,提供安全好用的用户体验是钱包产品的第一优先方向。过去很少有大规模的链与链之间的迁移,更多是新的公链独立发展,并不断壮大的过程。随着2020年DEFI夏季的出现,配合各类EVM公链间的兼容,各种标准、规范间的冲突逐渐被抹平,这就催生了大家对于目前的多链钱包上面各类跨链协议的使用需求。短期来看同构链间的跨链方案会大幅改进,各类三方桥以及各个公链的官方桥都是钱包首选去兼容去集成的对象,与此同时也产生了跨链效率以及安全的隐患。中长期来看的话,钱包会逐渐支持多链多网络,能无缝在网络间使用不同的dapp并提供资产跨链方案,同时集成法币及信用卡之类的OTC渠道。 70 | 71 | Alfred:关于理想的跨链体验,这是一个比较大的话题,今天来看是一种展望,我相信不同的从业者及用户都会有自己不同的看法。我们在近期发布的Anycall白皮书里对此有所阐述,对于多链生态以及多链的应用环境发展,对于DEX应用的形态以及带来的影响,我们认为这两者之间是互为推动互为成就的关系。讨论这个问题就不得不回溯一下多链生态以及跨链桥产生的原因,首先多链生态是围绕着兼容EVM的layer1公链间形成的生态,诞生的原因是ETH上面DEFI的发展产生了溢出效应。彼时并没有如今Arbitrum之类的layer2落地方案,在这样的情况下Defi应用要迁移到另一条兼容EVM的公链上,就会对数字资产的跨链提出需求。早期的跨链就是围绕解决资产交互的问题而产生的,我们认为多层多链是一个必然的趋势,对于ETH而言它扩充了ETH的容量,解决了上面提到的早期生态产生的部分原因,也进一步巩固了ETH在DEFI领域的优势地位。同时我们不认为目前兼容EVM的其他Layer1公链会消亡,因为从DEFI市场的角度来讲,区块链应用没有了地域界限,但是生态是以链为单位呈现的,我们认为未来即使是相同或者相似体系的区块链之间也会在应用和生态上呈现出个性化。第二方面我们看到很多非EVM兼容的区块链也在抓紧构造自己的生态,并且也提出了多层多链的建设方向,其中一个重要的需求是通过layer2的方式来实现对于EVM的兼容,目的是吸引用户和资金进入到他们的生态中去。基于这样的现状,我们预测未来的多链生态较之目前会具有更大的多样性。目前还处于多链交互的早期阶段,从DEX的应用特点来分析,目前主要的应用形态是多链部署单链运行的,随着多链生态的不断发展,必然会出现同时架构在两条甚至多条链上的跨链DEX,可以在多链部署,同时在多链上执行统一的业务逻辑,类比一下就有点像单机应用向多机网络化应用的发展,成为真正的crosschain DEX。 72 | 73 | Nina:是不是可以简单总结一下,就是无论对于开发者还是普通用户来说,大家在理想情况下都不太需要考虑多链多层这样的问题,而是由跨链的协议来统一满足用户的体验需求。对于用户来说他们只要正常交互就可以,对于协议开发者而言他们只要使用这个技术解决方案就可以了。 74 | 75 | Alfred:对,我们认为是这样的发展方向。就像我们现在使用一些移动互联网和互联网同时交互的应用时,开发者和使用者并不需要过多考虑底层计算的实现方式。 76 | 77 | Nina:谢谢Alfred,这也和我们Arbitrum的叙事非常相像,我们是layer2的技术解决方案,整个团队也非常以技术为核心,很多时候我们也不太奢望大家去了解我们内部的架构是如何如何。对于用户而言我们希望有一天大家在Arbitrum交互的时候就像在ETH进行交互一样,未来主网和二层的用户将不分彼此汇同一体,这是我们对于layer2未来的展望。Aboard Exchange是Arbitrum的原生项目,最早也是我来和你们对接的,对于Aboard我的问题是为什么一开始选择了layer2,选择了Arbitrum呢? 78 | 79 | J:关于部署在Arbitrum的逻辑是首先我们肯定要选择部署在最有前景的公链上,从现有规模、代币经济模型或者ESG的角度来讲肯定毫无疑问就是ETH,但是现在ETH一层已经不适合Aboard这种衍生品DEX直接部署了,所以二层就是最好的选择。回溯来看,Arbitrum的优势是多重的,第一是性能好、安全性高,相信随着Nitro的升级整体性能肯定会更上一层楼;第二是衍生品的用户生态比较成熟,社区有很多非常专业的有衍生品交易经验的用户,这对于DEX的成长和生存是很重要的;第三它兼容EVM,对于我们和其他开发者来说都是更加友好的,未来也会有更多的开发者加入crypto,所以其生态会有很大的扩张空间;最后,Arbitrum对于项目的支持力度很大,无论是早期项目刚上线的时候还是现在,Arbitrum和Nina都对Aboard提供了很多的帮助和支持,所以说“橘生淮南则为橘,生于淮北则为枳”(兄弟文化人啊)所以我相信Arbitrum是Aboard最好的生长土壤,未来也会给大家结出硕果,请大家多多关注我们的推特和社群,谢谢大家。 80 | 81 | Nina:谢谢J的一通狂奶,我也非常开心看到Aboard可以在Arbitrum生态中一步步成长起来,期待未来Aboard可以更佳茁壮成长,更多和我们的社区进行互动,我们也可以有更多合作的机会。 82 | 83 | 今天的采访就先告一段落了,相信大家也没有听够各位嘉宾老师的分享,大家可以关注各位老师的推特,关注他们所代表项目的推特以及社区discord,进行交流。如果大家对于刚刚各位老师提到的Odyssey活动期间的福利有问题的话,一定要去他们的社区提问,积极参与。作为Arbitrum官方我这里还有一个小福利,可以告诉大家如何在Odyssey活动期间更好地完成任务,这点由我们的Jason和大家说。 84 | 85 | Jason:相信大家也看到了我和Nina都发了一个推特,关于Odyssey活动期间可能到来的网络流量激增问题,我们的公共rpc届时响应速度可能没有那么快,大家可以添加一些第三方rpc进行备用,具体的添加方式我和Nina已经发了推特,大家可以点进去进行查看。除此之外我们马上要公布一个翻译计划,请大家帮助我们将一些英文文档翻译成中文,使得更多社区爱好者参与到Arbitrum的建设中来。翻译计划很快会公布,我们会将相关消息及时公布在discord以及github里面,github有如何搭建第三方rpc的中文教程,希望大家留用。 86 | 87 | (https://github.com/arbitrum-cn/Arbitrum-Library/blob/main/rpc-tutorials/README.md 这里是我们的第三方节点搭建中文教程,喂饭级别的,非常简单,一共7大供应商,大家可以选自己喜欢的进行尝试。) 88 | 89 | Nina:好的,大家要善用我们第三方的免费rpc,当然这个rpc的稳定性和链上稳定性不是一回事,rpc的响应速度和Arbitrum公链性能及区块处理速度不是一个概念,更和安全性无关。我们的公共rpc是有一个承压限制的,之所以给大家不厌其烦地讲述第三方自建rpc的好处也是为了让大家能在活动期间拥有一个更好的交互体验。我这里选取了一些社区提出的问题,现在在这里进行一下解答。 90 | 91 | 问:跨链周期间最受用户欢迎的跨链桥,指的是参与用户最多的跨链桥,还是跨链金额最多的跨链桥呢? 92 | 93 | 答:指的是跨链金额最多的产品。 94 | 95 | 问:ETH有最低跨链数目要求吗? 96 | 97 | 答:没有最低数目要求。 98 | 99 | 问:如果由于时间问题,错过了一些NFT,是否有补救的措施,如果没有凑齐13个,是否有办法可以参与最终NFT的兑换? 100 | 101 | 答:活动的NFT是可以被转赠或者在NFT二级市场进行交易的,官方不会做这样的交易,完全由市场主导,错过的人可以在市场购买获得。 102 | 103 | 问:之前交互过程中已经做过任务的,是否可以直接领取NFT? 104 | 105 | 答:不可以。每个项目都有自己的任务周期,必须要在指定的任务周期内完成,才可以获得NFT奖励。 106 | 107 | 问:一共多少个NFT? 108 | 109 | 答:16个普通NFT加上一个终极NFT,一共17个。你需要在16个普通NFT当中获得至少13个,才可以兑换到终极NFT。 110 | 111 | 问:Multichain目前支持从哪些链将ETH跨入Arbitrum? 112 | 113 | 答:目前开通的有ETH、BSC、Fantom、polygon、AVAX,以上这些链都支持将ETH跨入Arbitrum。 114 | 115 | Nina:谢谢各位嘉宾抽出宝贵的时间,来参与我们的AMA。大家也不要忘了每周二晚上8点,我们都会聚在这里,大家一起聊一聊layer2以及Arbitrum的生态技术,谢谢大家的参与。下周见! 116 | 117 | (感谢猴哥@SilenTGoKu的记录!) 118 | -------------------------------------------------------------------------------- /Share/Articles/ArbitrumGas.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Gas机制 2 | ## gas计算 3 | L1 fixed cost和L1calldata主要是放在layer1上面存档的开销,即用户的交易数据在压缩以后上传到inbox合约所需要在layer1支付的gas花销,也是二层协议的主要gas开销。 4 | L2 computational cost和L2 storage cost是在layer2执行开销和状态变化存储的交易开销,在计算layer2交易的gas开销的时候是需要将以下四项的开销加起来计算的。 5 | 6 | `L1 fixed cost` 7 | 8 | `L1 calldata cost` 9 | 10 | `L2 computational cost` 11 | 12 | `L2 storage cost` 13 | ## gas limit 14 | layer1的gas limit受限于区块大小,但是layer2并不是这样,Arbitrum现在的打包方式是先到先打包,所以并没有对gas limit有太多限制,而是使用了arbGas,这里的arbGas仅仅指的是L2 computational cost,可以看到每秒钟定序器能处理的arbGas limit是120K,而arbGas pool就和layer1区块的gas limit一致,每隔一段时间就会将arbGas pool加满,而不是像layer1一样更新,如果在一段时间内arbGas pool消耗完了,为了保证layer2的稳定和防止DDoS攻击,定序器就会拒绝交易。对于单笔交易的gas限制是2.4m。(仅限于Arbiitrum classic版本) 15 | - arbGas limit per second:120K 16 | - arbGas pool:20m 17 | - arbGas limit per tx:2.4m 18 | ## gas estimate 19 | layer1有JSON-RPC,layer2也有JSON-RPC,gas estimate指的是每次通过JSON-RPC发起一笔交易的交易gas费预估。由于layer2的gas开销和layer1息息相关,是L1+L2的gas花费,而且L1的gas是随时间波动的,所以有可能A时间获得的gas estimate在B时间就无法通过,所以最好是在每次发起交易前,都调用一次RPC获得新的gas estimate。 20 | ## 二维度的gas 21 | 由于arbitrum中的gas花销由上诉四部分所组成,但对于开发者来说,gas_price * gas_limit 是一个已经习惯使用的方法,因此如果l2要求开发者换一种新方式进行gas计算,那么将加大开发者的开发难度。 22 | 23 | 因此,arbitrum中也通过了一系列方式实现了这种方式的计算,我们取`l2 compuational gas price`(也就是`arbgas_price`)作为该算式的gas_price部分,那么gas_usage的使用方法将变为(`L1 fixed cost` + `L1 calldata cost` + `L2 computational cost` + `L2 storage cost`)/`arbgas_price`,展开可得`l2 computaional gas usage` + (`L1 fixed cost` + `L1 calldata cost` + `L2 storage cost`)/`arbgas_price`, 因此我们可以看到,随着l1 gas的变化或者`arbgas_price`的变化,最终的gas usage会出现不相同的情况,这也是为什么在奥德赛期间l2的`gas usage`和以往相似交易的开销相差较大的原因。 24 | 25 | 特别感谢社区爱好者0xCryptolee对参与此文的贡献。 26 | -------------------------------------------------------------------------------- /Share/Articles/ArbitrumNodeMap.md: -------------------------------------------------------------------------------- 1 | # Arbitrum节点工作流 2 | 3 | 4 | 在arbitrum中,节点有多种类型,而在arbitrum one中,最主要的为sequencer, validator和fullnode, 这三种节点的分工为: 5 | 6 | - sequencer:拥有排序交易的权限,由于可以排序交易,因此可以在收到用户的请求后直接将用户的交易定序,由于交易被定序后,就知道了交易的前序状态(既然此交易被定序,那么之前的交易也被定序了,也就能按照之前的交易顺序执行出状态结果),再加上该交易的运行,便可获得该交易执行后的状态,因此可以立即将执行结果返回给用户。同时sequencer也负责将l2的交易摘要发送给l1. 7 | - validator:对状态进行质押。在sequencer将交易定序后,用户可以直接相信sequencer的结果,但sequencer不对执行结果做保证(只对顺序做保证),因此需要一方来对交易结果做保证,而这一方则是validator。 8 | - fullnode:与其他网络的fullnode一样,可以在本地验证交易,也可以转发交易,但是没有出快权。 9 | 10 | 我们可以在下方看到这三种节点的工作流: 11 |

12 | 13 |

14 | 在上图中,我们可以把arbitrum one中的工作流分为两个部分,一个是以inbox合约为流程的,另一个是由rollup合约为流程的,而这两个部分分别对应了两种类型的链:Sequencer链和Rollup链。 15 | 16 | ## Sequencer chain 17 | Sequencer Chain指的是Sequencer(定序器,对二层交易进行打包排序的全节点,相当于二层网络的出块节点,不同于其他的layer1网络,Sequencer节点不需要通过共识算法来竞争出块权,而是直接具有打包区块的权力,目前Arbitrum的Sequencer由官方运行,出块权完全由官方掌握)对Rollup中交易进行打包进layer2区块并用链式结构连接起来的区块链,其数据结构和一般区块链的数据结构类似,Layer2的交易数据都通过Sequencer Chain来储存,用户发起一笔交易,通过RPC(远程过程调用)将交易发送给RPC node,RPC node再转发给Sequencer节点,Sequencer在接收到用户的交易之后立即对交易进行打包处理,实现layer2交易的初级确认。在收集到一段时间内的交易以后,Sequencer会将这段时间内的交易组成一个批次,经过压缩以后以calldata的形式将交易数据上传到Arbitrum部署在以太坊Layer1的Sequencer inbox合约中,以实现将数据可用性放在以太坊主网,而将执行迁移到layer2的设计,至此,实现了layer2的第二步确认。 18 | 19 | 20 | 21 | ## Rollup chain 22 | 前文我们提到,layer2的交易在Sequencer被打包以后以calldata的形式传到layer1的inbox合约中,那谁来替我们验证存到layer1的数据是否是真实的呢,Sequencer在上传的时候是否加入了虚假交易,这个时候就得说到Optimistic Rollup特殊的欺诈证明机制了。Sequncer传到inbox合约的数据都是公开透明的,可以被全网的参与者监督,这也是欺诈证明的基础。因为作恶者永远不知道有多少参与者在监督着链上的数据。这时候引入了另外一条链,即Rollup Chain,你也可以将之称为验证者链。每一个人在条件允许的情况下,都可以根据inbox合约中的calldata数据在自己的本地运行一条Sequencer链,因为inbox合约中的数据是还原layer2状态的最少数据,人们可以在本地运行所有的交易,以确保Sequencer没有作恶,但是真的要所有人来维护Arbitrum状态吗?显然是不可能的,只有类似交易所这样在Arbitrum拥有大量资产的参与者会自己在本地来维护Arbitrum状态,来确保自己的资产在Arbitrum是安全的。这就是为什么说会有很多的Sequencer Chain的原因,只不过仅有Sequencer自己运行的那条Sequencer链才是主要维持网络的链。而Rollup Chian则是网络中的验证者对于网络状态质押产生的链,其运行在以太坊主网,比方说在一段时间内,验证者会对Sequencer上传到Inbox合约中的状态进行质押,即验证者押注正确的交易,通过质押ETH发布断言,声称质押这段交易历史是正确的,如果所有的交易都是正确的,那么链的状态将会持续推进更新,可如果此时有人做恶,质押了错误的交易状态,这个时候网络中的正确验证者和作恶者产生冲突,正义验证者会押注正确的状态,此时Rollup chain出现分歧,正义验证者和作恶者通过类似二分法的方式找到存在争议的交易,查找争议交易数据的这些操作在layer2执行,当明确争议区间以后,由部署在layer1的Rollup合约重新执行争议交易,并裁决双方的断言,最后作恶者会被罚没质押的ETH,其中的一部分会奖励给正义验证者,以奖励其对网络的保护。为了防止作恶者攻击网络,罚没的ETH会有一部分销毁,防止正义验证者和作恶者串通,零成本攻击网络。而这就是Rollup Chain和欺诈证明。简单叙述就是网络中的众多参与者都在本地运行完整的layer2的状态,当发现有恶意交易企图篡改链的状态的时候,众多参与者会通过欺诈证明推进链的状态朝着正确的方向前进,而欺诈证明的挑战期为7天,7天以后交易状态确认,无法再进行修改。这是layer2交易的第三级确认,在以太坊主网不分叉的前提下,第三级确认即为layer2交易的最终确认状态。 23 | 24 | 特别感谢社区爱好者0xCryptolee对参与此文的贡献。 -------------------------------------------------------------------------------- /Share/Articles/AssertioTree.md: -------------------------------------------------------------------------------- 1 | ## Assertion Tree(断言树) 2 | 3 | ### 概述 4 | 5 | Arbitrum链的状态,通过"断言(assertions)",又称 "可争议断言 "或"DA",在以太坊上得到确认。这些是Arbitrum验证者对链的状态提出的claim。为了做出断言,验证者必须用以太币发布保证金。 6 | 7 | 在正常的情况下,所有未完成的断言都是有效的;也就是说,一个有效的断言会建立在另一个有效的断言上,而后者又建立在另一个有效的断言上——如此循环。在争议期(约1周)过后,如果一个断言没有受到质疑,那它会被确认回到L1上。 8 | 9 | 然而,如果存在两个或更多相互冲突的断言,断言树就会分叉成多个分支。 10 | 11 |

12 | 13 |

14 | 15 | 最重要的是,推进Arbitrum链的规则是具有确定性的;这意味着,一个链的既定状态和一些新的输入,只有一个有效的输出。因此,如果断言树包含一个以上的叶子(leaf),那么最多只有一个叶子可以代表有效的链上状态;如果我们假设:至少有一个诚实的活跃验证者,那么正好有一个叶子是有效的。 16 | 17 | 两个相互冲突的断言可以被放入一个争议(dispute)中;关于争议过程的细节,请看[交互式挑战](https://developer.offchainlabs.com/proving/challenge-manager)。为了理解断言树协议,我们只需要理解,双方争端最多持续一个固定的时间(一周)——在这个时间结束时,两个冲突的断言中,有一个将会被拒绝,而发布该断言的验证者将失去他们的质押。 18 | 19 | 为了使一个断言得到确认,并使其质押得到恢复,必须满足以下两个条件:必须有足够的时间进行争议,并且断言树中不能存在其他冲突的分支(即它们都已被争议/"修剪"掉了)。 20 | 21 | 这些属性共同确保了,只要至少有一个诚实的、活跃的验证者存在,那么有效的链上状态最终将会被确认。 22 | 23 | ### 延迟 24 | 25 | 即使断言树有不止一个冲突的叶子,比如说,有多个正在进行的争议,那么验证者也可以继续做出断言;诚实的验证者将简单地建立在一个有效的叶子上。同样的,用户可以继续在L2上进行交易,因为交易会继续在链上收件箱(inbox)中发布。 26 | 27 | 用户在争端期间经历的唯一延迟是他们[从L2到L1的信息](https://developer.offchainlabs.com/arbos/l2-to-l1-messaging)(即 "他们的withdrawals")。请注意,"延迟攻击者"(delay attacker) 试图通过故意造成这种延迟来破坏系统,会发现这种攻击的代价相当之高,因为每获得一点延迟时间,攻击者就会失去另一份质押。 28 | 29 | ### 详细规格 30 | 31 | 关于断言树协议的更详细的分解/规范,请看[Inside Arbitrum](https://developer.offchainlabs.com/inside-arbitrum-nitro#arbitrum#rollup#protocol)。 32 | 33 | 34 | [原文链接](https://developer.offchainlabs.com/assertion-tree) 35 | -------------------------------------------------------------------------------- /Share/Articles/Inbox.md: -------------------------------------------------------------------------------- 1 | # Inbox 2 | Inbox是用于存放l2 da(数据可用性)的合约,该合约保证了l2上的数据可用性,数据可用性是确保l2安全最关键的一环,其原因在于该da包含了l2上交易基本的信息,见[sequencerMessage](./sequencerMessage.md),并且这些交易也按照顺序进入了inbox,因此后续任何人可以通过读取l1中的inbox获得历史交易摘要,并将这些交易重新执行,便可获得l2上最新的状态,因此任何人都可以验证l2上的状态,并且只需要有一人诚实,用户便可获得正确的状态(或者用户自行运行node来验证)。 3 | 4 | 这一切使得l2中的安全保障能控制为1/n(即网络中有n个节点,我们只需要1个诚实,结果便可正确),而这一切最关键的地方则是l1上的inbox合约,下面,我们将解析inbox合约处理消息的过程: 5 | 6 | ## Sequencer Inbox 7 | Sequencer Inbox中包含了l2交易的输入信息(也就是da信息),点击[此处](https://github.com/OffchainLabs/nitro/blob/master/contracts/src/bridge/SequencerInbox.sol)访问代码。 8 | 在inbox里,我们可以看到有以下变量,其中包含了inbox中消息的摘要汇总,bridge合约地址, batch规范信息,时间间隔信息(如delayed inbox中的交易最早可以多久被并入inbox中),以及数据可用下委员会的密钥信息(主要用于anytrust): 9 | ``` 10 | uint256 public totalDelayedMessagesRead; 11 | 12 | IBridge public bridge; 13 | 14 | /// @dev The size of the batch header 15 | uint256 public constant HEADER_LENGTH = 40; 16 | /// @dev If the first batch data byte after the header has this bit set, 17 | /// the sequencer inbox has authenticated the data. Currently not used. 18 | bytes1 public constant DATA_AUTHENTICATED_FLAG = 0x40; 19 | 20 | IOwnable public rollup; 21 | mapping(address => bool) public isBatchPoster; 22 | ISequencerInbox.MaxTimeVariation public maxTimeVariation; 23 | mapping(bytes32 => DasKeySetInfo) public dasKeySetInfo; 24 | ``` 25 | 由于sequencerInbox是用于存放da信息的,因此同样也对应如何将数据'存放'入合约,当然,该存入并不是指写入合约状态,而是通过calldata的方式进行保存,这种方式虽然无法被其他合约链上读取,但是可以极大的减少da的开销。以下我们将介绍2种将da写入合约的方式: 26 | 27 | ## 1. sequencer直接写入batch 28 | 以下函数是inbox合约中最常被使用到的函数 `addSequencerL2Batch`, 该函数被sequencer用于添加新的交易batch。 29 | ``` 30 | function addSequencerL2Batch( 31 | uint256 sequenceNumber, 32 | bytes calldata data, 33 | uint256 afterDelayedMessagesRead, 34 | IGasRefunder gasRefunder 35 | ) external override refundsGas(gasRefunder) { 36 | if (!isBatchPoster[msg.sender] && msg.sender != address(rollup)) revert NotBatchPoster(); 37 | 38 | (bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash( 39 | data, 40 | afterDelayedMessagesRead 41 | ); 42 | // 将calldata长度设置为0是因为调用者非该交易的起发者,因此它可能没有为该input的calldata花费。 43 | ( 44 | uint256 seqMessageIndex, 45 | bytes32 beforeAcc, 46 | bytes32 delayedAcc, 47 | bytes32 afterAcc 48 | ) = addSequencerL2BatchImpl(dataHash, afterDelayedMessagesRead, 0); 49 | if (seqMessageIndex != sequenceNumber) 50 | revert BadSequencerNumber(seqMessageIndex, sequenceNumber); 51 | emit SequencerBatchDelivered( 52 | sequenceNumber, 53 | beforeAcc, 54 | afterAcc, 55 | delayedAcc, 56 | afterDelayedMessagesRead, 57 | timeBounds, 58 | BatchDataLocation.SeparateBatchEvent 59 | ); 60 | emit SequencerBatchData(sequenceNumber, data); 61 | } 62 | ``` 63 | 64 | ## 2.通过delayed inbox写入 65 | 除了最常用的sequencer直接写入batch以外,用户还可以选择发送忘delayed inbox并在间隔期(Sequencer-only window,目前是1天)过后将其forceInclusion,下面是该代码解析。 66 | ``` 67 | /// @inheritdoc ISequencerInbox 68 | function forceInclusion( 69 | uint256 _totalDelayedMessagesRead, 70 | uint8 kind, 71 | uint64[2] calldata l1BlockAndTime, 72 | uint256 baseFeeL1, 73 | address sender, 74 | bytes32 messageDataHash 75 | ) external { 76 | //检查读取是否正确 77 | if (_totalDelayedMessagesRead <= totalDelayedMessagesRead) revert DelayedBackwards(); 78 | bytes32 messageHash = Messages.messageHash( 79 | kind, 80 | sender, 81 | l1BlockAndTime[0], 82 | l1BlockAndTime[1], 83 | _totalDelayedMessagesRead - 1, 84 | baseFeeL1, 85 | messageDataHash 86 | ); 87 | // 需要在Sequencer-only window结束后才可forceInclusion 88 | if (l1BlockAndTime[0] + maxTimeVariation.delayBlocks >= block.number) 89 | revert ForceIncludeBlockTooSoon(); 90 | if (l1BlockAndTime[1] + maxTimeVariation.delaySeconds >= block.timestamp) 91 | revert ForceIncludeTimeTooSoon(); 92 | 93 | // 检查message hash是否代表未被确认的首个delayed交易 94 | bytes32 prevDelayedAcc = 0; 95 | if (_totalDelayedMessagesRead > 1) { 96 | prevDelayedAcc = bridge.delayedInboxAccs(_totalDelayedMessagesRead - 2); 97 | } 98 | if ( 99 | bridge.delayedInboxAccs(_totalDelayedMessagesRead - 1) != 100 | Messages.accumulateInboxMessage(prevDelayedAcc, messageHash) 101 | ) revert IncorrectMessagePreimage(); 102 | 103 | (bytes32 dataHash, TimeBounds memory timeBounds) = formEmptyDataHash( 104 | _totalDelayedMessagesRead 105 | ); 106 | // 将该交易并入合约中 107 | ( 108 | uint256 seqMessageIndex, 109 | bytes32 beforeAcc, 110 | bytes32 delayedAcc, 111 | bytes32 afterAcc 112 | ) = addSequencerL2BatchImpl(dataHash, _totalDelayedMessagesRead, 0); 113 | emit SequencerBatchDelivered( 114 | seqMessageIndex, 115 | beforeAcc, 116 | afterAcc, 117 | delayedAcc, 118 | totalDelayedMessagesRead, 119 | timeBounds, 120 | BatchDataLocation.NoData 121 | ); 122 | } 123 | ``` 124 | 关于如何调用以及获得相应参数,欢迎查看[Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk/blob/c-nitro-migration/src/lib/inbox/inbox.ts#L102) 125 | 126 | 注意,虽然在以上合约中有处理消息信息,但消息本身没有被写入状态,仅仅只是通过calldata记录。 -------------------------------------------------------------------------------- /Share/Articles/README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum技术教程 2 | 该部分是arbitrum技术教程,欢迎各位刚进入arbitrum的开发者阅读: 3 | 4 | ## 入门: 5 | 1. [Arbitrum节点工作流](./ArbitrumNodeMap.md) 6 | 2. [Arbitrum inbox信息流程](./inboxMessage.md) 7 | 3. [rollup合约](./RollupsContract.md) 8 | 4. [Arbitrum Gas机制](./ArbitrumGas.md) 9 | ## 进阶: 10 | 1. [如何从Arbitrm sequencer消息中获得交易信息](./sequencerMessage.md) 11 | 2. [Inbox](./Inbox.md) 12 | 3. [断言树](./AssertioTree.md) 13 | 4. [L1至L2信息传递](./l1ToL2Msg.md) 14 | 15 |

16 | 17 |

-------------------------------------------------------------------------------- /Share/Articles/RollupsContract.md: -------------------------------------------------------------------------------- 1 | # rollup合约 2 | ## 什么是Rollups合约 3 | Rollups合约管理了l2上的状态,与inbox合约不同(inbox存放交易摘要),rollups合约会存放validators对于l2状态的断言质押,并允许其他人对该断言进行挑战。由于rollups合约管理了l2的状态,因此也包含了l2->l1消息状态的管理,因此在状态段演的挑战期结束后,我们便可认同该状态,同时释放用户资金。 4 | 5 | validator是rollups合约的主要参与方,其可以提出断言,可以挑战断言,也可以确认断言(断言提出的7天后)。validators在arbitrum中主要有以下三种: 6 | 7 | - Validator: 8 | - 主动验证者: 9 | 通过提议新的Rollup区块来推进Rollup链的状态,由于提议Rollup区块需要质押,因此活跃的验证者需要一直提供质押,一条链只需要一个诚实的活跃验证者就可以不断推进Rolllup链的状态更新,因为它将始终会质押在正确的状态上面,当有攻击者质押错误状态的时候就会触发挑战。 10 | 11 | - 防御验证者: 12 | 防御型验证者并不主动参与Rollup链的状态更新,而是监视着Rollup协议的运行,如果主动验证者所提出的Rollup区块都是正确的,那么防御验证者不会参与网络。但是如果有恶意验证者提议了不正确的Rollup区块,那么防御验证者将会自行提议正确区块或者在其他已经提议的正确区块上面质押来干预。最后通过Rollups合约中的Challenge合约来裁决。在这个过程中,是存在经济博弈的,即作恶验证者需要质押资产才能提议新的Rollup区块,如果其质押了错误的状态,那么防御验证者可以通过质押正确的状态来获得作恶验证者被罚没的质押作为奖励。同时,为了防止作恶验证者和正义验证者串通进行无成本的作恶,正义验证者在接受作恶验证者的质押作为奖励时,仅能获得作恶验证者被罚没资产的一半。 13 | 14 | - 瞭望塔验证者: 15 | 瞭望台验证者从不质押,它只是监视Rollup协议网络,如果有恶意验证者提议了不正确的区块,瞭望塔验证者会发出警告,比如告知防御验证者去质押正确的状态来获取作恶验证者的质押作为奖励。 16 | 17 | ## Rollups区块 18 | 由验证者打包生成的区块,主要信息为Rollups相关信息,比如挑战根,L2->L1的消息根,质押信息等。 19 | 20 | ## Arbitrum Rollup区块 21 | Rollups系列合约主要负责对Arbitrum上的Rollups区块,L2->L1的Withdraw交易以及欺诈证明负责。[合约文件夹传送门](https://github.com/OffchainLabs/nitro/blob/master/contracts/src/rollup) 22 | - Node工厂合约(Nitro升级之后为lib): 23 | 用于创建Node合约 24 | - Node合约(Nitro升级之后为Struct): 25 | 即Rollup区块,需要注意的是每个Rollups区块都有一个对应的合约。 26 | - Challenge工厂合约: 27 | 用于创建Challenge合约。 28 | - Challenge合约: 29 | 发生Challenge的合约,每个Challenge对应一个合约 30 | - Rollup合约: 31 | 管理rollup协议,调度用户对于rollup系列操作的调用 32 | 33 | ## Arbitrum Rollup合约 34 | Rollups主要函数 35 | ·CreateNewNode 36 | 创建新的rollup区块需要调用的函数。 37 | ``` 38 | function createNewNode( 39 | RollupLib.Assertion calldata assertion, 40 | uint64 prevNodeNum, 41 | uint256 prevNodeInboxMaxCount, 42 | bytes32 expectedNodeHash 43 | ) returns (bytes32 newNodeHash) 44 | ``` 45 | 46 | - CreateNewStake 47 | 网络加入一个新的验证者进行质押存款需要调用的函数 48 | ``` 49 | function createNewStake(address stakerAddress, uint256 depositAmount) 50 | ``` 51 | 52 | - ConfirmNode 53 | 七天挑战期结束以后,确定一个区块内交易合法需要调用的函数。 54 | ``` 55 | function confirmNode( 56 | uint64 nodeNum, 57 | bytes32 blockHash, 58 | bytes32 sendRoot 59 | ) internal 60 | ``` 61 | 62 | 特别感谢社区爱好者0xCryptolee对参与此文的贡献。 -------------------------------------------------------------------------------- /Share/Articles/inboxMessage.md: -------------------------------------------------------------------------------- 1 | # Arbitrum inbox信息流程详解 2 | Arbitrum是建立在l1之上的l2网络,直接根植以太坊的安全性,除此之外,arbitrum选择使用Ethereum作为结算层,因此,自然涉及到资产在l2与l1之间的转换。而这其中,从l1到l2的资产转移合约为Inbox合约(Inbox合约除资产转移之外还有其他功能),本文将从代码层面讲解Inbox合约。 3 | 合约源代码仓库[传送门](https://github.com/OffchainLabs/nitro/blob/master/contracts/src/bridge/Inbox.sol) 4 | ## Message Type 5 | ``` 6 | uint8 constant L2_MSG = 3; 7 | uint8 constant L1MessageType_L2FundedByL1 = 7; 8 | uint8 constant L1MessageType_submitRetryableTx = 9; 9 | uint8 constant L1MessageType_ethDeposit = 12; 10 | uint8 constant L1MessageType_batchPostingReport = 13; 11 | uint8 constant L2MessageType_unsignedEOATx = 0; 12 | uint8 constant L2MessageType_unsignedContractTx = 1; 13 | 14 | uint8 constant ROLLUP_PROTOCOL_EVENT_TYPE = 8; 15 | uint8 constant INITIALIZATION_MSG_TYPE = 11; 16 | ``` 17 | 如上所示,l2中消息类型有多种,每种对应不同消息类型,关系详细解释,由于[官网](https://developer.offchainlabs.com/docs/arbos_formats#incoming-messages)已有阐述,因此本文不再复述。 18 | ## Deposit Eth 19 | ``` 20 | /// @notice deposit eth from L1 to L2 21 | /// @dev this does not trigger the fallback function when receiving in the L2 side. 22 | /// Look into retryable tickets if you are interested in this functionality. 23 | /// @dev this function should not be called inside contract constructors 24 | function depositEth() public payable override whenNotPaused onlyAllowed returns (uint256) { 25 | address dest = msg.sender; 26 | 27 | // solhint-disable-next-line avoid-tx-origin 28 | if (AddressUpgradeable.isContract(msg.sender) || tx.origin != msg.sender) { 29 | // 如果该合约函数在被任意constructor执行时调用,那么isContract的检查将会失败,因此需要加入对tx.orgin的检查 30 | // 我们不会调整来自 L1 合约的调用的地址,因为它们的地址被重新映射 31 | // 如果调用者是外部地址账户,那么我们将对其地址进行反向映射 32 | // 这是必需的,因为到 L2 的未签名消息(例如可重试)将会在发送到l2时对调用者地址进行映射 33 | // 如果我们先进性反映射,那么之后消息到达l2后将会是原本的调用者地址 34 | dest = AddressAliasHelper.applyL1ToL2Alias(msg.sender); 35 | } 36 | 37 | return 38 | _deliverMessage( 39 | L1MessageType_ethDeposit, 40 | msg.sender, 41 | abi.encodePacked(dest, msg.value) 42 | ); 43 | } 44 | ``` 45 | 该函数最关键处为对地址的映射调整步骤(可见上述代码中的中文注释),至于为什么消息在l1到l2时会对地址进行映射,推荐阅读[英文官网](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing)或是相关[分享视频](https://www.youtube.com/watch?v=sYo8DXvysJI)(中文) 46 | 47 | 之后,我们可以看到`_deliverMessage`里规范了消息类型(L1MessageType_ethDeposit),发送人地址和交易信息。 48 | 注:由于deposit eth不涉及到合约执行,且非转账(直接充值入dest地址),因此不会涉及到交易失败,因此nitro升级后deposit eth将被剥离出retryable类型。 49 | 50 | 51 | ## Retryable Ticket 52 | 可重试票证是arbitrum中使用来应对l1->l2消息中可能失败的场景,如在发起l1->l2 erc20转账时,有可能会因为一系列原因失败,但此时用户已在l1中向合约发送资产,如不能重新执行,则有可能使得用户代币丢失,因此我们设计了可重试票据,下面来看其工作流程: 53 | ``` 54 | function createRetryableTicket( 55 | address to, 56 | uint256 l2CallValue, 57 | uint256 maxSubmissionCost, 58 | address excessFeeRefundAddress, 59 | address callValueRefundAddress, 60 | uint256 gasLimit, 61 | uint256 maxFeePerGas, 62 | bytes calldata data 63 | ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) { 64 | // 保证交易过程中的msg.value足够支付交易中的其他费用 65 | if (msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) { 66 | revert InsufficientValue( 67 | maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas, 68 | msg.value 69 | ); 70 | } 71 | 72 | // 如果refund地址是合约地址,我们将对其进行映射 73 | // 因此该合约将可以操控其在l2中的资产 74 | // 这是由于refund地址在传送的我过程中不会映射,因此需要在此手动映射 75 | if (AddressUpgradeable.isContract(excessFeeRefundAddress)) { 76 | excessFeeRefundAddress = AddressAliasHelper.applyL1ToL2Alias(excessFeeRefundAddress); 77 | } 78 | if (AddressUpgradeable.isContract(callValueRefundAddress)) { 79 | // this is the beneficiary. be careful since this is the address that can cancel the retryable in the L2 80 | callValueRefundAddress = AddressAliasHelper.applyL1ToL2Alias(callValueRefundAddress); 81 | } 82 | 83 | return 84 | unsafeCreateRetryableTicket( 85 | to, 86 | l2CallValue, 87 | maxSubmissionCost, 88 | excessFeeRefundAddress, 89 | callValueRefundAddress, 90 | gasLimit, 91 | maxFeePerGas, 92 | data 93 | ); 94 | } 95 | 96 | ``` 97 | 可以看见,在发送可重试票据时,主要是做一些基础检查,以及地址映射。 98 | 除此之外,retryable的生命周期如下: 99 | 100 | 101 |

102 | 103 |

104 | 105 | ## delayed inbox 106 | delayed inbox是用户在sequencer无法连接或不信任sequencer时所会用到的一种方案。在正常的l2场景中,用户将交易发送给全节点,如果此时,因为各种原因,无法通过sequencer发送交易,那么用户可选择第二种方法:直接发送至delayed inbox,如下: 107 | 108 |

109 | 110 |

111 | 112 | 该方法原理是因为l2中所有交易都需要提取其摘要以calldata形式进入l1,因此除sequencer外,用户也可直接采取这种方式,具体调用函数为: 113 | 114 | ``` 115 | function sendL2Message(bytes calldata messageData) 116 | external 117 | override 118 | whenNotPaused 119 | onlyAllowed 120 | returns (uint256) 121 | { 122 | return _deliverMessage(L2_MSG, msg.sender, messageData); 123 | } 124 | ``` 125 | 126 | 这与之前提到的l1->l2消息不同,这种方式为原生的l2交易,且不会对sender地址进行映射,但不同是发送的messageData必须是通过私钥进行签名过的交易数据。 127 | -------------------------------------------------------------------------------- /Share/Articles/l1ToL2Msg.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## L1至L2信息传递 4 | ### Retryable(可重试) 5 | 6 | 可重试票据是Arbitrum创建L1到L2消息时,使用的最常见的方法——即在L1交易记录中开启一个在L2执行的消息,并且该消息可以重复执行(在有效期内)直到执行成功。一个可重试票可以按固定的费用 (只取决于它的calldata大小) 在L1提交;它在L1的提交与它在L2的执行是可分离且异步的。Retryable提供了跨链操作之间的原子性(atomicity);如果请求提交的L1交易成功了(即没有恢复),那么Retryable在L2的执行也有强大的保证,最终也会成功。 7 | 8 | 一个Retryable是通过「Inbox」的「createRetryableTicket」方法提交的。在常见的情况下,Retryable的提交后会有一次执行交易的尝试(即 "自动兑换")。如若尝试失败,或在Retryable提交后没有安排,任何人都可以通过调用「ArbRetryableTx」预编译的兑换(redeem)方法,尝试兑换它。请求兑换的一方提供将用于执行Retryable的gas费用。如果执行Retryable成功,Retryable会被删除。如果执行失败,Retryable将继续存在,可以进一步尝试兑换。如果一个固定的时间段(目前是一周)过去了而没有成功兑换,Retryable就会过期并被[自动丢弃](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/retryables/retryable.go#L262),除非有人支付了费用将Retryable再[续上](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/retryables/retryable.go#L207)一段时间。只要每次在过期之前续费,Retryable可以无限期存在。 9 | 10 | 11 | ### 提交Retryable 12 | 13 | 14 | 提交一次Retryable要做到以下: 15 | 16 | - 创建一个新的Retryable,包含caller、dest(目标地址)、callvalue、calldata和提交交易的受益人 17 | - 从caller那里扣除资金以支付call费(像往常一样),并将其放入托管(escrow),以便以后用于兑换Retryable 18 | - 为Retryable分配一个特殊的「TicketID」 19 | - 使「ArbRetryableTx」预编译的合同发出一个包含TicketID「TicketCreated」事件 20 | - 如果提交的交易包含gas,则安排对新的Retryable进行兑换,使用提供的gas,就像[「ArbRetryableTx」](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)预编译的[兑换](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)方法被调用一样。 21 | 22 | 23 | 在大多数情况下,提交者将提供gas,并希望能立即兑换成功,如果立即兑换失败,以后的重试只能作为一种备份机制。(例如:因为L2 gas价格意外地上涨——它可能会失败)。通过这种方式,L1合约可以向L2提交交易,交易通常会在L2立即运行,但允许任何一方进行重试——如果前一笔交易失败。 24 | 25 | 当Retryable被兑换时,它将以原始提交的发送者、目的地、callvalue和calldata执行。为了这个目的,在最初提交Retryable时,callvalue将被托管。如果一个有callvalue的Retryable最终被丢弃(它从未成功运行过),托管的callvalue将被支付给初始提交时指定的 "受益人 "账户。 26 | 27 | 一个Retryable的受益人有特殊的权力来[取消](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)Retryable。这与Retryable计时结束的效果相同。 28 | 29 | 30 | ### 兑换一个Retryable 31 | 32 | 如果在提交时没有进行兑换,或者提交的初始兑换失败,任何人都可以通过调用[「ArbRetryableTx」](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)的[兑换](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)预编译方法,再次尝试兑换Retryable,该方法将调用的gas捐赠给下一次尝试。ArbOS将把这个兑换(它是它自己的特殊ArbitrumRetryTx类型)[排列](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L245)到它的兑换表中,ArbOS会确保在转到它形成的区块中的下一个非兑换交易之前[用完](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L135)。通过这种方式,兑换被安排在尽可能短的时间窗口内发生,并且总是与安排它的交易在同一个区块中。请注意:兑换尝试的gas来自于对兑换的调用,所以在执行之前不会达到区块的gas限制。 33 | 34 | 在成功的状况下,「To」地址保持托管的callvalue,任何未使用的gas会被返还到ArbOS的gas池。失败时,callvalue将返回给下一个兑换者的托管。在这两种情况下,网络费用都是在调度交易期间支付的,所以不收取费用,也不退还费用。 35 | 36 | 在兑换Retryable的过程中,试图取消同一Retryable,或安排同一Retryable的另一次兑换,将会被退回。在这种方式下,Retryable程序是不可自我修改的。 37 | 38 | 39 | ### 收据(Receipts) 40 | 41 | 一个Retryable的收据的生命周期中会发出两种类型的L2交易收据: 42 | 43 | 票据创建收据。这种收据表示已经成功创建了一张Retryable票据;任何对「Inbox」的「createRetryableTicket」L1调用方法的成功都保证会生成一张票据。票据创建收据包括一个「TicketCreated」事件(来自[ArbRetryableTx](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx)),它将包括一个「ticketId」。这个「ticketId」可以通过RLP编码和散列交易来计算;见[calculateSubmitRetryableId](https://github.com/OffchainLabs/arbitrum-sdk/blob/6cc143a3bb019dc4c39c8bcc4aeac9f1a48acb01/src/lib/message/L1ToL2Message.ts#L109)。 44 | 45 | 兑换尝试。兑换尝试收据代表Retryable票据的L2执行尝试的结果。它包括一个来自 [ArbRetryableTx](https://developer.offchainlabs.com/arbos/precompiles#ArbRetryableTx) 的「RedeemScheduled」事件,有一个「ticketId」字段。对于一个给定的票据,最多只能有一次成功的兑换尝试;打比方说,如果初始创建时的自动兑换成功了,那么对于该票据,只有自动兑换的收据会被发出;如果自动兑换失败(或从未尝试过——即所提供的L2 gas限额*L2 gas价格=0),每次初始尝试都会发出一张兑换尝试收据,直到一次成功为止。 46 | 47 | ### 替代的 "不安全 "Retryable票据创建 48 | 49 | 「Inbox.createRetryableTicket」的“便利方法”,包括理智检查(sanity checks),以帮助最大限度地减少用户出错的风险:该方法将确保直接从L1提供足够的资金,以支付当前票据创建/执行的成本。它还将把所提供的受益人或信用回馈地址转换为其地址别名(见下文),如果这两个地址是同一个合同的话,就会为L1合同提供一条回收资金的路径。权威用户可以通过「Inbox」的「unsafeCreateRetryableTicket」方法绕过这些理智检查措施;正如该方法的名字在不断地尝试警告你的那样,只有真正清楚自己在做什么的用户才能访问它。 50 | 51 | ### ETH Deposit 52 | 53 | 有一种特殊的消息类型用于简单的ETH Deposit;即从L1到L2跨入ETH。ETH可以通过调用「Inbox」的「depositETH」方法来存入。如果L1的调用者是EOA,ETH将被存入L2的同一个EOA地址;如果L1的调用者是一个合约,资金将被存入合约的别名地址(见下文)。 54 | 55 | 注意,通过「depositETH」将ETH存入L2上的合约,不会触发合约的fallback功能。 56 | 57 | 原则上,Retryable票据也可以用来存入ETH;如果需要更灵活的目标地址,或者想在L2端触发fallback功能,这可能是比特殊的ETH-deposit消息类型更可取的方案。 58 | 59 | 60 | ### 通过延时收件箱(Delayed Inbox)进行交易 61 | 62 | 虽然Retryables和ETH Deposit必须通过延迟收件箱提交,但原则上,任何信息都可以通过这种方式提交(这是一个必要的手段),以确保Arbitrum链在序列器出现问题时,也能保留审查阻力(见序列器和审查阻力)。然而,在普通/happy的情况下,期望/建议客户只对Retryables和ETH Deposit使用延迟收件箱,而对所有其他信息通过序列器进行交易。 63 | 64 | 65 | ### 地址别名(Address Aliasing) 66 | 67 | 所有通过延迟收件箱提交的信息,其发件人的地址都是 "别名"(aliased)的:当这些无符号信息在L2上执行时,发件人的地址——即「msg.sender」返还的地址——将不只是仅仅给L1地址发送信息;而是将该地址的 "L2别名"。一个地址的L2别名是它的值增加了十六进制值0x1111000000000000000000000000000000001111: 68 | 69 | `L2_Alias = L1_Contract_ Address + 0x1111000000000000000000000000000000001111 ` 70 | 71 | Arbitrum合约对「L1-到-L2」消息使用L2别名的做法,防止了跨链漏洞,如果我们简单地重复使用与L2发送者相同的L1地址,就有可能出现这种情况;也就是说,通过从L1上预期的合约地址发送Retryable的票据,来欺诈期望从给定地址进行调用的L2合同。 72 | 73 | 如果出于某种原因,你需要从链上的L2别名计算L1地址,你可以使用我们的「AddressAliasHelper」库。 74 | ``` 75 | modifier onlyFromMyL1Contract() override { 76 | require(AddressAliasHelper.undoL1ToL2Alias(msg.sender) == myL1ContractAddress, "ONLY_COUNTERPART_CONTRACT") 。 77 | _; 78 | } 79 | ``` 80 | [原文链接](https://developer.offchainlabs.com/arbos/l1-to-l2-messaging 81 | ) -------------------------------------------------------------------------------- /Share/Articles/sequencerMessage.md: -------------------------------------------------------------------------------- 1 | # 详解如何从Arbitrm sequencer消息中获得交易信息 2 | Arbitrum中的sequencer可以定序用户交易,使得交易能瞬时确认。除此之外,sequencer还可作为其他全节点的feed器,全节点只需要链接到sequencer,并不断的舰艇feed信息,即可同步arbitrum网络中的状态。本文将从feed信息中详解full node在收取到feed信息后是如何从中提取交易信息的一些过程(目前本文仅适用于classic节点交易): 3 | 4 | 为了更好的获得sequencer的feed消息,您可以直接使用指令: 5 | `wscat -c wss://arb1.arbitrum.io/feed` 获得feed信息的订阅,下方是我们随便运行后得到的一组交易feed: 6 | ``` 7 | {"version":1,"messages":[{"feedItem":{"batchItem":{"lastSequenceNumber":39222805,"accumulator":[108,210,195,226,50,56,98,129,148,138,62,160,76,252,31,139,100,165,212,167,167,148,87,184,236,228,222,167,22,5,101,87],"totalDelayedCount":706410,"sequencerMessage":"A6SxCsYeeeoeFQ33C43aUzkZKP0UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoeB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYuNZ6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVn4VAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgYcH/w+EDzXaAYMJzUqUq7xfmWOcm2vLWFRN3wTvpoAvQGSAOMdN2QAAAAAAAAAAAAAAAD1rozHj2XAsXoqNJU5diihfIjq63WQYni+oBa5ulNyUj538KZKnESOcjMyx3+hCzPRKVQpMPjSyX3CYbSaTJE/vSfPBrpJC35xDd6Vf2hQEHLYSjgA="},"prevAcc":[113,217,121,246,71,90,245,191,67,243,101,177,64,209,218,90,128,11,70,215,226,234,81,155,143,138,221,144,180,76,6,243]},"signature":"csq6VAl0XDgqk26G9qLkiLQeYBYWfjSMFRanT1qROKUUw55+MeqDNzjG4LN8aJ8W0CvSFd+II59qDTdasqWZRQA="}],"confirmedAccumulator":{"isConfirmed":false,"accumulator":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}} 8 | ``` 9 | 该消息对应Arbitrum sequencer的[BroadcastMessage](https://github.com/OffchainLabs/arbitrum/blob/6c2d42e251c764859813db71c774dede3d00a289/packages/arb-util/broadcaster/types.go#L39),我们先来看看它的strcut结构: 10 | ``` 11 | type BroadcastMessage struct { 12 | Version int `json:"version"` 13 | Messages []*BroadcastFeedMessage `json:"messages"` 14 | ConfirmedAccumulator ConfirmedAccumulator `json:"confirmedAccumulator"` 15 | } 16 | ``` 17 | 可以看到,其中有三项,第一项为版本号,第二项为sequencer的消息信息。第三项为状态集成哈希。为了探解定序器消息信息,我们继续解剖第二项Message如下,并取得其中的SequencerFeedItem如下: 18 | ``` 19 | type SequencerBatchItem struct { 20 | LastSeqNum *big.Int `json:"lastSequenceNumber"` 21 | Accumulator common.Hash `json:"accumulator"` 22 | TotalDelayedCount *big.Int `json:"totalDelayedCount"` 23 | SequencerMessage []byte `json:"sequencerMessage"` 24 | } 25 | ``` 26 | 在该struct里,主要是和inbox交易序列有关的信息,而交易信息则主要在`SequencerMessage`里,因此我们继续查找得到该struct: 27 | ``` 28 | type InboxMessage struct { 29 | Kind Type 30 | Sender common.Address 31 | InboxSeqNum *big.Int 32 | GasPrice *big.Int 33 | Data []byte 34 | ChainTime ChainTime 35 | } 36 | ``` 37 | 同时该strcut也对应着刚刚获得的feed消息中的sequencerMessage: 38 | ``` 39 | A6SxCsYeeeoeFQ33C43aUzkZKP0UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoeB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYuNZ6gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVn4VAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgYcH/w+EDzXaAYMJzUqUq7xfmWOcm2vLWFRN3wTvpoAvQGSAOMdN2QAAAAAAAAAAAAAAAD1rozHj2XAsXoqNJU5diihfIjq63WQYni+oBa5ulNyUj538KZKnESOcjMyx3+hCzPRKVQpMPjSyX3CYbSaTJE/vSfPBrpJC35xDd6Vf2hQEHLYSjgA=" 40 | ``` 41 | 关直接看这串内容是无法获得任何信息的,这是因为该数据使用过base64编码,因此可以先使用base64解码器进行解码,获得数据如下: 42 | ``` 43 | 03a4b10a c61e79ea 1e150df7 0b8dda53 391928fd 14000000 00000000 00000000 00000000 00000000 00000000 00000000 0000e878 1d000000 00000000 00000000 00000000 00000000 00000000 00000000 0062e359 ea000000 00000000 00000000 00000000 00000000 00000000 00000000 0002567e 15000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00038187 07ff0f84 0f35da01 8309cd4a 94abbc5f 99639c9b 6bcb5854 4ddf04ef a6802f40 648038c7 4dd90000 00000000 00000000 00003d6b a331e3d9 702c5e8a 8d254e5d 8a285f22 3abadd64 189e2fa8 05ae6e94 dc948f9d fc2992a7 11239c8c ccb1dfe8 42ccf44a 550a4c3e 34b25f70 986d2693 244fef49 f3c1ae92 42df9c43 77a55fda 14041cb6 128e00 44 | ``` 45 | 当然,为了更好的查看数据顺序,大家也可以先参考[该函数](https://github.com/OffchainLabs/arbitrum/blob/6c2d42e251c764859813db71c774dede3d00a289/packages/arb-util/inbox/inboxMessage.go#L71) 尝试提取其中数据。 46 | 47 | 1.首先,我们获得第一个byte为0x03,该数据为[message type](https://developer.offchainlabs.com/docs/arbos_formats#incoming-messages) 48 | 49 | 2.第二个数据为`0xa4b10ac61e79ea1e150df70b8dda53391928fd14`,在struct里该数据为`sender`(20bytes)信息,由于是sequencer发送到l1的,因此该sender也为sequencer自己的地址信息。 50 | 51 | 3.第三部我们提取到`blocknumber`(32bytes)和`timestamp`(32bytes)信息分别为:`0xe8781d`和 `0x62e359ea`,即l1的区块高度和区块时间。 52 | 53 | 4.第四部继续提取其中的`inboxSeqNumber`(32bytes)信息,为`0x2567e15`,该数据的作用是标明该交易在inbox中的位置,以确定交易发生序号,这也是sequencer最主要的功能之一:为交易定序。 54 | 55 | 5.第五部提取交易的`gasPrice`(32bytes)信息,为`0x0`。 56 | 57 | 6.该部分我们将获得交易中的rlp编码后的data信息,拆分如下: 58 | 59 | ``` 60 | 1. 0f 为交易nonce 61 | 62 | 2. 84 为 0x84 - 0x80 = 4 bytes 63 | 64 | 获得对应后4bytes的数据为 0x0f35da01 (gasPrice) 65 | 66 | 3. 83 为 0x83 - 0x80 = 3 bytes 67 | 68 | 获得对应后3bytes的数据为0x09cd4a (gas) 69 | 70 | 4. 94 为 0x94 - 0x80 = 20 bytes 71 | 72 | 获得后20bytes的数据为0xabbc5f99639c9b6bcb58544ddf04efa6802f4064 (to) 73 | 74 | 5. 80 为 0x80 - 0x80 = 0 bytes, 也就是value为0 75 | 76 | 6. 该数据为交易的data数据:0x38c74dd90000000000000000000000003d6ba331e3d9702c5e8a8d254e5d8a285f223aba 77 | 首先获得前4bytes的数据0x38c74dd9为函数名,后32bytes数据为:0000000000000000000000003d6ba331e3d9702c5e8a8d254e5d8a285f223aba 78 | 79 | 7. 交易签名数据r: 0xdd64189e2fa805ae6e94dc948f9dfc2992a711239c8cccb1dfe842ccf44a550a 80 | 81 | 8. 交易签名数据s: 0x4c3e34b25f70986d2693244fef49f3c1ae9242df9c4377a55fda14041cb6128e 82 | ``` 83 | 由此,我们便获得了该交易的交易信息。该交易为:[0x50e6abe5e160358a71445457382b7b23b90c4199845396fda627c59f2f598732](https://arbiscan.io/tx/0x50e6abe5e160358a71445457382b7b23b90c4199845396fda627c59f2f598732),我们使用`eth_getTransactionByHash`获得信息如下: 84 | ``` 85 | {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x429f9272a4e3cd4bc9fbaadebf4001fcff00a094e1a6d56b64e12134efa2d8ae","blockNumber":"0x11f6f3c","from":"0x3abce4e29d700a3915209fdc46c81ffead2f3d5c","gas":"0x9cd4a","gasPrice":"0xf35da01","hash":"0x50e6abe5e160358a71445457382b7b23b90c4199845396fda627c59f2f598732","input":"0x38c74dd90000000000000000000000003d6ba331e3d9702c5e8a8d254e5d8a285f223aba","nonce":"0xf","to":"0xabbc5f99639c9b6bcb58544ddf04efa6802f4064","transactionIndex":"0x0","value":"0x0","v":"0x14986","r":"0xdd64189e2fa805ae6e94dc948f9dfc2992a711239c8cccb1dfe842ccf44a550a","s":"0x4c3e34b25f70986d2693244fef49f3c1ae9242df9c4377a55fda14041cb6128e","l1SequenceNumber":"0x2567e15","parentRequestId":"0x4f2c69785ac02ebe59d6804bbfd1752ca2e5e8161e80edf39e5e5a33a2e87d08","indexInParent":"0x0","arbType":"0x3","arbSubType":"0x4","l1BlockNumber":"0xe8781d"}} 86 | ``` 87 | 如上,我们将交易对应回去,发现信息全部吻合,因此,我们的sequencer交易解码成功。 88 | 89 | ## 查看交易在l1中的信息 90 | 91 | 得到交易后,前往arbiscan可查看更多信息,我们找到[Submission Tx Hash](https://etherscan.io/tx/0x1f39f0f26d6f4823dfc6860fb53a8a7e80703c41dd69577b4be97c085b681037),在该界面中,我们可以在`input data`中找到刚刚获得的sequencer消息(不包含sender,可使用data信息辅助查找)。 -------------------------------------------------------------------------------- /Share/guide/2_dimensional_fee.md: -------------------------------------------------------------------------------- 1 | ## 深入了解Arbitrum机制:二维费用(2-Dimensional Fees) 2 | 3 | 在几个月前的Arbitrum奥德赛中,Arbitrum One经历了史无前例的用户流量。一些敏锐的用户注意到了一个奇特的现象:随着L2 gas价格的上升,一笔特定的交易所使用的L2 gas数量实际上会减少。 4 | 5 | 事实证明,这正是系统的工作原理——但对不熟悉的人来说,这看起来确实有点令人困惑。 6 | 7 | 如果你对到目前为止我们所说的内容而感到有些困惑,那这篇文章会试图把它梳理清楚;如果你实在太困惑了,甚至都不知道在困惑什么,那么欢迎你继续读下去。与此同时,你可能会发现这些关于以太坊和Arbitrum gas的入门读物很有用。(而且如果你已经能准确地把握这篇文章的走向,那你完全应该在这里申请一个职位: https://offchainlabs.com/careers/) 8 | 9 | ### 二维费用 10 | 11 | 关于Layer 2的支付费用,在一个经济设计的系统中(如Arbitrum),你实际上是在为两件事项同时付费:L1-native资源和L2-native资源。Arbitrum One是一个Rollup,你所支付的L1资源基本上只是以太坊calldata;也就是说,你支付的是你交易的原始数据大小乘以L2对L1 calldata的价格估算(大致可以这样理解)。 12 | 13 | 你需要支付的L2资源是你的交易在Arbitrum上的通用虚拟机(VM)中进行的任何计算——执行、存储等。这个数值是L2gas价格乘以ArbGas——Arbitrum的基本计算单位——你的交易使用的数量。一笔交易要成功,需要支付的L2费用总额是这两部分的总和 14 | 15 | #### 在一维的世界里... 16 | 17 | 棘手的是,尽管像Arbitrum这样的L2费用本质上是二维的,但目前的以太坊生态系统主要是在L1建立的,其费用可以用一维(one-dimensional)来表示。这意味着目前的基础设施——钱包、开发者库等等——假设交易格式中的费用是单一gas单位和单一gas价格的产物;当在Arbitrum上进行交易时,我们要被迫将L1和L2维度都塞进这种限制性格式中。 18 | 19 | 那么,我们是如何做到的呢? 20 | 21 | ### 我们是如何做到的 22 | 23 | 因此,综上所述,我们的主要限制是总费用——必须包括L1和L2费用——需要表示为两个值的乘积,我们称之为 "类似gas价格"(P)和 "类似gas限额"(G)。 24 | 25 | 我们使用的P值(由Arbitrum的估算gas价格RPC折返)实际上只是L2的gas价格(估算gas价RPC增加了一个小百分比的缓冲;任何超出的部分都会被退回)。G是我们考虑L1维度的地方;调用Arbitrum的估算gas,RPC会给出一个值,其用于L2计算的ArbGas加上一个额外的缓冲区(B),这样P*G最终足以覆盖全部交易成本。换句话说,我们增加了 "gas限制 "之类的字段,以便在给定的gas价格下支付的总金额足以支付L1和L2层面的费用。 26 | 27 | 通过一些代数运算,我们发现这个缓冲区B必须等于(L1 calldata成本)/P。 28 | 29 | 因此,总的来说,G,解压为。 30 | 31 | L2 gas使用量+( L1 calldata价格*L1calldata大小) /(L2 gas价格) 32 | 33 | ...其中 "L2 gas价格 "的分母(回到最初的困惑)显示了为什么在所有其他数值相同的情况下,L2 gas价格的增加,实际上是减少了G的价值。 34 | 35 | 迈向二维收费标准 36 | 37 | 如果这一切看起来比较麻烦——我们非常清楚这一点;我们目前被困在生态系统目前支持的一维费用基础设施中,但理想的情况是,一个多维收费标准将被采纳并被广泛使用;现有几个提案存在,我们同样也有自己的想法。如果你有兴趣帮助协调一个新的标准,请联系我们/参与我们的研究论坛。 38 | 39 | 想要了解更多关于Arbitrum如何处理gas信息,请查看我们的Inside Arbitrum文档。 40 | -------------------------------------------------------------------------------- /Share/guide/bridgeUi.md: -------------------------------------------------------------------------------- 1 | # Arbitrum官方桥教程 2 | 3 | 本教程的预设是你已经下载了一个像MetaMask这样的web3钱包,并且在以太坊主网上的钱包里有ETH或代币。如果你还没有钱包,请访问我们的portal网站,去下载一个。 4 | 5 | 6 | 注意:ETH是Arbitrum上的gas费结算代币。你需要用ETH在Arbitrum上进行交易! 7 | 8 | 9 | ## 1. 添加一个网络。Arbitrum One或Arbitrum Nova 10 | 11 | 按照下面的步骤手动添加一个Arbitrum网络。 12 | 13 | 点击metamask的扩展 > 点击 「Ethereum Mainnet」网络 > 点击菜单末尾的 「Add Network」(添加网络),并输入你要连接的Arbitrum链的对应信息。 14 |

15 | 16 |

17 | --- 18 | 19 | - Network Name: Arbitrum One 20 | - RPC: https://arb1.arbitrum.io/rpc 21 | - Chain ID: 42161 22 | - Currency Symbol: ETH 23 | - Block Explorer URL: https://arbiscan.io 24 | 25 | --- 26 | - Network Name: Arbitrum Nova 27 | - RPC: https://nova.arbitrum.io/rpc 28 | - Chain ID: 42170 29 | - Currency Symbol: ETH 30 | - Block Explorer URL: https://nova-explorer.arbitrum.io 31 | 32 | 33 | 如果你是一个开发者,你可以使用Alchemy、Infura或Quicknode作为连接到Arbitrum One的替代方式。 34 | 35 | 对于Arbitrum Nova,目前你可以使用Quicknode作为替代方式 36 | 37 | ## 2. 将你的ETH/代币桥接 (L1 —> L2) 38 | 39 | 40 | 41 | 42 | 43 | 要使用Arbitrum One或Arbitrum Nova的话,你需要首先将资产从以太坊主网桥接到Arbitrum One或Nova上。可以点击这里访问我们的官桥。 44 | 45 | 1. 用你的钱包登录官桥,并确保你使用的网络是「Ethereum Mainnet」。 46 | 47 |

48 | 49 |

50 | 51 | 2. 请选择你希望桥接到的Arbitrum网络! 52 | 53 | 可选Arbitrum One或Arbitrum Nova 54 | 55 |

56 | 57 |

58 | 59 | 3. 在下拉菜单中选择你想桥接的代币。 60 | 你也可以通过点击下拉菜单右下角的「Manage Token lists」(管理代币列表)按钮,来启用/关闭代币列表。 61 | 62 |

63 | 64 |

65 | 66 | 67 | 4. 在「Mainnet」 (主网) 中输入你想要桥接的ETH/代币数量,然后按「Deposit」(存入)。 68 | 如图,按照Metamask上的提示操作。 69 | 70 | 在你通过Metamask提交了交易后,资金在大约10分钟到1小时内会到达Arbitrum One(实际时间取决于拥堵情况)。 71 | 72 | 同时,请确保你的MetaMask钱包被设置为Arbitrum One网络 或 Arbitrum Nova网络(取决于你选择桥接哪一个)——这样你就可以在资金到达时看到。 73 | 74 |

75 | 76 |

77 | 78 | 79 | ## 3. 游览我们的DAPPs 80 | 81 | Arbitrum的生态中有非常多的dapp、钱包、工具以及更多的项目! 82 | 83 | 你可以在这里浏览其中大部分的项目名单:Arbitrum One Portal, Arbitrum Nova Portal 84 | 85 |

86 | 87 |

88 | 89 | ## 4. 提取你的ETH/代币(L2 —> L1) 90 | 91 | 请注意:L2回L1至少需要7天的挑战期。 92 | 93 | 如果你通过官桥跨回你的资金,在资金能到达主网上并可被领取前,需要等待至少7天的时间, 94 | 95 | 96 | 1. 点击页面中间的箭头,然后在代币的下拉菜单中选择你想要桥接的代币。 97 | 98 | *请确保你连接到的网络是你想跨回资金的Arbitrum网络(Arbitrum One或Arbitrum Nova)。 99 | 100 |

101 | 102 |

103 | 104 | 105 | 2. (a)我们推荐使用这些跨链桥项目方中的一个!(如图) 106 | 107 | 根据你想选择哪一个跨链桥项目方,你将会被送到他们桥的页面上,并在那里完成你从Arbitrum的跨回资金。 108 | 109 |

110 | 111 |

112 | 113 | 114 | 2. (b)在「L2」中输入你想要桥接的ETH/代币的数量,然后按 「Withdraw」(提取)。 115 | 如图,按照Metamask上的提示操作。 116 | 117 | 请确保你留下足够的ETH来支付这笔交易,否则Metamask界面将不会弹出。 118 | 119 | 至少有7天的挑战期。 120 | 一旦你通过桥梁发送你的资金,你将不得不等待至少7天,直到你在主网上收到它们。 121 | 122 | 请注意:L2回L1至少需要7天的挑战期。 123 | 124 | 如果你通过官桥跨回你的资金,在资金能到达主网上并可被领取前,需要等待至少7天的时间。 125 | 126 |

127 | 128 |

129 | 130 | 131 | 3. 完成以上操作后,会弹出一个倒计时,显示你会在7-8天内得到你的资金。 132 | 133 | 你可以通过点击右上方的「个人资料」来检查你资金的提取状态——跨回完毕后,也是在此界面中领取你的资金。 134 | 135 |

136 | 137 |

138 | 139 | 4. 一旦倒计时结束,在你钱包上切换到以太坊主网网络,按一下现在已变成蓝色的「Claim」 (认领)按钮,这样可以领取你的资金了! 140 | 141 |

142 | 143 |

144 | 145 | ## 潜在的风险 146 | 147 | - 智能合约风险:任何智能合约都有可能存在漏洞,从而导致用户资金的损失。 148 | 149 | - 价格风险:一旦你的ETH或代币从L1→L2或L2→L1被桥接,你的资金价值可能会因为定期的市场波动而发生变化。 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Share/img/AssertionTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/AssertionTree.png -------------------------------------------------------------------------------- /Share/img/arch_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/arch_overview.png -------------------------------------------------------------------------------- /Share/img/bridgeUi/addNetwork.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/addNetwork.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/claim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/claim.png -------------------------------------------------------------------------------- /Share/img/bridgeUi/dapplist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/dapplist.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/deposit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/deposit.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/login.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/network.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/network.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/recommand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/recommand.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/selectToken.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/selectToken.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/status.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/status.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/withdraw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/withdraw.gif -------------------------------------------------------------------------------- /Share/img/bridgeUi/withdrawStart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/bridgeUi/withdrawStart.gif -------------------------------------------------------------------------------- /Share/img/delayedInbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/delayedInbox.png -------------------------------------------------------------------------------- /Share/img/nova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/nova.png -------------------------------------------------------------------------------- /Share/img/retryable-lifecycle.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/Share/img/retryable-lifecycle.JPG -------------------------------------------------------------------------------- /Share/news/nitro_upgrade.md: -------------------------------------------------------------------------------- 1 | # 准备好你的引擎——Nitro马上就要到来了! 2 | 言简意赅:Arbitrum One将于8月31日迁移到Nitro! 现在就准备好并测试你的合约吧! 请参考[迁移说明](https://github.com/OffchainLabs/nitro/blob/master/docs/migration/dapp_migration.md),并关注我们的[Twitter](https://twitter.com/arbitrum)和[Discord](https://discord.com/invite/Arbitrum)了解最新情况。 3 | 4 | 一周前,我们实施了Arbitrum Rinkeby测试网向Nitro的迁移。总的来说,这个过程进展得非常顺利,现在我们正期待着Arbitrum One的主网迁移到Nitro。我们很高兴与大家分享:我们计划在8月31日进行迁移——这恰好是我们Arbitrum One首次公测版本后的一周年! 5 | 6 | 我们选择这个日期是为了确保我们的生态系统继续有时间为迁移做准备,而且我们认为这天是一个具有代表性的里程碑,能够让我们凝聚在一起——这是一个属于我们所有人的生日礼物。 7 | 8 | ## 继续为Nitro做准备 9 | Nitro是一次重要的技术升级,它将带来整个堆栈的变化,极大地改善了在Arbitrum上的体验。我们最初在Arbitrum Goerli Nitro版本中详细介绍了这些变化,但稍微复习一下,Nitro将带来: 10 | 11 | - _高级Calldata压缩_,通过减少传至L1的数据量,从而进一步降低Arbitrum的交易成本。 12 | - _以太坊L1 Gas兼容性_,使EVM的定价和核算与以太坊完全一致。 13 | - _额外的L1互操作性_,包括与L1区块码更紧密的同步,以及对所有以太坊L1预编译的全面支持。 14 | - 安全重试,消除了可重试票据不能被创建的故障模式。 15 | - _Geth追踪_,提供更广泛的调试支持。 16 | - 还有很多很多的变动。 17 | 18 | 开发人员,现在是时候确保你的合约和前端为迁移做好准备,准备好应对任何必要的变化和尽可能多的测试。如果你还没有这样做,我们强烈建议你在Arbitrum测试网中进行部署,[Arbitrum Goerli](https://developer.offchainlabs.com/docs/Public_Chains)是我们长期会使用的网络。 19 | 20 | 在我们进行迁移的过程中,我们将继续更新我们的[迁移说明](https://github.com/OffchainLabs/nitro/blob/master/docs/migration/dapp_migration.md),当中会记录任何新出的,和具有突破性的变动,以及我们的[开发者文档](https://developer.offchainlabs.com/docs/Developer_Quickstart)。 21 | 22 | ## 在Nitro上线主网前的下一步工作 23 | 在Rinkeby顺利地迁移后,我们有信心,并已经准备好了将Arbitrum One升级至Nitro。在接下来的几周里,我们将继续与基础设施供应商、dapp和社区进行协调,以确保每个人都能充分了解即将到来的状况,并传递任何可能会出现的额外计划和更新。 24 | 25 | 在主网迁移前一周,即8月24日,我们计划进行一次影子分叉迁移——作为主网迁移前的最后彩排。影子分叉允许我们复制另一个链的状态,并运行一个平行链。它仍然接收来自主链的交易——也就是Arbitrum One。进行这一工作,然后再将影子分叉迁移到Nitro,将为我们一周后在主网上进行的迁移工作提供全面的覆盖性测试。随着时间的临近,我们会与合作伙伴团队保持充分的沟通,并携手参与影子分叉的工作。 26 | 27 | ## 周年庆 28 | 之前有提到过,我们迁移日期定在了8月31日,因为这可以让我们的开发者社区继续为迁移做准备,同时也因这与Arbitrum One上线主网的一周年的纪念日刚好吻合。 29 | 30 | 我们准备好了一些有非常趣的事情来庆祝我们的一周年庆和Nitro,并会在未来的几周内陆续分享我们的计划。 31 | 32 | 综上所述,请准备好迎接Nitro吧! 33 | 34 | -------------------------------------------------------------------------------- /Share/news/nitro_upgrade_instructions.md: -------------------------------------------------------------------------------- 1 | ## Arbitrum One 升级到 Nitro 期间节点操作员的说明 2 | 3 | _tl;dr — 当我们在 8 月 31 日将 Arbitrum One 迁移到 Nitro 时,节点操作员需要遵循的一组快速说明。_ 4 | 在 Arbitrum One 迁移到 Nitro 之后,Arbitrum Classic 节点将只提供对经典链(pre-nitro)状态的查询,并且为了正确同步迁移的链,任何运行节点的人都需要运行 Nitro 节点。在这里,我们列出了相应时间/事件要遵循的步骤,以确保您已设置好运行 Nitro 节点。 5 | 6 | ### 8 月 31 日 Nitro 迁移之前或期间 7 | 8 | **1) 下载最新的 Nitro 节点版本 v2.0.0** 9 | - [docker image链接](https://hub.docker.com/r/offchainlabs/nitro-node/tags?page=1&name=v2.0.0-292bbc4)和[docker hub链接](https://hub.docker.com/r/offchainlabs/nitro-node/tags?page=1&name=v2.0.0-292bbc4) 10 | - 成功迁移到 Nitro 后,将需要此版本才能同步链。 11 | - **9 月 1 日后Arbitrum Goerli 和 Rinkeby 也需要运行此版本。** 12 | 13 | **2) 使用最新版本(上面链接)启动和/或升级 Nitro 节点。** 14 | 15 | - 在迁移之前或期间执行此操作,尽管 Arbitrum One 上的 Nitro 节点在迁移完成之前不会提供 RPC 或响应请求。 16 | - 链 ID 将保持不变:42161。 17 | - 您也可以参考我们的[Nitro 节点部署说明](https://developer.offchainlabs.com/docs/Running_Nitro_Node)。 18 | 19 | ### **8 月 31 日迁移期间** 20 | 21 | **3) 给 Nitro 节点一个种子数据库,用于初始链同步。** 22 | 23 | - 对于种子数据库,有两个选项: 24 | - 选项 1 - 我们将在迁移期间执行链状态导出,完成后,我们将上传导出的副本作为种子数据库。我们会将其(数据快照)发布在我们的公共频道上,您可以在我们的 [Arbitrum Devs Twitter](https://twitter.com/ArbitrumDevs) 上关注。 25 | - 选项 2 - 各个操作员可以从他们自己的 Arbitrum Classic 节点导出状态,并将其用作 Nitro 节点的种子/创世数据库。但是,除非您已经提前开始了历史导出,否则这应该只用于验证我们的创世块,因为这需要一段时间。 26 | 27 | 1. 使用 `--node.rpc.nitroexport.enable` CLI 选项启动 Arbitrum Classic 节点 28 | 2.运行RPC命令`{"jsonrpc":"2.0","id":0,"method":"arb_exportHistory","params":["latest"]}`开始历史导出 29 | 3.运行RPC命令`{"jsonrpc":"2.0","id":0,"method":"arb_exportOutbox","params":["0xffffffffffffffff"]}`开始发件箱导出 30 | 4. 等待硝基迁移开始,然后创建最终块 31 | 5.运行RPC命令`{"jsonrpc":"2.0","id":0,"method":"arb_exportHistory","params":["latest"]}`完成历史导出 32 | 6. 运行RPC命令`{"jsonrpc":"2.0","id":0,"method":"arb_exportOutbox","params":["0xffffffffffffffff"]}`完成发件箱导出 33 | 7. 运行RPC命令`{"jsonrpc":"2.0","id":0,"method":"arb_exportState","params":["latest"]}`导出状态信息 34 | 8. 等待 RPC 命令 `{"jsonrpc":"2.0","id":0,"method":"arb_exportHistoryStatus","params":[]}` 返回与 `eth_blockNumber` 相同的值如果还没有 35 | 9. 等待 RPC 命令 `{"jsonrpc":"2.0","id":0,"method":"arb_exportOutboxStatus","params":[]}` 的结果停止增加(正常每秒增加一次左右) 36 | 10. 在经典节点的 db 文件夹旁边,您会找到一个 `nitroexport` 文件夹(通常位于 `~/.arbitrum/mainnet/nitroexport`)。将 `nitro` 文件夹复制到 `~/.arbitrum/arb1/nitro` 37 | 11. 使用 `nitro --l1.chain-id 1 --l2.chain-id 42161 --node.dangerous.no-l1-listener --node.l1-reader.enable=false --init 执行状态导入.then-quit --init.file ~/.arbitrum/mainnet/nitroexport/state/*/index.json` 38 | 12. 恭喜,你在 `~/.arbitrum/arb1/nitro` 有一个完整的nitro数据库! 39 | 40 | **4) 等待链恢复和节点开始响应请求。** 41 | 42 | - 状态导出完成后,我们将启动我们的序列器和 rpc 端点,此时整个生态系统的 Nitro 节点应该可以正常运行。 43 | 44 | 英文[原文链接](https://arbitrum.notion.site/EXTERNAL-Instructions-for-Node-Operators-During-the-Arbitrum-One-Migration-to-Nitro-bed55327954542c8939bbf7886e82ee8) -------------------------------------------------------------------------------- /Share/news/nitro_upgraded.md: -------------------------------------------------------------------------------- 1 | ## Arbitrum Nitro:Layer2的一小步,以太坊的一大步 2 | 3 | 4 | 时间终于到了,诸位Arbinauts…Arbitrum One现在已经完全迁移到了Nitro堆栈! 5 | 6 | 是的,你没有听错!Arbitrum One现在已经在Nitro上运行了——更高的吞吐量,更低的费用,下一代的rollup架构正在发生,而这一切现在都已经在Arbitrum One主网上上线。 7 | 8 | 为了达到这一个巨大的里程碑,我们确实已经为之努力了许久,因此非常高兴能与社区分享我们的进展和喜悦之情。Arbitrum One网络现在已经全面恢复上线,并在接下来的几个小时里,生态系统的基础设施将陆续恢复在线,大家可以在[这里](https://arbitrum.notion.site/arbitrum/External-Arbitrum-One-Nitro-Migration-Infra-Status-Tracker-99c8f27ff4054da08c82c8e48968e9b7)追踪...以及所有最近的更新都会第一时间发布在我们Twitter上。 9 | 10 | 11 | ### 万众期盼,Nitro来了! 12 | 13 | 我们之前详细讨论Nitro也有一段时间了,但既然现在迁移已经完成,那么开发者和用户终于可以实际地感受到升级带来的影响。简而言之,Nitro带来了一系列的优化改进,比如说: 14 | 15 | - _更高的吞吐量_——与Nitro升级之前相比,提高了7至10倍 16 | - _先进的calldata压缩_——通过减少发布到L1的数据量,可进一步降低Arbitrum的交易成本 17 | - _以太坊L1 gas兼容性_——使EVM的报价与核算与以太坊完全一致。 18 | - _额外的L1互操作性_——这包括与L1区块码更紧密的同步,以及对所有以太坊L1预编译的全面支持。 19 | - _更安全的重试_——消除了可重试票据不能被创建的故障模式。 20 | - _Geth追踪_——提供更广泛的调试支持。 21 | - 还有很多,更多的变化! 22 | 23 | 在一周年庆典之际,虽然Nitro确实是一个巨大的成就,但这也仅仅只是「Arbitrum之秋」的开始。 24 | 25 | 一个额外的优化——我们还对我们的[文档](https://developer.arbitrum.io/)进行了一次大规模整改 在浏览我们的新文档时,你可能会注意到:关于我们技术的新介绍,常见问题解答,以及美化了的ui界面——这些只是几个主要值得关注的改动 26 | 27 | ### Arbitrum One元年 28 | 29 | 等一下,新的堆栈...新的文档...今天到底是谁的生日啊?!? 30 | 31 | 对于那些已经在这里呆了很久的人,你可能还会记得一年前的今天... Arbitrum One主网正式向公众开放! 这真是令人难忘的一年! 和一个了不起的社区共同成长、和一些crypto行业中最大,在Arbitrum「安家」的项目方,共同携手推动着应用层面的创新...这都证明了以太坊的Layer2是通往可持续的链上未来的途径。 32 | 33 | 即使现在Nitro已在Arbitrum One和Nova上全面上线(别忘了,Nova其实是一直在Nitro上运行的!),Arbitrum的故事才刚刚开始。我们的团队正在努力工作,以进一步扩容,降低成本,使Arbitrum用户能享受到更好的体验。我们还有很多事情需要去做,而未来的工作重点主要是与你们——广大的社区——一起继续推动安全、效率和可扩容性的发展! 34 | 35 | 此外,当我们在过去几个月里埋头工作时,我们还将抽出一些时间去参加Devcon! 我们团队中的一些成员将会在那里...我们很高兴能有此机会与社区互动,届时会分享这一事项的进展。 36 | 37 | ### Arbitrum之秋将至... 38 | 39 | 关于Nitro的技术背景资料,我们的白皮书已经深入介绍了我们下一代rollup架构背后的技术设计和原理。你可以在[这里](https://github.com/OffchainLabs/nitro/blob/master/docs/Nitro-whitepaper.pdf)阅读该白皮书。 40 | 41 | 同时,我们也正在招聘! 所有这些创新之所以能够实现,是因为我们在Offchain Labs拥有一个强大的团队。我们的团队每天都在不知疲倦地工作,以推动区块链扩容的上限,而且我们还在继续壮大!如果你想加入区块链领域里最好的团队,请访问我们的[招聘页面](https://offchainlabs.com/careers/)并申请! 42 | 43 | 44 | -------------------------------------------------------------------------------- /Share/news/nova.md: -------------------------------------------------------------------------------- 1 | # 是时候迎接新的曙光了,Nova正式对公众开放了! 2 | 言简意赅: Nova现在向公众开放了! Reddit在Nova上推出了他们的社区积分系统。 3 | 4 | - 从[这里](https://bridge.arbitrum.io/?l2ChainId=42161)跨入Nova! 5 | - 从[这里](https://nova.arbitrum.io/)了解更多关于Nova的信息! 6 | - 从[这里](https://developer.offchainlabs.com/docs/Public_Chains)开始在Nova上进行建设! 7 | 8 | 诸位Arbinauts快来集合! Arbitrum Nova的大门现在正式开放了! 9 | 10 | 是的,你没有看错 - 从今天起,我们已经换下了Nova的开发者白名单。Nova是我们最新开发的链,主要用于游戏和社交应用,是我们的第一个建立在AnyTrust技术上的链。现在欢迎用户和更多的建设者们来体验并开发Nova。 11 | 12 | 你在[这里](https://bridge.arbitrum.io/?l2ChainId=42170)桥接Nova,并通过访问我们的[网站](https://nova.arbitrum.io/)了解更多关于Nova的信息。 13 | 14 |

15 | 16 |

17 | 18 | 在我们[最初的公告](https://medium.com/offchainlabs/introducing-nova-arbitrum-anytrust-mainnet-is-open-for-developers-9a54692f345e)中,我们分享了我们的理念:Nova将完美适应游戏和社交项目的需求——即交易量非常大,寻求将成本降到更低,但仍然需要高安全性的项目。在过去的几周里,我们看到了生态中的很多团队对Nova表达出了足够的兴趣,作为我们公平发布过程的一部分,我们授予了100多个开发团队以访问权。 19 | 20 | 我们还非常兴奋地想要分享:今天(8月9日),就在我们的Nova链公开发布的同时,Reddit正在向Nova部署他们的社区积分系统,从两个社区开始,你可以在[这里](https://www.prnewswire.com/news-releases/offchain-labs-works-with-reddit-to-bring-community-points-to-the-ethereum-mainnet-301602509.html)阅读更多关于他们发展的旅程。 21 | Reddit的旅程,从烘焙比赛到部署至主网,无疑加强了我们的信念——即Arbitrum技术栈是开发者的最佳扩展解决方案,不仅仅是在以太坊主网,而是在整个区块链领域。 22 | 23 | 对于Arbitrum的社区来说,这已经是,并将继续是一个重要的月份。上周,我们宣布了我们的主网迁移计划——在了8月31日,我们将Arbitrum One升级至Nitro。而今天我们又向大众开放了一条全新的链,它将成为Reddit社区积分系统的大本营,这是一个全新将社区带入链上的模式。我们还有更多关于Nova的信息想要分享,包括我们的数据可用性委员会(DAC)的成员,以及开始建设的最佳方式,但是说了这么多...... 24 | 25 | 欢迎来到「Arbitrum八月」! 26 | 27 | ## 首届 数据可用性委员会(DAC) 28 | 我们已经提到了Nova可以为项目提供超低的交易成本,并将使用DAC。今天我们想分享一下委员会的首届成员 29 | 30 | 这是一个由业内最好的团队共同组成的委员会,他们包括: 31 | - Consensys 32 | - FTX 33 | - Google Cloud 34 | - Offchain Labs 35 | - P2P 36 | - Quicknode 37 | - Reddit 38 | 39 | 我们很高兴能与这些团队合作,因为他们都是发布了,已经影响了大量用户群的「弹性技术」的组织,这也是我们希望通过Nova去实现的,而且他们也相信web3的前景。 随着时间的推移,委员会将不断扩大,我们期待随着事情的进展,将有机会与社区分享更多的细节。 40 | 41 | ## 开始使用Nova 42 | 所以你想在Nova上进行开发?很高兴你有这个兴趣并同时已经准备就绪了!你可以在[这里](https://nova.arbitrum.io/)了解更多关于Nova的运作原理。以下是一个快速的入门指南。 43 | 44 | 1. 首先,你可以使用[Arbitrum官桥](https://bridge.arbitrum.io)跨入Nova——现在支持在Arbitrum Nova和Arbitrum One之间轻松地进行网络切换。我们希望这将使在以太坊主网和我们的链之间的交互更加简单和便于操作。 45 | 46 | 2. 接下来,如果你想提前在测试网上部署,我们强烈建议使用Arbitrum Goerli,因为这将是我们未来的长期测试网。除了交易费用金额,Goerli和Nova之间的开发体验将是完全相同的,因为两者都是建立在Nitro技术之上的 47 | 48 | 3. 最后,在Nova上部署也很简单,只要把你的rpc端点和ChainID配置到nova链上,分别是nova.arbitrum.io/rpc和42170 - 你可以在[这里](https://developer.offchainlabs.com/docs/Public_Chains)找到这些,以及更多有关链的细节内容。对于新的开发者,你可以从我们的[教程](https://developer.offchainlabs.com/docs/Tutorials)开始,并加入我们的discord,有很多Arbitrum的开发者经常在那里互帮互助。 49 | -------------------------------------------------------------------------------- /TodoList/NonTech.md: -------------------------------------------------------------------------------- 1 | # non-Tech Todo-List 2 | ## 敬请期待。。 -------------------------------------------------------------------------------- /TodoList/Tech.md: -------------------------------------------------------------------------------- 1 | # Tech Todo-List 2 | 该Todo-List是为有技术基础或是准备在arbitrum开发的爱好者准备,不管您是开发小白还是开发大佬,您都可以在这里找到想要做的事情。 3 | 4 | - [Arbitrum Tutorial运行](Tech/RunArbTutorial.md) 5 | - [Arbitrum Tutorial新Idea贡献](Tech/AddNewTutorial.md) 6 | - Arbitrum技术社区问答活跃 7 | - 在arbitrum上部署项目 8 | - 在arbitrum上部署项目,并有一定用户活跃度 9 | 10 |

11 | 12 |

-------------------------------------------------------------------------------- /TodoList/Tech/AddNewTutorial.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Tutorial新Idea贡献 2 | 如果你对Arbitrum的tutorial有一些新的想法想要实现,或者是你在开发的过程中踩过一些坑,也欢迎提交PR,来创建一个新的Tutorial。 -------------------------------------------------------------------------------- /TodoList/Tech/RunArbTutorial.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Tutorial运行 2 | 在该Task中,你可以尝试运行Arbitrum Tutorial中的代码,并在运行的过程中,如果发现运行有问题或者资料不够详细,欢迎提交PR或添加Jason微信(Ethereum_wan)反应。 -------------------------------------------------------------------------------- /TodoList/TechAribiter.md: -------------------------------------------------------------------------------- 1 | # Tech arbiter 2 | Arbitrum的目标是为了构建一个更可用,更安全的以太坊扩容计划,因此,我们非常欢迎对于layer2技术感兴趣的朋友加入到我们生态中来,因此我们将开放Arbitrum中文区技术Arbiter来一起推广arbitrum。 3 | 4 | 如果你是: 5 | - 加密原生爱好者,并且对技术十分热爱 6 | - 精通于某一web3技术,并想深入了解layer2 7 | - web2开发者,并对web3已有一定了解 8 | - layer2技术的深度参与者 9 | 10 | 我们都非常欢迎大家的到来, 11 | 作为一名tech arbiter,你需要: 12 | - 了解 [Arbitrum Tutorial](Tech/RunArbTutorial.md) 的运行机制 13 | - Arbitrum技术社区问答活跃 14 | - 在其他web3推广arbitrum技术 15 | - 参与arbitrum社区AMA,并分享相关技术。 16 | 17 | 你可以获得: 18 | - 深入了解前沿layer2技术 19 | - 社区周边 20 | - 如果有部署项目的想法,我们会提供大力支持 21 | - 提供社区或团队工作机会 22 | 23 |

24 | 25 |

-------------------------------------------------------------------------------- /arbitrum-tutorials/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | commonjs: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | plugins: ['prettier'], 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 14 | sourceType: 'module', // Allows for the use of imports 15 | }, 16 | rules: { 17 | 'prettier/prettier': 'error', 18 | 'no-unused-vars': 'off', 19 | 'prefer-const': [2, { destructuring: 'all' }], 20 | 'object-curly-spacing': ['error', 'always'], 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /arbitrum-tutorials/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: | 2 | (?x)( 3 | ^packages/arb-avm-cpp/external/keccak/| 4 | ^packages/arb-bridge-eth/installed_contracts/ 5 | ) 6 | repos: 7 | - repo: https://github.com/psf/black 8 | rev: stable 9 | hooks: 10 | - id: black 11 | language_version: python3 12 | - repo: https://gitlab.com/pycqa/flake8 13 | rev: 3.9.0 14 | hooks: 15 | - id: flake8 16 | - repo: git://github.com/doublify/pre-commit-clang-format 17 | rev: master 18 | hooks: 19 | - id: clang-format 20 | - repo: git://github.com/dnephin/pre-commit-golang 21 | rev: master 22 | hooks: 23 | - id: go-fmt 24 | - repo: https://github.com/pre-commit/mirrors-prettier 25 | rev: "v2.2.1" # Use the sha or tag you want to point at 26 | hooks: 27 | - id: prettier 28 | - repo: https://github.com/pre-commit/mirrors-eslint 29 | rev: "v7.24.0" # Use the sha / tag you want to point at 30 | hooks: 31 | - id: eslint 32 | args: [--fix] 33 | types: [text] 34 | # ignore prettier config 35 | files: \.[jt]sx?$ 36 | additional_dependencies: 37 | - "eslint@7.3.1" 38 | - "typescript@3.8.3" 39 | - "@typescript-eslint/parser@3.4.0" 40 | - "@typescript-eslint/eslint-plugin@3.4.0" 41 | - eslint-config-prettier@6.11.0 42 | - repo: https://github.com/syntaqx/git-hooks 43 | rev: v0.0.16 44 | hooks: 45 | - id: circleci-config-validate 46 | - repo: local 47 | hooks: 48 | - id: prettier-soldity 49 | name: prettier-soldity 50 | language: node 51 | entry: ./node_modules/.bin/prettier --write --list-different 52 | files: \.sol$ 53 | additional_dependencies: 54 | - "prettier@2.0.5" 55 | - "prettier-plugin-solidity@^1.0.0-alpha.54" 56 | - id: go-vet 57 | name: Go Vet 58 | entry: ./scripts/run-go-packages "go list ./... | grep -v 'arb-node-core/ethbridge[^/]*contracts' | xargs go vet" 59 | language: script 60 | files: \.go$ 61 | -------------------------------------------------------------------------------- /arbitrum-tutorials/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'es5', 4 | singleQuote: true, 5 | printWidth: 80, 6 | tabWidth: 2, 7 | arrowParens: 'avoid', 8 | bracketSpacing: true, 9 | overrides: [ 10 | { 11 | files: '*.sol', 12 | options: { 13 | printWidth: 100, 14 | tabWidth: 4, 15 | useTabs: false, 16 | singleQuote: false, 17 | bracketSpacing: true, 18 | explicitTypes: 'always', 19 | }, 20 | }, 21 | ], 22 | } 23 | -------------------------------------------------------------------------------- /arbitrum-tutorials/README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Tutorials 2 | 3 | 4 | 5 | 这个 monorepo 将帮助您开始在 Arbitrum 上进行构建。它提供了各种简单的演示来展示和解释如何与 Arbitrum 交互——直接在 L2 上部署和使用合约,在 L1 和 L2 之间转移 Ether 和代币等等。 6 | 7 | 为了方便起见,我们展示了如何使用广泛支持的以太坊生态系统工具(Hardhat、Ethers-js 等)以及我们特殊的 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk)。 8 | 9 | ## Installation 10 | 11 | 从 root 目录: 12 | 13 | ```bash 14 | yarn install 15 | ``` 16 | 17 | ## 包含了哪些信息? 18 | 19 | #### :white_check_mark: 基础使用 20 | 21 | - 🐹 [Pet Shop DApp](./packages/demo-dapp-pet-shop/) (L2 only) 22 | - 🗳 [Election DApp](./packages/demo-dapp-election/) (L2 only) 23 | 24 | #### :white_check_mark: 资产转移 25 | 26 | - ⤴️ 🔹 [Deposit Ether](./packages/eth-deposit/) 27 | - ⤵️ 🔹 [Withdraw Ether](./packages/eth-withdraw/) 28 | - ⤴️ 💸 [Deposit Token](./packages/token-deposit/) 29 | - ⤵️ 💸 [Withdraw token](./packages/token-withdraw/) 30 | 31 | #### :white_check_mark: 基本操作 32 | 33 | - 🤝 [Greeter](./packages/greeter/) (L1 to L2) 34 | - 📤 [Outbox](./packages/outbox-execute/) (L2 to L1) 35 | 36 | #### :white_check_mark: 高级特性 37 | 38 | - ®️ [Arb Address Table](./packages/address-table/) 39 | - 🌉 [Bridging Custom Token](./packages/custom-token-bridging/) 40 | 41 |

42 | 43 |

44 | -------------------------------------------------------------------------------- /arbitrum-tutorials/assets/offchain_labs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/arbitrum-tutorials/assets/offchain_labs_logo.png -------------------------------------------------------------------------------- /arbitrum-tutorials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrum-tutorials", 3 | "version": "1.0.0", 4 | "description": "The Arbitrum Tutorials Monorepo", 5 | "author": "Offchain Labs, Inc.", 6 | "license": "Apache-2.0", 7 | "private": "true", 8 | "engines": { 9 | "node": ">= 8.0.0 < 17.0.0", 10 | "npm": "^6.0.0", 11 | "yarn": "^1.0.0" 12 | }, 13 | "scripts": { 14 | "lint": "eslint .", 15 | "format": "prettier './**/*.{js,json,md,yml,sol}' --write && yarn run lint --fix" 16 | }, 17 | "devDependencies": { 18 | "eslint": "^8.15.0", 19 | "eslint-config-prettier": "^8.3.0", 20 | "eslint-plugin-mocha": "^9.0.0", 21 | "eslint-plugin-prettier": "^4.0.0", 22 | "prettier": "^2.3.2", 23 | "prettier-plugin-solidity": "^1.0.0-beta.17" 24 | }, 25 | "workspaces": { 26 | "packages": [ 27 | "packages/*" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/.env-sample: -------------------------------------------------------------------------------- 1 | DEVNET_PRIVKEY="0x your key here" 2 | 3 | # this is rinkarby, can use any Arbitrum chain 4 | L2RPC="https://rinkeby.arbitrum.io/rpc" 5 | 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/README.md: -------------------------------------------------------------------------------- 1 | # Address Table Demo 2 | 3 | 4 | Address Table是 Arbitrum 上的一个预编译合约,用于注册地址,然后可以通过整数索引检索这些地址;这通过最小化用户address所需要占用的calldata数据来节省gas开销。 5 | 6 | 7 | 这个demo展示了一个简单的合约,它可以通过地址表中的索引从合约中检索地址,以及一个可以从客户端处提前注册地址的函数(如果有需要的话)。 8 | 9 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多. 10 | ### 运行demo 11 | 12 | ``` 13 | yarn run exec 14 | ``` 15 | 16 | ### 配置环境变量 17 | 18 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 19 | 20 | ```bash 21 | cp .env-sample .env 22 | ``` 23 | 24 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 25 | 26 | ### More info 27 | 28 | 查看我们的[开发者文档](https://developer.offchainlabs.com/docs/special_features)以了解更多。 29 | 30 |

31 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/contracts/ArbitrumVIP.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.2; 3 | 4 | import "arbos-precompiles/arbos/builtin/ArbAddressTable.sol"; 5 | import "hardhat/console.sol"; 6 | 7 | contract ArbitrumVIP { 8 | string greeting; 9 | mapping(address => uint256) arbitrumVIPPoints; // Maps address to vip points. More points you have, cooler you are. 10 | 11 | ArbAddressTable arbAddressTable; 12 | 13 | constructor() public { 14 | // connect to precomiled address table contract 15 | arbAddressTable = ArbAddressTable(102); 16 | } 17 | 18 | function addVIPPoints(uint256 addressIndex) external { 19 | // retreive address from address table 20 | address addressFromTable = arbAddressTable.lookupIndex(addressIndex); 21 | 22 | arbitrumVIPPoints[addressFromTable]++; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "address-table", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "exec": "hardhat run scripts/exec.js --network l2" 7 | }, 8 | "devDependencies": { 9 | "@nomiclabs/hardhat-ethers": "^2.0.2", 10 | "@nomiclabs/hardhat-waffle": "^2.0.1", 11 | "chai": "^4.3.4", 12 | "ethereum-waffle": "^3.4.0", 13 | "ethers": "^5.4.2", 14 | "hardhat": "^2.5.0" 15 | }, 16 | "dependencies": { 17 | "@arbitrum/sdk": "^2.0.0", 18 | "arbos-precompiles": "^1.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/address-table/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const hre = require('hardhat') 2 | const { 3 | ArbAddressTable__factory, 4 | } = require('@arbitrum/sdk/dist/lib/abi/factories/ArbAddressTable__factory') 5 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 6 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC']) 7 | require('dotenv').config() 8 | 9 | async function main() { 10 | await arbLog('Using the Address Table') 11 | /** 12 | * Deploy ArbitrumVIP contract to L2 13 | */ 14 | const ArbitrumVIP = await hre.ethers.getContractFactory('ArbitrumVIP') 15 | const arbitrumVIP = await ArbitrumVIP.deploy() 16 | 17 | await arbitrumVIP.deployed() 18 | 19 | console.log('ArbitrumVIP deployed to:', arbitrumVIP.address) 20 | 21 | const signers = await hre.ethers.getSigners() 22 | const myAddress = signers[0].address 23 | 24 | /** 25 | * Connect to the Arbitrum Address table pre-compile contract 26 | */ 27 | const arbAddressTable = ArbAddressTable__factory.connect( 28 | '0x0000000000000000000000000000000000000066', 29 | signers[0] 30 | ) 31 | 32 | //** 33 | /* Let's find out if our address is registered in the table: 34 | */ 35 | const addressIsRegistered = await arbAddressTable.addressExists(myAddress) 36 | 37 | if (!addressIsRegistered) { 38 | //** 39 | /* If it isn't registered yet, let's register it! 40 | */ 41 | 42 | const txnRes = await arbAddressTable.register(myAddress) 43 | const txnRec = await txnRes.wait() 44 | console.log(`Successfully registered address ${myAddress} to address table`) 45 | } else { 46 | console.log(`Address ${myAddress} already (previously) registered to table`) 47 | } 48 | /** 49 | * Now that we know it's registered, let's go ahead and retrieve its index 50 | */ 51 | const addressIndex = await arbAddressTable.lookup(myAddress) 52 | 53 | /** 54 | * From here on out we can use this index instead of our address as a paramter into any contract with affordances to look up out address in the address data. 55 | */ 56 | 57 | const txnRes = await arbitrumVIP.addVIPPoints(addressIndex) 58 | const txnRec = await txnRes.wait() 59 | /** 60 | * We got VIP points, and we minimized the calldata required, saving us precious gas. Yay rollups! 61 | */ 62 | console.log( 63 | `Successfully added VIP points using address w/ index ${addressIndex.toNumber()}` 64 | ) 65 | } 66 | main() 67 | .then(() => process.exit(0)) 68 | .catch(error => { 69 | console.error(error) 70 | process.exit(1) 71 | }) 72 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/arb-shared-dependencies/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | module.exports = { 3 | solidity: { 4 | compilers: [ 5 | { 6 | version: '0.8.2', 7 | settings: {}, 8 | }, 9 | 10 | { 11 | version: '0.7.2', 12 | settings: {}, 13 | }, 14 | { 15 | version: '0.6.12', 16 | settings: {}, 17 | }, 18 | { 19 | version: '0.6.11', 20 | settings: {}, 21 | }, 22 | ], 23 | }, 24 | networks: { 25 | l1: { 26 | gas: 2100000, 27 | gasLimit: 0, 28 | url: process.env['L1RPC'] || '', 29 | accounts: process.env['DEVNET_PRIVKEY'] 30 | ? [process.env['DEVNET_PRIVKEY']] 31 | : [], 32 | }, 33 | l2: { 34 | url: process.env['L2RPC'] || '', 35 | accounts: process.env['DEVNET_PRIVKEY'] 36 | ? [process.env['DEVNET_PRIVKEY']] 37 | : [], 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/arb-shared-dependencies/index.js: -------------------------------------------------------------------------------- 1 | const hardhatConfig = require('./hardhat.config.js') 2 | require('dotenv').config() 3 | 4 | const wait = (ms = 0) => { 5 | return new Promise(res => setTimeout(res, ms || 0)) 6 | } 7 | 8 | const arbLog = async text => { 9 | let str = '🔵' 10 | for (let i = 0; i < 25; i++) { 11 | await wait(40) 12 | if (i == 12) { 13 | str = `🔵${'🔵'.repeat(i)}🔵` 14 | } else { 15 | str = `🔵${' '.repeat(i * 2)}🔵` 16 | } 17 | while (str.length < 60) { 18 | str = ` ${str} ` 19 | } 20 | 21 | console.log(str) 22 | } 23 | 24 | console.log('Arbitrum Demo:', text) 25 | await wait(2000) 26 | 27 | console.log('Lets') 28 | await wait(1000) 29 | 30 | console.log('Go ➡️') 31 | await wait(1000) 32 | console.log('...🚀') 33 | await wait(1000) 34 | console.log('') 35 | } 36 | 37 | const requireEnvVariables = envVars => { 38 | for (const envVar of envVars) { 39 | if (!process.env[envVar]) { 40 | throw new Error(`Error: set your '${envVar}' environmental variable `) 41 | } 42 | } 43 | console.log('Environmental variables properly set 👍') 44 | } 45 | module.exports = { 46 | arbLog, 47 | hardhatConfig, 48 | requireEnvVariables, 49 | } 50 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/arb-shared-dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arb-shared-dependencies", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile" 7 | }, 8 | "main": "index.js", 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "eslint": "^7.30.0", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.2.0", 14 | "prettier": "^2.3.2" 15 | }, 16 | "dependencies": { 17 | "dotenv": "^8.2.0", 18 | "eslint-plugin-prettier": "^3.4.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY ='0x your key here' 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | 13 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/README.md: -------------------------------------------------------------------------------- 1 | # custom-token-bridging Tutorial 2 | 3 | 4 | 有一些特殊的Token需要除了StandardERC20网关之外的功能(如算法稳定币等)。因此`custom-token-bridging` 展示了如何设置这些自定义token来注册到我们的官方桥里。 5 | 6 | 如需了解更多关于Arbitrum bridge的信息, 欢迎查看 [token bridging docs](https://developer.offchainlabs.com/docs/bridging_assets). 7 | 8 | #### **使用Generic-Custom网关来使得自定义Token跨链** 9 | 10 | 将自定义令牌桥接到 Arbitrum 链是通过 Arbitrum Generic-Custom 网关完成的。我们的Generic-Custom网关的设计足够灵活,可以满足大多数(但不一定是全部)自定义erc20代币的需求。 11 | 12 | 13 | 14 | 在这里,我们在 L1 上部署了一个 [demo custom token](./contracts/L1Token.sol),在 L2 上部署了一个 [demo custom token](./contracts/L2Token.sol)。然后,我们使用 Arbitrum 自定义网关合约将我们的 L1 自定义令牌注册到我们的 L2 自定义令牌。将令牌注册到自定义网关后,我们将 L1 令牌注册到 L1 上的 Arbitrum 网关路由器。 15 | 16 | 我们使用 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 库来初始化并验证跨桥操作. 17 | 18 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多. 19 | ### 配置环境变量 20 | 21 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 22 | 23 | ```bash 24 | cp .env-sample .env 25 | ``` 26 | 27 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 28 | 29 | ### 运行: 30 | 31 | ``` 32 | yarn run custom-token-bridging 33 | ``` 34 | 35 |

36 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/contracts/ICustomToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2020, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity ^0.6.11; 20 | 21 | interface ArbitrumEnabledToken { 22 | /// @notice should return `0xa4b1` if token is enabled for arbitrum gateways 23 | function isArbitrumEnabled() external view returns (uint8); 24 | } 25 | 26 | /** 27 | * @title Minimum expected interface for L1 custom token (see TestCustomTokenL1.sol for an example implementation) 28 | */ 29 | interface ICustomToken is ArbitrumEnabledToken { 30 | /** 31 | * @notice Should make an external call to EthERC20Bridge.registerCustomL2Token 32 | */ 33 | function registerTokenOnL2( 34 | address l2CustomTokenAddress, 35 | uint256 maxSubmissionCostForCustomBridge, 36 | uint256 maxSubmissionCostForRouter, 37 | uint256 maxGasForCustomBridge, 38 | uint256 maxGasForRouter, 39 | uint256 gasPriceBid, 40 | uint256 valueForGateway, 41 | uint256 valueForRouter, 42 | address creditBackAddress 43 | ) external payable; 44 | 45 | function transferFrom( 46 | address sender, 47 | address recipient, 48 | uint256 amount 49 | ) external returns (bool); 50 | 51 | function balanceOf(address account) external view returns (uint256); 52 | } 53 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/contracts/L1Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2020, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity ^0.6.11; 20 | 21 | import "./ICustomToken.sol"; 22 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 23 | 24 | interface IGatewayRouter { 25 | function setGateway( 26 | address _gateway, 27 | uint256 _maxGas, 28 | uint256 _gasPriceBid, 29 | uint256 _maxSubmissionCost, 30 | address creditBackAddress 31 | ) external payable returns (uint256); 32 | } 33 | 34 | interface ICustomGateway { 35 | function registerTokenToL2( 36 | address _l2Address, 37 | uint256 _maxGas, 38 | uint256 _gasPriceBid, 39 | uint256 _maxSubmissionCost, 40 | address creditBackAddress 41 | ) external payable returns (uint256); 42 | } 43 | 44 | contract L1Token is ICustomToken, ERC20 { 45 | address public bridge; 46 | address public router; 47 | bool private shouldRegisterGateway; 48 | 49 | constructor( 50 | address _bridge, 51 | address _router, 52 | uint256 _premine 53 | ) public ERC20("L1CustomToken", "LCT") { 54 | bridge = _bridge; 55 | router = _router; 56 | _mint(msg.sender, _premine); 57 | } 58 | 59 | function transferFrom( 60 | address sender, 61 | address recipient, 62 | uint256 amount 63 | ) public override(ERC20, ICustomToken) returns (bool) { 64 | return ERC20.transferFrom(sender, recipient, amount); 65 | } 66 | 67 | function balanceOf(address account) 68 | public 69 | view 70 | override(ERC20, ICustomToken) 71 | returns (uint256) 72 | { 73 | return ERC20.balanceOf(account); 74 | } 75 | 76 | /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2` 77 | function isArbitrumEnabled() external view override returns (uint8) { 78 | require(shouldRegisterGateway, "NOT_EXPECTED_CALL"); 79 | return uint8(0xa4b1); 80 | } 81 | 82 | function registerTokenOnL2( 83 | address l2CustomTokenAddress, 84 | uint256 maxSubmissionCostForCustomBridge, 85 | uint256 maxSubmissionCostForRouter, 86 | uint256 maxGasForCustomBridge, 87 | uint256 maxGasForRouter, 88 | uint256 gasPriceBid, 89 | uint256 valueForGateway, 90 | uint256 valueForRouter, 91 | address creditBackAddress 92 | ) public payable override { 93 | // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed 94 | bool prev = shouldRegisterGateway; 95 | shouldRegisterGateway = true; 96 | 97 | ICustomGateway(bridge).registerTokenToL2{ value: valueForGateway }( 98 | l2CustomTokenAddress, 99 | maxGasForCustomBridge, 100 | gasPriceBid, 101 | maxSubmissionCostForCustomBridge, 102 | creditBackAddress 103 | ); 104 | 105 | IGatewayRouter(router).setGateway{ value: valueForRouter }( 106 | bridge, 107 | maxGasForRouter, 108 | gasPriceBid, 109 | maxSubmissionCostForRouter, 110 | creditBackAddress 111 | ); 112 | 113 | shouldRegisterGateway = prev; 114 | } 115 | 116 | function getChainId() public returns (uint256 chainId) { 117 | assembly { 118 | chainId := chainid() 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/contracts/L2Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2020, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity ^0.6.11; 20 | 21 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 22 | 23 | interface IArbToken { 24 | /** 25 | * @notice should increase token supply by amount, and should (probably) only be callable by the L1 bridge. 26 | */ 27 | function bridgeMint(address account, uint256 amount) external; 28 | 29 | /** 30 | * @notice should decrease token supply by amount, and should (probably) only be callable by the L1 bridge. 31 | */ 32 | function bridgeBurn(address account, uint256 amount) external; 33 | 34 | /** 35 | * @return address of layer 1 token 36 | */ 37 | function l1Address() external view returns (address); 38 | } 39 | 40 | contract L2Token is ERC20, IArbToken { 41 | address public l2Gateway; 42 | address public override l1Address; 43 | 44 | constructor(address _l2Gateway, address _l1TokenAddress) public ERC20("L2CustomToken", "L2CT") { 45 | l2Gateway = _l2Gateway; 46 | l1Address = _l1TokenAddress; 47 | } 48 | 49 | function getChainId() public returns (uint256 chainId) { 50 | assembly { 51 | chainId := chainid() 52 | } 53 | } 54 | 55 | modifier onlyL2Gateway() { 56 | require(msg.sender == l2Gateway, "NOT_GATEWAY"); 57 | _; 58 | } 59 | 60 | /** 61 | * @notice should increase token supply by amount, and should (probably) only be callable by the L1 bridge. 62 | */ 63 | function bridgeMint(address account, uint256 amount) external override onlyL2Gateway { 64 | _mint(account, amount); 65 | } 66 | 67 | /** 68 | * @notice should decrease token supply by amount, and should (probably) only be callable by the L1 bridge. 69 | */ 70 | function bridgeBurn(address account, uint256 amount) external override onlyL2Gateway { 71 | _burn(account, amount); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-token-bridging", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "hardhat compile", 6 | "custom-token-bridging": "hardhat run scripts/exec.js" 7 | }, 8 | "devDependencies": { 9 | "@nomiclabs/hardhat-ethers": "^2.0.2", 10 | "@openzeppelin/contracts": "^3.4.2", 11 | "chai": "^4.3.4", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.6.6" 14 | }, 15 | "dependencies": { 16 | "@arbitrum/sdk": "^2.0.0", 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/custom-token-bridging/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { providers, Wallet } = require('ethers') 3 | const { getL2Network, L1ToL2MessageStatus } = require('@arbitrum/sdk') 4 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 5 | const { 6 | AdminErc20Bridger, 7 | } = require('@arbitrum/sdk/dist/lib/assetBridger/erc20Bridger') 8 | const { expect } = require('chai') 9 | require('dotenv').config() 10 | requireEnvVariables(['DEVNET_PRIVKEY', 'L1RPC', 'L2RPC']) 11 | 12 | /** 13 | * Set up: instantiate L1 / L2 wallets connected to providers 14 | */ 15 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 16 | 17 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 18 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 19 | 20 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 21 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 22 | 23 | /** 24 | * Set the initial supply of L1 token that we want to bridge 25 | * Note that you can change the value 26 | */ 27 | const premine = ethers.utils.parseEther('3') 28 | 29 | const main = async () => { 30 | await arbLog( 31 | 'Setting Up Your Token With The Generic Custom Gateway Using Arbitrum SDK Library' 32 | ) 33 | 34 | /** 35 | * Use l2Network to create an Arbitrum SDK AdminErc20Bridger instance 36 | * We'll use AdminErc20Bridger for its convenience methods around registering tokens to the custom gateway 37 | */ 38 | const l2Network = await getL2Network(l2Provider) 39 | const adminTokenBridger = new AdminErc20Bridger(l2Network) 40 | 41 | const l1Gateway = l2Network.tokenBridge.l1CustomGateway 42 | const l1Router = l2Network.tokenBridge.l1GatewayRouter 43 | const l2Gateway = l2Network.tokenBridge.l2CustomGateway 44 | 45 | /** 46 | * Deploy our custom token smart contract to L1 47 | * We give the custom token contract the address of l1CustomGateway and l1GatewayRouter as well as the initial supply (premine) 48 | */ 49 | const L1CustomToken = await ( 50 | await ethers.getContractFactory('L1Token') 51 | ).connect(l1Wallet) 52 | console.log('Deploying custom token to L1') 53 | const l1CustomToken = await L1CustomToken.deploy(l1Gateway, l1Router, premine) 54 | await l1CustomToken.deployed() 55 | console.log(`custom token is deployed to L1 at ${l1CustomToken.address}`) 56 | 57 | /** 58 | * Deploy our custom token smart contract to L2 59 | * We give the custom token contract the address of l2CustomGateway and our l1CustomToken 60 | */ 61 | const L2CustomToken = await ( 62 | await ethers.getContractFactory('L2Token') 63 | ).connect(l2Wallet) 64 | console.log('Deploying custom token to L2') 65 | const l2CustomToken = await L2CustomToken.deploy( 66 | l2Gateway, 67 | l1CustomToken.address 68 | ) 69 | await l2CustomToken.deployed() 70 | console.log(`custom token is deployed to L2 at ${l2CustomToken.address}`) 71 | 72 | console.log('Registering custom token on L2:') 73 | 74 | /** 75 | * ٍRegister custom token on our custom gateway 76 | */ 77 | const registerTokenTx = await adminTokenBridger.registerCustomToken( 78 | l1CustomToken.address, 79 | l2CustomToken.address, 80 | l1Wallet, 81 | l2Provider 82 | ) 83 | 84 | const registerTokenRec = await registerTokenTx.wait() 85 | console.log( 86 | `Registering token txn confirmed on L1! 🙌 L1 receipt is: ${registerTokenRec.transactionHash}` 87 | ) 88 | 89 | /** 90 | * The L1 side is confirmed; now we listen and wait for the for the Sequencer to include the L2 side; we can do this by computing the expected txn hash of the L2 transaction. 91 | * To compute this txn hash, we need our message's "sequence numbers", unique identifiers of each L1 to L2 message. We'll fetch them from the event logs with a helper method 92 | */ 93 | const l1ToL2Msgs = await registerTokenRec.getL1ToL2Messages(l2Provider) 94 | 95 | /** 96 | * In principle, a single L1 txn can trigger any number of L1-to-L2 messages (each with its own sequencer number). 97 | * In this case, the registerTokenOnL2 method created 2 L1-to-L2 messages; (1) one to set the L1 token to the Custom Gateway via the Router, and (2) another to set the L1 token to its L2 token address via the Generic-Custom Gateway 98 | * Here, We check if both messages are redeemed on L2 99 | */ 100 | expect(l1ToL2Msgs.length, 'Should be 2 messages.').to.eq(2) 101 | 102 | const setTokenTx = await l1ToL2Msgs[0].waitForStatus() 103 | expect(setTokenTx.status, 'Set token not redeemed.').to.eq( 104 | L1ToL2MessageStatus.REDEEMED 105 | ) 106 | const setGateways = await l1ToL2Msgs[1].waitForStatus() 107 | expect(setGateways.status, 'Set gateways not redeemed.').to.eq( 108 | L1ToL2MessageStatus.REDEEMED 109 | ) 110 | 111 | console.log( 112 | 'Your custom token is now registered on our custom gateway 🥳 Go ahead and make the deposit!' 113 | ) 114 | } 115 | 116 | main() 117 | .then(() => process.exit(0)) 118 | .catch(error => { 119 | console.error(error) 120 | process.exit(1) 121 | }) 122 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/.env-sample: -------------------------------------------------------------------------------- 1 | DEVNET_PRIVKEY="0x your key here" 2 | 3 | # this is rinkarby, can use any Arbitrum chain 4 | L2RPC="https://rinkeby.arbitrum.io/rpc" 5 | 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/.gitignore: -------------------------------------------------------------------------------- 1 | *.ao 2 | .DS_Store 3 | build 4 | compiled.json 5 | compose 6 | docker-compose.arb-deploy.yml 7 | node_modules 8 | validator-states 9 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/README.md: -------------------------------------------------------------------------------- 1 | # demo-dapp-election Tutorial 2 | 3 | demo-dapp-election 是一个简单的demo示例,允许您将 Election 合约部署到 Arbitrum 并运行其功能。 4 | 5 | 合约完全依赖于 L2 且不涉及直接的 L1 交互;在该过程中,你可以感受到编写、部署和与之交互就像使用 L1 合约一样。 6 | 7 | ### 配置环境变量 8 | 9 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 10 | 11 | ```bash 12 | cp .env-sample .env 13 | ``` 14 | 15 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 16 | 17 | ### 运行Demo 18 | 19 | ```bash 20 | yarn run exec 21 | ``` 22 | 23 | ## 想要查看在Arbitrum链上的输出? 24 | 25 | 脚本执行成功后,您可以前往[Arbitrum 区块浏览器](https://rinkeby-explorer.arbitrum.io/#),输入您的 L2 地址,即可查看 Arbitrum 链上对应的交易! 26 | 27 |

28 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/contracts/Election.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.7.0; 2 | 3 | contract Election { 4 | // Model a Candidate 5 | struct Candidate { 6 | uint256 id; 7 | string name; 8 | uint256 voteCount; 9 | } 10 | 11 | // Store accounts that have voted 12 | mapping(address => bool) public voters; 13 | // Store Candidates 14 | // Fetch Candidate 15 | mapping(uint256 => Candidate) public candidates; 16 | // Store Candidates Count 17 | uint256 public candidatesCount; 18 | 19 | // voted event 20 | event votedEvent(uint256 indexed _candidateId); 21 | 22 | constructor() public { 23 | addCandidate("Candidate 1"); 24 | addCandidate("Candidate 2"); 25 | } 26 | 27 | function addCandidate(string memory _name) private { 28 | candidatesCount++; 29 | candidates[candidatesCount] = Candidate(candidatesCount, _name, 0); 30 | } 31 | 32 | function vote(uint256 _candidateId) public { 33 | // require that they haven't voted before 34 | require(!voters[msg.sender]); 35 | 36 | // require a valid candidate 37 | require(_candidateId > 0 && _candidateId <= candidatesCount); 38 | 39 | // record that voter has voted 40 | voters[msg.sender] = true; 41 | 42 | // update candidate vote Count 43 | candidates[_candidateId].voteCount++; 44 | 45 | // trigger voted event 46 | emit votedEvent(_candidateId); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | 3 | const { hardhatConfig } = require('arb-shared-dependencies') 4 | 5 | module.exports = hardhatConfig 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-dapp-election", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "exec": "hardhat run scripts/exec.js --network l2" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "chai": "^4.3.4", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.2.0" 14 | }, 15 | "dependencies": { 16 | "dotenv": "^8.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-election/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const hre = require('hardhat') 2 | const { ethers } = require('hardhat') 3 | const { expect } = require('chai') 4 | 5 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 6 | require('dotenv').config() 7 | 8 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC']) 9 | 10 | const main = async () => { 11 | await arbLog('Simple Election DApp') 12 | 13 | const l2Wallet = (await hre.ethers.getSigners())[0] 14 | console.log('Your wallet address:', l2Wallet.address) 15 | 16 | const L2Election = await ( 17 | await ethers.getContractFactory('Election') 18 | ).connect(l2Wallet) 19 | console.log('Deploying Election contract to L2') 20 | const l2election = await L2Election.deploy() 21 | await l2election.deployed() 22 | console.log( 23 | `Election contract is initialized with 2 candidates and deployed to ${l2election.address}` 24 | ) 25 | 26 | //Fetch the candidate count 27 | const count = await l2election.candidatesCount() 28 | expect(count.toNumber()).to.equal(2) 29 | console.log('The election is indeed initialized with two candidates!') 30 | 31 | //Fetch the candidates values (id, name, voteCount) and make sure they are set correctly 32 | var candidate1 = await l2election.candidates(1) 33 | expect(candidate1[0].toNumber()).to.equal(1) 34 | expect(candidate1[1]).to.equal('Candidate 1') 35 | expect(candidate1[2].toNumber()).to.equal(0) 36 | 37 | var candidate2 = await l2election.candidates(2) 38 | expect(candidate2[0].toNumber()).to.equal(2) 39 | expect(candidate2[1]).to.equal('Candidate 2') 40 | expect(candidate2[2].toNumber()).to.equal(0) 41 | console.log('candidates are initialized with the correct values!') 42 | 43 | //Cast a vote for candidate1 44 | var candidateId 45 | var candidate 46 | var voteCount 47 | candidateId = 1 48 | 49 | const voteTx1 = await l2election.vote(candidateId) 50 | const vote1Rec = await voteTx1.wait() 51 | expect(vote1Rec.status).to.equal(1) 52 | console.log('Vote tx is executed!') 53 | 54 | const voted = await l2election.voters(l2Wallet.address) 55 | expect(voted).to.be.true 56 | console.log('You have voted for candidate1!') 57 | 58 | //Fetch the candidate1 voteCount and make sure it's equal to 1 59 | candidate = await l2election.candidates(candidateId) 60 | voteCount = candidate[2] 61 | expect(voteCount.toNumber()).to.equal(1) 62 | console.log('Candidate1 has one vote!') 63 | 64 | //Fetch Candidate2 and make sure it did not receive any votes yet 65 | candidate = await l2election.candidates(2) 66 | voteCount = candidate[2] 67 | expect(voteCount.toNumber()).to.equal(0) 68 | console.log('Candidate2 has zero vote!') 69 | } 70 | 71 | main() 72 | .then(() => process.exit(0)) 73 | .catch(error => { 74 | console.error(error) 75 | process.exit(1) 76 | }) 77 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/.env-sample: -------------------------------------------------------------------------------- 1 | DEVNET_PRIVKEY="0x your key here" 2 | 3 | # this is rinkarby, can use any Arbitrum chain 4 | L2RPC="https://rinkeby.arbitrum.io/rpc" 5 | 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/README.md: -------------------------------------------------------------------------------- 1 | # demo-dapp-pet-shop Tutorial 2 | 3 | 4 | 5 | demo-dapp-pet-shop 是一个简单的demo示例,允许您将adoption合约部署到 Arbitrum 并运行其功能。 6 | 7 | 合约完全依赖于 L2 且不涉及直接的 L1 交互;编写、部署和与之交互就像使用 L1 合约一样。 8 | 9 | ### 配置环境变量 10 | 11 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 12 | 13 | ```bash 14 | cp .env-sample .env 15 | ``` 16 | 17 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 18 | 19 | ### 运行Demo 20 | 21 | ```bash 22 | yarn run exec 23 | ``` 24 | 25 | ### 想要查看在Arbitrum链上的输出? 26 | 27 | 脚本执行成功后,您可以前往[Arbitrum 区块浏览器](https://rinkeby-explorer.arbitrum.io/#),输入您的 L2 地址,即可查看 Arbitrum 链上对应的交易! 28 | 29 |

30 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/contracts/Adoption.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.7.0; 2 | 3 | contract Adoption { 4 | event PetAdopted(uint256 returnValue); 5 | 6 | address[16] public adopters = [ 7 | address(0), 8 | address(0), 9 | address(0), 10 | address(0), 11 | address(0), 12 | address(0), 13 | address(0), 14 | address(0), 15 | address(0), 16 | address(0), 17 | address(0), 18 | address(0), 19 | address(0), 20 | address(0), 21 | address(0), 22 | address(0) 23 | ]; 24 | 25 | // Adopting a pet 26 | function adopt(uint256 petId) public returns (uint256) { 27 | require(petId >= 0 && petId <= 15); 28 | 29 | adopters[petId] = msg.sender; 30 | emit PetAdopted(petId); 31 | return petId; 32 | } 33 | 34 | // Retrieving the adopters 35 | function getAdopters() public view returns (address[16] memory) { 36 | return adopters; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | 3 | const { hardhatConfig } = require('arb-shared-dependencies') 4 | 5 | module.exports = hardhatConfig 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-dapp-pet-shop", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "exec": "hardhat run scripts/exec.js --network l2" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "ethers": "^5.1.2", 12 | "hardhat": "^2.2.0" 13 | }, 14 | "dependencies": { 15 | "dotenv": "^8.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/demo-dapp-pet-shop/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const hre = require('hardhat') 2 | const { ethers } = require('hardhat') 3 | const { expect } = require('chai') 4 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 5 | require('dotenv').config() 6 | 7 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC']) 8 | 9 | const main = async () => { 10 | await arbLog('Simple Pet Shop DApp') 11 | 12 | const l2Wallet = (await hre.ethers.getSigners())[0] 13 | console.log('Your wallet address:', l2Wallet.address) 14 | 15 | const L2Adoption = await ( 16 | await ethers.getContractFactory('Adoption') 17 | ).connect(l2Wallet) 18 | console.log('Deploying Adoption contract to L2') 19 | const l2adoption = await L2Adoption.deploy() 20 | await l2adoption.deployed() 21 | console.log(`Adoption contract is deployed to ${l2adoption.address}`) 22 | 23 | // The id of the pet that will be used for testing 24 | const expectedPetId = 8 25 | 26 | // The expected owner of adopted pet is your l2wallet 27 | const expectedAdopter = l2Wallet.address 28 | 29 | // Testing the adopt() function 30 | console.log('Adopting pet:') 31 | 32 | const adoptionEventData = await l2adoption.adopt(expectedPetId) 33 | expect(adoptionEventData).to.exist 34 | 35 | // Testing retrieval of a single pet's owner 36 | const adopter = await l2adoption.adopters(expectedPetId) 37 | expect(expectedAdopter).to.equal(adopter) // The owner of the expected pet should be your l2wallet 38 | console.log(`Pet adopted; owner: ${adopter}`) 39 | 40 | // Testing retrieval of all pet owners 41 | let adopters = [16] 42 | adopters = await l2adoption.getAdopters() 43 | expect(adopters[expectedPetId]).to.equal(expectedAdopter) // The owner of the expected pet should be your l2wallet 44 | console.log('All pet owners:', adopters) 45 | } 46 | 47 | main() 48 | .then(() => process.exit(0)) 49 | .catch(error => { 50 | console.error(error) 51 | process.exit(1) 52 | }) 53 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY ='0x your key here' 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | cache/ 3 | build/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/README.md: -------------------------------------------------------------------------------- 1 | # eth-deposit Tutorial 2 | 3 | 4 | `eth-deposit` 展示了如何将以太币从以太坊(第 1 层)转移到 Arbitrum(第 2 层)链中。 5 | 6 | ### 它是如何工作的(底层) 7 | 8 | 用户使用 Arbitrum 的通用 L1-to-L2 消息传递系统将 Ether 存入 Arbitrum,只需将所需的 Ether 作为调用值传递,并且无需额外数据。有关详细信息,请参阅 [可重试票证文档](https://developer.offchainlabs.com/docs/l1_l2_messages#depositing-eth-via-retryables)。 9 | 10 | ### **使用Arbitrum SDK工具集** 11 | 12 | 13 | 我们的 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 提供了一种简单便捷的 Ether 存款方法,消除了客户端手动连接任何合约的需要。 14 | 15 | 16 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 17 | 18 | To run: 19 | 20 | ``` 21 | yarn run depositETH 22 | ``` 23 | 24 | ### 配置环境变量 25 | 26 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 27 | 28 | ```bash 29 | cp .env-sample .env 30 | ``` 31 | 32 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 33 | 34 | --- 35 | 36 | 脚本执行成功后,您可以前往 [Arbitrum 区块浏览器](https://rinkeby-explorer.arbitrum.io/#),输入您的地址,查看分配给您地址的 ETH 数量在仲裁链上! 37 | 38 |

39 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-deposit", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "depositETH": "hardhat run scripts/exec.js" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "ethers": "^5.4.1", 12 | "hardhat": "^2.2.0" 13 | }, 14 | "dependencies": { 15 | "@arbitrum/sdk": "^2.0.0", 16 | "dotenv": "^8.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-deposit/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { utils, providers, Wallet } = require('ethers') 2 | const { 3 | EthBridger, 4 | getL2Network, 5 | L1ToL2MessageStatus, 6 | } = require('@arbitrum/sdk') 7 | const { parseEther } = utils 8 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 9 | require('dotenv').config() 10 | requireEnvVariables(['DEVNET_PRIVKEY', 'L1RPC', 'L2RPC']) 11 | 12 | /** 13 | * Set up: instantiate L1 / L2 wallets connected to providers 14 | */ 15 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 16 | 17 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 18 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 19 | 20 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 21 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 22 | 23 | /** 24 | * Set the amount to be deposited in L2 (in wei) 25 | */ 26 | const ethToL2DepositAmount = parseEther('0.0001') 27 | 28 | const main = async () => { 29 | await arbLog('Deposit Eth via Arbitrum SDK') 30 | 31 | /** 32 | * Use l2Network to create an Arbitrum SDK EthBridger instance 33 | * We'll use EthBridger for its convenience methods around transferring ETH to L2 34 | */ 35 | 36 | const l2Network = await getL2Network(l2Provider) 37 | const ethBridger = new EthBridger(l2Network) 38 | 39 | /** 40 | * First, let's check the l2Wallet initial ETH balance 41 | */ 42 | const l2WalletInitialEthBalance = await l2Wallet.getBalance() 43 | 44 | /** 45 | * transfer ether from L1 to L2 46 | * This convenience method automatically queries for the retryable's max submission cost and forwards the appropriate amount to L2 47 | * Arguments required are: 48 | * (1) amount: The amount of ETH to be transferred to L2 49 | * (2) l1Signer: The L1 address transferring ETH to L2 50 | * (3) l2Provider: An l2 provider 51 | */ 52 | const depositTx = await ethBridger.deposit({ 53 | amount: ethToL2DepositAmount, 54 | l1Signer: l1Wallet, 55 | l2Provider: l2Provider, 56 | }) 57 | 58 | const depositRec = await depositTx.wait() 59 | console.warn('deposit L1 receipt is:', depositRec.transactionHash) 60 | 61 | /** 62 | * With the transaction confirmed on L1, we now wait for the L2 side (i.e., balance credited to L2) to be confirmed as well. 63 | * Here we're waiting for the Sequencer to include the L2 message in its off-chain queue. The Sequencer should include it in under 10 minutes. 64 | */ 65 | console.warn('Now we wait for L2 side of the transaction to be executed ⏳') 66 | const l2Result = await depositRec.waitForL2(l2Provider) 67 | 68 | /** 69 | * The `complete` boolean tells us if the l1 to l2 message was successul 70 | */ 71 | l2Result.complete 72 | ? console.log( 73 | `L2 message successful: status: ${L1ToL2MessageStatus[l2Result.status]}` 74 | ) 75 | : console.log( 76 | `L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}` 77 | ) 78 | 79 | /** 80 | * Our l2Wallet ETH balance should be updated now 81 | */ 82 | const l2WalletUpdatedEthBalance = await l2Wallet.getBalance() 83 | console.log( 84 | `your L2 ETH balance is updated from ${l2WalletInitialEthBalance.toString()} to ${l2WalletUpdatedEthBalance.toString()}` 85 | ) 86 | } 87 | main() 88 | .then(() => process.exit(0)) 89 | .catch(error => { 90 | console.error(error) 91 | process.exit(1) 92 | }) 93 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | artifacts/ 3 | cache/ 4 | build/ 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/README.md: -------------------------------------------------------------------------------- 1 | # eth-withdraw Tutorial 2 | 3 | 4 | 5 | `eth-withdraw` 展示了如何将Ether从 Arbitrum(第 2 层)转移到以太坊(第 1 层)链中。 6 | 7 | 请注意,此 repo 涵盖了初始化交易和 Ether withdraw;有关(稍后)从Outbox合约释放资金的演示,请参阅 [outbox-execute](../outbox-execute/README.md) 8 | 9 | ### 它是如何工作的(底层) 10 | 11 | 为了从 Arbitrum 提取 Ether,客户端使用“ArbSys”接口创建 `L2->L1` 消息,并在稍后允许他们从 L1 Bridge.sol 合约中的中释放 Ether。有关详细信息,请参阅 [Outgoing messages文档](https://developer.offchainlabs.com/docs/l1_l2_messages#l2-to-l1-messages-lifecycle)。 12 | 13 | 14 | _注意: 执行脚本将需要您的 L2 帐户资金中至少有0.000001 Eth._ 15 | 16 | ### **使用Arbitrum SDK工具集** 17 | 18 | 我们的 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 提供了一种提取 Ether 的简单方法,使得客户端不需要手动连接任何合约。 19 | 20 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 21 | 22 | ### 运行Demo 23 | 24 | ``` 25 | yarn run withdrawETH 26 | ``` 27 | 28 | ### 配置环境变量 29 | 30 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 31 | 32 | ```bash 33 | cp .env-sample .env 34 | ``` 35 | 36 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 37 | 38 | --- 39 | 40 | ### 想要查看在Arbitrum链上的输出? 41 | 42 | 43 | 脚本成功执行后,您可以前往 [Arbitrum 区块浏览器](https://rinkeby-explorer.arbitrum.io/#),输入您的地址,查看您的 Layer 2 余额中扣除的 ETH 数量.请注意,您的第 1 层余额只会在rollups的争议期结束后更新。 44 |

45 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-withdraw", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "withdrawETH": "hardhat run scripts/exec.js" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "ethers": "^5.1.2", 12 | "hardhat": "^2.2.0" 13 | }, 14 | "dependencies": { 15 | "@arbitrum/sdk": "^2.0.0", 16 | "dotenv": "^8.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/eth-withdraw/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { utils, providers, Wallet } = require('ethers') 2 | const { EthBridger, getL2Network, L2ToL1Message } = require('@arbitrum/sdk') 3 | const { parseEther } = utils 4 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 5 | require('dotenv').config() 6 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC', 'L1RPC']) 7 | 8 | /** 9 | * Set up: instantiate L2 wallet connected to provider 10 | */ 11 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 12 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 13 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 14 | 15 | /** 16 | * Set the amount to be withdrawn from L2 (in wei) 17 | */ 18 | const ethFromL2WithdrawAmount = parseEther('0.000001') 19 | 20 | const main = async () => { 21 | await arbLog('Withdraw Eth via Arbitrum SDK') 22 | 23 | /** 24 | * Use l2Network to create an Arbitrum SDK EthBridger instance 25 | * We'll use EthBridger for its convenience methods around transferring ETH from L2 to L1 26 | */ 27 | 28 | const l2Network = await getL2Network(l2Provider) 29 | const ethBridger = new EthBridger(l2Network) 30 | 31 | /** 32 | * First, let's check our L2 wallet's initial ETH balance and ensure there's some ETH to withdraw 33 | */ 34 | const l2WalletInitialEthBalance = await l2Wallet.getBalance() 35 | 36 | if (l2WalletInitialEthBalance.lt(ethFromL2WithdrawAmount)) { 37 | console.log( 38 | `Oops - not enough ether; fund your account L2 wallet currently ${l2Wallet.address} with at least 0.000001 ether` 39 | ) 40 | process.exit(1) 41 | } 42 | console.log('Wallet properly funded: initiating withdrawal now') 43 | 44 | /** 45 | * We're ready to withdraw ETH using the ethBridger instance from Arbitrum SDK 46 | * It will use our current wallet's address as the default destination 47 | */ 48 | 49 | const withdrawTx = await ethBridger.withdraw({ 50 | amount: ethFromL2WithdrawAmount, 51 | l2Signer: l2Wallet, 52 | }) 53 | const withdrawRec = await withdrawTx.wait() 54 | 55 | /** 56 | * And with that, our withdrawal is initiated! No additional time-sensitive actions are required. 57 | * Any time after the transaction's assertion is confirmed, funds can be transferred out of the bridge via the outbox contract 58 | * We'll display the withdrawals event data here: 59 | */ 60 | console.log(`Ether withdrawal initiated! 🥳 ${withdrawRec.transactionHash}`) 61 | 62 | const withdrawEventsData = await withdrawRec.getL2ToL1Events() 63 | console.log('Withdrawal data:', withdrawEventsData) 64 | console.log( 65 | `To to claim funds (after dispute period), see outbox-execute repo ✌️` 66 | ) 67 | } 68 | 69 | main() 70 | .then(() => process.exit(0)) 71 | .catch(error => { 72 | console.error(error) 73 | process.exit(1) 74 | }) 75 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | 13 | 14 | # Address of the Arbitrum Inbox on the L1 chain 15 | INBOX_ADDR=0x578bade599406a8fe3d24fd7f7211c0911f5b29e -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | cache/ 3 | build/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/README.md: -------------------------------------------------------------------------------- 1 | # Greeter Tutorial 2 | 3 | 4 | `greeter` 是 Arbitrum L1 到 L2 消息传递系统(又名“可重试票证”)的简单演示。 5 | 6 | 它部署了 2 个合约——一个到 L1,另一个到 L2,并让 L1 合约向 L2 合约发送消息以自动执行。 7 | 8 | 脚本和合约演示了如何与 Arbitrum 的核心桥接合约交互以创建这些可重试消息、如何计算并将适当的费用从 L1 转发到 L2 以及如何使用 Arbitrum 的 L1-to-L2 消息 [地址别名](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing)。 9 | 10 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多. 11 | 12 | 点击[这里](https://developer.offchainlabs.com/docs/l1_l2_messages)以获取可重试票据的更多相关信息。 13 | 14 | ### 运行Demo: 15 | 16 | ``` 17 | yarn run greeter 18 | ``` 19 | 20 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 21 | 22 | ### 配置环境变量 23 | 24 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 25 | 26 | ```bash 27 | cp .env-sample .env 28 | ``` 29 | 30 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 31 | 32 | 33 |

34 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | contract Greeter { 5 | string greeting; 6 | 7 | constructor(string memory _greeting) public { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public virtual { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/contracts/arbitrum/GreeterL2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | import "arbos-precompiles/arbos/builtin/ArbSys.sol"; 5 | import "arb-bridge-eth/contracts/libraries/AddressAliasHelper.sol"; 6 | import "../Greeter.sol"; 7 | 8 | contract GreeterL2 is Greeter { 9 | ArbSys constant arbsys = ArbSys(100); 10 | address public l1Target; 11 | 12 | event L2ToL1TxCreated(uint256 indexed withdrawalId); 13 | 14 | constructor(string memory _greeting, address _l1Target) public Greeter(_greeting) { 15 | l1Target = _l1Target; 16 | } 17 | 18 | function updateL1Target(address _l1Target) public { 19 | l1Target = _l1Target; 20 | } 21 | 22 | function setGreetingInL1(string memory _greeting) public returns (uint256) { 23 | bytes memory data = abi.encodeWithSelector(Greeter.setGreeting.selector, _greeting); 24 | 25 | uint256 withdrawalId = arbsys.sendTxToL1(l1Target, data); 26 | 27 | emit L2ToL1TxCreated(withdrawalId); 28 | return withdrawalId; 29 | } 30 | 31 | /// @notice only l1Target can update greeting 32 | function setGreeting(string memory _greeting) public override { 33 | // To check that message came from L1, we check that the sender is the L1 contract's L2 alias. 34 | require( 35 | msg.sender == AddressAliasHelper.applyL1ToL2Alias(l1Target), 36 | "Greeting only updateable by L1" 37 | ); 38 | Greeter.setGreeting(_greeting); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/contracts/ethereum/GreeterL1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | import "arb-bridge-eth/contracts/bridge/Inbox.sol"; 5 | import "arb-bridge-eth/contracts/bridge/Outbox.sol"; 6 | import "../Greeter.sol"; 7 | 8 | contract GreeterL1 is Greeter { 9 | address public l2Target; 10 | IInbox public inbox; 11 | 12 | event RetryableTicketCreated(uint256 indexed ticketId); 13 | 14 | constructor( 15 | string memory _greeting, 16 | address _l2Target, 17 | address _inbox 18 | ) public Greeter(_greeting) { 19 | l2Target = _l2Target; 20 | inbox = IInbox(_inbox); 21 | } 22 | 23 | function updateL2Target(address _l2Target) public { 24 | l2Target = _l2Target; 25 | } 26 | 27 | function setGreetingInL2( 28 | string memory _greeting, 29 | uint256 maxSubmissionCost, 30 | uint256 maxGas, 31 | uint256 gasPriceBid 32 | ) public payable returns (uint256) { 33 | bytes memory data = abi.encodeWithSelector(Greeter.setGreeting.selector, _greeting); 34 | uint256 ticketID = inbox.createRetryableTicket{ value: msg.value }( 35 | l2Target, 36 | 0, 37 | maxSubmissionCost, 38 | msg.sender, 39 | msg.sender, 40 | maxGas, 41 | gasPriceBid, 42 | data 43 | ); 44 | 45 | emit RetryableTicketCreated(ticketID); 46 | return ticketID; 47 | } 48 | 49 | /// @notice only l2Target can update greeting 50 | function setGreeting(string memory _greeting) public override { 51 | IBridge bridge = inbox.bridge(); 52 | // this prevents reentrancies on L2 to L1 txs 53 | require(msg.sender == address(bridge), "NOT_BRIDGE"); 54 | IOutbox outbox = IOutbox(bridge.activeOutbox()); 55 | address l2Sender = outbox.l2ToL1Sender(); 56 | require(l2Sender == l2Target, "Greeting only updateable by L2"); 57 | 58 | Greeter.setGreeting(_greeting); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greeter", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "greeter": "hardhat run scripts/exec.js" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "ethers": "^5.1.2", 12 | "hardhat": "^2.2.0" 13 | }, 14 | "dependencies": { 15 | "arb-bridge-eth": "^0.7.5", 16 | "@arbitrum/sdk": "^2.0.0", 17 | "arbos-precompiles": "^1.0.2", 18 | "dotenv": "^8.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/greeter/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { providers, Wallet } = require('ethers') 2 | const hre = require('hardhat') 3 | const ethers = require('ethers') 4 | const { hexDataLength } = require('@ethersproject/bytes') 5 | const { 6 | L1ToL2MessageGasEstimator, 7 | } = require('@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator') 8 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 9 | const { L1TransactionReceipt, L1ToL2MessageStatus } = require('@arbitrum/sdk') 10 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC', 'L1RPC', 'INBOX_ADDR']) 11 | 12 | /** 13 | * Set up: instantiate L1 / L2 wallets connected to providers 14 | */ 15 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 16 | 17 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 18 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 19 | 20 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 21 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 22 | 23 | const main = async () => { 24 | await arbLog('Cross-chain Greeter') 25 | 26 | /** 27 | * We deploy L1 Greeter to L1, L2 greeter to L2, each with a different "greeting" message. 28 | * After deploying, save set each contract's counterparty's address to its state so that they can later talk to each other. 29 | */ 30 | const L1Greeter = await ( 31 | await hre.ethers.getContractFactory('GreeterL1') 32 | ).connect(l1Wallet) // 33 | console.log('Deploying L1 Greeter 👋') 34 | const l1Greeter = await L1Greeter.deploy( 35 | 'Hello world in L1', 36 | ethers.constants.AddressZero, // temp l2 addr 37 | process.env.INBOX_ADDR 38 | ) 39 | await l1Greeter.deployed() 40 | console.log(`deployed to ${l1Greeter.address}`) 41 | const L2Greeter = await ( 42 | await hre.ethers.getContractFactory('GreeterL2') 43 | ).connect(l2Wallet) 44 | 45 | console.log('Deploying L2 Greeter 👋👋') 46 | 47 | const l2Greeter = await L2Greeter.deploy( 48 | 'Hello world in L2', 49 | ethers.constants.AddressZero // temp l1 addr 50 | ) 51 | await l2Greeter.deployed() 52 | console.log(`deployed to ${l2Greeter.address}`) 53 | 54 | const updateL1Tx = await l1Greeter.updateL2Target(l2Greeter.address) 55 | await updateL1Tx.wait() 56 | 57 | const updateL2Tx = await l2Greeter.updateL1Target(l1Greeter.address) 58 | await updateL2Tx.wait() 59 | console.log('Counterpart contract addresses set in both greeters 👍') 60 | 61 | /** 62 | * Let's log the L2 greeting string 63 | */ 64 | const currentL2Greeting = await l2Greeter.greet() 65 | console.log(`Current L2 greeting: "${currentL2Greeting}"`) 66 | 67 | console.log('Updating greeting from L1 to L2:') 68 | 69 | /** 70 | * Here we have a new greeting message that we want to set as the L2 greeting; we'll be setting it by sending it as a message from layer 1!!! 71 | */ 72 | const newGreeting = 'Greeting from far, far away' 73 | 74 | /** 75 | * To send an L1-to-L2 message (aka a "retryable ticket"), we need to send ether from L1 to pay for the txn costs on L2. 76 | * There are two costs we need to account for: base submission cost and cost of L2 execution. We'll start with base submission cost. 77 | */ 78 | 79 | /** 80 | * Base submission cost is a special cost for creating a retryable ticket; querying the cost requires us to know how many bytes of calldata out retryable ticket will require, so let's figure that out. 81 | * We'll get the bytes for our greeting data, then add 4 for the 4-byte function signature. 82 | */ 83 | 84 | const newGreetingBytes = ethers.utils.defaultAbiCoder.encode( 85 | ['string'], 86 | [newGreeting] 87 | ) 88 | const newGreetingBytesLength = hexDataLength(newGreetingBytes) + 4 // 4 bytes func identifier 89 | 90 | /** 91 | * Now we can query the submission price using a helper method; the first value returned tells us the best cost of our transaction; that's what we'll be using. 92 | * The second value (nextUpdateTimestamp) tells us when the base cost will next update (base cost changes over time with chain congestion; the value updates every 24 hours). We won't actually use it here, but generally it's useful info to have. 93 | */ 94 | const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider) 95 | 96 | const _submissionPriceWei = 97 | await l1ToL2MessageGasEstimate.estimateSubmissionFee( 98 | l1Provider, 99 | await l1Provider.getGasPrice(), 100 | newGreetingBytesLength 101 | ) 102 | 103 | console.log( 104 | `Current retryable base submission price: ${_submissionPriceWei.toString()}` 105 | ) 106 | 107 | /** 108 | * ...Okay, but on the off chance we end up underpaying, our retryable ticket simply fails. 109 | * This is highly unlikely, but just to be safe, let's increase the amount we'll be paying (the difference between the actual cost and the amount we pay gets refunded to our address on L2 anyway) 110 | * In nitro, submission fee will be charged in L1 based on L1 basefee, revert on L1 side upon insufficient fee. 111 | */ 112 | const submissionPriceWei = _submissionPriceWei.mul(5) 113 | /** 114 | * Now we'll figure out the gas we need to send for L2 execution; this requires the L2 gas price and gas limit for our L2 transaction 115 | */ 116 | 117 | /** 118 | * For the L2 gas price, we simply query it from the L2 provider, as we would when using L1 119 | */ 120 | const gasPriceBid = await l2Provider.getGasPrice() 121 | console.log(`L2 gas price: ${gasPriceBid.toString()}`) 122 | 123 | /** 124 | * For the gas limit, we'll use the estimateRetryableTicketMaxGas method in Arbitrum SDK 125 | */ 126 | 127 | /** 128 | * First, we need to calculate the calldata for the function being called (setGreeting()) 129 | */ 130 | const ABI = ['function setGreeting(string _greeting)'] 131 | const iface = new ethers.utils.Interface(ABI) 132 | const calldata = iface.encodeFunctionData('setGreeting', [newGreeting]) 133 | 134 | const maxGas = await l1ToL2MessageGasEstimate.estimateRetryableTicketMaxGas( 135 | l1Greeter.address, 136 | ethers.utils.parseEther('1'), 137 | l2Greeter.address, 138 | 0, 139 | submissionPriceWei, 140 | l2Wallet.address, 141 | l2Wallet.address, 142 | 100000, 143 | gasPriceBid, 144 | calldata 145 | ) 146 | /** 147 | * With these three values, we can calculate the total callvalue we'll need our L1 transaction to send to L2 148 | */ 149 | const callValue = submissionPriceWei.add(gasPriceBid.mul(maxGas)) 150 | 151 | console.log( 152 | `Sending greeting to L2 with ${callValue.toString()} callValue for L2 fees:` 153 | ) 154 | 155 | const setGreetingTx = await l1Greeter.setGreetingInL2( 156 | newGreeting, // string memory _greeting, 157 | submissionPriceWei, 158 | maxGas, 159 | gasPriceBid, 160 | { 161 | value: callValue, 162 | } 163 | ) 164 | const setGreetingRec = await setGreetingTx.wait() 165 | 166 | console.log( 167 | `Greeting txn confirmed on L1! 🙌 ${setGreetingRec.transactionHash}` 168 | ) 169 | 170 | const l1TxReceipt = new L1TransactionReceipt(setGreetingRec) 171 | 172 | /** 173 | * In principle, a single L1 txn can trigger any number of L1-to-L2 messages (each with its own sequencer number). 174 | * In this case, we know our txn triggered only one 175 | * Here, We check if our L1 to L2 message is redeemed on L2 176 | */ 177 | const message = await l1TxReceipt.getL1ToL2Message(l2Wallet) 178 | const status = await message.waitForStatus() 179 | console.log(status) 180 | if (status === L1ToL2MessageStatus.REDEEMED) { 181 | console.log(`L2 retryable txn executed 🥳 ${message.l2TxHash}`) 182 | } else { 183 | console.log( 184 | `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` 185 | ) 186 | } 187 | 188 | /** 189 | * Note that during L2 execution, a retryable's sender address is transformed to its L2 alias. 190 | * Thus, when GreeterL2 checks that the message came from the L1, we check that the sender is this L2 Alias. 191 | * See setGreeting in GreeterL2.sol for this check. 192 | */ 193 | 194 | /** 195 | * Now when we call greet again, we should see our new string on L2! 196 | */ 197 | const newGreetingL2 = await l2Greeter.greet() 198 | console.log(`Updated L2 greeting: "${newGreetingL2}"`) 199 | console.log('✌️') 200 | } 201 | 202 | main() 203 | .then(() => process.exit(0)) 204 | .catch(error => { 205 | console.error(error) 206 | process.exit(1) 207 | }) 208 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/outbox-execute/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC; i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/outbox-execute/README.md: -------------------------------------------------------------------------------- 1 | # Outbox Demo 2 | 3 | 4 | Outbox合约负责接收和执行所有“Outgoing”消息;即,从 Arbitrum 传递到以太坊的消息。 5 | 6 | (预期的)最常见的用例是提款(例如,以太币或其他erc20代币),但Outbox可以处理任何合约调用,如本演示所示。 7 | 8 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 9 | 10 | ## 配置环境变量 11 | 12 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 13 | 14 | ```bash 15 | cp .env-sample .env 16 | ``` 17 | 18 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 19 | 20 | ### 运行demo 21 | 22 | ``` 23 | yarn outbox-exec --txhash 0xmytxnhash 24 | ``` 25 | 26 | 27 | - _0xmytxnhash_ 是触发L2->L1消息的L2交易哈希。 28 | 29 | ### 更多信息 30 | 31 | 欢迎查看我们的[跨层消息文档](https://developer.offchainlabs.com/docs/l1_l2_messages). 32 | 33 |

34 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/outbox-execute/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const main = require('./scripts/exec.js') 3 | const { hardhatConfig } = require('arb-shared-dependencies') 4 | 5 | const { task } = require('hardhat/config') 6 | 7 | const accounts = { 8 | mnemonic: 9 | 'rule nation tired logic palace city picnic bubble ridge grain problem pilot', 10 | path: "m/44'/60'/0'/0", 11 | initialIndex: 0, 12 | count: 10, 13 | } 14 | 15 | task('outbox-exec', "Prints an account's balance") 16 | .addParam('txhash', 'Hash of txn that triggered and L2 to L1 message') 17 | 18 | .setAction(async args => { 19 | await main(args.txhash) 20 | }) 21 | 22 | /** 23 | * @type import('hardhat/config').HardhatUserConfig 24 | */ 25 | module.exports = hardhatConfig 26 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/outbox-execute/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outbox-execute", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "outbox-exec": "hardhat outbox-exec" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "chai": "^4.3.4", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.9.1" 14 | }, 15 | "dependencies": { 16 | "@arbitrum/sdk": "^2.0.0", 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/outbox-execute/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { providers, Wallet } = require('ethers') 2 | const { 3 | L2TransactionReceipt, 4 | getL2Network, 5 | L2ToL1MessageStatus, 6 | } = require('@arbitrum/sdk') 7 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 8 | require('dotenv').config() 9 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC', 'L1RPC']) 10 | 11 | /** 12 | * Set up: instantiate L1 wallet connected to provider 13 | */ 14 | 15 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 16 | 17 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 18 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 19 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 20 | 21 | module.exports = async txnHash => { 22 | await arbLog('Outbox Execution') 23 | /** 24 | / * We start with a txn hash; we assume this is transaction that triggered an L2 to L1 Message on L2 (i.e., ArbSys.sendTxToL1) 25 | */ 26 | 27 | if (!txnHash) 28 | throw new Error( 29 | 'Provide a transaction hash of an L2 transaction that sends an L2 to L1 message' 30 | ) 31 | if (!txnHash.startsWith('0x') || txnHash.trim().length != 66) 32 | throw new Error(`Hmm, ${txnHash} doesn't look like a txn hash...`) 33 | 34 | /** 35 | * First, let's find the Arbitrum txn from the txn hash provided 36 | */ 37 | const receipt = await l2Provider.getTransactionReceipt(txnHash) 38 | const l2Receipt = new L2TransactionReceipt(receipt) 39 | 40 | /** 41 | * Note that in principle, a single transaction could trigger any number of outgoing messages; the common case will be there's only one. 42 | * For the sake of this script, we assume there's only one / just grad the first one. 43 | */ 44 | const messages = await l2Receipt.getL2ToL1Messages(l1Wallet, l2Provider) 45 | const l2ToL1Msg = messages[0] 46 | 47 | /** 48 | * Check if already executed 49 | */ 50 | if ((await l2ToL1Msg.status(l2Provider)) == L2ToL1MessageStatus.EXECUTED) { 51 | console.log(`Message already executed! Nothing else to do here`) 52 | process.exit(1) 53 | } 54 | 55 | /** 56 | * before we try to execute out message, we need to make sure the l2 block it's included in is confirmed! (It can only be confirmed after the dispute period; Arbitrum is an optimistic rollup after-all) 57 | * waitUntilReadyToExecute() waits until the item outbox entry exists 58 | */ 59 | const timeToWaitMs = 1000 * 60 60 | console.log( 61 | "Waiting for the outbox entry to be created. This only happens when the L2 block is confirmed on L1, ~1 week after it's creation." 62 | ) 63 | await l2ToL1Msg.waitUntilReadyToExecute(l2Provider, timeToWaitMs) 64 | console.log('Outbox entry exists! Trying to execute now') 65 | 66 | /** 67 | * Now fetch the proof info we'll need in order to execute, or check execution 68 | */ 69 | const proofInfo = await l2ToL1Msg.getOutboxProof(l2Provider) 70 | 71 | /** 72 | * Now that its confirmed and not executed, we can use the Merkle proof data to execute our message in its outbox entry. 73 | */ 74 | const res = await l2ToL1Msg.execute(proofInfo) 75 | const rec = await res.wait() 76 | console.log('Done! Your transaction is executed', rec) 77 | } 78 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | 13 | # https://developer.offchainlabs.com/docs/useful_addresses 14 | INBOX_ADDR=0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | cache/ 3 | build/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/README.md: -------------------------------------------------------------------------------- 1 | # Redeem Failed Retryable Tickets Tutorial 2 | 3 | 4 | 可重试票据是 Arbitrum 协议的规范方法,用于将通用消息从以太坊传递到 Arbitrum。可重试票证是由 L1 编码和传递的 L2 消息;如果提供了gas,将立即执行。如果没有提供gas或执行失败,它将被放置在L2重试缓冲区中,任何用户都可以在规定时间内(大约一周)重新尝试执行。 5 | 您可以使用 `exec-createFailedRetryable` 脚本来创建一个会失败的可重试票证,然后使用 `redeem-failed-retryable` 向您展示如何赎回(重新执行)位于 L2 重试缓冲区中的票证。 6 | 7 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 8 | 9 | ### 运行 Demo: 10 | 11 | 创建一个会失败的可重试票据: 12 | 13 | ``` 14 | yarn run createFailedRetryable 15 | ``` 16 | 17 | 赎回刚刚失败后的票据: 18 | 19 | ``` 20 | yarn redeemFailedRetryable --txhash 0xmytxnhash 21 | ``` 22 | 23 | - _0xmytxnhash_ 是触发 L1->L2 消息的L1交易哈希。 24 | 25 | ### 配置环境变量 26 | 27 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 28 | 29 | ```bash 30 | cp .env-sample .env 31 | ``` 32 | 33 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 34 | 35 | ### More info 36 | 37 | 有关可重试票证的更多信息,请参阅我们的 [关于跨层消息传递的开发人员文档](https://developer.offchainlabs.com/docs/l1_l2_messages)。 38 | 39 |

40 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | contract Greeter { 5 | string greeting; 6 | 7 | constructor(string memory _greeting) public { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public virtual { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/contracts/arbitrum/GreeterL2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | import "arbos-precompiles/arbos/builtin/ArbSys.sol"; 5 | import "arb-bridge-eth/contracts/libraries/AddressAliasHelper.sol"; 6 | import "../Greeter.sol"; 7 | 8 | contract GreeterL2 is Greeter { 9 | ArbSys constant arbsys = ArbSys(100); 10 | address public l1Target; 11 | 12 | event L2ToL1TxCreated(uint256 indexed withdrawalId); 13 | 14 | constructor(string memory _greeting, address _l1Target) public Greeter(_greeting) { 15 | l1Target = _l1Target; 16 | } 17 | 18 | function updateL1Target(address _l1Target) public { 19 | l1Target = _l1Target; 20 | } 21 | 22 | function setGreetingInL1(string memory _greeting) public returns (uint256) { 23 | bytes memory data = abi.encodeWithSelector(Greeter.setGreeting.selector, _greeting); 24 | 25 | uint256 withdrawalId = arbsys.sendTxToL1(l1Target, data); 26 | 27 | emit L2ToL1TxCreated(withdrawalId); 28 | return withdrawalId; 29 | } 30 | 31 | /// @notice only l1Target can update greeting 32 | function setGreeting(string memory _greeting) public override { 33 | // To check that message came from L1, we check that the sender is the L1 contract's L2 alias. 34 | require( 35 | msg.sender == AddressAliasHelper.applyL1ToL2Alias(l1Target), 36 | "Greeting only updateable by L1" 37 | ); 38 | Greeter.setGreeting(_greeting); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/contracts/ethereum/GreeterL1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.6.11; 3 | 4 | import "arb-bridge-eth/contracts/bridge/Inbox.sol"; 5 | import "arb-bridge-eth/contracts/bridge/Outbox.sol"; 6 | import "../Greeter.sol"; 7 | 8 | contract GreeterL1 is Greeter { 9 | address public l2Target; 10 | IInbox public inbox; 11 | 12 | event RetryableTicketCreated(uint256 indexed ticketId); 13 | 14 | constructor( 15 | string memory _greeting, 16 | address _l2Target, 17 | address _inbox 18 | ) public Greeter(_greeting) { 19 | l2Target = _l2Target; 20 | inbox = IInbox(_inbox); 21 | } 22 | 23 | function updateL2Target(address _l2Target) public { 24 | l2Target = _l2Target; 25 | } 26 | 27 | function setGreetingInL2( 28 | string memory _greeting, 29 | uint256 maxSubmissionCost, 30 | uint256 maxGas, 31 | uint256 gasPriceBid 32 | ) public payable returns (uint256) { 33 | bytes memory data = abi.encodeWithSelector(Greeter.setGreeting.selector, _greeting); 34 | uint256 ticketID = inbox.createRetryableTicket{ value: msg.value }( 35 | l2Target, 36 | 0, 37 | maxSubmissionCost, 38 | msg.sender, 39 | msg.sender, 40 | maxGas, 41 | gasPriceBid, 42 | data 43 | ); 44 | 45 | emit RetryableTicketCreated(ticketID); 46 | return ticketID; 47 | } 48 | 49 | /// @notice only l2Target can update greeting 50 | function setGreeting(string memory _greeting) public override { 51 | IBridge bridge = inbox.bridge(); 52 | // this prevents reentrancies on L2 to L1 txs 53 | require(msg.sender == address(bridge), "NOT_BRIDGE"); 54 | IOutbox outbox = IOutbox(bridge.activeOutbox()); 55 | address l2Sender = outbox.l2ToL1Sender(); 56 | require(l2Sender == l2Target, "Greeting only updateable by L2"); 57 | 58 | Greeter.setGreeting(_greeting); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const main = require('./scripts/exec-redeem.js') 3 | const { hardhatConfig } = require('arb-shared-dependencies') 4 | 5 | const { task } = require('hardhat/config') 6 | 7 | task('redeem-failed-retryable') 8 | .addParam('txhash', 'Hash of the L1 txn that created the retryable ticket') 9 | 10 | .setAction(async args => { 11 | await main(args.txhash) 12 | }) 13 | 14 | /** 15 | * @type import('hardhat/config').HardhatUserConfig 16 | */ 17 | module.exports = hardhatConfig 18 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redeem-failed-retryable", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "createFailedRetryable": "hardhat run scripts/exec-createFailedRetryable.js", 8 | "redeemFailedRetryable": "hardhat redeem-failed-retryable" 9 | }, 10 | "devDependencies": { 11 | "@nomiclabs/hardhat-ethers": "^2.0.2", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.2.0" 14 | }, 15 | "dependencies": { 16 | "@arbitrum/sdk": "^2.0.0", 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/scripts/exec-createFailedRetryable.js: -------------------------------------------------------------------------------- 1 | const { providers, Wallet } = require('ethers') 2 | const hre = require('hardhat') 3 | const ethers = require('ethers') 4 | const { hexDataLength } = require('@ethersproject/bytes') 5 | const { 6 | L1ToL2MessageGasEstimator, 7 | } = require('@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator') 8 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 9 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC', 'L1RPC', 'INBOX_ADDR']) 10 | 11 | /** 12 | * Set up: instantiate L1 / L2 wallets connected to providers 13 | */ 14 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 15 | 16 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 17 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 18 | 19 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 20 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 21 | 22 | const main = async () => { 23 | await arbLog('Creating Failed Retryables for Cross-chain Greeter') 24 | 25 | /** 26 | * We deploy L1 Greeter to L1, L2 greeter to L2, each with a different "greeting" message. 27 | * After deploying, save set each contract's counterparty's address to its state so that they can later talk to each other. 28 | */ 29 | const L1Greeter = await ( 30 | await hre.ethers.getContractFactory('GreeterL1') 31 | ).connect(l1Wallet) // 32 | console.log('Deploying L1 Greeter 👋') 33 | const l1Greeter = await L1Greeter.deploy( 34 | 'Hello world in L1', 35 | ethers.constants.AddressZero, // temp l2 addr 36 | process.env.INBOX_ADDR 37 | ) 38 | await l1Greeter.deployed() 39 | console.log(`deployed to ${l1Greeter.address}`) 40 | const L2Greeter = await ( 41 | await hre.ethers.getContractFactory('GreeterL2') 42 | ).connect(l2Wallet) 43 | 44 | console.log('Deploying L2 Greeter 👋👋') 45 | 46 | const l2Greeter = await L2Greeter.deploy( 47 | 'Hello world in L2', 48 | ethers.constants.AddressZero // temp l1 addr 49 | ) 50 | await l2Greeter.deployed() 51 | console.log(`deployed to ${l2Greeter.address}`) 52 | 53 | const updateL1Tx = await l1Greeter.updateL2Target(l2Greeter.address) 54 | await updateL1Tx.wait() 55 | 56 | const updateL2Tx = await l2Greeter.updateL1Target(l1Greeter.address) 57 | await updateL2Tx.wait() 58 | console.log('Counterpart contract addresses set in both greeters 👍') 59 | 60 | /** 61 | * Let's log the L2 greeting string 62 | */ 63 | const currentL2Greeting = await l2Greeter.greet() 64 | console.log(`Current L2 greeting: "${currentL2Greeting}"`) 65 | 66 | console.log('Updating greeting from L1 to L2:') 67 | 68 | /** 69 | * Here we have a new greeting message that we want to set as the L2 greeting; we'll be setting it by sending it as a message from layer 1!!! 70 | */ 71 | const newGreeting = 'Greeting from far, far away' 72 | 73 | /** 74 | * To send an L1-to-L2 message (aka a "retryable ticket"), we need to send ether from L1 to pay for the txn costs on L2. 75 | * There are two costs we need to account for: base submission cost and cost of L2 execution. We'll start with base submission cost. 76 | */ 77 | 78 | /** 79 | * Base submission cost is a special cost for creating a retryable ticket; querying the cost requires us to know how many bytes of calldata out retryable ticket will require, so let's figure that out. 80 | * We'll get the bytes for our greeting data, then add 4 for the 4-byte function signature. 81 | */ 82 | 83 | const newGreetingBytes = ethers.utils.defaultAbiCoder.encode( 84 | ['string'], 85 | [newGreeting] 86 | ) 87 | const newGreetingBytesLength = hexDataLength(newGreetingBytes) + 4 // 4 bytes func identifier 88 | 89 | /** 90 | * Now we can query the submission price using a helper method; the first value returned tells us the best cost of our transaction; that's what we'll be using. 91 | * The second value (nextUpdateTimestamp) tells us when the base cost will next update (base cost changes over time with chain congestion; the value updates every 24 hours). We won't actually use it here, but generally it's useful info to have. 92 | */ 93 | const l1ToL2MessageGasEstimate = new L1ToL2MessageGasEstimator(l2Provider) 94 | 95 | const _submissionPriceWei = 96 | await l1ToL2MessageGasEstimate.estimateSubmissionFee( 97 | l1Provider, 98 | await l1Provider.getGasPrice(), 99 | newGreetingBytesLength 100 | ) 101 | 102 | console.log( 103 | `Current retryable base submission price: ${_submissionPriceWei.toString()}` 104 | ) 105 | 106 | /** 107 | * ...Okay, but on the off chance we end up underpaying, our retryable ticket simply fails. 108 | * This is highly unlikely, but just to be safe, let's increase the amount we'll be paying (the difference between the actual cost and the amount we pay gets refunded to our address on L2 anyway) 109 | * In nitro, submission fee will be charged in L1 based on L1 basefee, revert on L1 side upon insufficient fee. 110 | */ 111 | const submissionPriceWei = _submissionPriceWei.mul(5) 112 | /** 113 | * Now we'll figure out the gas we need to send for L2 execution; this requires the L2 gas price and gas limit for our L2 transaction 114 | */ 115 | 116 | /** 117 | * For the L2 gas price, we simply query it from the L2 provider, as we would when using L1 118 | */ 119 | const gasPriceBid = await l2Provider.getGasPrice() 120 | console.log(`L2 gas price: ${gasPriceBid.toString()}`) 121 | 122 | /** 123 | * With these three values, we can calculate the total callvalue we'll need our L1 transaction to send to L2 124 | * To create a failed retryable ticket, we hardcode a very low number for gas limit (e.g., 10) which leads to a failed auto redeem on L2 125 | */ 126 | const maxGas = 10 127 | const callValue = submissionPriceWei.add(gasPriceBid.mul(maxGas)) 128 | 129 | console.log( 130 | `Sending greeting to L2 with ${callValue.toString()} callValue for L2 fees:` 131 | ) 132 | 133 | const setGreetingTx = await l1Greeter.setGreetingInL2( 134 | newGreeting, 135 | submissionPriceWei, 136 | maxGas, 137 | gasPriceBid, 138 | { 139 | value: callValue, 140 | } 141 | ) 142 | const setGreetingRec = await setGreetingTx.wait() 143 | 144 | console.log( 145 | `Greeting txn confirmed on L1 but will fail to auto redeem on L2! Here's the L1 tx hash: ${setGreetingRec.transactionHash}` 146 | ) 147 | } 148 | main() 149 | .then(() => process.exit(0)) 150 | .catch(error => { 151 | console.error(error) 152 | process.exit(1) 153 | }) 154 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/redeem-failed-retryable/scripts/exec-redeem.js: -------------------------------------------------------------------------------- 1 | const { providers, Wallet } = require('ethers') 2 | const { 3 | L1TransactionReceipt, 4 | L1ToL2MessageStatus, 5 | getRawArbTransactionReceipt, 6 | } = require('@arbitrum/sdk') 7 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 8 | require('dotenv').config() 9 | requireEnvVariables(['DEVNET_PRIVKEY', 'L2RPC', 'L1RPC']) 10 | 11 | /** 12 | * Set up: instantiate the L2 wallet connected to provider 13 | */ 14 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 15 | 16 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 17 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 18 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 19 | 20 | module.exports = async txnHash => { 21 | await arbLog('Redeem A Failed Retryable Ticket') 22 | 23 | /** 24 | * We start with an L1 txn hash; this is transaction that triggers craeting a retryable ticket 25 | */ 26 | if (!txnHash) 27 | throw new Error('Provide a transaction hash of an L1 transaction') 28 | if (!txnHash.startsWith('0x') || txnHash.trim().length != 66) 29 | throw new Error(`Hmm, ${txnHash} doesn't look like a txn hash...`) 30 | 31 | /** 32 | * First, we check if our L1 to L2 message is redeemed on L2 33 | */ 34 | const receipt = await l1Provider.getTransactionReceipt(txnHash) 35 | const l1Receipt = new L1TransactionReceipt(receipt) 36 | 37 | const message = await l1Receipt.getL1ToL2Message(l2Wallet) 38 | const status = (await message.waitForStatus()).status 39 | 40 | if (status === L1ToL2MessageStatus.REDEEMED) { 41 | console.log(`L2 retryable txn is already executed 🥳 ${message.l2TxHash}`) 42 | return 43 | } else { 44 | console.log( 45 | `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` 46 | ) 47 | } 48 | 49 | console.log(`Redeeming the ticket now 🥳`) 50 | /** 51 | * We use the redeem() method from Arbitrum SDK to manually redeem our ticket 52 | */ 53 | await message.redeem() 54 | console.log( 55 | 'The L2 side of your transaction is now execeuted 🥳 :', 56 | message.l2TxHash 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your private key" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC; i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/README.md: -------------------------------------------------------------------------------- 1 | # token-deposit Tutorial 2 | 3 | `token-deposit` 演示了代币如何从以太坊(第 1 层)移动到 Arbitrum(第 2 层)链,该方法使用的是标准Token网关方法,该方法使用于标准逻辑实现的ERC20代币。 4 | 5 | 有关它如何在后台工作的信息,请参阅我们的 [token bridging docs](https://developer.offchainlabs.com/docs/bridging_assets)。 6 | #### **标准ERC20 Deposit** 7 | 8 | 我们将通过我们的 Arbitrum 代币桥以将 ERC20 代币存入 Arbitrum 链。 9 | 10 | 在这里,我们部署一个 [demo token](./contracts/DappToken.sol) 并触发存款;默认情况下,存款将通过标准 ERC20 网关路由,在初始存款时,标准 arb ERC20 合约将自动部署到 L2。 11 | 12 | 13 | 我们使用 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 库来启动和验证存款。 14 | 15 | 可以查看 [./exec.js](./scripts/exec.js) 里的注释以了解更多。 16 | 17 | ### 配置环境变量 18 | 19 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 20 | 21 | ```bash 22 | cp .env-sample .env 23 | ``` 24 | 25 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 26 | 27 | ### 运行Demo: 28 | 29 | ``` 30 | yarn run token-deposit 31 | ``` 32 | 33 |

34 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/contracts/DappToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.22; 2 | 3 | contract DappToken { 4 | string public name = "Dapp Token"; //Token name 5 | string public symbol = "DAPP"; //Toekn symbol 6 | string public standard = "Dapp Token v1.0"; 7 | uint256 public totalSupply; 8 | uint8 public decimals = 2; 9 | 10 | mapping(address => uint256) public balanceOf; //stores the balance of addresses. Every time a token is baught/sold/transferred, this mapping is responsible for knowing who has each token 11 | mapping(address => mapping(address => uint256)) public allowance; 12 | //It keeps track of all of my appovals that I have approved for transferring tokens 13 | //account A: 1)approved account B to spend 10 tokens 14 | // 2)approved account C to spend 20 tokens 15 | // ... 16 | 17 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 18 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); //I (the owner) approved account spender to spend _value of my Dapp tokens o 19 | 20 | //constructor: Set the value and the number of the tokens that we have 21 | //everytime the smart contract is deployed 22 | //The following cobstructor accepts the total supply as function arguments 23 | constructor(uint256 _initialSupply) public { 24 | balanceOf[msg.sender] = _initialSupply; //allocates the initial supply 25 | totalSupply = _initialSupply; //the number of tokens that will exist and store it in a variable 26 | } 27 | 28 | //transfer function allows users to trasfer tokens and MUST fire the "transfer event" 29 | //The function SHOULD throw exception if the _from address does not have enough tokens to spend 30 | function transfer(address _to, uint256 _value) public returns (bool success) { 31 | require(balanceOf[msg.sender] >= _value); 32 | balanceOf[msg.sender] -= _value; 33 | balanceOf[_to] += _value; 34 | emit Transfer(msg.sender, _to, _value); 35 | return true; 36 | } 37 | 38 | //approve function: allows sb to approve another account to spend tokens on tehir behalf. You're basically approving the exchange to spend x tokens on your behalf 39 | //MUST trigger the approval event 40 | function approve(address _spender, uint256 _value) public returns (bool success) { 41 | //spender: the exchange 42 | 43 | allowance[msg.sender][_spender] = _value; //set the allowance: the amount which _spender is still allowed to withdraw from _owner 44 | //Allowance: If I approve the exchange to spend x Dapp tokens on my belhaf, that x gets stored in allowance 45 | 46 | emit Approval(msg.sender, _spender, _value); //approve event 47 | return true; 48 | } 49 | 50 | //Once you approve that transfer, the transferFrom function allows the exchange to do that 51 | //acts like a transfer but on behalf on another account 52 | //account A approved account B to spend _value 53 | //so account B calls transferFrom function to transfer _value tokens from account A to some account (_to) 54 | function transferFrom( 55 | address _from, 56 | address _to, 57 | uint256 _value 58 | ) public returns (bool success) { 59 | require(_value <= balanceOf[_from]); 60 | require(_value <= allowance[_from][msg.sender]); 61 | 62 | balanceOf[_from] -= _value; //update the balance of _from and _to 63 | balanceOf[_to] += _value; //update the balance of _from and _to 64 | 65 | allowance[_from][msg.sender] -= _value; //update the allowance; ammount of tokens the exchange (msg.sender) can spend on behalf of _from account 66 | 67 | emit Transfer(_from, _to, _value); //transfer event: everytime there is a transfer happened we should emit an event 68 | 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-deposit", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "token-deposit": "hardhat run scripts/exec.js" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "chai": "^4.3.4", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.2.0" 14 | }, 15 | "dependencies": { 16 | "@arbitrum/sdk": "^2.0.0", 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-deposit/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { BigNumber, providers, Wallet } = require('ethers') 3 | const { 4 | getL2Network, 5 | Erc20Bridger, 6 | L1ToL2MessageStatus, 7 | } = require('@arbitrum/sdk') 8 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 9 | const { expect } = require('chai') 10 | require('dotenv').config() 11 | requireEnvVariables(['DEVNET_PRIVKEY', 'L1RPC', 'L2RPC']) 12 | 13 | /** 14 | * Set up: instantiate L1 / L2 wallets connected to providers 15 | */ 16 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 17 | 18 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 19 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 20 | 21 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 22 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 23 | 24 | /** 25 | * Set the amount of token to be transferred to L2 26 | */ 27 | const tokenDepositAmount = BigNumber.from(50) 28 | 29 | const main = async () => { 30 | await arbLog('Deposit token using Arbitrum SDK') 31 | 32 | /** 33 | * Use l2Network to create an Arbitrum SDK Erc20Bridger instance 34 | * We'll use Erc20Bridger for its convenience methods around transferring token to L2 35 | */ 36 | const l2Network = await getL2Network(l2Provider) 37 | const erc20Bridge = new Erc20Bridger(l2Network) 38 | 39 | /** 40 | * For the purpose of our tests, here we deploy an standard ERC20 token (DappToken) to L1 41 | * It sends its deployer (us) the initial supply of 1000000000000000 42 | */ 43 | console.log('Deploying the test DappToken to L1:') 44 | const L1DappToken = await ( 45 | await ethers.getContractFactory('DappToken') 46 | ).connect(l1Wallet) 47 | const l1DappToken = await L1DappToken.deploy(1000000000000000) 48 | await l1DappToken.deployed() 49 | console.log(`DappToken is deployed to L1 at ${l1DappToken.address}`) 50 | console.log('Approving:') 51 | const erc20Address = l1DappToken.address 52 | 53 | /** 54 | * We get the address of L1 Gateway for our DappToken, which later helps us to get the initial token balance of Bridge (before deposit) 55 | */ 56 | const expectedL1GatewayAddress = await erc20Bridge.getL1GatewayAddress( 57 | erc20Address, 58 | l1Provider 59 | ) 60 | const initialBridgeTokenBalance = await l1DappToken.balanceOf( 61 | expectedL1GatewayAddress 62 | ) 63 | 64 | /** 65 | * The Standard Gateway contract will ultimately be making the token transfer call; thus, that's the contract we need to approve. 66 | * erc20Bridge.approveToken handles this approval 67 | * Arguments required are: 68 | * (1) l1Signer: The L1 address transferring token to L2 69 | * (2) erc20L1Address: L1 address of the ERC20 token to be depositted to L2 70 | */ 71 | const approveTx = await erc20Bridge.approveToken({ 72 | l1Signer: l1Wallet, 73 | erc20L1Address: erc20Address, 74 | }) 75 | 76 | const approveRec = await approveTx.wait() 77 | console.log( 78 | `You successfully allowed the Arbitrum Bridge to spend DappToken ${approveRec.transactionHash}` 79 | ) 80 | 81 | /** 82 | * Deposit DappToken to L2 using erc20Bridge. This will escrow funds in the Gateway contract on L1, and send a message to mint tokens on L2. 83 | * The erc20Bridge.deposit method handles computing the necessary fees for automatic-execution of retryable tickets — maxSubmission cost & l2 gas price * gas — and will automatically forward the fees to L2 as callvalue 84 | * Also note that since this is the first DappToken deposit onto L2, a standard Arb ERC20 contract will automatically be deployed. 85 | * Arguments required are: 86 | * (1) amount: The amount of tokens to be transferred to L2 87 | * (2) erc20L1Address: L1 address of the ERC20 token to be depositted to L2 88 | * (2) l1Signer: The L1 address transferring token to L2 89 | * (3) l2Provider: An l2 provider 90 | */ 91 | const depositTx = await erc20Bridge.deposit({ 92 | amount: tokenDepositAmount, 93 | erc20L1Address: erc20Address, 94 | l1Signer: l1Wallet, 95 | l2Provider: l2Provider, 96 | }) 97 | 98 | /** 99 | * Now we wait for L1 and L2 side of transactions to be confirmed 100 | */ 101 | const depositRec = await depositTx.wait() 102 | const l2Result = await depositRec.waitForL2(l2Provider) 103 | 104 | /** 105 | * The `complete` boolean tells us if the l1 to l2 message was successul 106 | */ 107 | l2Result.complete 108 | ? console.log( 109 | `L2 message successful: status: ${L1ToL2MessageStatus[l2Result.status]}` 110 | ) 111 | : console.log( 112 | `L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}` 113 | ) 114 | 115 | /** 116 | * Get the Bridge token balance 117 | */ 118 | const finalBridgeTokenBalance = await l1DappToken.balanceOf( 119 | expectedL1GatewayAddress 120 | ) 121 | 122 | /** 123 | * Check if Bridge balance has been updated correctly 124 | */ 125 | expect( 126 | initialBridgeTokenBalance 127 | .add(tokenDepositAmount) 128 | .eq(finalBridgeTokenBalance), 129 | 'bridge balance not updated after L1 token deposit txn' 130 | ).to.be.true 131 | 132 | /** 133 | * Check if our l2Wallet DappToken balance has been updated correctly 134 | * To do so, we use erc20Bridge to get the l2Token address and contract 135 | */ 136 | const l2TokenAddress = await erc20Bridge.getL2ERC20Address( 137 | erc20Address, 138 | l1Provider 139 | ) 140 | const l2Token = erc20Bridge.getL2TokenContract(l2Provider, l2TokenAddress) 141 | 142 | const testWalletL2Balance = ( 143 | await l2Token.functions.balanceOf(l2Wallet.address) 144 | )[0] 145 | expect( 146 | testWalletL2Balance.eq(tokenDepositAmount), 147 | 'l2 wallet not updated after deposit' 148 | ).to.be.true 149 | } 150 | 151 | main() 152 | .then(() => process.exit(0)) 153 | .catch(error => { 154 | console.error(error) 155 | process.exit(1) 156 | }) 157 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/.env-sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC; i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/README.md: -------------------------------------------------------------------------------- 1 | # token-withdraw Tutorial 2 | 3 | `token-withdraw` 展示了如何将 ERC20 代币从 Arbitrum(第 2 层)转移到以太坊(第 1 层)。 4 | 5 | 请注意,此 repo 涵盖了启动令牌提取;有关(稍后)从发件箱释放资金的演示,请参阅 [outbox-execute](../outbox-execute/README.md) 6 | ### 它是如何工作的? 7 | 8 | 要从 Arbitrum 提取代币,网关合约会发送一条消息,该合约会在 L2 上烧掉代币,并向 L1 发送一条消息,一旦争议期结束,该代币就可以从托管中释放。有关详细信息,请参阅 [传出消息文档](https://developer.offchainlabs.com/docs/l1_l2_messages#l2-to-l1-messages-lifecycle)。 9 | 10 | --- 11 | 12 | #### **Standard ERC20 Withdrawal** 13 | 14 | 在这个Demo中,我们部署了一个新的代币,然后将一些该代币存入 L2。然后,我们使用这些新代币触发提款到 L1。 15 | 16 | 我们使用 [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 库进行token桥交互。 17 | 18 | See [./exec.js](./scripts/exec.js) for inline explanation. 19 | 20 | ### 配置环境变量 21 | 22 | 在 `.env-sample` 中设置参数. 并将其复制为 `.env` 文件: 23 | 24 | ```bash 25 | cp .env-sample .env 26 | ``` 27 | 28 | (你仍然需要设置一些参数, 比如 `DEVNET_PRIVKEY`) 29 | 30 | ### 运行Demo 31 | 32 | ``` 33 | yarn withdraw-token 34 | ``` 35 | 36 |

37 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/contracts/DappToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.22; 2 | 3 | contract DappToken { 4 | string public name = "Dapp Token"; //Token name 5 | string public symbol = "DAPP"; //Toekn symbol 6 | string public standard = "Dapp Token v1.0"; 7 | uint256 public totalSupply; 8 | uint8 public decimals = 2; 9 | 10 | mapping(address => uint256) public balanceOf; //stores the balance of addresses. Every time a token is baught/sold/transferred, this mapping is responsible for knowing who has each token 11 | mapping(address => mapping(address => uint256)) public allowance; 12 | //It keeps track of all of my appovals that I have approved for transferring tokens 13 | //account A: 1)approved account B to spend 10 tokens 14 | // 2)approved account C to spend 20 tokens 15 | // ... 16 | 17 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 18 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); //I (the owner) approved account spender to spend _value of my Dapp tokens o 19 | 20 | //constructor: Set the value and the number of the tokens that we have 21 | //everytime the smart contract is deployed 22 | //The following cobstructor accepts the total supply as function arguments 23 | constructor(uint256 _initialSupply) public { 24 | balanceOf[msg.sender] = _initialSupply; //allocates the initial supply 25 | totalSupply = _initialSupply; //the number of tokens that will exist and store it in a variable 26 | } 27 | 28 | //transfer function allows users to trasfer tokens and MUST fire the "transfer event" 29 | //The function SHOULD throw exception if the _from address does not have enough tokens to spend 30 | function transfer(address _to, uint256 _value) public returns (bool success) { 31 | require(balanceOf[msg.sender] >= _value); 32 | balanceOf[msg.sender] -= _value; 33 | balanceOf[_to] += _value; 34 | emit Transfer(msg.sender, _to, _value); 35 | return true; 36 | } 37 | 38 | //approve function: allows sb to approve another account to spend tokens on tehir behalf. You're basically approving the exchange to spend x tokens on your behalf 39 | //MUST trigger the approval event 40 | function approve(address _spender, uint256 _value) public returns (bool success) { 41 | //spender: the exchange 42 | 43 | allowance[msg.sender][_spender] = _value; //set the allowance: the amount which _spender is still allowed to withdraw from _owner 44 | //Allowance: If I approve the exchange to spend x Dapp tokens on my belhaf, that x gets stored in allowance 45 | 46 | emit Approval(msg.sender, _spender, _value); //approve event 47 | return true; 48 | } 49 | 50 | //Once you approve that transfer, the transferFrom function allows the exchange to do that 51 | //acts like a transfer but on behalf on another account 52 | //account A approved account B to spend _value 53 | //so account B calls transferFrom function to transfer _value tokens from account A to some account (_to) 54 | function transferFrom( 55 | address _from, 56 | address _to, 57 | uint256 _value 58 | ) public returns (bool success) { 59 | require(_value <= balanceOf[_from]); 60 | require(_value <= allowance[_from][msg.sender]); 61 | 62 | balanceOf[_from] -= _value; //update the balance of _from and _to 63 | balanceOf[_to] += _value; //update the balance of _from and _to 64 | 65 | allowance[_from][msg.sender] -= _value; //update the allowance; ammount of tokens the exchange (msg.sender) can spend on behalf of _from account 66 | 67 | emit Transfer(_from, _to, _value); //transfer event: everytime there is a transfer happened we should emit an event 68 | 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers') 2 | const { hardhatConfig } = require('arb-shared-dependencies') 3 | 4 | module.exports = hardhatConfig 5 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-withdraw", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile", 7 | "withdraw-token": "hardhat run scripts/exec.js" 8 | }, 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "chai": "^4.3.4", 12 | "ethers": "^5.1.2", 13 | "hardhat": "^2.2.0" 14 | }, 15 | "dependencies": { 16 | "@arbitrum/sdk": "^2.0.0", 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arbitrum-tutorials/packages/token-withdraw/scripts/exec.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { BigNumber, providers, Wallet } = require('ethers') 3 | const { expect } = require('chai') 4 | const { 5 | getL2Network, 6 | Erc20Bridger, 7 | L1ToL2MessageStatus, 8 | } = require('@arbitrum/sdk') 9 | const { arbLog, requireEnvVariables } = require('arb-shared-dependencies') 10 | require('dotenv').config() 11 | requireEnvVariables(['DEVNET_PRIVKEY', 'L1RPC', 'L2RPC']) 12 | 13 | /** 14 | * Set up: instantiate L1 / L2 wallets connected to providers 15 | */ 16 | const walletPrivateKey = process.env.DEVNET_PRIVKEY 17 | 18 | const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) 19 | const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) 20 | 21 | const l1Wallet = new Wallet(walletPrivateKey, l1Provider) 22 | const l2Wallet = new Wallet(walletPrivateKey, l2Provider) 23 | 24 | /** 25 | * Set the amount of token to be transferred to L2 and then withdrawn 26 | */ 27 | const tokenDepositAmount = BigNumber.from(50) 28 | const tokenWithdrawAmount = BigNumber.from(20) 29 | 30 | const main = async () => { 31 | await arbLog('Withdraw token using Arbitrum SDK') 32 | 33 | /** 34 | * Use l2Network to create an Arbitrum SDK Erc20Bridger instance 35 | * We'll use Erc20Bridger for its convenience methods around transferring token to L2 and back to L1 36 | */ 37 | const l2Network = await getL2Network(l2Provider) 38 | const erc20Bridge = new Erc20Bridger(l2Network) 39 | 40 | /** 41 | * Setup: we'll deploy a token contract, then approve and transfer onto L2 so we have some tokens to withdraw to L1 42 | * (For play-by-play details on the deposit flow, see token_deposit repo) 43 | */ 44 | console.log('Deploying the test DappToken to L1:') 45 | const L1DappToken = await ( 46 | await ethers.getContractFactory('DappToken') 47 | ).connect(l1Wallet) 48 | const l1DappToken = await L1DappToken.deploy(1000000000000000) 49 | await l1DappToken.deployed() 50 | console.log(`DappToken is deployed to L1 at ${l1DappToken.address}`) 51 | console.log('Approving:') 52 | 53 | const erc20Address = l1DappToken.address 54 | 55 | /** 56 | * The Standard Gateway contract will ultimately be making the token transfer call; thus, that's the contract we need to approve. 57 | * erc20Bridge.approveToken handles this approval 58 | * Arguments required are: 59 | * (1) l1Signer: The L1 address transferring token to L2 60 | * (2) erc20L1Address: L1 address of the ERC20 token to be deposited to L2 61 | */ 62 | const approveTx = await erc20Bridge.approveToken({ 63 | l1Signer: l1Wallet, 64 | erc20L1Address: erc20Address, 65 | }) 66 | 67 | const approveRec = await approveTx.wait() 68 | console.log( 69 | `You successfully allowed the Arbitrum Bridge to spend DappToken ${approveRec.transactionHash}` 70 | ) 71 | console.log('Transferring DappToken to L2:') 72 | 73 | /** 74 | * Deposit DappToken to L2 using Erc20Bridger. This will escrow funds in the Gateway contract on L1, and send a message to mint tokens on L2. 75 | * The erc20Bridge.deposit method handles computing the necessary fees for automatic-execution of retryable tickets — maxSubmission cost & l2 gas price * gas — and will automatically forward the fees to L2 as callvalue 76 | * Also note that since this is the first DappToken deposit onto L2, a standard Arb ERC20 contract will automatically be deployed. 77 | * Arguments required are: 78 | * (1) amount: The amount of tokens to be transferred to L2 79 | * (2) erc20L1Address: L1 address of the ERC20 token to be deposited to L2 80 | * (2) l1Signer: The L1 address transferring token to L2 81 | * (3) l2Provider: An l2 provider 82 | */ 83 | const depositTx = await erc20Bridge.deposit({ 84 | amount: tokenDepositAmount, 85 | erc20L1Address: erc20Address, 86 | l1Signer: l1Wallet, 87 | l2Provider: l2Provider, 88 | }) 89 | 90 | /** 91 | * Now we wait for L1 and L2 side of transactions to be confirmed 92 | */ 93 | console.log( 94 | `Deposit initiated: waiting for L2 retryable (takes < 10 minutes; current time: ${new Date().toTimeString()}) ` 95 | ) 96 | const depositRec = await depositTx.wait() 97 | const l2Result = await depositRec.waitForL2(l2Provider) 98 | console.log(`Setup complete`) 99 | /** 100 | * The `complete` boolean tells us if the l1 to l2 message was successul 101 | */ 102 | l2Result.complete 103 | ? console.log( 104 | `L2 message successful: status: ${L1ToL2MessageStatus[l2Result.status]}` 105 | ) 106 | : console.log( 107 | `L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}` 108 | ) 109 | 110 | console.log('Withdrawing:') 111 | 112 | /** 113 | * ... Okay, Now we begin withdrawing DappToken from L2. To withdraw, we'll use Erc20Bridger helper method withdraw 114 | * withdraw will call our L2 Gateway Router to initiate a withdrawal via the Standard ERC20 gateway 115 | * This transaction is constructed and paid for like any other L2 transaction (it just happens to (ultimately) make a call to ArbSys.sendTxToL1) 116 | * Arguments required are: 117 | * (1) amount: The amount of tokens to be transferred to L1 118 | * (2) erc20L1Address: L1 address of the ERC20 token 119 | * (3) l2Signer: The L2 address transferring token to L1 120 | */ 121 | 122 | const withdrawTx = await erc20Bridge.withdraw({ 123 | amount: tokenWithdrawAmount, 124 | erc20l1Address: erc20Address, 125 | l2Signer: l2Wallet, 126 | }) 127 | 128 | const withdrawRec = await withdrawTx.wait() 129 | console.log(`Token withdrawal initiated! 🥳 ${withdrawRec.transactionHash}`) 130 | /** 131 | * And with that, our withdrawal is initiated! No additional time-sensitive actions are required. 132 | * Any time after the transaction's assertion is confirmed, funds can be transferred out of the bridge via the outbox contract 133 | * We'll check our l2Wallet DappToken balance here: 134 | */ 135 | 136 | const l2Token = erc20Bridge.getL2TokenContract( 137 | l2Provider, 138 | await erc20Bridge.getL2ERC20Address(erc20Address, l1Provider) 139 | ) 140 | 141 | const l2WalletBalance = ( 142 | await l2Token.functions.balanceOf(await l2Wallet.getAddress()) 143 | )[0] 144 | 145 | expect( 146 | l2WalletBalance.add(tokenWithdrawAmount).eq(tokenDepositAmount), 147 | 'token withdraw balance not deducted' 148 | ).to.be.true 149 | 150 | console.log( 151 | `To to claim funds (after dispute period), see outbox-execute repo ✌️` 152 | ) 153 | } 154 | 155 | main() 156 | .then(() => process.exit(0)) 157 | .catch(error => { 158 | console.error(error) 159 | process.exit(1) 160 | }) 161 | -------------------------------------------------------------------------------- /tutorials/README.md: -------------------------------------------------------------------------------- 1 | # 第三方rpc设置教程 2 | 官方rpc由于使用率过多,因此可能会在每日峰值或使用量激增时出现相应过慢的情况。因此,我们推荐大家使用第三方rpc作为备用方案或直接使用第三方rpc。 3 | 以下为我们为大家准备的rpc添加教程: 4 | 5 | - [alchemy](https://mirror.xyz/0xc5a564961b5075Bde102a1f0C8606183ea9bBAa8/OaHlRX-WSyEkh8cOxifPqd2YyZ-9jzyZWd7uw25A5QQ) 6 | - [datahub](tutorials/datahub.md) 7 | - [getBlock](tutorials/getBlock.md) 8 | - [moralis](tutorials/moralis.md) 9 | - [quicknode](tutorials/quicknode.md) 10 | - [infura](https://mirror.xyz/petrarca.eth/ndw9whWAEEKOVJhs7WMx7TF8RRNuEQIlehfFjx6X2eQ) 11 | - [block vision](https://mirror.xyz/petrarca.eth/Nhml9oWbg4yiB44dPqkzHcMIRomPweU3EgnVw3-NxvY) 12 | 13 |

14 | 15 |

-------------------------------------------------------------------------------- /tutorials/img/datahub/datahub_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/datahub/datahub_1.png -------------------------------------------------------------------------------- /tutorials/img/datahub/datahub_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/datahub/datahub_2.png -------------------------------------------------------------------------------- /tutorials/img/datahub/datahub_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/datahub/datahub_3.png -------------------------------------------------------------------------------- /tutorials/img/datahub/datahub_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/datahub/datahub_4.png -------------------------------------------------------------------------------- /tutorials/img/datahub/datahub_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/datahub/datahub_5.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_1.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_2.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_3.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_4.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_5.png -------------------------------------------------------------------------------- /tutorials/img/getBlock/getblock_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/getBlock/getblock_6.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_1.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_2.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_3.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_4.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_5.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_6.png -------------------------------------------------------------------------------- /tutorials/img/moralis/moralis_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/moralis/moralis_7.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_1.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_2.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_3.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_4.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_5.png -------------------------------------------------------------------------------- /tutorials/img/quicknode/quicknode_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbitrum-cn/Arbitrum-Library/c9cec688099a044eeff83336d72dad93e2434868/tutorials/img/quicknode/quicknode_6.png -------------------------------------------------------------------------------- /tutorials/tutorials/datahub.md: -------------------------------------------------------------------------------- 1 | 进入[datahub官网](https://datahub.figment.io/auth/login)注册账号 2 | 3 | 创建新应用: 4 |

5 | 6 |

7 | 输入名称,环境选项区分应用程序的状态,任意选一个不影响功能,选择arbitrum主网,点击创建应用: 8 |

9 | 10 |

11 | 点击进入arbitrum: 12 |

13 | 14 |

15 | 免费版每月有300万数据请求,速度较低,每秒只能支持10次调用请求。 16 | 17 | 点击复制RPC 18 | 进入小狐狸钱包,点击设置--网络--Arbitrum主网--粘贴RPC链接,点击保存,即可成功创建arbitrum-RPC。 19 | 20 | 亲测可用,不卡,也不算丝滑,跟平时使用马蹄链差不多。 21 |

22 | 23 |

24 | 付费版本简介,如果需要更大的宽带,与更丝滑的体验,50美金一月。 25 |

26 | 27 |

-------------------------------------------------------------------------------- /tutorials/tutorials/getBlock.md: -------------------------------------------------------------------------------- 1 | 进入 [getblock官网](https://getblock.io/cn/)点击免费使用。 2 |

3 | 4 |

5 | 注册登录: 6 |

7 | 8 |

9 | 点击生产,点击生成终点: 10 |

11 | 12 |

13 | 选择arbitrum主网,点击创建: 14 |

15 | 16 |

17 | 这是共享节点,每秒支持30次调用请求,会比datahub网络好一点,付费增加计算数据,没有期限,直到用完为止。 18 | 19 | 专用节点建造,需要专用服务器,才能独享高性高速度,日常交互不必考虑。 20 |

21 | 22 |

23 | 点击复制RPC 24 | 进入小狐狸钱包,点击设置--网络--Arbitrum主网--粘贴RPC链接,点击保存,即可成功创建你的arbitrum-RPC。亲测可用。 25 | 26 | 觉得好用,可以考虑付费。免费节点可以备用,当没有那么多人使用的时候,切换arb官方节点才是最优选择。 27 | 28 | 付费续用价格如下: 29 |

30 | 31 |

-------------------------------------------------------------------------------- /tutorials/tutorials/moralis.md: -------------------------------------------------------------------------------- 1 | 1.进入[moralis官网](https://moralis.io/),点击免费开始。 2 |

3 | 4 |

5 | 2.注册账号 6 |

7 | 8 |

9 | 3.填写调查表 10 |

11 | 12 |

13 | 4.勾选订阅,接受条款,进行人机验证,创建你的账户 14 |

15 | 16 |

17 | 5.验证邮箱账户完成,自动生成RPC节点,点击快速节点(管理你自己的节点),点击arbitrum网络。 18 |

19 | 20 |

21 | 6.看到自己的RPC(HTTP地址),官方提醒手动添加到钱包(由于metamask发生错误,自动添加失效) 22 | 23 | 注意事项:鼠标选中链接,右键复制,不要点击最右边的复制图标。 24 |

25 | 26 |

27 | 7.专业计划40美元/月,服务器永不停机,增加RPC速率性能等等... 28 |

29 | 30 |

-------------------------------------------------------------------------------- /tutorials/tutorials/quicknode.md: -------------------------------------------------------------------------------- 1 | 2 | 1.进入(快速节点)注册账号,邮箱点击验证账户,此网站支持免费七天试用,无免费版。 3 | 点击[quicknode官网](https://www.quicknode.com/)。 4 |

5 | 6 |

7 | 2.邮箱验证成功,自动跳转此页面,点击创建你的RPC节点。 8 |

9 | 10 |

11 | 3.选择Arbitrum,选择主网。 12 |

13 | 14 |

15 | 4.不需要组件,点击继续下一步。 16 |

17 | 18 |

19 | 5.选择9美元/月,输入信用卡账号,S点击创建。 20 |

21 | 22 |

23 | 6.复制RPC链接(http开头) 24 | 进入小狐狸钱包,点击设置--网络--Arbitrum主网--粘贴RPC链接,点击保存,即可成功创建arbitrum-RPC。Quicknode,免费试用七天将自动扣费,实测服务器网速比其他免费版较快。 25 | 注意事项:如果把自己的RPC节点发在群里,让多人使用,超过规定量后,会产生大量扣费。 26 |

27 | 28 |

--------------------------------------------------------------------------------