@@ -1555,6 +1555,157 @@ public static async Task<List<NFT>> ERC1155_GetOwnedNFTs(this ThirdwebContract c
15551555
15561556 #endregion
15571557
1558+ #region Merkle
1559+
1560+ /// <summary>
1561+ /// Gets the allowlist proof for a wallet address to claim from a drop contract.
1562+ /// This fetches the Merkle tree data from IPFS and calculates the proof locally.
1563+ /// </summary>
1564+ /// <param name="contract">The drop contract to interact with.</param>
1565+ /// <param name="walletAddress">The wallet address to get the proof for.</param>
1566+ /// <param name="claimConditionId">Optional claim condition ID. If not provided, uses the active condition.</param>
1567+ /// <param name="tokenId">Optional token ID for ERC1155 drops.</param>
1568+ /// <returns>The allowlist proof, or null if the wallet is not in the allowlist or it's a public mint.</returns>
1569+ /// <exception cref="ArgumentNullException">Thrown when the contract is null.</exception>
1570+ /// <exception cref="ArgumentException">Thrown when the wallet address is null or empty.</exception>
1571+ public static async Task < AllowlistProof > GetAllowlistProof (
1572+ this ThirdwebContract contract ,
1573+ string walletAddress ,
1574+ BigInteger ? claimConditionId = null ,
1575+ BigInteger ? tokenId = null )
1576+ {
1577+ if ( contract == null )
1578+ {
1579+ throw new ArgumentNullException ( nameof ( contract ) ) ;
1580+ }
1581+
1582+ if ( string . IsNullOrEmpty ( walletAddress ) )
1583+ {
1584+ throw new ArgumentException ( "Wallet address must be provided" , nameof ( walletAddress ) ) ;
1585+ }
1586+
1587+ try
1588+ {
1589+ // Get contract metadata
1590+ var contractUri = await ThirdwebContract . Read < string > ( contract , "contractURI" ) . ConfigureAwait ( false ) ;
1591+ var metadata = await ThirdwebStorage . Download < ContractMetadata > ( contract . Client , contractUri ) . ConfigureAwait ( false ) ;
1592+
1593+ if ( metadata ? . Merkle == null || metadata . Merkle . Count == 0 )
1594+ {
1595+ // No merkle data, return empty proof (public mint)
1596+ return new AllowlistProof
1597+ {
1598+ Proof = new List < byte [ ] > ( ) ,
1599+ QuantityLimitPerWallet = BigInteger . Zero ,
1600+ PricePerToken = BigInteger . Parse ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) , // MAX_UINT256
1601+ Currency = Constants . ADDRESS_ZERO
1602+ } ;
1603+ }
1604+
1605+ // Get claim condition
1606+ Drop_ClaimCondition claimCondition ;
1607+ if ( claimConditionId . HasValue )
1608+ {
1609+ if ( tokenId . HasValue )
1610+ {
1611+ claimCondition = await ThirdwebContract . Read < Drop_ClaimCondition > ( contract , "getClaimConditionById" , tokenId . Value , claimConditionId . Value ) . ConfigureAwait ( false ) ;
1612+ }
1613+ else
1614+ {
1615+ claimCondition = await ThirdwebContract . Read < Drop_ClaimCondition > ( contract , "getClaimConditionById" , claimConditionId . Value ) . ConfigureAwait ( false ) ;
1616+ }
1617+ }
1618+ else
1619+ {
1620+ BigInteger activeId ;
1621+ if ( tokenId . HasValue )
1622+ {
1623+ activeId = await ThirdwebContract . Read < BigInteger > ( contract , "getActiveClaimConditionId" , tokenId . Value ) . ConfigureAwait ( false ) ;
1624+ claimCondition = await ThirdwebContract . Read < Drop_ClaimCondition > ( contract , "getClaimConditionById" , tokenId . Value , activeId ) . ConfigureAwait ( false ) ;
1625+ }
1626+ else
1627+ {
1628+ activeId = await ThirdwebContract . Read < BigInteger > ( contract , "getActiveClaimConditionId" ) . ConfigureAwait ( false ) ;
1629+ claimCondition = await ThirdwebContract . Read < Drop_ClaimCondition > ( contract , "getClaimConditionById" , activeId ) . ConfigureAwait ( false ) ;
1630+ }
1631+ }
1632+
1633+ // Check if it's a public mint (zero merkle root)
1634+ var merkleRootHex = claimCondition . MerkleRoot . BytesToHex ( ) ;
1635+ if ( merkleRootHex == "0x0000000000000000000000000000000000000000000000000000000000000000" )
1636+ {
1637+ // Public mint, no proof needed
1638+ return new AllowlistProof
1639+ {
1640+ Proof = new List < byte [ ] > ( ) ,
1641+ QuantityLimitPerWallet = BigInteger . Zero ,
1642+ PricePerToken = BigInteger . Parse ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) ,
1643+ Currency = Constants . ADDRESS_ZERO
1644+ } ;
1645+ }
1646+
1647+ // Find the tree info URI for this merkle root
1648+ if ( ! metadata . Merkle . TryGetValue ( merkleRootHex , out var treeInfoUri ) )
1649+ {
1650+ // Try without 0x prefix or with different case
1651+ var found = false ;
1652+ foreach ( var kvp in metadata . Merkle )
1653+ {
1654+ if ( kvp . Key . Equals ( merkleRootHex , StringComparison . OrdinalIgnoreCase ) )
1655+ {
1656+ treeInfoUri = kvp . Value ;
1657+ found = true ;
1658+ break ;
1659+ }
1660+ }
1661+
1662+ if ( ! found )
1663+ {
1664+ return null ; // Merkle root not found in metadata
1665+ }
1666+ }
1667+
1668+ // Download tree info
1669+ var treeInfo = await ThirdwebStorage . Download < MerkleTreeInfo > ( contract . Client , treeInfoUri ) . ConfigureAwait ( false ) ;
1670+ if ( treeInfo ? . BaseUri == null )
1671+ {
1672+ return null ;
1673+ }
1674+
1675+ // Calculate shard key and download shard
1676+ var shardKey = MerkleTreeUtils . GetShardKey ( walletAddress , treeInfo . ShardNybbles ) ;
1677+ var shardUri = $ "{ treeInfo . BaseUri } /{ shardKey } .json";
1678+
1679+ ShardData shardData ;
1680+ try
1681+ {
1682+ shardData = await ThirdwebStorage . Download < ShardData > ( contract . Client , shardUri ) . ConfigureAwait ( false ) ;
1683+ }
1684+ catch
1685+ {
1686+ // Try without .json extension
1687+ try
1688+ {
1689+ shardUri = $ "{ treeInfo . BaseUri } /{ shardKey } ";
1690+ shardData = await ThirdwebStorage . Download < ShardData > ( contract . Client , shardUri ) . ConfigureAwait ( false ) ;
1691+ }
1692+ catch
1693+ {
1694+ return null ; // Shard not found, wallet not in allowlist
1695+ }
1696+ }
1697+
1698+ // Calculate proof
1699+ return MerkleTreeUtils . CalculateMerkleProof ( shardData , walletAddress ) ;
1700+ }
1701+ catch
1702+ {
1703+ return null ;
1704+ }
1705+ }
1706+
1707+ #endregion
1708+
15581709 #region DropERC20
15591710
15601711 /// <summary>
@@ -1601,8 +1752,32 @@ public static async Task<ThirdwebTransactionReceipt> DropERC20_Claim(this Thirdw
16011752
16021753 var payableAmount = isNativeToken ? rawAmountToClaim * activeClaimCondition . PricePerToken / BigInteger . Pow ( 10 , 18 ) : BigInteger . Zero ;
16031754
1604- // TODO: Merkle
1605- var allowlistProof = new object [ ] { Array . Empty < byte > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
1755+ // Get merkle proof for allowlist
1756+ var allowlistProofData = await contract . GetAllowlistProof ( receiverAddress ) . ConfigureAwait ( false ) ;
1757+ object [ ] allowlistProof ;
1758+ if ( allowlistProofData != null && allowlistProofData . Proof != null && allowlistProofData . Proof . Count > 0 )
1759+ {
1760+ allowlistProof = new object [ ]
1761+ {
1762+ allowlistProofData . Proof . ToArray ( ) ,
1763+ allowlistProofData . QuantityLimitPerWallet ,
1764+ allowlistProofData . PricePerToken ,
1765+ allowlistProofData . Currency
1766+ } ;
1767+
1768+ // Recalculate payable amount if allowlist has price override
1769+ var maxUint256 = BigInteger . Parse ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) ;
1770+ if ( allowlistProofData . PricePerToken < maxUint256 )
1771+ {
1772+ var allowlistCurrency = allowlistProofData . Currency ;
1773+ isNativeToken = allowlistCurrency == Constants . NATIVE_TOKEN_ADDRESS || allowlistCurrency == Constants . ADDRESS_ZERO ;
1774+ payableAmount = isNativeToken ? rawAmountToClaim * allowlistProofData . PricePerToken / BigInteger . Pow ( 10 , 18 ) : BigInteger . Zero ;
1775+ }
1776+ }
1777+ else
1778+ {
1779+ allowlistProof = new object [ ] { Array . Empty < byte [ ] > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
1780+ }
16061781
16071782 var fnArgs = new object [ ]
16081783 {
@@ -1739,8 +1914,32 @@ public static async Task<ThirdwebTransactionReceipt> DropERC721_Claim(this Third
17391914
17401915 var payableAmount = isNativeToken ? quantity * activeClaimCondition . PricePerToken : BigInteger . Zero ;
17411916
1742- // TODO: Merkle
1743- var allowlistProof = new object [ ] { Array . Empty < byte > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
1917+ // Get merkle proof for allowlist
1918+ var allowlistProofData = await contract . GetAllowlistProof ( receiverAddress ) . ConfigureAwait ( false ) ;
1919+ object [ ] allowlistProof ;
1920+ if ( allowlistProofData != null && allowlistProofData . Proof != null && allowlistProofData . Proof . Count > 0 )
1921+ {
1922+ allowlistProof = new object [ ]
1923+ {
1924+ allowlistProofData . Proof . ToArray ( ) ,
1925+ allowlistProofData . QuantityLimitPerWallet ,
1926+ allowlistProofData . PricePerToken ,
1927+ allowlistProofData . Currency
1928+ } ;
1929+
1930+ // Recalculate payable amount if allowlist has price override
1931+ var maxUint256 = BigInteger . Parse ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) ;
1932+ if ( allowlistProofData . PricePerToken < maxUint256 )
1933+ {
1934+ var allowlistCurrency = allowlistProofData . Currency ;
1935+ isNativeToken = allowlistCurrency == Constants . NATIVE_TOKEN_ADDRESS || allowlistCurrency == Constants . ADDRESS_ZERO ;
1936+ payableAmount = isNativeToken ? quantity * allowlistProofData . PricePerToken : BigInteger . Zero ;
1937+ }
1938+ }
1939+ else
1940+ {
1941+ allowlistProof = new object [ ] { Array . Empty < byte [ ] > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
1942+ }
17441943
17451944 var fnArgs = new object [ ]
17461945 {
@@ -1902,8 +2101,32 @@ public static async Task<ThirdwebTransactionReceipt> DropERC1155_Claim(this Thir
19022101
19032102 var payableAmount = isNativeToken ? quantity * activeClaimCondition . PricePerToken : BigInteger . Zero ;
19042103
1905- // TODO: Merkle
1906- var allowlistProof = new object [ ] { Array . Empty < byte > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
2104+ // Get merkle proof for allowlist (passing tokenId for ERC1155)
2105+ var allowlistProofData = await contract . GetAllowlistProof ( receiverAddress , null , tokenId ) . ConfigureAwait ( false ) ;
2106+ object [ ] allowlistProof ;
2107+ if ( allowlistProofData != null && allowlistProofData . Proof != null && allowlistProofData . Proof . Count > 0 )
2108+ {
2109+ allowlistProof = new object [ ]
2110+ {
2111+ allowlistProofData . Proof . ToArray ( ) ,
2112+ allowlistProofData . QuantityLimitPerWallet ,
2113+ allowlistProofData . PricePerToken ,
2114+ allowlistProofData . Currency
2115+ } ;
2116+
2117+ // Recalculate payable amount if allowlist has price override
2118+ var maxUint256 = BigInteger . Parse ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) ;
2119+ if ( allowlistProofData . PricePerToken < maxUint256 )
2120+ {
2121+ var allowlistCurrency = allowlistProofData . Currency ;
2122+ isNativeToken = allowlistCurrency == Constants . NATIVE_TOKEN_ADDRESS || allowlistCurrency == Constants . ADDRESS_ZERO ;
2123+ payableAmount = isNativeToken ? quantity * allowlistProofData . PricePerToken : BigInteger . Zero ;
2124+ }
2125+ }
2126+ else
2127+ {
2128+ allowlistProof = new object [ ] { Array . Empty < byte [ ] > ( ) , BigInteger . Zero , BigInteger . Zero , Constants . ADDRESS_ZERO } ;
2129+ }
19072130
19082131 var fnArgs = new object [ ]
19092132 {
0 commit comments