@nowramp/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * @nowramp/sdk v0.1.0
3
+ * (c) 2026 NowRamp
4
+ * @license MIT
5
+ */
6
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={evm:{pattern:/^0x[a-fA-F0-9]{40}$/,description:"EVM address must start with 0x followed by 40 hexadecimal characters"},solana:{pattern:/^[1-9A-HJ-NP-Za-km-z]{32,44}$/,description:"Solana address must be 32-44 base58 characters"},bitcoin:{pattern:/^(1[a-km-zA-HJ-NP-Z1-9]{25,34}|3[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/,description:"Bitcoin address must be a valid legacy, SegWit, or native SegWit format"}},t={ETH:"evm",USDC:"evm",USDT:"evm",DAI:"evm",WETH:"evm",WBTC:"evm",MATIC:"evm",SOL:"solana",BTC:"bitcoin"},i=["https:"],n=["localhost","127.0.0.1"],r={public:"pk_",secret:"sk_"};function o(e){return e?e.includes(r.secret)?{valid:!1,error:"Secret API keys (sk_) cannot be used in the SDK. Use a public key (pk_) instead. Secret keys should only be used in backend server code."}:(e.includes(r.public)||console.warn("RampWidget: API key does not have a public key prefix (pk_). For security, please generate a new public key for frontend use."),{valid:!0}):{valid:!1,error:"apiKey is required"}}function s(e){if(!e)return{valid:!0};try{const t=new URL(e);return i.includes(t.protocol)||"http:"===t.protocol&&n.includes(t.hostname)?{valid:!0}:{valid:!1,error:`redirectUrl must use HTTPS (got ${t.protocol})`}}catch{return{valid:!1,error:"redirectUrl is not a valid URL"}}}function a(i,n){if(!i)return{valid:!0};const r=i.trim();if(n){const i=t[n.toUpperCase()];if(i){const{pattern:t,description:n}=e[i];return t.test(r)?{valid:!0}:{valid:!1,error:n}}}for(const{pattern:t}of Object.values(e))if(t.test(r))return{valid:!0};return r.length>=10&&r.length<=100?{valid:!0}:{valid:!1,error:"walletAddress format is not recognized"}}function d(e,t){if(!t)return!0;try{if(e===new URL(t).origin)return!0;const i=new URL(e);return"localhost"===i.hostname||"127.0.0.1"===i.hostname}catch{return!1}}const c={production:"https://widget.nowramp.com",staging:"https://widget.staging.nowramp.com",development:"http://localhost:7482"},l=c.production;class h{constructor(e){this.iframe=null,this.modalContainer=null,this.boundMessageHandler=null,this.isOpen=!1;const t=e.widgetUrl||(e.environment?c[e.environment]:l);this.config={widgetUrl:t,flowType:"buy",theme:"light",...e},this.validateConfig()}open(){this.isOpen?console.warn("RampWidget: Widget is already open"):(this.isOpen=!0,this.setupMessageListener(),this.createIframe())}close(){this.isOpen&&(this.isOpen=!1,this.cleanup(),this.config.onClose?.())}setAmount(e){this.sendCommand("SET_AMOUNT",e)}setTheme(e){this.sendCommand("SET_THEME",e)}refreshQuote(){this.sendCommand("REFRESH_QUOTE")}validateConfig(){if(!this.config.apiKey)throw new Error("RampWidget: apiKey is required");if(!this.config.projectId)throw new Error("RampWidget: projectId is required");if(!this.config.externalUserId)throw new Error("RampWidget: externalUserId is required");const e=o(this.config.apiKey);if(!e.valid)throw new Error(`RampWidget: ${e.error}`);if(this.config.redirectUrl){const e=s(this.config.redirectUrl);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}if(this.config.walletAddress){const e=a(this.config.walletAddress,this.config.destinationCurrency);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}}buildWidgetUrl(){const e=new URLSearchParams({apiKey:this.config.apiKey,projectId:this.config.projectId,externalUserId:this.config.externalUserId,parentOrigin:window.location.origin,flowType:this.config.flowType||"buy",theme:this.config.theme||"light"});return this.config.locale&&e.set("locale",this.config.locale),void 0!==this.config.amount&&e.set("amount",String(this.config.amount)),this.config.sourceCurrency&&e.set("sourceCurrency",this.config.sourceCurrency),this.config.destinationCurrency&&e.set("destinationCurrency",this.config.destinationCurrency),this.config.walletAddress&&e.set("walletAddress",this.config.walletAddress),this.config.redirectUrl&&e.set("redirectUrl",this.config.redirectUrl),!1===this.config.autoRedirect&&e.set("autoRedirect","false"),`${this.config.widgetUrl}?${e.toString()}`}createIframe(){this.iframe=document.createElement("iframe"),this.iframe.src=this.buildWidgetUrl(),this.iframe.style.border="none",this.iframe.style.width="100%",this.iframe.style.height="100%",this.iframe.allow="camera *; microphone *; geolocation *; encrypted-media *; clipboard-read; clipboard-write",this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals");const e=this.getContainer();e?e.appendChild(this.iframe):this.createModal()}getContainer(){return this.config.container?"string"==typeof this.config.container?document.querySelector(this.config.container):this.config.container:null}createModal(){this.modalContainer=document.createElement("div"),this.modalContainer.style.cssText="\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n ";const e=document.createElement("div");e.style.cssText="\n position: relative;\n width: 100%;\n max-width: 440px;\n height: 90vh;\n max-height: 700px;\n background: white;\n border-radius: 16px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n ";const t=document.createElement("button");t.innerHTML="&times;",t.style.cssText="\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n width: 32px;\n height: 32px;\n border: none;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n font-size: 20px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n ",t.onclick=()=>this.close(),e.appendChild(t),this.iframe&&e.appendChild(this.iframe),this.modalContainer.appendChild(e),this.modalContainer.onclick=e=>{e.target===this.modalContainer&&this.close()};document.addEventListener("keydown",e=>{"Escape"===e.key&&this.close()}),document.body.appendChild(this.modalContainer),document.body.style.overflow="hidden"}setupMessageListener(){this.boundMessageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.boundMessageHandler)}handleMessage(e){if(!d(e.origin,this.config.widgetUrl))return;const t=e.data;if(t&&"ramp-widget"===t.source)switch(t.type){case"WIDGET_READY":this.config.onReady?.();break;case"WIDGET_CLOSE":this.close();break;case"STEP_CHANGE":this.config.onStepChange?.(t.payload.step);break;case"QUOTE_CREATED":this.config.onQuoteCreated?.(t.payload);break;case"ORDER_CREATED":this.config.onOrderCreated?.(t.payload);break;case"ORDER_COMPLETED":this.config.onSuccess?.(t.payload);break;case"ERROR":this.config.onError?.(t.payload)}}sendCommand(e,t){if(!this.iframe?.contentWindow)return void console.warn("RampWidget: Cannot send command, widget not open");const i=this.config.widgetUrl?new URL(this.config.widgetUrl).origin:"*";this.iframe.contentWindow.postMessage({source:"ramp-sdk",type:e,payload:t},i)}cleanup(){this.boundMessageHandler&&(window.removeEventListener("message",this.boundMessageHandler),this.boundMessageHandler=null),this.iframe?.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.modalContainer?.parentNode&&(this.modalContainer.parentNode.removeChild(this.modalContainer),document.body.style.overflow=""),this.modalContainer=null}}exports.ADDRESS_PATTERNS=e,exports.ALLOWED_PROTOCOLS=i,exports.API_KEY_PREFIXES=r,exports.CURRENCY_CHAIN_MAP=t,exports.DEV_ALLOWED_HOSTNAMES=n,exports.RampWidget=h,exports.VERSION="0.1.0",exports.WIDGET_URLS=c,exports.default=h,exports.validateApiKey=o,exports.validateMessageOrigin=d,exports.validateRedirectUrl=s,exports.validateWalletAddress=a;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * SDK Type Definitions
3
+ */
4
+ type FlowType = 'buy' | 'sell';
5
+ type Theme = 'light' | 'dark';
6
+ type Locale = 'en' | 'es' | 'fr' | 'de';
7
+ type ChainType = 'evm' | 'solana' | 'bitcoin';
8
+ interface QuoteData {
9
+ id: string;
10
+ sourceAmount: number;
11
+ sourceCurrency: string;
12
+ destinationAmount: number;
13
+ destinationCurrency: string;
14
+ rate: number;
15
+ fees: {
16
+ network: number;
17
+ service: number;
18
+ total: number;
19
+ };
20
+ expiresAt: string;
21
+ }
22
+ interface OrderData {
23
+ id: string;
24
+ status: string;
25
+ quoteId: string;
26
+ txHash?: string;
27
+ }
28
+ interface ErrorData {
29
+ error: string;
30
+ }
31
+ interface RampWidgetConfig {
32
+ /** Partner API key (required) */
33
+ apiKey: string;
34
+ /** Project ID (required) */
35
+ projectId: string;
36
+ /** External user identifier from partner system (required) */
37
+ externalUserId: string;
38
+ /** Environment: 'production' (default), 'staging', or 'development' */
39
+ environment?: 'production' | 'staging' | 'development';
40
+ /** Custom widget base URL (overrides environment setting) */
41
+ widgetUrl?: string;
42
+ /** Flow type: buy crypto or sell crypto */
43
+ flowType?: FlowType;
44
+ /** Theme: light or dark */
45
+ theme?: Theme;
46
+ /** Locale for translations (default: 'en') */
47
+ locale?: Locale;
48
+ /** Pre-fill amount */
49
+ amount?: number;
50
+ /** Pre-fill source currency (fiat for buy, crypto for sell) */
51
+ sourceCurrency?: string;
52
+ /** Pre-fill destination currency (crypto for buy, fiat for sell) */
53
+ destinationCurrency?: string;
54
+ /** Pre-fill destination wallet address */
55
+ walletAddress?: string;
56
+ /** URL to redirect to after order completion */
57
+ redirectUrl?: string;
58
+ /** Auto-redirect after completion (default: true), or show button if false */
59
+ autoRedirect?: boolean;
60
+ /** Container element or selector (default: creates modal) */
61
+ container?: HTMLElement | string;
62
+ /** Callback when widget is ready */
63
+ onReady?: () => void;
64
+ /** Callback when step changes */
65
+ onStepChange?: (step: string) => void;
66
+ /** Callback when quote is created */
67
+ onQuoteCreated?: (quote: QuoteData) => void;
68
+ /** Callback when order is created */
69
+ onOrderCreated?: (order: OrderData) => void;
70
+ /** Callback when order is completed successfully */
71
+ onSuccess?: (order: OrderData) => void;
72
+ /** Callback when widget is closed */
73
+ onClose?: () => void;
74
+ /** Callback when an error occurs */
75
+ onError?: (error: ErrorData) => void;
76
+ }
77
+ type WidgetEventType = 'WIDGET_READY' | 'WIDGET_CLOSE' | 'STEP_CHANGE' | 'QUOTE_CREATED' | 'ORDER_CREATED' | 'ORDER_COMPLETED' | 'KYC_STARTED' | 'KYC_COMPLETED' | 'ERROR';
78
+ interface WidgetMessage {
79
+ source: 'ramp-widget';
80
+ type: WidgetEventType;
81
+ payload?: unknown;
82
+ }
83
+ interface ValidationResult {
84
+ valid: boolean;
85
+ error?: string;
86
+ }
87
+
88
+ /**
89
+ * SDK Validation Utilities
90
+ *
91
+ * Security-focused validation for API keys, wallet addresses, and URLs.
92
+ */
93
+
94
+ /**
95
+ * Address validation patterns by chain type
96
+ */
97
+ declare const ADDRESS_PATTERNS: Record<ChainType, {
98
+ pattern: RegExp;
99
+ description: string;
100
+ }>;
101
+ /**
102
+ * Currency to chain type mapping
103
+ */
104
+ declare const CURRENCY_CHAIN_MAP: Record<string, ChainType>;
105
+ /**
106
+ * Allowed protocols for redirect URLs
107
+ */
108
+ declare const ALLOWED_PROTOCOLS: string[];
109
+ /**
110
+ * Hostnames allowed for HTTP (development only)
111
+ */
112
+ declare const DEV_ALLOWED_HOSTNAMES: string[];
113
+ /**
114
+ * API Key prefixes
115
+ * - pk_ = public key (safe for frontend)
116
+ * - sk_ = secret key (backend only - NEVER expose!)
117
+ */
118
+ declare const API_KEY_PREFIXES: {
119
+ readonly public: "pk_";
120
+ readonly secret: "sk_";
121
+ };
122
+ /**
123
+ * Validate API key type and format
124
+ *
125
+ * Ensures secret keys (sk_) are never used in frontend code.
126
+ */
127
+ declare function validateApiKey(apiKey: string): ValidationResult;
128
+ /**
129
+ * Validate redirect URL
130
+ *
131
+ * Only HTTPS URLs are allowed in production.
132
+ * HTTP is allowed for localhost in development.
133
+ */
134
+ declare function validateRedirectUrl(url: string): ValidationResult;
135
+ /**
136
+ * Validate wallet address format
137
+ *
138
+ * Validates against chain-specific patterns if currency is provided,
139
+ * otherwise attempts auto-detection.
140
+ */
141
+ declare function validateWalletAddress(address: string, destinationCurrency?: string): ValidationResult;
142
+ /**
143
+ * Validate message origin against expected widget URL
144
+ *
145
+ * Allows localhost/127.0.0.1 for development.
146
+ */
147
+ declare function validateMessageOrigin(eventOrigin: string, widgetUrl?: string): boolean;
148
+
149
+ /**
150
+ * RampWidget Class
151
+ *
152
+ * Main widget controller that handles iframe creation, messaging, and modal display.
153
+ */
154
+
155
+ /** Widget URLs by environment */
156
+ declare const WIDGET_URLS: {
157
+ readonly production: "https://widget.nowramp.com";
158
+ readonly staging: "https://widget.staging.nowramp.com";
159
+ readonly development: "http://localhost:7482";
160
+ };
161
+ type Environment = keyof typeof WIDGET_URLS;
162
+ declare class RampWidget {
163
+ private config;
164
+ private iframe;
165
+ private modalContainer;
166
+ private boundMessageHandler;
167
+ private isOpen;
168
+ constructor(config: RampWidgetConfig);
169
+ /**
170
+ * Open the widget
171
+ */
172
+ open(): void;
173
+ /**
174
+ * Close the widget
175
+ */
176
+ close(): void;
177
+ /**
178
+ * Set amount programmatically
179
+ */
180
+ setAmount(amount: number): void;
181
+ /**
182
+ * Set theme programmatically
183
+ */
184
+ setTheme(theme: Theme): void;
185
+ /**
186
+ * Refresh the current quote
187
+ */
188
+ refreshQuote(): void;
189
+ private validateConfig;
190
+ private buildWidgetUrl;
191
+ private createIframe;
192
+ private getContainer;
193
+ private createModal;
194
+ private setupMessageListener;
195
+ private handleMessage;
196
+ private sendCommand;
197
+ private cleanup;
198
+ }
199
+
200
+ /**
201
+ * NowRamp Widget SDK
202
+ *
203
+ * Provides a simple API for embedding the NowRamp Widget in partner applications.
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * import { RampWidget } from '@nowramp/sdk';
208
+ *
209
+ * const widget = new RampWidget({
210
+ * apiKey: 'pk_xxx',
211
+ * projectId: 'proj_xxx',
212
+ * externalUserId: 'user_123',
213
+ * onSuccess: (data) => console.log('Order complete:', data),
214
+ * onClose: () => console.log('Widget closed'),
215
+ * });
216
+ *
217
+ * widget.open();
218
+ * ```
219
+ */
220
+ /**
221
+ * SDK version string (e.g., "1.0.0")
222
+ */
223
+ declare const VERSION: string;
224
+
225
+ export { ADDRESS_PATTERNS, ALLOWED_PROTOCOLS, API_KEY_PREFIXES, CURRENCY_CHAIN_MAP, DEV_ALLOWED_HOSTNAMES, RampWidget, VERSION, WIDGET_URLS, RampWidget as default, validateApiKey, validateMessageOrigin, validateRedirectUrl, validateWalletAddress };
226
+ export type { ChainType, Environment, ErrorData, FlowType, Locale, OrderData, QuoteData, RampWidgetConfig, Theme, ValidationResult, WidgetEventType, WidgetMessage };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * @nowramp/sdk v0.1.0
3
+ * (c) 2026 NowRamp
4
+ * @license MIT
5
+ */
6
+ const e={evm:{pattern:/^0x[a-fA-F0-9]{40}$/,description:"EVM address must start with 0x followed by 40 hexadecimal characters"},solana:{pattern:/^[1-9A-HJ-NP-Za-km-z]{32,44}$/,description:"Solana address must be 32-44 base58 characters"},bitcoin:{pattern:/^(1[a-km-zA-HJ-NP-Z1-9]{25,34}|3[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/,description:"Bitcoin address must be a valid legacy, SegWit, or native SegWit format"}},t={ETH:"evm",USDC:"evm",USDT:"evm",DAI:"evm",WETH:"evm",WBTC:"evm",MATIC:"evm",SOL:"solana",BTC:"bitcoin"},i=["https:"],n=["localhost","127.0.0.1"],r={public:"pk_",secret:"sk_"};function o(e){return e?e.includes(r.secret)?{valid:!1,error:"Secret API keys (sk_) cannot be used in the SDK. Use a public key (pk_) instead. Secret keys should only be used in backend server code."}:(e.includes(r.public)||console.warn("RampWidget: API key does not have a public key prefix (pk_). For security, please generate a new public key for frontend use."),{valid:!0}):{valid:!1,error:"apiKey is required"}}function s(e){if(!e)return{valid:!0};try{const t=new URL(e);return i.includes(t.protocol)||"http:"===t.protocol&&n.includes(t.hostname)?{valid:!0}:{valid:!1,error:`redirectUrl must use HTTPS (got ${t.protocol})`}}catch{return{valid:!1,error:"redirectUrl is not a valid URL"}}}function a(i,n){if(!i)return{valid:!0};const r=i.trim();if(n){const i=t[n.toUpperCase()];if(i){const{pattern:t,description:n}=e[i];return t.test(r)?{valid:!0}:{valid:!1,error:n}}}for(const{pattern:t}of Object.values(e))if(t.test(r))return{valid:!0};return r.length>=10&&r.length<=100?{valid:!0}:{valid:!1,error:"walletAddress format is not recognized"}}function d(e,t){if(!t)return!0;try{if(e===new URL(t).origin)return!0;const i=new URL(e);return"localhost"===i.hostname||"127.0.0.1"===i.hostname}catch{return!1}}const c={production:"https://widget.nowramp.com",staging:"https://widget.staging.nowramp.com",development:"http://localhost:7482"},l=c.production;class h{constructor(e){this.iframe=null,this.modalContainer=null,this.boundMessageHandler=null,this.isOpen=!1;const t=e.widgetUrl||(e.environment?c[e.environment]:l);this.config={widgetUrl:t,flowType:"buy",theme:"light",...e},this.validateConfig()}open(){this.isOpen?console.warn("RampWidget: Widget is already open"):(this.isOpen=!0,this.setupMessageListener(),this.createIframe())}close(){this.isOpen&&(this.isOpen=!1,this.cleanup(),this.config.onClose?.())}setAmount(e){this.sendCommand("SET_AMOUNT",e)}setTheme(e){this.sendCommand("SET_THEME",e)}refreshQuote(){this.sendCommand("REFRESH_QUOTE")}validateConfig(){if(!this.config.apiKey)throw new Error("RampWidget: apiKey is required");if(!this.config.projectId)throw new Error("RampWidget: projectId is required");if(!this.config.externalUserId)throw new Error("RampWidget: externalUserId is required");const e=o(this.config.apiKey);if(!e.valid)throw new Error(`RampWidget: ${e.error}`);if(this.config.redirectUrl){const e=s(this.config.redirectUrl);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}if(this.config.walletAddress){const e=a(this.config.walletAddress,this.config.destinationCurrency);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}}buildWidgetUrl(){const e=new URLSearchParams({apiKey:this.config.apiKey,projectId:this.config.projectId,externalUserId:this.config.externalUserId,parentOrigin:window.location.origin,flowType:this.config.flowType||"buy",theme:this.config.theme||"light"});return this.config.locale&&e.set("locale",this.config.locale),void 0!==this.config.amount&&e.set("amount",String(this.config.amount)),this.config.sourceCurrency&&e.set("sourceCurrency",this.config.sourceCurrency),this.config.destinationCurrency&&e.set("destinationCurrency",this.config.destinationCurrency),this.config.walletAddress&&e.set("walletAddress",this.config.walletAddress),this.config.redirectUrl&&e.set("redirectUrl",this.config.redirectUrl),!1===this.config.autoRedirect&&e.set("autoRedirect","false"),`${this.config.widgetUrl}?${e.toString()}`}createIframe(){this.iframe=document.createElement("iframe"),this.iframe.src=this.buildWidgetUrl(),this.iframe.style.border="none",this.iframe.style.width="100%",this.iframe.style.height="100%",this.iframe.allow="camera *; microphone *; geolocation *; encrypted-media *; clipboard-read; clipboard-write",this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals");const e=this.getContainer();e?e.appendChild(this.iframe):this.createModal()}getContainer(){return this.config.container?"string"==typeof this.config.container?document.querySelector(this.config.container):this.config.container:null}createModal(){this.modalContainer=document.createElement("div"),this.modalContainer.style.cssText="\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n ";const e=document.createElement("div");e.style.cssText="\n position: relative;\n width: 100%;\n max-width: 440px;\n height: 90vh;\n max-height: 700px;\n background: white;\n border-radius: 16px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n ";const t=document.createElement("button");t.innerHTML="&times;",t.style.cssText="\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n width: 32px;\n height: 32px;\n border: none;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n font-size: 20px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n ",t.onclick=()=>this.close(),e.appendChild(t),this.iframe&&e.appendChild(this.iframe),this.modalContainer.appendChild(e),this.modalContainer.onclick=e=>{e.target===this.modalContainer&&this.close()};document.addEventListener("keydown",e=>{"Escape"===e.key&&this.close()}),document.body.appendChild(this.modalContainer),document.body.style.overflow="hidden"}setupMessageListener(){this.boundMessageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.boundMessageHandler)}handleMessage(e){if(!d(e.origin,this.config.widgetUrl))return;const t=e.data;if(t&&"ramp-widget"===t.source)switch(t.type){case"WIDGET_READY":this.config.onReady?.();break;case"WIDGET_CLOSE":this.close();break;case"STEP_CHANGE":this.config.onStepChange?.(t.payload.step);break;case"QUOTE_CREATED":this.config.onQuoteCreated?.(t.payload);break;case"ORDER_CREATED":this.config.onOrderCreated?.(t.payload);break;case"ORDER_COMPLETED":this.config.onSuccess?.(t.payload);break;case"ERROR":this.config.onError?.(t.payload)}}sendCommand(e,t){if(!this.iframe?.contentWindow)return void console.warn("RampWidget: Cannot send command, widget not open");const i=this.config.widgetUrl?new URL(this.config.widgetUrl).origin:"*";this.iframe.contentWindow.postMessage({source:"ramp-sdk",type:e,payload:t},i)}cleanup(){this.boundMessageHandler&&(window.removeEventListener("message",this.boundMessageHandler),this.boundMessageHandler=null),this.iframe?.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.modalContainer?.parentNode&&(this.modalContainer.parentNode.removeChild(this.modalContainer),document.body.style.overflow=""),this.modalContainer=null}}const p="0.1.0";export{e as ADDRESS_PATTERNS,i as ALLOWED_PROTOCOLS,r as API_KEY_PREFIXES,t as CURRENCY_CHAIN_MAP,n as DEV_ALLOWED_HOSTNAMES,h as RampWidget,p as VERSION,c as WIDGET_URLS,h as default,o as validateApiKey,d as validateMessageOrigin,s as validateRedirectUrl,a as validateWalletAddress};
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * @nowramp/sdk v0.1.0
3
+ * (c) 2026 NowRamp
4
+ * @license MIT
5
+ */
6
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).NowRamp={})}(this,function(e){"use strict";const t={evm:{pattern:/^0x[a-fA-F0-9]{40}$/,description:"EVM address must start with 0x followed by 40 hexadecimal characters"},solana:{pattern:/^[1-9A-HJ-NP-Za-km-z]{32,44}$/,description:"Solana address must be 32-44 base58 characters"},bitcoin:{pattern:/^(1[a-km-zA-HJ-NP-Z1-9]{25,34}|3[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-zA-HJ-NP-Z0-9]{39,59})$/,description:"Bitcoin address must be a valid legacy, SegWit, or native SegWit format"}},i={ETH:"evm",USDC:"evm",USDT:"evm",DAI:"evm",WETH:"evm",WBTC:"evm",MATIC:"evm",SOL:"solana",BTC:"bitcoin"},n=["https:"],r=["localhost","127.0.0.1"],o={public:"pk_",secret:"sk_"};function s(e){return e?e.includes(o.secret)?{valid:!1,error:"Secret API keys (sk_) cannot be used in the SDK. Use a public key (pk_) instead. Secret keys should only be used in backend server code."}:(e.includes(o.public)||console.warn("RampWidget: API key does not have a public key prefix (pk_). For security, please generate a new public key for frontend use."),{valid:!0}):{valid:!1,error:"apiKey is required"}}function a(e){if(!e)return{valid:!0};try{const t=new URL(e);return n.includes(t.protocol)||"http:"===t.protocol&&r.includes(t.hostname)?{valid:!0}:{valid:!1,error:`redirectUrl must use HTTPS (got ${t.protocol})`}}catch{return{valid:!1,error:"redirectUrl is not a valid URL"}}}function d(e,n){if(!e)return{valid:!0};const r=e.trim();if(n){const e=i[n.toUpperCase()];if(e){const{pattern:i,description:n}=t[e];return i.test(r)?{valid:!0}:{valid:!1,error:n}}}for(const{pattern:e}of Object.values(t))if(e.test(r))return{valid:!0};return r.length>=10&&r.length<=100?{valid:!0}:{valid:!1,error:"walletAddress format is not recognized"}}function c(e,t){if(!t)return!0;try{if(e===new URL(t).origin)return!0;const i=new URL(e);return"localhost"===i.hostname||"127.0.0.1"===i.hostname}catch{return!1}}const l={production:"https://widget.nowramp.com",staging:"https://widget.staging.nowramp.com",development:"http://localhost:7482"},h=l.production;class p{constructor(e){this.iframe=null,this.modalContainer=null,this.boundMessageHandler=null,this.isOpen=!1;const t=e.widgetUrl||(e.environment?l[e.environment]:h);this.config={widgetUrl:t,flowType:"buy",theme:"light",...e},this.validateConfig()}open(){this.isOpen?console.warn("RampWidget: Widget is already open"):(this.isOpen=!0,this.setupMessageListener(),this.createIframe())}close(){this.isOpen&&(this.isOpen=!1,this.cleanup(),this.config.onClose?.())}setAmount(e){this.sendCommand("SET_AMOUNT",e)}setTheme(e){this.sendCommand("SET_THEME",e)}refreshQuote(){this.sendCommand("REFRESH_QUOTE")}validateConfig(){if(!this.config.apiKey)throw new Error("RampWidget: apiKey is required");if(!this.config.projectId)throw new Error("RampWidget: projectId is required");if(!this.config.externalUserId)throw new Error("RampWidget: externalUserId is required");const e=s(this.config.apiKey);if(!e.valid)throw new Error(`RampWidget: ${e.error}`);if(this.config.redirectUrl){const e=a(this.config.redirectUrl);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}if(this.config.walletAddress){const e=d(this.config.walletAddress,this.config.destinationCurrency);if(!e.valid)throw new Error(`RampWidget: ${e.error}`)}}buildWidgetUrl(){const e=new URLSearchParams({apiKey:this.config.apiKey,projectId:this.config.projectId,externalUserId:this.config.externalUserId,parentOrigin:window.location.origin,flowType:this.config.flowType||"buy",theme:this.config.theme||"light"});return this.config.locale&&e.set("locale",this.config.locale),void 0!==this.config.amount&&e.set("amount",String(this.config.amount)),this.config.sourceCurrency&&e.set("sourceCurrency",this.config.sourceCurrency),this.config.destinationCurrency&&e.set("destinationCurrency",this.config.destinationCurrency),this.config.walletAddress&&e.set("walletAddress",this.config.walletAddress),this.config.redirectUrl&&e.set("redirectUrl",this.config.redirectUrl),!1===this.config.autoRedirect&&e.set("autoRedirect","false"),`${this.config.widgetUrl}?${e.toString()}`}createIframe(){this.iframe=document.createElement("iframe"),this.iframe.src=this.buildWidgetUrl(),this.iframe.style.border="none",this.iframe.style.width="100%",this.iframe.style.height="100%",this.iframe.allow="camera *; microphone *; geolocation *; encrypted-media *; clipboard-read; clipboard-write",this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals");const e=this.getContainer();e?e.appendChild(this.iframe):this.createModal()}getContainer(){return this.config.container?"string"==typeof this.config.container?document.querySelector(this.config.container):this.config.container:null}createModal(){this.modalContainer=document.createElement("div"),this.modalContainer.style.cssText="\n position: fixed;\n inset: 0;\n z-index: 9999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n ";const e=document.createElement("div");e.style.cssText="\n position: relative;\n width: 100%;\n max-width: 440px;\n height: 90vh;\n max-height: 700px;\n background: white;\n border-radius: 16px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n ";const t=document.createElement("button");t.innerHTML="&times;",t.style.cssText="\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n width: 32px;\n height: 32px;\n border: none;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n font-size: 20px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n ",t.onclick=()=>this.close(),e.appendChild(t),this.iframe&&e.appendChild(this.iframe),this.modalContainer.appendChild(e),this.modalContainer.onclick=e=>{e.target===this.modalContainer&&this.close()};document.addEventListener("keydown",e=>{"Escape"===e.key&&this.close()}),document.body.appendChild(this.modalContainer),document.body.style.overflow="hidden"}setupMessageListener(){this.boundMessageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.boundMessageHandler)}handleMessage(e){if(!c(e.origin,this.config.widgetUrl))return;const t=e.data;if(t&&"ramp-widget"===t.source)switch(t.type){case"WIDGET_READY":this.config.onReady?.();break;case"WIDGET_CLOSE":this.close();break;case"STEP_CHANGE":this.config.onStepChange?.(t.payload.step);break;case"QUOTE_CREATED":this.config.onQuoteCreated?.(t.payload);break;case"ORDER_CREATED":this.config.onOrderCreated?.(t.payload);break;case"ORDER_COMPLETED":this.config.onSuccess?.(t.payload);break;case"ERROR":this.config.onError?.(t.payload)}}sendCommand(e,t){if(!this.iframe?.contentWindow)return void console.warn("RampWidget: Cannot send command, widget not open");const i=this.config.widgetUrl?new URL(this.config.widgetUrl).origin:"*";this.iframe.contentWindow.postMessage({source:"ramp-sdk",type:e,payload:t},i)}cleanup(){this.boundMessageHandler&&(window.removeEventListener("message",this.boundMessageHandler),this.boundMessageHandler=null),this.iframe?.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.modalContainer?.parentNode&&(this.modalContainer.parentNode.removeChild(this.modalContainer),document.body.style.overflow=""),this.modalContainer=null}}e.ADDRESS_PATTERNS=t,e.ALLOWED_PROTOCOLS=n,e.API_KEY_PREFIXES=o,e.CURRENCY_CHAIN_MAP=i,e.DEV_ALLOWED_HOSTNAMES=r,e.RampWidget=p,e.VERSION="0.1.0",e.WIDGET_URLS=l,e.default=p,e.validateApiKey=s,e.validateMessageOrigin=c,e.validateRedirectUrl=a,e.validateWalletAddress=d,Object.defineProperty(e,"__esModule",{value:!0})});
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@nowramp/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for embedding the NowRamp fiat-to-crypto widget",
5
+ "author": "NowRamp <support@nowramp.com>",
6
+ "license": "MIT",
7
+ "homepage": "https://docs.nowramp.com/sdk",
8
+ "keywords": [
9
+ "crypto",
10
+ "fiat",
11
+ "onramp",
12
+ "offramp",
13
+ "widget",
14
+ "payment"
15
+ ],
16
+ "type": "module",
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "unpkg": "./dist/sdk.umd.js",
21
+ "jsdelivr": "./dist/sdk.umd.js",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs",
26
+ "types": "./dist/index.d.ts"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "build": "rollup -c",
35
+ "build:prod": "NODE_ENV=production rollup -c",
36
+ "dev": "rollup -c -w",
37
+ "clean": "rm -rf dist",
38
+ "prepublishOnly": "npm run build:prod"
39
+ },
40
+ "devDependencies": {
41
+ "@rollup/plugin-json": "^6.1.0",
42
+ "@rollup/plugin-replace": "^5.0.7",
43
+ "@rollup/plugin-terser": "^0.4.4",
44
+ "@rollup/plugin-typescript": "^11.1.6",
45
+ "rollup": "^4.18.0",
46
+ "rollup-plugin-dts": "^6.1.1",
47
+ "tslib": "^2.6.3",
48
+ "typescript": "^5.4.5"
49
+ },
50
+ "engines": {
51
+ "node": ">=18"
52
+ }
53
+ }