diff --git a/examples/arb-owner/index.ts b/examples/arb-owner/index.ts new file mode 100644 index 00000000..14b2d3ec --- /dev/null +++ b/examples/arb-owner/index.ts @@ -0,0 +1,120 @@ +import { Chain, createPublicClient, http } from 'viem'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { arbitrumSepolia } from 'viem/chains'; +import { + createRollupPrepareConfig, + prepareChainConfig, + createRollupPrepareTransactionRequest, + createRollupPrepareTransactionReceipt, + arbOwnerPublicActions, +} from '@arbitrum/orbit-sdk'; +import { generateChainId } from '@arbitrum/orbit-sdk/utils'; + +function sanitizePrivateKey(privateKey: string): `0x${string}` { + if (!privateKey.startsWith('0x')) { + return `0x${privateKey}`; + } + + return privateKey as `0x${string}`; +} + +function withFallbackPrivateKey(privateKey: string | undefined): `0x${string}` { + if (typeof privateKey === 'undefined') { + return generatePrivateKey(); + } + + return sanitizePrivateKey(privateKey); +} + +function getBlockExplorerUrl(chain: Chain) { + return chain.blockExplorers?.default.url; +} + +if (typeof process.env.DEPLOYER_PRIVATE_KEY === 'undefined') { + throw new Error( + `Please provide the "DEPLOYER_PRIVATE_KEY" environment variable` + ); +} + +// load or generate a random batch poster account +const batchPosterPrivateKey = withFallbackPrivateKey( + process.env.BATCH_POSTER_PRIVATE_KEY +); +const batchPoster = privateKeyToAccount(batchPosterPrivateKey).address; + +// load or generate a random validator account +const validatorPrivateKey = withFallbackPrivateKey( + process.env.VALIDATOR_PRIVATE_KEY +); +const validator = privateKeyToAccount(validatorPrivateKey).address; + +// set the parent chain and create a public client for it +const parentChain = arbitrumSepolia; +const publicClient = createPublicClient({ + chain: parentChain, + transport: http(), +}).extend(arbOwnerPublicActions); + +// load the deployer account +const deployer = privateKeyToAccount( + sanitizePrivateKey(process.env.DEPLOYER_PRIVATE_KEY) +); + +async function main() { + const publicClient = createPublicClient({ + chain: arbitrumSepolia, + transport: http(), + }).extend(arbOwnerPublicActions); + + publicClient.arbOwnerPrepareTransactionRequest({ + functionName: 'setL1BaseFeeEstimateInertia', + args: [BigInt(0)], + account: deployer.address, + }); + + // generate a random chain id + const chainId = generateChainId(); + + // create the chain config + const chainConfig = prepareChainConfig({ + chainId, + arbitrum: { + InitialChainOwner: deployer.address, + DataAvailabilityCommittee: true, + }, + }); + + // prepare the transaction for deploying the core contracts + const request = await createRollupPrepareTransactionRequest({ + params: { + config: createRollupPrepareConfig({ + chainId: BigInt(chainId), + owner: deployer.address, + chainConfig, + }), + batchPoster, + validators: [validator], + nativeToken: '0xf861378b543525ae0c47d33c90c954dc774ac1f9', // $ARB + }, + account: deployer.address, + publicClient: publicClient, + }); + + // sign and send the transaction + const txHash = await publicClient.sendRawTransaction({ + serializedTransaction: await deployer.signTransaction(request), + }); + + // get the transaction receipt after waiting for the transaction to complete + const txReceipt = createRollupPrepareTransactionReceipt( + await publicClient.waitForTransactionReceipt({ hash: txHash }) + ); + + console.log( + `Deployed in ${getBlockExplorerUrl(parentChain)}/tx/${ + txReceipt.transactionHash + }` + ); +} + +main(); diff --git a/examples/arb-owner/package.json b/examples/arb-owner/package.json new file mode 100644 index 00000000..bfa67292 --- /dev/null +++ b/examples/arb-owner/package.json @@ -0,0 +1,13 @@ +{ + "name": "custom-fee-token", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "tsc --outDir dist && node ./dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.9.0", + "typescript": "^5.2.2" + } +} diff --git a/examples/arb-owner/tsconfig.json b/examples/arb-owner/tsconfig.json new file mode 100644 index 00000000..abf0a90d --- /dev/null +++ b/examples/arb-owner/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["./**/*"] +} diff --git a/src/arbOwnerClient.ts b/src/arbOwnerClient.ts index f665d39e..80130202 100644 --- a/src/arbOwnerClient.ts +++ b/src/arbOwnerClient.ts @@ -4,20 +4,26 @@ import { EncodeFunctionDataParameters, Address, PrepareTransactionRequestReturnType, + zeroAddress, + Chain, + Transport, + Abi, } from 'viem'; import { arbOwner } from './contracts'; import { upgradeExecutorEncodeFunctionData } from './upgradeExecutor'; -import { Prettify } from './types/utils'; +import { GetFunctionName, PickReadFunctionFromAbi, Prettify } from './types/utils'; -type ArbOwnerFunctionDataParameters = Prettify< +type ArbOwnerEncodeFunctionDataParameters = Prettify< Omit, 'abi'> >; +type X = GetFunctionName> + function arbOwnerEncodeFunctionData({ functionName, args, -}: ArbOwnerFunctionDataParameters) { +}: ArbOwnerEncodeFunctionDataParameters) { return encodeFunctionData({ abi: arbOwner.abi, functionName, @@ -25,18 +31,8 @@ function arbOwnerEncodeFunctionData({ }); } -type ArbOwnerClient = { - prepareFunctionData( - params: ArbOwnerFunctionDataParameters - ): ArbOwnerClientPrepareFunctionDataResult; - - prepareTransactionRequest( - params: ArbOwnerClientPrepareTransactionRequestParams - ): Promise; -}; - -type ArbOwnerClientPrepareTransactionRequestParams = Prettify< - ArbOwnerFunctionDataParameters & { +export type ArbOwnerPrepareTransactionRequestParameters = Prettify< + ArbOwnerEncodeFunctionDataParameters & { account: Address; } >; @@ -47,18 +43,23 @@ type ArbOwnerClientPrepareFunctionDataResult = { value: bigint; }; -type CreateArbOwnerClientParams = { +export type CreateArbOwnerClientParams = { publicClient: PublicClient; upgradeExecutor: Address | false; // this one is intentionally not optional, so you have to explicitly pass `upgradeExecutor: false` if you're not using one }; +export type ArbOwnerClient = ReturnType; + +// arbOwnerSimulateContract +// arbOwnerPrepareTransactionRequest + export function createArbOwnerClient({ publicClient, upgradeExecutor, -}: CreateArbOwnerClientParams): ArbOwnerClient { +}: CreateArbOwnerClientParams) { return { prepareFunctionData( - params: ArbOwnerFunctionDataParameters + params: ArbOwnerEncodeFunctionDataParameters ): ArbOwnerClientPrepareFunctionDataResult { if (!upgradeExecutor) { return { @@ -82,7 +83,7 @@ export function createArbOwnerClient({ }, async prepareTransactionRequest( - params: ArbOwnerClientPrepareTransactionRequestParams + params: ArbOwnerPrepareTransactionRequestParameters ): Promise { const { to, data, value } = this.prepareFunctionData(params); @@ -96,3 +97,65 @@ export function createArbOwnerClient({ }, }; } + +function arbOwnerPrepareFunctionData( + params: ArbOwnerEncodeFunctionDataParameters +): ArbOwnerClientPrepareFunctionDataResult { + const upgradeExecutor = zeroAddress; + + if (!upgradeExecutor) { + return { + to: arbOwner.address, + data: arbOwnerEncodeFunctionData(params), + value: BigInt(0), + }; + } + + return { + to: upgradeExecutor, + data: upgradeExecutorEncodeFunctionData({ + functionName: 'executeCall', + args: [ + arbOwner.address, // target + arbOwnerEncodeFunctionData(params), // targetCallData + ], + }), + value: BigInt(0), + }; +} + +function applyDefaults(obj: T,defaults: { abi: Abi }) { + return { + return {...obj, abi: defaults.abi} + }; +} + +function arbOwnerReadContract( + client: PublicClient, + params: { functionName: string, args: any } +) { + return client.readContract({ + address: arbOwner.address, + abi: arbOwner.abi, + functionName: params.functionName, + args: params.args + }); +} + +export async function arbOwnerPrepareTransactionRequest< + TChain extends Chain | undefined +>( + client: PublicClient, + params: ArbOwnerPrepareTransactionRequestParameters +) { + const { to, data, value } = arbOwnerPrepareFunctionData(params); + + // @ts-ignore + return client.prepareTransactionRequest({ + chain: client.chain, + to, + data, + value, + account: params.account, + }); +} diff --git a/src/decorators/arbOwnerPublicActions.ts b/src/decorators/arbOwnerPublicActions.ts new file mode 100644 index 00000000..9dd09e39 --- /dev/null +++ b/src/decorators/arbOwnerPublicActions.ts @@ -0,0 +1,35 @@ +import { + Transport, + Chain, + Account, + Client, + PrepareTransactionRequestReturnType, + PublicClient, +} from 'viem'; + +import { + arbOwnerPrepareTransactionRequest, + ArbOwnerPrepareTransactionRequestParameters, +} from '../arbOwnerClient'; + +// arbOwnerReadContract +// arbOwnerSimulateContract +// arbOwnerPrepareTransactionRequest + +type ArbOwnerPublicActions< + TChain extends Chain | undefined = Chain | undefined +> = { + arbOwnerPrepareTransactionRequest: ( + args: ArbOwnerPrepareTransactionRequestParameters + ) => Promise>; +}; + +export function arbOwnerPublicActions< + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>(client: PublicClient): ArbOwnerPublicActions { + return { + arbOwnerPrepareTransactionRequest: (args) => + arbOwnerPrepareTransactionRequest(client, args), + }; +} diff --git a/src/index.ts b/src/index.ts index b62d67ff..d9e96468 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,6 +36,7 @@ import { upgradeExecutorEncodeFunctionData, UpgradeExecutorEncodeFunctionDataParameters, } from './upgradeExecutor'; +import { arbOwnerPublicActions } from './decorators/arbOwnerPublicActions'; import { ChainConfig, ChainConfigArbitrumParams } from './types/ChainConfig'; import { CoreContracts } from './types/CoreContracts'; @@ -45,6 +46,7 @@ import { prepareNodeConfig } from './prepareNodeConfig'; import * as utils from './utils'; export { + arbOwnerPublicActions, createRollup, createRollupPrepareTransactionRequest, createRollupPrepareConfig, diff --git a/src/types/utils.ts b/src/types/utils.ts index f093fc78..b7494ef2 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -1,4 +1,5 @@ import { Abi } from 'viem'; +import { arbOwner } from '../contracts'; // https://twitter.com/mattpocockuk/status/1622730173446557697 export type Prettify = { @@ -9,3 +10,16 @@ export type GetFunctionName = Extract< TAbi[number], { type: 'function' } >['name']; + +export type ExtractReadFunctionsFromAbi = Extract< + TAbi[number], + { type: 'function'; stateMutability: 'view' | 'pure' } +>[]; + +export type ExtractWriteFunctionsFromAbi = Extract< + TAbi[number], + { type: 'function'; stateMutability: 'nonpayable' | 'payable' } +>[]; + +type X = GetFunctionName>; +type Y = GetFunctionName>;