Recipe: Digital + physical bundle
A bundle is a single offer that represents multiple items. The buyer commits once; redemption delivers all items.
// Build BUNDLE metadata directly per the schema — there's no
// bundleMetadataFactory export. See Reference → Metadata schemas for the
// full shape (required top-level fields: schemaUrl, type, uuid, name,
// description, externalUrl, licenseUrl, image, attributes, items, seller).
const uuid = crypto.randomUUID()
const metadata = {
schemaUrl: "https://schema.org/Product",
type: "BUNDLE" as const,
uuid,
name: "Hoodie + Discord access",
description: "A limited hoodie and one year of Discord access.",
externalUrl: `https://example.com/${uuid}`,
licenseUrl: `https://example.com/${uuid}/license`,
image: "https://cdn.example.com/bundle.jpg",
attributes: [
{ traitType: "Type", value: "Bundle" },
{ traitType: "Items", value: "2" },
],
items: [
{ type: "ITEM_PRODUCT_V1", productV1: { /* full PRODUCT_V1 item — see Reference → Metadata schemas */ } },
{ type: "ITEM_NFT", nft: { name: "Discord access pass", description: "1 year of access to our Discord.", image: "https://cdn.example.com/pass.png" } },
],
seller: { defaultVersion: 1, name: "Your brand", contactLinks: [{ tag: "email", url: "mailto:hi@example.com" }] },
}
const metadataUri = await sdk.storeMetadata(metadata)
await sdk.createOffer({ /* … */, metadataUri, metadataHash: metadataUri.replace(/^ipfs:\/\//, "") })At redemption
When the buyer redeems, your fulfillment system delivers both items:
- Physical: ship the hoodie via your normal logistics.
- Digital: send the Discord invite via email / XMTP / postback.
Boson doesn't separate the two — they're a single exchange with a single redemption. If something goes wrong with one item, the dispute is over the whole bundle.