sequenceDiagram
actor o as Owner
participant h as Hypercert Minter Contract
participant b as BatchTransfer Contract
actor r as Recipients
o ->> h: call splitFraction()
h ->> h: split fraction
o ->> h: call setApprovalForAll()
o ->> b: call batchTransfer()
critical will be reverted if the owner is not the owner of each fractions
b --> h: check if caller is the owner of fraction
b ->> r: safeTransferFrom()
end
/// @dev msg.sender must be the owner of all the fraction IDs being transferred
/// @dev msg.sender must have approved the contract to transfer the fractions
/// @dev The length of recipients and fractionIds must be the same
/// @param data The encoded data containing the recipients and fraction IDs
function batchTransfer(bytes memory data) external {
require(data.length > 0, INVALID_DATA());
TransferData memory transferData = abi.decode(data, (TransferData));
require(transferData.recipients.length == transferData.fractionIds.length, INVALID_LENGTHS());
_batchTransfer(transferData.recipients, transferData.fractionIds);
}
/// @notice Transfers fractions to multiple recipients
/// @dev The length of recipients and fractionIds must be the same
/// @dev The caller must be the owner of all the fraction IDs being transferred
/// @param recipients The addresses of the recipients
/// @param fractionIds The IDs of the fractions to be transferred
function _batchTransfer(address[] memory recipients, uint256[] memory fractionIds) internal {
uint256 length = recipients.length;
for (uint256 i = 0; i < length; i++) {
address recipient = recipients[i];
uint256 fractionId = fractionIds[i];
require(hypercertToken.ownerOf(fractionId) == msg.sender, INVALID_CALLER(msg.sender));
hypercertToken.safeTransferFrom(msg.sender, recipient, fractionId, 1, "");
}
emit BatchFractionTransfer(msg.sender, recipients, fractionIds);
}
Important
make sure you own the hypercerts.
most of hypercerts have CREATOR ONLY transfer restriction, so make sure you created the hypercerts as well.
-
Encode data with ethers or viem.
with ethers
import { ethers } from "ethers"; ... const recipients = ["0x123....", "0x456...."]; const fractionIds = [BigInt("23894....5301"), BigInt("23894....5302")]; const encodedData = ethers.AbiCoder.defaultAbiCoder().encode( [`tuple(address[], uint256[])`], [[recipients, fractionIds]] ); console.log('Encoded Data:', encodedData); // e.g. Encoded Data: 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000006aa005386f53ba7b980c61e0d067cabc7602a620000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000c000000000000000000000000000000002
with viem
import { encodeAbiParameters } from 'viem'; ... const recipients = ["0x123....", "0x456...."]; const fractionIds = [BigInt("23894....5301"), BigInt("23894....5302")]; const abiParams = [ { type: 'tuple', components: [{ type: 'address[]' }, { type: 'uint256[]' }], }, ]; const encodedData = encodeAbiParameters(abiParams, [ [recipients, fractionIds], ]);
-
In the HypercertsMinter contract on the chain where you want to execute batch transfer, call setApprovalForAll (0xa22cb465) to approve the batchTransfer contract to send hypercerts.
operator == BatchTransfer contract address, approved == true
-
call batchTransfer function
chain | chainId | address |
---|---|---|
Sepolia | 11155111 | 0x59e07f1cc8eb8eca2703179a7217673318a0fe47 |
Base Sepolia | 84532 | 0x3C0FaAA04078d715BB05Af82Ca99c41623AeC5Ae |
Arbitrum Sepolia | 421614 | 0x0fCCa2bAd3103934304874E782450688B7a044B0 |
Optimism | 10 | 0xf77e452ec289da0616574aae371800ca4d6315b1 |
Base | 8453 | 0xc4aEB039BC432343bf4dB57Be203E0540d385a18 |
Arbitrum | 42161 | 0x8b973c408c2748588b3ECFfDA06D670819FbEb1D |
Celo | 42220 | 0xB64B7e4793D72958e028B1D5D556888b115c4c3E |