diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js
new file mode 100644
index 00000000000..517d6a93177
--- /dev/null
+++ b/modules/dsp_genieeBidAdapter.js
@@ -0,0 +1,123 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { deepAccess, deepSetValue } from '../src/utils.js';
+import { config } from '../src/config.js';
+const BIDDER_CODE = 'dsp_geniee';
+const ENDPOINT_URL = 'https://rt.gsspat.jp/prebid_auction';
+const ENDPOINT_URL_UNCOMFORTABLE = 'https://rt.gsspat.jp/prebid_uncomfortable';
+const ENDPOINT_USERSYNC = 'https://rt.gsspat.jp/prebid_cs';
+const VALID_CURRENCIES = ['USD', 'JPY'];
+const converter = ortbConverter({
+ context: { ttl: 300, netRevenue: true },
+ // set optional parameters
+ imp(buildImp, bidRequest, context) {
+ const imp = buildImp(bidRequest, context);
+ deepSetValue(imp, 'ext', bidRequest.params);
+ return imp;
+ }
+});
+
+function USPConsent(consent) {
+ return typeof consent === 'string' && consent[0] === '1' && consent.toUpperCase()[2] === 'Y';
+}
+
+function invalidCurrency(currency) {
+ return typeof currency === 'string' && VALID_CURRENCIES.indexOf(currency.toUpperCase()) === -1;
+}
+
+function hasTest(imp) {
+ if (typeof imp !== 'object') {
+ return false;
+ }
+ for (let i = 0; i < imp.length; i++) {
+ if (deepAccess(imp[i], 'ext.test') === 1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER],
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} - The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function (_) {
+ return true;
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @param {bidderRequest} - the master bidRequest object
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || // gdpr
+ USPConsent(bidderRequest.uspConsent) || // usp
+ config.getConfig('coppa') || // coppa
+ invalidCurrency(config.getConfig('currency.adServerCurrency')) // currency validation
+ ) {
+ return {
+ method: 'GET',
+ url: ENDPOINT_URL_UNCOMFORTABLE
+ };
+ }
+
+ const payload = converter.toORTB({ validBidRequests, bidderRequest });
+
+ if (hasTest(deepAccess(payload, 'imp'))) {
+ deepSetValue(payload, 'test', 1);
+ }
+
+ deepSetValue(payload, 'at', 1); // first price auction only
+
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: payload
+ };
+ },
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @param {BidRequest} bidRequest - the master bidRequest object
+ * @return {bids} - An array of bids which were nested inside the server.
+ */
+ interpretResponse: function (serverResponse, bidRequest) {
+ if (!serverResponse.body) { // empty response (no bids)
+ return [];
+ }
+ const bids = converter.fromORTB({ response: serverResponse.body, request: bidRequest.data }).bids;
+ return bids;
+ },
+
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @param {ServerResponse[]} serverResponses List of server's responses.
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncs = [];
+ // gdpr & usp
+ if (deepAccess(gdprConsent, 'gdprApplies') || USPConsent(uspConsent)) {
+ return syncs;
+ }
+ if (syncOptions.pixelEnabled) {
+ syncs.push({
+ type: 'image',
+ url: ENDPOINT_USERSYNC
+ });
+ }
+ return syncs;
+ }
+};
+registerBidder(spec);
diff --git a/modules/dsp_genieeBidAdapter.md b/modules/dsp_genieeBidAdapter.md
new file mode 100644
index 00000000000..d51d66884af
--- /dev/null
+++ b/modules/dsp_genieeBidAdapter.md
@@ -0,0 +1,39 @@
+# Overview
+
+```markdown
+Module Name: Geniee Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: dsp_back@geniee.co.jp
+```
+
+# Description
+This is [Geniee](https://geniee.co.jp) Bidder Adapter for Prebid.js.
+
+Please contact us before using the adapter.
+
+We will provide ads when satisfy the following conditions:
+
+- There are a certain number bid requests by zone
+- The request is a Banner ad
+- Payment is possible in Japanese yen or US dollars
+- The request is not for GDPR or COPPA users
+
+Thus, even if the following test, it will be no bids if the request does not reach a certain requests.
+
+# Test AdUnits
+```javascript
+var adUnits={
+ code: 'geniee-test-ad',
+ bids: [{
+ bidder: 'dsp_geniee',
+ params: {
+ test: 1,
+ }
+ }],
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ }
+};
+```
diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js
new file mode 100644
index 00000000000..94ec1011fbf
--- /dev/null
+++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js
@@ -0,0 +1,173 @@
+import { expect } from 'chai';
+import { spec } from 'modules/dsp_genieeBidAdapter.js';
+import { config } from 'src/config';
+
+describe('Geniee adapter tests', () => {
+ const validBidderRequest = {
+ code: 'sample_request',
+ bids: [{
+ bidId: 'bid-id',
+ bidder: 'dsp_geniee',
+ params: {
+ test: 1
+ }
+ }],
+ gdprConsent: {
+ gdprApplies: false
+ },
+ uspConsent: '1YNY'
+ };
+
+ describe('isBidRequestValid function test', () => {
+ it('valid', () => {
+ expect(spec.isBidRequestValid(validBidderRequest.bids[0])).equal(true);
+ });
+ });
+ describe('buildRequests function test', () => {
+ it('auction', () => {
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ const auction_id = request.data.id;
+ expect(request).deep.equal({
+ method: 'POST',
+ url: 'https://rt.gsspat.jp/prebid_auction',
+ data: {
+ at: 1,
+ id: auction_id,
+ imp: [
+ {
+ ext: {
+ test: 1
+ },
+ id: 'bid-id'
+ }
+ ],
+ test: 1
+ },
+ });
+ });
+ it('uncomfortable (gdpr)', () => {
+ validBidderRequest.gdprConsent.gdprApplies = true;
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ expect(request).deep.equal({
+ method: 'GET',
+ url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+ });
+ validBidderRequest.gdprConsent.gdprApplies = false;
+ });
+ it('uncomfortable (usp)', () => {
+ validBidderRequest.uspConsent = '1YYY';
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ expect(request).deep.equal({
+ method: 'GET',
+ url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+ });
+ validBidderRequest.uspConsent = '1YNY';
+ });
+ it('uncomfortable (coppa)', () => {
+ config.setConfig({ coppa: true });
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ expect(request).deep.equal({
+ method: 'GET',
+ url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+ });
+ config.resetConfig();
+ });
+ it('uncomfortable (currency)', () => {
+ config.setConfig({ currency: { adServerCurrency: 'TWD' } });
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ expect(request).deep.equal({
+ method: 'GET',
+ url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+ });
+ config.resetConfig();
+ });
+ });
+ describe('interpretResponse function test', () => {
+ it('sample bid', () => {
+ const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
+ const auction_id = request.data.id;
+ const adm = "\n";
+ const serverResponse = {
+ body: {
+ id: auction_id,
+ cur: 'JPY',
+ seatbid: [{
+ bid: [{
+ id: '7b77235d599e06d289e58ddfa9390443e22d7071',
+ impid: 'bid-id',
+ price: 0.6666000000000001,
+ adid: '8405715',
+ adm: adm,
+ adomain: ['geniee.co.jp'],
+ iurl: 'http://img.gsspat.jp/e/068c8e1eafbf0cb6ac1ee95c36152bd2/04f4bd4e6b71f978d343d84ecede3877.png',
+ cid: '8405715',
+ crid: '1383823',
+ cat: ['IAB1'],
+ w: 300,
+ h: 250,
+ mtype: 1
+ }]
+ }]
+ }
+ };
+ const bids = spec.interpretResponse(serverResponse, request);
+ expect(bids).deep.equal([{
+ ad: adm,
+ cpm: 0.6666000000000001,
+ creativeId: '1383823',
+ creative_id: '1383823',
+ height: 250,
+ width: 300,
+ currency: 'JPY',
+ mediaType: 'banner',
+ meta: {
+ advertiserDomains: ['geniee.co.jp']
+ },
+ netRevenue: true,
+ requestId: 'bid-id',
+ seatBidId: '7b77235d599e06d289e58ddfa9390443e22d7071',
+ ttl: 300
+ }]);
+ });
+ it('no bid', () => {
+ const serverResponse = {};
+ const bids = spec.interpretResponse(serverResponse, validBidderRequest);
+ expect(bids).deep.equal([]);
+ });
+ });
+ describe('getUserSyncs function test', () => {
+ it('sync enabled', () => {
+ const syncOptions = {
+ iframeEnabled: true,
+ pixelEnabled: true
+ };
+ const serverResponses = [];
+ const syncs = spec.getUserSyncs(syncOptions, serverResponses);
+ expect(syncs).deep.equal([{
+ type: 'image',
+ url: 'https://rt.gsspat.jp/prebid_cs'
+ }]);
+ });
+ it('sync disabled (option false)', () => {
+ const syncOptions = {
+ iframeEnabled: false,
+ pixelEnabled: false
+ };
+ const serverResponses = [];
+ const syncs = spec.getUserSyncs(syncOptions, serverResponses);
+ expect(syncs).deep.equal([]);
+ });
+ it('sync disabled (gdpr)', () => {
+ const syncOptions = {
+ iframeEnabled: true,
+ pixelEnabled: true
+ };
+ const serverResponses = [];
+ const gdprConsent = {
+ gdprApplies: true
+ };
+ const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent);
+ expect(syncs).deep.equal([]);
+ });
+ });
+});