From 892a7afdebab3646ef692db6801306a85b2e538a Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 21 Oct 2025 17:12:16 +0200 Subject: [PATCH 1/5] Add transfer_expiry example --- transfer_expiry/.gitignore | 5 ++++ transfer_expiry/program.json | 16 ++++++++++ transfer_expiry/src/main.leo | 29 +++++++++++++++++++ .../tests/test_transfer_expiry.leo | 15 ++++++++++ 4 files changed, 65 insertions(+) create mode 100644 transfer_expiry/.gitignore create mode 100644 transfer_expiry/program.json create mode 100644 transfer_expiry/src/main.leo create mode 100644 transfer_expiry/tests/test_transfer_expiry.leo diff --git a/transfer_expiry/.gitignore b/transfer_expiry/.gitignore new file mode 100644 index 0000000..f721f7f --- /dev/null +++ b/transfer_expiry/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/transfer_expiry/program.json b/transfer_expiry/program.json new file mode 100644 index 0000000..0cfb948 --- /dev/null +++ b/transfer_expiry/program.json @@ -0,0 +1,16 @@ +{ + "program": "transfer_public_expiry.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.2.0", + "dependencies": [ + { + "name": "credits.aleo", + "location": "network", + "path": null, + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/transfer_expiry/src/main.leo b/transfer_expiry/src/main.leo new file mode 100644 index 0000000..975f314 --- /dev/null +++ b/transfer_expiry/src/main.leo @@ -0,0 +1,29 @@ +// The 'transfer_public_expiry' program. +import credits.aleo; + +program transfer_public_expiry.aleo { + + // Default constructor which doesn't allow for program upgrades. + @noupgrade + async constructor() {} + + // Wrapper around credits.aleo::transfer_public with an expiry. + async transition transfer_public_with_expiry( + public to: address, + public amount: u64, + public expiry_block_height: u32 + ) -> Future { + // Call the original transfer_public function from credits.aleo + let f1 = credits.aleo/transfer_public(to, amount); + + let f2: Future = async { + // Check if current block height is below the expiry block height + let current_block_height: u32 = block.height; + assert(current_block_height < expiry_block_height); + f1.await(); + }; + + return f2; + } +} + diff --git a/transfer_expiry/tests/test_transfer_expiry.leo b/transfer_expiry/tests/test_transfer_expiry.leo new file mode 100644 index 0000000..023e09d --- /dev/null +++ b/transfer_expiry/tests/test_transfer_expiry.leo @@ -0,0 +1,15 @@ +// The 'test_transfer_public_expiry' test program. +import transfer_public_expiry.aleo; +program test_transfer_public_expiry.aleo { + @test + transition test_expiry() { + // Test the transfer_public_with_expiry function + // This would normally require proper setup with credits, but demonstrates the interface + let to: address = aleo1kanvv2cs88dpguwz5yrv890zz89rmycyk7qe6q4qm7vnz30flyzs56zae8; + let amount: u64 = 1000u64; + let expiry_block_height: u32 = 1000000u32; + + // Call the wrapper function + transfer_public_expiry.aleo/transfer_public_with_expiry(to, amount, expiry_block_height); + } +} From 818af439653a365392f49819b6b63cbec8fc88f3 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 21 Oct 2025 17:41:59 +0200 Subject: [PATCH 2/5] .gitignore build files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0fda7a0..509a06b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ **/README.md + +**/build From 26b25dd2db2d6913b151451fa1bbbb812dcf673b Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 21 Oct 2025 17:45:50 +0200 Subject: [PATCH 3/5] Add support for nonce-based transactions --- transfer_expiry/src/main.leo | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/transfer_expiry/src/main.leo b/transfer_expiry/src/main.leo index 975f314..6fa232a 100644 --- a/transfer_expiry/src/main.leo +++ b/transfer_expiry/src/main.leo @@ -3,20 +3,47 @@ import credits.aleo; program transfer_public_expiry.aleo { + // Struct to hold address and nonce for hashing + struct NonceKey { + sender: address, + nonce: u64, + } + + // Mapping to store used nonces for each sender address + // Key is the NonceKey struct containing address and nonce + mapping used_nonces: NonceKey => bool; + // Default constructor which doesn't allow for program upgrades. @noupgrade async constructor() {} - // Wrapper around credits.aleo::transfer_public with an expiry. + // Wrapper around credits.aleo::transfer_public with an expiry and nonce validation. async transition transfer_public_with_expiry( public to: address, public amount: u64, - public expiry_block_height: u32 + public expiry_block_height: u32, + public nonce: u64 ) -> Future { + // Get the sender's address before entering async block + let sender: address = self.caller; + // Call the original transfer_public function from credits.aleo let f1 = credits.aleo/transfer_public(to, amount); let f2: Future = async { + // Create a NonceKey struct with the sender address and nonce + let nonce_key: NonceKey = NonceKey { + sender: sender, + nonce: nonce, + }; + + // Check if the nonce has already been used for this sender + let is_used: bool = used_nonces.get(nonce_key); + assert(!is_used); + + // Store the nonce as used for this sender + used_nonces.set(nonce_key, true); + // Check if current block height is below the expiry block height let current_block_height: u32 = block.height; assert(current_block_height < expiry_block_height); From c79ef100a5f1e40020d154e7b981faf46864b835 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Thu, 23 Oct 2025 15:44:55 +0200 Subject: [PATCH 4/5] Rename leo test --- .../{test_transfer_expiry.leo => transfer_public_expiry.leo} | 1 + 1 file changed, 1 insertion(+) rename transfer_expiry/tests/{test_transfer_expiry.leo => transfer_public_expiry.leo} (99%) diff --git a/transfer_expiry/tests/test_transfer_expiry.leo b/transfer_expiry/tests/transfer_public_expiry.leo similarity index 99% rename from transfer_expiry/tests/test_transfer_expiry.leo rename to transfer_expiry/tests/transfer_public_expiry.leo index 023e09d..be22cbb 100644 --- a/transfer_expiry/tests/test_transfer_expiry.leo +++ b/transfer_expiry/tests/transfer_public_expiry.leo @@ -1,5 +1,6 @@ // The 'test_transfer_public_expiry' test program. import transfer_public_expiry.aleo; + program test_transfer_public_expiry.aleo { @test transition test_expiry() { From 27c5c9a93d03b47a9efd063b526bd5243a4a9393 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 28 Oct 2025 11:18:45 +0100 Subject: [PATCH 5/5] Fixes to enable test to run --- ...expiry.leo => test_transfer_public_expiry.leo} | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) rename transfer_expiry/tests/{transfer_public_expiry.leo => test_transfer_public_expiry.leo} (57%) diff --git a/transfer_expiry/tests/transfer_public_expiry.leo b/transfer_expiry/tests/test_transfer_public_expiry.leo similarity index 57% rename from transfer_expiry/tests/transfer_public_expiry.leo rename to transfer_expiry/tests/test_transfer_public_expiry.leo index be22cbb..76078db 100644 --- a/transfer_expiry/tests/transfer_public_expiry.leo +++ b/transfer_expiry/tests/test_transfer_public_expiry.leo @@ -2,15 +2,26 @@ import transfer_public_expiry.aleo; program test_transfer_public_expiry.aleo { + // Default constructor which doesn't allow for program upgrades. + @noupgrade + async constructor() {} + @test - transition test_expiry() { + async transition test_expiry() -> Future { // Test the transfer_public_with_expiry function // This would normally require proper setup with credits, but demonstrates the interface let to: address = aleo1kanvv2cs88dpguwz5yrv890zz89rmycyk7qe6q4qm7vnz30flyzs56zae8; let amount: u64 = 1000u64; let expiry_block_height: u32 = 1000000u32; + let nonce: u64 = 1u64; // Call the wrapper function - transfer_public_expiry.aleo/transfer_public_with_expiry(to, amount, expiry_block_height); + let f1 = transfer_public_expiry.aleo/transfer_public_with_expiry(to, amount, expiry_block_height, nonce); + + let f2 = async { + f1.await(); + }; + + return f2; } }