diff --git a/src/chains.ts b/src/chains.ts index b2525fe4..0f5f9d91 100644 --- a/src/chains.ts +++ b/src/chains.ts @@ -96,3 +96,31 @@ export { nitroTestnodeL2, nitroTestnodeL3, }; + +export const xai = defineChain({ + id: 660279, + network: 'Xai Mainnet', + name: 'Xai Mainnet', + nativeCurrency: { name: 'Xai', symbol: 'XAI', decimals: 18 }, + rpcUrls: { + public: { + http: ['https://xai-chain.net/rpc'], + }, + default: { + http: ['https://xai-chain.net/rpc'], + }, + }, + blockExplorers: { + default: { + name: 'Blockscout', + url: 'https://explorer.xai-chain.net', + }, + }, + contracts: { + multicall3: { + address: '0xca11bde05977b3631167028862be2a173976ca11', + blockCreated: 222549, + }, + }, + testnet: false, +}); diff --git a/src/decorators/__snapshots__/publicActionsChildChain.unit.test.ts.snap b/src/decorators/__snapshots__/publicActionsChildChain.unit.test.ts.snap new file mode 100644 index 00000000..56398936 --- /dev/null +++ b/src/decorators/__snapshots__/publicActionsChildChain.unit.test.ts.snap @@ -0,0 +1,28 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Getters > [getAllChainOwners] Should return all chain owners 1`] = `[]`; + +exports[`Getters > [getGasAccountingParams] Should return gas accounting params 1`] = ` +[ + 7000000n, + 32000000n, + 32000000n, +] +`; + +exports[`Getters > [getInfraFeeAccount] Should return infra fee account 1`] = `"0xbF5041Fc07E1c866D15c749156657B8eEd0fb649"`; + +exports[`Getters > [getMinimumGasPrice] Should return minimum gas price 1`] = `10000000n`; + +exports[`Getters > [getNetworkFeeAccount] Should return network fee account 1`] = `"0x32e7AF5A8151934F3787d0cD59EB6EDd0a736b1d"`; + +exports[`Getters > [getParentRewardRate] Should return parent reward rate 1`] = `0n`; + +exports[`Getters > [getParentRewardRecipient] Should return parent reward recipient 1`] = `"0x2E041280627800801E90E9Ac83532fadb6cAd99A"`; + +exports[`Getters > [getScheduledUpgrade] Should return scheduled upgrade 1`] = ` +{ + "arbosVersion": 0n, + "scheduledForTimestamp": 0n, +} +`; diff --git a/src/decorators/publicActionsChildChain.ts b/src/decorators/publicActionsChildChain.ts new file mode 100644 index 00000000..9f234b9c --- /dev/null +++ b/src/decorators/publicActionsChildChain.ts @@ -0,0 +1,178 @@ +import { Chain, PublicClient, Transport } from 'viem'; + +// Getters +import { + getAllChainOwners, + GetAllChainOwnersParameters, + GetAllChainOwnersReturnType, +} from '../actions/getAllChainOwners'; +import { + getInfraFeeAccount, + GetInfraFeeAccountParameters, + GetInfraFeeAccountReturnType, +} from '../actions/getInfraFeeAccount'; +import { + getNetworkFeeAccount, + GetNetworkFeeAccountParameters, + GetNetworkFeeAccountReturnType, +} from '../actions/getNetworkFeeAccount'; +import { + getScheduledUpgrade, + GetScheduledUpgradeParameters, + GetScheduledUpgradeReturnType, +} from '../actions/getScheduledUpgrade'; +import { + isChainOwner, + IsChainOwnerParameters, + IsChainOwnerReturnType, +} from '../actions/isChainOwner'; +import { + getGasAccountingParams, + GetGasAccountingParamsParameters, + GetGasAccountingParamsReturnType, +} from '../actions/getGasAccountingParams'; +import { + getMinimumGasPrice, + GetMinimumGasPriceParameters, + GetMinimumGasPriceReturnType, +} from '../actions/getMinimumGasPrice'; +import { + getParentBaseFeeEstimate, + GetParentBaseFeeEstimateParameters, + GetParentBaseFeeEstimateReturnType, +} from '../actions/getParentBaseFeeEstimate'; +import { + getParentRewardRate, + GetParentRewardRateParameters, + GetParentRewardRateReturnType, +} from '../actions/getParentRewardRate'; +import { + getParentRewardRecipient, + GetParentRewardRecipientParameters, + GetParentRewardRecipientReturnType, +} from '../actions/getParentRewardRecipient'; +// Setters +import { + addChainOwner, + AddChainOwnerParameters, + AddChainOwnerReturnType, +} from '../actions/addChainOwner'; +import { + removeChainOwner, + RemoveChainOwnerParameters, + RemoveChainOwnerReturnType, +} from '../actions/removeChainOwner'; +import { + setMaxTxGasLimit, + SetMaxTxGasLimitParameters, + SetMaxTxGasLimitReturnType, +} from '../actions/setMaxTxGasLimit'; +import { + setParentPricePerUnit, + SetParentPricePerUnitParameters, + SetParentPricePerUnitReturnType, +} from '../actions/setParentPricePerUnit'; +import { + setParentPricingRewardRate, + SetParentPricingRewardRateParameters, + SetParentPricingRewardRateReturnType, +} from '../actions/setParentPricingRewardRate'; +import { + setParentPricingRewardRecipient, + SetParentPricingRewardRecipientParameters, + SetParentPricingRewardRecipientReturnType, +} from '../actions/setParentPricingRewardRecipient'; +import { + setSpeedLimit, + SetSpeedLimitParameters, + SetSpeedLimitReturnType, +} from '../actions/setSpeedLimit'; + +export type PublicActionsChildChain = { + // Getters + getAllChainOwners: ( + parameters: GetAllChainOwnersParameters, + ) => Promise; + getInfraFeeAccount: ( + parameters: GetInfraFeeAccountParameters, + ) => Promise; + getNetworkFeeAccount: ( + parameters: GetNetworkFeeAccountParameters, + ) => Promise; + getScheduledUpgrade: ( + parameters: GetScheduledUpgradeParameters, + ) => Promise; + isChainOwner: (parameters: IsChainOwnerParameters) => Promise; + getGasAccountingParams: ( + parameters: GetGasAccountingParamsParameters, + ) => Promise; + getMinimumGasPrice: ( + parameters: GetMinimumGasPriceParameters, + ) => Promise; + getParentBaseFeeEstimate: ( + parameters: GetParentBaseFeeEstimateParameters, + ) => Promise; + getParentRewardRate: ( + parameters: GetParentRewardRateParameters, + ) => Promise; + getParentRewardRecipient: ( + parameters: GetParentRewardRecipientParameters, + ) => Promise; + // Setters + addChainOwner: (parameters: AddChainOwnerParameters) => Promise; + removeChainOwner: (parameters: RemoveChainOwnerParameters) => Promise; + setMaxTxGasLimit: (parameters: SetMaxTxGasLimitParameters) => Promise; + setParentPricePerUnit: ( + parameters: SetParentPricePerUnitParameters, + ) => Promise; + setParentPricingRewardRate: ( + parameters: SetParentPricingRewardRateParameters, + ) => Promise; + setParentPricingRewardRecipient: ( + parameters: SetParentPricingRewardRecipientParameters, + ) => Promise; + setSpeedLimit: (parameters: SetSpeedLimitParameters) => Promise; +}; + +/** + * Public actions for child chain + * + * @example + * import { createPublicClient, http } from 'viem' + * import { publicActionsChildChain } from '@arbitrum/orbit-sdk' + * + * export const publicClientChildChain = createPublicClient({ + * chain: orbitChain, + * transport: http(), + * }).extend(publicActionsChildChain()) + * + * const isAChainOwner = await publicClientChildChain.isChainOwner({ address: zeroAddress }) + */ +export function publicActionsChildChain< + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, +>() { + return (client: PublicClient) => { + return { + // Getters + getAllChainOwners: () => getAllChainOwners(client), + getInfraFeeAccount: () => getInfraFeeAccount(client), + getNetworkFeeAccount: () => getNetworkFeeAccount(client), + getScheduledUpgrade: () => getScheduledUpgrade(client), + isChainOwner: (args) => isChainOwner(client, args), + getGasAccountingParams: () => getGasAccountingParams(client), + getMinimumGasPrice: () => getMinimumGasPrice(client), + getParentBaseFeeEstimate: () => getParentBaseFeeEstimate(client), + getParentRewardRate: () => getParentRewardRate(client), + getParentRewardRecipient: () => getParentRewardRecipient(client), + // Setters + addChainOwner: (args) => addChainOwner(client, args), + removeChainOwner: (args) => removeChainOwner(client, args), + setMaxTxGasLimit: (args) => setMaxTxGasLimit(client, args), + setParentPricePerUnit: (args) => setParentPricePerUnit(client, args), + setParentPricingRewardRate: (args) => setParentPricingRewardRate(client, args), + setParentPricingRewardRecipient: (args) => setParentPricingRewardRecipient(client, args), + setSpeedLimit: (args) => setSpeedLimit(client, args), + } satisfies PublicActionsChildChain; + }; +} diff --git a/src/decorators/publicActionsChildChain.unit.test.ts b/src/decorators/publicActionsChildChain.unit.test.ts new file mode 100644 index 00000000..08166f26 --- /dev/null +++ b/src/decorators/publicActionsChildChain.unit.test.ts @@ -0,0 +1,72 @@ +import { it, expect, describe } from 'vitest'; + +import { createPublicClient, http, zeroAddress } from 'viem'; +import { publicActionsChildChain } from './publicActionsChildChain'; +import { arbitrum } from 'viem/chains'; +import { xai } from '../chains'; + +const client = createPublicClient({ + chain: arbitrum, + transport: http(), +}).extend(publicActionsChildChain()); + +describe('Getters', () => { + it('[getAllChainOwners] Should return all chain owners', async () => { + const allChainOwners = await client.getAllChainOwners(); + expect(allChainOwners).toMatchSnapshot(); + }); + + it('[getInfraFeeAccount] Should return infra fee account', async () => { + const infraFeeAccount = await client.getInfraFeeAccount(); + expect(infraFeeAccount).toMatchSnapshot(); + }); + + it('[getNetworkFeeAccount] Should return network fee account', async () => { + const networkFeeAccount = await client.getNetworkFeeAccount(); + expect(networkFeeAccount).toMatchSnapshot(); + }); + + it('[getScheduledUpgrade] Should return scheduled upgrade', async () => { + const scheduledUpgrade = await client.getScheduledUpgrade(); + expect(scheduledUpgrade).toMatchSnapshot(); + }); + + it('[isChainOwner] Should return if an address is a chain owner', async () => { + const xaiClient = createPublicClient({ + chain: xai, + transport: http(), + }).extend(publicActionsChildChain()); + const isZeroAddressChainOwner = await xaiClient.isChainOwner({ address: zeroAddress }); + expect(isZeroAddressChainOwner).toBeFalsy(); + const allChainOwners = await xaiClient.getAllChainOwners(); + const isChainOwner = await xaiClient.isChainOwner({ + address: allChainOwners[0], + }); + expect(isChainOwner).toBeTruthy(); + }); + + it('[getGasAccountingParams] Should return gas accounting params', async () => { + const gasAccountingParams = await client.getGasAccountingParams(); + expect(gasAccountingParams).toMatchSnapshot(); + }); + + it('[getMinimumGasPrice] Should return minimum gas price', async () => { + const minimumGasPrice = await client.getMinimumGasPrice(); + expect(minimumGasPrice).toMatchSnapshot(); + }); + + it('[getParentBaseFeeEstimate] Should return parent base fee estimate', async () => { + const parentBaseFeeEstimate = await client.getParentBaseFeeEstimate(); + expect(parentBaseFeeEstimate).toBeGreaterThan(0n); + }); + + it('[getParentRewardRate] Should return parent reward rate', async () => { + const parentRewardRate = await client.getParentRewardRate(); + expect(parentRewardRate).toMatchSnapshot(); + }); + + it('[getParentRewardRecipient] Should return parent reward recipient', async () => { + const parentRewardRecipient = await client.getParentRewardRecipient(); + expect(parentRewardRecipient).toMatchSnapshot(); + }); +}); diff --git a/src/decorators/publicActionsParentChain.ts b/src/decorators/publicActionsParentChain.ts new file mode 100644 index 00000000..b4995147 --- /dev/null +++ b/src/decorators/publicActionsParentChain.ts @@ -0,0 +1,161 @@ +import { Address, Chain, PublicClient, Transport } from 'viem'; + +// Getters +import { + getMaxTimeVariation, + GetMaxTimeVariationParameters, + GetMaxTimeVariationReturnType, +} from '../actions/getMaxTimeVariation'; +import { + isBatchPoster, + IsBatchPosterParameters, + IsBatchPosterReturnType, +} from '../actions/isBatchPoster'; +import { + isValidKeysetHash, + IsValidKeysetHashParameters, + IsValidKeysetHashReturnType, +} from '../actions/isValidKeysetHash'; +// Setters +import { + invalidateKeysetHash, + InvalidateKeysetHashParameters, + InvalidateKeysetHashReturnType, +} from '../actions/invalidateKeysetHash'; +import { + enableBatchPoster, + disableBatchPoster, + SetIsBatchPosterParameters, + SetIsBatchPosterReturnType, +} from '../actions/setIsbatchPoster'; +import { setKeyset, SetKeysetParameters, SetKeysetReturnType } from '../actions/setKeyset'; +import { + setMaxTimeVariation, + SetMaxTimeVariationParameters, + SetMaxTimeVariationReturnType, +} from '../actions/setMaxTimeVariation'; + +type Params = { sequencerInbox: Address } | void; + +export type PublicActionsParentChain = { + // Getters + getMaxTimeVariation: ( + parameters: GetMaxTimeVariationParameters, + ) => Promise; + isBatchPoster: (parameters: IsBatchPosterParameters) => Promise; + isValidKeysetHash: ( + parameters: IsValidKeysetHashParameters, + ) => Promise; + // Setters + invalidateKeysetHash: ( + parameters: InvalidateKeysetHashParameters, + ) => Promise; + enableBatchPoster: ( + parameters: SetIsBatchPosterParameters, + ) => Promise; + disableBatchPoster: ( + parameters: SetIsBatchPosterParameters, + ) => Promise; + setKeyset: (parameters: SetKeysetParameters) => Promise; + setMaxTimeVariation: ( + parameters: SetMaxTimeVariationParameters, + ) => Promise; +}; + +/** + * Simplifies the overall typing with curried sequencerInbox address + * + * By design, sequencerInbox is either passed initially from the decorator, or on each call + * + * Address passed through each call has the priority over the address passed to the decorator, for override + */ + +function getSequencerInboxAddress( + params: Params, + args: { sequencerInbox?: Address } | void, +): Address { + return ((args && args.sequencerInbox) ?? (params && params.sequencerInbox)) as unknown as Address; +} + +/** + * Public actions for parent chain + * + * @example + * import { createPublicClient, http } from 'viem' + * import { publicActionsParentChain } from '@arbitrum/orbit-sdk' + * import { arbitrum } from 'viem/chains' + * + * export const publicClientParentChain = createPublicClient({ + * chain: arbitrum, + * transport: http(), + * }).extend(publicActionsParentChain({ + * sequencerInbox: '0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6' + * })) + * + * const { delayBlocks, futureBlocks, delaySeconds, futureSeconds } = await publicClientParentChain.getMaxTimeVariation() + */ +export function publicActionsParentChain< + TParams extends Params = void, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, +>(params: void): (client: PublicClient) => PublicActionsParentChain; +export function publicActionsParentChain< + TParams extends Params = { sequencerInbox: Address }, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, +>(params: TParams): (client: PublicClient) => PublicActionsParentChain; +export function publicActionsParentChain< + TParams extends Params, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, +>(params: TParams) { + return (client: PublicClient) => { + // sequencerInbox is curried, sequencerInbox param is optional. + return { + // Getters + getMaxTimeVariation: (args) => + getMaxTimeVariation(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + isBatchPoster: (args) => + isBatchPoster(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + isValidKeysetHash: (args) => + isValidKeysetHash(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + // Setters + invalidateKeysetHash: (args) => + invalidateKeysetHash(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + enableBatchPoster: (args) => + enableBatchPoster(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + disableBatchPoster: (args) => + disableBatchPoster(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + setKeyset: (args) => + setKeyset(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + setMaxTimeVariation: (args) => + setMaxTimeVariation(client, { + ...args, + sequencerInbox: getSequencerInboxAddress(params, args), + }), + } satisfies PublicActionsParentChain< + TParams extends { sequencerInbox: Address } ? true : false + >; + }; +} diff --git a/src/decorators/publicActionsParentChain.unit.test.ts b/src/decorators/publicActionsParentChain.unit.test.ts new file mode 100644 index 00000000..3c5a235a --- /dev/null +++ b/src/decorators/publicActionsParentChain.unit.test.ts @@ -0,0 +1,56 @@ +import { it, expect, describe } from 'vitest'; + +import { createPublicClient, http, padHex, zeroAddress } from 'viem'; +import { sepolia } from '../chains'; +import { publicActionsParentChain } from './publicActionsParentChain'; +import { arbitrum } from 'viem/chains'; + +const arbSepoliaSequencerInbox = '0x6c97864CE4bEf387dE0b3310A44230f7E3F1be0D'; +const sepoliaClient = createPublicClient({ + chain: sepolia, + transport: http(), +}).extend(publicActionsParentChain({ sequencerInbox: arbSepoliaSequencerInbox })); +const arbitrumClient = createPublicClient({ + chain: arbitrum, + transport: http(), +}).extend( + publicActionsParentChain({ sequencerInbox: '0x995a9d3ca121D48d21087eDE20bc8acb2398c8B1' }), +); + +describe('Getters', () => { + it('[maxTimeVariation] Should return max time variation', async () => { + const maxTimeVariation = await sepoliaClient.getMaxTimeVariation(); + expect(maxTimeVariation).toEqual({ + delayBlocks: 5760n, + futureBlocks: 64n, + delaySeconds: 86400n, + futureSeconds: 768n, + }); + }); + + it('[isBatchPoster] Should return if an address is a batch poster', async () => { + const isZeroAddressBatchPoster = await arbitrumClient.isBatchPoster({ + batchPoster: zeroAddress, + }); + expect(isZeroAddressBatchPoster).toBeFalsy(); + + const isBatchPosterOnXai = await arbitrumClient.isBatchPoster({ + batchPoster: '0x7F68dba68E72a250004812fe04F1123Fca89aBa9', + }); + expect(isBatchPosterOnXai).toBeTruthy(); + }); + + it('[isValidKeysetHash] Should return if a keysetHash is a valid one', async () => { + const isEmptyHashValidKeysetHash = await arbitrumClient.isValidKeysetHash({ + keysetHash: padHex('0x'), + }); + expect(isEmptyHashValidKeysetHash).toBeFalsy(); + + // Test on ProofOfPlay + const isAValidKeysetHashOnPoP = await arbitrumClient.isValidKeysetHash({ + keysetHash: '0xc2c008db9d0d25ca30d60080f5ebd3d114dbccd95f2bd2df05446eae6b1acadf', + sequencerInbox: '0xa58F38102579dAE7C584850780dDA55744f67DF1', + }); + expect(isAValidKeysetHashOnPoP).toBeTruthy(); + }); +});