├── .gitignore ├── README.md ├── package.json ├── packages ├── arb-bridge-eth │ ├── abis │ │ ├── Bridge.json │ │ ├── IERC20.json │ │ ├── IRollupCore.json │ │ ├── Inbox.json │ │ ├── L1ArbitrumGateway.json │ │ ├── L1GatewayRouter.json │ │ └── Outbox.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ ├── bridge.ts │ │ ├── bridgeUtils.ts │ │ ├── interface │ │ │ ├── IRollupCore.ts │ │ │ ├── build.json │ │ │ └── codegen.json │ │ ├── outbox.ts │ │ ├── rlp.ts │ │ ├── rollup.ts │ │ ├── tokenBridge.ts │ │ └── utils.ts │ ├── subgraph.template.yaml │ └── tests │ │ └── inbox │ │ └── inbox.test.ts ├── arbitrum-blocks │ ├── abis │ │ └── IERC20.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ └── mapping.ts │ └── subgraph.template.yaml ├── arbitrum-precompiles │ ├── abis │ │ ├── ArbRetryableTx.json │ │ └── ArbSys.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ └── mapping.ts │ └── subgraph.template.yaml ├── arbitrum-retryables │ ├── abis │ │ └── ArbRetryableTx.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ └── mapping.ts │ └── subgraph.template.yaml ├── cctp │ ├── abis │ │ └── USDCMessageTransmitter.json │ ├── l1Subgraph.template.yaml │ ├── l2Subgraph.template.yaml │ ├── package.json │ ├── schema.graphql │ ├── src │ │ └── usdc-message-transmitter.ts │ ├── tests │ │ ├── .latest.json │ │ ├── usdc-message-transmitter-utils.ts │ │ └── usdc-message-transmitter.test.ts │ └── tsconfig.json ├── layer1-token-gateway │ ├── abis │ │ └── L1GatewayRouter.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ └── mapping.ts │ └── subgraph.template.yaml ├── layer2-token-gateway │ ├── abis │ │ ├── ClassicArbRetryableTx.json │ │ ├── ClassicArbSys.json │ │ ├── L2ArbitrumGateway.json │ │ ├── L2GatewayRouter.json │ │ ├── NitroArbRetryableTx.json │ │ └── NitroArbSys.json │ ├── metadata.template.ts │ ├── package.json │ ├── schema.graphql │ ├── src │ │ ├── abi.ts │ │ ├── mapping.ts │ │ └── util.ts │ ├── subgraph.template.yaml │ └── tests │ │ ├── l1ToL2 │ │ └── l1ToL2.test.ts │ │ └── weth │ │ └── weth.test.ts ├── orbit-deployments │ ├── abis │ │ ├── ProxyAdmin.json │ │ ├── Rollup.json │ │ ├── RollupCreator.json │ │ └── SequencerInbox.json │ ├── package.json │ ├── schema.graphql │ ├── src │ │ ├── rollup-creator.ts │ │ └── sequencer-inbox.ts │ ├── subgraph.yaml │ └── tsconfig.json ├── subgraph-common │ ├── config │ │ ├── goerli.json │ │ ├── mainnet.json │ │ ├── nitro-mainnet.json │ │ ├── nova-mainnet.json │ │ ├── nova.json │ │ ├── rinkeby.json │ │ └── sepolia.json │ ├── index.ts │ ├── package.json │ └── src │ │ └── helpers.ts └── teleporter │ ├── abis │ └── IL1Teleporter.json │ ├── package.json │ ├── schema.graphql │ ├── src │ └── mapping.ts │ └── subgraph.template.yaml └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | subgraph.preprocessed.yaml 4 | subgraph.yaml 5 | !packages/orbit-deployments/subgraph.yaml 6 | .bin 7 | generated 8 | packages/layer2-token-gateway/metadata.ts 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrum subgraphs 2 | 3 | This project contains subgraphs to track Arbitrum token bridge and cross chain messaging. 4 | 5 | ## arb-bridge-eth 6 | 7 | Tracks ETH and token deposits from L1 to L2, both classic and nitro versions. It also tracks retryable tickets. Entity `sender` field is always original address of the deposit/retryable creator, not aliased or unaliased version of address. 8 | 9 | `Nitro` 10 | Query endpoint: https://api.thegraph.com/subgraphs/name/gvladika/arb-bridge-eth-nitro 11 | Playground: https://thegraph.com/hosted-service/subgraph/gvladika/arb-bridge-eth-nitro 12 | 13 | `Nova` 14 | Query endpoint: https://api.thegraph.com/subgraphs/name/gvladika/arb-bridge-eth-nova 15 | Playground: https://thegraph.com/hosted-service/subgraph/gvladika/arb-bridge-eth-nova 16 | 17 | `Goerli Nitro` 18 | Query endpoint: https://api.thegraph.com/subgraphs/name/gvladika/arb-bridge-eth-goerli 19 | Playground: https://thegraph.com/hosted-service/subgraph/gvladika/arb-bridge-eth-goerli 20 | 21 | `Sepolia` 22 | Query endpoint: https://api.thegraph.com/subgraphs/name/fionnachan/arb-bridge-eth-sepolia 23 | Playground: https://thegraph.com/hosted-service/subgraph/fionnachan/arb-bridge-eth-sepolia 24 | 25 | #### Query example - get first 3 deposits (including Eth deposits and token deposits) 26 | 27 | ``` 28 | deposits(first: 3) { 29 | type 30 | sender 31 | receiver 32 | ethValue 33 | l1Token { 34 | id 35 | name 36 | symbol 37 | } 38 | sequenceNumber 39 | tokenAmount 40 | isClassic 41 | timestamp 42 | transactionHash 43 | blockCreatedAt 44 | } 45 | ``` 46 | 47 | Result: 48 | 49 | ``` 50 | { 51 | "data": { 52 | "deposits": [ 53 | { 54 | "type": "EthDeposit", 55 | "sender": "0x3808d4d05ae4d21d20bbd0143e8f41e09b3ce309", 56 | "receiver": "0x3808d4d05ae4d21d20bbd0143e8f41e09b3ce309", 57 | "ethValue": "1000000000000000", 58 | "l1Token": null, 59 | "sequenceNumber": "9277", 60 | "tokenAmount": null, 61 | "isClassic": false, 62 | "timestamp": "1662276473", 63 | "transactionHash": "0x00000947484b2117be463199be889da38caf1a231d29e6128d81d12e6a0a1cee", 64 | "blockCreatedAt": "15470347" 65 | }, 66 | { 67 | "type": "TokenDeposit", 68 | "sender": "0xa2e06c19ee14255889f0ec0ca37f6d0778d06754", 69 | "receiver": "0xa2e06c19ee14255889f0ec0ca37f6d0778d06754", 70 | "ethValue": "0", 71 | "l1Token": { 72 | "id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 73 | "name": "Wrapped Ether", 74 | "symbol": "WETH" 75 | }, 76 | "sequenceNumber": "20556", 77 | "tokenAmount": "600000000000000000", 78 | "isClassic": false, 79 | "timestamp": "1662696576", 80 | "transactionHash": "0x00000a61331187be51ab9ae792d74f601a5a21fb112f5b9ac5bccb23d4d5aaba", 81 | "blockCreatedAt": "15500657" 82 | }, 83 | { 84 | "type": "EthDeposit", 85 | "sender": "0x49211e8da72a9549541c7914f85837b294abf992", 86 | "receiver": "0x49211e8da72a9549541c7914f85837b294abf992", 87 | "ethValue": "24000000000000000", 88 | "l1Token": null, 89 | "sequenceNumber": "390496", 90 | "tokenAmount": null, 91 | "isClassic": true, 92 | "timestamp": "1648568434", 93 | "transactionHash": "0x00000a813d47f2c478dcc3298d5361cb3aed817648f25cace6d0c1a59d2b8309", 94 | "blockCreatedAt": "14481946" 95 | } 96 | ] 97 | } 98 | } 99 | ``` 100 | 101 | ## arbitrum-precompiles 102 | 103 | // TODO 104 | 105 | ## arb-retryables 106 | 107 | Tracks Nitro retryables on L2 side. It includes ticket status for every retryable, execution params as well as retryable submission params (through parsing tx calldata). 108 | 109 | `Nitro` 110 | Query endpoint: https://api.thegraph.com/subgraphs/name/gvladika/arbitrum-retryables 111 | Playground: https://thegraph.com/hosted-service/subgraph/gvladika/arbitrum-retryables 112 | 113 | `Nova` 114 | To be added (Nova is not supported by Graph hosted service atm) 115 | 116 | `Goerli Nitro` 117 | Query endpoint: https://api.thegraph.com/subgraphs/name/gvladika/arbitrum-retryables-goerli 118 | Playground: https://thegraph.com/hosted-service/subgraph/gvladika/arbitrum-retryables-goerli 119 | 120 | `Sepolia` 121 | Query endpoint: https://api.thegraph.com/subgraphs/name/fionnachan/arbitrum-retryables-sepolia 122 | Playground: https://thegraph.com/hosted-service/subgraph/fionnachan/arbitrum-retryables-sepolia 123 | 124 | #### Query example - get last 10 retryables tickets which failed to redeem 125 | 126 | ``` 127 | { 128 | retryables(orderBy: createdAtTimestamp, orderDirection: desc, first:3, where: {status: "RedeemFailed"}) { 129 | id 130 | retryTxHash 131 | } 132 | } 133 | ``` 134 | 135 | Result: 136 | 137 | ``` 138 | { 139 | "data": { 140 | "retryables": [ 141 | { 142 | "id": "0x97016fdc9ec92b6899ea5b685bbfd21435e5843b19a3d375fce79e08d4c914d1", 143 | "retryTxHash": "0xafdeba836ec4ae1b9234c943bc434069e686773ea8977fefcd16114f17e4251b" 144 | }, 145 | { 146 | "id": "0x9d6c19f5fb9055d64b37b9a58c56806bbc5a973d0cdca18cfbfa7b609d4812f1", 147 | "retryTxHash": "0xdaebe273820c3ab7197682c532f0e112ad2a97680ea3ed9b78710e0adf8b3ea5" 148 | }, 149 | { 150 | "id": "0xc3803fd826e9b834a31c8ef52426292bb3d56bc23695d82657dfe74215e8d94a", 151 | "retryTxHash": "0x2150aaa2a6715b3a0611651f4162191de1aab6c93095956e43f38b553c161343" 152 | } 153 | ] 154 | } 155 | } 156 | ``` 157 | 158 | #### Query example - get total stats 159 | 160 | ``` 161 | { 162 | totalRetryableStats(id: "NitroStats") { 163 | id 164 | totalCreated 165 | autoRedeemed 166 | successfullyRedeemed 167 | failedToRedeem 168 | canceled 169 | } 170 | } 171 | ``` 172 | 173 | Result: 174 | 175 | ``` 176 | { 177 | "data": { 178 | "totalRetryableStats": { 179 | "id": "NitroStats", 180 | "totalCreated": "83845", 181 | "autoRedeemed": "83710", 182 | "successfullyRedeemed": "83822", 183 | "failedToRedeem": "21", 184 | "canceled": "0" 185 | } 186 | } 187 | } 188 | ``` 189 | 190 | #### Query example - get latest 2 retryables including all the fields 191 | 192 | ``` 193 | { 194 | retryables(orderBy: createdAtTimestamp, orderDirection: desc, first:2) { 195 | id 196 | status 197 | retryTxHash 198 | timeoutTimestamp 199 | createdAtTimestamp 200 | createdAtBlockNumber 201 | createdAtTxHash 202 | redeemedAtTimestamp 203 | isAutoRedeemed 204 | sequenceNum 205 | donatedGas 206 | gasDonor 207 | maxRefund 208 | submissionFeeRefund 209 | requestId 210 | l1BaseFee 211 | deposit 212 | callvalue 213 | gasFeeCap 214 | gasLimit 215 | maxSubmissionFee 216 | feeRefundAddress 217 | beneficiary 218 | retryTo 219 | retryData 220 | } 221 | } 222 | ``` 223 | 224 | Result: 225 | 226 | ``` 227 | { 228 | "data": { 229 | "retryables": [ 230 | { 231 | "id": "0xdef8e761a76ee0c466536c5fa7c4d3a13df912cbacb879cfe2d6d9e17a4a8884", 232 | "status": "Redeemed", 233 | "retryTxHash": "0x1dd3570cdb33b954fd0c94c4a37fe1cec2c1a73946df6bf3c8539dfb2153b0b2", 234 | "timeoutTimestamp": "1677077599", 235 | "createdAtTimestamp": "1676472799", 236 | "createdAtBlockNumber": "61174964", 237 | "createdAtTxHash": "0xdef8e761a76ee0c466536c5fa7c4d3a13df912cbacb879cfe2d6d9e17a4a8884", 238 | "redeemedAtTimestamp": "1676472799", 239 | "isAutoRedeemed": true, 240 | "sequenceNum": "0", 241 | "donatedGas": "284615", 242 | "gasDonor": "0xfd81392229b6252cf761459d370c239be3afc54f", 243 | "maxRefund": "1776913467122000", 244 | "submissionFeeRefund": "84592690642000", 245 | "requestId": "0x000000000000000000000000000000000000000000000000000000000007813b", 246 | "l1BaseFee": "42296345321", 247 | "deposit": "1800859226480000", 248 | "callvalue": "0", 249 | "gasFeeCap": "130000000", 250 | "gasLimit": "284615", 251 | "maxSubmissionFee": "100000000000000", 252 | "feeRefundAddress": "0xfd81392229b6252cf761459d370c239be3afc54f", 253 | "beneficiary": "0xfd81392229b6252cf761459d370c239be3afc54f", 254 | "retryTo": "0xfd81392229b6252cf761459d370c239be3afc54f", 255 | "retryData": "0x4ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002054655cabed180b3705ececcde806c0879b5b60ebb4705b013cb80bf3d3960736" 256 | }, 257 | ... 258 | ] 259 | } 260 | } 261 | ``` 262 | 263 | ## layer1-token-gateway 264 | 265 | // TODO 266 | 267 | ## layer2-token-gateway 268 | 269 | // TODO 270 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrum-subgraphs", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "private": true, 6 | "workspaces": { 7 | "packages": [ "packages/*" ], 8 | "nohoist": [ "**" ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/abis/IERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/abis/Outbox.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "destAddr", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "l2Sender", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "uint256", 20 | "name": "outboxEntryIndex", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "transactionIndex", 27 | "type": "uint256" 28 | } 29 | ], 30 | "name": "OutBoxTransactionExecuted", 31 | "type": "event" 32 | }, 33 | { 34 | "anonymous": false, 35 | "inputs": [ 36 | { 37 | "indexed": true, 38 | "internalType": "uint256", 39 | "name": "batchNum", 40 | "type": "uint256" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "outboxEntryIndex", 46 | "type": "uint256" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "bytes32", 51 | "name": "outputRoot", 52 | "type": "bytes32" 53 | }, 54 | { 55 | "indexed": false, 56 | "internalType": "uint256", 57 | "name": "numInBatch", 58 | "type": "uint256" 59 | } 60 | ], 61 | "name": "OutboxEntryCreated", 62 | "type": "event" 63 | }, 64 | { 65 | "inputs": [], 66 | "name": "OUTBOX_VERSION", 67 | "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], 68 | "stateMutability": "view", 69 | "type": "function" 70 | }, 71 | { 72 | "inputs": [], 73 | "name": "bridge", 74 | "outputs": [{ "internalType": "contract IBridge", "name": "", "type": "address" }], 75 | "stateMutability": "view", 76 | "type": "function" 77 | }, 78 | { 79 | "inputs": [ 80 | { "internalType": "address", "name": "l2Sender", "type": "address" }, 81 | { "internalType": "address", "name": "destAddr", "type": "address" }, 82 | { "internalType": "uint256", "name": "l2Block", "type": "uint256" }, 83 | { "internalType": "uint256", "name": "l1Block", "type": "uint256" }, 84 | { "internalType": "uint256", "name": "l2Timestamp", "type": "uint256" }, 85 | { "internalType": "uint256", "name": "amount", "type": "uint256" }, 86 | { "internalType": "bytes", "name": "calldataForL1", "type": "bytes" } 87 | ], 88 | "name": "calculateItemHash", 89 | "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], 90 | "stateMutability": "pure", 91 | "type": "function" 92 | }, 93 | { 94 | "inputs": [ 95 | { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" }, 96 | { "internalType": "uint256", "name": "path", "type": "uint256" }, 97 | { "internalType": "bytes32", "name": "item", "type": "bytes32" } 98 | ], 99 | "name": "calculateMerkleRoot", 100 | "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], 101 | "stateMutability": "pure", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { "internalType": "uint256", "name": "batchNum", "type": "uint256" }, 107 | { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" }, 108 | { "internalType": "uint256", "name": "index", "type": "uint256" }, 109 | { "internalType": "address", "name": "l2Sender", "type": "address" }, 110 | { "internalType": "address", "name": "destAddr", "type": "address" }, 111 | { "internalType": "uint256", "name": "l2Block", "type": "uint256" }, 112 | { "internalType": "uint256", "name": "l1Block", "type": "uint256" }, 113 | { "internalType": "uint256", "name": "l2Timestamp", "type": "uint256" }, 114 | { "internalType": "uint256", "name": "amount", "type": "uint256" }, 115 | { "internalType": "bytes", "name": "calldataForL1", "type": "bytes" } 116 | ], 117 | "name": "executeTransaction", 118 | "outputs": [], 119 | "stateMutability": "nonpayable", 120 | "type": "function" 121 | }, 122 | { 123 | "inputs": [ 124 | { "internalType": "address", "name": "_rollup", "type": "address" }, 125 | { 126 | "internalType": "contract IBridge", 127 | "name": "_bridge", 128 | "type": "address" 129 | } 130 | ], 131 | "name": "initialize", 132 | "outputs": [], 133 | "stateMutability": "nonpayable", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "isMaster", 139 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 140 | "stateMutability": "view", 141 | "type": "function" 142 | }, 143 | { 144 | "inputs": [], 145 | "name": "l2ToL1BatchNum", 146 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 147 | "stateMutability": "view", 148 | "type": "function" 149 | }, 150 | { 151 | "inputs": [], 152 | "name": "l2ToL1Block", 153 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 154 | "stateMutability": "view", 155 | "type": "function" 156 | }, 157 | { 158 | "inputs": [], 159 | "name": "l2ToL1EthBlock", 160 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 161 | "stateMutability": "view", 162 | "type": "function" 163 | }, 164 | { 165 | "inputs": [], 166 | "name": "l2ToL1OutputId", 167 | "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], 168 | "stateMutability": "view", 169 | "type": "function" 170 | }, 171 | { 172 | "inputs": [], 173 | "name": "l2ToL1Sender", 174 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 175 | "stateMutability": "view", 176 | "type": "function" 177 | }, 178 | { 179 | "inputs": [], 180 | "name": "l2ToL1Timestamp", 181 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 182 | "stateMutability": "view", 183 | "type": "function" 184 | }, 185 | { 186 | "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 187 | "name": "outboxEntries", 188 | "outputs": [{ "internalType": "bytes32", "name": "root", "type": "bytes32" }], 189 | "stateMutability": "view", 190 | "type": "function" 191 | }, 192 | { 193 | "inputs": [{ "internalType": "uint256", "name": "batchNum", "type": "uint256" }], 194 | "name": "outboxEntryExists", 195 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 196 | "stateMutability": "view", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [ 201 | { "internalType": "bytes", "name": "sendsData", "type": "bytes" }, 202 | { 203 | "internalType": "uint256[]", 204 | "name": "sendLengths", 205 | "type": "uint256[]" 206 | } 207 | ], 208 | "name": "processOutgoingMessages", 209 | "outputs": [], 210 | "stateMutability": "nonpayable", 211 | "type": "function" 212 | }, 213 | { 214 | "inputs": [], 215 | "name": "rollup", 216 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 217 | "stateMutability": "view", 218 | "type": "function" 219 | } 220 | ] 221 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arb-bridge-eth", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:codegen && yarn prepare:nitro-mainnet && graph codegen", 7 | "build": "yarn prepare:build && yarn prepare:nitro-mainnet && graph build", 8 | "postinstall": "yarn codegen", 9 | "create-local": "graph create --node http://localhost:8020/ arbitrum/arb-bridge-eth-nitro", 10 | "remove-local": "graph remove --node http://localhost:8020/ arbitrum/arb-bridge-eth-nitro", 11 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 arbitrum/arb-bridge-eth-nitro", 12 | 13 | "prepare:codegen": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/src/interface/codegen.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.preprocessed.yaml", 14 | "prepare:build": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/src/interface/build.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.preprocessed.yaml", 15 | 16 | "prepare:nitro-mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nitro-mainnet.json $(pwd)/subgraph.preprocessed.yaml | tail -n +2 > subgraph.yaml", 17 | "prepare:nova": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nova-mainnet.json $(pwd)/subgraph.preprocessed.yaml | tail -n +2 > subgraph.yaml", 18 | "prepare:nitro-goerli": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/subgraph.preprocessed.yaml | tail -n +2 > subgraph.yaml", 19 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/subgraph.preprocessed.yaml | tail -n +2 > subgraph.yaml", 20 | 21 | "deploy:nitro-mainnet": "yarn prepare:build && yarn prepare:nitro-mainnet && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arb-bridge-eth-nitro", 22 | "deploy:nova": "yarn prepare:build && yarn prepare:nova && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arb-bridge-eth-nova", 23 | "deploy:nitro-goerli": "yarn prepare:build && yarn prepare:nitro-goerli && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arb-bridge-eth-goerli", 24 | "deploy:sepolia": "yarn prepare:build && yarn prepare:sepolia && graph deploy --node https://api.thegraph.com/deploy/ fionnachan/arb-bridge-eth-sepolia", 25 | 26 | "test": "rm -rf tests/.bin && yarn codegen && yarn build && graph test --version 0.5.0" 27 | }, 28 | "dependencies": { 29 | "@arbitrum/subgraph-common": "0.0.1", 30 | "@graphprotocol/graph-ts": "^0.32.0", 31 | "matchstick-as": "^0.5.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/schema.graphql: -------------------------------------------------------------------------------- 1 | type OutboxEntry @entity { 2 | "batchNum hexstring which is the unique identifier and primary key of the entity" 3 | id: ID! 4 | outboxEntryIndex: BigInt! # uint256 5 | outputRoot: Bytes! # bytes32 6 | numInBatch: BigInt! # uint256 7 | spentOutput: [OutboxOutput!] @derivedFrom(field: "outboxEntry") 8 | } 9 | 10 | type OutboxOutput @entity { 11 | "this ID is not the same as the outputId used on chain" 12 | id: ID! 13 | destAddr: Bytes! # address 14 | l2Sender: Bytes! # address 15 | path: BigInt! # uint256 16 | outboxEntry: OutboxEntry! 17 | "if OutboxOutput exists, it means it has been spent" 18 | spent: Boolean! 19 | } 20 | 21 | enum MessageKind { 22 | Retryable 23 | NotSupported 24 | EthDeposit 25 | } 26 | 27 | type RawMessage @entity { 28 | "the inbox sequence number of the current message" 29 | id: ID! 30 | "Nitro message type" 31 | kind: MessageKind! 32 | "message sender" 33 | sender: Bytes! # address 34 | } 35 | 36 | type ClassicRawMessage @entity { 37 | "the inbox sequence number of the current message" 38 | id: ID! 39 | "classic message type" 40 | kind: MessageKind! 41 | "message sender" 42 | sender: Bytes! # address 43 | } 44 | 45 | type Retryable @entity(immutable: true) { 46 | "the inbox sequence number of the current message" 47 | id: ID! 48 | "account that created retryable ticket, original address" 49 | sender: Bytes! # address 50 | "is the current retryable a deposit of eth" 51 | isEthDeposit: Boolean! 52 | "value sent with message" 53 | value: BigInt! 54 | "indicates the L2 address destination" 55 | destAddr: Bytes! # bytes32 56 | "ticket id of retryable" 57 | retryableTicketID: Bytes! # bytes32 58 | "calldata used in L2 call" 59 | l2Calldata: Bytes! 60 | "L2 call value" 61 | l2Callvalue: BigInt! 62 | "L1 block timestamp" 63 | timestamp: BigInt! 64 | "L1 tx retryable is created at" 65 | transactionHash: Bytes! 66 | "L1 block retryable is created at" 67 | blockCreatedAt: BigInt! 68 | } 69 | 70 | type ClassicRetryable @entity(immutable: true) { 71 | "the inbox sequence number of the current message" 72 | id: ID! 73 | "account that created retryable ticket, original address" 74 | sender: Bytes! # address 75 | "is the current retryable a deposit of eth" 76 | isEthDeposit: Boolean! 77 | "value sent with message" 78 | value: BigInt! 79 | "indicates the L2 address destination" 80 | destAddr: Bytes! # bytes32 81 | "ticket id of retryable" 82 | retryableTicketID: Bytes! # bytes32 83 | "calldata used in L2 call" 84 | l2Calldata: Bytes! 85 | "L2 call value" 86 | l2Callvalue: BigInt! 87 | "L1 block timestamp" 88 | timestamp: BigInt! 89 | "L1 tx retryable is created at" 90 | transactionHash: Bytes! 91 | "L1 block retryable is created at" 92 | blockCreatedAt: BigInt! 93 | } 94 | 95 | enum DepositType { 96 | EthDeposit 97 | TokenDeposit 98 | } 99 | 100 | type Deposit @entity(immutable: true) { 101 | "txHash-txIndex" 102 | id: ID! 103 | "EthDeposit or TokenDeposit" 104 | type: DepositType! 105 | "account that deposited funds on L1, original address" 106 | sender: Bytes! # address 107 | "receiver L2 account" 108 | receiver: Bytes! # address 109 | "Eth value being deposited in wei" 110 | ethValue: BigInt! # uint256 111 | "token being deposited" 112 | l1Token: Token 113 | "unique id for retryable transaction" 114 | sequenceNumber: BigInt # uint256 115 | "retryable's calculated ID on L2" 116 | l2TicketId: String 117 | "token amount being deposited on L1" 118 | tokenAmount: BigInt # uint256 119 | "true -> classic; false -> nitro" 120 | isClassic: Boolean! 121 | "L1 block timestamp" 122 | timestamp: BigInt! 123 | "L1 deposit tx" 124 | transactionHash: String! 125 | "L1 block in which deposit is made" 126 | blockCreatedAt: BigInt! 127 | } 128 | 129 | type Gateway @entity { 130 | "gateway address" 131 | id: ID! 132 | "L1 block number in which this gateway was first registered in router" 133 | registeredAtBlock: BigInt! 134 | "tokens this gateway supports" 135 | tokens: [Token!] @derivedFrom(field: "gateway") 136 | } 137 | 138 | type Token @entity { 139 | "L1 token address" 140 | id: ID! 141 | "token name" 142 | name: String 143 | "token symbol" 144 | symbol: String 145 | "number of decimals" 146 | decimals: Int 147 | "gateway that supports this token" 148 | gateway: Gateway 149 | "L1 block number in which this token was first registered in router/gateway" 150 | registeredAtBlock: BigInt! 151 | } 152 | 153 | enum NodeStatus { 154 | Pending 155 | Confirmed 156 | Rejected 157 | } 158 | 159 | type Node @entity { 160 | "node number in hex" 161 | id: ID! 162 | 163 | "hash of information contained in this node" 164 | nodeHash: Bytes! 165 | 166 | "hash of parent node" 167 | parentHash: Bytes! 168 | 169 | "block the node was created" 170 | blockCreatedAt: BigInt! 171 | 172 | "count of sequencer inbox when assertion is created" 173 | inboxMaxCount: BigInt! 174 | 175 | "Total number of AVM sends emitted from the beginning of time after this node is confirmed" 176 | afterSendCount: BigInt! 177 | 178 | "timestamp the node was created" 179 | timestampCreated: BigInt! 180 | 181 | "A node is created as pending, this is the timestamp in which it was either confirmed or rejected" 182 | timestampStatusUpdate: BigInt 183 | 184 | "confirmation status of node in the rollup" 185 | status: NodeStatus! 186 | } 187 | 188 | type DefaultGatewayUpdated @entity { 189 | id: ID! 190 | newDefaultGateway: Bytes! # address 191 | timestamp: BigInt! 192 | transactionHash: Bytes! 193 | blockNumber: BigInt! 194 | } 195 | 196 | type GatewaySet @entity { 197 | id: ID! 198 | l1Token: Bytes! # address 199 | gateway: Bytes! # address 200 | timestamp: BigInt! 201 | transactionHash: Bytes! 202 | blockNumber: BigInt! 203 | } 204 | 205 | type TransferRouted @entity { 206 | id: ID! 207 | token: Bytes! # address 208 | _userFrom: Bytes! # address 209 | _userTo: Bytes! # address 210 | gateway: Bytes! # address 211 | timestamp: BigInt! 212 | transactionHash: Bytes! 213 | blockNumber: BigInt! 214 | } 215 | 216 | type TxToL2 @entity { 217 | id: ID! 218 | _from: Bytes! # address 219 | _to: Bytes! # address 220 | _seqNum: BigInt! # uint256 221 | _data: Bytes! # bytes 222 | } 223 | 224 | type WhitelistSourceUpdated @entity { 225 | id: ID! 226 | newSource: Bytes! # address 227 | timestamp: BigInt! 228 | transactionHash: Bytes! 229 | blockNumber: BigInt! 230 | } 231 | 232 | type Inbox @entity { 233 | id: ID! 234 | } 235 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/bridgeUtils.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt } from "@graphprotocol/graph-ts"; 2 | import { Gateway, Inbox, Token } from "../generated/schema"; 3 | import { L1ArbitrumGateway } from "../generated/templates"; 4 | import { IERC20 } from "../generated/Bridge/IERC20"; 5 | 6 | export function getOrCreateGateway(gatewayAddress: Address, blockNumber: BigInt): Gateway { 7 | let gateway = Gateway.load(gatewayAddress.toHexString()); 8 | if (gateway != null) { 9 | return gateway; 10 | } 11 | 12 | // create new gateway entity 13 | gateway = new Gateway(gatewayAddress.toHexString()); 14 | gateway.registeredAtBlock = blockNumber; 15 | gateway.save(); 16 | 17 | // start indexing new gateway 18 | L1ArbitrumGateway.create(gatewayAddress); 19 | 20 | return gateway; 21 | } 22 | 23 | export function getOrCreateToken(tokenAddress: Address, blockNumber: BigInt): Token { 24 | let token = Token.load(tokenAddress.toHexString()); 25 | if (token != null) { 26 | return token; 27 | } 28 | 29 | // create new Token entity 30 | token = new Token(tokenAddress.toHexString()); 31 | 32 | // fetch data doing one time contract calls 33 | let tokenInstance = IERC20.bind(tokenAddress); 34 | let tryName = tokenInstance.try_name(); 35 | if (!tryName.reverted) { 36 | token.name = tryName.value; 37 | } 38 | let trySymbol = tokenInstance.try_symbol(); 39 | if (!trySymbol.reverted) { 40 | token.symbol = trySymbol.value; 41 | } 42 | let tryDecimals = tokenInstance.try_decimals(); 43 | if (!tryDecimals.reverted) { 44 | token.decimals = tryDecimals.value; 45 | } 46 | 47 | token.registeredAtBlock = blockNumber; 48 | token.save(); 49 | 50 | return token; 51 | } 52 | 53 | export function getOrCreateInbox(inboxAddress: Address): Inbox { 54 | let inbox = Inbox.load(inboxAddress.toHexString()); 55 | if (inbox != null) { 56 | return inbox as Inbox; 57 | } 58 | inbox = new Inbox(inboxAddress.toHexString()); 59 | inbox.save(); 60 | return inbox; 61 | } 62 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/interface/IRollupCore.ts: -------------------------------------------------------------------------------- 1 | // This was created manually because of https://github.com/graphprotocol/graph-cli/issues/342 2 | 3 | import { 4 | ethereum, 5 | JSONValue, 6 | TypedMap, 7 | Entity, 8 | Bytes, 9 | Address, 10 | BigInt 11 | } from "@graphprotocol/graph-ts"; 12 | 13 | export class IRollupCoreNodeRejected extends ethereum.Event { 14 | get params(): IRollupCoreNodeRejected__Params { 15 | return new IRollupCoreNodeRejected__Params(this); 16 | } 17 | } 18 | 19 | export class IRollupCoreNodeRejected__Params { 20 | _event: IRollupCoreNodeRejected; 21 | 22 | constructor(event: IRollupCoreNodeRejected) { 23 | this._event = event; 24 | } 25 | 26 | get nodeNum(): BigInt { 27 | return this._event.parameters[0].value.toBigInt(); 28 | } 29 | } 30 | 31 | 32 | export class IRollupCoreNodeConfirmed extends ethereum.Event { 33 | get params(): IRollupCoreNodeConfirmed__Params { 34 | return new IRollupCoreNodeConfirmed__Params(this); 35 | } 36 | } 37 | export class IRollupCoreNodeConfirmed__Params { 38 | _event: IRollupCoreNodeConfirmed; 39 | 40 | constructor(event: IRollupCoreNodeConfirmed) { 41 | this._event = event; 42 | } 43 | 44 | get nodeNum(): BigInt { 45 | return this._event.parameters[0].value.toBigInt(); 46 | } 47 | 48 | get afterSendAcc(): Bytes { 49 | return this._event.parameters[1].value.toBytes(); 50 | } 51 | 52 | get afterSendCount(): BigInt { 53 | return this._event.parameters[2].value.toBigInt(); 54 | } 55 | 56 | // TODO: add in other parameters 57 | } 58 | 59 | 60 | export class IRollupCoreNodeCreated extends ethereum.Event { 61 | get params(): IRollupCoreNodeCreated__Params { 62 | return new IRollupCoreNodeCreated__Params(this); 63 | } 64 | } 65 | 66 | export class IRollupCoreNodeCreated__Params { 67 | _event: IRollupCoreNodeCreated; 68 | 69 | constructor(event: IRollupCoreNodeCreated) { 70 | this._event = event; 71 | } 72 | 73 | get nodeNum(): BigInt { 74 | return this._event.parameters[0].value.toBigInt(); 75 | } 76 | 77 | get parentNodeHash(): Bytes { 78 | return this._event.parameters[1].value.toBytes(); 79 | } 80 | 81 | get nodeHash(): Bytes { 82 | return this._event.parameters[2].value.toBytes(); 83 | } 84 | 85 | get executionHash(): Bytes { 86 | return this._event.parameters[3].value.toBytes(); 87 | } 88 | 89 | get inboxMaxCount(): BigInt { 90 | return this._event.parameters[4].value.toBigInt(); 91 | } 92 | 93 | get afterInboxBatchEndCount(): BigInt { 94 | return this._event.parameters[5].value.toBigInt(); 95 | } 96 | 97 | get afterInboxBatchAcc(): Bytes { 98 | return this._event.parameters[6].value.toBytes(); 99 | } 100 | 101 | get assertionBytes32Fields(): Array> { 102 | const valueArray = this._event.parameters[7].value.toArray() 103 | 104 | let out = new Array>(valueArray.length) 105 | for (let i: i32 = 0; i < valueArray.length; i++) { 106 | out[i] = valueArray[i].toBytesArray() 107 | } 108 | return out 109 | } 110 | 111 | get assertionIntFields(): Array> { 112 | const valueArray = this._event.parameters[8].value.toArray() 113 | 114 | let out = new Array>(valueArray.length) 115 | for (let i: i32 = 0; i < valueArray.length; i++) { 116 | out[i] = valueArray[i].toBigIntArray() 117 | } 118 | return out 119 | } 120 | } 121 | 122 | export class IRollupCore extends ethereum.SmartContract { 123 | static bind(address: Address): IRollupCore { 124 | return new IRollupCore("IRollupCore", address); 125 | } 126 | // TODO: add in read-only methods 127 | } 128 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/interface/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "rollupPreprocessor": " - kind: ethereum/contract\n name: IRollupCore\n network: {{ l1Network }}\n source:\n address: '{{ rollupAddress }}'\n abi: IRollupCore\n startBlock: {{ rollupDeploymentBlock }}\n mapping:\n kind: ethereum/events\n apiVersion: 0.0.6\n language: wasm/assemblyscript\n entities:\n - Node\n abis:\n - name: IRollupCore\n file: ./abis/IRollupCore.json\n eventHandlers:\n - event: NodeCreated(indexed uint256,indexed bytes32,bytes32,bytes32,uint256,uint256,bytes32,bytes32[3][2],uint256[4][2])\n handler: handleNodeCreated\n - event: NodeConfirmed(indexed uint256,bytes32,uint256,bytes32,uint256)\n handler: handleNodeConfirmed\n - event: NodeRejected(indexed uint256)\n handler: handleNodeRejected\n file: ./src/rollup.ts", 3 | 4 | "l1Network": "{{ l1Network }}", 5 | "outboxDeploymentBlock": "{{ outboxDeploymentBlock }}", 6 | "outboxAddress": "{{ outboxAddress }}", 7 | "inboxDeploymentBlock": "{{ inboxDeploymentBlock }}", 8 | "inboxAddress": "{{ inboxAddress }}", 9 | "bridgeDeploymentBlock": "{{ bridgeDeploymentBlock }}", 10 | "bridgeAddress": "{{ bridgeAddress }}", 11 | "classicBridgeDeploymentBlock": "{{ classicBridgeDeploymentBlock }}", 12 | "classicBridgeAddress": "{{ classicBridgeAddress }}", 13 | "classicBridgeStart": "{{#classicBridgeAddress}}", 14 | "classicBridgeEnd": "{{/classicBridgeAddress}}", 15 | "l1GatewayRouter": "{{ l1GatewayRouter }}", 16 | "l1GatewayRouterDeployBlock": "{{ l1GatewayRouterDeployBlock }}", 17 | "rollupAddress": "{{ rollupAddress }}", 18 | "rollupDeploymentBlock": "{{ rollupDeploymentBlock }}" 19 | } 20 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/interface/codegen.json: -------------------------------------------------------------------------------- 1 | { 2 | "rollupPreprocessor": "", 3 | 4 | "l1Network": "{{ l1Network }}", 5 | "outboxDeploymentBlock": "{{ outboxDeploymentBlock }}", 6 | "outboxAddress": "{{ outboxAddress }}", 7 | "inboxDeploymentBlock": "{{ inboxDeploymentBlock }}", 8 | "inboxAddress": "{{ inboxAddress }}", 9 | "bridgeDeploymentBlock": "{{ bridgeDeploymentBlock }}", 10 | "bridgeAddress": "{{ bridgeAddress }}", 11 | "classicBridgeDeploymentBlock": "{{ classicBridgeDeploymentBlock }}", 12 | "classicBridgeAddress": "{{ classicBridgeAddress }}", 13 | "classicBridgeStart": "{{#classicBridgeAddress}}", 14 | "classicBridgeEnd": "{{/classicBridgeAddress}}", 15 | "l1GatewayRouter": "{{ l1GatewayRouter }}", 16 | "l1GatewayRouterDeployBlock": "{{ l1GatewayRouterDeployBlock }}", 17 | "rollupAddress": "{{ rollupAddress }}", 18 | "rollupDeploymentBlock": "{{ rollupDeploymentBlock }}" 19 | } 20 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/outbox.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OutBoxTransactionExecuted as OutBoxTransactionExecutedEvent, 3 | OutboxEntryCreated as OutboxEntryCreatedEvent, 4 | } from "../generated/Outbox/Outbox"; 5 | import { OutboxEntry, OutboxOutput } from "../generated/schema"; 6 | import { bigIntToId } from "./utils"; 7 | 8 | export function handleOutBoxTransactionExecuted(event: OutBoxTransactionExecutedEvent): void { 9 | // this ID is not the same as the outputId used on chain 10 | const id = event.transaction.hash.toHex() + "-" + event.logIndex.toString(); 11 | let entity = new OutboxOutput(id); 12 | entity.destAddr = event.params.destAddr; 13 | entity.l2Sender = event.params.l2Sender; 14 | entity.outboxEntry = bigIntToId(event.params.outboxEntryIndex); 15 | entity.path = event.params.transactionIndex; 16 | // if OutBoxTransactionExecuted was emitted then the OutboxOutput was spent 17 | entity.spent = true; 18 | entity.save(); 19 | } 20 | 21 | export function handleOutboxEntryCreated(event: OutboxEntryCreatedEvent): void { 22 | let entity = new OutboxEntry(bigIntToId(event.params.batchNum)); 23 | entity.outboxEntryIndex = event.params.outboxEntryIndex; 24 | entity.outputRoot = event.params.outputRoot; 25 | entity.numInBatch = event.params.numInBatch; 26 | entity.save(); 27 | } 28 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/rlp.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ByteArray, Bytes, log } from "@graphprotocol/graph-ts"; 2 | 3 | export function getBytes(num: BigInt): ByteArray { 4 | if (num == BigInt.fromI32(0)) { 5 | return new ByteArray(0); 6 | } 7 | 8 | // operate on a copy to avoid mutating the original 9 | const numCopy = BigInt.fromString(num.toString()); 10 | const reverse = Bytes.fromUint8Array(Bytes.fromBigInt(numCopy).reverse()); 11 | const stripped = stripZeros(reverse); 12 | 13 | return stripped; 14 | } 15 | 16 | function stripZeros(bytes: Bytes): Bytes { 17 | // Find the first non-zero byte 18 | let firstNonZeroByteIndex = 0; 19 | while (firstNonZeroByteIndex < bytes.byteLength && bytes[firstNonZeroByteIndex] == 0) { 20 | firstNonZeroByteIndex++; 21 | } 22 | 23 | // If all bytes are zero, return a byte array with a single zero byte 24 | if (firstNonZeroByteIndex == bytes.length) { 25 | return new Bytes(1); 26 | } 27 | 28 | // Create a new byte array with only the non-zero bytes 29 | let strippedBytes = new Bytes(bytes.length - firstNonZeroByteIndex); 30 | for (let i = 0; i < strippedBytes.length; i++) { 31 | strippedBytes[i] = bytes[firstNonZeroByteIndex + i]; 32 | } 33 | 34 | return strippedBytes; 35 | } 36 | 37 | function encodeItem(item: ByteArray): ByteArray { 38 | if (item.byteLength === 1 && item[0] < 0x80) { 39 | return item; 40 | } else if (item.byteLength < 56) { 41 | const prefix = new ByteArray(1); 42 | prefix[0] = 0x80 + item.byteLength; 43 | return concatBytes(prefix, item); 44 | } else { 45 | const lengthBytes = getLengthBytes(item.byteLength); 46 | const prefix = new ByteArray(1); 47 | prefix[0] = 0xb7 + lengthBytes.byteLength; 48 | const strLength = getBytes(BigInt.fromU32(item.byteLength)); 49 | return concatBytes( 50 | concatBytes( 51 | prefix, 52 | Bytes.fromUint8Array(strLength.slice(strLength.length - lengthBytes.length)) 53 | ), 54 | item 55 | ); 56 | } 57 | } 58 | 59 | function getLengthBytes(length: i32): ByteArray { 60 | let lengthBytes = 1; 61 | let temp = length; 62 | while (temp > 0xff) { 63 | temp >>= 8; 64 | lengthBytes++; 65 | } 66 | const bytes = new ByteArray(lengthBytes); 67 | for (let i = 0; i < lengthBytes; i++) { 68 | bytes[lengthBytes - 1 - i] = temp & 0xff; 69 | temp >>= 8; 70 | } 71 | return bytes; 72 | } 73 | 74 | function concatBytes(a: ByteArray, b: ByteArray): ByteArray { 75 | return a.concat(b); 76 | } 77 | 78 | export function rlpEncodeList(items: ByteArray[]): ByteArray { 79 | let encodedItems = new ByteArray(0); 80 | for (let i = 0; i < items.length; i++) { 81 | const encodedItem = encodeItem(items[i]); 82 | // log.debug("RLP input: {}; RLP output: {}", [items[i].toHexString(), encodedItem.toHexString()]); 83 | encodedItems = concatBytes(encodedItems, encodedItem); 84 | } 85 | const prefix = new ByteArray(1); 86 | if (encodedItems.byteLength < 56) { 87 | prefix[0] = 0xc0 + encodedItems.byteLength; 88 | return concatBytes(prefix, encodedItems); 89 | } else { 90 | const lengthBytes = getLengthBytes(encodedItems.byteLength); 91 | prefix[0] = 0xf7 + lengthBytes.byteLength; 92 | const strLength = getBytes(BigInt.fromU32(encodedItems.byteLength)); 93 | return concatBytes( 94 | concatBytes( 95 | prefix, 96 | Bytes.fromUint8Array(strLength.slice(strLength.length - lengthBytes.length)) 97 | ), 98 | encodedItems 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/rollup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IRollupCoreNodeCreated as NodeCreatedEvent, 3 | IRollupCoreNodeConfirmed as NodeConfirmedEvent, 4 | IRollupCoreNodeRejected as NodeRejectedEvent, 5 | } from "./interface/IRollupCore"; 6 | import { Node as NodeEntity } from "../generated/schema"; 7 | import { log } from "@graphprotocol/graph-ts"; 8 | import { bigIntToId } from "./utils"; 9 | 10 | export function handleNodeCreated(event: NodeCreatedEvent): void { 11 | const id = bigIntToId(event.params.nodeNum); 12 | let entity = new NodeEntity(id); 13 | entity.nodeHash = event.params.nodeHash; 14 | entity.inboxMaxCount = event.params.inboxMaxCount; 15 | entity.parentHash = event.params.parentNodeHash; 16 | entity.blockCreatedAt = event.block.number; 17 | entity.timestampCreated = event.block.timestamp; 18 | entity.timestampStatusUpdate = null; 19 | entity.status = "Pending"; 20 | entity.afterSendCount = event.params.assertionIntFields[1][2]; 21 | entity.save(); 22 | } 23 | 24 | export function handleNodeConfirmed(event: NodeConfirmedEvent): void { 25 | const id = bigIntToId(event.params.nodeNum); 26 | // we just edit 1 field, we know the node is already created, so we just update its status 27 | // used to be faster to do a `new NodeEntity(id)` than load since it wouldn't overwrite other fields 28 | // but that doesn't seem to hold anymore 29 | let entity = NodeEntity.load(id); 30 | if (!entity) { 31 | log.critical("Should not confirm non-existent node", []); 32 | throw new Error("no node to confirm"); 33 | } 34 | entity.timestampStatusUpdate = event.block.timestamp; 35 | entity.status = "Confirmed"; 36 | 37 | if (entity.afterSendCount != event.params.afterSendCount) { 38 | log.critical("Something went wrong parsing the after send count", []); 39 | throw new Error("Wrong send cound"); 40 | } 41 | 42 | entity.save(); 43 | } 44 | 45 | export function handleNodeRejected(event: NodeRejectedEvent): void { 46 | const id = bigIntToId(event.params.nodeNum); 47 | // we just edit 1 field, we know the node is already created, so we just update its status 48 | // used to be faster to do a `new NodeEntity(id)` than load since it wouldn't overwrite other fields 49 | // but that doesn't seem to hold anymore 50 | let entity = NodeEntity.load(id); 51 | if (!entity) { 52 | log.critical("Should not reject non-existent node", []); 53 | throw new Error("no node to reject"); 54 | } 55 | entity.timestampStatusUpdate = event.block.timestamp; 56 | entity.status = "Rejected"; 57 | entity.save(); 58 | } 59 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/tokenBridge.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultGatewayUpdated, 3 | GatewaySet, 4 | TransferRouted, 5 | TxToL2, 6 | WhitelistSourceUpdated, 7 | Deposit, 8 | Retryable, 9 | ClassicRetryable, 10 | } from "../generated/schema"; 11 | import { BigInt } from "@graphprotocol/graph-ts"; 12 | import { 13 | DefaultGatewayUpdated as DefaultGatewayUpdatedEvent, 14 | GatewaySet as GatewaySetEvent, 15 | InitializeCall, 16 | TransferRouted as TransferRoutedEvent, 17 | TxToL2 as TxToL2Event, 18 | WhitelistSourceUpdated as WhitelistSourceUpdatedEvent, 19 | } from "../generated/L1GatewayRouter/L1GatewayRouter"; 20 | import { DepositInitiated } from "../generated/templates/L1ArbitrumGateway/L1ArbitrumGateway"; 21 | import { getOrCreateGateway, getOrCreateToken } from "./bridgeUtils"; 22 | import { isArbOne } from "./utils"; 23 | 24 | /** 25 | * Last token deposit prior to Nitro was in TX 0xbc4324b4fe584f573e82b8b5b458f8303be318bf2bf46b0fc71087146bea4e37. 26 | * Used to distinguish between classic and nitro token deposits. 27 | */ 28 | const ARB_ONE_BLOCK_OF_LAST_CLASSIC_TOKEN_DEPOSIT = 15446977; 29 | 30 | /** 31 | * Create TokenDeposit entities when deposit is initiated on L1 side. 32 | * @param event 33 | */ 34 | export function handleDepositInitiated(event: DepositInitiated): void { 35 | let tokenDepositId = 36 | event.transaction.hash.toHexString() + "-" + event.logIndex.toString(); 37 | 38 | let tokenDeposit = new Deposit(tokenDepositId); 39 | tokenDeposit.type = "TokenDeposit"; 40 | tokenDeposit.tokenAmount = event.params._amount; 41 | tokenDeposit.sender = event.params._from; 42 | tokenDeposit.receiver = event.params._to; 43 | tokenDeposit.ethValue = BigInt.fromI32(0); 44 | tokenDeposit.sequenceNumber = event.params._sequenceNumber; 45 | tokenDeposit.l1Token = getOrCreateToken(event.params.l1Token, event.block.number).id; 46 | 47 | let firstNitroBlock = 0; 48 | if (isArbOne()) { 49 | firstNitroBlock = ARB_ONE_BLOCK_OF_LAST_CLASSIC_TOKEN_DEPOSIT + 1; 50 | } 51 | tokenDeposit.isClassic = event.block.number.lt(BigInt.fromI32(firstNitroBlock)); 52 | tokenDeposit.l2TicketId = getRetryablesID(event.params._sequenceNumber, tokenDeposit.isClassic); 53 | tokenDeposit.timestamp = event.block.timestamp; 54 | tokenDeposit.transactionHash = event.transaction.hash.toHexString(); 55 | tokenDeposit.blockCreatedAt = event.block.number; 56 | tokenDeposit.save(); 57 | } 58 | 59 | /** 60 | * Keep track of default gateway updates 61 | * @param event 62 | */ 63 | export function handleDefaultGatewayUpdated(event: DefaultGatewayUpdatedEvent): void { 64 | let entity = new DefaultGatewayUpdated( 65 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 66 | ); 67 | entity.newDefaultGateway = event.params.newDefaultGateway; 68 | entity.timestamp = event.block.timestamp; 69 | entity.transactionHash = event.transaction.hash; 70 | entity.blockNumber = event.block.number; 71 | entity.save(); 72 | 73 | // create deafult gateway entity and start indexing contract 74 | getOrCreateGateway(event.params.newDefaultGateway, event.block.number); 75 | } 76 | 77 | /** 78 | * Keep track of token->gateway registrations 79 | * @param event 80 | */ 81 | export function handleGatewaySet(event: GatewaySetEvent): void { 82 | let entity = new GatewaySet(event.transaction.hash.toHex() + "-" + event.logIndex.toString()); 83 | entity.l1Token = event.params.l1Token; 84 | entity.gateway = event.params.gateway; 85 | entity.timestamp = event.block.timestamp; 86 | entity.transactionHash = event.transaction.hash; 87 | entity.blockNumber = event.block.number; 88 | entity.save(); 89 | 90 | // create entities if needed and set gateway ref 91 | let gateway = getOrCreateGateway(event.params.gateway, event.block.number); 92 | let token = getOrCreateToken(event.params.l1Token, event.block.number); 93 | token.gateway = gateway.id; 94 | token.save(); 95 | } 96 | 97 | /** 98 | * Keep track of TransferRouted events 99 | * @param event 100 | */ 101 | export function handleTransferRouted(event: TransferRoutedEvent): void { 102 | let entity = new TransferRouted(event.transaction.hash.toHex() + "-" + event.logIndex.toString()); 103 | entity.token = event.params.token; 104 | entity._userFrom = event.params._userFrom; 105 | entity._userTo = event.params._userTo; 106 | entity.gateway = event.params.gateway; 107 | entity.timestamp = event.block.timestamp; 108 | entity.transactionHash = event.transaction.hash; 109 | entity.blockNumber = event.block.number; 110 | entity.save(); 111 | } 112 | 113 | /** 114 | * Keep track of TxToL2 events 115 | * @param event 116 | */ 117 | export function handleTxToL2(event: TxToL2Event): void { 118 | let entity = new TxToL2(event.transaction.hash.toHex() + "-" + event.logIndex.toString()); 119 | entity._from = event.params._from; 120 | entity._to = event.params._to; 121 | entity._seqNum = event.params._seqNum; 122 | entity._data = event.params._data; 123 | entity.save(); 124 | } 125 | 126 | /** 127 | * Keep track of Whitelist updates 128 | * @param event 129 | */ 130 | export function handleWhitelistSourceUpdated(event: WhitelistSourceUpdatedEvent): void { 131 | let entity = new WhitelistSourceUpdated( 132 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 133 | ); 134 | entity.newSource = event.params.newSource; 135 | entity.timestamp = event.block.timestamp; 136 | entity.transactionHash = event.transaction.hash; 137 | entity.blockNumber = event.block.number; 138 | entity.save(); 139 | } 140 | 141 | /** 142 | * Handle one-time call to `initialize` in order to pick up defaultGateway (no event emitted there) 143 | * @param call 144 | */ 145 | export function handleInitialize(call: InitializeCall): void { 146 | // create deafult gateway entity and start indexing contract 147 | getOrCreateGateway(call.inputs._defaultGateway, call.block.number); 148 | } 149 | 150 | function getRetryablesID(seqNumber: BigInt, isClassic: boolean): string | null { 151 | if (isClassic) { 152 | const ticket = ClassicRetryable.load(seqNumber.toHexString()); 153 | if (ticket) { 154 | return ticket.retryableTicketID.toHexString(); 155 | } 156 | } else { 157 | const ticket = Retryable.load(seqNumber.toHexString()); 158 | if (ticket) { 159 | return ticket.retryableTicketID.toHexString(); 160 | } 161 | } 162 | 163 | return null; 164 | } 165 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Bytes, 3 | BigInt, 4 | ethereum, 5 | Address, 6 | log, 7 | dataSource, 8 | crypto, 9 | ByteArray, 10 | } from "@graphprotocol/graph-ts"; 11 | import { encodePadded, padBytes } from "@arbitrum/subgraph-common"; 12 | import { getBytes, rlpEncodeList } from "./rlp"; 13 | import { InboxMessageDelivered as InboxMessageDeliveredEvent } from "../generated/Inbox/Inbox"; 14 | import { Inbox } from "../generated/schema"; 15 | 16 | const NOVA_INBOX_ADDRESS = "0xc4448b71118c9071bcb9734a0eac55d18a153949"; 17 | const ADDRESS_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; 18 | 19 | export function isArbOne(): boolean { 20 | const l2ChainId = getL2ChainId(); 21 | const arbId = Bytes.fromByteArray(Bytes.fromHexString("0xa4b1")); 22 | return l2ChainId == arbId; 23 | } 24 | 25 | export const getL2ChainId = (): Bytes => { 26 | const network = dataSource.network(); 27 | if (network == "mainnet") { 28 | // determine if L2 is Nova 29 | if (Inbox.load(NOVA_INBOX_ADDRESS) != null) { 30 | return Bytes.fromByteArray(Bytes.fromHexString("0xa4ba")); 31 | } else { 32 | // Arb One 33 | return Bytes.fromByteArray(Bytes.fromHexString("0xa4b1")); 34 | } 35 | } 36 | 37 | if (network == "goerli") return Bytes.fromByteArray(Bytes.fromHexString("0x066eed")); 38 | if (network == "sepolia") return Bytes.fromByteArray(Bytes.fromHexString("0x066eee")); 39 | 40 | log.critical("No chain id recognised", []); 41 | throw new Error("No chain id found"); 42 | }; 43 | 44 | export const bitFlip = (input: BigInt): Bytes => { 45 | // base hex string is all zeroes, with the highest bit set. equivalent to 1 << 255 46 | const base = Bytes.fromHexString( 47 | "0x8000000000000000000000000000000000000000000000000000000000000000" 48 | ); 49 | const bytes = padBytes(Bytes.fromBigInt(input), 32); 50 | 51 | for (let i: i32 = 0; i < base.byteLength; i++) { 52 | base[i] = base[i] | bytes[i]; 53 | } 54 | 55 | return Bytes.fromByteArray(base); 56 | }; 57 | 58 | export const getL2RetryableTicketId = (inboxSequenceNumber: BigInt): Bytes => { 59 | // keccak256(zeroPad(l2ChainId), zeroPad(bitFlipedinboxSequenceNumber)) 60 | const l2ChainId = getL2ChainId(); 61 | const flipped = bitFlip(inboxSequenceNumber); 62 | const encoded: ByteArray = encodePadded(l2ChainId, flipped); 63 | const res = Bytes.fromByteArray(crypto.keccak256(encoded)); 64 | 65 | // log.info( 66 | // "Getting Retryable ticket Id. l2Chain id {} . inboxSeq {} . flipped {} . encoded {} . retTicketId {}", 67 | // [ 68 | // l2ChainId.toHexString(), 69 | // inboxSequenceNumber.toHexString(), 70 | // flipped.toHexString(), 71 | // encoded.toHexString(), 72 | // res.toHexString(), 73 | // ] 74 | // ); 75 | 76 | return res; 77 | }; 78 | 79 | export const getL2NitroRetryableTicketId = ( 80 | event: InboxMessageDeliveredEvent, 81 | retryable: RetryableTx, 82 | messageSenderAddress: Address 83 | ): Bytes => { 84 | const l2ChainId: Bytes = getL2ChainId(); 85 | const msgNum = padBytes(getBytes(event.params.messageNum), 32); 86 | const fromAddress: ByteArray = ByteArray.fromHexString(messageSenderAddress.toHexString()); 87 | let l1BaseFee: ByteArray = getBytes(BigInt.fromI32(0)); 88 | if (event.block.baseFeePerGas) { 89 | l1BaseFee = getBytes(event.block.baseFeePerGas!); 90 | } 91 | const l1Value: ByteArray = getBytes(retryable.l1CallValue); 92 | const maxFeePerGas: ByteArray = getBytes(retryable.gasPriceBid); 93 | const gasLimit: ByteArray = getBytes(retryable.maxGas); 94 | const destAddressString: string = retryable.destAddress.toHexString(); 95 | const destAddress: ByteArray = ByteArray.fromHexString( 96 | destAddressString == "0x0000000000000000000000000000000000000000" ? "0x" : destAddressString 97 | ); 98 | const l2CallValue: ByteArray = getBytes(retryable.l2CallValue); 99 | const callValueRefundAddress: ByteArray = ByteArray.fromHexString( 100 | retryable.callValueRefundAddress.toHexString() 101 | ); 102 | const maxSubmissionFee: ByteArray = getBytes(retryable.maxSubmissionCost); 103 | const excessFeeRefundAddress: ByteArray = ByteArray.fromHexString( 104 | retryable.excessFeeRefundAddress.toHexString() 105 | ); 106 | const data: ByteArray = ByteArray.fromHexString(retryable.data.toHexString()); 107 | const input: ByteArray[] = [ 108 | l2ChainId, 109 | msgNum, 110 | fromAddress, 111 | l1BaseFee, 112 | l1Value, 113 | maxFeePerGas, 114 | gasLimit, 115 | destAddress, 116 | l2CallValue, 117 | callValueRefundAddress, 118 | maxSubmissionFee, 119 | excessFeeRefundAddress, 120 | data, 121 | ]; 122 | 123 | const rlpEncoded = rlpEncodeList(input); 124 | const prefix = "0x69"; 125 | const rlpEncodedWithPrefix = ByteArray.fromHexString(prefix).concat(rlpEncoded); 126 | 127 | const ticketId = Bytes.fromByteArray(crypto.keccak256(rlpEncodedWithPrefix)); 128 | return ticketId; 129 | }; 130 | 131 | export const bigIntToId = (input: BigInt): string => input.toHexString(); 132 | 133 | export const bigIntToAddress = (input: BigInt): Address => { 134 | // remove the prepended 0x 135 | const hexString = input.toHexString().substr(2); 136 | // add missing padding so address is 20 bytes long 137 | const missingZeroes = "0".repeat(40 - hexString.length); 138 | // build hexstring again 139 | const addressString = "0x" + missingZeroes + hexString; 140 | return Address.fromString(addressString); 141 | }; 142 | 143 | export const addressToBigInt = (input: Address): BigInt => { 144 | const addressBytes = Bytes.fromHexString(input.toHexString()); 145 | // reverse it in order to use big-endian instead of little-endian 146 | const addressBytesRev = addressBytes.reverse() as Bytes; 147 | const bigInt = BigInt.fromUnsignedBytes(addressBytesRev); 148 | return bigInt; 149 | }; 150 | 151 | /** 152 | * Apply or undo alias based on Arbitrum smart contract aliasing implementation. 153 | * @param input address to which alias is applied 154 | * @param reverse if true undo alias 155 | * @returns 156 | */ 157 | export const applyAlias = (input: Address, reverse: boolean): Address => { 158 | let inputBigInt = addressToBigInt(input); 159 | 160 | const offset = Address.fromBytes(Bytes.fromHexString(ADDRESS_ALIAS_OFFSET)); 161 | let offsetAddressBigInt = addressToBigInt(offset); 162 | 163 | // 2^160 - 1 164 | let maxUint160 = BigInt.fromI32(2) 165 | .pow(160) 166 | .minus(BigInt.fromI32(1)); 167 | 168 | let result: Address; 169 | if (reverse) { 170 | // reverse -> undo alias 171 | if (inputBigInt.ge(offsetAddressBigInt)) { 172 | result = bigIntToAddress(inputBigInt.minus(offsetAddressBigInt)); 173 | } else { 174 | // handle underflow 175 | let diff = offsetAddressBigInt.minus(inputBigInt); 176 | let underflowAddress = maxUint160.minus(diff).plus(BigInt.fromI32(1)); 177 | result = bigIntToAddress(underflowAddress); 178 | } 179 | } else { 180 | // apply alias 181 | if (inputBigInt.plus(offsetAddressBigInt).le(maxUint160)) { 182 | result = bigIntToAddress(inputBigInt.plus(offsetAddressBigInt)); 183 | } else { 184 | // handle overflow 185 | let diff = inputBigInt.plus(offsetAddressBigInt).minus(maxUint160); 186 | let overflowAddress = diff.minus(BigInt.fromI32(1)); 187 | result = bigIntToAddress(overflowAddress); 188 | } 189 | } 190 | 191 | return result; 192 | }; 193 | 194 | export class RetryableTx { 195 | private constructor( 196 | public destAddress: Address, 197 | public l2CallValue: BigInt, 198 | public l1CallValue: BigInt, 199 | public maxSubmissionCost: BigInt, 200 | public excessFeeRefundAddress: Address, 201 | public callValueRefundAddress: Address, 202 | public maxGas: BigInt, 203 | public gasPriceBid: BigInt, 204 | public dataLength: BigInt, 205 | public data: Bytes 206 | ) {} 207 | 208 | static parseRetryable(data: Bytes): RetryableTx | null { 209 | const parsedWithoutData = ethereum.decode( 210 | "(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", 211 | data 212 | ); 213 | if (parsedWithoutData) { 214 | const parsedArray = parsedWithoutData.toTuple(); 215 | const dataLength = parsedArray[8].toBigInt().toI32(); 216 | const l2Calldata = new Bytes(dataLength); 217 | 218 | for (let i = 0; i < dataLength; i++) { 219 | l2Calldata[dataLength - i - 1] = data[data.length - i - 1]; 220 | } 221 | 222 | return new RetryableTx( 223 | bigIntToAddress(parsedArray[0].toBigInt()), 224 | parsedArray[1].toBigInt(), 225 | parsedArray[2].toBigInt(), 226 | parsedArray[3].toBigInt(), 227 | bigIntToAddress(parsedArray[4].toBigInt()), 228 | bigIntToAddress(parsedArray[5].toBigInt()), 229 | parsedArray[6].toBigInt(), 230 | parsedArray[7].toBigInt(), 231 | BigInt.fromI32(dataLength), 232 | l2Calldata 233 | ); 234 | } 235 | 236 | return null; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /packages/arb-bridge-eth/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | description: Subgraph that indexes Arbitrum eth-bridge contracts 3 | schema: 4 | file: ./schema.graphql 5 | templates: 6 | - name: L1ArbitrumGateway 7 | kind: ethereum/contract 8 | network: {{ l1Network }} 9 | source: 10 | abi: L1ArbitrumGateway 11 | mapping: 12 | kind: ethereum/events 13 | apiVersion: 0.0.6 14 | language: wasm/assemblyscript 15 | file: ./src/tokenBridge.ts 16 | entities: 17 | - L1ArbitrumGateway 18 | abis: 19 | - name: L1ArbitrumGateway 20 | file: ./abis/L1ArbitrumGateway.json 21 | - name: IERC20 22 | file: ./abis/IERC20.json 23 | eventHandlers: 24 | - event: DepositInitiated(address,indexed address,indexed address,indexed uint256,uint256) 25 | handler: handleDepositInitiated 26 | dataSources: 27 | - kind: ethereum/contract 28 | name: Outbox 29 | network: {{ l1Network }} 30 | source: 31 | address: '{{ outboxAddress }}' 32 | abi: Outbox 33 | startBlock: {{ outboxDeploymentBlock }} 34 | mapping: 35 | kind: ethereum/events 36 | apiVersion: 0.0.6 37 | language: wasm/assemblyscript 38 | entities: 39 | - OutBoxTransactionExecuted 40 | - OutboxEntryCreated 41 | abis: 42 | - name: Outbox 43 | file: ./abis/Outbox.json 44 | eventHandlers: 45 | - event: OutBoxTransactionExecuted(indexed address,indexed address,indexed uint256,uint256) 46 | handler: handleOutBoxTransactionExecuted 47 | - event: OutboxEntryCreated(indexed uint256,uint256,bytes32,uint256) 48 | handler: handleOutboxEntryCreated 49 | file: ./src/outbox.ts 50 | - kind: ethereum/contract 51 | name: Inbox 52 | network: {{ l1Network }} 53 | source: 54 | address: '{{ inboxAddress }}' 55 | abi: Inbox 56 | startBlock: {{ inboxDeploymentBlock }} 57 | mapping: 58 | kind: ethereum/events 59 | apiVersion: 0.0.6 60 | language: wasm/assemblyscript 61 | entities: 62 | - InboxMessageDelivered 63 | abis: 64 | - name: Inbox 65 | file: ./abis/Inbox.json 66 | - name: IERC20 67 | file: ./abis/IERC20.json 68 | eventHandlers: 69 | - event: InboxMessageDelivered(indexed uint256,bytes) 70 | handler: handleInboxMessageDelivered 71 | file: ./src/bridge.ts 72 | - kind: ethereum/contract 73 | name: Bridge 74 | network: {{ l1Network }} 75 | source: 76 | address: '{{ bridgeAddress }}' 77 | abi: Bridge 78 | startBlock: {{ bridgeDeploymentBlock }} 79 | mapping: 80 | kind: ethereum/events 81 | apiVersion: 0.0.6 82 | language: wasm/assemblyscript 83 | entities: 84 | - MessageDelivered 85 | abis: 86 | - name: Bridge 87 | file: ./abis/Bridge.json 88 | - name: IERC20 89 | file: ./abis/IERC20.json 90 | eventHandlers: 91 | - event: MessageDelivered(indexed uint256,indexed bytes32,address,uint8,address,bytes32,uint256,uint64) 92 | handler: handleNitroMessageDelivered 93 | file: ./src/bridge.ts 94 | {{ classicBridgeStart }} 95 | - kind: ethereum/contract 96 | name: ClassicBridge 97 | network: {{ l1Network }} 98 | source: 99 | address: '{{ classicBridgeAddress }}' 100 | abi: Bridge 101 | startBlock: {{ classicBridgeDeploymentBlock }} 102 | mapping: 103 | kind: ethereum/events 104 | apiVersion: 0.0.6 105 | language: wasm/assemblyscript 106 | entities: 107 | - MessageDelivered 108 | abis: 109 | - name: Bridge 110 | file: ./abis/Bridge.json 111 | eventHandlers: 112 | - event: MessageDelivered(indexed uint256,indexed bytes32,address,uint8,address,bytes32) 113 | handler: handleClassicMessageDelivered 114 | file: ./src/bridge.ts 115 | {{{ classicBridgeEnd }}} 116 | - kind: ethereum/contract 117 | name: L1GatewayRouter 118 | network: {{ l1Network }} 119 | source: 120 | address: "{{ l1GatewayRouter }}" 121 | abi: L1GatewayRouter 122 | startBlock: {{ l1GatewayRouterDeployBlock }} 123 | mapping: 124 | kind: ethereum/events 125 | apiVersion: 0.0.6 126 | language: wasm/assemblyscript 127 | entities: 128 | - DefaultGatewayUpdated 129 | - GatewaySet 130 | - TransferRouted 131 | - TxToL2 132 | - WhitelistSourceUpdated 133 | abis: 134 | - name: L1GatewayRouter 135 | file: ./abis/L1GatewayRouter.json 136 | - name: IERC20 137 | file: ./abis/IERC20.json 138 | eventHandlers: 139 | - event: DefaultGatewayUpdated(address) 140 | handler: handleDefaultGatewayUpdated 141 | - event: GatewaySet(indexed address,indexed address) 142 | handler: handleGatewaySet 143 | - event: TransferRouted(indexed address,indexed address,indexed address,address) 144 | handler: handleTransferRouted 145 | - event: TxToL2(indexed address,indexed address,indexed uint256,bytes) 146 | handler: handleTxToL2 147 | - event: WhitelistSourceUpdated(address) 148 | handler: handleWhitelistSourceUpdated 149 | callHandlers: 150 | - function: initialize(address,address,address,address,address) 151 | handler: handleInitialize 152 | file: ./src/tokenBridge.ts 153 | {{{ rollupPreprocessor }}} 154 | -------------------------------------------------------------------------------- /packages/arbitrum-blocks/abis/IERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /packages/arbitrum-blocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrum-blocks", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:arb && graph codegen", 7 | "build": "yarn prepare:arb && graph build", 8 | "postinstall": "yarn codegen", 9 | 10 | "prepare:arb": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nitro-mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 11 | "prepare:nova": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nova.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 12 | "prepare:goerli": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 13 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 14 | 15 | "deploy:arb": "yarn build && yarn prepare:arb && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-blocks", 16 | "deploy:nova": "yarn build && yarn prepare:nova && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-blocks-nova", 17 | "deploy:goerli": "yarn build && yarn prepare:goerli && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-blocks-goerli", 18 | "deploy:sepolia": "yarn build && yarn prepare:sepolia && graph deploy --node https://api.thegraph.com/deploy/ fionnachan/arbitrum-blocks-sepolia" 19 | }, 20 | "dependencies": { 21 | "@arbitrum/subgraph-common": "0.0.1", 22 | "@graphprotocol/graph-ts": "^0.32.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/arbitrum-blocks/schema.graphql: -------------------------------------------------------------------------------- 1 | type Block @entity(immutable:true) { 2 | "block hash" 3 | id: Bytes! 4 | number: BigInt! 5 | timestamp: BigInt! 6 | } 7 | -------------------------------------------------------------------------------- /packages/arbitrum-blocks/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { ethereum } from "@graphprotocol/graph-ts"; 2 | import { Block } from "../generated/schema"; 3 | 4 | export function handleBlock(block: ethereum.Block): void { 5 | let id = block.hash; 6 | let blockEntity = new Block(id); 7 | blockEntity.number = block.number; 8 | blockEntity.timestamp = block.timestamp; 9 | blockEntity.save(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/arbitrum-blocks/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.4 2 | description: Subgraph that tracks Arbitrum blocks 3 | schema: 4 | file: schema.graphql 5 | dataSources: 6 | - kind: ethereum/contract 7 | name: ArbitrumBlocks 8 | network: {{ l2Network }} 9 | source: 10 | address: "0x0000000000000000000000000000000000000000" 11 | abi: IERC20 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.5 15 | language: wasm/assemblyscript 16 | entities: 17 | - Block 18 | abis: 19 | - name: IERC20 20 | file: ./abis/IERC20.json 21 | blockHandlers: 22 | - handler: handleBlock 23 | file: ./src/mapping.ts 24 | -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/abis/ArbRetryableTx.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "bytes32", 8 | "name": "userTxHash", 9 | "type": "bytes32" 10 | } 11 | ], 12 | "name": "Canceled", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "bytes32", 21 | "name": "userTxHash", 22 | "type": "bytes32" 23 | }, 24 | { 25 | "indexed": false, 26 | "internalType": "uint256", 27 | "name": "newTimeout", 28 | "type": "uint256" 29 | } 30 | ], 31 | "name": "LifetimeExtended", 32 | "type": "event" 33 | }, 34 | { 35 | "anonymous": false, 36 | "inputs": [ 37 | { 38 | "indexed": true, 39 | "internalType": "bytes32", 40 | "name": "userTxHash", 41 | "type": "bytes32" 42 | } 43 | ], 44 | "name": "Redeemed", 45 | "type": "event" 46 | }, 47 | { 48 | "anonymous": false, 49 | "inputs": [ 50 | { 51 | "indexed": true, 52 | "internalType": "bytes32", 53 | "name": "userTxHash", 54 | "type": "bytes32" 55 | } 56 | ], 57 | "name": "TicketCreated", 58 | "type": "event" 59 | }, 60 | { 61 | "inputs": [ 62 | { 63 | "internalType": "bytes32", 64 | "name": "userTxHash", 65 | "type": "bytes32" 66 | } 67 | ], 68 | "name": "cancel", 69 | "outputs": [], 70 | "stateMutability": "nonpayable", 71 | "type": "function" 72 | }, 73 | { 74 | "inputs": [ 75 | { 76 | "internalType": "bytes32", 77 | "name": "userTxHash", 78 | "type": "bytes32" 79 | } 80 | ], 81 | "name": "getBeneficiary", 82 | "outputs": [ 83 | { 84 | "internalType": "address", 85 | "name": "", 86 | "type": "address" 87 | } 88 | ], 89 | "stateMutability": "view", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [ 94 | { 95 | "internalType": "bytes32", 96 | "name": "userTxHash", 97 | "type": "bytes32" 98 | } 99 | ], 100 | "name": "getKeepalivePrice", 101 | "outputs": [ 102 | { 103 | "internalType": "uint256", 104 | "name": "", 105 | "type": "uint256" 106 | }, 107 | { 108 | "internalType": "uint256", 109 | "name": "", 110 | "type": "uint256" 111 | } 112 | ], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [], 118 | "name": "getLifetime", 119 | "outputs": [ 120 | { 121 | "internalType": "uint256", 122 | "name": "", 123 | "type": "uint256" 124 | } 125 | ], 126 | "stateMutability": "view", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [ 131 | { 132 | "internalType": "uint256", 133 | "name": "calldataSize", 134 | "type": "uint256" 135 | } 136 | ], 137 | "name": "getSubmissionPrice", 138 | "outputs": [ 139 | { 140 | "internalType": "uint256", 141 | "name": "", 142 | "type": "uint256" 143 | }, 144 | { 145 | "internalType": "uint256", 146 | "name": "", 147 | "type": "uint256" 148 | } 149 | ], 150 | "stateMutability": "view", 151 | "type": "function" 152 | }, 153 | { 154 | "inputs": [ 155 | { 156 | "internalType": "bytes32", 157 | "name": "userTxHash", 158 | "type": "bytes32" 159 | } 160 | ], 161 | "name": "getTimeout", 162 | "outputs": [ 163 | { 164 | "internalType": "uint256", 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "stateMutability": "view", 170 | "type": "function" 171 | }, 172 | { 173 | "inputs": [ 174 | { 175 | "internalType": "bytes32", 176 | "name": "userTxHash", 177 | "type": "bytes32" 178 | } 179 | ], 180 | "name": "keepalive", 181 | "outputs": [ 182 | { 183 | "internalType": "uint256", 184 | "name": "", 185 | "type": "uint256" 186 | } 187 | ], 188 | "stateMutability": "payable", 189 | "type": "function" 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "bytes32", 195 | "name": "userTxHash", 196 | "type": "bytes32" 197 | } 198 | ], 199 | "name": "redeem", 200 | "outputs": [], 201 | "stateMutability": "nonpayable", 202 | "type": "function" 203 | } 204 | ] -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/abis/ArbSys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "address", 8 | "name": "caller", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "destination", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "uint256", 20 | "name": "uniqueId", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": true, 25 | "internalType": "uint256", 26 | "name": "batchNumber", 27 | "type": "uint256" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "uint256", 32 | "name": "indexInBatch", 33 | "type": "uint256" 34 | }, 35 | { 36 | "indexed": false, 37 | "internalType": "uint256", 38 | "name": "arbBlockNum", 39 | "type": "uint256" 40 | }, 41 | { 42 | "indexed": false, 43 | "internalType": "uint256", 44 | "name": "ethBlockNum", 45 | "type": "uint256" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "timestamp", 51 | "type": "uint256" 52 | }, 53 | { 54 | "indexed": false, 55 | "internalType": "uint256", 56 | "name": "callvalue", 57 | "type": "uint256" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "bytes", 62 | "name": "data", 63 | "type": "bytes" 64 | } 65 | ], 66 | "name": "L2ToL1Transaction", 67 | "type": "event" 68 | }, 69 | { 70 | "inputs": [], 71 | "name": "arbBlockNumber", 72 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 73 | "stateMutability": "view", 74 | "type": "function" 75 | }, 76 | { 77 | "inputs": [], 78 | "name": "arbChainID", 79 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 80 | "stateMutability": "view", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [], 85 | "name": "arbOSVersion", 86 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 87 | "stateMutability": "pure", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [ 92 | { "internalType": "address", "name": "account", "type": "address" }, 93 | { "internalType": "uint256", "name": "index", "type": "uint256" } 94 | ], 95 | "name": "getStorageAt", 96 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 97 | "stateMutability": "view", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [ 102 | { "internalType": "address", "name": "account", "type": "address" } 103 | ], 104 | "name": "getTransactionCount", 105 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 106 | "stateMutability": "view", 107 | "type": "function" 108 | }, 109 | { 110 | "inputs": [], 111 | "name": "isTopLevelCall", 112 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [ 118 | { "internalType": "address", "name": "destination", "type": "address" }, 119 | { "internalType": "bytes", "name": "calldataForL1", "type": "bytes" } 120 | ], 121 | "name": "sendTxToL1", 122 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 123 | "stateMutability": "payable", 124 | "type": "function" 125 | }, 126 | { 127 | "inputs": [ 128 | { "internalType": "address", "name": "destination", "type": "address" } 129 | ], 130 | "name": "withdrawEth", 131 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 132 | "stateMutability": "payable", 133 | "type": "function" 134 | } 135 | ] 136 | -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrum-precompiles", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:mainnet && graph codegen", 7 | "build": "yarn prepare:mainnet && graph build", 8 | "postinstall": "yarn codegen", 9 | 10 | "prepare:mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 11 | "prepare:rinkeby": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/rinkeby.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 12 | 13 | "deploy:mainnet": "yarn build && yarn prepare:mainnet && graph deploy --node https://api.thegraph.com/deploy/ fredlacs/arb-builtins", 14 | "deploy:rinkeby": "yarn build && yarn prepare:rinkeby && graph deploy --node https://api.thegraph.com/deploy/ fredlacs/arb-builtins-rinkeby" 15 | }, 16 | "dependencies": { 17 | "@arbitrum/subgraph-common": "0.0.1", 18 | "@graphprotocol/graph-ts": "^0.32.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/schema.graphql: -------------------------------------------------------------------------------- 1 | type L2ToL1Transaction @entity { 2 | id: ID! 3 | caller: Bytes! # address 4 | destination: Bytes! # address 5 | uniqueId: BigInt! # uint256 6 | batchNumber: BigInt! # uint256 7 | indexInBatch: BigInt! # uint256 8 | arbBlockNum: BigInt! # uint256 9 | ethBlockNum: BigInt! # uint256 10 | timestamp: BigInt! # uint256 11 | callvalue: BigInt! # uint256 12 | data: Bytes! # bytes 13 | } 14 | 15 | enum RetryableState { 16 | Created 17 | Canceled 18 | Redeemed 19 | } 20 | 21 | type Retryable @entity { 22 | "user tx hash that will be created by retryable. Bytes! # bytes32" 23 | id: ID! 24 | 25 | "ticket id of retryable" 26 | retryableTicketID: Bytes! # bytes32 27 | 28 | "tx that redeemed the current retryable. this could have been an autoredeem" 29 | redeemTxId: Bytes # bytes32 30 | 31 | 32 | "this value is the creation timestamp plus the lifetime, which can be extended" 33 | timeoutTimestamp: BigInt! # uint256 34 | 35 | status: RetryableState! 36 | } 37 | -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { L2ToL1Transaction as L2ToL1TransactionEvent } from "../generated/ArbSys/ArbSys"; 2 | import { L2ToL1Transaction, Retryable } from "../generated/schema"; 3 | import { 4 | Canceled as CanceledEvent, 5 | LifetimeExtended as LifetimeExtendedEvent, 6 | Redeemed as RedeemedEvent, 7 | TicketCreated as TicketCreatedEvent, 8 | } from "../generated/ArbRetryableTx/ArbRetryableTx"; 9 | import { log } from "@graphprotocol/graph-ts"; 10 | import { RETRYABLE_LIFETIME_SECONDS } from "@arbitrum/subgraph-common/src/helpers"; 11 | 12 | export function handleL2ToL1Transaction(event: L2ToL1TransactionEvent): void { 13 | // TODO: make the uniqueId the actual ID 14 | let entity = new L2ToL1Transaction( 15 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 16 | ); 17 | entity.caller = event.params.caller; 18 | entity.destination = event.params.destination; 19 | entity.uniqueId = event.params.uniqueId; 20 | entity.batchNumber = event.params.batchNumber; 21 | entity.indexInBatch = event.params.indexInBatch; 22 | entity.arbBlockNum = event.params.arbBlockNum; 23 | entity.ethBlockNum = event.params.ethBlockNum; 24 | entity.timestamp = event.params.timestamp; 25 | entity.callvalue = event.params.callvalue; 26 | entity.data = event.params.data; 27 | 28 | // TODO: query for L2 to L1 tx proof 29 | // TODO: don't make this an archive query 30 | // this will either be the proof or null 31 | // if not null, backfill previous ones that were null 32 | 33 | entity.save(); 34 | } 35 | 36 | export function handleCanceled(event: CanceledEvent): void { 37 | let entity = Retryable.load(event.params.userTxHash.toHexString()); 38 | if (!entity) { 39 | log.critical("Missed a retryable ticket somewhere!", []); 40 | throw new Error("No retryable ticket"); 41 | } 42 | entity.status = "Canceled"; 43 | entity.save(); 44 | } 45 | 46 | export function handleLifetimeExtended(event: LifetimeExtendedEvent): void { 47 | let entity = Retryable.load(event.params.userTxHash.toHexString()); 48 | if (!entity) { 49 | log.critical("Missed a retryable ticket somewhere!", []); 50 | throw new Error("No retryable ticket"); 51 | } 52 | entity.timeoutTimestamp = entity.timeoutTimestamp.plus( 53 | event.params.newTimeout 54 | ); 55 | entity.save(); 56 | } 57 | 58 | export function handleRedeemed(event: RedeemedEvent): void { 59 | let entity = Retryable.load(event.params.userTxHash.toHexString()); 60 | if (!entity) { 61 | log.critical("Missed a retryable ticket somewhere!", []); 62 | throw new Error("No retryable ticket"); 63 | } 64 | // TODO: we can compare hash(retryableTicketID, 1) to redeemTxId to infer if this was an auto redeem or not 65 | entity.redeemTxId = event.transaction.hash 66 | entity.status = "Redeemed"; 67 | entity.save(); 68 | } 69 | 70 | export function handleTicketCreated(event: TicketCreatedEvent): void { 71 | let entity = new Retryable(event.params.userTxHash.toHexString()); 72 | 73 | // could query the precompile at `getLifetime()` but we don't need the expensive archive query 74 | entity.timeoutTimestamp = event.block.timestamp.plus( 75 | RETRYABLE_LIFETIME_SECONDS 76 | ); 77 | entity.retryableTicketID = event.transaction.hash; 78 | entity.status = "Created"; 79 | entity.save(); 80 | } 81 | -------------------------------------------------------------------------------- /packages/arbitrum-precompiles/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | description: Subgraph that indexes Arbitrum precompile contracts 3 | schema: 4 | file: ./schema.graphql 5 | dataSources: 6 | - kind: ethereum/contract 7 | name: ArbSys 8 | network: {{ l2Network }} 9 | source: 10 | address: "0x0000000000000000000000000000000000000064" 11 | abi: ArbSys 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.5 15 | language: wasm/assemblyscript 16 | entities: 17 | - L2ToL1Transaction 18 | abis: 19 | - name: ArbSys 20 | file: ./abis/ArbSys.json 21 | eventHandlers: 22 | - event: L2ToL1Transaction(address,indexed address,indexed uint256,indexed uint256,uint256,uint256,uint256,uint256,uint256,bytes) 23 | handler: handleL2ToL1Transaction 24 | file: ./src/mapping.ts 25 | - kind: ethereum/contract 26 | name: ArbRetryableTx 27 | network: {{ l2Network }} 28 | source: 29 | address: "0x000000000000000000000000000000000000006E" 30 | abi: ArbRetryableTx 31 | mapping: 32 | kind: ethereum/events 33 | apiVersion: 0.0.5 34 | language: wasm/assemblyscript 35 | entities: 36 | - Canceled 37 | - LifetimeExtended 38 | - Redeemed 39 | - TicketCreated 40 | abis: 41 | - name: ArbRetryableTx 42 | file: ./abis/ArbRetryableTx.json 43 | eventHandlers: 44 | - event: Canceled(indexed bytes32) 45 | handler: handleCanceled 46 | - event: LifetimeExtended(indexed bytes32,uint256) 47 | handler: handleLifetimeExtended 48 | - event: Redeemed(indexed bytes32) 49 | handler: handleRedeemed 50 | - event: TicketCreated(indexed bytes32) 51 | handler: handleTicketCreated 52 | file: ./src/mapping.ts 53 | -------------------------------------------------------------------------------- /packages/arbitrum-retryables/abis/ArbRetryableTx.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ArbRetryableTx", 4 | "sourceName": "src/precompiles/ArbRetryableTx.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "NoTicketWithID", 9 | "type": "error" 10 | }, 11 | { 12 | "inputs": [], 13 | "name": "NotCallable", 14 | "type": "error" 15 | }, 16 | { 17 | "anonymous": false, 18 | "inputs": [ 19 | { 20 | "indexed": true, 21 | "internalType": "bytes32", 22 | "name": "ticketId", 23 | "type": "bytes32" 24 | } 25 | ], 26 | "name": "Canceled", 27 | "type": "event" 28 | }, 29 | { 30 | "anonymous": false, 31 | "inputs": [ 32 | { 33 | "indexed": true, 34 | "internalType": "bytes32", 35 | "name": "ticketId", 36 | "type": "bytes32" 37 | }, 38 | { 39 | "indexed": false, 40 | "internalType": "uint256", 41 | "name": "newTimeout", 42 | "type": "uint256" 43 | } 44 | ], 45 | "name": "LifetimeExtended", 46 | "type": "event" 47 | }, 48 | { 49 | "anonymous": false, 50 | "inputs": [ 51 | { 52 | "indexed": true, 53 | "internalType": "bytes32", 54 | "name": "ticketId", 55 | "type": "bytes32" 56 | }, 57 | { 58 | "indexed": true, 59 | "internalType": "bytes32", 60 | "name": "retryTxHash", 61 | "type": "bytes32" 62 | }, 63 | { 64 | "indexed": true, 65 | "internalType": "uint64", 66 | "name": "sequenceNum", 67 | "type": "uint64" 68 | }, 69 | { 70 | "indexed": false, 71 | "internalType": "uint64", 72 | "name": "donatedGas", 73 | "type": "uint64" 74 | }, 75 | { 76 | "indexed": false, 77 | "internalType": "address", 78 | "name": "gasDonor", 79 | "type": "address" 80 | }, 81 | { 82 | "indexed": false, 83 | "internalType": "uint256", 84 | "name": "maxRefund", 85 | "type": "uint256" 86 | }, 87 | { 88 | "indexed": false, 89 | "internalType": "uint256", 90 | "name": "submissionFeeRefund", 91 | "type": "uint256" 92 | } 93 | ], 94 | "name": "RedeemScheduled", 95 | "type": "event" 96 | }, 97 | { 98 | "anonymous": false, 99 | "inputs": [ 100 | { 101 | "indexed": true, 102 | "internalType": "bytes32", 103 | "name": "userTxHash", 104 | "type": "bytes32" 105 | } 106 | ], 107 | "name": "Redeemed", 108 | "type": "event" 109 | }, 110 | { 111 | "anonymous": false, 112 | "inputs": [ 113 | { 114 | "indexed": true, 115 | "internalType": "bytes32", 116 | "name": "ticketId", 117 | "type": "bytes32" 118 | } 119 | ], 120 | "name": "TicketCreated", 121 | "type": "event" 122 | }, 123 | { 124 | "inputs": [ 125 | { 126 | "internalType": "bytes32", 127 | "name": "ticketId", 128 | "type": "bytes32" 129 | } 130 | ], 131 | "name": "cancel", 132 | "outputs": [], 133 | "stateMutability": "nonpayable", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [ 138 | { 139 | "internalType": "bytes32", 140 | "name": "ticketId", 141 | "type": "bytes32" 142 | } 143 | ], 144 | "name": "getBeneficiary", 145 | "outputs": [ 146 | { 147 | "internalType": "address", 148 | "name": "", 149 | "type": "address" 150 | } 151 | ], 152 | "stateMutability": "view", 153 | "type": "function" 154 | }, 155 | { 156 | "inputs": [], 157 | "name": "getCurrentRedeemer", 158 | "outputs": [ 159 | { 160 | "internalType": "address", 161 | "name": "", 162 | "type": "address" 163 | } 164 | ], 165 | "stateMutability": "view", 166 | "type": "function" 167 | }, 168 | { 169 | "inputs": [], 170 | "name": "getLifetime", 171 | "outputs": [ 172 | { 173 | "internalType": "uint256", 174 | "name": "", 175 | "type": "uint256" 176 | } 177 | ], 178 | "stateMutability": "view", 179 | "type": "function" 180 | }, 181 | { 182 | "inputs": [ 183 | { 184 | "internalType": "bytes32", 185 | "name": "ticketId", 186 | "type": "bytes32" 187 | } 188 | ], 189 | "name": "getTimeout", 190 | "outputs": [ 191 | { 192 | "internalType": "uint256", 193 | "name": "", 194 | "type": "uint256" 195 | } 196 | ], 197 | "stateMutability": "view", 198 | "type": "function" 199 | }, 200 | { 201 | "inputs": [ 202 | { 203 | "internalType": "bytes32", 204 | "name": "ticketId", 205 | "type": "bytes32" 206 | } 207 | ], 208 | "name": "keepalive", 209 | "outputs": [ 210 | { 211 | "internalType": "uint256", 212 | "name": "", 213 | "type": "uint256" 214 | } 215 | ], 216 | "stateMutability": "nonpayable", 217 | "type": "function" 218 | }, 219 | { 220 | "inputs": [ 221 | { 222 | "internalType": "bytes32", 223 | "name": "ticketId", 224 | "type": "bytes32" 225 | } 226 | ], 227 | "name": "redeem", 228 | "outputs": [ 229 | { 230 | "internalType": "bytes32", 231 | "name": "", 232 | "type": "bytes32" 233 | } 234 | ], 235 | "stateMutability": "nonpayable", 236 | "type": "function" 237 | }, 238 | { 239 | "inputs": [ 240 | { 241 | "internalType": "bytes32", 242 | "name": "requestId", 243 | "type": "bytes32" 244 | }, 245 | { 246 | "internalType": "uint256", 247 | "name": "l1BaseFee", 248 | "type": "uint256" 249 | }, 250 | { 251 | "internalType": "uint256", 252 | "name": "deposit", 253 | "type": "uint256" 254 | }, 255 | { 256 | "internalType": "uint256", 257 | "name": "callvalue", 258 | "type": "uint256" 259 | }, 260 | { 261 | "internalType": "uint256", 262 | "name": "gasFeeCap", 263 | "type": "uint256" 264 | }, 265 | { 266 | "internalType": "uint64", 267 | "name": "gasLimit", 268 | "type": "uint64" 269 | }, 270 | { 271 | "internalType": "uint256", 272 | "name": "maxSubmissionFee", 273 | "type": "uint256" 274 | }, 275 | { 276 | "internalType": "address", 277 | "name": "feeRefundAddress", 278 | "type": "address" 279 | }, 280 | { 281 | "internalType": "address", 282 | "name": "beneficiary", 283 | "type": "address" 284 | }, 285 | { 286 | "internalType": "address", 287 | "name": "retryTo", 288 | "type": "address" 289 | }, 290 | { 291 | "internalType": "bytes", 292 | "name": "retryData", 293 | "type": "bytes" 294 | } 295 | ], 296 | "name": "submitRetryable", 297 | "outputs": [], 298 | "stateMutability": "nonpayable", 299 | "type": "function" 300 | } 301 | ], 302 | "bytecode": "0x", 303 | "deployedBytecode": "0x", 304 | "linkReferences": {}, 305 | "deployedLinkReferences": {} 306 | } 307 | -------------------------------------------------------------------------------- /packages/arbitrum-retryables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrum-retryables", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:arb && graph codegen", 7 | "build": "yarn prepare:arb && graph build", 8 | "postinstall": "yarn codegen", 9 | 10 | "prepare:arb": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nitro-mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 11 | "prepare:nova": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nova.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 12 | "prepare:goerli": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 13 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 14 | 15 | "deploy:arb": "yarn build && yarn prepare:arb && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-retryables", 16 | "deploy:nova": "yarn build && yarn prepare:nova && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-retryables-nova", 17 | "deploy:goerli": "yarn build && yarn prepare:goerli && graph deploy --node https://api.thegraph.com/deploy/ gvladika/arbitrum-retryables-goerli", 18 | "deploy:sepolia": "yarn build && yarn prepare:sepolia && graph deploy --node https://api.thegraph.com/deploy/ fionnachan/arbitrum-retryables-sepolia" 19 | }, 20 | "dependencies": { 21 | "@arbitrum/subgraph-common": "0.0.1", 22 | "@graphprotocol/graph-ts": "^0.32.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/arbitrum-retryables/schema.graphql: -------------------------------------------------------------------------------- 1 | enum RetryableState { 2 | Created 3 | Canceled 4 | Redeemed 5 | RedeemFailed 6 | } 7 | 8 | type Retryable @entity { 9 | "ticket id of retryable" 10 | id: ID! 11 | 12 | "ticket's status" 13 | status: RetryableState! 14 | 15 | "tx that redeemed the current retryable. this could have been an autoredeem" 16 | retryTxHash: Bytes # bytes32 17 | "this value is the creation timestamp plus the lifetime, which can be extended" 18 | timeoutTimestamp: BigInt! # uint256 19 | "timestamp at which ticket was created" 20 | createdAtTimestamp: BigInt! # uint256 21 | "block number at which ticket was created" 22 | createdAtBlockNumber: BigInt! # uint256 23 | "tx at which ticket was created" 24 | createdAtTxHash: Bytes! # uint256 25 | "timestamp at which ticket was successfully redeemed" 26 | redeemedAtTimestamp: BigInt # uint256 27 | "true if 1st reedem was successful" 28 | isAutoRedeemed: Boolean 29 | 30 | "retryable execution params from event" 31 | sequenceNum: BigInt 32 | donatedGas: BigInt 33 | gasDonor: Bytes #Address 34 | maxRefund: BigInt 35 | submissionFeeRefund: BigInt 36 | 37 | "retryable submission params from tx input" 38 | requestId: Bytes 39 | l1BaseFee: BigInt 40 | deposit: BigInt 41 | callvalue: BigInt 42 | gasFeeCap: BigInt 43 | gasLimit: BigInt 44 | maxSubmissionFee: BigInt 45 | feeRefundAddress: String 46 | beneficiary: String 47 | retryTo: String 48 | retryData: Bytes 49 | } 50 | 51 | type TotalRetryableStats @entity { 52 | id: ID! 53 | 54 | totalCreated: BigInt! 55 | autoRedeemed: BigInt! 56 | successfullyRedeemed: BigInt! 57 | failedToRedeem: BigInt! 58 | canceled: BigInt! 59 | } 60 | -------------------------------------------------------------------------------- /packages/arbitrum-retryables/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Retryable, TotalRetryableStats } from "../generated/schema"; 2 | import { 3 | Canceled, 4 | LifetimeExtended, 5 | RedeemScheduled, 6 | TicketCreated, 7 | ArbRetryableTx as ArbRetryableTxContract, 8 | } from "../generated/ArbRetryableTx/ArbRetryableTx"; 9 | import { BigInt, Bytes, ethereum, log } from "@graphprotocol/graph-ts"; 10 | import { RETRYABLE_LIFETIME_SECONDS } from "@arbitrum/subgraph-common/src/helpers"; 11 | 12 | /** 13 | * Create retryable entity when ticket is first created 14 | * @param event 15 | */ 16 | export function handleTicketCreated(event: TicketCreated): void { 17 | let ticketId = event.params.ticketId; 18 | let entity = new Retryable(ticketId.toHexString()); 19 | entity.status = "Created"; 20 | entity.timeoutTimestamp = event.block.timestamp.plus(RETRYABLE_LIFETIME_SECONDS); 21 | entity.createdAtTimestamp = event.block.timestamp; 22 | entity.createdAtBlockNumber = event.block.number; 23 | entity.createdAtTxHash = event.transaction.hash; 24 | entity.save(); 25 | 26 | // decode and save retyrable submission param 27 | decodeRetryableParamsFromTxInput(entity, event.transaction.input); 28 | 29 | const stats = getOrCreateTotalRetryableStats(); 30 | stats.totalCreated = stats.totalCreated.plus(BigInt.fromI32(1)); 31 | stats.save(); 32 | } 33 | 34 | /** 35 | * Update retryable's status to canceled 36 | * @param event 37 | */ 38 | export function handleCanceled(event: Canceled): void { 39 | let ticketId = event.params.ticketId; 40 | let entity = Retryable.load(ticketId.toHexString()); 41 | if (!entity) { 42 | log.critical("Missed a retryable ticket somewhere!", []); 43 | throw new Error("No retryable ticket"); 44 | } 45 | entity.status = "Canceled"; 46 | entity.save(); 47 | 48 | const stats = getOrCreateTotalRetryableStats(); 49 | stats.canceled = stats.canceled.plus(BigInt.fromI32(1)); 50 | stats.save(); 51 | } 52 | 53 | /** 54 | * Extend retryable's timeout 55 | * @param event 56 | */ 57 | export function handleLifetimeExtended(event: LifetimeExtended): void { 58 | let ticketId = event.params.ticketId; 59 | let entity = Retryable.load(ticketId.toHexString()); 60 | if (!entity) { 61 | log.critical("Missed a retryable ticket somewhere!", []); 62 | throw new Error("No retryable ticket"); 63 | } 64 | entity.timeoutTimestamp = entity.timeoutTimestamp.plus(event.params.newTimeout); 65 | entity.save(); 66 | } 67 | 68 | /** 69 | * Deduce if redeem was successful by doing contract call - if redeem was successful call will fail 70 | * because ticket has been deleted from queue. 71 | * @param event 72 | */ 73 | export function handleRedeemScheduled(event: RedeemScheduled): void { 74 | let ticketId = event.params.ticketId; 75 | let entity = Retryable.load(ticketId.toHexString()); 76 | if (!entity) { 77 | log.critical("Missed a retryable ticket somewhere!", []); 78 | throw new Error("No retryable ticket"); 79 | } 80 | 81 | const stats = getOrCreateTotalRetryableStats(); 82 | 83 | const prevStatus = entity.status; 84 | const redeemSuccessful = isRedeemSuccessful(ArbRetryableTxContract.bind(event.address), ticketId); 85 | if (redeemSuccessful) { 86 | if (prevStatus == "Created") { 87 | entity.isAutoRedeemed = true; 88 | stats.autoRedeemed = stats.autoRedeemed.plus(BigInt.fromI32(1)); 89 | } 90 | if (prevStatus == "RedeemFailed") { 91 | stats.failedToRedeem = stats.failedToRedeem.minus(BigInt.fromI32(1)); 92 | } 93 | entity.status = "Redeemed"; 94 | entity.redeemedAtTimestamp = event.block.timestamp; 95 | stats.successfullyRedeemed = stats.successfullyRedeemed.plus(BigInt.fromI32(1)); 96 | } else { 97 | if (prevStatus != "RedeemFailed") { 98 | stats.failedToRedeem = stats.failedToRedeem.plus(BigInt.fromI32(1)); 99 | } 100 | entity.isAutoRedeemed = false; 101 | entity.status = "RedeemFailed"; 102 | } 103 | 104 | entity.retryTxHash = event.params.retryTxHash; 105 | entity.sequenceNum = event.params.sequenceNum; 106 | entity.donatedGas = event.params.donatedGas; 107 | entity.gasDonor = event.params.gasDonor; 108 | entity.maxRefund = event.params.maxRefund; 109 | entity.submissionFeeRefund = event.params.submissionFeeRefund; 110 | entity.save(); 111 | stats.save(); 112 | } 113 | 114 | function isRedeemSuccessful(contract: ArbRetryableTxContract, ticketId: Bytes): boolean { 115 | const beneficiaryCall = contract.try_getBeneficiary(ticketId); 116 | return beneficiaryCall.reverted; 117 | } 118 | 119 | function getOrCreateTotalRetryableStats(): TotalRetryableStats { 120 | let stats = TotalRetryableStats.load("NitroStats"); 121 | if (stats != null) { 122 | return stats as TotalRetryableStats; 123 | } 124 | 125 | stats = new TotalRetryableStats("NitroStats"); 126 | stats.totalCreated = BigInt.fromI32(0); 127 | stats.autoRedeemed = BigInt.fromI32(0); 128 | stats.successfullyRedeemed = BigInt.fromI32(0); 129 | stats.failedToRedeem = BigInt.fromI32(0); 130 | stats.canceled = BigInt.fromI32(0); 131 | stats.save(); 132 | 133 | return stats; 134 | } 135 | 136 | function decodeRetryableParamsFromTxInput(entity: Retryable, calldata: Bytes): void { 137 | // take out function sig and add tuple offset as prefix 138 | const noSigCalldataStr = calldata.toHexString().slice(10); 139 | const prefixNoSigCalldataStr = 140 | "0x0000000000000000000000000000000000000000000000000000000000000020" + noSigCalldataStr; 141 | const toDecode = Bytes.fromByteArray(Bytes.fromHexString(prefixNoSigCalldataStr)); 142 | 143 | const decoded = ethereum.decode( 144 | "(bytes32,uint256,uint256,uint256,uint256,uint64,uint256,address,address,address,bytes)", 145 | toDecode 146 | ); 147 | 148 | if (decoded) { 149 | const parsedArray = decoded.toTuple(); 150 | 151 | entity.requestId = parsedArray[0].toBytes(); 152 | entity.l1BaseFee = parsedArray[1].toBigInt(); 153 | entity.deposit = parsedArray[2].toBigInt(); 154 | entity.callvalue = parsedArray[3].toBigInt(); 155 | entity.gasFeeCap = parsedArray[4].toBigInt(); 156 | entity.gasLimit = parsedArray[5].toBigInt(); 157 | entity.maxSubmissionFee = parsedArray[6].toBigInt(); 158 | entity.feeRefundAddress = parsedArray[7].toAddress().toHexString(); 159 | entity.beneficiary = parsedArray[8].toAddress().toHexString(); 160 | entity.retryTo = parsedArray[9].toAddress().toHexString(); 161 | entity.retryData = parsedArray[10].toBytes(); 162 | entity.save(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /packages/arbitrum-retryables/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.4 2 | description: Subgraph that indexes Arbitrum precompile contracts 3 | schema: 4 | file: schema.graphql 5 | dataSources: 6 | - kind: ethereum/contract 7 | name: ArbRetryableTx 8 | network: {{ l2Network }} 9 | source: 10 | address: "0x000000000000000000000000000000000000006E" 11 | abi: ArbRetryableTx 12 | startBlock: {{ nitroGenesisBlockNum }} 13 | mapping: 14 | kind: ethereum/events 15 | apiVersion: 0.0.5 16 | language: wasm/assemblyscript 17 | entities: 18 | - Canceled 19 | - LifetimeExtended 20 | - RedeemScheduled 21 | - TicketCreated 22 | abis: 23 | - name: ArbRetryableTx 24 | file: ./abis/ArbRetryableTx.json 25 | eventHandlers: 26 | - event: Canceled(indexed bytes32) 27 | handler: handleCanceled 28 | - event: LifetimeExtended(indexed bytes32,uint256) 29 | handler: handleLifetimeExtended 30 | - event: TicketCreated(indexed bytes32) 31 | handler: handleTicketCreated 32 | - event: RedeemScheduled(indexed bytes32,indexed bytes32,indexed uint64,uint64,address,uint256,uint256) 33 | handler: handleRedeemScheduled 34 | file: ./src/mapping.ts 35 | -------------------------------------------------------------------------------- /packages/cctp/l1Subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.5 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum 6 | name: USDCMessageTransmitter 7 | network: "{{ l1Network }}" 8 | source: 9 | address: "{{ L1USDCMessageTransmitter }}" 10 | abi: USDCMessageTransmitter 11 | startBlock: {{ L1USDCMessageTransmitterStartBlock }} 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.7 15 | language: wasm/assemblyscript 16 | entities: 17 | - MessageSent 18 | - MessageReceived 19 | abis: 20 | - name: USDCMessageTransmitter 21 | file: ./abis/USDCMessageTransmitter.json 22 | eventHandlers: 23 | - event: MessageReceived(indexed address,uint32,indexed uint64,bytes32,bytes) 24 | handler: handleMessageReceivedL1 25 | receipt: true 26 | - event: MessageSent(bytes) 27 | handler: handleMessageSentL1 28 | receipt: true 29 | file: ./src/usdc-message-transmitter.ts 30 | -------------------------------------------------------------------------------- /packages/cctp/l2Subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.5 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum 6 | name: USDCMessageTransmitter 7 | network: "{{ l2Network }}" 8 | source: 9 | address: "{{ L2USDCMessageTransmitter }}" 10 | abi: USDCMessageTransmitter 11 | startBlock: {{ L2USDCMessageTransmitterStartBlock }} 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.7 15 | language: wasm/assemblyscript 16 | entities: 17 | - MessageSent 18 | - MessageReceived 19 | abis: 20 | - name: USDCMessageTransmitter 21 | file: ./abis/USDCMessageTransmitter.json 22 | eventHandlers: 23 | - event: MessageReceived(indexed address,uint32,indexed uint64,bytes32,bytes) 24 | handler: handleMessageReceivedL2 25 | receipt: true 26 | - event: MessageSent(bytes) 27 | handler: handleMessageSentL2 28 | receipt: true 29 | file: ./src/usdc-message-transmitter.ts 30 | -------------------------------------------------------------------------------- /packages/cctp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cctp", 3 | "license": "Apache-2.0", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "codegen:mainnet": "yarn prepare:mainnet && graph codegen", 7 | "codegen:sepolia": "yarn prepare:sepolia && graph codegen", 8 | "codegen:arbOne": "yarn prepare:arbOne && graph codegen", 9 | "codegen:arbSepolia": "yarn prepare:arbSepolia && graph codegen", 10 | "postinstall": "yarn codegen:mainnet", 11 | "prepare:mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/l1Subgraph.template.yaml | tail -n +2 > subgraph.yaml", 12 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/l1Subgraph.template.yaml | tail -n +2 > subgraph.yaml", 13 | "prepare:arbOne": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/l2Subgraph.template.yaml | tail -n +2 > subgraph.yaml", 14 | "prepare:arbSepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/l2Subgraph.template.yaml | tail -n +2 > subgraph.yaml", 15 | "deploy:mainnet": "graph build && yarn prepare:mainnet && graph deploy --studio cctp-mainnet", 16 | "deploy:sepolia": "graph build && yarn prepare:sepolia && graph deploy --studio cctp-sepolia", 17 | "deploy:arbOne": "graph build && yarn prepare:arbOne && graph deploy --studio cctp-arb-one", 18 | "deploy:arbSepolia": "graph build && yarn prepare:arbSepolia && graph deploy --studio cctp-arb-sepolia", 19 | "test": "graph test" 20 | }, 21 | "dependencies": { 22 | "@graphprotocol/graph-ts": "^0.32.0" 23 | }, 24 | "devDependencies": { 25 | "matchstick-as": "0.5.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/cctp/schema.graphql: -------------------------------------------------------------------------------- 1 | type MessageReceived @entity(immutable: true) { 2 | id: Bytes! 3 | caller: Bytes! # address 4 | sourceDomain: BigInt! # uint32 5 | nonce: BigInt! # uint64 6 | sender: Bytes! # address 7 | recipient: Bytes! # address 8 | messageBody: Bytes! # bytes 9 | blockNumber: BigInt! 10 | blockTimestamp: BigInt! 11 | transactionHash: Bytes! 12 | } 13 | 14 | type MessageSent @entity(immutable: false) { 15 | id: Bytes! 16 | message: Bytes! # bytes 17 | blockNumber: BigInt! 18 | blockTimestamp: BigInt! 19 | transactionHash: Bytes! 20 | sender: Bytes! # address 21 | recipient: Bytes! # address 22 | attestationHash: Bytes! 23 | nonce: BigInt! # uint64 24 | sourceDomain: BigInt! # uint32 25 | amount: BigInt # uint256 26 | } 27 | -------------------------------------------------------------------------------- /packages/cctp/src/usdc-message-transmitter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | BigInt, 4 | Bytes, 5 | crypto, 6 | ethereum, 7 | } from "@graphprotocol/graph-ts"; 8 | import { 9 | MessageReceived as MessageReceivedEvent, 10 | MessageSent as MessageSentEvent, 11 | } from "../generated/USDCMessageTransmitter/USDCMessageTransmitter"; 12 | import { MessageReceived, MessageSent } from "../generated/schema"; 13 | import { log } from "matchstick-as"; 14 | 15 | function leftPadBytes(data: Bytes, length: number): Bytes { 16 | const completeData = new Bytes(length as i32); 17 | const zeroBytesToFillPrefix = completeData.length - data.length; 18 | for (let i = 0; i < completeData.length; i++) { 19 | if (i < zeroBytesToFillPrefix) { 20 | completeData[i] = 0; 21 | } else { 22 | completeData[i] = data[i - zeroBytesToFillPrefix]; 23 | } 24 | } 25 | return completeData; 26 | } 27 | 28 | function getIdFromMessage(sourceDomain: BigInt, noncePadded: Bytes): Bytes { 29 | return Bytes.fromHexString( 30 | `0${sourceDomain.toString()}${noncePadded.toHexString()}` 31 | ); 32 | } 33 | 34 | function getAddressFromBytes32(bytes: Bytes): Bytes { 35 | assert( 36 | bytes.length === 32, 37 | `getAddressFromBytes32: Address bytes length is incorrect (${bytes.length})` 38 | ); 39 | const slicedBytes = bytes.slice(12); 40 | return Address.fromUint8Array(slicedBytes); 41 | } 42 | 43 | function decodeMessageBodyData(messageBody: Bytes): Array | null { 44 | // Remove the first 8 characters 45 | const messageBodyData = ethereum.decode( 46 | // (usdcContract, recipient, amount, sender) 47 | "(bytes32,bytes32,uint64,bytes32)", 48 | messageBody 49 | ); 50 | 51 | if (!messageBodyData) { 52 | return null; 53 | } 54 | const decodedMessageBodyDataTuple = messageBodyData.toTuple(); 55 | const recipient = decodedMessageBodyDataTuple[1].toBytes(); 56 | const amount = decodedMessageBodyDataTuple[2].toBigInt(); 57 | const sender = decodedMessageBodyDataTuple[3].toBytes(); 58 | 59 | return [recipient, Bytes.fromByteArray(Bytes.fromBigInt(amount)), sender]; 60 | } 61 | 62 | export enum ChainDomain { 63 | Mainnet = 0, 64 | Arbitrum = 3, 65 | } 66 | 67 | function handleMessageReceived( 68 | event: MessageReceivedEvent, 69 | expectedSourceDomain: ChainDomain 70 | ): void { 71 | // Only index messages from expected source domain 72 | if ( 73 | event.params.sourceDomain.notEqual(BigInt.fromI32(expectedSourceDomain)) 74 | ) { 75 | log.warning( 76 | "[handleMessageReceived]: sourceDomain {} doesn't correspond to the expected source domain {}", 77 | [event.params.sourceDomain.toString(), expectedSourceDomain.toString()] 78 | ); 79 | return; 80 | } 81 | 82 | const nonce = event.params.nonce; 83 | const sourceDomain = event.params.sourceDomain; 84 | 85 | const noncePadded = leftPadBytes( 86 | Bytes.fromHexString("0x".concat(nonce.toHex().slice(2).padStart(8, "0"))), 87 | 32 88 | ); 89 | const id = getIdFromMessage(sourceDomain, noncePadded); 90 | 91 | let entity = new MessageReceived(id); 92 | const messageBodyWithoutSignature = Bytes.fromUint8Array( 93 | event.params.messageBody.slice(4, event.params.messageBody.length) 94 | ); 95 | const decodedMessageBodyData = decodeMessageBodyData( 96 | messageBodyWithoutSignature 97 | ); 98 | if (!decodedMessageBodyData) { 99 | log.error("messageBodyData doesn't exist", []); 100 | return; 101 | } 102 | 103 | const recipient = decodedMessageBodyData[0]; 104 | const sender = decodedMessageBodyData[2]; 105 | 106 | entity.caller = event.params.caller; 107 | entity.sourceDomain = event.params.sourceDomain; 108 | entity.nonce = event.params.nonce; 109 | entity.sender = getAddressFromBytes32(sender); 110 | entity.recipient = getAddressFromBytes32(recipient); 111 | entity.messageBody = event.params.messageBody; 112 | 113 | entity.blockNumber = event.block.number; 114 | entity.blockTimestamp = event.block.timestamp; 115 | entity.transactionHash = event.transaction.hash; 116 | 117 | entity.save(); 118 | } 119 | 120 | function handleMessageSent( 121 | event: MessageSentEvent, 122 | expectedDestinationDomain: ChainDomain 123 | ): void { 124 | // message is encoded with encodePacked, we need to pad non-bytes parameter (uint32, uint64) to 32 bytes (= 256 bits) 125 | const message = event.params.message; 126 | const versionSlice = message.slice(0, 4); 127 | const sourceDomainSlice = message.slice(4, 8); 128 | const destinationDomainSlice = message.slice(8, 12); 129 | const nonceSlice = message.subarray(12, 20); 130 | 131 | const versionPadded = leftPadBytes(Bytes.fromUint8Array(versionSlice), 32); 132 | const sourceDomainPadded = leftPadBytes( 133 | Bytes.fromUint8Array(sourceDomainSlice), 134 | 32 135 | ); 136 | const destinationDomainPadded = leftPadBytes( 137 | Bytes.fromUint8Array(destinationDomainSlice), 138 | 32 139 | ); 140 | 141 | const noncePadded = leftPadBytes(Bytes.fromUint8Array(nonceSlice), 32); 142 | const messagePadded = versionPadded 143 | .concat(sourceDomainPadded) 144 | .concat(destinationDomainPadded) 145 | .concat(noncePadded) 146 | .concat(Bytes.fromUint8Array(message.slice(24))); 147 | 148 | // see https://developers.circle.com/stablecoin/docs/cctp-technical-reference#message 149 | const decodedMessageData = ethereum.decode( 150 | // (version, sourceDomain, destinationDomain, nonce, sender, recipient, destinationcaller, messageBody) 151 | "(uint32,uint32,uint32,uint64,bytes32,bytes32,bytes32,bytes128)", 152 | messagePadded 153 | ); 154 | 155 | if (!decodedMessageData) { 156 | log.error("decodedMessageData doesn't exist", []); 157 | return; 158 | } 159 | 160 | const decodedMessageDataTuple = decodedMessageData.toTuple(); 161 | const destinationDomain = decodedMessageDataTuple[2].toBigInt(); 162 | const sourceDomain = decodedMessageDataTuple[1].toBigInt(); 163 | const nonce = decodedMessageDataTuple[3].toBigInt(); 164 | const messageBody = decodedMessageDataTuple[7].toBytes(); 165 | 166 | if (destinationDomain.notEqual(BigInt.fromI32(expectedDestinationDomain))) { 167 | log.warning( 168 | "[handleMessageSent]: destinationDomain {} doesn't correspond to the expected destination domain {}", 169 | [destinationDomain.toString(), expectedDestinationDomain.toString()] 170 | ); 171 | return; 172 | } 173 | 174 | const decodedMessageBodyData = decodeMessageBodyData(messageBody); 175 | if (!decodedMessageBodyData) { 176 | log.error("messageBodyData doesn't exist", []); 177 | return; 178 | } 179 | 180 | const recipient = decodedMessageBodyData[0]; 181 | const amount = BigInt.fromUnsignedBytes(decodedMessageBodyData[1]); 182 | const sender = decodedMessageBodyData[2]; 183 | 184 | const id = getIdFromMessage(sourceDomain, noncePadded); 185 | const entityFromStore = MessageSent.load(id); 186 | 187 | // Multiple MessageSent might have the same id when replaced with `replaceMessage` 188 | // We're only interested in the most recent one 189 | // Events might not arrive in order, we need to compare timestamp to get the most recent one 190 | if (entityFromStore) { 191 | // If the new MessageSent is more recent, override the one in store 192 | // If the MessageEvent in the store is the most recent, skip 193 | if (entityFromStore.blockTimestamp.gt(event.block.timestamp)) { 194 | return; 195 | } 196 | } 197 | const entity = new MessageSent(id); 198 | entity.message = event.params.message; 199 | entity.blockNumber = event.block.number; 200 | entity.blockTimestamp = event.block.timestamp; 201 | entity.transactionHash = event.transaction.hash; 202 | entity.sender = getAddressFromBytes32(sender); 203 | entity.recipient = getAddressFromBytes32(recipient); 204 | entity.attestationHash = Bytes.fromByteArray( 205 | crypto.keccak256(event.params.message) 206 | ); 207 | entity.sourceDomain = sourceDomain; 208 | entity.nonce = nonce; 209 | entity.amount = amount; 210 | entity.save(); 211 | } 212 | 213 | export function handleMessageReceivedL1(event: MessageReceivedEvent): void { 214 | handleMessageReceived(event, ChainDomain.Arbitrum); 215 | } 216 | 217 | export function handleMessageSentL1(event: MessageSentEvent): void { 218 | handleMessageSent(event, ChainDomain.Arbitrum); 219 | } 220 | 221 | export function handleMessageReceivedL2(event: MessageReceivedEvent): void { 222 | handleMessageReceived(event, ChainDomain.Mainnet); 223 | } 224 | 225 | export function handleMessageSentL2(event: MessageSentEvent): void { 226 | handleMessageSent(event, ChainDomain.Mainnet); 227 | } 228 | -------------------------------------------------------------------------------- /packages/cctp/tests/.latest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.0", 3 | "timestamp": 1730998364172 4 | } -------------------------------------------------------------------------------- /packages/cctp/tests/usdc-message-transmitter-utils.ts: -------------------------------------------------------------------------------- 1 | import { newMockEvent } from "matchstick-as"; 2 | import { ethereum, Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; 3 | import { 4 | MessageReceived, 5 | MessageSent, 6 | } from "../generated/USDCMessageTransmitter/USDCMessageTransmitter"; 7 | 8 | export function createMessageReceivedEvent( 9 | caller: Address, 10 | sourceDomain: BigInt, 11 | nonce: BigInt, 12 | sender: Bytes, 13 | messageBody: Bytes 14 | ): MessageReceived { 15 | let messageReceivedEvent = changetype(newMockEvent()); 16 | 17 | messageReceivedEvent.parameters = new Array(); 18 | 19 | messageReceivedEvent.parameters.push( 20 | new ethereum.EventParam("caller", ethereum.Value.fromAddress(caller)) 21 | ); 22 | messageReceivedEvent.parameters.push( 23 | new ethereum.EventParam( 24 | "sourceDomain", 25 | ethereum.Value.fromUnsignedBigInt(sourceDomain) 26 | ) 27 | ); 28 | messageReceivedEvent.parameters.push( 29 | new ethereum.EventParam("nonce", ethereum.Value.fromUnsignedBigInt(nonce)) 30 | ); 31 | messageReceivedEvent.parameters.push( 32 | new ethereum.EventParam("sender", ethereum.Value.fromFixedBytes(sender)) 33 | ); 34 | messageReceivedEvent.parameters.push( 35 | new ethereum.EventParam( 36 | "messageBody", 37 | ethereum.Value.fromBytes(messageBody) 38 | ) 39 | ); 40 | 41 | return messageReceivedEvent; 42 | } 43 | 44 | export function createMessageSentEvent(message: Bytes): MessageSent { 45 | let messageSentEvent = changetype(newMockEvent()); 46 | 47 | messageSentEvent.parameters = new Array(); 48 | 49 | messageSentEvent.parameters.push( 50 | new ethereum.EventParam("message", ethereum.Value.fromBytes(message)) 51 | ); 52 | 53 | return messageSentEvent; 54 | } 55 | -------------------------------------------------------------------------------- /packages/cctp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", 3 | "include": ["src", "tests"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/layer1-token-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layer1-token-gateway", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:mainnet && graph codegen", 7 | "build": "yarn prepare:mainnet && graph build", 8 | "postinstall": "yarn codegen", 9 | 10 | "prepare:mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 11 | "prepare:rinkeby": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/rinkeby.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 12 | 13 | "deploy:mainnet": "yarn build && yarn prepare:mainnet && graph deploy --node https://api.thegraph.com/deploy/ fredlacs/layer1-token-gateway", 14 | "deploy:rinkeby": "yarn build && yarn prepare:rinkeby && graph deploy --node https://api.thegraph.com/deploy/ fredlacs/layer1-token-gateway-rinkeby" 15 | }, 16 | "dependencies": { 17 | "@arbitrum/subgraph-common": "^0.0.1", 18 | "@graphprotocol/graph-ts": "^0.32.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/layer1-token-gateway/schema.graphql: -------------------------------------------------------------------------------- 1 | type DefaultGatewayUpdated @entity { 2 | id: ID! 3 | newDefaultGateway: Bytes! # address 4 | } 5 | 6 | type GatewaySet @entity { 7 | id: ID! 8 | l1Token: Bytes! # address 9 | gateway: Bytes! # address 10 | } 11 | 12 | type TransferRouted @entity { 13 | id: ID! 14 | token: Bytes! # address 15 | _userFrom: Bytes! # address 16 | _userTo: Bytes! # address 17 | gateway: Bytes! # address 18 | } 19 | 20 | type TxToL2 @entity { 21 | id: ID! 22 | _from: Bytes! # address 23 | _to: Bytes! # address 24 | _seqNum: BigInt! # uint256 25 | _data: Bytes! # bytes 26 | } 27 | 28 | type WhitelistSourceUpdated @entity { 29 | id: ID! 30 | newSource: Bytes! # address 31 | } 32 | -------------------------------------------------------------------------------- /packages/layer1-token-gateway/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultGatewayUpdated as DefaultGatewayUpdatedEvent, 3 | GatewaySet as GatewaySetEvent, 4 | TransferRouted as TransferRoutedEvent, 5 | TxToL2 as TxToL2Event, 6 | WhitelistSourceUpdated as WhitelistSourceUpdatedEvent 7 | } from "../generated/L1GatewayRouter/L1GatewayRouter" 8 | import { 9 | DefaultGatewayUpdated, 10 | GatewaySet, 11 | TransferRouted, 12 | TxToL2, 13 | WhitelistSourceUpdated 14 | } from "../generated/schema" 15 | 16 | export function handleDefaultGatewayUpdated( 17 | event: DefaultGatewayUpdatedEvent 18 | ): void { 19 | let entity = new DefaultGatewayUpdated( 20 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 21 | ) 22 | entity.newDefaultGateway = event.params.newDefaultGateway 23 | entity.save() 24 | } 25 | 26 | export function handleGatewaySet(event: GatewaySetEvent): void { 27 | let entity = new GatewaySet( 28 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 29 | ) 30 | entity.l1Token = event.params.l1Token 31 | entity.gateway = event.params.gateway 32 | entity.save() 33 | } 34 | 35 | export function handleTransferRouted(event: TransferRoutedEvent): void { 36 | let entity = new TransferRouted( 37 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 38 | ) 39 | entity.token = event.params.token 40 | entity._userFrom = event.params._userFrom 41 | entity._userTo = event.params._userTo 42 | entity.gateway = event.params.gateway 43 | entity.save() 44 | } 45 | 46 | export function handleTxToL2(event: TxToL2Event): void { 47 | let entity = new TxToL2( 48 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 49 | ) 50 | entity._from = event.params._from 51 | entity._to = event.params._to 52 | entity._seqNum = event.params._seqNum 53 | entity._data = event.params._data 54 | entity.save() 55 | } 56 | 57 | export function handleWhitelistSourceUpdated( 58 | event: WhitelistSourceUpdatedEvent 59 | ): void { 60 | let entity = new WhitelistSourceUpdated( 61 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 62 | ) 63 | entity.newSource = event.params.newSource 64 | entity.save() 65 | } 66 | -------------------------------------------------------------------------------- /packages/layer1-token-gateway/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum/contract 6 | name: L1GatewayRouter 7 | network: mainnet 8 | source: 9 | address: "{{ l1GatewayRouter }}" 10 | abi: L1GatewayRouter 11 | mapping: 12 | kind: ethereum/events 13 | apiVersion: 0.0.5 14 | language: wasm/assemblyscript 15 | entities: 16 | - DefaultGatewayUpdated 17 | - GatewaySet 18 | - TransferRouted 19 | - TxToL2 20 | - WhitelistSourceUpdated 21 | abis: 22 | - name: L1GatewayRouter 23 | file: ./abis/L1GatewayRouter.json 24 | eventHandlers: 25 | - event: DefaultGatewayUpdated(address) 26 | handler: handleDefaultGatewayUpdated 27 | - event: GatewaySet(indexed address,indexed address) 28 | handler: handleGatewaySet 29 | - event: TransferRouted(indexed address,indexed address,indexed address,address) 30 | handler: handleTransferRouted 31 | - event: TxToL2(indexed address,indexed address,indexed uint256,bytes) 32 | handler: handleTxToL2 33 | - event: WhitelistSourceUpdated(address) 34 | handler: handleWhitelistSourceUpdated 35 | file: ./src/mapping.ts 36 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/abis/ClassicArbRetryableTx.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "bytes32", 8 | "name": "userTxHash", 9 | "type": "bytes32" 10 | } 11 | ], 12 | "name": "Canceled", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "bytes32", 21 | "name": "userTxHash", 22 | "type": "bytes32" 23 | }, 24 | { 25 | "indexed": false, 26 | "internalType": "uint256", 27 | "name": "newTimeout", 28 | "type": "uint256" 29 | } 30 | ], 31 | "name": "LifetimeExtended", 32 | "type": "event" 33 | }, 34 | { 35 | "anonymous": false, 36 | "inputs": [ 37 | { 38 | "indexed": true, 39 | "internalType": "bytes32", 40 | "name": "userTxHash", 41 | "type": "bytes32" 42 | } 43 | ], 44 | "name": "Redeemed", 45 | "type": "event" 46 | }, 47 | { 48 | "anonymous": false, 49 | "inputs": [ 50 | { 51 | "indexed": true, 52 | "internalType": "bytes32", 53 | "name": "userTxHash", 54 | "type": "bytes32" 55 | } 56 | ], 57 | "name": "TicketCreated", 58 | "type": "event" 59 | }, 60 | { 61 | "inputs": [ 62 | { 63 | "internalType": "bytes32", 64 | "name": "userTxHash", 65 | "type": "bytes32" 66 | } 67 | ], 68 | "name": "cancel", 69 | "outputs": [], 70 | "stateMutability": "nonpayable", 71 | "type": "function" 72 | }, 73 | { 74 | "inputs": [ 75 | { 76 | "internalType": "bytes32", 77 | "name": "userTxHash", 78 | "type": "bytes32" 79 | } 80 | ], 81 | "name": "getBeneficiary", 82 | "outputs": [ 83 | { 84 | "internalType": "address", 85 | "name": "", 86 | "type": "address" 87 | } 88 | ], 89 | "stateMutability": "view", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [ 94 | { 95 | "internalType": "bytes32", 96 | "name": "userTxHash", 97 | "type": "bytes32" 98 | } 99 | ], 100 | "name": "getKeepalivePrice", 101 | "outputs": [ 102 | { 103 | "internalType": "uint256", 104 | "name": "", 105 | "type": "uint256" 106 | }, 107 | { 108 | "internalType": "uint256", 109 | "name": "", 110 | "type": "uint256" 111 | } 112 | ], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [], 118 | "name": "getLifetime", 119 | "outputs": [ 120 | { 121 | "internalType": "uint256", 122 | "name": "", 123 | "type": "uint256" 124 | } 125 | ], 126 | "stateMutability": "view", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [ 131 | { 132 | "internalType": "uint256", 133 | "name": "calldataSize", 134 | "type": "uint256" 135 | } 136 | ], 137 | "name": "getSubmissionPrice", 138 | "outputs": [ 139 | { 140 | "internalType": "uint256", 141 | "name": "", 142 | "type": "uint256" 143 | }, 144 | { 145 | "internalType": "uint256", 146 | "name": "", 147 | "type": "uint256" 148 | } 149 | ], 150 | "stateMutability": "view", 151 | "type": "function" 152 | }, 153 | { 154 | "inputs": [ 155 | { 156 | "internalType": "bytes32", 157 | "name": "userTxHash", 158 | "type": "bytes32" 159 | } 160 | ], 161 | "name": "getTimeout", 162 | "outputs": [ 163 | { 164 | "internalType": "uint256", 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "stateMutability": "view", 170 | "type": "function" 171 | }, 172 | { 173 | "inputs": [ 174 | { 175 | "internalType": "bytes32", 176 | "name": "userTxHash", 177 | "type": "bytes32" 178 | } 179 | ], 180 | "name": "keepalive", 181 | "outputs": [ 182 | { 183 | "internalType": "uint256", 184 | "name": "", 185 | "type": "uint256" 186 | } 187 | ], 188 | "stateMutability": "payable", 189 | "type": "function" 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "bytes32", 195 | "name": "userTxHash", 196 | "type": "bytes32" 197 | } 198 | ], 199 | "name": "redeem", 200 | "outputs": [], 201 | "stateMutability": "nonpayable", 202 | "type": "function" 203 | } 204 | ] 205 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/abis/ClassicArbSys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "address", 8 | "name": "caller", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "destination", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "uint256", 20 | "name": "uniqueId", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": true, 25 | "internalType": "uint256", 26 | "name": "batchNumber", 27 | "type": "uint256" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "uint256", 32 | "name": "indexInBatch", 33 | "type": "uint256" 34 | }, 35 | { 36 | "indexed": false, 37 | "internalType": "uint256", 38 | "name": "arbBlockNum", 39 | "type": "uint256" 40 | }, 41 | { 42 | "indexed": false, 43 | "internalType": "uint256", 44 | "name": "ethBlockNum", 45 | "type": "uint256" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "timestamp", 51 | "type": "uint256" 52 | }, 53 | { 54 | "indexed": false, 55 | "internalType": "uint256", 56 | "name": "callvalue", 57 | "type": "uint256" 58 | }, 59 | { 60 | "indexed": false, 61 | "internalType": "bytes", 62 | "name": "data", 63 | "type": "bytes" 64 | } 65 | ], 66 | "name": "L2ToL1Transaction", 67 | "type": "event" 68 | }, 69 | { 70 | "inputs": [], 71 | "name": "arbBlockNumber", 72 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 73 | "stateMutability": "view", 74 | "type": "function" 75 | }, 76 | { 77 | "inputs": [], 78 | "name": "arbChainID", 79 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 80 | "stateMutability": "view", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [], 85 | "name": "arbOSVersion", 86 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 87 | "stateMutability": "pure", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [ 92 | { "internalType": "address", "name": "account", "type": "address" }, 93 | { "internalType": "uint256", "name": "index", "type": "uint256" } 94 | ], 95 | "name": "getStorageAt", 96 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 97 | "stateMutability": "view", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [ 102 | { "internalType": "address", "name": "account", "type": "address" } 103 | ], 104 | "name": "getTransactionCount", 105 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 106 | "stateMutability": "view", 107 | "type": "function" 108 | }, 109 | { 110 | "inputs": [], 111 | "name": "isTopLevelCall", 112 | "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], 113 | "stateMutability": "view", 114 | "type": "function" 115 | }, 116 | { 117 | "inputs": [ 118 | { "internalType": "address", "name": "destination", "type": "address" }, 119 | { "internalType": "bytes", "name": "calldataForL1", "type": "bytes" } 120 | ], 121 | "name": "sendTxToL1", 122 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 123 | "stateMutability": "payable", 124 | "type": "function" 125 | }, 126 | { 127 | "inputs": [ 128 | { "internalType": "address", "name": "destination", "type": "address" } 129 | ], 130 | "name": "withdrawEth", 131 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], 132 | "stateMutability": "payable", 133 | "type": "function" 134 | } 135 | ] 136 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/abis/L2GatewayRouter.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "address", 8 | "name": "newDefaultGateway", 9 | "type": "address" 10 | } 11 | ], 12 | "name": "DefaultGatewayUpdated", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "address", 21 | "name": "l1Token", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "gateway", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "GatewaySet", 32 | "type": "event" 33 | }, 34 | { 35 | "anonymous": false, 36 | "inputs": [ 37 | { 38 | "indexed": true, 39 | "internalType": "address", 40 | "name": "token", 41 | "type": "address" 42 | }, 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "_userFrom", 47 | "type": "address" 48 | }, 49 | { 50 | "indexed": true, 51 | "internalType": "address", 52 | "name": "_userTo", 53 | "type": "address" 54 | }, 55 | { 56 | "indexed": false, 57 | "internalType": "address", 58 | "name": "gateway", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "TransferRouted", 63 | "type": "event" 64 | }, 65 | { 66 | "anonymous": false, 67 | "inputs": [ 68 | { 69 | "indexed": true, 70 | "internalType": "address", 71 | "name": "_from", 72 | "type": "address" 73 | }, 74 | { 75 | "indexed": true, 76 | "internalType": "address", 77 | "name": "_to", 78 | "type": "address" 79 | }, 80 | { 81 | "indexed": true, 82 | "internalType": "uint256", 83 | "name": "_id", 84 | "type": "uint256" 85 | }, 86 | { 87 | "indexed": false, 88 | "internalType": "bytes", 89 | "name": "_data", 90 | "type": "bytes" 91 | } 92 | ], 93 | "name": "TxToL1", 94 | "type": "event" 95 | }, 96 | { 97 | "inputs": [ 98 | { "internalType": "address", "name": "l1ERC20", "type": "address" } 99 | ], 100 | "name": "calculateL2TokenAddress", 101 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "inputs": [], 107 | "name": "counterpartGateway", 108 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 109 | "stateMutability": "view", 110 | "type": "function" 111 | }, 112 | { 113 | "inputs": [], 114 | "name": "defaultGateway", 115 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, 119 | { 120 | "inputs": [ 121 | { "internalType": "address", "name": "", "type": "address" }, 122 | { "internalType": "address", "name": "", "type": "address" }, 123 | { "internalType": "address", "name": "", "type": "address" }, 124 | { "internalType": "uint256", "name": "", "type": "uint256" }, 125 | { "internalType": "bytes", "name": "", "type": "bytes" } 126 | ], 127 | "name": "finalizeInboundTransfer", 128 | "outputs": [], 129 | "stateMutability": "payable", 130 | "type": "function" 131 | }, 132 | { 133 | "inputs": [ 134 | { "internalType": "address", "name": "_token", "type": "address" } 135 | ], 136 | "name": "getGateway", 137 | "outputs": [ 138 | { "internalType": "address", "name": "gateway", "type": "address" } 139 | ], 140 | "stateMutability": "view", 141 | "type": "function" 142 | }, 143 | { 144 | "inputs": [ 145 | { "internalType": "address", "name": "_token", "type": "address" }, 146 | { "internalType": "address", "name": "_from", "type": "address" }, 147 | { "internalType": "address", "name": "_to", "type": "address" }, 148 | { "internalType": "uint256", "name": "_amount", "type": "uint256" }, 149 | { "internalType": "bytes", "name": "_data", "type": "bytes" } 150 | ], 151 | "name": "getOutboundCalldata", 152 | "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], 153 | "stateMutability": "view", 154 | "type": "function" 155 | }, 156 | { 157 | "inputs": [ 158 | { 159 | "internalType": "address", 160 | "name": "_counterpartGateway", 161 | "type": "address" 162 | }, 163 | { 164 | "internalType": "address", 165 | "name": "_defaultGateway", 166 | "type": "address" 167 | } 168 | ], 169 | "name": "initialize", 170 | "outputs": [], 171 | "stateMutability": "nonpayable", 172 | "type": "function" 173 | }, 174 | { 175 | "inputs": [{ "internalType": "address", "name": "", "type": "address" }], 176 | "name": "l1TokenToGateway", 177 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 178 | "stateMutability": "view", 179 | "type": "function" 180 | }, 181 | { 182 | "inputs": [ 183 | { "internalType": "address", "name": "_l1Token", "type": "address" }, 184 | { "internalType": "address", "name": "_to", "type": "address" }, 185 | { "internalType": "uint256", "name": "_amount", "type": "uint256" }, 186 | { "internalType": "bytes", "name": "_data", "type": "bytes" } 187 | ], 188 | "name": "outboundTransfer", 189 | "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], 190 | "stateMutability": "payable", 191 | "type": "function" 192 | }, 193 | { 194 | "inputs": [ 195 | { "internalType": "address", "name": "_token", "type": "address" }, 196 | { "internalType": "address", "name": "_to", "type": "address" }, 197 | { "internalType": "uint256", "name": "_amount", "type": "uint256" }, 198 | { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, 199 | { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, 200 | { "internalType": "bytes", "name": "_data", "type": "bytes" } 201 | ], 202 | "name": "outboundTransfer", 203 | "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], 204 | "stateMutability": "payable", 205 | "type": "function" 206 | }, 207 | { 208 | "inputs": [], 209 | "name": "postUpgradeInit", 210 | "outputs": [], 211 | "stateMutability": "nonpayable", 212 | "type": "function" 213 | }, 214 | { 215 | "inputs": [], 216 | "name": "router", 217 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 218 | "stateMutability": "view", 219 | "type": "function" 220 | }, 221 | { 222 | "inputs": [ 223 | { 224 | "internalType": "address", 225 | "name": "newL2DefaultGateway", 226 | "type": "address" 227 | } 228 | ], 229 | "name": "setDefaultGateway", 230 | "outputs": [], 231 | "stateMutability": "nonpayable", 232 | "type": "function" 233 | }, 234 | { 235 | "inputs": [ 236 | { "internalType": "address[]", "name": "_l1Token", "type": "address[]" }, 237 | { "internalType": "address[]", "name": "_gateway", "type": "address[]" } 238 | ], 239 | "name": "setGateway", 240 | "outputs": [], 241 | "stateMutability": "nonpayable", 242 | "type": "function" 243 | } 244 | ] 245 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/abis/NitroArbRetryableTx.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "name": "NoTicketWithID", 5 | "type": "error" 6 | }, 7 | { 8 | "inputs": [], 9 | "name": "NotCallable", 10 | "type": "error" 11 | }, 12 | { 13 | "anonymous": false, 14 | "inputs": [ 15 | { 16 | "indexed": true, 17 | "internalType": "bytes32", 18 | "name": "ticketId", 19 | "type": "bytes32" 20 | } 21 | ], 22 | "name": "Canceled", 23 | "type": "event" 24 | }, 25 | { 26 | "anonymous": false, 27 | "inputs": [ 28 | { 29 | "indexed": true, 30 | "internalType": "bytes32", 31 | "name": "ticketId", 32 | "type": "bytes32" 33 | }, 34 | { 35 | "indexed": false, 36 | "internalType": "uint256", 37 | "name": "newTimeout", 38 | "type": "uint256" 39 | } 40 | ], 41 | "name": "LifetimeExtended", 42 | "type": "event" 43 | }, 44 | { 45 | "anonymous": false, 46 | "inputs": [ 47 | { 48 | "indexed": true, 49 | "internalType": "bytes32", 50 | "name": "ticketId", 51 | "type": "bytes32" 52 | }, 53 | { 54 | "indexed": true, 55 | "internalType": "bytes32", 56 | "name": "retryTxHash", 57 | "type": "bytes32" 58 | }, 59 | { 60 | "indexed": true, 61 | "internalType": "uint64", 62 | "name": "sequenceNum", 63 | "type": "uint64" 64 | }, 65 | { 66 | "indexed": false, 67 | "internalType": "uint64", 68 | "name": "donatedGas", 69 | "type": "uint64" 70 | }, 71 | { 72 | "indexed": false, 73 | "internalType": "address", 74 | "name": "gasDonor", 75 | "type": "address" 76 | } 77 | ], 78 | "name": "RedeemScheduled", 79 | "type": "event" 80 | }, 81 | { 82 | "anonymous": false, 83 | "inputs": [ 84 | { 85 | "indexed": true, 86 | "internalType": "bytes32", 87 | "name": "userTxHash", 88 | "type": "bytes32" 89 | } 90 | ], 91 | "name": "Redeemed", 92 | "type": "event" 93 | }, 94 | { 95 | "anonymous": false, 96 | "inputs": [ 97 | { 98 | "indexed": true, 99 | "internalType": "bytes32", 100 | "name": "ticketId", 101 | "type": "bytes32" 102 | } 103 | ], 104 | "name": "TicketCreated", 105 | "type": "event" 106 | }, 107 | { 108 | "inputs": [ 109 | { 110 | "internalType": "bytes32", 111 | "name": "ticketId", 112 | "type": "bytes32" 113 | } 114 | ], 115 | "name": "cancel", 116 | "outputs": [], 117 | "stateMutability": "nonpayable", 118 | "type": "function" 119 | }, 120 | { 121 | "inputs": [ 122 | { 123 | "internalType": "bytes32", 124 | "name": "ticketId", 125 | "type": "bytes32" 126 | } 127 | ], 128 | "name": "getBeneficiary", 129 | "outputs": [ 130 | { 131 | "internalType": "address", 132 | "name": "", 133 | "type": "address" 134 | } 135 | ], 136 | "stateMutability": "view", 137 | "type": "function" 138 | }, 139 | { 140 | "inputs": [], 141 | "name": "getLifetime", 142 | "outputs": [ 143 | { 144 | "internalType": "uint256", 145 | "name": "", 146 | "type": "uint256" 147 | } 148 | ], 149 | "stateMutability": "view", 150 | "type": "function" 151 | }, 152 | { 153 | "inputs": [ 154 | { 155 | "internalType": "bytes32", 156 | "name": "ticketId", 157 | "type": "bytes32" 158 | } 159 | ], 160 | "name": "getTimeout", 161 | "outputs": [ 162 | { 163 | "internalType": "uint256", 164 | "name": "", 165 | "type": "uint256" 166 | } 167 | ], 168 | "stateMutability": "view", 169 | "type": "function" 170 | }, 171 | { 172 | "inputs": [ 173 | { 174 | "internalType": "bytes32", 175 | "name": "ticketId", 176 | "type": "bytes32" 177 | } 178 | ], 179 | "name": "keepalive", 180 | "outputs": [ 181 | { 182 | "internalType": "uint256", 183 | "name": "", 184 | "type": "uint256" 185 | } 186 | ], 187 | "stateMutability": "nonpayable", 188 | "type": "function" 189 | }, 190 | { 191 | "inputs": [ 192 | { 193 | "internalType": "bytes32", 194 | "name": "ticketId", 195 | "type": "bytes32" 196 | } 197 | ], 198 | "name": "redeem", 199 | "outputs": [ 200 | { 201 | "internalType": "bytes32", 202 | "name": "", 203 | "type": "bytes32" 204 | } 205 | ], 206 | "stateMutability": "nonpayable", 207 | "type": "function" 208 | }, 209 | { 210 | "inputs": [ 211 | { 212 | "internalType": "bytes32", 213 | "name": "requestId", 214 | "type": "bytes32" 215 | }, 216 | { 217 | "internalType": "uint256", 218 | "name": "l1BaseFee", 219 | "type": "uint256" 220 | }, 221 | { 222 | "internalType": "uint256", 223 | "name": "deposit", 224 | "type": "uint256" 225 | }, 226 | { 227 | "internalType": "uint256", 228 | "name": "callvalue", 229 | "type": "uint256" 230 | }, 231 | { 232 | "internalType": "uint256", 233 | "name": "gasFeeCap", 234 | "type": "uint256" 235 | }, 236 | { 237 | "internalType": "uint64", 238 | "name": "gasLimit", 239 | "type": "uint64" 240 | }, 241 | { 242 | "internalType": "uint256", 243 | "name": "maxSubmissionFee", 244 | "type": "uint256" 245 | }, 246 | { 247 | "internalType": "address", 248 | "name": "feeRefundAddress", 249 | "type": "address" 250 | }, 251 | { 252 | "internalType": "address", 253 | "name": "beneficiary", 254 | "type": "address" 255 | }, 256 | { 257 | "internalType": "address", 258 | "name": "retryTo", 259 | "type": "address" 260 | }, 261 | { 262 | "internalType": "bytes", 263 | "name": "retryData", 264 | "type": "bytes" 265 | } 266 | ], 267 | "name": "submitRetryable", 268 | "outputs": [], 269 | "stateMutability": "nonpayable", 270 | "type": "function" 271 | } 272 | ] 273 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/metadata.template.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "@graphprotocol/graph-ts"; 2 | 3 | // export const nitroStartBlock = BigInt.fromString("{{{ nitroGenesisBlockNum }}}") 4 | 5 | // we use moustache to template these values in (as used in the subgraph manifest template) 6 | export const L2_STD_GATEWAY = Address.fromString("{{{ l2StandardGateway }}}".toLowerCase()) 7 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layer2-token-gateway", 3 | "version": "0.0.2", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:mainnet && graph codegen", 7 | "build": "yarn prepare:mainnet && graph build", 8 | "postinstall": "yarn codegen", 9 | 10 | "prepare:mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml && yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/metadata.template.ts | tail -n +2 > ./metadata.ts", 11 | "prepare:goerli": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml && yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/metadata.template.ts | tail -n +2 > ./metadata.ts", 12 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml && yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/goerli.json $(pwd)/metadata.template.ts | tail -n +2 > ./metadata.ts", 13 | "prepare:nova": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nova.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml && yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/nova.json $(pwd)/metadata.template.ts | tail -n +2 > ./metadata.ts", 14 | 15 | "deploy:mainnet": "yarn build && yarn prepare:mainnet && graph deploy --node https://api.thegraph.com/deploy/ gvladika/layer2-token-gateway-arb1", 16 | "deploy:goerli": "yarn build && yarn prepare:goerli && graph deploy --node https://api.thegraph.com/deploy/ gvladika/layer2-token-gateway-goerli", 17 | "deploy:sepolia": "yarn build && yarn prepare:sepolia && graph deploy --node https://api.thegraph.com/deploy/ fionnachan/layer2-token-gateway-sepolia", 18 | "deploy:nova": "yarn build && yarn prepare:nova && graph deploy --node https://api.thegraph.com/deploy/ fionnachan/layer2-token-gateway-nova", 19 | "test": "yarn codegen && yarn build && graph test -r --version 0.5.0" 20 | }, 21 | "dependencies": { 22 | "@arbitrum/subgraph-common": "0.0.1", 23 | "@graphprotocol/graph-ts": "^0.32.0", 24 | "matchstick-as": "^0.5.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/schema.graphql: -------------------------------------------------------------------------------- 1 | type Gateway @entity { 2 | "gateway address hex string" 3 | id: ID! 4 | "tokens this gateway supports" 5 | tokens: [TokenGatewayJoinTable!] @derivedFrom(field: "gateway") 6 | } 7 | 8 | type Token @entity { 9 | "l1 token address hex string" 10 | id: ID! 11 | "gateway that supports this token" 12 | gateway: [TokenGatewayJoinTable!] @derivedFrom(field: "token") 13 | } 14 | 15 | type TokenGatewayJoinTable @entity { 16 | "Set to concat `gateway.id` and `token.id`" 17 | id: ID! 18 | "block in which the token-gateway were associated" 19 | l2BlockNum: BigInt! 20 | 21 | gateway: Gateway! 22 | token: Token! 23 | 24 | withdrawals: [GatewayWithdrawalData!] @derivedFrom(field: "tokenGatewayJoin") 25 | deposits: [GatewayDepositData!] @derivedFrom(field: "tokenGatewayJoin") 26 | } 27 | 28 | type GatewayDepositData @entity { 29 | "the L2 tx hash that got executed" 30 | id: ID! 31 | 32 | "L2 Tx hash that triggered this event" 33 | l2TxHash: Bytes! 34 | 35 | "block in which the event was emitted" 36 | l2BlockNum: BigInt! 37 | 38 | from: Bytes! 39 | to: Bytes! 40 | amount: BigInt! 41 | 42 | tokenGatewayJoin: TokenGatewayJoinTable! 43 | 44 | # disabled since we're not able to link the creation and redemption txs efficiently 45 | # l1ToL2Transaction: L1ToL2Transaction! 46 | } 47 | 48 | type GatewayWithdrawalData @entity { 49 | "this is the l2ToL1Id of the transaction as surfaced by arb-os" 50 | id: ID! 51 | 52 | "L2 Tx hash that triggered this event" 53 | l2TxHash: Bytes! 54 | 55 | "block in which the event was emitted" 56 | l2BlockNum: BigInt! 57 | 58 | from: Bytes! 59 | to: Bytes! 60 | amount: BigInt! 61 | exitNum: BigInt! 62 | 63 | tokenGatewayJoin: TokenGatewayJoinTable! 64 | 65 | # disabled to keep consistent with the L1ToL2 flow 66 | # l2ToL1Event: L2ToL1Transaction! 67 | } 68 | 69 | type L1ToL2Transaction @entity { 70 | "the L1 to L2 retryable creation id" 71 | id: ID! 72 | 73 | "L2 Tx hash that triggered this event" 74 | l2TxHash: Bytes! 75 | 76 | "aliased L1 address that called the inbox in the L1" 77 | l1FromAliased: Bytes! #address 78 | 79 | "L2 destination address of retryable" 80 | l2To: Bytes! #address 81 | 82 | "was this created before the nitro migration" 83 | isClassic: Boolean! 84 | 85 | "this is the amount of eth transfered from L1 to L2 that is creditted to the from account. note that eth can also be deposited through the eth deposit message which isn't tracked here" 86 | deposit: BigInt! 87 | 88 | l2Calldata: Bytes! 89 | l2Callvalue: BigInt! 90 | 91 | "block in which the event was emitted" 92 | l2BlockNum: BigInt! 93 | 94 | # we are not able to corellate these 2 events, without indexing every block 95 | # gatewayDepositData: [GatewayDepositData!] @derivedFrom(field: "l1ToL2Transaction") 96 | 97 | # "the user tx hash of this retryable. undefined in nitro since" 98 | # userTxHash: Bytes # bytes32 99 | } 100 | 101 | type L2ToL1Transaction @entity { 102 | "a unique identifier used as a PK for the subgraph - when not classic this is the unique id, in classic it is deterministically generated from the uniqueId (sets the highest bit of the unique id as a uint64)" 103 | id: ID! 104 | 105 | "the unique id surfaced by Arb-Os L2 to L1 tx - this might not always actually be unique when comparing pre vs post nitro" 106 | uniqueId: BigInt! 107 | 108 | "L2 Tx hash that triggered this event" 109 | l2TxHash: Bytes! 110 | 111 | "address that initiated the L2 to L1 tx" 112 | l2From: Bytes! # address 113 | 114 | "address that will receive the L1 call from the outbox" 115 | l1To: Bytes! # address 116 | 117 | "batch numbers are not available in nitro" 118 | batchNumber: BigInt # uint256 119 | 120 | "this is position in global merkle tree for nitro, but classic its within a batch num" 121 | indexInBatch: BigInt! # uint256 122 | 123 | "block in which the event was emitted" 124 | l2BlockNum: BigInt! 125 | 126 | "L1 block estimate when the event was emitted" 127 | l1BlockNum: BigInt! # uint256 128 | 129 | "timestamp when the event was emitted" 130 | l2Timestamp: BigInt! # uint256 131 | 132 | "value that was sent in the L2, that will be transferred in the L2" 133 | l1Callvalue: BigInt! # uint256 134 | 135 | "data that will be used to call the L1 contract" 136 | l1Calldata: Bytes! # bytes 137 | 138 | "indicates if this L2 to L1 tx was initiated before the nitro upgrade" 139 | isClassic: Boolean! 140 | 141 | # disabled to keep consistent with L1ToL2 flow 142 | # gatewayWithdrawalData: [GatewayWithdrawalData!] @derivedFrom(field: "l2ToL1Event") 143 | } 144 | 145 | enum WithdrawalType { 146 | EthWithdrawal 147 | TokenWithdrawal 148 | } 149 | 150 | type Withdrawal @entity(immutable: true) { 151 | "txHash-txIndex" 152 | id: ID! 153 | "EthWithdrawal or TokenWithdrawal" 154 | type: WithdrawalType! 155 | "account withdrawing funds to L1, original address" 156 | sender: Bytes! # address 157 | "receiver L1 account" 158 | receiver: Bytes! # address 159 | "Eth value being withdrawn in wei" 160 | ethValue: BigInt! # uint256 161 | "token being withdrawn" 162 | l1Token: Token 163 | "token amount being withdrawn to L1" 164 | tokenAmount: BigInt # uint256 165 | "true -> classic; false -> nitro" 166 | isClassic: Boolean! 167 | "L2 block timestamp" 168 | l2BlockTimestamp: BigInt! 169 | "L2 withdrawal tx" 170 | l2TxHash: String! 171 | "block in which the event was emitted" 172 | l2BlockNum: BigInt! 173 | } -------------------------------------------------------------------------------- /packages/layer2-token-gateway/src/abi.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Bytes, 4 | ethereum, 5 | log, 6 | BigInt, 7 | } from "@graphprotocol/graph-ts"; 8 | import { TicketCreated as NitroTicketCreatedEvent } from "../generated/NitroArbRetryableTx/NitroArbRetryableTx"; 9 | 10 | class RetryableInput { 11 | constructor( 12 | public deposit: BigInt, 13 | public l2Callvalue: BigInt, 14 | public l2Calldata: Bytes, 15 | public l2To: Address, 16 | ) {} 17 | } 18 | 19 | export const parseRetryableInput = ( 20 | event: ethereum.Event 21 | ): RetryableInput => { 22 | const funcSig = Bytes.fromUint8Array(event.transaction.input.slice(0, 4)); 23 | 24 | if (funcSig.equals(Bytes.fromHexString("0xc9f95d32"))) { 25 | // parsing fields from 26 | // function submitRetryable( 27 | // bytes32 requestId, 28 | // uint256 l1BaseFee, 29 | // uint256 deposit, 30 | // uint256 callvalue, 31 | // uint256 gasFeeCap, 32 | // uint64 gasLimit, 33 | // uint256 maxSubmissionFee, 34 | // address feeRefundAddress, 35 | // address beneficiary, 36 | // address retryTo, 37 | // bytes calldata retryData 38 | // ) external; 39 | const inputWithoutSelector = Bytes.fromUint8Array( 40 | event.transaction.input.slice(4) 41 | ); 42 | // TODO: what if we decode one at a time instead of decoding the tuple 43 | const parsedWithoutData = ethereum.decode( 44 | "(bytes32,uint256,uint256,uint256,uint256,uint64,uint256,address,address,address,uint256,uint256)", 45 | inputWithoutSelector 46 | ); 47 | if (!parsedWithoutData) { 48 | log.error("didn't expect !parsedWithoutData", []); 49 | throw new Error("goddamn"); 50 | } 51 | const parsedArray = parsedWithoutData.toTuple(); 52 | 53 | const deposit = parsedArray[2].toBigInt(); 54 | const l2Callvalue = parsedArray[3].toBigInt(); 55 | const l2To = parsedArray[9].toAddress(); 56 | 57 | // TODO: DRY up logic used here and classic (ie abi decoding the input data) 58 | // const lengthOfDataLength = parsedArray[10].toBigInt() 59 | const dataLength = parsedArray[11].toBigInt(); 60 | 61 | const sliceStart = ethereum.encode(parsedWithoutData)!.byteLength; 62 | if (!sliceStart) { 63 | // throw new Error("oh damn somethin broke") 64 | log.error("ah damn no encoding of start", []); 65 | throw new Error("goddamn2"); 66 | } 67 | 68 | log.debug("expect slice to start at {}", [sliceStart.toString()]); 69 | const l2Calldata = Bytes.fromByteArray( 70 | Bytes.fromUint8Array( 71 | inputWithoutSelector.slice(sliceStart, sliceStart + dataLength.toI32()) 72 | ) 73 | ); 74 | 75 | return new RetryableInput(deposit, l2Callvalue, l2Calldata, l2To) 76 | } else if (funcSig.equals(Bytes.fromHexString("0x679b6ded"))) { 77 | // parsing fields from classic 78 | // function createRetryableTicket( 79 | // address destAddr, 80 | // uint256 l2CallValue, 81 | // uint256 maxSubmissionCost, 82 | // address excessFeeRefundAddress, 83 | // address callValueRefundAddress, 84 | // uint256 maxGas, 85 | // uint256 gasPriceBid, 86 | // bytes calldata data 87 | // ) external payable; 88 | 89 | // we want to skip the `0x679b6ded` at the start and parse the bytes length instead of the bytes explicitly 90 | const inputWithoutSelector = Bytes.fromUint8Array( 91 | event.transaction.input.slice(4) 92 | ); 93 | const parsedWithoutData = ethereum.decode( 94 | "(address,uint256,uint256,address,address,uint256,uint256,uint256,uint256)", 95 | inputWithoutSelector 96 | ); 97 | 98 | if (!parsedWithoutData) { 99 | log.critical("didn't expect !parsedWithoutData", []); 100 | throw new Error("somethin bad happened"); 101 | } 102 | 103 | const parsedArray = parsedWithoutData.toTuple(); 104 | 105 | const l2Callvalue = parsedArray[1].toBigInt(); 106 | const deposit = event.transaction.value; 107 | 108 | // this is due to how dynamic length data types are encoded 109 | const lengthOfDataLength = parsedArray[7].toBigInt(); 110 | if (lengthOfDataLength != BigInt.fromI32(256)) { 111 | log.critical( 112 | "something unexpected went wrong with lengthOfDataLength {}", 113 | [lengthOfDataLength.toString()] 114 | ); 115 | throw new Error("oh damn somethin broke"); 116 | } 117 | 118 | const dataLength = parsedArray[8].toBigInt(); 119 | log.debug("lengthOfDataLength expected: {}", [ 120 | lengthOfDataLength.toString(), 121 | ]); 122 | log.debug("data length expected: {}", [dataLength.toString()]); 123 | 124 | log.debug("input length {}", [inputWithoutSelector.length.toString()]); 125 | 126 | // we do this because the graph seems weird when parsing dynamic length data types 127 | // can maybe be fixed if we don't parse it as `toTuple` 128 | // https://ethereum.stackexchange.com/questions/114582/the-graph-nodes-cant-decode-abi-encoded-data-containing-arrays 129 | const sliceStart = ethereum.encode(parsedWithoutData)!.byteLength; 130 | if (!sliceStart) { 131 | // throw new Error("oh damn somethin broke") 132 | log.critical("something broke", []); 133 | throw new Error("oh damn somethin broke 2"); 134 | } 135 | 136 | log.debug("expect slice to start at {}", [sliceStart.toString()]); 137 | const l2Calldata = Bytes.fromByteArray( 138 | Bytes.fromUint8Array( 139 | inputWithoutSelector.slice(sliceStart, sliceStart + dataLength.toI32()) 140 | ) 141 | ); 142 | 143 | const l2To = event.transaction.to; 144 | if(!l2To) { 145 | log.error("not expected null to since this isn't contract deploy", []) 146 | throw new Error("not expected null to since this isn't contract deploy") 147 | } 148 | 149 | return new RetryableInput(deposit, l2Callvalue, l2Calldata, l2To) 150 | } else { 151 | log.error("not recorgnized retryable input", []); 152 | throw new Error("Not recognised input"); 153 | } 154 | }; 155 | 156 | // export class CreateRetryableTicketInputFields { 157 | // public deposit: BigInt; 158 | // public l2Callvalue: BigInt; 159 | // public l2Calldata: Bytes; 160 | // public l2To: Address; 161 | 162 | // constructor(tx: ethereum.Transaction) { 163 | 164 | // } 165 | // } 166 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/src/util.ts: -------------------------------------------------------------------------------- 1 | import { Address, Bytes, ethereum, BigInt } from "@graphprotocol/graph-ts" 2 | import { L2_STD_GATEWAY as _L2_STD_GATEWAY } from "../metadata" 3 | 4 | export const isNitro = (block: ethereum.Block): boolean => { 5 | 6 | // return block.stateRoot.notEqual(Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")) || block.number.ge(nitroStartBlock) 7 | 8 | // would be better to check the mix digest or extra data, but they arent exposed in the subgraph 9 | return block.stateRoot.notEqual(Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")) 10 | } 11 | 12 | export const addressToId = (input: Address): string => 13 | input.toHexString().toLowerCase(); 14 | 15 | export const getJoinId = (gatewayId: string, tokenId: string): string => 16 | gatewayId.concat(tokenId) 17 | 18 | 19 | export const bigIntToId = (input: BigInt): string => input.toHexString() 20 | 21 | export const DISABLED_GATEWAY_ADDR = Address.fromString("0x0000000000000000000000000000000000000001"); 22 | 23 | export const L2_STD_GATEWAY: Address = _L2_STD_GATEWAY; 24 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | schema: 3 | file: ./schema.graphql 4 | description: Subgraph of arbitrum L2 token bridge 5 | dataSources: 6 | - kind: ethereum/contract 7 | name: L2GatewayRouter 8 | network: {{ l2Network }} 9 | source: 10 | address: "{{ l2GatewayRouter }}" 11 | abi: L2GatewayRouter 12 | startBlock: {{ l2GatewayRouterDeployBlock }} 13 | mapping: 14 | kind: ethereum/events 15 | apiVersion: 0.0.5 16 | language: wasm/assemblyscript 17 | entities: 18 | - GatewaySet 19 | abis: 20 | - name: L2GatewayRouter 21 | file: ./abis/L2GatewayRouter.json 22 | eventHandlers: 23 | - event: GatewaySet(indexed address,indexed address) 24 | handler: handleGatewaySet 25 | file: ./src/mapping.ts 26 | - kind: ethereum/contract 27 | name: NitroArbRetryableTx 28 | network: {{ l2Network }} 29 | source: 30 | address: "0x000000000000000000000000000000000000006E" 31 | abi: NitroArbRetryableTx 32 | startBlock: {{ arbRetryableTxStartBlock }} 33 | mapping: 34 | kind: ethereum/events 35 | apiVersion: 0.0.5 36 | language: wasm/assemblyscript 37 | entities: 38 | - L1ToL2Transaction 39 | abis: 40 | - name: NitroArbRetryableTx 41 | file: ./abis/NitroArbRetryableTx.json 42 | eventHandlers: 43 | - event: TicketCreated(indexed bytes32) 44 | handler: handleTicketCreated 45 | file: ./src/mapping.ts 46 | - kind: ethereum/contract 47 | name: ClassicArbSys 48 | network: {{ l2Network }} 49 | source: 50 | address: "0x0000000000000000000000000000000000000064" 51 | abi: ClassicArbSys 52 | startBlock: {{ arbSysStartBlock }} 53 | mapping: 54 | kind: ethereum/events 55 | apiVersion: 0.0.5 56 | language: wasm/assemblyscript 57 | entities: 58 | - L2ToL1Transaction 59 | abis: 60 | - name: ClassicArbSys 61 | file: ./abis/ClassicArbSys.json 62 | eventHandlers: 63 | - event: L2ToL1Transaction(address,indexed address,indexed uint256,indexed uint256,uint256,uint256,uint256,uint256,uint256,bytes) 64 | handler: handleClassicL2ToL1Transaction 65 | file: ./src/mapping.ts 66 | - kind: ethereum/contract 67 | name: NitroArbSys 68 | network: {{ l2Network }} 69 | source: 70 | address: "0x0000000000000000000000000000000000000064" 71 | abi: NitroArbSys 72 | startBlock: {{ arbSysStartBlock }} 73 | mapping: 74 | kind: ethereum/events 75 | apiVersion: 0.0.5 76 | language: wasm/assemblyscript 77 | entities: 78 | - L2ToL1Transaction 79 | abis: 80 | - name: NitroArbSys 81 | file: ./abis/NitroArbSys.json 82 | eventHandlers: 83 | - event: L2ToL1Tx(address,indexed address,indexed uint256,indexed uint256,uint256,uint256,uint256,uint256,bytes) 84 | handler: handleNitroL2ToL1Transaction 85 | file: ./src/mapping.ts 86 | - kind: ethereum/contract 87 | name: L2StandardGateway 88 | network: {{ l2Network }} 89 | source: 90 | address: "{{ l2StandardGateway }}" 91 | abi: L2ArbitrumGateway 92 | startBlock: {{ l2StandardGatewayDeployBlock }} 93 | mapping: 94 | kind: ethereum/events 95 | apiVersion: 0.0.5 96 | language: wasm/assemblyscript 97 | entities: 98 | - Token 99 | abis: 100 | - name: L2ArbitrumGateway 101 | file: ./abis/L2ArbitrumGateway.json 102 | eventHandlers: 103 | - event: DepositFinalized(indexed address,indexed address,indexed address,uint256) 104 | handler: handleDeposit 105 | - event: WithdrawalInitiated(address,indexed address,indexed address,indexed uint256,uint256,uint256) 106 | handler: handleWithdrawal 107 | file: ./src/mapping.ts 108 | templates: 109 | - name: L2ArbitrumGateway 110 | kind: ethereum/contract 111 | network: {{ l2Network }} 112 | source: 113 | abi: L2ArbitrumGateway 114 | mapping: 115 | kind: ethereum/events 116 | apiVersion: 0.0.5 117 | language: wasm/assemblyscript 118 | file: ./src/mapping.ts 119 | entities: 120 | - L2ArbitrumGateway 121 | abis: 122 | - name: L2ArbitrumGateway 123 | file: ./abis/L2ArbitrumGateway.json 124 | eventHandlers: 125 | - event: WithdrawalInitiated(address,indexed address,indexed address,indexed uint256,uint256,uint256) 126 | handler: handleWithdrawal 127 | - event: DepositFinalized(indexed address,indexed address,indexed address,uint256) 128 | handler: handleDeposit 129 | 130 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/tests/l1ToL2/l1ToL2.test.ts: -------------------------------------------------------------------------------- 1 | import { handleClassicTicketCreated } from "../../src/mapping"; 2 | import { 3 | Address, 4 | BigInt, 5 | Bytes, 6 | ethereum, 7 | store, 8 | log, 9 | } from "@graphprotocol/graph-ts"; 10 | import { 11 | newMockEvent, 12 | test, 13 | assert, 14 | // createMockedFunction, 15 | } from "matchstick-as"; 16 | import { TicketCreated as TicketCreatedEvent } from "../../generated/ClassicArbRetryableTx/ClassicArbRetryableTx"; 17 | import { L1ToL2Transaction } from "../../generated/schema"; 18 | import { parseRetryableInput } from "../../src/abi"; 19 | import { TicketCreated as NitroTicketCreatedEvent } from "../../generated/NitroArbRetryableTx/NitroArbRetryableTx"; 20 | 21 | 22 | const createEthDeposit = (): TicketCreatedEvent => { 23 | let mockEvent = newMockEvent(); 24 | 25 | let parameters = new Array(); 26 | let userTxHash = new ethereum.EventParam( 27 | "userTxHash", 28 | ethereum.Value.fromBytes( 29 | Bytes.fromByteArray( 30 | Bytes.fromHexString( 31 | "0x55795cbae70c38e3b8ac26e0f9bd69bebe36e452f80eb624a1b5a6f9b97d8db1" 32 | ) 33 | ) 34 | ) 35 | ); 36 | parameters.push(userTxHash); 37 | 38 | let tx = mockEvent.transaction; 39 | tx.hash = Bytes.fromByteArray( 40 | Bytes.fromHexString( 41 | "0xf3976cd587d833873f508e7fb67f7cbaf8606a51f0eca25981d3f7bc93ff64d3" 42 | ) 43 | ); 44 | tx.input = Bytes.fromByteArray( 45 | Bytes.fromHexString( 46 | "0x679b6ded000000000000000000000000092d7963e38c41e482ae7ef6378f15fb8c3678a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000294f7fc4e000000000000000000000000092d7963e38c41e482ae7ef6378f15fb8c3678a5000000000000000000000000092d7963e38c41e482ae7ef6378f15fb8c3678a50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000" 47 | ) 48 | ); 49 | tx.from = Address.fromString("0x092d7963e38c41e482ae7ef6378f15fb8c3678a5"); 50 | tx.value = BigInt.fromI64(270000000000000000); 51 | 52 | let newDeposit = new TicketCreatedEvent( 53 | mockEvent.address, 54 | mockEvent.logIndex, 55 | mockEvent.transactionLogIndex, 56 | mockEvent.logType, 57 | mockEvent.block, 58 | tx, 59 | parameters, 60 | mockEvent.receipt 61 | ); 62 | 63 | return newDeposit; 64 | }; 65 | 66 | const createTokenDeposit = (): TicketCreatedEvent => { 67 | let mockEvent = newMockEvent(); 68 | 69 | let parameters = new Array(); 70 | let userTxHash = new ethereum.EventParam( 71 | "userTxHash", 72 | ethereum.Value.fromBytes( 73 | Bytes.fromByteArray( 74 | Bytes.fromHexString( 75 | "0xdff1d27d56fd595e979061e707b0a4867d82f4c326031d060cc4ae5b51c476a6" 76 | ) 77 | ) 78 | ) 79 | ); 80 | parameters.push(userTxHash); 81 | 82 | let tx = mockEvent.transaction; 83 | tx.hash = Bytes.fromByteArray( 84 | Bytes.fromHexString( 85 | "0x003bcde82f7710122e52fed83d52a6dc70cbbed3d8a52c579d7d9b6f243d97e9" 86 | ) 87 | ); 88 | tx.input = Bytes.fromByteArray( 89 | Bytes.fromHexString( 90 | "0x679b6ded0000000000000000000000009b014455acc2fe90c52803849d0002aeec184a06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010bad8dca0000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f000000000000000000000000000000000000000000000000000000000007b7a300000000000000000000000000000000000000000000000000000000119b9690000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001442e567b36000000000000000000000000393fd5b96f6459511d7368778318c31d719720ad000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f00000000000000000000000000000000000000000000003635c9adc5dea0000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 91 | ) 92 | ); 93 | tx.from = Address.fromString("0x917dc9a69f65dc3082d518192cd3725e1fa96ca2"); 94 | tx.value = BigInt.fromI64(149478946024016); 95 | 96 | let newDeposit = new TicketCreatedEvent( 97 | mockEvent.address, 98 | mockEvent.logIndex, 99 | mockEvent.transactionLogIndex, 100 | mockEvent.logType, 101 | mockEvent.block, 102 | tx, 103 | parameters, 104 | mockEvent.receipt 105 | ); 106 | 107 | return newDeposit; 108 | }; 109 | 110 | 111 | 112 | test("Can parse nitro submit retryable", () => { 113 | // sample from https://testnet.arbiscan.io/txs?block=14349610 114 | 115 | // submitRetryable(bytes32,uint256,uint256,uint256,uint256,uint64,uint256,address,address,address,bytes) 116 | const txInput = "0xc9f95d320000000000000000000000000000000000000000000000000000000000001d250000000000000000000000000000000000000000000000000000000000052a29000000000000000000000000000000000000000000000000000087f6b90a28c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000000016cf600000000000000000000000000000000000000000000000000000000155e6c8c0000000000000000000000000b5a646adf963cbe6b0574947976afc19a8b42ef8000000000000000000000000b5a646adf963cbe6b0574947976afc19a8b42ef8000000000000000000000000b5a646adf963cbe6b0574947976afc19a8b42ef8000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000c4c28e83fd000000000000000000000000201169156a01750c638811874bb84cdaeda12b3b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044c92ff21f0000000000000000000000007de3fa3b1a17d18bf8aec29dd0f8f498849c5c390000000000000000000000004d29069a89b0a9aa16f49115bed6572a8f9160e70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; 117 | const event = newMockEvent() 118 | // const event = NitroTicketCreatedEvent(newMockEvent()); 119 | event.transaction.input = Bytes.fromHexString(txInput); 120 | const data = parseRetryableInput(event) 121 | 122 | assert.bigIntEquals(BigInt.fromU64(149493736155328), data.deposit) 123 | assert.bigIntEquals(BigInt.fromU64(0), data.l2Callvalue) 124 | assert.bytesEquals( 125 | Bytes.fromHexString("0xc28e83fd000000000000000000000000201169156a01750c638811874bb84cdaeda12b3b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044c92ff21f0000000000000000000000007de3fa3b1a17d18bf8aec29dd0f8f498849c5c390000000000000000000000004d29069a89b0a9aa16f49115bed6572a8f9160e700000000000000000000000000000000000000000000000000000000"), 126 | data.l2Calldata 127 | ) 128 | assert.addressEquals( 129 | Address.fromBytes(Bytes.fromHexString("0xb5a646adf963cbe6b0574947976afc19a8b42ef8")), 130 | data.l2To 131 | ) 132 | }) 133 | 134 | // test("Can process eth deposit", () => { 135 | // let newEthDeposit = createEthDeposit(); 136 | // handleClassicTicketCreated(newEthDeposit); 137 | 138 | // let entity = L1ToL2Transaction.load(newEthDeposit.transaction.hash.toHex()); 139 | // if (!entity) throw new Error("No entity found"); 140 | 141 | // assert.booleanEquals(entity.looksLikeEthDeposit, true); 142 | // assert.bigIntEquals(entity.l2Callvalue, BigInt.zero()); 143 | 144 | // const expectedL2Calldata = "0x"; 145 | // assert.stringEquals(entity.l2Calldata.toHex(), expectedL2Calldata); 146 | // }); 147 | 148 | // test("Can process token deposit", () => { 149 | // let newTokenDeposit = createTokenDeposit(); 150 | // handleClassicTicketCreated(newTokenDeposit); 151 | 152 | // let entity = L1ToL2Transaction.load(newTokenDeposit.transaction.hash.toHex()); 153 | // if (!entity) throw new Error("No entity found"); 154 | 155 | // assert.booleanEquals(entity.looksLikeEthDeposit, false); 156 | // assert.bigIntEquals(entity.l2Callvalue, BigInt.zero()); 157 | 158 | // const expectedL2Calldata = 159 | // "0x2e567b36000000000000000000000000393fd5b96f6459511d7368778318c31d719720ad000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f000000000000000000000000e0bff5a3e31fbc9bfcc229e4e8f15236dd37029f00000000000000000000000000000000000000000000003635c9adc5dea0000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; 160 | // assert.stringEquals(entity.l2Calldata.toHex(), expectedL2Calldata); 161 | // }); 162 | -------------------------------------------------------------------------------- /packages/layer2-token-gateway/tests/weth/weth.test.ts: -------------------------------------------------------------------------------- 1 | import { addressToId, getJoinId, handleGatewaySet, handleDeposit, DISABLED_GATEWAY_ADDR } from "../../src/mapping"; 2 | import { Address, BigInt, Bytes, ethereum, store, log } from "@graphprotocol/graph-ts"; 3 | import { newMockEvent, test, assert, createMockedFunction } from "matchstick-as"; 4 | import { GatewaySet as GatewaySetEvent } from "../../generated/L2GatewayRouter/L2GatewayRouter"; 5 | import { DepositFinalized as DepositFinalizedEvent } from "../../generated/templates/L2ArbitrumGateway/L2ArbitrumGateway"; 6 | import { Gateway, Token, TokenGatewayJoinTable } from "../../generated/schema"; 7 | 8 | const createGatewaySet = (token: Address, gateway: Address): GatewaySetEvent => { 9 | let mockEvent = newMockEvent(); 10 | 11 | let parameters = new Array(); 12 | let tokenParam = new ethereum.EventParam("l1Token", ethereum.Value.fromAddress(token)); 13 | let gatewayParam = new ethereum.EventParam("gateway", ethereum.Value.fromAddress(gateway)); 14 | 15 | parameters.push(tokenParam); 16 | parameters.push(gatewayParam); 17 | 18 | let newGatewayEvent = new GatewaySetEvent(mockEvent.address, mockEvent.logIndex, mockEvent.transactionLogIndex, 19 | mockEvent.logType, mockEvent.block, mockEvent.transaction, parameters, mockEvent.receipt) 20 | 21 | return newGatewayEvent 22 | } 23 | 24 | const createDisabledGatewayEvent = (token: Address): GatewaySetEvent => { 25 | return createGatewaySet(token, DISABLED_GATEWAY_ADDR) 26 | } 27 | 28 | const createDepositFinalized = (token: Address, gateway: Address): DepositFinalizedEvent => { 29 | let mockEvent = newMockEvent(); 30 | 31 | let parameters = new Array(); 32 | let tokenParam = new ethereum.EventParam("l1Token", ethereum.Value.fromAddress(token)); 33 | // let fromParam = new ethereum.EventParam("_from", ethereum.Value.fromAddress(from)); 34 | // let toParam = new ethereum.EventParam("_to", ethereum.Value.fromAddress(to)); 35 | // let amountParam = new ethereum.EventParam("_amount", ethereum.Value.fromI32(amount)); 36 | 37 | parameters.push(tokenParam); 38 | // parameters.push(fromParam); 39 | // parameters.push(toParam); 40 | // parameters.push(amountParam); 41 | 42 | let tx = mockEvent.transaction 43 | tx.to = gateway 44 | 45 | let newDepositFinalized = new DepositFinalizedEvent(mockEvent.address, mockEvent.logIndex, mockEvent.transactionLogIndex, 46 | mockEvent.logType, mockEvent.block, tx, parameters, mockEvent.receipt) 47 | 48 | return newDepositFinalized 49 | } 50 | 51 | // test("Can process gateway set event followed by deposit", () => { 52 | // const tokenAddr = Address.fromString("0xc778417e063141139fce010982780140aa0cd5ab") 53 | // const gatewayAddr = Address.fromString("0xf94bc045c4e926cc0b34e8d1c41cd7a043304ac9") 54 | 55 | // let newGatewaySetEvent1 = createGatewaySet(tokenAddr, gatewayAddr) 56 | // handleGatewaySet(newGatewaySetEvent1) 57 | 58 | 59 | // const joinTableId = getJoinId( 60 | // addressToId(gatewayAddr), 61 | // addressToId(tokenAddr) 62 | // ) 63 | 64 | // let joinTableRes1 = TokenGatewayJoinTable.load(joinTableId) 65 | // if(!joinTableRes1) throw new Error("No join table found") 66 | 67 | // assert.stringEquals( 68 | // joinTableRes1.gateway.toString(), 69 | // gatewayAddr.toHexString() 70 | // ) 71 | // assert.stringEquals( 72 | // joinTableRes1.token.toString(), 73 | // tokenAddr.toHexString() 74 | // ) 75 | 76 | // let newDepositFinalizedEvent1 = createDepositFinalized(tokenAddr, gatewayAddr); 77 | // handleDeposit(newDepositFinalizedEvent1); 78 | 79 | // let joinTableRes2 = TokenGatewayJoinTable.load(joinTableId) 80 | // if(!joinTableRes2) throw new Error("No join table found") 81 | 82 | // assert.stringEquals( 83 | // joinTableRes2.gateway.toString(), 84 | // gatewayAddr.toHexString() 85 | // ) 86 | // assert.stringEquals( 87 | // joinTableRes2.token.toString(), 88 | // tokenAddr.toHexString() 89 | // ) 90 | // }) 91 | 92 | // test("Can disable then enable gateway", () => { 93 | // const tokenAddr = Address.fromString("0xc778417e063141139fce010982780140aa065431") 94 | // const gatewayAddr = Address.fromString("0xf94bc045c4e926cc0b34e8d1c41cd7a0145156a5") 95 | // const joinTableId = getJoinId( 96 | // addressToId(gatewayAddr), 97 | // addressToId(tokenAddr) 98 | // ) 99 | // const disabledJoinTableId = getJoinId( 100 | // addressToId(DISABLED_GATEWAY_ADDR), 101 | // addressToId(tokenAddr) 102 | // ) 103 | 104 | // let newGatewaySetEvent0 = createDisabledGatewayEvent(tokenAddr) 105 | // handleGatewaySet(newGatewaySetEvent0) 106 | 107 | // let joinTableRes0 = TokenGatewayJoinTable.load(disabledJoinTableId) 108 | // if(!joinTableRes0) throw new Error("No join table found") 109 | 110 | // assert.stringEquals( 111 | // joinTableRes0.gateway.toString(), 112 | // DISABLED_GATEWAY_ADDR.toHexString() 113 | // ) 114 | 115 | // assert.stringEquals( 116 | // joinTableRes0.token.toString(), 117 | // tokenAddr.toHexString() 118 | // ) 119 | // let newGatewaySetEvent1 = createGatewaySet(tokenAddr, gatewayAddr) 120 | // handleGatewaySet(newGatewaySetEvent1) 121 | 122 | // let joinTableRes1 = TokenGatewayJoinTable.load(joinTableId) 123 | // if(!joinTableRes1) throw new Error("No join table found") 124 | 125 | // assert.stringEquals( 126 | // joinTableRes1.gateway.toString(), 127 | // gatewayAddr.toHexString() 128 | // ) 129 | // assert.stringEquals( 130 | // joinTableRes1.token.toString(), 131 | // tokenAddr.toHexString() 132 | // ) 133 | 134 | // let newDepositFinalizedEvent1 = createDepositFinalized(tokenAddr, gatewayAddr); 135 | // handleDeposit(newDepositFinalizedEvent1); 136 | 137 | // let joinTableRes2 = TokenGatewayJoinTable.load(joinTableId) 138 | // if(!joinTableRes2) throw new Error("No join table found") 139 | 140 | // assert.stringEquals( 141 | // joinTableRes2.gateway.toString(), 142 | // gatewayAddr.toHexString() 143 | // ) 144 | // assert.stringEquals( 145 | // joinTableRes2.token.toString(), 146 | // tokenAddr.toHexString() 147 | // ) 148 | // }) 149 | -------------------------------------------------------------------------------- /packages/orbit-deployments/abis/ProxyAdmin.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, 6 | { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } 7 | ], 8 | "name": "OwnershipTransferred", 9 | "type": "event" 10 | }, 11 | { 12 | "inputs": [ 13 | { 14 | "internalType": "contract TransparentUpgradeableProxy", 15 | "name": "proxy", 16 | "type": "address" 17 | }, 18 | { "internalType": "address", "name": "newAdmin", "type": "address" } 19 | ], 20 | "name": "changeProxyAdmin", 21 | "outputs": [], 22 | "stateMutability": "nonpayable", 23 | "type": "function" 24 | }, 25 | { 26 | "inputs": [ 27 | { "internalType": "contract TransparentUpgradeableProxy", "name": "proxy", "type": "address" } 28 | ], 29 | "name": "getProxyAdmin", 30 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 31 | "stateMutability": "view", 32 | "type": "function" 33 | }, 34 | { 35 | "inputs": [ 36 | { "internalType": "contract TransparentUpgradeableProxy", "name": "proxy", "type": "address" } 37 | ], 38 | "name": "getProxyImplementation", 39 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 40 | "stateMutability": "view", 41 | "type": "function" 42 | }, 43 | { 44 | "inputs": [], 45 | "name": "owner", 46 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 47 | "stateMutability": "view", 48 | "type": "function" 49 | }, 50 | { 51 | "inputs": [], 52 | "name": "renounceOwnership", 53 | "outputs": [], 54 | "stateMutability": "nonpayable", 55 | "type": "function" 56 | }, 57 | { 58 | "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], 59 | "name": "transferOwnership", 60 | "outputs": [], 61 | "stateMutability": "nonpayable", 62 | "type": "function" 63 | }, 64 | { 65 | "inputs": [ 66 | { 67 | "internalType": "contract TransparentUpgradeableProxy", 68 | "name": "proxy", 69 | "type": "address" 70 | }, 71 | { "internalType": "address", "name": "implementation", "type": "address" } 72 | ], 73 | "name": "upgrade", 74 | "outputs": [], 75 | "stateMutability": "nonpayable", 76 | "type": "function" 77 | }, 78 | { 79 | "inputs": [ 80 | { 81 | "internalType": "contract TransparentUpgradeableProxy", 82 | "name": "proxy", 83 | "type": "address" 84 | }, 85 | { "internalType": "address", "name": "implementation", "type": "address" }, 86 | { "internalType": "bytes", "name": "data", "type": "bytes" } 87 | ], 88 | "name": "upgradeAndCall", 89 | "outputs": [], 90 | "stateMutability": "payable", 91 | "type": "function" 92 | } 93 | ] 94 | -------------------------------------------------------------------------------- /packages/orbit-deployments/abis/RollupCreator.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, 3 | { 4 | "anonymous": false, 5 | "inputs": [ 6 | { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, 7 | { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } 8 | ], 9 | "name": "OwnershipTransferred", 10 | "type": "event" 11 | }, 12 | { 13 | "anonymous": false, 14 | "inputs": [ 15 | { "indexed": true, "internalType": "address", "name": "rollupAddress", "type": "address" }, 16 | { "indexed": false, "internalType": "address", "name": "inboxAddress", "type": "address" }, 17 | { "indexed": false, "internalType": "address", "name": "adminProxy", "type": "address" }, 18 | { "indexed": false, "internalType": "address", "name": "sequencerInbox", "type": "address" }, 19 | { "indexed": false, "internalType": "address", "name": "bridge", "type": "address" } 20 | ], 21 | "name": "RollupCreated", 22 | "type": "event" 23 | }, 24 | { "anonymous": false, "inputs": [], "name": "TemplatesUpdated", "type": "event" }, 25 | { 26 | "inputs": [], 27 | "name": "bridgeCreator", 28 | "outputs": [{ "internalType": "contract BridgeCreator", "name": "", "type": "address" }], 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "inputs": [], 34 | "name": "challengeManagerTemplate", 35 | "outputs": [{ "internalType": "contract IChallengeManager", "name": "", "type": "address" }], 36 | "stateMutability": "view", 37 | "type": "function" 38 | }, 39 | { 40 | "inputs": [ 41 | { 42 | "components": [ 43 | { "internalType": "uint64", "name": "confirmPeriodBlocks", "type": "uint64" }, 44 | { "internalType": "uint64", "name": "extraChallengeTimeBlocks", "type": "uint64" }, 45 | { "internalType": "address", "name": "stakeToken", "type": "address" }, 46 | { "internalType": "uint256", "name": "baseStake", "type": "uint256" }, 47 | { "internalType": "bytes32", "name": "wasmModuleRoot", "type": "bytes32" }, 48 | { "internalType": "address", "name": "owner", "type": "address" }, 49 | { "internalType": "address", "name": "loserStakeEscrow", "type": "address" }, 50 | { "internalType": "uint256", "name": "chainId", "type": "uint256" }, 51 | { "internalType": "string", "name": "chainConfig", "type": "string" }, 52 | { "internalType": "uint64", "name": "genesisBlockNum", "type": "uint64" }, 53 | { 54 | "components": [ 55 | { "internalType": "uint256", "name": "delayBlocks", "type": "uint256" }, 56 | { "internalType": "uint256", "name": "futureBlocks", "type": "uint256" }, 57 | { "internalType": "uint256", "name": "delaySeconds", "type": "uint256" }, 58 | { "internalType": "uint256", "name": "futureSeconds", "type": "uint256" } 59 | ], 60 | "internalType": "struct ISequencerInbox.MaxTimeVariation", 61 | "name": "sequencerInboxMaxTimeVariation", 62 | "type": "tuple" 63 | } 64 | ], 65 | "internalType": "struct Config", 66 | "name": "config", 67 | "type": "tuple" 68 | } 69 | ], 70 | "name": "createRollup", 71 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 72 | "stateMutability": "nonpayable", 73 | "type": "function" 74 | }, 75 | { 76 | "inputs": [], 77 | "name": "osp", 78 | "outputs": [{ "internalType": "contract IOneStepProofEntry", "name": "", "type": "address" }], 79 | "stateMutability": "view", 80 | "type": "function" 81 | }, 82 | { 83 | "inputs": [], 84 | "name": "owner", 85 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 86 | "stateMutability": "view", 87 | "type": "function" 88 | }, 89 | { 90 | "inputs": [], 91 | "name": "renounceOwnership", 92 | "outputs": [], 93 | "stateMutability": "nonpayable", 94 | "type": "function" 95 | }, 96 | { 97 | "inputs": [], 98 | "name": "rollupAdminLogic", 99 | "outputs": [{ "internalType": "contract IRollupAdmin", "name": "", "type": "address" }], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "inputs": [], 105 | "name": "rollupUserLogic", 106 | "outputs": [{ "internalType": "contract IRollupUser", "name": "", "type": "address" }], 107 | "stateMutability": "view", 108 | "type": "function" 109 | }, 110 | { 111 | "inputs": [ 112 | { "internalType": "contract BridgeCreator", "name": "_bridgeCreator", "type": "address" }, 113 | { "internalType": "contract IOneStepProofEntry", "name": "_osp", "type": "address" }, 114 | { 115 | "internalType": "contract IChallengeManager", 116 | "name": "_challengeManagerLogic", 117 | "type": "address" 118 | }, 119 | { "internalType": "contract IRollupAdmin", "name": "_rollupAdminLogic", "type": "address" }, 120 | { "internalType": "contract IRollupUser", "name": "_rollupUserLogic", "type": "address" }, 121 | { "internalType": "address", "name": "_validatorUtils", "type": "address" }, 122 | { "internalType": "address", "name": "_validatorWalletCreator", "type": "address" } 123 | ], 124 | "name": "setTemplates", 125 | "outputs": [], 126 | "stateMutability": "nonpayable", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], 131 | "name": "transferOwnership", 132 | "outputs": [], 133 | "stateMutability": "nonpayable", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "validatorUtils", 139 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 140 | "stateMutability": "view", 141 | "type": "function" 142 | }, 143 | { 144 | "inputs": [], 145 | "name": "validatorWalletCreator", 146 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 147 | "stateMutability": "view", 148 | "type": "function" 149 | } 150 | ] 151 | -------------------------------------------------------------------------------- /packages/orbit-deployments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orbit-deployments", 3 | "version": "0.0.1", 4 | "license": "UNLICENSED", 5 | "scripts": { 6 | "codegen": "graph codegen", 7 | "build": "graph build", 8 | "deploy": "graph deploy --node https://api.thegraph.com/deploy/ gvladika/orbit-deployments-goerli" 9 | }, 10 | "dependencies": { 11 | "@graphprotocol/graph-ts": "0.29.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/orbit-deployments/schema.graphql: -------------------------------------------------------------------------------- 1 | type RollupCreator @entity { 2 | id: Bytes! 3 | totalRollupsCreated: BigInt! 4 | totalRollupsWithPostedBatches: BigInt! 5 | } 6 | 7 | type Rollup @entity { 8 | id: Bytes! 9 | inbox: Bytes! 10 | adminProxy: Bytes! 11 | sequencerInbox: Bytes! 12 | bridge: Bytes! 13 | outbox: Bytes! 14 | chainId: BigInt! 15 | baseStake: BigInt! 16 | latestConfirmed: BigInt! 17 | rollupDeploymentBlock: BigInt! 18 | stakeToken: Bytes! 19 | owner: Bytes! 20 | deployer: Bytes! 21 | numOfBatches: BigInt! 22 | } 23 | -------------------------------------------------------------------------------- /packages/orbit-deployments/src/rollup-creator.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt } from "@graphprotocol/graph-ts"; 2 | import { RollupCreated as RollupCreatedEvent } from "../generated/RollupCreator/RollupCreator"; 3 | import { Rollup as RollupContract } from "../generated/RollupCreator/Rollup"; 4 | import { Rollup, RollupCreator } from "../generated/schema"; 5 | import { SequencerInbox } from "../generated/templates"; 6 | 7 | export function handleRollupCreated(event: RollupCreatedEvent): void { 8 | const rollupCreator = getOrCreateRollupCreator(event.address); 9 | rollupCreator.totalRollupsCreated = rollupCreator.totalRollupsCreated.plus(BigInt.fromI32(1)); 10 | rollupCreator.save(); 11 | 12 | const rollup = new Rollup(event.params.rollupAddress); 13 | rollup.inbox = event.params.inboxAddress; 14 | rollup.adminProxy = event.params.adminProxy; 15 | rollup.bridge = event.params.bridge; 16 | rollup.sequencerInbox = event.params.sequencerInbox; 17 | rollup.deployer = event.transaction.from; 18 | 19 | // fetch remaining info from contract 20 | const rollupContract = RollupContract.bind(Address.fromBytes(rollup.id)); 21 | rollup.outbox = rollupContract.outbox(); 22 | rollup.chainId = rollupContract.chainId(); 23 | rollup.baseStake = rollupContract.baseStake(); 24 | rollup.latestConfirmed = rollupContract.latestConfirmed(); 25 | rollup.rollupDeploymentBlock = rollupContract.rollupDeploymentBlock(); 26 | rollup.stakeToken = rollupContract.stakeToken(); 27 | rollup.owner = rollupContract.owner(); 28 | rollup.numOfBatches = BigInt.fromI32(0); 29 | 30 | rollup.save(); 31 | 32 | startIndexingSequencerInbox(Address.fromBytes(rollup.sequencerInbox)); 33 | } 34 | 35 | function getOrCreateRollupCreator(rollupCreatorAddress: Address): RollupCreator { 36 | let rollupCreator = RollupCreator.load(rollupCreatorAddress); 37 | if (rollupCreator != null) { 38 | return rollupCreator as RollupCreator; 39 | } 40 | 41 | rollupCreator = new RollupCreator(rollupCreatorAddress); 42 | rollupCreator.totalRollupsCreated = BigInt.fromI32(0); 43 | rollupCreator.totalRollupsWithPostedBatches = BigInt.fromI32(0); 44 | rollupCreator.save(); 45 | 46 | return rollupCreator; 47 | } 48 | 49 | function startIndexingSequencerInbox(seqInbox: Address): void { 50 | SequencerInbox.create(seqInbox); 51 | } 52 | -------------------------------------------------------------------------------- /packages/orbit-deployments/src/sequencer-inbox.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt } from "@graphprotocol/graph-ts"; 2 | import { SequencerBatchDelivered as SequencerBatchDeliveredEvent } from "../generated/templates/SequencerInbox/SequencerInbox"; 3 | import { SequencerInbox as SequencerInboxContract } from "../generated/templates/SequencerInbox/SequencerInbox"; 4 | import { Rollup, RollupCreator } from "../generated/schema"; 5 | 6 | const ROLLUP_CREATOR = "0xcb6e6240682eba7b24c82c8a8fd1655b36c23f95"; 7 | 8 | export function handleSequencerBatchDelivered(event: SequencerBatchDeliveredEvent): void { 9 | const seqInboxContract = SequencerInboxContract.bind(event.address); 10 | const rollupAddress = seqInboxContract.rollup(); 11 | 12 | // numOfBatches++ 13 | const rollup = Rollup.load(rollupAddress) as Rollup; 14 | rollup.numOfBatches = rollup.numOfBatches.plus(BigInt.fromI32(1)); 15 | rollup.save(); 16 | 17 | if (rollup.numOfBatches.equals(BigInt.fromI32(2))) { 18 | const rollupCreator = RollupCreator.load( 19 | Address.fromHexString(ROLLUP_CREATOR) 20 | ) as RollupCreator; 21 | rollupCreator.totalRollupsWithPostedBatches = rollupCreator.totalRollupsWithPostedBatches.plus( 22 | BigInt.fromI32(1) 23 | ); 24 | rollupCreator.save(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/orbit-deployments/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.5 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum 6 | name: RollupCreator 7 | network: arbitrum-goerli 8 | source: 9 | address: "0xCB6E6240682EbA7b24c82c8a8fd1655b36C23F95" 10 | abi: RollupCreator 11 | startBlock: 27086873 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.7 15 | language: wasm/assemblyscript 16 | entities: 17 | - RollupCreator 18 | - Rollup 19 | abis: 20 | - name: RollupCreator 21 | file: ./abis/RollupCreator.json 22 | - name: Rollup 23 | file: ./abis/Rollup.json 24 | eventHandlers: 25 | - event: RollupCreated(indexed address,address,address,address,address) 26 | handler: handleRollupCreated 27 | file: ./src/rollup-creator.ts 28 | 29 | templates: 30 | - kind: ethereum 31 | name: SequencerInbox 32 | network: arbitrum-goerli 33 | source: 34 | abi: SequencerInbox 35 | mapping: 36 | kind: ethereum/events 37 | apiVersion: 0.0.7 38 | language: wasm/assemblyscript 39 | entities: 40 | - Rollup 41 | abis: 42 | - name: RollupCreator 43 | file: ./abis/RollupCreator.json 44 | - name: SequencerInbox 45 | file: ./abis/SequencerInbox.json 46 | eventHandlers: 47 | - event: SequencerBatchDelivered(indexed uint256,indexed bytes32,indexed bytes32,bytes32,uint256,(uint64,uint64,uint64,uint64),uint8) 48 | handler: handleSequencerBatchDelivered 49 | file: ./src/sequencer-inbox.ts 50 | -------------------------------------------------------------------------------- /packages/orbit-deployments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/goerli.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "goerli", 3 | "l2Network": "arbitrum-goerli", 4 | "arbRetryableTxStartBlock": 0, 5 | "arbSysStartBlock": 0, 6 | "l2GatewayRouter": "0xE5B9d8d42d656d1DcB8065A6c012FE3780246041", 7 | "l2GatewayRouterDeployBlock": 5, 8 | "l2StandardGateway": "0x2eC7Bc552CE8E51f098325D2FcF0d3b9d3d2A9a2", 9 | "l2StandardGatewayDeployBlock": 7, 10 | "outboxAddress": "0x45af9ed1d03703e480ce7d328fb684bb67da5049", 11 | "outboxDeploymentBlock": 7217526, 12 | "inboxAddress": "0x6bebc4925716945d46f0ec336d5c2564f419682c", 13 | "inboxDeploymentBlock": 7217526, 14 | "bridgeAddress": "0xaf4159a80b6cc41ed517db1c453d1ef5c2e4db72", 15 | "bridgeDeploymentBlock": 7217526, 16 | "rollupAddress": "0x45e5caea8768f42b385a366d3551ad1e0cbfab17", 17 | "rollupDeploymentBlock": 7217526, 18 | "l1GatewayRouter": "0x4c7708168395aEa569453Fc36862D2ffcDaC588c", 19 | "l1GatewayRouterDeployBlock": 7223070, 20 | "nitroGenesisBlockNum": 0, 21 | "L1USDCMessageTransmitter": "0x26413e8157cd32011e726065a5462e97dd4d03d9", 22 | "L2USDCMessageTransmitter": "0x109bc137cb64eab7c0b1dddd1edf341467dc2d35", 23 | "L2USDCMessageTransmitterStartBlock": 18459566, 24 | "L1USDCMessageTransmitterStartBlock": 8123751, 25 | "L1USDCContract": "0x07865c6e87b9f70255377e024ace6630c1eaa37f", 26 | "L2USDCContract": "0xfd064a18f3bf249cf1f87fc203e90d8f650f2d63" 27 | } 28 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "mainnet", 3 | "l2Network": "arbitrum-one", 4 | "outboxDeploymentBlock": 13124585, 5 | "outboxAddress": "0x760723CD2e632826c38Fef8CD438A4CC7E7E1A40", 6 | "inboxDeploymentBlock": 12525700, 7 | "inboxAddress": "0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f", 8 | "bridgeDeploymentBlock": 12525700, 9 | "bridgeAddress": "0x011b6e24ffb0b5f5fcc564cf4183c5bbbc96d515", 10 | "arbRetryableTxStartBlock": 0, 11 | "arbSysStartBlock": 0, 12 | "l2GatewayRouter": "0x5288c571Fd7aD117beA99bF60FE0846C4E84F933", 13 | "l2GatewayRouterDeployBlock": 2108, 14 | "l2StandardGateway": "0x09e9222E96E7B4AE2a407B98d48e330053351EEe", 15 | "l2StandardGatewayDeployBlock": 2107, 16 | "l1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", 17 | "l1GatewayRouterDeployBlock": 12640865, 18 | "rollupAddress": "0xC12BA48c781F6e392B49Db2E25Cd0c28cD77531A", 19 | "rollupDeploymentBlock": 12525700, 20 | "nitroGenesisBlockNum": 99999999999999, 21 | "L1USDCMessageTransmitter": "0x0a992d191deec32afe36203ad87d7d289a738f81", 22 | "L2USDCMessageTransmitter": "0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca", 23 | "L2USDCMessageTransmitterStartBlock": 91339254, 24 | "L1USDCMessageTransmitterStartBlock": 16730025, 25 | "L1USDCContract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 26 | "L2USDCContract": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", 27 | "l1Teleporter": "0xCBd9c6e310D6AaDeF9F025f716284162F0158992", 28 | "l1TeleporterDeployBlock": 19775948 29 | } 30 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/nitro-mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "mainnet", 3 | "l2Network": "arbitrum-one", 4 | "outboxDeploymentBlock": 15411056, 5 | "outboxAddress": "0x0b9857ae2d4a3dbe74ffe1d7df045bb7f96e4840", 6 | "inboxDeploymentBlock": 12525700, 7 | "inboxAddress": "0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f", 8 | "bridgeDeploymentBlock": 15411056, 9 | "bridgeAddress": "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a", 10 | "classicBridgeDeploymentBlock": 12525700, 11 | "classicBridgeAddress": "0x011b6e24ffb0b5f5fcc564cf4183c5bbbc96d515", 12 | "rollupAddress": "0x5ef0d09d1e6204141b4d37530808ed19f60fba35", 13 | "rollupDeploymentBlock": 15411056, 14 | "l1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", 15 | "l1GatewayRouterDeployBlock": 12640865, 16 | "nitroGenesisBlockNum": 22207817 17 | } 18 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/nova-mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "mainnet", 3 | "outboxAddress": "0xd4b80c3d7240325d18e645b49e6535a3bf95cc58", 4 | "outboxDeploymentBlock": 15016829, 5 | "inboxAddress": "0xc4448b71118c9071bcb9734a0eac55d18a153949", 6 | "inboxDeploymentBlock": 15016829, 7 | "bridgeAddress": "0xc1ebd02f738644983b6c4b2d440b8e77dde276bd", 8 | "bridgeDeploymentBlock": 15016829, 9 | "rollupAddress": "0xfb209827c58283535b744575e11953dcc4bead88", 10 | "rollupDeploymentBlock": 15016829, 11 | "l1GatewayRouter": "0xC840838Bc438d73C16c2f8b22D2Ce3669963cD48", 12 | "l1GatewayRouterDeployBlock": 15033094 13 | } 14 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/nova.json: -------------------------------------------------------------------------------- 1 | { 2 | "l2Network": "arbitrum-nova", 3 | "outboxDeploymentBlock": 15016829, 4 | "arbRetryableTxStartBlock": 0, 5 | "arbSysStartBlock": 0, 6 | "outboxAddress": "0xD4B80C3D7240325D18E645B49e6535A3Bf95cc58", 7 | "inboxDeploymentBlock": 15016829, 8 | "inboxAddress": "0xc4448b71118c9071bcb9734a0eac55d18a153949", 9 | "bridgeDeploymentBlock": 15016829, 10 | "bridgeAddress": "0xC1Ebd02f738644983b6C4B2d440b8e77DdE276Bd", 11 | "rollupAddress": "0xFb209827c58283535b744575e11953DCC4bEAD88", 12 | "rollupDeploymentBlock": 15016829, 13 | "nitroGenesisBlockNum": 0, 14 | "l2GatewayRouter": "0x21903d3F8176b1a0c17E953Cd896610Be9fFDFa8", 15 | "l2GatewayRouterDeployBlock": 12, 16 | "l2StandardGateway": "0xcF9bAb7e53DDe48A6DC4f286CB14e05298799257", 17 | "l2StandardGatewayDeployBlock": 14, 18 | "l1GatewayRouter": "0xC840838Bc438d73C16c2f8b22D2Ce3669963cD48", 19 | "l1GatewayRouterDeployBlock": 15033094 20 | } 21 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/rinkeby.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "rinkeby", 3 | "l2Network": "arbitrum-rinkeby", 4 | "outboxDeploymentBlock": 9203449, 5 | "outboxAddress": "0x2360A33905dc1c72b12d975d975F42BaBdcef9F3", 6 | "inboxDeploymentBlock": 8700589, 7 | "inboxAddress": "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", 8 | "bridgeDeploymentBlock": 8700589, 9 | "bridgeAddress": "0x9a28e783c47bbeb813f32b861a431d0776681e95", 10 | "arbRetryableTxStartBlock": 0, 11 | "arbSysStartBlock": 0, 12 | "l2GatewayRouter": "0x9413AD42910c1eA60c737dB5f58d1C504498a3cD", 13 | "l2GatewayRouterDeployBlock": 123, 14 | "l2StandardGateway": "0x195C107F3F75c4C93Eba7d9a1312F19305d6375f", 15 | "l2StandardGatewayDeployBlock": 122, 16 | "l1GatewayRouter": "0x70C143928eCfFaf9F5b406f7f4fC28Dc43d68380", 17 | "l1GatewayRouterDeployBlock": 16210, 18 | "rollupAddress": "0xFe2c86CF40F89Fe2F726cFBBACEBae631300b50c", 19 | "rollupDeploymentBlock": 8700589, 20 | "nitroGenesisBlockNum": 13919178 21 | } 22 | -------------------------------------------------------------------------------- /packages/subgraph-common/config/sepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "l1Network": "sepolia", 3 | "l2Network": "arbitrum-sepolia", 4 | "arbRetryableTxStartBlock": 0, 5 | "arbSysStartBlock": 0, 6 | "l2GatewayRouter": "0x9fDD1C4E4AA24EEc1d913FABea925594a20d43C7", 7 | "l2GatewayRouterDeployBlock": 124, 8 | "l2StandardGateway": "0x6e244cD02BBB8a6dbd7F626f05B2ef82151Ab502", 9 | "l2StandardGatewayDeployBlock": 126, 10 | "outboxAddress": "0x65f07C7D521164a4d5DaC6eB8Fac8DA067A3B78F", 11 | "outboxDeploymentBlock": 4139226, 12 | "inboxAddress": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", 13 | "inboxDeploymentBlock": 4139226, 14 | "bridgeAddress": "0x38f918D0E9F1b721EDaA41302E399fa1B79333a9", 15 | "bridgeDeploymentBlock": 4139226, 16 | "rollupAddress": "0xd80810638dbDF9081b72C1B33c65375e807281C8", 17 | "rollupDeploymentBlock": 4139226, 18 | "l1GatewayRouter": "0xcE18836b233C83325Cc8848CA4487e94C6288264", 19 | "l1GatewayRouterDeployBlock": 4143154, 20 | "nitroGenesisBlockNum": 0, 21 | "L1USDCMessageTransmitter": "0x7865fAfC2db2093669d92c0F33AeEF291086BEFD", 22 | "L2USDCMessageTransmitter": "0xaCF1ceeF35caAc005e15888dDb8A3515C41B4872", 23 | "L1USDCMessageTransmitterStartBlock": 5015228, 24 | "L2USDCMessageTransmitterStartBlock": 3250777, 25 | "L1USDCContract": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", 26 | "L2USDCContract": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", 27 | "l1Teleporter": "0x9E86BbF020594D7FFe05bF32EEDE5b973579A968", 28 | "l1TeleporterDeployBlock": 5342153 29 | } 30 | -------------------------------------------------------------------------------- /packages/subgraph-common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/helpers"; 2 | -------------------------------------------------------------------------------- /packages/subgraph-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arbitrum/subgraph-common", 3 | "version": "0.0.1", 4 | "license": "Apache-2.0", 5 | "private": true, 6 | "dependencies": { 7 | "@graphprotocol/graph-ts": "^0.32.0", 8 | "mustache": "^4.2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/subgraph-common/src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { ByteArray, BigInt, Address } from "@graphprotocol/graph-ts"; 2 | 3 | export const appendBytes = (a: ByteArray, b: ByteArray): ByteArray => { 4 | let result = new ByteArray(a.length + b.length); 5 | 6 | for (let i: i32 = 0; i < a.length; i++) { 7 | result[i] = a[i]; 8 | } 9 | for (let i: i32 = 0; i < b.length; i++) { 10 | result[a.length + i] = b[i]; 11 | } 12 | return result; 13 | }; 14 | 15 | export const padBytes = (_a: ByteArray, expectedLength: i32): ByteArray => { 16 | if (_a.byteLength < expectedLength) { 17 | const paddingContent = "00".repeat(expectedLength - _a.length); 18 | return ByteArray.fromHexString( 19 | "0x" + paddingContent + _a.toHexString().substr(2) 20 | ); 21 | } 22 | return _a; 23 | }; 24 | 25 | export const encodePadded = (_a: ByteArray, _b: ByteArray): ByteArray => 26 | appendBytes(padBytes(_a, 32), padBytes(_b, 32)); 27 | 28 | export const RETRYABLE_LIFETIME_SECONDS = BigInt.fromI32(604800); 29 | 30 | export const bigIntToAddress = (input: BigInt): Address => { 31 | // remove the prepended 0x 32 | const hexString = input.toHexString().substr(2); 33 | // add missing padding so address is 20 bytes long 34 | const missingZeroes = "0".repeat(40 - hexString.length); 35 | // build hexstring again 36 | const addressString = "0x" + missingZeroes + hexString; 37 | return Address.fromString(addressString); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/teleporter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teleporter", 3 | "version": "0.0.3", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "codegen": "yarn prepare:mainnet && graph codegen", 7 | "postinstall": "yarn codegen", 8 | 9 | "prepare:mainnet": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/mainnet.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 10 | "prepare:sepolia": "yarn workspace @arbitrum/subgraph-common mustache $(pwd)/../subgraph-common/config/sepolia.json $(pwd)/subgraph.template.yaml | tail -n +2 > subgraph.yaml", 11 | 12 | "build:mainnet": "yarn prepare:mainnet && graph build", 13 | "build:sepolia": "yarn prepare:sepolia && graph build", 14 | 15 | "studio": "graph deploy --studio -l $(cat package.json | jq -r '.version')", 16 | "studio:mainnet": "yarn build:mainnet && yarn studio", 17 | "studio:sepolia": "yarn build:sepolia && yarn studio" 18 | }, 19 | "dependencies": { 20 | "@arbitrum/subgraph-common": "^0.0.1", 21 | "@graphprotocol/graph-ts": "^0.32.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/teleporter/schema.graphql: -------------------------------------------------------------------------------- 1 | type Teleported @entity { 2 | id: ID! 3 | transactionHash: Bytes! # bytes32 4 | sender: Bytes! # address 5 | l1Token: Bytes! # address 6 | l3FeeTokenL1Addr: Bytes! # address 7 | l1l2Router: Bytes! # address 8 | l2l3RouterOrInbox: Bytes! # address 9 | to: Bytes! # address 10 | amount: BigInt! # uint256 11 | blockNumber: BigInt! # uint256 12 | timestamp: BigInt! # uint256 13 | } 14 | -------------------------------------------------------------------------------- /packages/teleporter/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Teleported as TeleportedEvent } from "../generated/L1Teleporter/L1Teleporter"; 2 | 3 | import { Teleported } from "../generated/schema"; 4 | 5 | export function handleTeleported(event: TeleportedEvent): void { 6 | let entity = new Teleported(event.transaction.hash.toHex() + "-" + event.logIndex.toString()) 7 | entity.transactionHash = event.transaction.hash 8 | entity.sender = event.params.sender 9 | entity.l1Token = event.params.l1Token 10 | entity.l3FeeTokenL1Addr = event.params.l3FeeTokenL1Addr 11 | entity.l1l2Router = event.params.l1l2Router 12 | entity.l2l3RouterOrInbox = event.params.l2l3RouterOrInbox 13 | entity.to = event.params.to 14 | entity.amount = event.params.amount 15 | entity.blockNumber = event.block.number 16 | entity.timestamp = event.block.timestamp 17 | entity.save() 18 | } 19 | -------------------------------------------------------------------------------- /packages/teleporter/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum/contract 6 | name: L1Teleporter 7 | network: "{{ l1Network }}" 8 | source: 9 | address: "{{ l1Teleporter }}" 10 | abi: IL1Teleporter 11 | startBlock: {{ l1TeleporterDeployBlock }} 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.5 15 | language: wasm/assemblyscript 16 | entities: 17 | - Teleported 18 | abis: 19 | - name: IL1Teleporter 20 | file: ./abis/IL1Teleporter.json 21 | eventHandlers: 22 | - event: Teleported(indexed address,address,address,address,address,address,uint256) 23 | handler: handleTeleported 24 | file: ./src/mapping.ts 25 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@graphprotocol/graph-ts@0.29.1": 6 | version "0.29.1" 7 | resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.29.1.tgz#8f8fb6815fc4a7470bb8aff8004f032a2639300b" 8 | integrity sha512-GhAP2ijk3cTM0xBjoAFxEmdZbYl1BueCYqAGw5G7UyBX3EV8FWkvD5DMam6IkLGqXasBmelCFrROK3B5t6zVdg== 9 | dependencies: 10 | assemblyscript "0.19.10" 11 | 12 | "@graphprotocol/graph-ts@^0.27.0": 13 | version "0.27.0" 14 | resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.27.0.tgz#948fe1716f6082964a01a63a19bcbf9ac44e06ff" 15 | integrity sha512-r1SPDIZVQiGMxcY8rhFSM0y7d/xAbQf5vHMWUf59js1KgoyWpM6P3tczZqmQd7JTmeyNsDGIPzd9FeaxllsU4w== 16 | dependencies: 17 | assemblyscript "0.19.10" 18 | 19 | "@graphprotocol/graph-ts@^0.32.0": 20 | version "0.32.0" 21 | resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.32.0.tgz#36f8cd8e4ef42c3c32536cceb9995ab990f51d29" 22 | integrity sha512-YfKLT2w+ItXD/VPYQiAKtINQONVsAOkcqVFMHlhUy0fcEBVWuFBT53hJNI0/l5ujQa4TSxtzrKW/7EABAdgI8g== 23 | dependencies: 24 | assemblyscript "0.19.10" 25 | 26 | assemblyscript@0.19.10: 27 | version "0.19.10" 28 | resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.19.10.tgz#7ede6d99c797a219beb4fa4614c3eab9e6343c8e" 29 | integrity sha512-HavcUBXB3mBTRGJcpvaQjmnmaqKHBGREjSPNsIvnAk2f9dj78y4BkMaSSdvBQYWcDDzsHQjyUC8stICFkD1Odg== 30 | dependencies: 31 | binaryen "101.0.0-nightly.20210723" 32 | long "^4.0.0" 33 | 34 | assemblyscript@^0.19.20: 35 | version "0.19.23" 36 | resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.19.23.tgz#16ece69f7f302161e2e736a0f6a474e6db72134c" 37 | integrity sha512-fwOQNZVTMga5KRsfY80g7cpOl4PsFQczMwHzdtgoqLXaYhkhavufKb0sB0l3T1DUxpAufA0KNhlbpuuhZUwxMA== 38 | dependencies: 39 | binaryen "102.0.0-nightly.20211028" 40 | long "^5.2.0" 41 | source-map-support "^0.5.20" 42 | 43 | binaryen@101.0.0-nightly.20210723: 44 | version "101.0.0-nightly.20210723" 45 | resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-101.0.0-nightly.20210723.tgz#b6bb7f3501341727681a03866c0856500eec3740" 46 | integrity sha512-eioJNqhHlkguVSbblHOtLqlhtC882SOEPKmNFZaDuz1hzQjolxZ+eu3/kaS10n3sGPONsIZsO7R9fR00UyhEUA== 47 | 48 | binaryen@102.0.0-nightly.20211028: 49 | version "102.0.0-nightly.20211028" 50 | resolved "https://registry.yarnpkg.com/binaryen/-/binaryen-102.0.0-nightly.20211028.tgz#8f1efb0920afd34509e342e37f84313ec936afb2" 51 | integrity sha512-GCJBVB5exbxzzvyt8MGDv/MeUjs6gkXDvf4xOIItRBptYl0Tz5sm1o/uG95YK0L0VeG5ajDu3hRtkBP2kzqC5w== 52 | 53 | buffer-from@^1.0.0: 54 | version "1.1.2" 55 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 56 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 57 | 58 | long@^4.0.0: 59 | version "4.0.0" 60 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" 61 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== 62 | 63 | long@^5.2.0: 64 | version "5.2.0" 65 | resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" 66 | integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== 67 | 68 | matchstick-as@0.5.0, matchstick-as@^0.5.0: 69 | version "0.5.0" 70 | resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.5.0.tgz#cdafc1ef49d670b9cbe98e933bc2a5cb7c450aeb" 71 | integrity sha512-4K619YDH+so129qt4RB4JCNxaFwJJYLXPc7drpG+/mIj86Cfzg6FKs/bA91cnajmS1CLHdhHl9vt6Kd6Oqvfkg== 72 | dependencies: 73 | "@graphprotocol/graph-ts" "^0.27.0" 74 | assemblyscript "^0.19.20" 75 | wabt "1.0.24" 76 | 77 | mustache@^4.2.0: 78 | version "4.2.0" 79 | resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" 80 | integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== 81 | 82 | source-map-support@^0.5.20: 83 | version "0.5.21" 84 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 85 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 86 | dependencies: 87 | buffer-from "^1.0.0" 88 | source-map "^0.6.0" 89 | 90 | source-map@^0.6.0: 91 | version "0.6.1" 92 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 93 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 94 | 95 | wabt@1.0.24: 96 | version "1.0.24" 97 | resolved "https://registry.yarnpkg.com/wabt/-/wabt-1.0.24.tgz#c02e0b5b4503b94feaf4a30a426ef01c1bea7c6c" 98 | integrity sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg== 99 | --------------------------------------------------------------------------------