Skip to content
This repository was archived by the owner on Jul 10, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ yarn test # Run tests against existing sandbox

The sandbox runs on `http://localhost:8080` by default.

## Artifact registry (devnet.aztec-registry.xyz)

This boilerplate can optionally upload your compiled contract artifact JSONs to the Aztec **artifact registry** during deployment.

- **Registry URL**: set `AZTEC_ARTIFACT_REGISTRY_URL` (defaults to `https://devnet.aztec-registry.xyz/`)
- **Enable upload**: set `AZTEC_ARTIFACT_REGISTRY_UPLOAD=1`
- **Fail deploy on upload errors** (optional): set `AZTEC_ARTIFACT_REGISTRY_STRICT=1`

### Deploy the Counter contract

You can deploy contracts programmatically using the `deployCounter` utility function from `src/ts/utils.ts`. The deployment automatically uploads artifacts to the registry if `AZTEC_ARTIFACT_REGISTRY_UPLOAD=1` is set. See `src/ts/counter.test.ts` for an example of how to use it.

### All tests
Run both Noir contract tests and TypeScript integration tests:

Expand Down
128 changes: 128 additions & 0 deletions src/ts/artifactRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { readFile } from "node:fs/promises";

export type ArtifactRegistryUploadResponse =
| {
success: true;
filename?: string;
classId?: string;
contractName?: string;
functionCount?: number;
[key: string]: unknown;
}
| {
success: false;
error?: string;
message?: string;
[key: string]: unknown;
};

function normalizeBaseUrl(baseUrl: string): string {
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
}

export function getArtifactRegistryBaseUrl(): string {
return (
process.env.AZTEC_ARTIFACT_REGISTRY_URL ??
"https://devnet.aztec-registry.xyz/"
);
}

export function shouldUploadArtifacts(): boolean {
const v = process.env.AZTEC_ARTIFACT_REGISTRY_UPLOAD ?? "";
return v === "1" || v.toLowerCase() === "true";
}

export function isStrictUpload(): boolean {
const v = process.env.AZTEC_ARTIFACT_REGISTRY_STRICT ?? "";
return v === "1" || v.toLowerCase() === "true";
}

export async function uploadArtifactToRegistry(params: {
artifact: unknown;
filename: string;
registryBaseUrl?: string;
}): Promise<ArtifactRegistryUploadResponse> {
const base = normalizeBaseUrl(
params.registryBaseUrl ?? getArtifactRegistryBaseUrl(),
);
const uploadUrl = new URL("api/upload", base).toString();

const body = new FormData();
const payload = JSON.stringify(params.artifact);
body.set(
"file",
new Blob([payload], { type: "application/json" }),
params.filename,
);

const res = await fetch(uploadUrl, { method: "POST", body });

// The registry commonly returns JSON even on 409; treat duplicates as non-fatal by default.
const text = await res.text();
const parsed: unknown = text ? safeJsonParse(text) : { success: res.ok };

if (res.ok) {
return (parsed as ArtifactRegistryUploadResponse) ?? { success: true };
}

// Duplicate artifact (already uploaded) should not break deploys unless strict mode is enabled.
if (res.status === 409) {
return (
(parsed as ArtifactRegistryUploadResponse) ?? {
success: true,
message: "Artifact already exists in registry",
}
);
}

const msg =
typeof parsed === "object" && parsed
? JSON.stringify(parsed)
: text || `HTTP ${res.status} ${res.statusText}`;
throw new Error(`Artifact registry upload failed (${res.status}): ${msg}`);
}

export async function uploadArtifactFileToRegistry(params: {
artifactPath: string;
filename?: string;
registryBaseUrl?: string;
}): Promise<ArtifactRegistryUploadResponse> {
const buf = await readFile(params.artifactPath, "utf8");
const artifact = JSON.parse(buf) as unknown;
const filename =
params.filename ?? params.artifactPath.split("/").pop() ?? "artifact.json";
return await uploadArtifactToRegistry({
artifact,
filename,
registryBaseUrl: params.registryBaseUrl,
});
}

export async function maybeUploadArtifactToRegistry(params: {
artifact: unknown;
filename: string;
registryBaseUrl?: string;
}): Promise<ArtifactRegistryUploadResponse | null> {
if (!shouldUploadArtifacts()) return null;
try {
const resp = await uploadArtifactToRegistry(params);
return resp;
} catch (err) {
if (isStrictUpload()) throw err;
// Best-effort upload; do not fail deployments by default.
console.warn(
`[artifact-registry] Upload failed (continuing): ${
err instanceof Error ? err.message : String(err)
}`,
);
return null;
}
}

function safeJsonParse(text: string): unknown {
try {
return JSON.parse(text) as unknown;
} catch {
return text;
}
}
5 changes: 5 additions & 0 deletions src/ts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "../artifacts/Counter.js";
import { AztecAddress } from "@aztec/stdlib/aztec-address";
import { Contract } from "@aztec/aztec.js/contracts";
import { maybeUploadArtifactToRegistry } from "./artifactRegistry.js";

/**
* Deploys the Counter contract.
Expand All @@ -27,5 +28,9 @@ export async function deployCounter(
from: deployerAddress,
});
const contract = await tx.deployed();
await maybeUploadArtifactToRegistry({
artifact: CounterContractArtifact,
filename: "counter_contract-Counter.json",
});
Comment on lines +31 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐞 Bug - Deployment State Inconsistency: Either move the upload before deployment (if feasible), or catch upload errors and return the contract with a warning/metadata about upload failure instead of throwing. Alternatively, clearly document this behavior and consider returning both the contract and upload result in a structured response.

Suggested change
await maybeUploadArtifactToRegistry({
artifact: CounterContractArtifact,
filename: "counter_contract-Counter.json",
});
try {
await maybeUploadArtifactToRegistry({
artifact: CounterContractArtifact,
filename: "counter_contract-Counter.json",
});
} catch (uploadError) {
console.warn("Failed to upload artifact to registry:", uploadError);
}
Is this review accurate? Use πŸ‘ or πŸ‘Ž to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

return contract as CounterContract;
}
Loading