Skip to content
Open
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
13 changes: 13 additions & 0 deletions config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface TimeoutConfig {
waitTimeout: number;
}

export interface AztecScanConfig {
apiUrl: string;
apiKey: string;
}

export interface EnvironmentConfig {
name: string;
environment: 'local' | 'testnet' | 'devnet' | 'mainnet';
Expand All @@ -26,6 +31,7 @@ export interface EnvironmentConfig {
version: string;
};
timeouts?: TimeoutConfig;
aztecscan?: AztecScanConfig;
}

export class ConfigManager {
Expand Down Expand Up @@ -138,5 +144,12 @@ export function getTimeouts(): TimeoutConfig {
return configManager.getTimeouts();
}

/**
* Gets AztecScan configuration, or undefined if not configured for this environment.
*/
export function getAztecScanConfig(): AztecScanConfig | undefined {
return configManager.getConfig().aztecscan;
}

// Export the singleton instance for direct access if needed
export default configManager;
4 changes: 4 additions & 0 deletions config/devnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
"deployTimeout": 1200000,
"txTimeout": 180000,
"waitTimeout": 60000
},
"aztecscan": {
"apiUrl": "https://api.devnet.aztecscan.xyz",
"apiKey": "temporary-api-key"
Copy link
Collaborator

Choose a reason for hiding this comment

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

im assuming this key is retrieved from your site?

Copy link
Author

Choose a reason for hiding this comment

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

Well, yes, but we don't have signup yet. So this will work for all users for now.

}
}
71 changes: 66 additions & 5 deletions scripts/deploy_contract.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { readFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { PodRacingContract } from "../src/artifacts/PodRacing.js"
import { type Logger, createLogger } from "@aztec/foundation/log";
import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee";
import { setupWallet } from "../src/utils/setup_wallet.js";
import { getSponsoredFPCInstance } from "../src/utils/sponsored_fpc.js";
import { SponsoredFPCContractArtifact } from "@aztec/noir-contracts.js/SponsoredFPC";
import { deploySchnorrAccount } from "../src/utils/deploy_account.js";
import { getTimeouts } from "../config/config.js";
import { getTimeouts, getAztecScanConfig } from "../config/config.js";
import { verifyArtifactOnAztecScan, verifyInstanceOnAztecScan } from "../src/utils/verify_on_aztecscan.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

async function main() {
let logger: Logger;
Expand Down Expand Up @@ -51,10 +57,6 @@ async function main() {
logger.info(`📍 Contract address: ${podRacingContract.address}`);
logger.info(`👤 Admin address: ${address}`);

// Verify deployment
logger.info('🔍 Verifying contract deployment...');
logger.info('✅ Contract deployed and ready for game creation');

// Log contract instantiation data
if (instance) {
logger.info('📦 Contract instantiation data:');
Expand All @@ -68,6 +70,65 @@ async function main() {
}
logger.info(`Constructor args: ${JSON.stringify([address.toString()])}`);
}

// Verify on AztecScan (best-effort — deploy succeeds regardless)
const aztecscanConfig = getAztecScanConfig();
if (aztecscanConfig && instance) {
logger.info('🔍 Verifying contract on AztecScan...');

const contractClassId = instance.currentContractClassId.toString();
const publicKeysString = instance.publicKeys
? instance.publicKeys.toString()
: "0x" + "0".repeat(512);
const constructorArgs = [address.toString()];

// Load the raw JSON artifact (Noir compiler output) — NOT the codegen'd
// ContractArtifact. The server expects the original JSON with base64 bytecode
// strings, snake_case keys, etc. The codegen'd artifact has Buffer objects,
// camelCase keys, and transformed function structure that the server can't parse.
const rawArtifactPath = join(__dirname, "../target/pod_racing_contract-PodRacing.json");
const rawArtifact = JSON.parse(readFileSync(rawArtifactPath, "utf8"));

try {
// Wait for the AztecScan indexer to pick up the on-chain data
logger.info('⏳ Waiting 15s for AztecScan indexer...');
await new Promise(resolve => setTimeout(resolve, 15_000));

// 1. Verify artifact (contract class)
const artifactResult = await verifyArtifactOnAztecScan(
aztecscanConfig,
contractClassId,
1,
rawArtifact,
);
logger.info(`📋 Artifact verification: ${artifactResult.status} ${artifactResult.statusText}`);

// 2. Verify instance (deployment)
const instanceResult = await verifyInstanceOnAztecScan(
aztecscanConfig,
podRacingContract.address.toString(),
{
publicKeysString,
deployer: instance.deployer.toString(),
salt: instance.salt.toString(),
constructorArgs,
},
rawArtifact,
);
logger.info(`📋 Instance verification: ${instanceResult.status} ${instanceResult.statusText}`);

if (artifactResult.ok && instanceResult.ok) {
logger.info('✅ Contract verified on AztecScan');
} else {
logger.warn('⚠️ AztecScan verification returned non-OK status (contract still deployed successfully)');
}
} catch (err) {
logger.warn(`⚠️ AztecScan verification failed (contract still deployed successfully): ${err}`);
}
} else if (!aztecscanConfig) {
logger.info('ℹ️ AztecScan not configured for this environment, skipping verification');
}

logger.info('🏁 Deployment process completed successfully!');
logger.info(`📋 Summary:`);
logger.info(` - Contract Address: ${podRacingContract.address}`);
Expand Down
4 changes: 3 additions & 1 deletion src/utils/deploy_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { setupWallet } from "./setup_wallet.js";
import { AztecAddress } from "@aztec/aztec.js/addresses";
import { AccountManager } from "@aztec/aztec.js/wallet";
import { EmbeddedWallet } from "@aztec/wallets/embedded";
import { getTimeouts } from "../../config/config.js";

export async function deploySchnorrAccount(wallet?: EmbeddedWallet): Promise<AccountManager> {
let logger: Logger;
Expand Down Expand Up @@ -41,7 +42,8 @@ export async function deploySchnorrAccount(wallet?: EmbeddedWallet): Promise<Acc
logger.info('✅ Sponsored fee payment method configured for account deployment');

// Deploy account
await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: 120000 } });
const timeouts = getTimeouts();
await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: timeouts.deployTimeout } });

