Skip to content

Commit a6b7bf3

Browse files
xeno097claude
andauthored
feat(deploy-sdk): deploy sdk core artifact api implementation (#8147)
Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
1 parent a99b106 commit a6b7bf3

18 files changed

Lines changed: 2380 additions & 137 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperlane-xyz/deploy-sdk': minor
3+
---
4+
5+
Added `CoreWriter` and `CoreArtifactReader` for coordinating core deployments using the Artifact API pattern. The `CoreWriter` orchestrates mailbox, ISM, hook, and validator announce deployments with support for both create and update flows. Updated `AltVMCoreModule` to handle `UnsetArtifactAddress` in derived core configs.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperlane-xyz/provider-sdk': minor
3+
---
4+
5+
Added `toDeployedOrUndefined` utility and `UnsetArtifactAddress` type to the artifact module. Extended `ProtocolProvider` interface with `createMailboxArtifactManager` and `createValidatorAnnounceArtifactManager` methods. Updated `mailboxArtifactToDerivedCoreConfig` to handle UNDERIVED artifacts with zero addresses gracefully. Widened `DerivedCoreConfig` fields to accept `UnsetArtifactAddress`.

typescript/aleo-sdk/src/clients/protocol.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,26 @@ import {
1313
import { type IProvider } from '@hyperlane-xyz/provider-sdk/altvm';
1414
import { type IRawHookArtifactManager } from '@hyperlane-xyz/provider-sdk/hook';
1515
import { type IRawIsmArtifactManager } from '@hyperlane-xyz/provider-sdk/ism';
16+
import { type IRawMailboxArtifactManager } from '@hyperlane-xyz/provider-sdk/mailbox';
1617
import {
1718
type AnnotatedTx,
1819
type TxReceipt,
1920
} from '@hyperlane-xyz/provider-sdk/module';
2021
import { type IRawWarpArtifactManager } from '@hyperlane-xyz/provider-sdk/warp';
22+
import { type IRawValidatorAnnounceArtifactManager } from '@hyperlane-xyz/provider-sdk/validator-announce';
2123
import { assert } from '@hyperlane-xyz/utils';
2224

2325
import { AleoHookArtifactManager } from '../hook/hook-artifact-manager.js';
2426
import { AleoIsmArtifactManager } from '../ism/ism-artifact-manager.js';
27+
import { AleoMailboxArtifactManager } from '../mailbox/mailbox-artifact-manager.js';
2528
import {
2629
fromAleoAddress,
2730
getNetworkPrefix,
2831
getProgramIdFromSuffix,
2932
getProgramSuffix,
3033
} from '../utils/helper.js';
31-
import { AleoNetworkId } from '../utils/types.js';
34+
import { AleoNetworkId, toAleoNetworkId } from '../utils/types.js';
35+
import { AleoValidatorAnnounceArtifactManager } from '../validator-announce/validator-announce-artifact-manager.js';
3236
import { AleoWarpArtifactManager } from '../warp/warp-artifact-manager.js';
3337

3438
import { AleoProvider } from './provider.js';
@@ -144,6 +148,48 @@ export class AleoProtocolProvider implements ProtocolProvider {
144148
});
145149
}
146150

