Skip to content
Merged
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
39 changes: 30 additions & 9 deletions .github/workflows/compile-cairo-alpha-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
declare -a all_access_options=("disabled" "ownable" "roles" "roles-dar-default" "roles-dar-custom")
declare -a all_upgradeable_options=("true" "false")
declare -a all_royalty_options=("disabled" "enabled-default" "enabled-custom")
declare -a all_flashmint_options=("disabled" "enabled-default" "enabled-percent-fee" "enabled-custom-fee")

kind="$KIND"

Expand All @@ -78,18 +79,38 @@ jobs:
done
done

elif [[ "$kind" == "ERC20" || "$kind" == "ERC6909" || "$kind" == "Custom" ]]; then
elif [[ "$kind" == "ERC20" ]]; then
for access_option in "${all_access_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option
for upgradeable_option in "${all_upgradeable_options[@]}"; do
for flashmint_option in "${all_flashmint_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option, upgradeable: $upgradeable_option, flashmint: $flashmint_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option --upgradeable=$upgradeable_option --flashmint=$flashmint_option

echo "Compiling $proj_name..."
scarb clean
scarb build

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
done
done
done

echo "Compiling $proj_name..."
scarb clean
scarb build
elif [[ "$kind" == "ERC6909" || "$kind" == "Custom" ]]; then
for access_option in "${all_access_options[@]}"; do
for upgradeable_option in "${all_upgradeable_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option, upgradeable: $upgradeable_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option --upgradeable=$upgradeable_option

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
echo "Compiling $proj_name..."
scarb clean
scarb build

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
done
done

else
Expand Down
39 changes: 30 additions & 9 deletions .github/workflows/compile-cairo-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
declare -a all_access_options=("disabled" "ownable" "roles" "roles-dar-default" "roles-dar-custom")
declare -a all_upgradeable_options=("true" "false")
declare -a all_royalty_options=("disabled" "enabled-default" "enabled-custom")
declare -a all_flashmint_options=("disabled" "enabled-default" "enabled-percent-fee" "enabled-custom-fee")

kind="$KIND"

Expand All @@ -78,18 +79,38 @@ jobs:
done
done

elif [[ "$kind" == "ERC20" || "$kind" == "ERC6909" || "$kind" == "Custom" ]]; then
elif [[ "$kind" == "ERC20" ]]; then
for access_option in "${all_access_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option
for upgradeable_option in "${all_upgradeable_options[@]}"; do
for flashmint_option in "${all_flashmint_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option, upgradeable: $upgradeable_option, flashmint: $flashmint_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option --upgradeable=$upgradeable_option --flashmint=$flashmint_option

echo "Compiling $proj_name..."
scarb clean
scarb build

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
done
done
done

echo "Compiling $proj_name..."
scarb clean
scarb build
elif [[ "$kind" == "ERC6909" || "$kind" == "Custom" ]]; then
for access_option in "${all_access_options[@]}"; do
for upgradeable_option in "${all_upgradeable_options[@]}"; do
proj_name="'$kind, macros: $macros_option, access: $access_option, upgradeable: $upgradeable_option' test project"
echo "Generating $proj_name..."
yarn run update_scarb_project --kind=$kind --macros=$macros_option --access=$access_option --upgradeable=$upgradeable_option

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
echo "Compiling $proj_name..."
scarb clean
scarb build

echo "✅ Compiled $proj_name!"
echo "---------------------------------"
done
done

else
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Changelog


## 0.2.1 (2026-09-08)

