@nibgate/sdk 0.2.7 → 0.2.9

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.
@@ -11,12 +11,17 @@ export async function createCircleGatewayBrowserAdapter(options = {}) {
11
11
  return gateway.createCircleGatewayBrowserAdapter(options);
12
12
  }
13
13
 
14
- const DEFAULT_CIRCLE_CDN = 'https://esm.sh/@circle-fin/x402-batching@3.2.0/client?bundle';
14
+ const HOSTED_PAY_URL = 'https://api.nibgate.xyz/api/hub/pay';
15
+
16
+ function resolveAccessPath(resource, options) {
17
+ if (options.hosted || options.accessPath === 'hosted') return HOSTED_PAY_URL;
18
+ return options.accessPath || resource.accessPath || '/api/nibgate/access';
19
+ }
15
20
 
16
21
  export function createEvmGatewayUnlock(resource, options = {}) {
17
22
  const item = createGate(resource, options.gateOptions || {});
18
23
  const win = browserWindow();
19
- const accessPath = options.accessPath || item.resource.accessPath || '/api/nibgate/access';
24
+ const accessPath = resolveAccessPath(item.resource, options);
20
25
  const source = options.source || 'nibgate-evm-gateway';
21
26
  const network = options.network || 'eip155:5042002';
22
27
  const statusTarget = typeof options.status === 'string' ? win?.document.querySelector(options.status) : options.status;
@@ -115,8 +120,7 @@ export function createEvmGatewayUnlock(resource, options = {}) {
115
120
  address: walletAddress,
116
121
  signTypedData: (typedData) => evm.request({ method: 'eth_signTypedData_v4', params: [walletAddress, stringifyJson(typedData)] })
117
122
  },
118
- clientModule: options.circleClientModule,
119
- clientModuleUrl: options.circleClientModuleUrl || DEFAULT_CIRCLE_CDN
123
+ clientModule: options.circleClientModule
120
124
  });
121
125
  return gatewayWallet.pay(input);
122
126
  }
@@ -133,6 +137,9 @@ export function createEvmGatewayUnlock(resource, options = {}) {
133
137
  challengeMessage: options.challengeMessage || 'Gateway payment required. Connect your wallet to continue...',
134
138
  paymentMessage: options.paymentMessage || 'Approve the Gateway payment proof in your wallet...',
135
139
  successMessage: options.successMessage || `Unlocked ${item.resource.title || 'content'}.`,
140
+ method: options.method,
141
+ headers: options.headers,
142
+ body: options.body,
136
143
  checkout, onStatus: setStatus
137
144
  });
