Summary
@bosonprotocol/core-sdk's erc20.handler.encodeTransferAuthorizationEntry and
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorization cannot
express empty / fallback entries (0x) inside a token-transfer-authorization
queue. The on-chain
MetaTransactionsHandlerFacet.executeMetaTransactionWithTokenTransferAuthorization
TokenTransferAuthorizationLib.loadQueue accept 0x as a shortcut for "no
auth — fall back to ERC-20 allowance", and several commit-time flows
require at least one empty leading slot. The SDK gap forces downstream
consumers to bypass the handler and hand-roll the queue + outer calldata.
Where the gap is
In packages/contracts-sdk/src/erc20/handler.ts (and the corresponding
meta-tx/handler.ts), the public type is:
export type UnsignedTransferAuthorization =
| { strategy: "ERC3009"; data: { ... } }
| { strategy: "EIP2612"; data: { ... } }
| { strategy: "Permit2"; data: { ... } }
| { strategy: "DAIPermit"; data: { ... } };
encodeTransferAuthorizationEntry is an exhaustive switch over these four
strategies — every element of transferAuthorizations: TransferAuthorization[]
must carry a strategy id + signature. There is no representation for
bytes("") (the empty entry the on-chain queue accepts as a placeholder).
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorization then
maps the input array via .map(encodeTransferAuthorizationEntry) and passes
the result to the meta-tx handler's executeMetaTransactionWithTokenTransferAuthorization(..., bytes[]) ABI member — so the same restriction applies end-to-end.
Why it matters (BPIP-12 / commit-time flows)
The protocol's commit-time meta-transactions
(createOfferAndCommit, createOfferCommitAndRedeem) emit multiple
transferFundsIn calls in sequence — one for the zero-amount seller
deposit, one for the buyer's price. TokenTransferAuthorizationLib.loadQueue
consumes one queue entry per transferFundsIn site via discardNext(). For
these flows the queue must have a leading empty entry so the zero-amount
seller-deposit transferFundsIn advances past 0x (no auth needed for a
zero-value pull) and the buyer's strategy-typed auth lands on the next slot.
Today, downstream consumers that want to drive BPIP-12 meta-txes have to:
- Hand-roll the outer envelope ABI (or import it from
@bosonprotocol/common's
IBosonMetaTransactionsHandlerABI),
- Hand-roll the per-entry
abi.encode(uint8 strategy, bytes data) layout,
- Hand-pin the on-chain
TokenTransferAuthorizationStrategy enum values
(ERC3009=1, EIP2612=2, Permit2=3, DAIPermit=4),
- And manage the leading-empty-slot insertion themselves.
That's exactly what the SDK is supposed to encapsulate. The current shape
forces a separate code path for every consumer that needs the empty-slot
queue.
Proposed change
Add an explicit fallback variant, called "None".
export type UnsignedTransferAuthorization =
| { strategy: "None" }
| { strategy: "ERC3009"; data: { ... } }
| { strategy: "EIP2612"; data: { ... } }
| { strategy: "Permit2"; data: { ... } }
| { strategy: "DAIPermit"; data: { ... } };
encodeTransferAuthorizationEntry returns "0x" for the "None" case.
No signature fields required. Cleanly typed; callers can build queues
declaratively.
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorization
should accept the same shape transparently.
Reference
- On-chain semantics:
boson-protocol-contracts —
TokenTransferAuthorizationLib.loadQueue / discardNext / consumeForTransfer.
- On-chain enum:
BosonTypes.TokenTransferAuthorizationStrategy (None=0,
ERC3009=1, EIP2612=2, Permit2=3, DAIPermit=4).
- Downstream motivation: the
@bosonprotocol/x402-facilitator package in
bosonprotocol/x402B currently
ships typescript/packages/facilitator/src/internal/build-bpip12-calldata.ts
as a local workaround. Closing this issue would let us delete that helper
and route the BPIP-12 commit path through
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorization({ returnTxInfo: true }).
Acceptance
UnsignedTransferAuthorization (or the queue array shape) can express an
empty / fallback entry without consumers reaching into ABI primitives.
- A test that round-trips a queue like
[fallback, erc3009Auth] through
executeMetaTransactionWithTokenTransferAuthorization and asserts the
encoded bytes[] matches the protocol's expectation ([ "0x", <strategy-typed entry> ]).
Summary
@bosonprotocol/core-sdk'serc20.handler.encodeTransferAuthorizationEntryandmetaTx.handler.executeMetaTransactionWithTokenTransferAuthorizationcannotexpress empty / fallback entries (
0x) inside a token-transfer-authorizationqueue. The on-chain
MetaTransactionsHandlerFacet.executeMetaTransactionWithTokenTransferAuthorizationTokenTransferAuthorizationLib.loadQueueaccept0xas a shortcut for "noauth — fall back to ERC-20 allowance", and several commit-time flows
require at least one empty leading slot. The SDK gap forces downstream
consumers to bypass the handler and hand-roll the queue + outer calldata.
Where the gap is
In
packages/contracts-sdk/src/erc20/handler.ts(and the correspondingmeta-tx/handler.ts), the public type is:encodeTransferAuthorizationEntryis an exhaustiveswitchover these fourstrategies — every element of
transferAuthorizations: TransferAuthorization[]must carry a strategy id + signature. There is no representation for
bytes("")(the empty entry the on-chain queue accepts as a placeholder).metaTx.handler.executeMetaTransactionWithTokenTransferAuthorizationthenmaps the input array via
.map(encodeTransferAuthorizationEntry)and passesthe result to the meta-tx handler's
executeMetaTransactionWithTokenTransferAuthorization(..., bytes[])ABI member — so the same restriction applies end-to-end.Why it matters (BPIP-12 / commit-time flows)
The protocol's commit-time meta-transactions
(
createOfferAndCommit,createOfferCommitAndRedeem) emit multipletransferFundsIncalls in sequence — one for the zero-amount sellerdeposit, one for the buyer's price.
TokenTransferAuthorizationLib.loadQueueconsumes one queue entry per
transferFundsInsite viadiscardNext(). Forthese flows the queue must have a leading empty entry so the zero-amount
seller-deposit
transferFundsInadvances past0x(no auth needed for azero-value pull) and the buyer's strategy-typed auth lands on the next slot.
Today, downstream consumers that want to drive BPIP-12 meta-txes have to:
@bosonprotocol/common'sIBosonMetaTransactionsHandlerABI),abi.encode(uint8 strategy, bytes data)layout,TokenTransferAuthorizationStrategyenum values(
ERC3009=1,EIP2612=2,Permit2=3,DAIPermit=4),That's exactly what the SDK is supposed to encapsulate. The current shape
forces a separate code path for every consumer that needs the empty-slot
queue.
Proposed change
Add an explicit fallback variant, called "None".
encodeTransferAuthorizationEntryreturns"0x"for the"None"case.No signature fields required. Cleanly typed; callers can build queues
declaratively.
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorizationshould accept the same shape transparently.
Reference
boson-protocol-contracts—TokenTransferAuthorizationLib.loadQueue/discardNext/consumeForTransfer.BosonTypes.TokenTransferAuthorizationStrategy(None=0,ERC3009=1,EIP2612=2,Permit2=3,DAIPermit=4).@bosonprotocol/x402-facilitatorpackage inbosonprotocol/x402Bcurrentlyships
typescript/packages/facilitator/src/internal/build-bpip12-calldata.tsas a local workaround. Closing this issue would let us delete that helper
and route the BPIP-12 commit path through
metaTx.handler.executeMetaTransactionWithTokenTransferAuthorization({ returnTxInfo: true }).Acceptance
UnsignedTransferAuthorization(or the queue array shape) can express anempty / fallback entry without consumers reaching into ABI primitives.
[fallback, erc3009Auth]throughexecuteMetaTransactionWithTokenTransferAuthorizationand asserts theencoded
bytes[]matches the protocol's expectation ([ "0x", <strategy-typed entry> ]).