diff --git a/src/offramp/generateOffRampURL.test.ts b/src/offramp/generateOffRampURL.test.ts index 6d8eff6..608bc1f 100644 --- a/src/offramp/generateOffRampURL.test.ts +++ b/src/offramp/generateOffRampURL.test.ts @@ -5,15 +5,22 @@ describe('generateOffRampURL', () => { const url = new URL( generateOffRampURL({ appId: 'test', - addresses: { '0x1': ['base'] }, - redirectUrl: 'https://example.com', }), ); expect(url.origin).toEqual('https://pay.coinbase.com'); expect(url.pathname).toEqual('/v3/offramp/input'); expect(url.searchParams.get('appId')).toEqual('test'); - expect(url.searchParams.get('addresses')).toEqual('{"0x1":["base"]}'); + }); + + it('should support redirectUrl', () => { + const url = new URL( + generateOffRampURL({ + appId: 'test', + redirectUrl: 'https://example.com', + }), + ); + expect(url.searchParams.get('redirectUrl')).toEqual('https://example.com'); }); @@ -45,7 +52,6 @@ describe('generateOffRampURL', () => { '90123jd09ef09df': ['solana'], }, assets: ['USDC', 'SOL'], - redirectUrl: 'https://example.com', }), ); @@ -60,8 +66,6 @@ describe('generateOffRampURL', () => { generateOffRampURL({ host: 'http://localhost:3000', appId: 'test', - addresses: { '0x1': ['base'] }, - redirectUrl: 'https://example.com', }), ); @@ -74,8 +78,6 @@ describe('generateOffRampURL', () => { const url = new URL( generateOffRampURL({ appId: 'test', - addresses: { '0x1': ['base'] }, - redirectUrl: 'https://example.com', presetCryptoAmount: 0.1, presetFiatAmount: 20, }), @@ -89,8 +91,6 @@ describe('generateOffRampURL', () => { const url = new URL( generateOffRampURL({ appId: 'test', - addresses: { '0x1': ['base'] }, - redirectUrl: 'https://example.com', defaultNetwork: 'ethereum', }), ); diff --git a/src/offramp/initOffRamp.test.ts b/src/offramp/initOffRamp.test.ts new file mode 100644 index 0000000..c97ed07 --- /dev/null +++ b/src/offramp/initOffRamp.test.ts @@ -0,0 +1,25 @@ +import { initOffRamp } from './initOffRamp'; +import { CBPayInstance } from '../utils/CBPayInstance'; + +jest.mock('../utils/CBPayInstance'); + +describe('initOffRamp', () => { + it('should return CBPayInstance', async () => { + let instance: unknown; + initOffRamp( + { + experienceLoggedIn: 'popup', + experienceLoggedOut: 'popup', + appId: 'abc123', + widgetParameters: { addresses: { '0x1': ['base'] }, redirectUrl: 'https://example.com' }, + }, + (_, newInstance) => { + instance = newInstance; + }, + ); + + expect(CBPayInstance).toHaveBeenCalledTimes(1); + + expect(instance instanceof CBPayInstance).toBe(true); + }); +}); diff --git a/src/offramp/initOffRamp.ts b/src/offramp/initOffRamp.ts new file mode 100644 index 0000000..300b7fb --- /dev/null +++ b/src/offramp/initOffRamp.ts @@ -0,0 +1,27 @@ +import { CBPayExperienceOptions } from '../types/widget'; +import { CBPayInstance, CBPayInstanceType } from '../utils/CBPayInstance'; +import { OffRampAppParams } from '../types/offramp'; + +export type InitOffRampParams = CBPayExperienceOptions; + +export type InitOffRampCallback = { + (error: Error, instance: null): void; + (error: null, instance: CBPayInstanceType): void; +}; + +export const initOffRamp = ( + { + experienceLoggedIn = 'new_tab', // default experience type + widgetParameters, + ...options + }: InitOffRampParams, + callback: InitOffRampCallback, +): void => { + const instance = new CBPayInstance({ + ...options, + widget: 'offramp', + experienceLoggedIn, + appParams: widgetParameters, + }); + callback(null, instance); +}; diff --git a/src/types/offramp.ts b/src/types/offramp.ts index 4e802e5..8182e84 100644 --- a/src/types/offramp.ts +++ b/src/types/offramp.ts @@ -11,9 +11,9 @@ export type BaseOffRampAppParams = { * * `{ "0x1": ["base"] }` */ - addresses: Record; + addresses?: Record; /** A URL that the user will be redirected to after to sign their transaction after the transaction has been committed. */ - redirectUrl: string; + redirectUrl?: string; /** * This optional parameter will restrict the assets available for the user to cash out. It acts as a filter on the * networks specified in the {addresses} param. diff --git a/src/types/widget.ts b/src/types/widget.ts index d025128..f2a721f 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -1,6 +1,6 @@ import { EventMetadata } from './events'; -export type WidgetType = 'buy' | 'checkout'; +export type WidgetType = 'buy' | 'checkout' | 'offramp'; export type IntegrationType = 'direct' | 'secure_standalone'; diff --git a/src/utils/CBPayInstance.ts b/src/utils/CBPayInstance.ts index e865ceb..8314fef 100644 --- a/src/utils/CBPayInstance.ts +++ b/src/utils/CBPayInstance.ts @@ -17,6 +17,7 @@ export type CBPayInstanceConstructorArguments = { const widgetRoutes: Record = { buy: '/buy', checkout: '/checkout', + offramp: '/v3/offramp', }; export interface CBPayInstanceType { diff --git a/src/utils/CoinbasePixel.test.ts b/src/utils/CoinbasePixel.test.ts index 7592f51..7fc6bae 100644 --- a/src/utils/CoinbasePixel.test.ts +++ b/src/utils/CoinbasePixel.test.ts @@ -107,6 +107,27 @@ describe('CoinbasePixel', () => { }); }); + it('should handle opening offramp the new_tab experience in chrome extensions', () => { + window.chrome = { + // @ts-expect-error - test + tabs: { + create: jest.fn(), + }, + }; + + const instance = new CoinbasePixel(defaultArgs); + + instance.openExperience({ + ...defaultOpenOptions, + experienceLoggedIn: 'new_tab', + path: '/v3/offramp', + }); + + expect(window.chrome.tabs.create).toHaveBeenCalledWith({ + url: 'https://pay.coinbase.com/v3/offramp/input?addresses=%7B%220x0%22%3A%5B%22ethereum%22%5D%7D&appId=test', + }); + }); + it('should handle opening the popup experience in browsers', () => { const instance = new CoinbasePixel(defaultArgs); @@ -119,6 +140,22 @@ describe('CoinbasePixel', () => { ); }); + it('should handle opening offramp in the popup experience in browsers', () => { + const instance = new CoinbasePixel(defaultArgs); + + instance.openExperience({ + ...defaultOpenOptions, + experienceLoggedIn: 'popup', + path: '/v3/offramp', + }); + + expect(window.open).toHaveBeenCalledWith( + 'https://pay.coinbase.com/v3/offramp/input?addresses=%7B%220x0%22%3A%5B%22ethereum%22%5D%7D&appId=test', + 'Coinbase', + 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, height=730,width=460', + ); + }); + it('should handle opening the new_tab experience in browsers', () => { const instance = new CoinbasePixel(defaultArgs); @@ -131,6 +168,22 @@ describe('CoinbasePixel', () => { ); }); + it('should handle opening the offramp experience in new_tab in browsers', () => { + const instance = createUntypedPixel(defaultArgs); + + instance.openExperience({ + ...defaultOpenOptions, + experienceLoggedIn: 'new_tab', + path: '/v3/offramp', + }); + + expect(window.open).toHaveBeenCalledWith( + 'https://pay.coinbase.com/v3/offramp/input?addresses=%7B%220x0%22%3A%5B%22ethereum%22%5D%7D&appId=test', + 'Coinbase', + undefined, + ); + }); + it('.destroy should remove embedded pixel', () => { const instance = createUntypedPixel(defaultArgs); expect(instance.unsubs).toHaveLength(0); diff --git a/src/utils/CoinbasePixel.ts b/src/utils/CoinbasePixel.ts index 6b96e3e..2e6d507 100644 --- a/src/utils/CoinbasePixel.ts +++ b/src/utils/CoinbasePixel.ts @@ -5,6 +5,7 @@ import { JsonObject } from 'types/JsonTypes'; import { onBroadcastedPostMessage } from './postMessage'; import { EventMetadata } from 'types/events'; import { generateOnRampURL } from '../onramp/generateOnRampURL'; +import { generateOffRampURL } from '../offramp/generateOffRampURL'; const PopupSizes: Record<'signin' | 'widget', { width: number; height: number }> = { signin: { @@ -73,12 +74,23 @@ export class CoinbasePixel { const experience = experienceLoggedOut || experienceLoggedIn; - const url = generateOnRampURL({ - appId: this.appId, - host: this.host, - theme: this.theme ?? undefined, - ...this.appParams, - }); + let url = ''; + if (options.path === '/v3/offramp') { + url = generateOffRampURL({ + appId: this.appId, + host: this.host, + theme: this.theme ?? undefined, + ...this.appParams, + }); + } else { + url = generateOnRampURL({ + appId: this.appId, + host: this.host, + theme: this.theme ?? undefined, + ...this.appParams, + }); + } + console.log({ url }); this.log('Opening experience', { experience });