create_listing
Create peer-to-peer marketplace listings for selling items using cryptocurrency payments. Specify title, price, currency, and wallet address without merchant onboarding requirements.
Instructions
Create a P2P marketplace listing. Anyone can sell items — no merchant onboarding needed. Specify title, price, currency (USDC/ETH/etc), and your wallet address for payment.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Item title (e.g. "PS5 Console") | |
| description | Yes | Item description | |
| price | Yes | Price amount (e.g. 200). Set to 0 for promotional items. | |
| currency | No | Payment currency (USDC, ETH, DAI, etc). Default: USDC | |
| category | No | Category: electronics, clothing, collectibles, home, sports, gifts, books, promotional, other | |
| sellerAddress | No | Seller wallet address for payment. Uses W3SHIP_PUBLIC_KEY if not provided. | |
| sellerName | No | Optional display name for the seller | |
| condition | No | Item condition: new, like_new, used, refurbished. Default: new | |
| quantity | No | How many available. Default: 1 | |
| shipsTo | No | Countries that can be shipped to (e.g. ["US", "CA"]). Default: ["US"] | |
| expiresInDays | No | Listing expires after N days. Default: 30 | |
| isPromo | No | Set to true for promotional/free items. Price auto-sets to 0, category to "promotional". | |
| shippingCost | No | For promo items: shipping cost the buyer pays (e.g. 8.99). Default: 0 | |
| promoQuantity | No | For promo items: how many are available to claim (e.g. 500). Default: 100 | |
| fulfillmentType | No | Fulfillment method: "ship" (mail only), "pickup" (in-store only), "both" (customer chooses). Default: "ship". Set to "pickup" for zero-cost promo distribution. | |
| pickupLocations | No | Pickup locations for in-store fulfillment. Required when fulfillmentType is "pickup" or "both". |
Implementation Reference
- src/index.ts:918-1029 (handler)Handler for 'create_listing' tool which validates input, verifies the seller, and makes a POST request to the W3Ship API.
case 'create_listing': { const { title, description: desc, price, currency: cur, category: cat, sellerAddress: seller, sellerName, condition: cond, quantity: qty, shipsTo, expiresInDays, isPromo, shippingCost, promoQuantity, fulfillmentType, pickupLocations } = args as any; const sellerAddr = seller || CONFIGURED_KEY; if (!sellerAddr) { return { content: [{ type: 'text', text: 'Error: Seller wallet address required. Set W3SHIP_PUBLIC_KEY or provide sellerAddress.' }], isError: true, }; } // Seller verification — check if wallet has a registered identity try { const verifyRes = await fetch(`${W3SHIP_API}/api/listing/verify-seller?publicKey=${encodeURIComponent(sellerAddr)}`); const verifyData = await verifyRes.json() as any; if (!verifyRes.ok || !verifyData.verified) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'Seller not verified', message: 'Your wallet address is not registered in the W3Ship identity ledger. To sell on the marketplace, register your identity first at w3ship.com or link your address via Dah.mx.', publicKey: sellerAddr.substring(0, 20) + '...', registerUrl: 'https://w3ship.com/setup-mcp', }, null, 2) }], isError: true, }; } } catch (e: any) { // If verification service is down, log warning but allow (fail-open for demo) console.warn(`[Listing] Seller verification failed (allowing): ${e.message}`); } const listingId = `LST-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).substring(2, 6).toUpperCase()}`; const expDays = expiresInDays || 30; const expiresAt = new Date(Date.now() + expDays * 24 * 60 * 60 * 1000).toISOString(); const currency = (cur || 'USDC').toUpperCase(); const category = (cat || 'other').toLowerCase(); const condition = cond || 'new'; const quantity = qty || 1; const ships = shipsTo || ['US']; try { const createRes = await fetch(`${W3SHIP_API}/api/listing`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: listingId, title, description: desc, price: isPromo ? 0 : price, currency, category: isPromo ? 'promotional' : category, sellerAddress: sellerAddr, sellerName, condition, quantity: isPromo ? (promoQuantity || quantity) : quantity, shipsTo: ships, expiresAt, isPromo: isPromo || undefined, shippingCost: isPromo ? (shippingCost || 0) : undefined, promoQuantity: isPromo ? (promoQuantity || quantity || 100) : undefined, fulfillmentType: fulfillmentType || undefined, pickupLocations: pickupLocations || undefined, }), }); const createData = await createRes.json() as any; if (!createRes.ok) { return { content: [{ type: 'text', text: `Error creating listing: ${createData.error || 'Unknown error'}` }], isError: true }; } if (isPromo) { const ft = fulfillmentType || (pickupLocations?.length ? 'pickup' : 'ship'); const pickupMsg = ft !== 'ship' && pickupLocations?.length ? ` Pickup available at: ${pickupLocations.map((l: any) => l.name).join(', ')}.` : ''; const shipMsg = ft !== 'pickup' ? ` Shipping: ${shippingCost || 0} ${currency}.` : ''; return { content: [{ type: 'text', text: JSON.stringify({ listing: { id: listingId, title, type: 'PROMOTIONAL', fulfillment: ft, ...(ft !== 'pickup' ? { shippingCost: `${shippingCost || 0} ${currency}` } : {}), ...(pickupLocations?.length ? { pickupLocations: pickupLocations.map((l: any) => ({ id: l.id, name: l.name, address: l.address })) } : {}), totalAvailable: promoQuantity || quantity || 100, expiresAt, }, message: `🎁 Promotional listing created! "${title}" is FREE.${shipMsg}${pickupMsg} ${promoQuantity || quantity || 100} available. One per wallet.`, claimWith: `Customers use claim_promo(listingId: "${listingId}"${ft !== 'ship' ? ', fulfillmentChoice: "pickup"' : ''}) to claim.`, }, null, 2) }] }; } return { content: [{ type: 'text', text: JSON.stringify({ listing: { id: listingId, title, price: `${price} ${currency}`, category, condition, quantity, shipsTo: ships, expiresAt, payTo: sellerAddr }, message: `Listing created! Share listing ID "${listingId}" with buyers. Payment goes to ${sellerAddr}. Stored persistently in DynamoDB.`, addToCart: `Buyers can use add_item with productOffering.id = "${listingId}" to add this to their cart.`, }, null, 2) }] }; } catch (e: any) { return { content: [{ type: 'text', text: `Error creating listing: ${e.message}` }], isError: true }; } } - src/index.ts:250-290 (schema)Definition and input schema for the 'create_listing' tool.
name: 'create_listing', description: 'Create a P2P marketplace listing. Anyone can sell items — no merchant onboarding needed. Specify title, price, currency (USDC/ETH/etc), and your wallet address for payment.', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Item title (e.g. "PS5 Console")' }, description: { type: 'string', description: 'Item description' }, price: { type: 'number', description: 'Price amount (e.g. 200). Set to 0 for promotional items.' }, currency: { type: 'string', description: 'Payment currency (USDC, ETH, DAI, etc). Default: USDC' }, category: { type: 'string', description: 'Category: electronics, clothing, collectibles, home, sports, gifts, books, promotional, other' }, sellerAddress: { type: 'string', description: 'Seller wallet address for payment. Uses W3SHIP_PUBLIC_KEY if not provided.' }, sellerName: { type: 'string', description: 'Optional display name for the seller' }, condition: { type: 'string', description: 'Item condition: new, like_new, used, refurbished. Default: new' }, quantity: { type: 'number', description: 'How many available. Default: 1' }, shipsTo: { type: 'array', items: { type: 'string' }, description: 'Countries that can be shipped to (e.g. ["US", "CA"]). Default: ["US"]' }, expiresInDays: { type: 'number', description: 'Listing expires after N days. Default: 30' }, isPromo: { type: 'boolean', description: 'Set to true for promotional/free items. Price auto-sets to 0, category to "promotional".' }, shippingCost: { type: 'number', description: 'For promo items: shipping cost the buyer pays (e.g. 8.99). Default: 0' }, promoQuantity: { type: 'number', description: 'For promo items: how many are available to claim (e.g. 500). Default: 100' }, fulfillmentType: { type: 'string', description: 'Fulfillment method: "ship" (mail only), "pickup" (in-store only), "both" (customer chooses). Default: "ship". Set to "pickup" for zero-cost promo distribution.' }, pickupLocations: { type: 'array', items: { type: 'object', properties: { id: { type: 'string', description: 'Unique location ID (e.g. "qbm")' }, name: { type: 'string', description: 'Location name (e.g. "Quaker Bridge Mall")' }, address: { type: 'string', description: 'Full street address' }, city: { type: 'string' }, state: { type: 'string' }, hours: { type: 'string', description: 'Operating hours (e.g. "Mon-Sat 10am-9pm")' }, instructions: { type: 'string', description: 'Pickup instructions (e.g. "Ask at VR kiosk near entrance")' }, }, required: ['id', 'name', 'address'], }, description: 'Pickup locations for in-store fulfillment. Required when fulfillmentType is "pickup" or "both".', }, }, required: ['title', 'description', 'price'], }, },