diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js
new file mode 100644
index 00000000000..df81e144380
--- /dev/null
+++ b/modules/silverpushBidAdapter.js
@@ -0,0 +1,326 @@
+import { config } from '../src/config.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import * as utils from '../src/utils.js';
+import { mergeDeep } from '../src/utils.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { Renderer } from '../src/Renderer.js';
+import { ajax } from '../src/ajax.js';
+
+const BIDDER_CODE = 'silverpush';
+const bidderConfig = 'sp_pb_ortb';
+const bidderVersion = '1.0.0';
+const DEFAULT_CURRENCY = 'USD';
+
+export const REQUEST_URL = 'https://apac.chocolateplatform.com/bidder/?identifier=prebidchoc';
+export const SP_OUTSTREAM_PLAYER_URL = 'https://xaido.sgp1.cdn.digitaloceanspaces.com/prebid/spoutstream.min.js';
+
+const VIDEO_ORTB_PARAMS = [
+ 'mimes',
+ 'minduration',
+ 'maxduration',
+ 'placement',
+ 'protocols',
+ 'startdelay',
+ 'skip',
+ 'skipafter',
+ 'minbitrate',
+ 'maxbitrate',
+ 'delivery',
+ 'playbackmethod',
+ 'api',
+ 'linearity'
+];
+
+export const VIDEO_ORTB_REQUIRED = ['api', 'mimes', 'placement', 'protocols', 'minduration', 'maxduration', 'startdelay'];
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO],
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ onBidWon,
+ getRequest: function(endpoint) {
+ ajax(endpoint, null, undefined, {method: 'GET'});
+ },
+ getOS: function(ua) {
+ if (ua.indexOf('Windows') != -1) { return 'Windows'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'iOS'; } else if (ua.indexOf('Mac OS X') != -1) { return 'macOS'; } else if (ua.match(/Android/)) { return 'Android'; } else if (ua.indexOf('Linux') != -1) { return 'Linux'; } else { return 'Unknown'; }
+ }
+};
+
+registerBidder(spec);
+
+export const CONVERTER = ortbConverter({
+ context: {
+ netRevenue: true,
+ ttl: 300
+ },
+ imp(buildImp, bidRequest, context) {
+ let imp = buildImp(bidRequest, context);
+
+ if (bidRequest.mediaTypes[VIDEO]) {
+ imp = buildVideoImp(bidRequest, imp);
+ } else if (bidRequest.mediaTypes[BANNER]) {
+ imp = buildBannerImp(bidRequest, imp);
+ }
+
+ const bidFloor = getBidFloor(bidRequest);
+
+ utils.deepSetValue(imp, 'bidfloor', bidFloor);
+
+ if (bidRequest.params.deals && bidRequest.params.deals.length > 0) {
+ utils.deepSetValue(imp, 'pmp', { deals: bidRequest.params.deals });
+ }
+
+ return imp;
+ },
+ request(buildRequest, imps, bidderRequest, context) {
+ const req = buildRequest(imps, bidderRequest, context);
+ mergeDeep(req, {
+ at: 1,
+ ext: {
+ bc: `${bidderConfig}_${bidderVersion}`
+ }
+ })
+
+ let userAgent = navigator.userAgent;
+ utils.deepSetValue(req, 'device.os', spec.getOS(userAgent));
+ utils.deepSetValue(req, 'device.devicetype', _isMobile() ? 1 : _isConnectedTV() ? 3 : 2);
+
+ const bid = context.bidRequests[0];
+ if (bid.params.publisherId) {
+ utils.deepSetValue(req, 'site.publisher.id', bid.params.publisherId);
+ }
+
+ return req;
+ },
+
+ bidResponse(buildBidResponse, bid, context) {
+ let bidResponse = buildBidResponse(bid, context);
+
+ if (bid.ext) {
+ bidResponse.meta.networkId = bid.ext.dsp_id;
+ bidResponse.meta.advertiserId = bid.ext.buyer_id;
+ bidResponse.meta.brandId = bid.ext.brand_id;
+ }
+
+ if (context.ortbResponse.ext && context.ortbResponse.ext.paf) {
+ bidResponse.meta.paf = Object.assign({}, context.ortbResponse.ext.paf);
+ bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id');
+ }
+
+ bidResponse = buildVideoVastResponse(bidResponse)
+ bidResponse = buildVideoOutstreamResponse(bidResponse, context)
+
+ return bidResponse;
+ },
+ response(buildResponse, bidResponses, ortbResponse, context) {
+ const response = buildResponse(bidResponses, ortbResponse, context);
+
+ let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs');
+ if (fledgeAuctionConfigs) {
+ fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => {
+ return Object.assign({
+ bidId,
+ auctionSignals: {}
+ }, cfg);
+ });
+ return {
+ bids: response.bids,
+ fledgeAuctionConfigs,
+ }
+ } else {
+ return response.bids
+ }
+ }
+});
+
+function isBidRequestValid(bidRequest) {
+ return (isPublisherIdValid(bidRequest) && (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest)));
+}
+
+function isPublisherIdValid(bidRequest) {
+ let pubId = utils.deepAccess(bidRequest, 'params.publisherId');
+ return (pubId != null && utils.isStr(pubId) && pubId != '');
+}
+
+function isValidBannerRequest(bidRequest) {
+ const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`);
+
+ return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
+}
+
+function isValidVideoRequest(bidRequest) {
+ const videoSizes = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.playerSize`);
+ const PARAM_EXISTS = VIDEO_ORTB_REQUIRED.every(param => utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.${param}`) != null);
+
+ return PARAM_EXISTS && utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1]));
+}
+
+function buildRequests(validBids, bidderRequest) {
+ let videoBids = validBids.filter(bid => isVideoBid(bid));
+ let bannerBids = validBids.filter(bid => isBannerBid(bid));
+ let requests = [];
+
+ bannerBids.forEach(bid => {
+ requests.push(createRequest([bid], bidderRequest, BANNER));
+ });
+
+ videoBids.forEach(bid => {
+ requests.push(createRequest([bid], bidderRequest, VIDEO));
+ });
+
+ return requests;
+}
+
+function buildVideoImp(bidRequest, imp) {
+ if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') {
+ imp.video.placement = imp.video.placement || 4;
+ }
+
+ const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`);
+ const videoSizes = (videoMediaType && videoMediaType.playerSize) || [];
+
+ if (videoSizes && videoSizes.length > 0) {
+ utils.deepSetValue(imp, 'video.w', videoSizes[0][0]);
+ utils.deepSetValue(imp, 'video.h', videoSizes[0][1]);
+ }
+
+ const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {});
+ const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {});
+
+ const videoParams = { ...videoAdUnitParams, ...videoBidderParams };
+
+ VIDEO_ORTB_PARAMS.forEach((param) => {
+ if (videoParams.hasOwnProperty(param)) {
+ utils.deepSetValue(imp, `video.${param}`, videoParams[param]);
+ }
+ });
+
+ return { ...imp };
+}
+
+function buildBannerImp(bidRequest, imp) {
+ const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`, []);
+
+ if (bannerSizes && bannerSizes.length > 0) {
+ utils.deepSetValue(imp, 'banner.w', bannerSizes[0][0]);
+ utils.deepSetValue(imp, 'banner.h', bannerSizes[0][1]);
+ }
+
+ return {...imp};
+}
+
+function createRequest(bidRequests, bidderRequest, mediaType) {
+ return {
+ method: 'POST',
+ url: REQUEST_URL,
+ data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } })
+ }
+}
+
+function buildVideoVastResponse(bidResponse) {
+ if (bidResponse.mediaType == VIDEO && bidResponse.vastXml) {
+ bidResponse.vastUrl = bidResponse.vastXml;
+ }
+
+ return { ...bidResponse }
+}
+
+function buildVideoOutstreamResponse(bidResponse, context) {
+ if (context.bidRequest.mediaTypes[VIDEO]?.context === 'outstream') {
+ bidResponse.rendererUrl = SP_OUTSTREAM_PLAYER_URL;
+ bidResponse.adUnitCode = context.bidRequest.adUnitCode;
+
+ bidResponse.renderer = Renderer.install({
+ id: bidResponse.requestId,
+ adUnitCode: context.bidRequest.adUnitCode,
+ url: bidResponse.rendererUrl
+ });
+
+ bidResponse.renderer.setRender(_renderer(bidResponse));
+
+ bidResponse.renderer.render(bidResponse);
+ }
+
+ return {...bidResponse};
+}
+
+function getBidFloor(bid) {
+ const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
+
+ if (typeof bid.getFloor !== 'function') {
+ return utils.deepAccess(bid, 'params.bidFloor', 0.05);
+ }
+
+ const bidFloor = bid.getFloor({
+ currency: currency,
+ mediaType: '*',
+ size: '*',
+ });
+ return bidFloor.floor;
+}
+
+function _renderer(bid) {
+ bid.renderer.push(() => {
+ if (typeof window.SPOutStreamPlayer === 'function') {
+ const spoplayer = new window.SPOutStreamPlayer(bid);
+
+ spoplayer.on('ready', () => {
+ spoplayer.startAd();
+ });
+
+ try {
+ let vastUrlbt = 'data:text/xml;charset=utf-8;base64,' + btoa(bid.vastUrl.replace(/\\"/g, '"'));
+ spoplayer.load(vastUrlbt).then(function() {
+ window.spoplayer = spoplayer;
+ }).catch(function(reason) {
+ setTimeout(function() { throw reason; }, 0);
+ });
+ } catch (err) {
+ utils.logMessage(err);
+ }
+ } else {
+ utils.logMessage(`Silverpush outstream player is not defined`);
+ }
+ });
+}
+
+function isVideoBid(bid) {
+ return utils.deepAccess(bid, 'mediaTypes.video');
+}
+
+function isBannerBid(bid) {
+ return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid);
+}
+
+function interpretResponse(resp, req) {
+ if (!resp.body) {
+ resp.body = { nbr: 0 };
+ }
+
+ return CONVERTER.fromORTB({ request: req.data, response: resp.body });
+}
+
+function onBidWon(bid) {
+ if (bid == null) { return; }
+ if (bid['burl'] == null) { return; }
+
+ let burlMac = bid['burl'];
+ burlMac = burlMac.replace('$' + '{AUCTION_PRICE}', bid['cpm']);
+ burlMac = burlMac.replace('$' + '{AUCTION_ID}', bid['auctionId']);
+ burlMac = burlMac.replace('$' + '{AUCTION_IMP_ID}', bid['requestId']);
+ burlMac = burlMac.replace('$' + '{AUCTION_AD_ID}', bid['adId']);
+ burlMac = burlMac.replace('$' + '{AUCTION_SEAT_ID}', bid['seatBidId']);
+
+ spec.getRequest(burlMac);
+}
+
+function _isMobile() {
+ return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent);
+}
+
+function _isConnectedTV() {
+ return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent);
+}
diff --git a/modules/silverpushBidAdapter.md b/modules/silverpushBidAdapter.md
new file mode 100644
index 00000000000..d0af8ba8da8
--- /dev/null
+++ b/modules/silverpushBidAdapter.md
@@ -0,0 +1,107 @@
+# Overview
+
+```
+Module Name: Silverpush OpenRTB Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: prebid@silverpush.co
+```
+
+# Description
+
+Prebid.JS adapter that connects to the Chocolate Ad Exchange.
+
+*NOTE*: The Silverpush Bidder Adapter requires setup and approval before use. Please reach out to prebid@silverpush.co representative for more details.
+
+# Bid Parameters
+
+## Banner/Video
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| -------------- | ----------- | ------------------------------------------ | ------------- | ------------ |
+| `publisherId` | required | Publisher id provided by silverpush | "123456" | String |
+| `bidFloor` | optional | Minimum price in USD. bidFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50.
| 1.50 | Number |
+
+
+
+
+# mediaTypes Parameters
+
+## mediaTypes.banner
+
+The following banner parameters are supported here so publishers may fully declare their banner inventory:
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- |
+| sizes | required | Avalaible sizes supported for banner ad unit | [ [300, 250], [300, 600] ] | [[Integer, Integer], [Integer, Integer]] |
+
+## mediaTypes.video
+
+
+The following video parameters are supported here so publishers may fully declare their video inventory:
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- |
+| context | required | instream or outstream |"outstream" | string |
+| playerSize | required | Avalaible sizes supported for video ad unit. | [300, 250] | [Integer, Integer] |
+| mimes | required | List of content MIME types supported by the player. | ["video/mp4"]| [String]|
+| protocols | required | Supported video bid response protocol values. | [2,3,5,6] | [integers]|
+| api | required | Supported API framework values. | [2] | [integers] |
+| maxduration | required | Maximum video ad duration in seconds. | 30 | Integer |
+| minduration | required | Minimum video ad duration in seconds. | 6 | Integer |
+| startdelay | required | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | 0 | Integer |
+| placement | required | Placement type for the impression. | 1 | Integer |
+| minbitrate | optional | Minimum bit rate in Kbps. | 300 | Integer |
+| maxbitrate | optional | Maximum bit rate in Kbps. | 9600 | Integer |
+| playbackmethod | optional | Playback methods that may be in use. Only one method is typically used in practice. | [2]| [Integers] |
+| linearity | optional | OpenRTB2 linearity. in-strea,overlay... | 1 | Integer |
+| skip | optional | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes . | 1 | Integer |
+| skipafter | optional | Number of seconds a video must play before skipping is enabled; only applicable if the ad is skippable. | 5 | Integer |
+| delivery | optional | OpenRTB2 delivery. Supported delivery methods (e.g., streaming, progressive). If none specified, assume all are supported. | 1 | [Integer] |
+
+
+# Example
+```javascript
+ var adUnits = [{
+ code: 'div-1',
+ mediaTypes: {
+ banner: {
+ sizes: [ [300, 250], [300,600] ]
+ }
+ },
+ bids: [{
+ bidder: 'silverpush',
+ params: {
+ publisherId: "123456",
+ bidFloor: 1.2
+ }
+ }]
+ },{
+ code: 'video-1',
+ mediaTypes: {
+ video: {
+ api: [1, 2, 4, 6],
+ mimes: ['video/mp4'],
+ context: 'instream', // or 'outstream'
+ playerSize: [ 640, 480 ],
+ protocols: [4,5,6,7],
+ placement: 1,
+ minduration: 0,
+ maxduration: 60,
+ startdelay: 0
+ }
+ },
+ bids: [
+ {
+ bidder: 'silverpush',
+ params: {
+ publisherId: "123456",
+ bidfloor: 2.5
+ }
+ }
+ ]
+ }
+];
+```
diff --git a/test/spec/modules/silverpushBidAdapter_spec.js b/test/spec/modules/silverpushBidAdapter_spec.js
new file mode 100644
index 00000000000..de31135eabe
--- /dev/null
+++ b/test/spec/modules/silverpushBidAdapter_spec.js
@@ -0,0 +1,394 @@
+import { expect } from 'chai';
+import * as utils from 'src/utils';
+import { newBidder } from 'src/adapters/bidderFactory.js';
+import { REQUEST_URL, SP_OUTSTREAM_PLAYER_URL, CONVERTER, spec } from '../../../modules/silverpushBidAdapter.js';
+
+const bannerBid = {
+ bidder: 'silverpush',
+ params: {
+ publisherId: '012345',
+ bidFloor: 1.5
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 50],
+ ],
+ },
+ },
+ adUnitCode: 'div-gpt-ad-928572628472-0',
+ bidId: 'dl38fjf9d',
+ bidderRequestId: 'brid00000000',
+ auctionId: 'aucid0000000',
+};
+
+const videoBid = {
+ bidder: 'silverpush',
+ params: {
+ publisherId: '012345',
+ bidFloor: 0.1
+ },
+ mediaTypes: {
+ video: {
+ api: [1, 2, 4, 6],
+ mimes: ['video/mp4'],
+ playbackmethod: [2, 4, 6],
+ playerSize: [[1024, 768]],
+ protocols: [3, 4, 7, 8, 10],
+ placement: 1,
+ minduration: 0,
+ maxduration: 60,
+ startdelay: 0
+ },
+ },
+ adUnitCode: 'div-gpt-ad-928572628472-1',
+ bidId: '281141d3541362',
+ bidderRequestId: 'brid00000000',
+ auctionId: 'aucid0000000',
+};
+
+const bidderRequest = {
+ auctionId: 'aucid0000000',
+ bidderRequestId: 'brid00000000',
+ timeout: 200,
+ refererInfo: {
+ page: 'https://hello-world-page.com/',
+ domain: 'hello-world-page.com',
+ ref: 'http://example-domain.com/foo',
+ }
+};
+
+const bannerReponse = {
+ 'id': 'brid00000000',
+ 'seatbid': [
+ {
+ 'bid': [
+ {
+ 'id': 'ARUYoUZx',
+ 'impid': 'dl38fjf9d',
+ 'price': 1.64,
+ 'adid': 'aaaaadddddddd',
+ 'burl': 'http://0.0.0.0:8181/burl',
+ 'adm': '