138
145
  if (result.ok) {
@@ -180,3 +187,11 @@ export function createEvmGatewayUnlock(resource, options = {}) {
180
187
  if (options.autoMount !== false) mount();
181
188
  return controller;
182
189
  }
190
+
191
+ export function createHostedUnlock(resource, options = {}) {
192
+ return createEvmGatewayUnlock(resource, {
193
+ ...options,
194
+ hosted: true,
195
+ noWalletMessage: options.noWalletMessage || 'Install MetaMask or another EVM wallet to unlock premium content.',
196
+ });
197
+ }
@@ -1,35 +1,6 @@
1
+ import { BatchEvmScheme } from './schemes/batch-scheme.js';
1
2
  import { stringifyJson } from './json.js';
2
3
 
3
- let cachedCircleModule = null;
4
-
5
- async function getCircleClient(options = {}) {
6
- if (cachedCircleModule) return cachedCircleModule;
7
-
8
- if (options.clientModule) {
9
- cachedCircleModule = options.clientModule;
10
- return cachedCircleModule;
11
- }
12
-
13
- const moduleUrl = options.clientModuleUrl || '@circle-fin/x402-batching/client';
14
-
15
- try {
16
- cachedCircleModule = await import(moduleUrl);
17
- return cachedCircleModule;
18
- } catch (error) {
19
- if (!options.clientModuleUrl) {
20
- throw new Error(
21
- `Could not load @circle-fin/x402-batching/client from node_modules. ` +
22
- `Provide a clientModuleUrl option (e.g. "https://esm.sh/@circle-fin/x402-batching@3.2.0/client?bundle") ` +
23
- `to load from CDN. Original error: ${error.message}`
24
- );
25
- }
26
- throw new Error(
27
- `Could not load Circle Gateway client from CDN: ${error.message}. ` +
28
- `Check that ${options.clientModuleUrl} is reachable.`
29
- );
30
- }
31
- }
32
-
33
4
  function encodeBase64(value) {
34
5
  const text = typeof value === 'string' ? value : stringifyJson(value);
35
6
  if (typeof Buffer !== 'undefined') return Buffer.from(text).toString('base64');
@@ -47,9 +18,9 @@ export async function createCircleGatewayBrowserAdapter(options = {}) {
47
18
  throw new Error('Circle Gateway browser adapter requires an EVM signer with address and signTypedData.');
48
19
  }
49
20
 
50
- const circleClientModule = await getCircleClient(options);
51
- const { BatchEvmScheme } = circleClientModule;
52
- const scheme = new BatchEvmScheme(signer);
21
+ const scheme = options.clientModule?.BatchEvmScheme
22
+ ? new options.clientModule.BatchEvmScheme(signer)
23
+ : new BatchEvmScheme(signer);
53
24
  const network = options.network || options.chainId && `eip155:${options.chainId}` || 'eip155:5042002';
54
25
 
55
26
  function parsePaymentRequired(input) {
@@ -2,7 +2,7 @@ export { createGate } from './gate.js';
2
2
  export { checkResourceAccess, payWithPaymentSignature, payAndUnlockResource } from './access.js';
3
3
  export { createWalletCheckout } from './checkout.js';
4
4
  export { createNibgate, nibgate } from './client.js';
5
- export { createEvmGatewayUnlock, createCircleGatewayBrowserAdapter } from './evm-gateway.js';
5
+ export { createEvmGatewayUnlock, createHostedUnlock, createCircleGatewayBrowserAdapter } from './evm-gateway.js';
6
6
  export { rateResource, createOnchainRating, mountRatingUI } from './rating-ui.js';
7
7
  export { trackResourcePage, setupResourcePage } from './track.js';
8
8
  export { createTransferCheckout, payWithTransfer } from './transfer.js';
@@ -0,0 +1,161 @@
1
+ export const CIRCLE_BATCHING_NAME = 'GatewayWalletBatched';
2
+ export const CIRCLE_BATCHING_VERSION = '1';
3
+ export const GATEWAY_MIN_AUTH_VALIDITY_SECONDS = 7 * 24 * 60 * 60;
4
+ export const GATEWAY_AUTH_VALIDITY_WINDOW_SECONDS = GATEWAY_MIN_AUTH_VALIDITY_SECONDS + 100;
5
+
6
+ const authorizationTypes = {
7
+ TransferWithAuthorization: [
8
+ { name: 'from', type: 'address' },
9
+ { name: 'to', type: 'address' },
10
+ { name: 'value', type: 'uint256' },
11
+ { name: 'validAfter', type: 'uint256' },
12
+ { name: 'validBefore', type: 'uint256' },
13
+ { name: 'nonce', type: 'bytes32' }
14
+ ]
15
+ };
16
+
17
+ function supportsBatching(requirements) {
18
+ const extra = requirements.extra;
19
+ if (!extra) return false;
20
+ return extra.name === CIRCLE_BATCHING_NAME && extra.version === CIRCLE_BATCHING_VERSION;
21
+ }
22
+
23
+ function getVerifyingContract(requirements) {
24
+ if (!supportsBatching(requirements)) return undefined;
25
+ return requirements.extra?.verifyingContract;
26
+ }
27
+
28
+ function createNonce() {
29
+ const bytes = new Uint8Array(32);
30
+ crypto.getRandomValues(bytes);
31
+ return '0x' + Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
32
+ }
33
+
34
+ function getAddress(value) {
35
+ return value && typeof value === 'string' && value.startsWith('0x') ? value.toLowerCase() : value;
36
+ }
37
+
38
+ export class BatchEvmScheme {
39
+ constructor(signer) {
40
+ this.signer = signer;
41
+ this.scheme = CIRCLE_BATCHING_NAME;
42
+ this._beforeHooks = [];
43
+ this._afterHooks = [];
44
+ this._failureHooks = [];
45
+ }
46
+
47
+ onBeforePaymentCreation(hook) {
48
+ this._beforeHooks.push(hook);
49
+ return this;
50
+ }
51
+
52
+ onAfterPaymentCreation(hook) {
53
+ this._afterHooks.push(hook);
54
+ return this;
55
+ }
56
+
57
+ onPaymentCreationFailure(hook) {
58
+ this._failureHooks.push(hook);
59
+ return this;
60
+ }
61
+
62
+ async createPaymentPayload(x402Version, paymentRequirements) {
63
+ const context = {
64
+ paymentRequired: { x402Version, accepts: [paymentRequirements] },
65
+ selectedRequirements: paymentRequirements
66
+ };
67
+
68
+ for (const hook of this._beforeHooks) {
69
+ const result = await hook(context);
70
+ if (result && result.abort) {
71
+ throw new Error('Payment creation aborted: ' + result.reason);
72
+ }
73
+ }
74
+
75
+ try {
76
+ if (!supportsBatching(paymentRequirements)) {
77
+ throw new Error(
78
+ 'BatchEvmScheme can only handle Circle batching options. Expected extra.name="' +
79
+ CIRCLE_BATCHING_NAME + '" and extra.version="' + CIRCLE_BATCHING_VERSION + '"'
80
+ );
81
+ }
82
+
83
+ const verifyingContract = getVerifyingContract(paymentRequirements);
84
+ if (!verifyingContract) {
85
+ throw new Error(
86
+ 'Circle batching option missing extra.verifyingContract (GatewayWallet address)'
87
+ );
88
+ }
89
+
90
+ const nonce = createNonce();
91
+ const now = Math.floor(Date.now() / 1000);
92
+ const validityWindowSeconds = Math.max(
93
+ paymentRequirements.maxTimeoutSeconds || 0,
94
+ GATEWAY_AUTH_VALIDITY_WINDOW_SECONDS
95
+ );
96
+
97
+ const authorization = {
98
+ from: getAddress(this.signer.address),
99
+ to: getAddress(paymentRequirements.payTo),
100
+ value: paymentRequirements.amount,
101
+ validAfter: String(now - 600),
102
+ validBefore: String(now + validityWindowSeconds),
103
+ nonce
104
+ };
105
+
106
+ const signature = await this.signAuthorization(authorization, paymentRequirements, verifyingContract);
107
+
108
+ const payload = { authorization, signature };
109
+ const result = { x402Version, payload };
110
+
111
+ const createdContext = { ...context, paymentPayload: result };
112
+ for (const hook of this._afterHooks) {
113
+ try { await hook(createdContext); } catch {}
114
+ }
115
+
116
+ return result;
117
+ } catch (error) {
118
+ const failureContext = { ...context, error };
119
+ for (const hook of this._failureHooks) {
120
+ const result = await hook(failureContext);
121
+ if (result && result.recovered) {
122
+ return { x402Version: result.payload.x402Version, payload: result.payload.payload };
123
+ }
124
+ }
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ async signAuthorization(authorization, requirements, verifyingContract) {
130
+ if (!requirements.network.startsWith('eip155:')) {
131
+ throw new Error('BatchEvmScheme: unsupported network format "' + requirements.network + '". Expected "eip155:<chainId>"');
132
+ }
133
+
134
+ const chainId = parseInt(requirements.network.split(':')[1], 10);
135
+
136
+ const domain = {
137
+ name: CIRCLE_BATCHING_NAME,
138
+ version: CIRCLE_BATCHING_VERSION,
139
+ chainId,
140
+ verifyingContract: getAddress(verifyingContract)
141
+ };
142
+
143
+ const message = {
144
+ from: getAddress(authorization.from),
145
+ to: getAddress(authorization.to),
146
+ value: BigInt(authorization.value),
147
+ validAfter: BigInt(authorization.validAfter),
148
+ validBefore: BigInt(authorization.validBefore),
149
+ nonce: authorization.nonce
150
+ };
151
+
152
+ const typedData = {
153
+ domain,
154
+ types: authorizationTypes,
155
+ primaryType: 'TransferWithAuthorization',
156
+ message
157
+ };
158
+
159
+ return this.signer.signTypedData(typedData);
160
+ }
161
+ }
package/src/index.d.ts CHANGED
@@ -112,6 +112,7 @@ export type NibgateClient = {
112
112
  createTransferCheckout(resource: NibgateResource | string, options: NibgateTransferCheckoutOptions): NibgateTransferCheckout;
113
113
  payWithTransfer(resource: NibgateResource | string, options: NibgateTransferCheckoutOptions & NibgateAccessCheckOptions): Promise<NibgateAccessCheckResult>;
114
114
  createEvmGatewayUnlock(resource: NibgateResource | string, options?: NibgateEvmGatewayUnlockOptions): NibgateEvmGatewayUnlockController;
115
+ createHostedUnlock(resource: NibgateResource | string, options?: NibgateEvmGatewayUnlockOptions): NibgateEvmGatewayUnlockController;
115
116
  rateContentOnchain(resource: NibgateResource | string, options: NibgateOnchainRatingOptions): Promise<NibgateOnchainRatingResult>;
116
117
  createOnchainRating(resource: NibgateResource | string, options?: NibgateOnchainRatingUiOptions): NibgateOnchainRatingController;
117
118
  mountRatingUI(resource: NibgateResource | string, options?: NibgateRatingUiOptions): NibgateRatingUiController | null;
@@ -216,7 +217,6 @@ export type NibgateCircleGatewayBrowserAdapterOptions = {
216
217
  network?: string;
217
218
  chainId?: number | string;
218
219
  clientModule?: Record<string, unknown>;
219
- clientModuleUrl?: string;
220
220
  };
221
221
 
222
222
  export type NibgateCircleGatewayBrowserAdapter = {
@@ -227,11 +227,17 @@ export type NibgateCircleGatewayBrowserAdapter = {
227
227
 
228
228
  export type NibgateEvmGatewayUnlockOptions = {
229
229
  source?: string;
230
+ hosted?: boolean;
230
231
  accessPath?: string;
232
+ method?: string;
233
+ headers?: Record<string, string>;
234
+ body?: BodyInit | null;
231
235
  network?: string;
232
236
  paymentProvider?: string;
233
237
  circleClientModule?: Record<string, unknown>;
238
+ /** @deprecated The SDK now ships a native BatchEvmScheme — no CDN needed */
234
239
  circleClientModuleUrl?: string;
240
+
235
241
  provider?: {
236
242
  request(args: { method: string; params?: unknown[] }): Promise<unknown>;
237
243
  };
@@ -414,6 +420,7 @@ export declare function payWithPaymentSignature(resource: NibgateResource | stri
414
420
  export declare function createWalletCheckout(resource: NibgateResource | string, options: NibgateWalletCheckoutOptions): NibgateWalletCheckoutController;
415
421
  export declare function createCircleGatewayBrowserAdapter(options: NibgateCircleGatewayBrowserAdapterOptions): Promise<NibgateCircleGatewayBrowserAdapter>;
416
422
  export declare function createEvmGatewayUnlock(resource: NibgateResource | string, options?: NibgateEvmGatewayUnlockOptions): NibgateEvmGatewayUnlockController;
423
+ export declare function createHostedUnlock(resource: NibgateResource | string, options?: NibgateEvmGatewayUnlockOptions): NibgateEvmGatewayUnlockController;
417
424
  export declare function payAndUnlockResource(resource: NibgateResource | string, options?: NibgatePaymentOptions): Promise<NibgatePaymentResult>;
418
425
  export declare function setupResourcePage(resource: NibgateResource | string, options?: NibgatePageSetupOptions): NibgateGate;
419
426
  export declare function rateResource(resource: NibgateResource | string, rating?: NibgateRating | number, extra?: Record<string, unknown>): boolean;