- Cairo: Add ERC20FlashMint extension for the ERC20 token kind. ([#870](https://github.com/OpenZeppelin/contracts-wizard/pull/870))
- Configurable max flash loan (default or custom cap), flash fee (percent of the loan amount or custom stub), and fee destination (burn or fee receiver).
- Add `flashmint` options to the `cairo-erc20` MCP tool and CLI command.
- Updated dependencies [[`5ca9bbd`](https://github.com/OpenZeppelin/contracts-wizard/commit/5ca9bbd13725dc7baf74ca8d8ca8e1bdf5e0a94f), [`df1fa2f`](https://github.com/OpenZeppelin/contracts-wizard/commit/df1fa2ffb82bc7ee3652b165e7ef045b17b3664a)]:
- @openzeppelin/wizard-cairo@4.1.0
- @openzeppelin/wizard-common@0.5.9
- @openzeppelin/wizard-stellar@0.6.5
- @openzeppelin/wizard@0.10.16

## 0.2.0 (2026-09-01)

- Cairo: support OpenZeppelin Contracts for Cairo v4.0.1. ([#861](https://github.com/OpenZeppelin/contracts-wizard/pull/861))
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/contracts-cli",
"version": "0.2.0",
"version": "0.2.1",
"description": "CLI for generating smart contracts using OpenZeppelin Contracts Wizard",
"license": "AGPL-3.0-only",
"repository": "https://github.com/OpenZeppelin/contracts-wizard",
Expand All @@ -27,10 +27,10 @@
},
"dependencies": {
"zod": "^4.0",
"@openzeppelin/wizard-common": "^0.5.8",
"@openzeppelin/wizard": "^0.10.14",
"@openzeppelin/wizard-cairo": "^4.0.0",
"@openzeppelin/wizard-stellar": "^0.6.4",
"@openzeppelin/wizard-common": "^0.5.9",
"@openzeppelin/wizard": "^0.10.16",
"@openzeppelin/wizard-cairo": "^4.1.0",
"@openzeppelin/wizard-stellar": "^0.6.5",
"@openzeppelin/wizard-stylus": "^0.3.0",
"@openzeppelin/wizard-confidential": "^0.1.2",
"@openzeppelin/wizard-uniswap-hooks": "^0.1.1"
Expand Down
42 changes: 42 additions & 0 deletions packages/cli/src/cli-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
erc721 as cairoErc721,
erc1155 as cairoErc1155,
erc6909 as cairoErc6909,
flashMintDefaults as cairoFlashMintDefaults,
account as cairoAccount,
multisig as cairoMultisig,
governor as cairoGovernor,
Expand Down Expand Up @@ -401,6 +402,47 @@ test('cairo-erc20: access ownable without dar delays', t => {
t.is(output, cairoErc20.print(opts));
});

test('cairo-erc20: flash mint with defaults', t => {
const opts = {
name: 'TestToken',
symbol: 'TST',
flashmint: { ...cairoFlashMintDefaults, enabled: true },
};
const output = run('cairo-erc20', '--name', opts.name, '--symbol', opts.symbol, '--flashmint.enabled');
t.is(output, cairoErc20.print(opts));
});

test('cairo-erc20: flash mint with custom config', t => {
const opts = {
name: 'TestToken',
symbol: 'TST',
flashmint: {
enabled: true,
maxAmount: '1000000',
feeMode: 'percent' as const,
feePercent: '0.5',
feeDestination: 'fee_receiver' as const,
},
};
const output = run(
'cairo-erc20',
'--name',
opts.name,
'--symbol',
opts.symbol,
'--flashmint.enabled',
'--flashmint.maxAmount',
opts.flashmint.maxAmount,
'--flashmint.feeMode',
opts.flashmint.feeMode,
'--flashmint.feePercent',
opts.flashmint.feePercent,
'--flashmint.feeDestination',
opts.flashmint.feeDestination,
);
t.is(output, cairoErc20.print(opts));
});

test('cairo-erc721: most options', t => {
const opts = {
name: 'TestNFT',
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/cli.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ Generated by [AVA](https://avajs.dev).
--votes Whether to keep track of historical balances for voting in on-chain governance, with a way to delegate one's voting power to a trusted account.␊
--appName <string> Required when votes is enabled, for hashing and signing typed structured data. Name for domain separator implementing SNIP12Metadata trait. Prevents two applications from producing the same hash.␊
--appVersion <string> Required when votes is enabled, for hashing and signing typed structured data. Version for domain separator implementing SNIP12Metadata trait. Prevents two versions of the same application from producing the same hash.␊
--flashmint.enabled Whether to include ERC20FlashMint functionality, allowing flash loans of tokens compliant with ERC-3156.␊
--flashmint.maxAmount <string> Maximum amount of tokens that can be flash-loaned in a single call. Use the literal string "max" to inherit the default (the maximum representable u256 minus the current total supply), or a non-negative number in the token's decimal units to set a custom cap. A value of 0 effectively disables flash loans without removing the extension.␊
--flashmint.feeMode <percent|custom> Mode for the flash loan fee. 'percent' charges a percentage of the loaned amount (value provided via feePercent). 'custom' emits a TODO stub for the caller to implement.␊
--flashmint.feePercent <string> Percentage of the loan amount charged as the flash loan fee. Number between 0 and 100, fractional values supported (e.g. "0.0013725"). Used when feeMode is 'percent'. Defaults to 0 (no fee).␊
--flashmint.feeDestination <burn|fee_receiver> Where the flash loan fee is sent. 'burn' sends it to the zero address (effectively burning it). 'fee_receiver' adds a constructor argument that the deployer must populate with a non-zero address; the address is stored on-chain and validated at deploy time.␊
--access.type <false|ownable|roles|roles-dar> The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Roles (Default Admin Rules) provides additional enforced security measures on top of standard Roles mechanism for managing the most privileged role: default admin.␊
--access.darInitialDelay <string> The initial delay for the default admin role (in case Roles (Default Admin Rules) is used). Default is "1 day".␊
--access.darDefaultDelayIncrease <string> The default delay increase for the default admin role (in case Roles (Default Admin Rules) is used). Default is "5 days".␊
Expand Down
Binary file modified packages/cli/src/cli.test.ts.snap
Binary file not shown.
6 changes: 6 additions & 0 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog


## 0.5.9 (2026-09-08)

- Cairo: Add ERC20FlashMint extension for the ERC20 token kind. ([#870](https://github.com/OpenZeppelin/contracts-wizard/pull/870))
- Configurable max flash loan (default or custom cap), flash fee (percent of the loan amount or custom stub), and fee destination (burn or fee receiver).
- Add `flashmint` options to the `cairo-erc20` MCP tool and CLI command.

## 0.5.8 (2026-09-01)

- Cairo: support OpenZeppelin Contracts for Cairo v4.0.1. ([#861](https://github.com/OpenZeppelin/contracts-wizard/pull/861))
Expand Down
8 changes: 4 additions & 4 deletions packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/wizard-common",
"version": "0.5.8",
"version": "0.5.9",
"description": "Common library for OpenZeppelin Contracts Wizard components. Used internally.",
"license": "AGPL-3.0-only",
"repository": "https://github.com/OpenZeppelin/contracts-wizard",
Expand Down Expand Up @@ -34,9 +34,9 @@
"zod": "^4.0"
},
"devDependencies": {
"@openzeppelin/wizard": "^0.10.14",
"@openzeppelin/wizard-cairo": "^4.0.0",
"@openzeppelin/wizard-stellar": "^0.6.4",
"@openzeppelin/wizard": "^0.10.16",
"@openzeppelin/wizard-cairo": "^4.1.0",
"@openzeppelin/wizard-stellar": "^0.6.5",
"@openzeppelin/wizard-stylus": "^0.3.0",
"@openzeppelin/wizard-confidential": "^0.1.2",
"@openzeppelin/wizard-uniswap-hooks": "^0.1.1",
Expand Down
12 changes: 12 additions & 0 deletions packages/common/src/ai/descriptions/cairo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export const cairoERC20Descriptions = {
wrapper: 'Whether to include ERC20Wrapper functionality for depositing and withdrawing an underlying token.',
votes:
"Whether to keep track of historical balances for voting in on-chain governance, with a way to delegate one's voting power to a trusted account.",
flashmint:
'Configuration object for the ERC20FlashMint extension (ERC-3156 flash loans). The extension is included only when `enabled` is true; the other fields tune the loan limit, fee, and fee destination.',
flashMintEnabled:
'Whether to include ERC20FlashMint functionality, allowing flash loans of tokens compliant with ERC-3156.',
flashMintMaxAmount:
'Maximum amount of tokens that can be flash-loaned in a single call. Use the literal string "max" to inherit the default (the maximum representable u256 minus the current total supply), or a non-negative number in the token\'s decimal units to set a custom cap. A value of 0 effectively disables flash loans without removing the extension.',
flashMintFeeMode:
"Mode for the flash loan fee. 'percent' charges a percentage of the loaned amount (value provided via feePercent). 'custom' emits a TODO stub for the caller to implement.",
flashMintFeePercent:
'Percentage of the loan amount charged as the flash loan fee. Number between 0 and 100, fractional values supported (e.g. "0.0013725"). Used when feeMode is \'percent\'. Defaults to 0 (no fee).',
flashMintFeeDestination:
"Where the flash loan fee is sent. 'burn' sends it to the zero address (effectively burning it). 'fee_receiver' adds a constructor argument that the deployer must populate with a non-zero address; the address is stored on-chain and validated at deploy time.",
};

export const cairoERC721Descriptions = {
Expand Down
13 changes: 13 additions & 0 deletions packages/common/src/ai/schemas/cairo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ export const cairoERC20Schema = {
votes: z.boolean().optional().describe(cairoERC20Descriptions.votes),
appName: z.string().optional().describe(cairoCommonDescriptions.appName),
appVersion: z.string().optional().describe(cairoCommonDescriptions.appVersion),
flashmint: z
.object({
enabled: z.boolean().describe(cairoERC20Descriptions.flashMintEnabled),
maxAmount: z.string().default('max').describe(cairoERC20Descriptions.flashMintMaxAmount),
feeMode: z.enum(['percent', 'custom']).default('percent').describe(cairoERC20Descriptions.flashMintFeeMode),
feePercent: z.string().default('0').describe(cairoERC20Descriptions.flashMintFeePercent),
feeDestination: z
.enum(['burn', 'fee_receiver'])
.default('burn')
.describe(cairoERC20Descriptions.flashMintFeeDestination),
})
.optional()
.describe(cairoERC20Descriptions.flashmint),
...cairoCommonSchema,
} as const satisfies z.ZodRawShape;

Expand Down
8 changes: 8 additions & 0 deletions packages/core/cairo/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Changelog


## 4.1.0 (2026-09-08)

- Cairo: Add ERC20FlashMint extension for the ERC20 token kind. ([#870](https://github.com/OpenZeppelin/contracts-wizard/pull/870))
- Configurable max flash loan (default or custom cap), flash fee (percent of the loan amount or custom stub), and fee destination (burn or fee receiver).
- Add `flashmint` options to the `cairo-erc20` MCP tool and CLI command.

- Improve performance of premint amount parsing. ([#872](https://github.com/OpenZeppelin/contracts-wizard/pull/872))

## 4.0.0 (2026-09-01)

- Cairo: support OpenZeppelin Contracts for Cairo v4.0.1. ([#861](https://github.com/OpenZeppelin/contracts-wizard/pull/861))
Expand Down
2 changes: 1 addition & 1 deletion packages/core/cairo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/wizard-cairo",
"version": "4.0.0",
"version": "4.1.0",
"description": "A boilerplate generator to get started with OpenZeppelin Contracts for Cairo",
"license": "AGPL-3.0-only",
"repository": "https://github.com/OpenZeppelin/contracts-wizard",
Expand Down
25 changes: 25 additions & 0 deletions packages/core/cairo/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export interface Contract {
upgradeable: boolean;
implementedTraits: ImplementedTrait[];
superVariables: Variable[];
storageMembers: StorageMember[];
}

export interface StorageMember {
name: string;
type: string;
}

export type Value = string | number | bigint | { lit: string } | { note: string; value: Value };
Expand Down Expand Up @@ -118,6 +124,7 @@ export class ContractBuilder implements Contract {
private constantsMap: Map<string, Variable> = new Map();
private useClausesMap: Map<string, UseClause> = new Map();
private interfaceFlagsSet: Set<string> = new Set();
private storageMembersMap: Map<string, StorageMember> = new Map();

constructor(name: string, macros: MacrosOptions, account: boolean = false) {
this.name = toIdentifier(name, true);
Expand Down Expand Up @@ -145,6 +152,24 @@ export class ContractBuilder implements Contract {
return [...this.useClausesMap.values()];
}

get storageMembers(): StorageMember[] {
return [...this.storageMembersMap.values()];
}

addStorageMember(member: StorageMember): boolean {
const existing = this.storageMembersMap.get(member.name);
if (existing !== undefined) {
if (existing.type !== member.type) {
throw new Error(
`Tried to add duplicate storage member ${member.name} with different type: ${member.type} instead of ${existing.type}.`,
);
}
return false;
}
this.storageMembersMap.set(member.name, member);
return true;
}

/**
* Custom flags to denote that the contract implements a specific interface, e.g. ISRC5, to avoid duplicates
**/
Expand Down
Loading
Loading