logger.info(`✅ Account deployment transaction successful!`);

Expand Down
9 changes: 8 additions & 1 deletion src/utils/setup_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { createAztecNodeClient } from '@aztec/aztec.js/node';
import { getAztecNodeUrl } from '../../config/config.js';
import { EmbeddedWallet } from '@aztec/wallets/embedded';
import configManager from '../../config/config.js';

export async function setupWallet(): Promise<EmbeddedWallet> {
const nodeUrl = getAztecNodeUrl();
const node = createAztecNodeClient(nodeUrl);
const wallet = await EmbeddedWallet.create(node, { ephemeral: true });
// Real proofs required on devnet/testnet (fake proofs are rejected).
// Disabled on local network for faster iteration.
const proverEnabled = !configManager.isLocalNetwork();
const wallet = await EmbeddedWallet.create(node, {
ephemeral: true,
pxeConfig: { proverEnabled },
});
return wallet;
}
134 changes: 134 additions & 0 deletions src/utils/verify_on_aztecscan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Verify contracts on AztecScan after deployment.
*
* Two-step verification:
* 1. Artifact verification — uploads the contract artifact JSON so AztecScan can
* match it byte-for-byte against the on-chain bytecode.
* 2. Instance verification — sends the deployment parameters (salt, deployer,
* publicKeysString, constructorArgs) so AztecScan can recompute and confirm
* the contract address.
*
* Uses raw fetch — no SDK dependency required.
*/

import type { AztecScanConfig } from "../../config/config.js";

// ── Types ───────────────────────────────────────────────────────────

