From d417d2d9f8eeea099becb2247482a3d5d923f43c Mon Sep 17 00:00:00 2001 From: Kartik Date: Tue, 27 Feb 2024 17:47:17 +0800 Subject: [PATCH 1/7] blast: 1. automaticyield 2. gas collector 3. rm getDeposit() in Account(for size) --- contracts/v1.5.x/BlastConstant.sol | 15 +++++++++++ contracts/v1.5.x/BlastGasCollector.sol | 23 +++++++++++++++++ contracts/v1.5.x/BloctoAccount.sol | 7 ------ .../v1.5.x/BloctoAccountCloneableWallet.sol | 25 ++++++++++++++++++- contracts/v1.5.x/BloctoAccountFactoryBase.sol | 8 ++++++ contracts/v1.5.x/CoreWallet.sol | 2 +- deploy/1_0_deploy_account_accountFactory.ts | 15 ++++++----- hardhat.config.ts | 16 +++++++++++- 8 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 contracts/v1.5.x/BlastConstant.sol create mode 100644 contracts/v1.5.x/BlastGasCollector.sol diff --git a/contracts/v1.5.x/BlastConstant.sol b/contracts/v1.5.x/BlastConstant.sol new file mode 100644 index 0000000..d7c13fb --- /dev/null +++ b/contracts/v1.5.x/BlastConstant.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.17; + +// blast yield contract +IBlast constant BLAST = IBlast(0x4300000000000000000000000000000000000002); +// blast gas collector contract +address constant GAS_COLLECTOR = 0xadBd636A9fF51f2aB6999833AAB784f2C1Efa6F1; + +interface IBlast { + // see https://docs.blast.io/building/guides/gas-fees + function configureAutomaticYield() external; + function configureClaimableGas() external; + function configureGovernor(address governor) external; + function claimAllGas(address contractAddress, address recipient) external returns (uint256); +} diff --git a/contracts/v1.5.x/BlastGasCollector.sol b/contracts/v1.5.x/BlastGasCollector.sol new file mode 100644 index 0000000..edf07a2 --- /dev/null +++ b/contracts/v1.5.x/BlastGasCollector.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; + +contract BlastGasCollector is Ownable { + /// @notice constructor setting owner and configure BLAST + constructor() Ownable() { + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + } + + /// @notice can claim gas including self + /// @param target claim target + /// @param receiver claimed gas receiver + function claimGas(address target, address receiver) onlyOwner external { + BLAST.claimAllGas(target, receiver); + } +} diff --git a/contracts/v1.5.x/BloctoAccount.sol b/contracts/v1.5.x/BloctoAccount.sol index a82f36f..a978877 100644 --- a/contracts/v1.5.x/BloctoAccount.sol +++ b/contracts/v1.5.x/BloctoAccount.sol @@ -111,13 +111,6 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas return 0; } - /** - * check current account deposit in the entryPoint StakeManager - */ - function getDeposit() public view returns (uint256) { - return entryPoint().balanceOf(address(this)); - } - /** * deposit more funds for this account in the entryPoint StakeManager */ diff --git a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol index aca33b0..f9a5996 100644 --- a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol +++ b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import "./BloctoAccount.sol"; +import "./BlastConstant.sol"; +import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; /// @title BloctoAccountCloneableWallet Wallet /// @notice This contract represents a complete but non working wallet. @@ -12,4 +14,25 @@ contract BloctoAccountCloneableWallet is BloctoAccount { initialized = true; initializedImplementation = true; } + + /// @notice same as CoreWallet's `init` function, but add yield and gas collector function for Blast + /// @param _authorizedAddress the initial authorized address, must not be zero! + /// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress` + /// @param _recoveryAddress the initial recovery address for the wallet, can be address(0) + /// @param _mergedKeyIndexWithParity the corresponding index of mergedKeys = authVersion + _mergedIndex + /// @param _mergedKey the corresponding mergedKey (using Schnorr merged key) + function init( + address _authorizedAddress, + uint256 _cosigner, + address _recoveryAddress, + uint8 _mergedKeyIndexWithParity, + bytes32 _mergedKey + ) public override onlyOnce { + super.init(_authorizedAddress, _cosigner, _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey); + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + BLAST.configureGovernor(GAS_COLLECTOR); + } } diff --git a/contracts/v1.5.x/BloctoAccountFactoryBase.sol b/contracts/v1.5.x/BloctoAccountFactoryBase.sol index 0f012ba..111df78 100644 --- a/contracts/v1.5.x/BloctoAccountFactoryBase.sol +++ b/contracts/v1.5.x/BloctoAccountFactoryBase.sol @@ -8,6 +8,8 @@ import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import "../BloctoAccountProxy.sol"; import "./BloctoAccount.sol"; +import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; + // BloctoAccountFactory for creating BloctoAccountProxy contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable { /// @notice create account role for using createAccount() and createAccount2() @@ -49,6 +51,12 @@ contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable { bloctoAccountImplementation = _bloctoAccountImplementation; entryPoint = _entryPoint; _setupRole(DEFAULT_ADMIN_ROLE, _admin); + + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + BLAST.configureGovernor(GAS_COLLECTOR); } /// @notice only the admin can update admin functioins diff --git a/contracts/v1.5.x/CoreWallet.sol b/contracts/v1.5.x/CoreWallet.sol index 8cbc309..a276996 100644 --- a/contracts/v1.5.x/CoreWallet.sol +++ b/contracts/v1.5.x/CoreWallet.sol @@ -202,7 +202,7 @@ contract CoreWallet is IERC1271 { address _recoveryAddress, uint8 _mergedKeyIndexWithParity, bytes32 _mergedKey - ) public onlyOnce { + ) public virtual onlyOnce { require(_authorizedAddress != address(0), "authorized addresses must not be zero"); require(_authorizedAddress != _recoveryAddress, "do not use the recovery address as an authorized address"); require(address(uint160(_cosigner)) != _recoveryAddress, "do not use the recovery address as a cosigner"); diff --git a/deploy/1_0_deploy_account_accountFactory.ts b/deploy/1_0_deploy_account_accountFactory.ts index 99609d7..9fe057a 100644 --- a/deploy/1_0_deploy_account_accountFactory.ts +++ b/deploy/1_0_deploy_account_accountFactory.ts @@ -12,12 +12,12 @@ import { hexZeroPad } from '@ethersproject/bytes' const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' // NOTE: don't forget to change this according to the backend deploy account // prod mainnet -const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' -const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' +// const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' +// const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' // dev testnet -// const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' -// const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' +const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' +const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' // BloctowalletCloneableSalt const BloctoAccountCloneableWalletSalt = 'BloctoAccount_v140' @@ -54,7 +54,7 @@ async function main (): Promise { const BloctoAccountFactory = await ethers.getContractFactory('BloctoAccountFactory') const accountFactory = await create3DeployTransparentProxy(BloctoAccountFactory, [walletCloneable, EntryPoint, owner.address], - { initializer: 'initialize' }, create3Factory, owner, accountFactorySalt) + { initializer: 'initialize', constructorArgs: [walletCloneable], unsafeAllow: ['constructor', 'state-variable-immutable'] }, create3Factory, owner, accountFactorySalt) await accountFactory.deployed() console.log(`BloctoAccountFactory JUST deployed to: ${accountFactory.address}`) @@ -94,7 +94,10 @@ async function main (): Promise { const accountFactoryImplAddress = await getImplementationAddress(ethers.provider, accountFactoryAddr) await hre.run('verify:verify', { address: accountFactoryImplAddress, - contract: 'contracts/v1.5.x/BloctoAccountFactory.sol:BloctoAccountFactory' + contract: 'contracts/v1.5.x/BloctoAccountFactory.sol:BloctoAccountFactory', + constructorArguments: [ + walletCloneable + ] }) } diff --git a/hardhat.config.ts b/hardhat.config.ts index 66985b3..ed65bcb 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -198,6 +198,11 @@ const config: HardhatUserConfig = { url: 'https://rpc.startale.com/zkatana', accounts: getDeployAccount(), chainId: 1261120 + }, + blast_sepolia: { + url: 'https://blast-sepolia.blockpi.network/v1/rpc/public', + accounts: getDeployAccount(), + chainId: 168587773 } }, mocha: { @@ -231,7 +236,8 @@ const config: HardhatUserConfig = { scroll: SCROLLSCAN_API_KEY, scrollSepolia: SCROLLSCAN_API_KEY, astarZkevmSepolia: SCROLLSCAN_API_KEY, - taikoJolnirSepolia: SCROLLSCAN_API_KEY + taikoJolnirSepolia: SCROLLSCAN_API_KEY, + blast_sepolia: 'blast_sepolia' }, customChains: [ { @@ -345,6 +351,14 @@ const config: HardhatUserConfig = { apiURL: 'https://explorer.jolnir.taiko.xyz/api', browserURL: 'https://explorer.jolnir.taiko.xyz/' } + }, + { + network: 'blast_sepolia', + chainId: 168587773, + urls: { + apiURL: 'https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan', + browserURL: 'https://testnet.blastscan.io' + } } ] } From f8ead0cf7bed4f0ff7ebe1606b4225b6b7dacb27 Mon Sep 17 00:00:00 2001 From: Kartik Date: Thu, 29 Feb 2024 18:34:13 +0800 Subject: [PATCH 2/7] balst: update gas collector --- contracts/v1.5.x/BlastConstant.sol | 5 +- contracts/v1.5.x/BlastGasCollector.sol | 55 ++++++++++++++-- contracts/v1.5.x/BloctoAccount.sol | 10 ++- .../v1.5.x/BloctoAccountCloneableWallet.sol | 23 ------- contracts/v1.5.x/CoreWallet.sol | 2 +- deploy/3_2_createMultipleSchnorrAccount.ts | 2 +- deploy/6_deploy_BlastGasCollector.ts | 66 +++++++++++++++++++ deploy/upgrade/0_upgrade.ts | 23 ++++--- deploy/upgrade/0_upgrade_factory.ts | 2 +- hardhat.config.ts | 3 +- package.json | 3 +- test/blast.test.ts | 42 ++++++++++++ test/bloctoaccount.test.ts | 2 +- 13 files changed, 189 insertions(+), 49 deletions(-) create mode 100644 deploy/6_deploy_BlastGasCollector.ts create mode 100644 test/blast.test.ts diff --git a/contracts/v1.5.x/BlastConstant.sol b/contracts/v1.5.x/BlastConstant.sol index d7c13fb..023b545 100644 --- a/contracts/v1.5.x/BlastConstant.sol +++ b/contracts/v1.5.x/BlastConstant.sol @@ -3,13 +3,14 @@ pragma solidity 0.8.17; // blast yield contract IBlast constant BLAST = IBlast(0x4300000000000000000000000000000000000002); -// blast gas collector contract -address constant GAS_COLLECTOR = 0xadBd636A9fF51f2aB6999833AAB784f2C1Efa6F1; +// BlastGasCollector contract using Create3Factory to generate constant contract address +address constant GAS_COLLECTOR = 0xBd9D6d96b21d679983Af4ed6182Fd9fff0031eA4; interface IBlast { // see https://docs.blast.io/building/guides/gas-fees function configureAutomaticYield() external; function configureClaimableGas() external; function configureGovernor(address governor) external; + function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external; function claimAllGas(address contractAddress, address recipient) external returns (uint256); } diff --git a/contracts/v1.5.x/BlastGasCollector.sol b/contracts/v1.5.x/BlastGasCollector.sol index edf07a2..bfab1d5 100644 --- a/contracts/v1.5.x/BlastGasCollector.sol +++ b/contracts/v1.5.x/BlastGasCollector.sol @@ -3,21 +3,62 @@ pragma solidity ^0.8.17; import "@openzeppelin/contracts/access/Ownable.sol"; -import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; +import {BLAST} from "./BlastConstant.sol"; + +contract BlastGasCollector is AccessControl { + /// @notice gas collector role for using claimGas() + bytes32 public constant GAS_COLLECTOR_ROLE = keccak256("GAS_COLLECTOR_ROLE"); -contract BlastGasCollector is Ownable { /// @notice constructor setting owner and configure BLAST - constructor() Ownable() { + constructor(address _admin) { // contract balance will grow automatically BLAST.configureAutomaticYield(); // let GAS_COLLECTOR collect gas BLAST.configureClaimableGas(); + // sender as default admin + _setupRole(DEFAULT_ADMIN_ROLE, _admin); } - /// @notice can claim gas including self + /// @notice can claim gas including self gas /// @param target claim target - /// @param receiver claimed gas receiver - function claimGas(address target, address receiver) onlyOwner external { - BLAST.claimAllGas(target, receiver); + /// @param recipientOfGas claimed gas recipientOfGas + function claimGas(address target, address recipientOfGas) external { + require(hasRole(GAS_COLLECTOR_ROLE, msg.sender), "caller is not gas collecotr role"); + BLAST.claimAllGas(target, recipientOfGas); + } + + /// @notice can claim gas including self gas + /// @param targetAry claim target + /// @param recipientOfGas claimed gas recipientOfGas + function claimGasBatch(address[] calldata targetAry, address recipientOfGas) external { + require(hasRole(GAS_COLLECTOR_ROLE, msg.sender), "caller is not gas collecotr role"); + for (uint256 i = 0; i < targetAry.length; i++) { + BLAST.claimAllGas(targetAry[i], recipientOfGas); + } + } + + /// @notice configures the governor for a specific contract. Called by an authorized user + /// @param _newGovernor the address of new governor + /// @param target the address of the contract to be configured + function configureGovernorOnBehalf(address _newGovernor, address target) external { + require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin"); + BLAST.configureGovernorOnBehalf(_newGovernor, target); + } + + /// @notice configures the governor for a specific contract. Called by an authorized user + /// @param _newGovernor the address of new governor + /// @param targetAry the address of the contract to be configured + function configureGovernorOnBehalf(address _newGovernor, address[] calldata targetAry) external { + require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin"); + for (uint256 i = 0; i < targetAry.length; i++) { + BLAST.configureGovernorOnBehalf(_newGovernor, targetAry[i]); + } + } + + /// @notice selfdestruct to transfer all funds to the owner + function destruct() external { + require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin"); + selfdestruct(payable(msg.sender)); } } diff --git a/contracts/v1.5.x/BloctoAccount.sol b/contracts/v1.5.x/BloctoAccount.sol index a978877..fbc28e8 100644 --- a/contracts/v1.5.x/BloctoAccount.sol +++ b/contracts/v1.5.x/BloctoAccount.sol @@ -11,6 +11,8 @@ import "@account-abstraction/contracts/core/BaseAccount.sol"; import "../utils/TokenCallbackHandler.sol"; import "./CoreWallet.sol"; +import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; + /** * Blocto account. * compatibility for EIP-4337 and smart contract wallet with cosigner functionality (CoreWallet) @@ -136,8 +138,14 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas /// @notice initialize BloctoAccountProxy for adding the implementation address /// @param implementation implementation address - function initImplementation(address implementation) public onlyOnceInitImplementation { + function initImplementation(address implementation) public virtual onlyOnceInitImplementation { require(Address.isContract(implementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation; + + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + BLAST.configureGovernor(GAS_COLLECTOR); } } diff --git a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol index f9a5996..bcaa1ae 100644 --- a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol +++ b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.17; import "./BloctoAccount.sol"; -import "./BlastConstant.sol"; -import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; /// @title BloctoAccountCloneableWallet Wallet /// @notice This contract represents a complete but non working wallet. @@ -14,25 +12,4 @@ contract BloctoAccountCloneableWallet is BloctoAccount { initialized = true; initializedImplementation = true; } - - /// @notice same as CoreWallet's `init` function, but add yield and gas collector function for Blast - /// @param _authorizedAddress the initial authorized address, must not be zero! - /// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress` - /// @param _recoveryAddress the initial recovery address for the wallet, can be address(0) - /// @param _mergedKeyIndexWithParity the corresponding index of mergedKeys = authVersion + _mergedIndex - /// @param _mergedKey the corresponding mergedKey (using Schnorr merged key) - function init( - address _authorizedAddress, - uint256 _cosigner, - address _recoveryAddress, - uint8 _mergedKeyIndexWithParity, - bytes32 _mergedKey - ) public override onlyOnce { - super.init(_authorizedAddress, _cosigner, _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey); - // contract balance will grow automatically - BLAST.configureAutomaticYield(); - // let GAS_COLLECTOR collect gas - BLAST.configureClaimableGas(); - BLAST.configureGovernor(GAS_COLLECTOR); - } } diff --git a/contracts/v1.5.x/CoreWallet.sol b/contracts/v1.5.x/CoreWallet.sol index a276996..8cbc309 100644 --- a/contracts/v1.5.x/CoreWallet.sol +++ b/contracts/v1.5.x/CoreWallet.sol @@ -202,7 +202,7 @@ contract CoreWallet is IERC1271 { address _recoveryAddress, uint8 _mergedKeyIndexWithParity, bytes32 _mergedKey - ) public virtual onlyOnce { + ) public onlyOnce { require(_authorizedAddress != address(0), "authorized addresses must not be zero"); require(_authorizedAddress != _recoveryAddress, "do not use the recovery address as an authorized address"); require(address(uint160(_cosigner)) != _recoveryAddress, "do not use the recovery address as a cosigner"); diff --git a/deploy/3_2_createMultipleSchnorrAccount.ts b/deploy/3_2_createMultipleSchnorrAccount.ts index f12efcd..fa5ee36 100644 --- a/deploy/3_2_createMultipleSchnorrAccount.ts +++ b/deploy/3_2_createMultipleSchnorrAccount.ts @@ -38,7 +38,7 @@ async function main (): Promise { console.log('ethersSigner address: ', await ethersSigner.getAddress()) console.log('factory.address', factory.address) - const tx = await factory.createAccount2([authorizedWallet.address, authorizedWallet2.address, cosignerWallet.address], + const tx = await factory.createAccount2_1_5_3([authorizedWallet.address, authorizedWallet2.address, cosignerWallet.address], cosignerWallet.address, RecoverAddress, SALT, // random salt [pxIndexWithParity, pxIndexWithParity2, pxIndexWithParity3], diff --git a/deploy/6_deploy_BlastGasCollector.ts b/deploy/6_deploy_BlastGasCollector.ts new file mode 100644 index 0000000..43d3cf1 --- /dev/null +++ b/deploy/6_deploy_BlastGasCollector.ts @@ -0,0 +1,66 @@ +import hre, { ethers } from 'hardhat' +import { getDeployCode } from '../src/create3Factory' +import { + BlastGasCollector__factory, + CREATE3Factory__factory +} from '../typechain' +import { hexZeroPad } from '@ethersproject/bytes' + +// prod mainnet +// const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' +// dev testnet +const GasCollectorBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' + +// create3Factory +const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' + +// BloctocontractInstanceSalt +const BlastGasCollectorSalt = 'BlastGasCollector_v1.0' + +async function main (): Promise { + // const lockedAmount = ethers.utils.parseEther("1"); + const [owner] = await ethers.getSigners() + console.log('deploy with account: ', owner.address) + + const create3Factory = CREATE3Factory__factory.connect(Create3FactoryAddress, owner) + // -------------------BlastGasCollector------------------------------// + const contractSalt = hexZeroPad(Buffer.from(BlastGasCollectorSalt, 'utf-8'), 32) + console.log(`Deploying BlastGasCollector with -> \n\t salt str: ${BlastGasCollectorSalt}`) + const contractInstance = await create3Factory.getDeployed(owner.address, contractSalt) + + if ((await ethers.provider.getCode(contractInstance)) === '0x') { + console.log(`BlastGasCollector deploying to: ${contractInstance}`) + const tx = await create3Factory.deploy( + contractSalt, + getDeployCode(new BlastGasCollector__factory(), [owner.address])) + await tx.wait() + console.log(`BlastGasCollector JUST deployed to: ${contractInstance}`) + console.log('Granting gas collector role to backend address: ', GasCollectorBackend) + + const gasCollector = BlastGasCollector__factory.connect(contractInstance, owner) + await gasCollector.grantRole(await gasCollector.GAS_COLLECTOR_ROLE(), GasCollectorBackend) + } else { + console.log(`BlastGasCollector WAS deployed to: ${contractInstance}`) + } + + // sleep 10 seconds + console.log('sleep 10 seconds for chain sync...') + await new Promise(f => setTimeout(f, 10000)) + + // -------------------Verify------------------------------// + // verify BlastGasCollector + await hre.run('verify:verify', { + address: contractInstance, + contract: 'contracts/v1.5.x/BlastGasCollector.sol:BlastGasCollector', + constructorArguments: [ + owner.address + ] + }) +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/deploy/upgrade/0_upgrade.ts b/deploy/upgrade/0_upgrade.ts index 880306e..cc62ead 100644 --- a/deploy/upgrade/0_upgrade.ts +++ b/deploy/upgrade/0_upgrade.ts @@ -13,15 +13,18 @@ const NextVersion = '1.5.3' // entrypoint from 4337 official (0.6.0) const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' // mainnet -// const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' -// const BloctoAccountFactoryAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' +let Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' +let BloctoAccountFactoryAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' // testnet -const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' -const BloctoAccountFactoryAddr = '0x38DDa3Aed6e71457d573F993ee06380b1cDaF3D1' async function main (): Promise { const [owner] = await ethers.getSigners() console.log('upgrade with owner:', owner.address) + // testnet deployer + if (owner.address === '0x162235eBF3381eDE497dFa523b2a77E2941583eC') { + Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' + BloctoAccountFactoryAddr = '0x38DDa3Aed6e71457d573F993ee06380b1cDaF3D1' + } const create3Factory = CREATE3Factory__factory.connect(Create3FactoryAddress, owner) // deploy BloctoAccount next version const nextVersionBloctoAccountCloneable = 'BloctoAccount_' + NextVersion @@ -49,12 +52,12 @@ async function main (): Promise { const nowFactoryVersoin = await factory.VERSION() console.log(`Factory version: ${nowFactoryVersoin}`) - if (nowFactoryVersoin !== NextVersion) { - console.log('\t upgrading factory...') - const UpgradeContract = await ethers.getContractFactory('BloctoAccountFactory') - await upgrades.upgradeProxy(BloctoAccountFactoryAddr, UpgradeContract, { constructorArgs: [implementation], unsafeAllow: ['constructor', 'state-variable-immutable'] }) - console.log('\t new factory versoin', await factory.VERSION()) - } + // if (nowFactoryVersoin !== NextVersion) { + console.log('\t upgrading factory...') + const UpgradeContract = await ethers.getContractFactory('BloctoAccountFactory') + await upgrades.upgradeProxy(BloctoAccountFactoryAddr, UpgradeContract, { constructorArgs: [implementation], unsafeAllow: ['constructor', 'state-variable-immutable'] }) + console.log('\t new factory versoin', await factory.VERSION()) + // } // verify BloctoAccountCloneableWallet await hre.run('verify:verify', { diff --git a/deploy/upgrade/0_upgrade_factory.ts b/deploy/upgrade/0_upgrade_factory.ts index 1d0f85f..4a36ec9 100644 --- a/deploy/upgrade/0_upgrade_factory.ts +++ b/deploy/upgrade/0_upgrade_factory.ts @@ -3,7 +3,7 @@ import hre, { ethers } from 'hardhat' import { getImplementationAddress } from '@openzeppelin/upgrades-core' const BloctoAccountFactoryAddr = '0x38DDa3Aed6e71457d573F993ee06380b1cDaF3D1' -const BloctoAccountCloneablelAddr = '0x77E262adD1b7DBF4ad7C39045CCC0FB22f060867' +const BloctoAccountCloneablelAddr = '0x89EbeBE2bA6638729FBD2F33d200A48C81684c3c' async function main (): Promise { const [owner] = await ethers.getSigners() diff --git a/hardhat.config.ts b/hardhat.config.ts index ed65bcb..d52e1a7 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -202,7 +202,8 @@ const config: HardhatUserConfig = { blast_sepolia: { url: 'https://blast-sepolia.blockpi.network/v1/rpc/public', accounts: getDeployAccount(), - chainId: 168587773 + chainId: 168587773, + gasPrice: 3000000000 } }, mocha: { diff --git a/package.json b/package.json index 2d36cd6..9b1988d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "deploy-create3Factory": "hardhat run deploy/0_deploy_create3Factory.ts", "deploy-verifyingPaymaster": "hardhat run deploy/2_deploy_VerifyingPaymaster.ts", "deploy-upgrade": "hardhat run deploy/upgrade/0_upgrade.ts", - "deploy-verify": "hardhat run deploy/4_verify.ts" + "deploy-verify": "hardhat run deploy/4_verify.ts", + "deploy-blastGasCollector": "hardhat run deploy/6_deploy_BlastGasCollector.ts" }, "devDependencies": { "@account-abstraction/contracts": "^0.6.0", diff --git a/test/blast.test.ts b/test/blast.test.ts new file mode 100644 index 0000000..c80c355 --- /dev/null +++ b/test/blast.test.ts @@ -0,0 +1,42 @@ +import { ethers } from 'hardhat' +import { Contract, BigNumber } from 'ethers' +import { expect } from 'chai' +import { + BlastGasCollector__factory +} from '../typechain' + +const BlastGasCollectorAddr = '0xBd9D6d96b21d679983Af4ed6182Fd9fff0031eA4' +const GasAddr = '0x4300000000000000000000000000000000000001' + +async function readGasCanBeClaimed (chkAddr: string): Promise<[BigNumber, number]> { + // see https://testnet.blastscan.io/address/0x4300000000000000000000000000000000000001/contract/168587773/code + + const gasAbi = [ + // Get the account balance + 'function readGasParams(address) view returns (uint256,uint256,uint256,uint)' + ] + + const gasContract = new Contract(GasAddr, gasAbi, ethers.provider) + // (uint256 etherSeconds, uint256 etherBalance, uint256 lastUpdated, GasMode mode) + const [, etherBalance, , mode] = await gasContract.readGasParams(chkAddr) + return [etherBalance, mode] +} + +describe('Blast Gas Collector Test', function () { + const targetAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' + const ethersSigner = ethers.provider.getSigner(0) + const gasCollecotr = BlastGasCollector__factory.connect(BlastGasCollectorAddr, ethersSigner) + + it('should collect gas if exist', async () => { + const [etherBalance, mode] = await readGasCanBeClaimed(targetAddr) + // sholue be 1 (CLAIMABLE mode), see https://testnet.blastscan.io/address/0x4300000000000000000000000000000000000001/contract/168587773/code + expect(mode).to.equal(1) + if (etherBalance.gt(0)) { + console.log(`collecting ${targetAddr} etherBalance ${etherBalance.toString()}...`) + const tx = await gasCollecotr.claimGas(targetAddr, await ethersSigner.getAddress()) + await tx.wait() + } else { + console.log('no gas to be collected of', targetAddr) + } + }) +}) diff --git a/test/bloctoaccount.test.ts b/test/bloctoaccount.test.ts index 8cb7eb5..7e0659a 100644 --- a/test/bloctoaccount.test.ts +++ b/test/bloctoaccount.test.ts @@ -1,5 +1,5 @@ import { ethers } from 'hardhat' -import { Wallet, BigNumber, ContractTransaction } from 'ethers' +import { Wallet, BigNumber, ContractTransaction, Contract } from 'ethers' import { expect } from 'chai' import { BloctoAccount, From dd3c00dabe5439f2dcc01a9d12265fd181a5e73a Mon Sep 17 00:00:00 2001 From: Kartik Date: Fri, 1 Mar 2024 12:14:04 +0800 Subject: [PATCH 3/7] blast: move blast configuration to init and init2 --- contracts/v1.5.x/BloctoAccount.sol | 8 -------- contracts/v1.5.x/CoreWallet.sol | 14 ++++++++++++++ deploy/upgrade/0_upgrade.ts | 14 +++++++------- test/blast.test.ts | 5 ++++- test/bloctoaccount.test.ts | 2 +- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/contracts/v1.5.x/BloctoAccount.sol b/contracts/v1.5.x/BloctoAccount.sol index fbc28e8..5a0ff56 100644 --- a/contracts/v1.5.x/BloctoAccount.sol +++ b/contracts/v1.5.x/BloctoAccount.sol @@ -11,8 +11,6 @@ import "@account-abstraction/contracts/core/BaseAccount.sol"; import "../utils/TokenCallbackHandler.sol"; import "./CoreWallet.sol"; -import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; - /** * Blocto account. * compatibility for EIP-4337 and smart contract wallet with cosigner functionality (CoreWallet) @@ -141,11 +139,5 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas function initImplementation(address implementation) public virtual onlyOnceInitImplementation { require(Address.isContract(implementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation; - - // contract balance will grow automatically - BLAST.configureAutomaticYield(); - // let GAS_COLLECTOR collect gas - BLAST.configureClaimableGas(); - BLAST.configureGovernor(GAS_COLLECTOR); } } diff --git a/contracts/v1.5.x/CoreWallet.sol b/contracts/v1.5.x/CoreWallet.sol index 8cbc309..f5fbba1 100644 --- a/contracts/v1.5.x/CoreWallet.sol +++ b/contracts/v1.5.x/CoreWallet.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "../utils/BytesExtractSignature.sol"; +import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; import "@openzeppelin/contracts/interfaces/IERC1271.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -215,6 +216,8 @@ contract CoreWallet is IERC1271 { authorizations[AUTH_VERSION_INCREMENTOR + uint256(uint160(_authorizedAddress))] = _cosigner; mergedKeys[AUTH_VERSION_INCREMENTOR + _mergedKeyIndexWithParity] = _mergedKey; emit Authorized(_authorizedAddress, _cosigner); + // configure blast + configureBlast(); } /// @notice The shared initialization code used to setup the contract state regardless of whether or @@ -248,6 +251,17 @@ contract CoreWallet is IERC1271 { emit Authorized(_authorizedAddress, _cosigner); } + + configureBlast(); + } + + /// @notice configure blast for yield and gas + function configureBlast() internal { + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + BLAST.configureGovernor(GAS_COLLECTOR); } /// @notice The fallback function, invoked whenever we receive a transaction that doesn't call any of our diff --git a/deploy/upgrade/0_upgrade.ts b/deploy/upgrade/0_upgrade.ts index cc62ead..f9b1b25 100644 --- a/deploy/upgrade/0_upgrade.ts +++ b/deploy/upgrade/0_upgrade.ts @@ -9,7 +9,7 @@ import { hexZeroPad } from '@ethersproject/bytes' import { getDeployCode } from '../../src/create3Factory' import { getImplementationAddress } from '@openzeppelin/upgrades-core' -const NextVersion = '1.5.3' +const NextVersion = '1.5.3-blast' // entrypoint from 4337 official (0.6.0) const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' // mainnet @@ -52,12 +52,12 @@ async function main (): Promise { const nowFactoryVersoin = await factory.VERSION() console.log(`Factory version: ${nowFactoryVersoin}`) - // if (nowFactoryVersoin !== NextVersion) { - console.log('\t upgrading factory...') - const UpgradeContract = await ethers.getContractFactory('BloctoAccountFactory') - await upgrades.upgradeProxy(BloctoAccountFactoryAddr, UpgradeContract, { constructorArgs: [implementation], unsafeAllow: ['constructor', 'state-variable-immutable'] }) - console.log('\t new factory versoin', await factory.VERSION()) - // } + if (nowFactoryVersoin !== NextVersion) { + console.log('\t upgrading factory...') + const UpgradeContract = await ethers.getContractFactory('BloctoAccountFactory') + await upgrades.upgradeProxy(BloctoAccountFactoryAddr, UpgradeContract, { constructorArgs: [implementation], unsafeAllow: ['constructor', 'state-variable-immutable'] }) + console.log('\t new factory versoin', await factory.VERSION()) + } // verify BloctoAccountCloneableWallet await hre.run('verify:verify', { diff --git a/test/blast.test.ts b/test/blast.test.ts index c80c355..17b9e48 100644 --- a/test/blast.test.ts +++ b/test/blast.test.ts @@ -23,7 +23,10 @@ async function readGasCanBeClaimed (chkAddr: string): Promise<[BigNumber, number } describe('Blast Gas Collector Test', function () { - const targetAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' + // can claim factory gas + // const targetAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' + // can claim wallet gas + const targetAddr = '0xB6cbD452647435971F5ddbE72D85808d06CBcD28' const ethersSigner = ethers.provider.getSigner(0) const gasCollecotr = BlastGasCollector__factory.connect(BlastGasCollectorAddr, ethersSigner) diff --git a/test/bloctoaccount.test.ts b/test/bloctoaccount.test.ts index 7e0659a..8cb7eb5 100644 --- a/test/bloctoaccount.test.ts +++ b/test/bloctoaccount.test.ts @@ -1,5 +1,5 @@ import { ethers } from 'hardhat' -import { Wallet, BigNumber, ContractTransaction, Contract } from 'ethers' +import { Wallet, BigNumber, ContractTransaction } from 'ethers' import { expect } from 'chai' import { BloctoAccount, From 5cb7ca18cc4c7273ded6f067c11a954b7d2d55e5 Mon Sep 17 00:00:00 2001 From: Kartik Date: Tue, 5 Mar 2024 10:31:06 +0800 Subject: [PATCH 4/7] blast: check and delete not necessary code --- contracts/v1.5.x/BloctoAccount.sol | 2 +- contracts/v1.5.x/BloctoAccountCloneableWallet.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/v1.5.x/BloctoAccount.sol b/contracts/v1.5.x/BloctoAccount.sol index 5a0ff56..a978877 100644 --- a/contracts/v1.5.x/BloctoAccount.sol +++ b/contracts/v1.5.x/BloctoAccount.sol @@ -136,7 +136,7 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas /// @notice initialize BloctoAccountProxy for adding the implementation address /// @param implementation implementation address - function initImplementation(address implementation) public virtual onlyOnceInitImplementation { + function initImplementation(address implementation) public onlyOnceInitImplementation { require(Address.isContract(implementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation; } diff --git a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol index bcaa1ae..aca33b0 100644 --- a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol +++ b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.17; +pragma solidity 0.8.17; import "./BloctoAccount.sol"; From e6cc0558e565eafa33f3f3705baf428d983d830a Mon Sep 17 00:00:00 2001 From: Kartik Date: Thu, 7 Mar 2024 10:20:07 +0800 Subject: [PATCH 5/7] blast: contract part for Blast Points --- contracts/v1.5.x/BlastConstant.sol | 4 +++ contracts/v1.5.x/BloctoAccount.sol | 27 +++++++++++++------ .../v1.5.x/BloctoAccountCloneableWallet.sol | 3 ++- .../v1.5.x/BloctoAccountFactoryV1_5_2.sol | 4 +++ .../v1.5.x/BloctoAccountFactoryV1_5_3.sol | 2 ++ contracts/v1.5.x/CoreWallet.sol | 15 ----------- deploy/1_0_deploy_account_accountFactory.ts | 13 +++++++-- deploy/upgrade/0_upgrade.ts | 15 ++++++++--- 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/contracts/v1.5.x/BlastConstant.sol b/contracts/v1.5.x/BlastConstant.sol index 023b545..9739c23 100644 --- a/contracts/v1.5.x/BlastConstant.sol +++ b/contracts/v1.5.x/BlastConstant.sol @@ -14,3 +14,7 @@ interface IBlast { function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external; function claimAllGas(address contractAddress, address recipient) external returns (uint256); } + +interface IBlastPoints { + function configurePointsOperator(address operator) external; +} diff --git a/contracts/v1.5.x/BloctoAccount.sol b/contracts/v1.5.x/BloctoAccount.sol index a978877..c4b877e 100644 --- a/contracts/v1.5.x/BloctoAccount.sol +++ b/contracts/v1.5.x/BloctoAccount.sol @@ -10,6 +10,7 @@ import "@account-abstraction/contracts/core/BaseAccount.sol"; import "../utils/TokenCallbackHandler.sol"; import "./CoreWallet.sol"; +import {BLAST, IBlastPoints, GAS_COLLECTOR} from "./BlastConstant.sol"; /** * Blocto account. @@ -24,6 +25,9 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas /// @notice entrypoint from 4337 official IEntryPoint private immutable _entryPoint; + /// @notice blast points contract address, testnet address DIFF from mainnet + IBlastPoints public immutable _blastPoints; + /// @notice initialized _IMPLEMENTATION_SLOT bool public initializedImplementation = false; @@ -31,8 +35,9 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas * constructor for BloctoAccount * @param anEntryPoint entrypoint address */ - constructor(IEntryPoint anEntryPoint) { + constructor(IEntryPoint anEntryPoint, address blastPointsAddr) { _entryPoint = anEntryPoint; + _blastPoints = IBlastPoints(blastPointsAddr); } /** @@ -111,13 +116,6 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas return 0; } - /** - * deposit more funds for this account in the entryPoint StakeManager - */ - function addDeposit() public payable { - entryPoint().depositTo{value: msg.value}(address(this)); - } - /** * withdraw deposit to withdrawAddress from entryPoint StakeManager * @param withdrawAddress target to send to @@ -140,4 +138,17 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas require(Address.isContract(implementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation; } + + /// @notice configure blast for yield, gas, and points + /// @param pointsOperator blast points contract operator address, should be EOA from https://docs.blast.io/airdrop/api#configuring-a-points-operator + function configureBlast(address pointsOperator) external { + require(!initialized, "blast: must not be initialized"); + // contract balance will grow automatically + BLAST.configureAutomaticYield(); + // let GAS_COLLECTOR collect gas + BLAST.configureClaimableGas(); + BLAST.configureGovernor(GAS_COLLECTOR); + // operator should be EOA + _blastPoints.configurePointsOperator(pointsOperator); + } } diff --git a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol index aca33b0..1198bdc 100644 --- a/contracts/v1.5.x/BloctoAccountCloneableWallet.sol +++ b/contracts/v1.5.x/BloctoAccountCloneableWallet.sol @@ -8,7 +8,8 @@ import "./BloctoAccount.sol"; contract BloctoAccountCloneableWallet is BloctoAccount { /// @notice constructor that deploys a NON-FUNCTIONAL version of `BloctoAccount` /// @param anEntryPoint entrypoint address - constructor(IEntryPoint anEntryPoint) BloctoAccount(anEntryPoint) { + /// @param blastPointsAddr blast points contract address + constructor(IEntryPoint anEntryPoint, address blastPointsAddr) BloctoAccount(anEntryPoint, blastPointsAddr) { initialized = true; initializedImplementation = true; } diff --git a/contracts/v1.5.x/BloctoAccountFactoryV1_5_2.sol b/contracts/v1.5.x/BloctoAccountFactoryV1_5_2.sol index 2c1fdd2..7e2e53c 100644 --- a/contracts/v1.5.x/BloctoAccountFactoryV1_5_2.sol +++ b/contracts/v1.5.x/BloctoAccountFactoryV1_5_2.sol @@ -27,6 +27,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase { ); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation); + ret.configureBlast(msg.sender); ret.init( _authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey ); @@ -55,6 +56,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase { ); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation); + ret.configureBlast(msg.sender); ret.init2( _authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys ); @@ -98,6 +100,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase { Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation)))); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation151Plus); + ret.configureBlast(msg.sender); ret.init( _authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey ); @@ -124,6 +127,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase { Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation)))); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation151Plus); + ret.configureBlast(msg.sender); ret.init2( _authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys ); diff --git a/contracts/v1.5.x/BloctoAccountFactoryV1_5_3.sol b/contracts/v1.5.x/BloctoAccountFactoryV1_5_3.sol index 0b5089e..3bad044 100644 --- a/contracts/v1.5.x/BloctoAccountFactoryV1_5_3.sol +++ b/contracts/v1.5.x/BloctoAccountFactoryV1_5_3.sol @@ -32,6 +32,7 @@ contract BloctoAccountFactoryV1_5_3 is BloctoAccountFactoryBase { Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation)))); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation_1_5_3); + ret.configureBlast(msg.sender); ret.init( _authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey ); @@ -58,6 +59,7 @@ contract BloctoAccountFactoryV1_5_3 is BloctoAccountFactoryBase { Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation)))); ret = BloctoAccount(payable(newProxy)); ret.initImplementation(bloctoAccountImplementation_1_5_3); + ret.configureBlast(msg.sender); ret.init2( _authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys ); diff --git a/contracts/v1.5.x/CoreWallet.sol b/contracts/v1.5.x/CoreWallet.sol index f5fbba1..2dceed4 100644 --- a/contracts/v1.5.x/CoreWallet.sol +++ b/contracts/v1.5.x/CoreWallet.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.17; import "../utils/BytesExtractSignature.sol"; -import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; import "@openzeppelin/contracts/interfaces/IERC1271.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -216,8 +215,6 @@ contract CoreWallet is IERC1271 { authorizations[AUTH_VERSION_INCREMENTOR + uint256(uint160(_authorizedAddress))] = _cosigner; mergedKeys[AUTH_VERSION_INCREMENTOR + _mergedKeyIndexWithParity] = _mergedKey; emit Authorized(_authorizedAddress, _cosigner); - // configure blast - configureBlast(); } /// @notice The shared initialization code used to setup the contract state regardless of whether or @@ -248,20 +245,8 @@ contract CoreWallet is IERC1271 { require(_authorizedAddress != _recoveryAddress, "do not use the recovery address as an authorized address"); authorizations[AUTH_VERSION_INCREMENTOR + uint256(uint160(_authorizedAddress))] = _cosigner; mergedKeys[AUTH_VERSION_INCREMENTOR + _mergedKeyIndexWithParitys[i]] = _mergedKeys[i]; - emit Authorized(_authorizedAddress, _cosigner); } - - configureBlast(); - } - - /// @notice configure blast for yield and gas - function configureBlast() internal { - // contract balance will grow automatically - BLAST.configureAutomaticYield(); - // let GAS_COLLECTOR collect gas - BLAST.configureClaimableGas(); - BLAST.configureGovernor(GAS_COLLECTOR); } /// @notice The fallback function, invoked whenever we receive a transaction that doesn't call any of our diff --git a/deploy/1_0_deploy_account_accountFactory.ts b/deploy/1_0_deploy_account_accountFactory.ts index 9fe057a..dada2e9 100644 --- a/deploy/1_0_deploy_account_accountFactory.ts +++ b/deploy/1_0_deploy_account_accountFactory.ts @@ -23,6 +23,12 @@ const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' const BloctoAccountCloneableWalletSalt = 'BloctoAccount_v140' const BloctoAccountFactorySalt = 'BloctoAccountFactoryProxy_v140' +async function getBlastPointAddress (): Promise { + const { chainId } = await ethers.provider.getNetwork() + // 81457: mainnet using 0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800 from https://docs.blast.io/airdrop/api#configuring-a-points-operator + return chainId === 81457 ? '0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800' : '0x2fc95838c71e76ec69ff817983BFf17c710F34E0' +} + async function main (): Promise { // const lockedAmount = ethers.utils.parseEther("1"); const [owner] = await ethers.getSigners() @@ -34,11 +40,14 @@ async function main (): Promise { console.log(`Deploying BloctoAccountCloneableWallet with -> \n\t salt str: ${BloctoAccountCloneableWalletSalt}`) const walletCloneable = await create3Factory.getDeployed(owner.address, accountSalt) + const blastPointAddress = await getBlastPointAddress() + console.log('Using blastPointAddress: ', blastPointAddress) + if ((await ethers.provider.getCode(walletCloneable)) === '0x') { console.log(`BloctowalletCloneableWallet deploying to: ${walletCloneable}`) const tx = await create3Factory.deploy( accountSalt, - getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint])) + getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint, blastPointAddress])) await tx.wait() console.log(`BloctowalletCloneableWallet JUST deployed to: ${walletCloneable}`) @@ -86,7 +95,7 @@ async function main (): Promise { address: walletCloneable, contract: 'contracts/v1.5.x/BloctoAccountCloneableWallet.sol:BloctoAccountCloneableWallet', constructorArguments: [ - EntryPoint + EntryPoint, blastPointAddress ] }) diff --git a/deploy/upgrade/0_upgrade.ts b/deploy/upgrade/0_upgrade.ts index f9b1b25..8935fae 100644 --- a/deploy/upgrade/0_upgrade.ts +++ b/deploy/upgrade/0_upgrade.ts @@ -9,7 +9,7 @@ import { hexZeroPad } from '@ethersproject/bytes' import { getDeployCode } from '../../src/create3Factory' import { getImplementationAddress } from '@openzeppelin/upgrades-core' -const NextVersion = '1.5.3-blast' +const NextVersion = '1.5.3-blast-0.1' // entrypoint from 4337 official (0.6.0) const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' // mainnet @@ -17,6 +17,12 @@ let Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' let BloctoAccountFactoryAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' // testnet +async function getBlastPointAddress (): Promise { + const { chainId } = await ethers.provider.getNetwork() + // 81457: mainnet using 0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800 from https://docs.blast.io/airdrop/api#configuring-a-points-operator + return chainId === 81457 ? '0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800' : '0x2fc95838c71e76ec69ff817983BFf17c710F34E0' +} + async function main (): Promise { const [owner] = await ethers.getSigners() console.log('upgrade with owner:', owner.address) @@ -31,11 +37,14 @@ async function main (): Promise { const accountCloneableSalt = hexZeroPad(Buffer.from(nextVersionBloctoAccountCloneable, 'utf-8'), 32) const implementation = await create3Factory.getDeployed(await owner.getAddress(), accountCloneableSalt) + const blastPointAddress = await getBlastPointAddress() + console.log('Using blastPointAddress: ', blastPointAddress) + if ((await ethers.provider.getCode(implementation)) === '0x') { console.log(`BloctowalletCloneableWallet ${NextVersion} deploying to: ${implementation}`) const tx = await create3Factory.deploy( accountCloneableSalt, - getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint]) + getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint, blastPointAddress]) ) await tx.wait() console.log(`BloctowalletCloneableWallet ${NextVersion} JUST deployed to: ${implementation}`) @@ -64,7 +73,7 @@ async function main (): Promise { address: implementation, contract: 'contracts/v1.5.x/BloctoAccountCloneableWallet.sol:BloctoAccountCloneableWallet', constructorArguments: [ - EntryPoint + EntryPoint, blastPointAddress ] }) From 2c122f801775a7124ff45edc31f4bec3ee5bf564 Mon Sep 17 00:00:00 2001 From: Kartik Date: Fri, 8 Mar 2024 11:44:34 +0800 Subject: [PATCH 6/7] blast: add blast points --- contracts/v1.5.x/BloctoAccountFactoryBase.sol | 9 +- deploy/1_0_deploy_account_accountFactory.ts | 11 +- package.json | 3 +- test/blast.test.ts | 180 ++++++++++++++++-- yarn.lock | 19 ++ 5 files changed, 199 insertions(+), 23 deletions(-) diff --git a/contracts/v1.5.x/BloctoAccountFactoryBase.sol b/contracts/v1.5.x/BloctoAccountFactoryBase.sol index 111df78..7dd359c 100644 --- a/contracts/v1.5.x/BloctoAccountFactoryBase.sol +++ b/contracts/v1.5.x/BloctoAccountFactoryBase.sol @@ -8,7 +8,7 @@ import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import "../BloctoAccountProxy.sol"; import "./BloctoAccount.sol"; -import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol"; +import {BLAST, IBlastPoints, GAS_COLLECTOR} from "./BlastConstant.sol"; // BloctoAccountFactory for creating BloctoAccountProxy contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable { @@ -59,6 +59,13 @@ contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable { BLAST.configureGovernor(GAS_COLLECTOR); } + /// @notice configure blast for yield, gas, and points + /// @param pointsOperator blast points contract operator address, should be EOA from https://docs.blast.io/airdrop/api#configuring-a-points-operator + function configureBlastPoints(address blastPointsAddr, address pointsOperator) external onlyAdmin { + // operator should be EOA + IBlastPoints(blastPointsAddr).configurePointsOperator(pointsOperator); + } + /// @notice only the admin can update admin functioins modifier onlyAdmin() { require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not a admin"); diff --git a/deploy/1_0_deploy_account_accountFactory.ts b/deploy/1_0_deploy_account_accountFactory.ts index dada2e9..bd842cd 100644 --- a/deploy/1_0_deploy_account_accountFactory.ts +++ b/deploy/1_0_deploy_account_accountFactory.ts @@ -12,12 +12,12 @@ import { hexZeroPad } from '@ethersproject/bytes' const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' // NOTE: don't forget to change this according to the backend deploy account // prod mainnet -// const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' -// const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' +const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' +const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' // dev testnet -const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' -const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' +// const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' +// const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b' // BloctowalletCloneableSalt const BloctoAccountCloneableWalletSalt = 'BloctoAccount_v140' @@ -72,6 +72,9 @@ async function main (): Promise { await accountFactory.grantRole(await accountFactory.CREATE_ACCOUNT_ROLE(), CreateAccountBackend) console.log('setImplementation_1_5_1 to address: ', walletCloneable) await accountFactory.setImplementation_1_5_1(walletCloneable) + console.log('set blast point to address: ', CreateAccountBackend) + const blastPointAddress = await getBlastPointAddress() + await accountFactory.configureBlastPoints(blastPointAddress, CreateAccountBackend) } else { console.log(`BloctoAccountFactory WAS deployed to: ${accountFactoryAddr}`) } diff --git a/package.json b/package.json index 9b1988d..564ee40 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/node": "^16.4.12", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.5", + "axios": "^1.6.7", "chai": "^4.3.4", "eslint": "^8.19.0", "eslint-config-standard": "^17.0.0", @@ -67,4 +68,4 @@ "typescript": "^4.3.5" }, "license": "GPL-3.0" -} \ No newline at end of file +} diff --git a/test/blast.test.ts b/test/blast.test.ts index 17b9e48..e0e4866 100644 --- a/test/blast.test.ts +++ b/test/blast.test.ts @@ -4,6 +4,7 @@ import { expect } from 'chai' import { BlastGasCollector__factory } from '../typechain' +import axios, { AxiosResponse } from 'axios' const BlastGasCollectorAddr = '0xBd9D6d96b21d679983Af4ed6182Fd9fff0031eA4' const GasAddr = '0x4300000000000000000000000000000000000001' @@ -22,24 +23,169 @@ async function readGasCanBeClaimed (chkAddr: string): Promise<[BigNumber, number return [etherBalance, mode] } -describe('Blast Gas Collector Test', function () { - // can claim factory gas - // const targetAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' - // can claim wallet gas - const targetAddr = '0xB6cbD452647435971F5ddbE72D85808d06CBcD28' +describe('Blast Test', function () { const ethersSigner = ethers.provider.getSigner(0) - const gasCollecotr = BlastGasCollector__factory.connect(BlastGasCollectorAddr, ethersSigner) - - it('should collect gas if exist', async () => { - const [etherBalance, mode] = await readGasCanBeClaimed(targetAddr) - // sholue be 1 (CLAIMABLE mode), see https://testnet.blastscan.io/address/0x4300000000000000000000000000000000000001/contract/168587773/code - expect(mode).to.equal(1) - if (etherBalance.gt(0)) { - console.log(`collecting ${targetAddr} etherBalance ${etherBalance.toString()}...`) - const tx = await gasCollecotr.claimGas(targetAddr, await ethersSigner.getAddress()) - await tx.wait() - } else { - console.log('no gas to be collected of', targetAddr) + describe('Blast Gas Collector Test', function () { + // can claim factory gas + // const targetAddr = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' + // can claim wallet gas + const targetAddr = '0xB6cbD452647435971F5ddbE72D85808d06CBcD28' + + const gasCollecotr = BlastGasCollector__factory.connect(BlastGasCollectorAddr, ethersSigner) + + it('should collect gas if exist', async () => { + const [etherBalance, mode] = await readGasCanBeClaimed(targetAddr) + // sholue be 1 (CLAIMABLE mode), see https://testnet.blastscan.io/address/0x4300000000000000000000000000000000000001/contract/168587773/code + expect(mode).to.equal(1) + if (etherBalance.gt(0)) { + console.log(`collecting ${targetAddr} etherBalance ${etherBalance.toString()}...`) + const tx = await gasCollecotr.claimGas(targetAddr, await ethersSigner.getAddress()) + await tx.wait() + } else { + console.log('no gas to be collected of', targetAddr) + } + }) + }) + + describe('Blast Points Test', function () { + // follow https://docs.blast.io/airdrop/api Mainnet Points API + const APIBaseURL = 'https://waitlist-api.develop.testblast.io' + type PointType = 'LIQUIDITY' | 'DEVELOPER' + interface PointsByAsset { + ETH: AssetPoints + WETH: AssetPoints + USDB: AssetPoints + } + + interface AssetPoints { + // same semantics as PointBalances.earnedCumulative + // but specific to an asset + earnedCumulative: string // decimal string + // earnedCumulative is the sum of points earned + // from block 0 to earnedCumulativeBlock + earnedCumulativeBlock: number + } + interface PointBalances { + // decimal strings + available: string + pendingSent: string + + // also decimal strings + // cumulative so they don't decrease + // a batch may become finalized before these numbers update + earnedCumulative: string + receivedCumulative: string // received from transfers (finalized) + finalizedSentCumulative: string // sent from transfers (finalized) + } + + interface PointBalancesResponse { + success: boolean + balancesByPointType: { + LIQUIDITY: PointBalances & { byAsset: PointsByAsset } + DEVELOPER: PointBalances + } + } + + let bearerToken: string + async function obtainChallenge (contractAddress: string, operatorAddress: string): Promise<[string, string]> { + const requestPayload = { + contractAddress: contractAddress, + operatorAddress: operatorAddress + } + + interface Response { + success: boolean + challengeData: string + message: string + } + + try { + const response: AxiosResponse = await axios.post(APIBaseURL + '/v1/dapp-auth/challenge', requestPayload) + const responseData: Response = response.data + + if (!responseData.success) { + throw new Error('obtainChallenge fail (success=false)') + } + return [responseData.challengeData, responseData.message] + } catch (error) { + throw new Error('obtainChallenge Error -> ' + error.toString()) + } + } + + async function signMessage (message: string): Promise { + const wallet = new ethers.Wallet(process.env.TEST_ENV_KEY) + console.log('sign message with wallet: ', wallet.address) + return await wallet.signMessage(message) } + + // note: this function generate signature with EIP191 + async function obtainBearerToken (challengeData: string, message: string): Promise { + const signature = await signMessage(message) + + const requestPayload = { + challengeData: challengeData, + signature: signature + } + + interface Response { + success: boolean + bearerToken: string // will last 1 hour + } + + try { + const response: AxiosResponse = await axios.post(APIBaseURL + '/v1/dapp-auth/solve', requestPayload) + const responseData: Response = response.data + + if (!responseData.success) { + throw new Error('obtainBearerToken fail (success=false)') + } + + return responseData.bearerToken + } catch (error) { + throw new Error('obtainBearerToken Error -> ' + error.toString()) + } + } + + async function checkPointBalance (contractAddress: string): Promise { + // GET /v1/contracts/:contractAddress/point-balances + + try { + const pointURL = APIBaseURL + '/v1/contracts/' + contractAddress + '/point-balances' + + const config = { + headers: { Authorization: `Bearer ${bearerToken}` } + } + + const response: AxiosResponse = await axios.get(pointURL, config) + const responseData: PointBalancesResponse = response.data + + if (!responseData.success) { + throw new Error('checkPointBalance fail (success=false)') + } + return responseData + } catch (error) { + throw new Error('checkPointBalance Error -> ' + error.toString()) + } + } + + it('should get blast point', async () => { + const contractAddress = '0x7fc24BaF14D225522242D6E50264E40EDc6bD0DF' + // const contractAddress = '0xF7cCFaee69cD8A0B3a62C2A0f35F95cC7e588183' + const operatorAddress = '0xadBd636A9fF51f2aB6999833AAB784f2C1Efa6F1' + + try { + const [challenge, message] = await obtainChallenge(contractAddress, operatorAddress) + + // set bearerToken in this block global variable + bearerToken = await obtainBearerToken(challenge, message) + + const pointBalances = await checkPointBalance(contractAddress) + // console.log('pointBalances:', pointBalances) + } catch (error) { + // Handle errors + console.error(error) + expect.fail('should not fail') + } + }) }) }) diff --git a/yarn.lock b/yarn.lock index 90bd4e5..db51742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1901,6 +1901,15 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" +axios@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -4995,6 +5004,11 @@ follow-redirects@^1.12.1, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -8007,6 +8021,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" From 5ba06a5514a81f6534b6fa96a00f010970dff779 Mon Sep 17 00:00:00 2001 From: Kartik Date: Thu, 14 Mar 2024 12:01:15 +0800 Subject: [PATCH 7/7] blast: mainnet data (rpc scan) --- deploy/6_deploy_BlastGasCollector.ts | 4 +++- hardhat.config.ts | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/deploy/6_deploy_BlastGasCollector.ts b/deploy/6_deploy_BlastGasCollector.ts index 43d3cf1..2ce1736 100644 --- a/deploy/6_deploy_BlastGasCollector.ts +++ b/deploy/6_deploy_BlastGasCollector.ts @@ -9,7 +9,9 @@ import { hexZeroPad } from '@ethersproject/bytes' // prod mainnet // const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' // dev testnet -const GasCollectorBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' +// const GasCollectorBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62' +// prod mainnet +const GasCollectorBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29' // create3Factory const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054' diff --git a/hardhat.config.ts b/hardhat.config.ts index d52e1a7..cad8079 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,7 +18,8 @@ const { BASESCAN_API_KEY, // base scan API KEY LINEASCAN_API_KEY, // linea scan API KEY BASE_SEPOLIA_API_KEY, // base sepolia scan API KEY - SCROLLSCAN_API_KEY // scroll scan API KEY + SCROLLSCAN_API_KEY, // scroll scan API KEY + BLASTSCAN_API_KEY // blast scan API KEY } = process.env function getDeployAccount (): string[] { @@ -204,6 +205,11 @@ const config: HardhatUserConfig = { accounts: getDeployAccount(), chainId: 168587773, gasPrice: 3000000000 + }, + blast: { + url: 'https://rpc.ankr.com/blast', + accounts: getDeployAccount(), + chainId: 81457 } }, mocha: { @@ -238,7 +244,8 @@ const config: HardhatUserConfig = { scrollSepolia: SCROLLSCAN_API_KEY, astarZkevmSepolia: SCROLLSCAN_API_KEY, taikoJolnirSepolia: SCROLLSCAN_API_KEY, - blast_sepolia: 'blast_sepolia' + blast_sepolia: 'blast_sepolia', + blast: BLASTSCAN_API_KEY }, customChains: [ { @@ -360,6 +367,14 @@ const config: HardhatUserConfig = { apiURL: 'https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan', browserURL: 'https://testnet.blastscan.io' } + }, + { + network: 'blast', + chainId: 81457, + urls: { + apiURL: 'https://api.blastscan.io/api', + browserURL: 'https://blastscan.io/' + } } ] }