Skip to content

Integration question: composing a Monad transfer gate with SignedZone token-specific authorization #1418

Description

@riffsmon-bit

Integration question: composing a transfer gate with SignedZone authorization

We are evaluating an optional transfer gate for dYØØR Season 2 on Monad. We need
to preserve current creator-fee enforcement and existing marketplace transfers.
This is a compatibility/design question, not a report of an exploit in Seaport
or the deployed registry
. The current validator works directly; our proposed
forwarding approach changes the caller context and is not deployed.

If this belongs with a different SignedZone/transfer-registry maintainer, please
point us to the appropriate integration channel. We found no duplicate issue here.

Public deployment and reproduction context

Item Value
Project https://dyoor.fun
Chain Monad, 143
Collection 0x349d8eb480c92cf75371fba5c6344a4d11b9103a
Collection validator 0xa000027a9b2802e1ddf7000061001e5c005a0000
Validator name from matching published runtime StrictAuthorizedTransferSecurityRegistry
Validator runtime keccak256 0xc7dfe8ae4da5613f6406eab346f0808ee2322316f44d186136c830a71491366c
Policy security level 3, operator list 4
Registered authorizer 0x000056f7000000ece9003ca63978907a00ffd100
Local fork block 102648547
Block hash 0x07aceeb45656eea920296a98bbea1fe93f7deb2292dc761d5dc585e19149dd13
Public RPC used https://rpc.monad.xyz

The Monad validator runtime matches the published Ethereum deployed runtime at
the same address byte-for-byte. Published source/runtime evidence.
This does not establish source equivalence for the Monad authorizer itself.

Observed behavior

The validator reads collection policy using msg.sender; token-specific temporary
authorization also depends on the calling collection and token ID. A new gate
calling the existing validator is therefore not transparent.

On a local Foundry fork, five checks passed:

  1. Direct validation preserves the collection's existing World escrow whitelist
    entry; naive forwarding does not.
  2. Applying the same list and security level to the forwarding gate restores
    static whitelist decisions and continues to reject an unknown operator.
  3. The registered authorizer's beforeAuthorizedTransfer(collection, tokenId)
    permits the direct path, but not the mirrored forwarding path.
  4. An actual collection transferFrom succeeds with token approval plus that
    temporary authorization before installing the gate, then fails after installing
    it. The token approval is restored before the second attempt.
  5. An unregistered caller cannot create the temporary authorization.

Minimal local reproduction sequence

Use a disposable fork at the block above. All impersonation, configuration changes
and transfers below are local test operations, not broadcasts or instructions
to reconfigure the live collection. No private keys or funded accounts are needed.

The incomplete forwarding gate used for the comparison has a public owner()
returning the collection administrator and this validation body:

function validateTransfer(address caller, address from, address to, uint256 id)
    external view
{
    require(msg.sender == collection);
    legacy.validateTransfer(caller, from, to, id);
}

Foundry-style sequence (pseudocode, not a complete standalone harness):

S2 = collection above; V = existing validator; tokenId = 11
governor = S2.owner(); holder = S2.ownerOf(11)
authorizer = V.getAuthorizerAccounts(4)[0]
operator = address(0xBAD); recipient = address(0xB0B)
assert V.isAccountWhitelisted(4, operator) == false

prank(holder): S2.approve(operator, tokenId)
prank(authorizer): V.beforeAuthorizedTransfer(S2, tokenId)
prank(operator): S2.transferFrom(holder, recipient, tokenId) // succeeds
prank(recipient): S2.transferFrom(recipient, holder, tokenId)
prank(holder): S2.approve(operator, tokenId)

deploy local gate(collection=S2, legacy=V, owner=governor)
prank(governor): V.applyListToCollection(gate, 4)
prank(governor): V.setTransferSecurityLevelOfCollection(gate, 3)
prank(governor): S2.setTransferValidator(gate)
prank(operator): S2.transferFrom(holder, recipient, tokenId) // reverts
assert S2.ownerOf(tokenId) == holder

Run the sequence in one test transaction so transient authorization remains in
scope. Impersonating the registered authorizer demonstrates registry semantics;
it is not a valid signed OpenSea order or an end-to-end fulfillment test.

Questions before choosing any integration

  1. Is there a supported composable gate or explicit-collection validation/read
    interface that preserves the original collection's temporary authorization?
  2. If SignedZone must instead authorize a new registry, does OpenSea support that
    integration on Monad? What onboarding/configuration is required, and how are
    existing orders and already-issued fulfillment extraData affected?
  3. Is there a reproducible signed-order fixture or supported local test procedure
    for token-specific authorization and fulfillment on this deployment?
  4. Where can we obtain the exact Monad authorizer source/build and immutable
    configuration to verify compatibility independently?

The creator-fee enforcement documentation
describes the SignedZone/registry handshake and API-provided signed extraData.
We do not want to assume custom forwarding is supported merely because the ABI
matches. We will not disable filtering or blanket-whitelist operators to make the
test pass. NFT custody and existing wallet addresses must remain unchanged.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions