Skip to content

Commit 69348fd

Browse files
committed
final tweaks
1 parent a0fb70a commit 69348fd

15 files changed

Lines changed: 576 additions & 351 deletions

File tree

contracts/token/Nargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ authors = [""]
44
type = "contract"
55

66
[dependencies]
7-
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/aztec" }
8-
uint_note = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/uint-note" }
9-
compressed_string = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/compressed-string" }
10-
balance_set = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/balance-set" }
7+
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-nightly.20260410", directory = "noir-projects/aztec-nr/aztec" }
8+
uint_note = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-nightly.20260410", directory = "noir-projects/aztec-nr/uint-note" }
9+
compressed_string = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-nightly.20260410", directory = "noir-projects/aztec-nr/compressed-string" }
10+
balance_set = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-nightly.20260410", directory = "noir-projects/aztec-nr/balance-set" }

contracts/token/src/main.nr

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,27 @@ pub contract Token {
265265
);
266266
}
267267

268+
/// FPC-friendly variant of `transfer_offchain` that takes the sender explicitly,
269+
/// allowing the call to be sponsored (msg_sender will be the FPC, not the user).
270+
/// Authwitness ensures the user authorized this exact transfer.
271+
#[authorize_once("from", "authwit_nonce")]
272+
#[external("private")]
273+
fn transfer_offchain_from(
274+
from: AztecAddress,
275+
to: AztecAddress,
276+
amount: u128,
277+
authwit_nonce: Field,
278+
) {
279+
let change = self.internal.subtract_balance(from, amount, INITIAL_TRANSFER_CALL_MAX_NOTES);
280+
self.storage.balances.at(from).add(change).deliver(MessageDelivery.OFFCHAIN);
281+
self.storage.balances.at(to).add(amount).deliver(MessageDelivery.OFFCHAIN);
282+
283+
self.emit(Transfer { from, to, amount }).deliver_to(
284+
to,
285+
MessageDelivery.OFFCHAIN,
286+
);
287+
}
288+
268289
#[internal("private")]
269290
fn subtract_balance(account: AztecAddress, amount: u128, max_notes: u32) -> u128 {
270291
let subtracted = self.storage.balances.at(account).try_sub(amount, max_notes);

package.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,21 @@
2727
"local-aztec:status": "node scripts/toggle-local-aztec.js status"
2828
},
2929
"dependencies": {
30-
"@aztec/accounts": "4.2.0-nightly.20260409",
31-
"@aztec/aztec.js": "4.2.0-nightly.20260409",
32-
"@aztec/constants": "4.2.0-nightly.20260409",
33-
"@aztec/entrypoints": "4.2.0-nightly.20260409",
34-
"@aztec/foundation": "4.2.0-nightly.20260409",
35-
"@aztec/noir-contracts.js": "4.2.0-nightly.20260409",
36-
"@aztec/protocol-contracts": "4.2.0-nightly.20260409",
37-
"@aztec/pxe": "4.2.0-nightly.20260409",
38-
"@aztec/stdlib": "4.2.0-nightly.20260409",
39-
"@aztec/wallet-sdk": "4.2.0-nightly.20260409",
30+
"@aztec/accounts": "4.2.0-nightly.20260410",
31+
"@aztec/aztec.js": "4.2.0-nightly.20260410",
32+
"@aztec/constants": "4.2.0-nightly.20260410",
33+
"@aztec/entrypoints": "4.2.0-nightly.20260410",
34+
"@aztec/ethereum": "4.2.0-nightly.20260410",
35+
"@aztec/foundation": "4.2.0-nightly.20260410",
36+
"@aztec/noir-contracts.js": "4.2.0-nightly.20260410",
37+
"@aztec/protocol-contracts": "4.2.0-nightly.20260410",
38+
"@aztec/pxe": "4.2.0-nightly.20260410",
39+
"@aztec/stdlib": "4.2.0-nightly.20260410",
40+
"@aztec/wallet-sdk": "4.2.0-nightly.20260410",
4041
"@emotion/react": "^11.14.0",
4142
"@emotion/styled": "^11.14.0",
42-
"@gregojuice/contracts": "portal:/mnt/user-data/martin/gregojuice/packages/contracts",
43-
"@gregojuice/embedded-wallet": "portal:/mnt/user-data/martin/gregojuice/packages/embedded-wallet",
43+
"@gregojuice/contracts": "0.0.12",
44+
"@gregojuice/embedded-wallet": "0.0.12",
4445
"@mui/icons-material": "^6.3.1",
4546
"@mui/material": "^6.3.1",
4647
"@mui/styles": "^6.3.1",
@@ -52,7 +53,7 @@
5253
"zod": "^3.23.8"
5354
},
5455
"devDependencies": {
55-
"@aztec/wallets": "4.2.0-nightly.20260409",
56+
"@aztec/wallets": "4.2.0-nightly.20260410",
5657
"@eslint/js": "^9.18.0",
5758
"@playwright/test": "1.49.0",
5859
"@types/buffer-json": "^2",

scripts/deploy-subscription-fpc.ts

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,27 @@ import fs from 'fs';
88
import path from 'path';
99
import { SubscriptionFPC } from '@gregojuice/contracts/subscription-fpc';
1010
import { FunctionSelector } from '@aztec/stdlib/abi';
11+
import { L1FeeJuicePortalManager } from '@aztec/aztec.js/ethereum';
12+
import { waitForL1ToL2MessageReady } from '@aztec/aztec.js/messaging';
13+
import { createExtendedL1Client } from '@aztec/ethereum/client';
14+
import { createLogger } from '@aztec/foundation/log';
15+
import { foundry } from 'viem/chains';
16+
import { Fr } from '@aztec/foundation/curves/bn254';
1117
import { ProofOfPasswordContractArtifact } from '../contracts/target/ProofOfPassword.ts';
1218
import { AMMContractArtifact } from '../contracts/target/AMM.ts';
19+
import { TokenContractArtifact } from '../contracts/target/Token.ts';
1320
import { setupWallet, getOrCreateDeployer } from './utils.ts';
1421

22+
// Well-known Anvil account #0 — used to sign the L1 bridge transaction on local sandbox
23+
const ANVIL_KEY_0 = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
24+
1525
async function main() {
16-
const { wallet, paymentMethod } = await setupWallet('http://localhost:8080', 'local');
26+
const { wallet, node, paymentMethod } = await setupWallet('http://localhost:8080', 'local');
1727
const deployer = await getOrCreateDeployer(wallet, paymentMethod);
1828

1929
console.log('Deploying SubscriptionFPC...');
2030
const { deployment, secretKey } = await SubscriptionFPC.deployWithKeys(wallet, deployer);
21-
const receipt = await deployment.send({ fee: { paymentMethod } });
31+
const receipt = await deployment.send({ from: deployer, fee: { paymentMethod } });
2232
const fpcAddress = receipt.contract.address.toString();
2333
console.log('SubscriptionFPC deployed at:', fpcAddress);
2434
console.log('Secret key:', secretKey.toString());
@@ -30,6 +40,12 @@ async function main() {
3040
const ammFn = AMMContractArtifact.functions.find(f => f.name === 'swap_tokens_for_exact_tokens_from');
3141
const ammSelector = await FunctionSelector.fromNameAndParameters(ammFn!.name, ammFn!.parameters);
3242

43+
const transferOffchainFn = TokenContractArtifact.functions.find(f => f.name === 'transfer_offchain_from');
44+
const transferOffchainSelector = await FunctionSelector.fromNameAndParameters(
45+
transferOffchainFn!.name,
46+
transferOffchainFn!.parameters,
47+
);
48+
3349
// Update local.json
3450
const configPath = path.join(import.meta.dirname, '../src/config/networks/local.json');
3551
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
@@ -44,11 +60,81 @@ async function main() {
4460
[config.contracts.amm]: {
4561
[ammSelector.toString()]: 0,
4662
},
63+
[config.contracts.gregoCoin]: {
64+
[transferOffchainSelector.toString()]: 0,
65+
},
66+
[config.contracts.gregoCoinPremium]: {
67+
[transferOffchainSelector.toString()]: 0,
68+
},
4769
},
4870
};
4971

5072
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
5173
console.log(`\nUpdated ${configPath} with subscriptionFPC config.`);
74+
75+
// Re-register the FPC contract with its secret key so PXE can compute tagging secrets
76+
const { SubscriptionFPCContractArtifact: fpcArtifact } = await import('@gregojuice/contracts/artifacts/SubscriptionFPC');
77+
const fpcInstance = await node.getContract(receipt.contract.address);
78+
if (!fpcInstance) throw new Error('FPC contract not found on-chain after deploy');
79+
await wallet.registerContract(fpcInstance, fpcArtifact, secretKey);
80+
81+
// Start the L1 bridge early so the message can propagate while we do sign_up on L2.
82+
// On local sandbox, the fee asset handler mints a fixed amount per call (1000 FJ).
83+
// When mint=true, bridgeTokensPublic must match this exact amount.
84+
const bridgeAmount: bigint = BigInt('1000000000000000000000'); // 1000 FJ
85+
86+
console.log(`\nBridging ${bridgeAmount} wei of fee juice to FPC...`);
87+
const l1Client = createExtendedL1Client(['http://localhost:8545'], ANVIL_KEY_0, foundry);
88+
const portalManager = await L1FeeJuicePortalManager.new(node, l1Client, createLogger('bridge'));
89+
const claim = await portalManager.bridgeTokensPublic(receipt.contract.address, bridgeAmount, true);
90+
console.log('L1 bridge tx mined.');
91+
92+
// Sign up functions so users can subscribe. These L2 txs also advance the L2 chain,
93+
// which helps the sequencer include the pending L1->L2 bridge message.
94+
const { SubscriptionFPCContract } = await import('@gregojuice/contracts/artifacts/SubscriptionFPC');
95+
const fpc = SubscriptionFPCContract.at(receipt.contract.address, wallet);
96+
97+
const maxUses = 100;
98+
const maxFee = BigInt('1000000000000000000000'); // 1000 FJ
99+
const maxUsers = 100;
100+
101+
const popAddress = config.contracts.pop;
102+
console.log(`\nSigning up PoP selector ${popSelector} at index 0...`);
103+
await fpc.methods
104+
.sign_up(popAddress, popSelector, 0, maxUses, maxFee, maxUsers)
105+
.send({ from: deployer, fee: { paymentMethod } });
106+
console.log('PoP sign_up done!');
107+
108+
const ammAddress = config.contracts.amm;
109+
console.log(`Signing up AMM selector ${ammSelector} at index 0...`);
110+
await fpc.methods
111+
.sign_up(ammAddress, ammSelector, 0, maxUses, maxFee, maxUsers)
112+
.send({ from: deployer, fee: { paymentMethod } });
113+
console.log('AMM sign_up done!');
114+
115+
// Sign up transfer_offchain_from on both token contracts
116+
for (const tokenKey of ['gregoCoin', 'gregoCoinPremium'] as const) {
117+
const tokenAddress = config.contracts[tokenKey];
118+
console.log(`Signing up ${tokenKey}.transfer_offchain_from at index 0...`);
119+
await fpc.methods
120+
.sign_up(tokenAddress, transferOffchainSelector, 0, maxUses, maxFee, maxUsers)
121+
.send({ from: deployer, fee: { paymentMethod } });
122+
console.log(`${tokenKey} sign_up done!`);
123+
}
124+
125+
// Wait for the L1->L2 bridge message and claim the FJ to credit the FPC's balance.
126+
console.log('\nWaiting for L1->L2 message sync...');
127+
const messageHash = Fr.fromHexString(claim.messageHash);
128+
await waitForL1ToL2MessageReady(node, messageHash, { timeoutSeconds: 120 });
129+
console.log('Message ready');
130+
131+
const { FeeJuiceContract } = await import('@aztec/aztec.js/protocol');
132+
const feeJuice = FeeJuiceContract.at(wallet);
133+
console.log('Claiming fee juice on L2 for FPC...');
134+
await feeJuice.methods
135+
.claim(receipt.contract.address, claim.claimAmount, claim.claimSecret, claim.messageLeafIndex)
136+
.send({ from: deployer, fee: { paymentMethod } });
137+
console.log('FPC funded!');
52138
}
53139

54140
main().catch(err => {

scripts/deploy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33

4-
import { TokenContract } from '@aztec/noir-contracts.js/Token';
4+
import { TokenContract } from '../contracts/target/Token.ts';
55
import { AMMContract } from '../contracts/target/AMM.ts';
66
import { AztecAddress } from '@aztec/stdlib/aztec-address';
77
import { Fr } from '@aztec/foundation/curves/bn254';

scripts/signup-fpc.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Calls sign_up on the SubscriptionFPC for each configured function (PoP + AMM).
3+
* Uses the same deployer wallet as deploy-subscription-fpc.ts.
4+
*
5+
* Usage: node --experimental-transform-types scripts/signup-fpc.ts
6+
*/
7+
8+
import fs from 'fs';
9+
import path from 'path';
10+
import { AztecAddress } from '@aztec/stdlib/aztec-address';
11+
import { FunctionSelector } from '@aztec/stdlib/abi';
12+
import { SubscriptionFPCContract } from '@gregojuice/contracts/artifacts/SubscriptionFPC';
13+
import { ProofOfPasswordContractArtifact } from '../contracts/target/ProofOfPassword.ts';
14+
import { AMMContractArtifact } from '../contracts/target/AMM.ts';
15+
import { setupWallet, getOrCreateDeployer } from './utils.ts';
16+
17+
async function main() {
18+
const configPath = path.join(import.meta.dirname, '../src/config/networks/local.json');
19+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
20+
21+
const { wallet, node, paymentMethod } = await setupWallet('http://localhost:8080', 'local');
22+
const adminAddress = await getOrCreateDeployer(wallet, paymentMethod);
23+
24+
const fpcAddress = AztecAddress.fromString(config.subscriptionFPC.address);
25+
26+
// Register the FPC contract in this PXE so it can interact with it
27+
const { SubscriptionFPCContractArtifact } = await import('@gregojuice/contracts/artifacts/SubscriptionFPC');
28+
const fpcInstance = await node.getContract(fpcAddress);
29+
if (!fpcInstance) throw new Error('FPC contract not found on-chain');
30+
await wallet.registerContract(fpcInstance, SubscriptionFPCContractArtifact);
31+
32+
const fpc = SubscriptionFPCContract.at(fpcAddress, wallet);
33+
console.log('Admin:', adminAddress.toString());
34+
console.log('FPC:', fpcAddress.toString());
35+
36+
// sign_up params: generous for local dev
37+
const maxUses = 100;
38+
const maxFee = BigInt('1000000000000000000000'); // 1000 FJ in wei
39+
const maxUsers = 100;
40+
41+
// 1. Sign up PoP.check_password_and_mint
42+
const popFn = ProofOfPasswordContractArtifact.functions.find(f => f.name === 'check_password_and_mint');
43+
const popSelector = await FunctionSelector.fromNameAndParameters(popFn!.name, popFn!.parameters);
44+
const popAddress = AztecAddress.fromString(config.contracts.pop);
45+
const popConfigIndex = config.subscriptionFPC.functions[config.contracts.pop][popSelector.toString()];
46+
47+
console.log(`\nSigning up PoP (${popAddress}) selector ${popSelector} at index ${popConfigIndex}...`);
48+
await fpc.methods
49+
.sign_up(popAddress, popSelector, popConfigIndex, maxUses, maxFee, maxUsers)
50+
.send({ from: adminAddress, fee: { paymentMethod } });
51+
console.log('PoP sign_up done!');
52+
53+
// 2. Sign up AMM.swap_tokens_for_exact_tokens_from
54+
const ammFn = AMMContractArtifact.functions.find(f => f.name === 'swap_tokens_for_exact_tokens_from');
55+
const ammSelector = await FunctionSelector.fromNameAndParameters(ammFn!.name, ammFn!.parameters);
56+
const ammAddress = AztecAddress.fromString(config.contracts.amm);
57+
const ammConfigIndex = config.subscriptionFPC.functions[config.contracts.amm][ammSelector.toString()];
58+
59+
console.log(`\nSigning up AMM (${ammAddress}) selector ${ammSelector} at index ${ammConfigIndex}...`);
60+
await fpc.methods
61+
.sign_up(ammAddress, ammSelector, ammConfigIndex, maxUses, maxFee, maxUsers)
62+
.send({ from: adminAddress, fee: { paymentMethod } });
63+
console.log('AMM sign_up done!');
64+
65+
console.log('\nAll functions signed up successfully!');
66+
}
67+
68+
main().catch(err => {
69+
console.error(err);
70+
process.exit(1);
71+
});

src/App.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react';
2-
import { ThemeProvider, CssBaseline, Container, Box, Typography, Tabs, Tab } from '@mui/material';
2+
import { ThemeProvider, CssBaseline, Container, Box, Typography, Tabs, Tab, Snackbar } from '@mui/material';
33
import { theme } from './theme';
44
import { GregoSwapLogo } from './components/GregoSwapLogo';
55
import { WalletChip } from './components/WalletChip';
@@ -17,17 +17,20 @@ import type { AztecAddress } from '@aztec/aztec.js/addresses';
1717

1818
export function App() {
1919
const [activeTab, setActiveTab] = useState(0);
20+
const [addressCopied, setAddressCopied] = useState(false);
2021
const { disconnectWallet, setCurrentAddress, currentAddress, error: walletError, isLoading: walletLoading } = useWallet();
2122
const { isOnboardingModalOpen, startOnboarding, resetOnboarding, status: onboardingStatus } = useOnboarding();
2223

2324
const isOnboarded = onboardingStatus === 'completed';
2425

25-
const handleWalletClick = () => {
26-
// If already onboarded, start a new onboarding flow to change wallet
26+
const handleWalletClick = async () => {
27+
// If connected, copy the address. Otherwise start onboarding.
2728
if (isOnboarded && currentAddress) {
28-
resetOnboarding();
29+
await navigator.clipboard.writeText(currentAddress.toString());
30+
setAddressCopied(true);
31+
return;
2932
}
30-
startOnboarding(); // Start onboarding when clicked from wallet chip
33+
startOnboarding();
3134
};
3235

3336
const handleDisconnect = async () => {
@@ -106,6 +109,12 @@ export function App() {
106109
onClick={handleWalletClick}
107110
onDisconnect={handleDisconnect}
108111
/>
112+
<Snackbar
113+
open={addressCopied}
114+
autoHideDuration={2000}
115+
onClose={() => setAddressCopied(false)}
116+
message="Address copied!"
117+
/>
109118

110119
<Container maxWidth="sm" sx={{ position: 'relative', zIndex: 1 }}>
111120
{/* Header */}

src/components/claim/ClaimPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function ClaimPage() {
6363
await claimOffchainTransfer(tokenKey, {
6464
ciphertext: data.payload.map((s: string) => Fr.fromString(s)),
6565
recipient: AztecAddress.fromString(data.recipient),
66-
tx_hash: data.txHash,
66+
tx_hash: Fr.fromString(data.txHash),
6767
anchor_block_timestamp: BigInt(data.anchorBlockTimestamp),
6868
});
6969

src/components/send/SendContainer.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,22 @@ import { useEffect, useState } from 'react';
1010

1111
export function SendContainer() {
1212
const { phase, error, generatedLink, token, amount, recipientAddress, dismissError, reset } = useSend();
13-
const { currentAddress, isUsingEmbeddedWallet } = useWallet();
13+
const { currentAddress } = useWallet();
1414
const { fetchBalances } = useContracts();
1515
const [balances, setBalances] = useState<{ gc: bigint | null; gcp: bigint | null }>({ gc: null, gcp: null });
1616

1717
useEffect(() => {
18-
if (currentAddress && !isUsingEmbeddedWallet) {
18+
if (currentAddress) {
1919
fetchBalances().then(([gc, gcp]) => setBalances({ gc, gcp }));
2020
}
21-
}, [currentAddress, isUsingEmbeddedWallet, fetchBalances]);
21+
}, [currentAddress, fetchBalances]);
2222

2323
useEffect(() => {
2424
if (phase === 'link_ready' && currentAddress) {
2525
fetchBalances().then(([gc, gcp]) => setBalances({ gc, gcp }));
2626
}
2727
}, [phase, currentAddress, fetchBalances]);
2828

29-
if (isUsingEmbeddedWallet) {
30-
return (
31-
<Box sx={{ p: 3, textAlign: 'center' }}>
32-
<Alert severity="info">Connect an external wallet to send tokens.</Alert>
33-
</Box>
34-
);
35-
}
36-
3729
return (
3830
<Box>
3931
{phase === 'link_ready' && generatedLink ? (

0 commit comments

Comments
 (0)