From 22230910720c1551f08905d46835e3bded0601d7 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 17:56:48 +0400 Subject: [PATCH 1/6] Bump version to 0.5.0 --- core/package.json | 2 +- events/package.json | 2 +- libraries/package.json | 2 +- mock/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/package.json b/core/package.json index 0c57b03..70a8539 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@biomapper-sdk/core", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "type": "module", "files": [ diff --git a/events/package.json b/events/package.json index fb4829b..50c0f94 100644 --- a/events/package.json +++ b/events/package.json @@ -1,6 +1,6 @@ { "name": "@biomapper-sdk/events", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "type": "module", "files": [ diff --git a/libraries/package.json b/libraries/package.json index 7cf559a..9fc9714 100644 --- a/libraries/package.json +++ b/libraries/package.json @@ -1,6 +1,6 @@ { "name": "@biomapper-sdk/libraries", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "type": "module", "files": [ diff --git a/mock/package.json b/mock/package.json index 50d9ba6..d63f4c4 100644 --- a/mock/package.json +++ b/mock/package.json @@ -1,6 +1,6 @@ { "name": "@biomapper-sdk/mock", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "type": "module", "files": [ From bd4bbf5864091ded80f77ed5d2c4341f0d99e670 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 17:59:19 +0400 Subject: [PATCH 2/6] Add IBiomapperLogAddressesPerGenerationEnumerator interface --- ...perLogAddressesPerGenerationEnumerator.sol | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 core/IBiomapperLogAddressesPerGenerationEnumerator.sol diff --git a/core/IBiomapperLogAddressesPerGenerationEnumerator.sol b/core/IBiomapperLogAddressesPerGenerationEnumerator.sol new file mode 100644 index 0000000..9e52d56 --- /dev/null +++ b/core/IBiomapperLogAddressesPerGenerationEnumerator.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IBiomapperLogAddressesPerGenerationEnumerator { + /** + * @dev Retrieves all biomapped accounts within a specified generation. + * @param generationPtr The block number marking the start of the generation to get the list of biomapped accounts. + * @param cursor The starting index of the page. For the first request, set `cursor` to address(0). + * @param maxPageSize The maximum number of elements to return in this call (also soft-capped in the contract). + * @return nextCursor The starting index for the next page of results. + * @return biomappedAccounts An array of addresses that were biomapped within a specified generation. + * + * Notes: + * - For the first request, set `cursor` to address(0) to start from the beginning of the dataset. + * - If `nextCursor` is address(0), all available elements have been retrieved, indicating the end of the dataset. + * - There is a soft cap on the max page size that is implementation-dependent. + */ + function listAddressesPerGeneration( + uint256 generationPtr, + address cursor, + uint256 maxPageSize + ) + external + view + returns (address nextCursor, address[] memory biomappedAccounts); +} From 18224f121b102bbd556272db6778b2eac2677db2 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 17:59:38 +0400 Subject: [PATCH 3/6] Update README.md --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2bf3f8b..eac823f 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,15 @@ Find the up-to-date contract addresses [here][contract-addresses]. ## Implementation Table -| Contract | Implemented Interfaces | -| ------------------ | ------------------------------------------------------- | -| `Biomapper` | [`IGenerationChangeEvents`], [`IProveUniquenessEvents`] | -| `BiomapperLog` | [`IBiomapperLogRead`] | -| `BridgedBiomapper` | [`IBridgedBiomapperRead`], [`IBridgeBiomappingEvents`] | +| Contract | Implemented Interfaces | +| ------------------ | ------------------------------------------------------------------------ | +| `Biomapper` | [`IGenerationChangeEvents`], [`IProveUniquenessEvents`] | +| `BiomapperLog` | [`IBiomapperLogRead`], [`IBiomapperLogAddressesPerGenerationEnumerator`] | +| `BridgedBiomapper` | [`IBridgedBiomapperRead`], [`IBridgeBiomappingEvents`] | [`IBiomapperLogRead`]: core/IBiomapperLogRead.sol/interface.IBiomapperLogRead.html [`IBridgedBiomapperRead`]: core/IBridgedBiomapperRead.sol/interface.IBridgedBiomapperRead.html +[`IBiomapperLogAddressesPerGenerationEnumerator`]: core/IBiomapperLogAddressesPerGenerationEnumerator.sol/interface.IBiomapperLogAddressesPerGenerationEnumerator.html [`IGenerationChangeEvents`]: events/IGenerationChangeEvents.sol/interface.IGenerationChangeEvents.html [`IProveUniquenessEvents`]: events/IProveUniquenessEvents.sol/interface.IProveUniquenessEvents.html [`IBridgeBiomappingEvents`]: events/IBridgeBiomappingEvents.sol/interface.IBridgeBiomappingEvents.html @@ -48,6 +49,7 @@ Import the dependencies from the `@biomapper-sdk` like this: ```solidity import {IBiomapperLogRead} from "@biomapper-sdk/core/IBiomapperLogRead.sol"; import {IBridgedBiomapperRead} from "@biomapper-sdk/core/IBridgedBiomapperRead.sol"; +import {IBiomapperLogAddressesPerGenerationEnumerator} from "@biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol"; import {BiomapperLogLib} from "@biomapper-sdk/libraries/BiomapperLogLib.sol"; import {BridgedBiomapperLib} from "@biomapper-sdk/libraries/BridgedBiomapperLib.sol"; ``` @@ -65,6 +67,7 @@ Import the dependencies from `biomapper-sdk` like this: ```solidity import {IBiomapperLogRead} from "biomapper-sdk/core/IBiomapperLogRead.sol"; import {IBridgedBiomapperRead} from "biomapper-sdk/core/IBridgedBiomapperRead.sol"; +import {IBiomapperLogAddressesPerGenerationEnumerator} from "biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol"; import {BiomapperLogLib} from "biomapper-sdk/libraries/BiomapperLogLib.sol"; import {BridgedBiomapperLib} from "biomapper-sdk/libraries/BridgedBiomapperLib.sol"; ``` From 3b9b9087a89e47b88ccf7027e606a0861fd6f5b4 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 18:01:04 +0400 Subject: [PATCH 4/6] Add ProactiveSybilResistantAirdrop example contract --- .../ProactiveSybilResistantAirdrop.sol | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol diff --git a/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol b/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol new file mode 100644 index 0000000..af17dbe --- /dev/null +++ b/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBiomapperLogRead} from "@biomapper-sdk/core/IBiomapperLogRead.sol"; +import {IBiomapperLogAddressesPerGenerationEnumerator} from "@biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol"; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title Proactive Sybil-Resistant Airdrop + * @dev A contract for conducting a Sybil-resistant airdrop by sending tokens to all biomapped users. + */ +contract ProactiveSybilResistantAirdrop { + using SafeERC20 for IERC20; + + IERC20 public immutable ERC20_TOKEN; // The ERC20 token being airdropped + address public immutable TOKEN_VAULT; // The address holding the tokens for the airdrop + uint256 public immutable AMOUNT_PER_USER; // The amount of tokens to send to each user + IBiomapperLogAddressesPerGenerationEnumerator + public immutable BIOMAPPER_LOG; // The contract for retrieving unique users list + uint256 public immutable MAX_USERS_PER_AIRDROP; // Maximum amount of users to get tokens for each function call + uint256 public immutable GENERATION_PTR; // Current generation pointer at the moment of contract deployment + + address public nextAccountToGetAirdrop; // The cursor for enumertor + bool public airdropCompleted; // The completion flag + + /** + * @dev Constructor to initialize the contract with required parameters. + * @param tokenAddress The address of the ERC20 token being airdropped. + * @param tokenVault The address holding the tokens for the airdrop. + * @param amountPerUser The amount of tokens to send to each user. + * @param biomapperLogAddress The address of the contract for retrieving unique users list. + * @param maxUsersPerAirdrop The address of the contract for checking uniqueness of users. + */ + constructor( + address tokenAddress, + address tokenVault, + uint256 amountPerUser, + address biomapperLogAddress, + uint256 maxUsersPerAirdrop + ) { + ERC20_TOKEN = IERC20(tokenAddress); + TOKEN_VAULT = tokenVault; + AMOUNT_PER_USER = amountPerUser; + BIOMAPPER_LOG = IBiomapperLogAddressesPerGenerationEnumerator( + biomapperLogAddress + ); + MAX_USERS_PER_AIRDROP = maxUsersPerAirdrop; + GENERATION_PTR = IBiomapperLogRead(biomapperLogAddress) + .generationsHead(); + } + + event AirdropIsCompleted(); + + /** + * @dev Send tokens to biomapped users in the set generation, no more than `MAX_USERS_PER_AIRDROP` users per call. + */ + function airdrop() public { + require(!airdropCompleted, "Airdrop is completed"); + + (address nextCursor, address[] memory biomappedAccounts) = BIOMAPPER_LOG + .listAddressesPerGeneration( + GENERATION_PTR, + nextAccountToGetAirdrop, + MAX_USERS_PER_AIRDROP + ); + + for (uint index = 0; index < biomappedAccounts.length; index++) { + ERC20_TOKEN.safeTransferFrom( + TOKEN_VAULT, + biomappedAccounts[index], + AMOUNT_PER_USER + ); + } + + if (nextCursor == address(0)) { + airdropCompleted = true; + emit AirdropIsCompleted(); + } + + nextAccountToGetAirdrop = nextCursor; + } +} From c77c48605b049630899ff6314f7fd16c15538ee7 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 18:41:19 +0400 Subject: [PATCH 5/6] Add a new function to example --- .../ProactiveSybilResistantAirdrop.sol | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol b/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol index af17dbe..690171f 100644 --- a/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol +++ b/examples/proactive-sybil-resistant-airdrop/contracts/ProactiveSybilResistantAirdrop.sol @@ -19,7 +19,7 @@ contract ProactiveSybilResistantAirdrop { uint256 public immutable AMOUNT_PER_USER; // The amount of tokens to send to each user IBiomapperLogAddressesPerGenerationEnumerator public immutable BIOMAPPER_LOG; // The contract for retrieving unique users list - uint256 public immutable MAX_USERS_PER_AIRDROP; // Maximum amount of users to get tokens for each function call + uint256 public immutable MAX_USERS_PER_AIRDROP_TICK; // Maximum amount of users to get tokens for each function call uint256 public immutable GENERATION_PTR; // Current generation pointer at the moment of contract deployment address public nextAccountToGetAirdrop; // The cursor for enumertor @@ -31,14 +31,14 @@ contract ProactiveSybilResistantAirdrop { * @param tokenVault The address holding the tokens for the airdrop. * @param amountPerUser The amount of tokens to send to each user. * @param biomapperLogAddress The address of the contract for retrieving unique users list. - * @param maxUsersPerAirdrop The address of the contract for checking uniqueness of users. + * @param maxUsersPerAirdropTick The address of the contract for checking uniqueness of users. */ constructor( address tokenAddress, address tokenVault, uint256 amountPerUser, address biomapperLogAddress, - uint256 maxUsersPerAirdrop + uint256 maxUsersPerAirdropTick ) { ERC20_TOKEN = IERC20(tokenAddress); TOKEN_VAULT = tokenVault; @@ -46,7 +46,7 @@ contract ProactiveSybilResistantAirdrop { BIOMAPPER_LOG = IBiomapperLogAddressesPerGenerationEnumerator( biomapperLogAddress ); - MAX_USERS_PER_AIRDROP = maxUsersPerAirdrop; + MAX_USERS_PER_AIRDROP_TICK = maxUsersPerAirdropTick; GENERATION_PTR = IBiomapperLogRead(biomapperLogAddress) .generationsHead(); } @@ -54,16 +54,17 @@ contract ProactiveSybilResistantAirdrop { event AirdropIsCompleted(); /** - * @dev Send tokens to biomapped users in the set generation, no more than `MAX_USERS_PER_AIRDROP` users per call. + * @dev Send tokens to biomapped users in the set generation, no more than `MAX_USERS_PER_AIRDROP_TICK` users per call. + * @return needsMoreTicks The list of biomapped accounts is not exhausted, the airdrop is not completed. */ - function airdrop() public { + function airdropTick() public returns (bool needsMoreTicks) { require(!airdropCompleted, "Airdrop is completed"); (address nextCursor, address[] memory biomappedAccounts) = BIOMAPPER_LOG .listAddressesPerGeneration( GENERATION_PTR, nextAccountToGetAirdrop, - MAX_USERS_PER_AIRDROP + MAX_USERS_PER_AIRDROP_TICK ); for (uint index = 0; index < biomappedAccounts.length; index++) { @@ -74,11 +75,24 @@ contract ProactiveSybilResistantAirdrop { ); } + nextAccountToGetAirdrop = nextCursor; + if (nextCursor == address(0)) { airdropCompleted = true; emit AirdropIsCompleted(); + return false; } - nextAccountToGetAirdrop = nextCursor; + return true; + } + + /** + * @dev Send tokens to all biomapped users in the set generation. + * This function may fail due to excessive gas usage, call `airdropTick` in multiple transactions instead. + */ + function airdrop() external { + require(!airdropCompleted, "Airdrop is completed"); + + while (airdropTick()) {} } } From f3002fe22cd7abc7e9ea40f2ac1190747e295b71 Mon Sep 17 00:00:00 2001 From: Tony C Date: Tue, 21 Oct 2025 21:27:04 +0400 Subject: [PATCH 6/6] Update CanImport test --- tests/hardhat/contracts/CanImport.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/hardhat/contracts/CanImport.sol b/tests/hardhat/contracts/CanImport.sol index 4a8240e..27a9679 100644 --- a/tests/hardhat/contracts/CanImport.sol +++ b/tests/hardhat/contracts/CanImport.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {IBiomapperLogRead} from "@biomapper-sdk/core/IBiomapperLogRead.sol"; +import {IBiomapperLogAddressesPerGenerationEnumerator} from "@biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol"; import {BiomapperLogLib} from "@biomapper-sdk/libraries/BiomapperLogLib.sol"; import {BridgedBiomapperLib} from "@biomapper-sdk/libraries/BridgedBiomapperLib.sol"; import {IGenerationChangeEvents} from "@biomapper-sdk/events/IGenerationChangeEvents.sol";