Skip to content

Commit 04b4152

Browse files
committed
feat: add orbit-actions wrapper to get contract versions
1 parent 0c9dbc3 commit 04b4152

File tree

9 files changed

+321
-0
lines changed

9 files changed

+321
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
INBOX_ADDRESS=0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9
2+
NETWORK=arb1
3+
ORBIT_ACTIONS_IMAGE=offchainlabs/chain-actions
4+
PARENT_CHAIN_RPC=
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Get Orbit chain contract versions
2+
3+
This example uses the SDK's `getOrbitChainContractVersions` helper to inspect the deployed Nitro contract versions for an Orbit chain on Arbitrum.
4+
5+
It runs against the current repository checkout, so `pnpm dev` builds the local SDK before executing the example.
6+
7+
It requires:
8+
9+
- Docker installed and available on `PATH`
10+
- an inbox address for the chain you want to inspect
11+
- a parent chain RPC URL
12+
13+
## Setup
14+
15+
1. Install dependencies
16+
17+
```bash
18+
pnpm install
19+
```
20+
21+
2. Create `.env`
22+
23+
```bash
24+
cp .env.example .env
25+
```
26+
27+
3. Run the example
28+
29+
```bash
30+
pnpm dev
31+
```
32+
33+
The script prints the discovered contract versions and any upgrade recommendation as JSON.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { config } from 'dotenv';
2+
import { getOrbitChainContractVersions } from '@arbitrum/chain-sdk';
3+
import { isAddress } from 'viem';
4+
5+
config();
6+
7+
const orbitActionsImage = process.env.ORBIT_ACTIONS_IMAGE ?? 'offchainlabs/chain-actions';
8+
const network = process.env.NETWORK ?? 'arb1';
9+
const inboxAddress = process.env.INBOX_ADDRESS;
10+
const parentChainRpc = process.env.PARENT_CHAIN_RPC;
11+
12+
if (!inboxAddress || !isAddress(inboxAddress)) {
13+
throw new Error('Please provide the "INBOX_ADDRESS" environment variable');
14+
}
15+
16+
if (!parentChainRpc || parentChainRpc === '') {
17+
throw new Error('Please provide the "PARENT_CHAIN_RPC" environment variable');
18+
}
19+
20+
async function main() {
21+
console.log('Getting Orbit chain contract versions...');
22+
const result = await getOrbitChainContractVersions({
23+
image: orbitActionsImage,
24+
inboxAddress: inboxAddress as `0x${string}`,
25+
network,
26+
env: {
27+
PARENT_CHAIN_RPC: parentChainRpc,
28+
},
29+
});
30+
31+
console.log(result);
32+
}
33+
34+
main().catch((error) => {
35+
console.error(error);
36+
process.exitCode = 1;
37+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "get-chain-contract-versions",
3+
"private": true,
4+
"version": "0.1.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "tsc --outDir dist && node ./dist/index.js",
8+
"predev": "pnpm --dir ../.. build"
9+
},
10+
"devDependencies": {
11+
"@types/node": "^20.9.0",
12+
"typescript": "^5.2.2"
13+
},
14+
"dependencies": {
15+
"@arbitrum/chain-sdk": "workspace:*"
16+
}
17+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../tsconfig.base.json",
3+
"include": ["./**/*"]
4+
}

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Address } from 'viem';
2+
3+
import { runDockerCommand } from './runDockerCommand';
4+
5+
export type GetOrbitChainContractVersionsParameters = {
6+
image?: string;
7+
inboxAddress: Address;
8+
network: string;
9+
env?: Record<string, string | undefined>;
10+
};
11+
12+
export type GetOrbitChainContractVersionsResult = {
13+
versions: Record<string, string | null>;
14+
upgradeRecommendation: unknown;
15+
};
16+
17+
export async function getOrbitChainContractVersions({
18+
image = 'offchainlabs/chain-actions',
19+
inboxAddress,
20+
network,
21+
env,
22+
}: GetOrbitChainContractVersionsParameters): Promise<GetOrbitChainContractVersionsResult> {
23+
const { stdout } = await runDockerCommand({
24+
image,
25+
entrypoint: 'yarn',
26+
command: ['--silent', 'orbit:contracts:version', '--network', network, '--no-compile'],
27+
env: {
28+
...env,
29+
INBOX_ADDRESS: inboxAddress,
30+
JSON_OUTPUT: 'true',
31+
},
32+
});
33+
34+
let parsed: unknown;
35+
36+
try {
37+
parsed = JSON.parse(stdout) as unknown;
38+
} catch {
39+
throw new Error('Failed to parse Orbit chain contract versions');
40+
}
41+
42+
if (
43+
typeof parsed !== 'object' ||
44+
parsed === null ||
45+
!('versions' in parsed) ||
46+
!('upgradeRecommendation' in parsed)
47+
) {
48+
throw new Error('Failed to parse Orbit chain contract versions');
49+
}
50+
51+
return parsed as GetOrbitChainContractVersionsResult;
52+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import { getOrbitChainContractVersions } from './getOrbitChainContractVersions';
4+
import { runDockerCommand } from './runDockerCommand';
5+
6+
vi.mock('./runDockerCommand', () => ({
7+
runDockerCommand: vi.fn(),
8+
}));
9+
10+
describe('getOrbitChainContractVersions', () => {
11+
beforeEach(() => {
12+
vi.mocked(runDockerCommand).mockReset();
13+
});
14+
15+
it('uses the default Orbit Actions image through the internal docker runner', async () => {
16+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
17+
argv: ['docker', 'run'],
18+
stdout:
19+
'{"versions":{"Inbox":"v1.1.1","RollupProxy":"v1.1.1"},"upgradeRecommendation":{"message":"No upgrade path found"}}',
20+
stderr: '',
21+
exitCode: 0,
22+
});
23+
24+
const result = await getOrbitChainContractVersions({
25+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
26+
network: 'arb1',
27+
env: {
28+
PARENT_CHAIN_RPC: 'https://rpc.example',
29+
},
30+
});
31+
32+
expect(runDockerCommand).toHaveBeenCalledWith({
33+
image: 'offchainlabs/chain-actions',
34+
entrypoint: 'yarn',
35+
command: ['--silent', 'orbit:contracts:version', '--network', 'arb1', '--no-compile'],
36+
env: {
37+
PARENT_CHAIN_RPC: 'https://rpc.example',
38+
INBOX_ADDRESS: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
39+
JSON_OUTPUT: 'true',
40+
},
41+
});
42+
43+
expect(result).toEqual({
44+
versions: {
45+
Inbox: 'v1.1.1',
46+
RollupProxy: 'v1.1.1',
47+
},
48+
upgradeRecommendation: {
49+
message: 'No upgrade path found',
50+
},
51+
});
52+
});
53+
54+
it('uses a custom Orbit Actions image when one is provided', async () => {
55+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
56+
argv: ['docker', 'run'],
57+
stdout:
58+
'{"versions":{"Inbox":"v1.1.1","Bridge":"v1.1.2","RollupProxy":null},"upgradeRecommendation":null}',
59+
stderr: '',
60+
exitCode: 0,
61+
});
62+
63+
const result = await getOrbitChainContractVersions({
64+
image: 'offchainlabs/chain-actions:custom',
65+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
66+
network: 'arb-sepolia',
67+
});
68+
69+
expect(runDockerCommand).toHaveBeenCalledWith({
70+
image: 'offchainlabs/chain-actions:custom',
71+
entrypoint: 'yarn',
72+
command: ['--silent', 'orbit:contracts:version', '--network', 'arb-sepolia', '--no-compile'],
73+
env: {
74+
INBOX_ADDRESS: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
75+
JSON_OUTPUT: 'true',
76+
},
77+
});
78+
79+
expect(result).toEqual({
80+
versions: {
81+
Inbox: 'v1.1.1',
82+
Bridge: 'v1.1.2',
83+
RollupProxy: null,
84+
},
85+
upgradeRecommendation: null,
86+
});
87+
});
88+
89+
it('throws when the docker output does not contain a valid JSON payload', async () => {
90+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
91+
argv: ['docker', 'run'],
92+
stdout: 'not json',
93+
stderr: '',
94+
exitCode: 0,
95+
});
96+
97+
await expect(
98+
getOrbitChainContractVersions({
99+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
100+
network: 'arb1',
101+
}),
102+
).rejects.toThrow('Failed to parse Orbit chain contract versions');
103+
});
104+
105+
it.each(['null', '"unexpected"', '42', 'true', '[]'])(
106+
'throws when the parsed JSON payload is not an object: %s',
107+
async (stdout) => {
108+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
109+
argv: ['docker', 'run'],
110+
stdout,
111+
stderr: '',
112+
exitCode: 0,
113+
});
114+
115+
await expect(
116+
getOrbitChainContractVersions({
117+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
118+
network: 'arb1',
119+
}),
120+
).rejects.toThrow('Failed to parse Orbit chain contract versions');
121+
},
122+
);
123+
124+
it('throws when the JSON payload is missing top level fields', async () => {
125+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
126+
argv: ['docker', 'run'],
127+
stdout: '{"versions":{"Inbox":"v1.1.1"}}',
128+
stderr: '',
129+
exitCode: 0,
130+
});
131+
132+
await expect(
133+
getOrbitChainContractVersions({
134+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
135+
network: 'arb1',
136+
}),
137+
).rejects.toThrow('Failed to parse Orbit chain contract versions');
138+
});
139+
140+
it('accepts any JSON payload that includes versions and upgradeRecommendation', async () => {
141+
vi.mocked(runDockerCommand).mockResolvedValueOnce({
142+
argv: ['docker', 'run'],
143+
stdout: '{"versions":"not-validated","upgradeRecommendation":null}',
144+
stderr: '',
145+
exitCode: 0,
146+
});
147+
148+
await expect(
149+
getOrbitChainContractVersions({
150+
inboxAddress: '0xaE21fDA3de92dE2FDAF606233b2863782Ba046F9',
151+
network: 'arb1',
152+
}),
153+
).resolves.toEqual({
154+
versions: 'not-validated',
155+
upgradeRecommendation: null,
156+
});
157+
});
158+
});

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ import {
191191
FetchDecimalsProps,
192192
} from './utils/erc20';
193193
import { prepareArbitrumNetwork } from './utils/registerNewNetwork';
194+
import { getOrbitChainContractVersions } from './getOrbitChainContractVersions';
194195

195196
export {
196197
arbOwnerPublicActions,
@@ -354,4 +355,6 @@ export {
354355
FetchDecimalsProps,
355356
//
356357
prepareArbitrumNetwork,
358+
//
359+
getOrbitChainContractVersions,
357360
};

0 commit comments

Comments
 (0)