151+
createMailboxArtifactManager(
152+
chainMetadata: ChainMetadataForAltVM,
153+
): IRawMailboxArtifactManager {
154+
const aleoNetworkId = toAleoNetworkId(
155+
parseInt(chainMetadata.chainId.toString()),
156+
);
157+
158+
const [rpcUrl] = chainMetadata.rpcUrls?.map(({ http }) => http) ?? [];
159+
assert(rpcUrl, 'got no rpcUrls');
160+
161+
const aleoClient =
162+
aleoNetworkId === AleoNetworkId.MAINNET
163+
? new AleoMainnetNetworkClient(rpcUrl)
164+
: new AleoTestnetNetworkClient(rpcUrl);
165+
166+
return new AleoMailboxArtifactManager(
167+
{ domainId: chainMetadata.domainId, aleoNetworkId },
168+
aleoClient,
169+
);
170+
}
171+
172+
createValidatorAnnounceArtifactManager(
173+
chainMetadata: ChainMetadataForAltVM,
174+
): IRawValidatorAnnounceArtifactManager | null {
175+
const aleoNetworkId = toAleoNetworkId(
176+
parseInt(chainMetadata.chainId.toString()),
177+
);
178+
179+
const [rpcUrl] = chainMetadata.rpcUrls?.map(({ http }) => http) ?? [];
180+
assert(rpcUrl, 'got no rpcUrls');
181+
182+
const aleoClient =
183+
aleoNetworkId === AleoNetworkId.MAINNET
184+
? new AleoMainnetNetworkClient(rpcUrl)
185+
: new AleoTestnetNetworkClient(rpcUrl);
186+
187+
return new AleoValidatorAnnounceArtifactManager(
188+
{ domainId: chainMetadata.domainId, aleoNetworkId },
189+
aleoClient,
190+
);
191+
}
192+
147193
getMinGas(): MinimumRequiredGasByAction {
148194
return {
149195
CORE_DEPLOY_GAS: 0n,

typescript/cosmos-sdk/src/clients/protocol.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import {
1010
import { type IProvider } from '@hyperlane-xyz/provider-sdk/altvm';
1111
import { type IRawHookArtifactManager } from '@hyperlane-xyz/provider-sdk/hook';
1212
import { type IRawIsmArtifactManager } from '@hyperlane-xyz/provider-sdk/ism';
13+
import { type IRawMailboxArtifactManager } from '@hyperlane-xyz/provider-sdk/mailbox';
1314
import {
1415
type AnnotatedTx,
1516
type TxReceipt,
1617
} from '@hyperlane-xyz/provider-sdk/module';
1718
import { type IRawWarpArtifactManager } from '@hyperlane-xyz/provider-sdk/warp';
19+
import { type IRawValidatorAnnounceArtifactManager } from '@hyperlane-xyz/provider-sdk/validator-announce';
1820
import { assert } from '@hyperlane-xyz/utils';
1921

2022
import { CosmosHookArtifactManager } from '../hook/hook-artifact-manager.js';
@@ -23,6 +25,7 @@ import { CosmosWarpArtifactManager } from '../warp/warp-artifact-manager.js';
2325

2426
import { CosmosNativeProvider } from './provider.js';
2527
import { CosmosNativeSigner } from './signer.js';
28+
import { CosmosMailboxArtifactManager } from '../mailbox/mailbox-artifact-manager.js';
2629

2730
export class CosmosNativeProtocolProvider implements ProtocolProvider {
2831
createProvider(chainMetadata: ChainMetadataForAltVM): Promise<IProvider> {
@@ -92,6 +95,29 @@ export class CosmosNativeProtocolProvider implements ProtocolProvider {
9295
return new CosmosWarpArtifactManager(rpcUrls);
9396
}
9497

98+
createMailboxArtifactManager(
99+
chainMetadata: ChainMetadataForAltVM,
100+
): IRawMailboxArtifactManager {
101+
const [rpcUrl, ...otherRpcUrls] =
102+
chainMetadata.rpcUrls?.map((rpc) => rpc.http) ?? [];
103+
assert(
104+
rpcUrl,
105+
`Expected at least one rpc url for chain ${chainMetadata.name}`,
106+
);
107+
108+
return new CosmosMailboxArtifactManager({
109+
domainId: chainMetadata.domainId,
110+
rpcUrls: [rpcUrl, ...otherRpcUrls],
111+
});
112+
}
113+
114+
createValidatorAnnounceArtifactManager(
115+
_chainMetadata: ChainMetadataForAltVM,
116+
): IRawValidatorAnnounceArtifactManager | null {
117+
// Cosmos does not support validator announce
118+
return null;
119+
}
120+
95121
getMinGas(): MinimumRequiredGasByAction {
96122
return {
97123
CORE_DEPLOY_GAS: BigInt(1e6),

typescript/deploy-sdk/src/AltVMCoreModule.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import {
2424
HypModuleArgs,
2525
TxReceipt,
2626
} from '@hyperlane-xyz/provider-sdk/module';
27-
import { Address, Logger, rootLogger } from '@hyperlane-xyz/utils';
27+
import {
28+
Address,
29+
Logger,
30+
eqAddress,
31+
isEmptyAddress,
32+
rootLogger,
33+
} from '@hyperlane-xyz/utils';
2834

2935
import { AltVMCoreReader } from './AltVMCoreReader.js';
3036
import { createHookWriter } from './hook/hook-writer.js';
@@ -289,6 +295,10 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
289295
const updateTransactions: AnnotatedTx[] = [];
290296

291297
const actualDefaultIsmConfig = actualConfig.defaultIsm;
298+
const actualIsmAddress =
299+
typeof actualDefaultIsmConfig === 'string'
300+
? actualDefaultIsmConfig
301+
: actualDefaultIsmConfig.address;
292302

293303
// Try to update (may also deploy) Ism with the expected config
294304
const { deployedIsm, ismUpdateTxs } = await this.deployOrUpdateIsm(
@@ -300,11 +310,11 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
300310
updateTransactions.push(...ismUpdateTxs);
301311
}
302312

303-
const newIsmDeployed = actualDefaultIsmConfig.address !== deployedIsm;
313+
const newIsmDeployed = !eqAddress(actualIsmAddress, deployedIsm);
304314
if (newIsmDeployed) {
305315
const { mailbox } = this.serialize();
306316
updateTransactions.push({
307-
annotation: `Updating default ISM of Mailbox from ${actualDefaultIsmConfig.address} to ${deployedIsm}`,
317+
annotation: `Updating default ISM of Mailbox from ${actualIsmAddress} to ${deployedIsm}`,
308318
...(await this.signer.getSetDefaultIsmTransaction({
309319
signer: actualConfig.owner,
310320
mailboxAddress: mailbox,
@@ -322,7 +332,7 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
322332
* @returns Object with deployedIsm address, and update Transactions
323333
*/
324334
public async deployOrUpdateIsm(
325-
actualDefaultIsmConfig: DerivedIsmConfig,
335+
actualDefaultIsmConfig: DerivedIsmConfig | string,
326336
expectDefaultIsmConfig: IsmConfig | string,
327337
): Promise<{
328338
deployedIsm: Address;
@@ -343,15 +353,29 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
343353
this.signer,
344354
);
345355

346-
// Read actual ISM state
347-
const actualArtifact = await writer.read(actualDefaultIsmConfig.address);
348-
349356
// Convert expected config to artifact format
350357
const expectedArtifact = ismConfigToArtifact(
351358
expectDefaultIsmConfig,
352359
this.chainLookup,
353360
);
354361

362+
// No existing ISM on chain — deploy the expected one directly
363+
const actualIsmAddress =
364+
typeof actualDefaultIsmConfig === 'string'
365+
? actualDefaultIsmConfig
366+
: actualDefaultIsmConfig.address;
367+
368+
if (isEmptyAddress(actualIsmAddress)) {
369+
const [deployed] = await writer.create(expectedArtifact);
370+
return {
371+
deployedIsm: deployed.deployed.address,
372+
ismUpdateTxs: [],
373+
};
374+
}
375+
376+
// Read actual ISM state
377+
const actualArtifact = await writer.read(actualIsmAddress);
378+
355379
this.logger.info(
356380
`Comparing target ISM config with ${this.args.chain} chain`,
357381
);
@@ -391,6 +415,10 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
391415
const updateTransactions: AnnotatedTx[] = [];
392416

393417
const actualDefaultHookConfig = actualConfig.defaultHook;
418+
const actualDefaultHookAddress =
419+
typeof actualDefaultHookConfig === 'string'
420+
? actualDefaultHookConfig
421+
: actualDefaultHookConfig.address;
394422

395423
// Try to update (may also deploy) Hook with the expected config
396424
const { deployedHook, hookUpdateTxs } = await this.deployOrUpdateHook(
@@ -402,11 +430,11 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
402430
updateTransactions.push(...hookUpdateTxs);
403431
}
404432

405-
const newHookDeployed = actualDefaultHookConfig.address !== deployedHook;
433+
const newHookDeployed = !eqAddress(actualDefaultHookAddress, deployedHook);
406434
if (newHookDeployed) {
407435
const { mailbox } = this.serialize();
408436
updateTransactions.push({
409-
annotation: `Updating default Hook of Mailbox from ${actualDefaultHookConfig.address} to ${deployedHook}`,
437+
annotation: `Updating default Hook of Mailbox from ${actualDefaultHookAddress} to ${deployedHook}`,
410438
...(await this.signer.getSetDefaultHookTransaction({
411439
signer: actualConfig.owner,
412440
mailboxAddress: mailbox,
@@ -432,6 +460,10 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
432460
const updateTransactions: AnnotatedTx[] = [];
433461

434462
const actualRequiredHookConfig = actualConfig.requiredHook;
463+
const actualRequiredHookAddress =
464+
typeof actualRequiredHookConfig === 'string'
465+
? actualRequiredHookConfig
466+
: actualRequiredHookConfig.address;
435467

436468
// Try to update (may also deploy) Hook with the expected config
437469
const { deployedHook, hookUpdateTxs } = await this.deployOrUpdateHook(
@@ -443,11 +475,11 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
443475
updateTransactions.push(...hookUpdateTxs);
444476
}
445477

446-
const newHookDeployed = actualRequiredHookConfig.address !== deployedHook;
478+
const newHookDeployed = !eqAddress(actualRequiredHookAddress, deployedHook);
447479
if (newHookDeployed) {
448480
const { mailbox } = this.serialize();
449481
updateTransactions.push({
450-
annotation: `Updating required Hook of Mailbox from ${actualRequiredHookConfig.address} to ${deployedHook}`,
482+
annotation: `Updating required Hook of Mailbox from ${actualRequiredHookAddress} to ${deployedHook}`,
451483
...(await this.signer.getSetRequiredHookTransaction({
452484
signer: actualConfig.owner,
453485
mailboxAddress: mailbox,
@@ -465,7 +497,7 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
465497
* @returns Object with deployedHook address, and update Transactions
466498
*/
467499
public async deployOrUpdateHook(
468-
actualHookConfig: DerivedHookConfig,
500+
actualHookConfig: DerivedHookConfig | string,
469501
expectHookConfig: HookConfig | string,
470502
): Promise<{
471503
deployedHook: Address;
@@ -493,9 +525,14 @@ export class AltVMCoreModule implements HypModule<CoreModuleType> {
493525
`Comparing target Hook config with ${this.args.chain} chain`,
494526
);
495527

528+
const actualHookAddress =
529+
typeof actualHookConfig === 'string'
530+
? actualHookConfig
531+
: actualHookConfig.address;
532+
496533
// Use the new deployOrUpdate method from HookWriter
497534
const result = await writer.deployOrUpdate({
498-
actualAddress: actualHookConfig.address,
535+
actualAddress: actualHookAddress,
499536
expectedConfig: expectHookConfig,
500537
});
501538

0 commit comments

Comments
 (0)