export interface VerificationResult {
ok: boolean;
status: number;
statusText: string;
}

export interface VerifyInstanceArgs {
publicKeysString: string;
deployer: string;
salt: string;
constructorArgs: string[];
}

// ── Artifact verification ───────────────────────────────────────────

/**
* Verify a contract artifact (contract class) on AztecScan.
*
* POST /v1/{apiKey}/l2/contract-classes/{classId}/versions/{version}
* Body: { stringifiedArtifactJson: string }
*
* Returns 200 if already verified, 201 if newly verified.
*/
export async function verifyArtifactOnAztecScan(
config: AztecScanConfig,
contractClassId: string,
version: number,
artifact: Record<string, unknown>,
): Promise<VerificationResult> {
const url = `${config.apiUrl}/v1/${config.apiKey}/l2/contract-classes/${contractClassId}/versions/${version}`;

// Handle { default: artifact } module-style exports
const raw = "default" in artifact && typeof artifact.default === "object"
? artifact.default
: artifact;

const body = JSON.stringify({ stringifiedArtifactJson: JSON.stringify(raw) });
const sizeMB = (new TextEncoder().encode(body).length / 1_000_000).toFixed(2);

console.log(`[aztecscan] Verifying artifact -> POST ${url} (${sizeMB} MB)`);

const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});

console.log(`[aztecscan] Artifact verification: ${response.status} ${response.statusText}`);
if (!response.ok) {
const text = await response.text().catch(() => "(no body)");
console.log(`[aztecscan] Artifact verification response: ${text.slice(0, 500)}`);
}
return { ok: response.ok, status: response.status, statusText: response.statusText };
}

// ── Instance verification ───────────────────────────────────────────

/**
* Verify a contract instance deployment on AztecScan.
*
* POST /v1/{apiKey}/l2/contract-instances/{address}
* Body: { verifiedDeploymentArguments: { salt, deployer, publicKeysString, constructorArgs, stringifiedArtifactJson? } }
*
* The server recomputes the contract address from the provided parameters.
*/
export async function verifyInstanceOnAztecScan(
config: AztecScanConfig,
contractAddress: string,
args: VerifyInstanceArgs,
artifact?: Record<string, unknown>,
): Promise<VerificationResult> {
// Validate field lengths (matching server-side Zod schema)
if (args.publicKeysString.length !== 514) {
throw new Error(`Invalid publicKeysString length: expected 514, got ${args.publicKeysString.length}`);
}
if (args.deployer.length !== 66) {
throw new Error(`Invalid deployer length: expected 66, got ${args.deployer.length}`);
}
if (args.salt.length !== 66) {
throw new Error(`Invalid salt length: expected 66, got ${args.salt.length}`);
}

const url = `${config.apiUrl}/v1/${config.apiKey}/l2/contract-instances/${contractAddress}`;

const verifiedDeploymentArguments: Record<string, unknown> = {
salt: args.salt,
deployer: args.deployer,
publicKeysString: args.publicKeysString,
constructorArgs: args.constructorArgs,
};

// Optionally include the artifact for combined verification
if (artifact) {
const raw = "default" in artifact && typeof artifact.default === "object"
? artifact.default
: artifact;
verifiedDeploymentArguments.stringifiedArtifactJson = JSON.stringify(raw);
}

const body = JSON.stringify({ verifiedDeploymentArguments });
const sizeMB = (new TextEncoder().encode(body).length / 1_000_000).toFixed(2);

console.log(`[aztecscan] Verifying instance -> POST ${url} (${sizeMB} MB)`);

const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});

console.log(`[aztecscan] Instance verification: ${response.status} ${response.statusText}`);
if (!response.ok) {
const text = await response.text().catch(() => "(no body)");
console.log(`[aztecscan] Instance verification response: ${text.slice(0, 500)}`);
}
return { ok: response.ok, status: response.status, statusText: response.statusText };
}