Skip to content

Commit 26d08de

Browse files
xeno097claude
andauthored
feat(aleo-sdk): aleo core artifact api implementation (#8142)
Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
1 parent d1879b5 commit 26d08de

27 files changed

Lines changed: 3077 additions & 1144 deletions

.changeset/aleo-core-artifacts.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperlane-xyz/aleo-sdk": minor
3+
---
4+
5+
Implemented mailbox and validator announce artifact API for Aleo protocol, enabling deployment and configuration management through the unified artifact pattern.

.github/workflows/test-sdk-e2e.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ jobs:
5353
- 4_warp
5454
- 5_ism_artifacts
5555
- 6_hook_artifacts
56+
- 7a_warp_native_artifacts
57+
- 7b_warp_collateral_artifacts
58+
- 7c_warp_synthetic_artifacts
59+
- 8_mailbox_artifacts
60+
- 9_validator_announce_artifacts
5661
steps:
5762
- uses: actions/checkout@v6
5863
with:

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import {
1818
import { assert, retryAsync } from '@hyperlane-xyz/utils';
1919

2020
import {
21-
MAINNET_PREFIX,
21+
getNetworkPrefix,
2222
RETRY_ATTEMPTS,
2323
RETRY_DELAY_MS,
24-
TESTNET_PREFIX,
2524
} from '../utils/helper.js';
25+
import { toAleoNetworkId } from '../utils/types.js';
2626

2727
export type AnyAleoNetworkClient =
2828
| AleoMainnetNetworkClient
@@ -44,18 +44,15 @@ export class AleoBase {
4444
protected readonly warpSuffix: string;
4545

4646
constructor(rpcUrls: string[], chainId: string | number) {
47-
assert(
48-
+chainId === 0 || +chainId === 1,
49-
`Unknown chain id ${chainId} for Aleo, only 0 or 1 allowed`,
50-
);
47+
const aleoNetworkId = toAleoNetworkId(+chainId);
5148
assert(rpcUrls.length > 0, `got no rpcUrls`);
5249

5350
// because the aleo provable sdk appends /testnet or /mainnet to the base
5451
// rpc automatically we need to remove it here
5552
this.rpcUrls = rpcUrls.map((r) =>
5653
r.replaceAll('/testnet', '').replaceAll('/mainnet', ''),
5754
);
58-
this.chainId = +chainId;
55+
this.chainId = aleoNetworkId;
5956

6057
this.aleoClient = this.chainId
6158
? new AleoTestnetNetworkClient(this.rpcUrls[0])
@@ -72,7 +69,7 @@ export class AleoBase {
7269
getOrInitConsensusVersionTestHeights(this.consensusVersionHeights);
7370
}
7471

75-
this.prefix = this.chainId ? TESTNET_PREFIX : MAINNET_PREFIX;
72+
this.prefix = getNetworkPrefix(aleoNetworkId);
7673

7774
this.ismManager = process.env['ALEO_ISM_MANAGER_SUFFIX']
7875
? `${this.prefix}_ism_manager_${process.env['ALEO_ISM_MANAGER_SUFFIX']}.aleo`
@@ -81,6 +78,10 @@ export class AleoBase {
8178
this.warpSuffix = process.env['ALEO_WARP_SUFFIX'] || '';
8279
}
8380

81+
getAleoClient(): AnyAleoNetworkClient {
82+
return this.aleoClient;
83+
}
84+
8485
protected getProgramManager(privateKey?: string): AnyProgramManager {
8586
if (this.chainId) {
8687
const account = privateKey

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ import { assert } from '@hyperlane-xyz/utils';
2323
import { AleoHookArtifactManager } from '../hook/hook-artifact-manager.js';
2424
import { AleoIsmArtifactManager } from '../ism/ism-artifact-manager.js';
2525
import {
26-
MAINNET_PREFIX,
27-
TESTNET_PREFIX,
2826
fromAleoAddress,
27+
getNetworkPrefix,
2928
getProgramIdFromSuffix,
3029
getProgramSuffix,
3130
} from '../utils/helper.js';
@@ -123,8 +122,7 @@ export class AleoProtocolProvider implements ProtocolProvider {
123122
? new AleoMainnetNetworkClient(rpcUrl)
124123
: new AleoTestnetNetworkClient(rpcUrl);
125124

126-
const prefix =
127-
chainId === AleoNetworkId.TESTNET ? TESTNET_PREFIX : MAINNET_PREFIX;
125+
const prefix = getNetworkPrefix(chainId);
128126
const customIsmSuffix = process.env['ALEO_ISM_MANAGER_SUFFIX'];
129127
const ismManagerAddress = customIsmSuffix
130128
? `${prefix}_ism_manager_${customIsmSuffix}.aleo`

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

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
SUFFIX_LENGTH_LONG,
1010
SUFFIX_LENGTH_SHORT,
1111
fromAleoAddress,
12-
getProgramIdFromSuffix,
1312
getProgramSuffix,
13+
getUnusedSuffix,
1414
loadProgramsInDeployOrder,
1515
programIdToPlaintext,
1616
toAleoAddress,
@@ -32,7 +32,7 @@ export class AleoSigner
3232
rpcUrls: string[],
3333
privateKey: string,
3434
extraParams?: Record<string, any>,
35-
): Promise<AltVM.ISigner<AleoTransaction, AleoReceipt>> {
35+
): Promise<AleoSigner> {
3636
assert(extraParams, `extra params not defined`);
3737

3838
const metadata = extraParams.metadata as Record<string, unknown>;
@@ -91,28 +91,6 @@ export class AleoSigner
9191
);
9292
}
9393

94-
private async getUnusedSuffix(
95-
programName: AleoProgram,
96-
length: number,
97-
maxAttempts = 20,
98-
): Promise<string> {
99-
for (let i = 0; i < maxAttempts; i++) {
100-
const suffix = this.generateSuffix(length);
101-
const programId = getProgramIdFromSuffix(
102-
this.prefix,
103-
programName,
104-
suffix,
105-
);
106-
if (!(await this.isProgramDeployed(programId))) {
107-
return suffix;
108-
}
109-
}
110-
111-
throw new Error(
112-
`Could not find an unused suffix for ${programName} after ${maxAttempts} attempts`,
113-
);
114-
}
115-
11694
async getWarpTokenSuffix(
11795
tokenType: TokenType,
11896
preferredSuffix?: string,
@@ -264,7 +242,9 @@ export class AleoSigner
264242
);
265243
}
266244

267-
const mailboxSuffix = await this.getUnusedSuffix(
245+
const mailboxSuffix = await getUnusedSuffix(
246+
this.aleoClient,
247+
this.prefix,
268248
'mailbox',
269249
SUFFIX_LENGTH_LONG,
270250
);

typescript/aleo-sdk/src/hook/hook-artifact-manager.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ export class AleoHookArtifactManager implements IRawHookArtifactManager {
6767
new AleoIgpHookReader(this.aleoClient),
6868
};
6969

70-
const maybeReader = readers[type]();
71-
72-
assert(maybeReader, `Hook writer for ${type} not found`);
73-
return maybeReader;
70+
assert(readers[type], `Hook reader for ${type} not found`);
71+
return readers[type]();
7472
}
7573

7674
createWriter<T extends HookType>(
@@ -92,9 +90,7 @@ export class AleoHookArtifactManager implements IRawHookArtifactManager {
9290
new AleoIgpHookWriter(this.aleoClient, signer, mailboxAddress),
9391
};
9492

95-
const maybeWriter = writers[type]();
96-
97-
assert(maybeWriter, `Hook writer for ${type} not found`);
98-
return maybeWriter;
93+
assert(writers[type], `Hook writer for ${type} not found`);
94+
return writers[type]();
9995
}
10096
}

typescript/aleo-sdk/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ export {
1414
} from './hook/merkle-tree-hook.js';
1515
export { AleoIgpHookReader, AleoIgpHookWriter } from './hook/igp-hook.js';
1616
export { AleoWarpArtifactManager } from './warp/warp-artifact-manager.js';
17+
export { AleoMailboxArtifactManager } from './mailbox/mailbox-artifact-manager.js';
18+
export { AleoMailboxReader, AleoMailboxWriter } from './mailbox/mailbox.js';
19+
export { AleoValidatorAnnounceArtifactManager } from './validator-announce/validator-announce-artifact-manager.js';
20+
export {
21+
AleoValidatorAnnounceReader,
22+
AleoValidatorAnnounceWriter,
23+
} from './validator-announce/validator-announce.js';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {
2+
type ArtifactReader,
3+
type ArtifactWriter,
4+
} from '@hyperlane-xyz/provider-sdk/artifact';
5+
import {
6+
type DeployedMailboxAddress,
7+
type IRawMailboxArtifactManager,
8+
type MailboxType,
9+
type RawMailboxArtifactConfigs,
10+
} from '@hyperlane-xyz/provider-sdk/mailbox';
11+
import { assert } from '@hyperlane-xyz/utils';
12+
13+
import { type AnyAleoNetworkClient } from '../clients/base.js';
14+
import { type AleoSigner } from '../clients/signer.js';
15+
import { type AleoArtifactNetworkConfig } from '../utils/types.js';
16+
17+
import { AleoMailboxReader, AleoMailboxWriter } from './mailbox.js';
18+
19+
/**
20+
* Aleo Mailbox Artifact Manager implementing IRawMailboxArtifactManager.
21+
*
22+
* This manager:
23+
* - Provides factory methods for creating mailbox readers and writers
24+
* - Handles mailbox deployment and configuration
25+
*/
26+
export class AleoMailboxArtifactManager implements IRawMailboxArtifactManager {
27+
constructor(
28+
private readonly config: AleoArtifactNetworkConfig,
29+
private readonly aleoClient: AnyAleoNetworkClient,
30+
) {}
31+
32+
async readMailbox(address: string) {
33+
const reader = this.createReader('mailbox');
34+
return reader.read(address);
35+
}
36+
37+
createReader<T extends MailboxType>(
38+
type: T,
39+
): ArtifactReader<RawMailboxArtifactConfigs[T], DeployedMailboxAddress> {
40+
const readers: {
41+
[K in MailboxType]: () => ArtifactReader<
42+
RawMailboxArtifactConfigs[K],
43+
DeployedMailboxAddress
44+
>;
45+
} = {
46+
mailbox: () => new AleoMailboxReader(this.aleoClient),
47+
};
48+
49+
assert(readers[type], `Mailbox reader for ${type} not found`);
50+
return readers[type]();
51+
}
52+
53+
createWriter<T extends MailboxType>(
54+
type: T,
55+
signer: AleoSigner,
56+
): ArtifactWriter<RawMailboxArtifactConfigs[T], DeployedMailboxAddress> {
57+
const writers: {
58+
[K in MailboxType]: () => ArtifactWriter<
59+
RawMailboxArtifactConfigs[K],
60+
DeployedMailboxAddress
61+
>;
62+
} = {
63+
mailbox: () =>
64+
new AleoMailboxWriter(this.config, this.aleoClient, signer),
65+
};
66+
67+
assert(writers[type], `Mailbox writer for ${type} not found`);
68+
return writers[type]();
69+
}
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { assert } from '@hyperlane-xyz/utils';
2+
3+
import { type AnyAleoNetworkClient } from '../clients/base.js';
4+
import { queryMappingValue } from '../utils/base-query.js';
5+
import {
6+
formatHookAddress,
7+
formatIsmAddress,
8+
fromAleoAddress,
9+
} from '../utils/helper.js';
10+
import {
11+
type AleoMailboxConfig,
12+
type AleoMailboxData,
13+
} from '../utils/types.js';
14+
15+
/**
16+
* Query mailbox configuration from the chain.
17+
*
18+
* @param aleoClient - The Aleo network client
19+
* @param mailboxAddress - The full mailbox address (e.g., "mailbox.aleo/aleo1...")
20+
* @param onChainArtifactManagers - Artifact manager addresses (hookManagerAddress is ignored and derived from mailbox)
21+
* @returns The mailbox configuration
22+
*/
23+
export async function getMailboxConfig(
24+
aleoClient: AnyAleoNetworkClient,
25+
mailboxAddress: string,
26+
): Promise<AleoMailboxConfig> {
27+
const { programId: mailboxProgramId } = fromAleoAddress(mailboxAddress);
28+
assert(
29+
mailboxProgramId,
30+
`Program Id is required for reading the on chain mailbox config. Is the input address formatted as "programId/address"?`,
31+
);
32+
33+
const mailboxData = await queryMappingValue(
34+
aleoClient,
35+
mailboxProgramId,
36+
'mailbox',
37+
'true',
38+
(raw): AleoMailboxData => {
39+
const data = raw as AleoMailboxData | undefined;
40+
assert(
41+
data?.mailbox_owner,
42+
`Invalid mailbox data structure for mailbox ${mailboxAddress}, expected object with mailbox_owner field`,
43+
);
44+
return data;
45+
},
46+
);
47+
48+
const imports = await aleoClient.getProgramImportNames(mailboxProgramId);
49+
const ismManagerProgramId = imports.find((i) => i.includes('ism_manager'));
50+
assert(
51+
ismManagerProgramId,
52+
`Expected to find ISM manager program id in mailbox program at ${mailboxAddress}`,
53+
);
54+
55+
return {
56+
address: mailboxAddress,
57+
owner: mailboxData.mailbox_owner,
58+
localDomain: mailboxData.local_domain,
59+
nonce: mailboxData.nonce,
60+
defaultIsm: formatIsmAddress(mailboxData.default_ism, ismManagerProgramId),
61+
defaultHook: formatHookAddress(mailboxData.default_hook, mailboxProgramId),
62+
requiredHook: formatHookAddress(
63+
mailboxData.required_hook,
64+
mailboxProgramId,
65+
),
66+
};
67+
}

0 commit comments

Comments
 (0)