Shipment POAPs

Shipment POAPs

We can write a contract, ApePackageCreator, that tacks on an ApePOAP NFT to any user shipment that includes a Bored Ape Yacht Club NFT to another user via the PostOffice contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "./PostOffice.sol";
import "./ApePOAP.sol";

interface CapsuleData {
    enum CapsuleType {

    struct CapsuleContent {
        CapsuleType capsuleType;
        address[] tokenAddresses;
        uint256[] tokenIds;
        uint256[] amounts;
        string tokenURI;

contract ApePackageCreator is ERC721Holder {
    PostOffice private postOffice;
    ApePOAP private apePOAP;
    IERC721 private boredApe;

    // replace these with the actual contract addresses
    address constant POST_OFFICE_ADDRESS = 0x3b61403AdE9240699042D3663814a3679dc15d56;
    address constant APE_POAP_ADDRESS = 0xC67F5E3a5B697AE004Edd8F84925189a81c6DC4b;
    address constant BORED_APE_ADDRESS = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;

    constructor() {
        postOffice = PostOffice(POST_OFFICE_ADDRESS);
        boredApe = IERC721(BORED_APE_ADDRESS);

    function createApePackage(
        address to_,
        uint256 tokenId,
        bytes32 passwordHash_,
        uint64 unlockTimestamp_
    ) external returns (uint256) {
        // Transfer Bored Ape from user to this contract
        boredApe.safeTransferFrom(msg.sender, address(this), tokenId);

        // Mint a new Ape POAP;

        address[] memory _tokenAddresses = new address[](2);
        _tokenAddresses[0] = address(boredApe);
        _tokenAddresses[1] = address(apePOAP);

        uint256[] memory _tokenIds = new uint256[](2);
        _tokenIds[0] = tokenId;
        _tokenIds[1] = apePOAP.tokenOfOwnerByIndex(address(this), apePOAP.balanceOf(address(this)) - 1);

        CapsuleData.CapsuleContent memory _packageContent = CapsuleData.CapsuleContent({
            capsuleType: CapsuleData.CapsuleType.ERC721,
            tokenAddresses: _tokenAddresses,
            tokenIds: _tokenIds,
            amounts: new uint256[](2),
            tokenURI: ""

        SecurityInfo memory _securityInfo = SecurityInfo({
            passwordHash: passwordHash_,
            unlockTimestamp: unlockTimestamp_,
            keyAddress: address(0),
            keyId: 0

        // ship package through PostOffice
        uint256 packageId = postOffice.shipPackage(_packageContent, _securityInfo, to_);

        return packageId;

    function pickup(
        uint256 packageId_,
        string calldata rawPassword_,
        string calldata salt_,
        bool shouldRedeem_
    ) external {
        postOffice.pickup(packageId_, rawPassword_, salt_, shouldRedeem_);

ApePackageCreator accepts the recipient's address, the Bored Ape's token ID, a security hash, and an unlock timestamp. It then transfers the Bored Ape NFT from the sender to itself, mints a new ApePOAP NFT for the sender, and creates a new package in the PostOffice contract containing both NFTs. The new package can then be redeemed by the recipient using the PostOffice's methods.

Last updated