@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.
- package/SKILL.md +19 -0
- package/dist/nibgate.js +165 -30
- package/dist/nibgate.js.map +3 -3
- package/dist/nibgate.min.js +2 -2
- package/dist/nibgate.min.js.map +4 -4
- package/package.json +6 -2
- package/src/browser/evm-gateway.js +19 -4
- package/src/browser/gateway.js +4 -33
- package/src/browser/index.js +1 -1
- package/src/browser/schemes/batch-scheme.js +161 -0
- package/src/index.d.ts +8 -1
|
@@ -11,12 +11,17 @@ export async function createCircleGatewayBrowserAdapter(options = {}) {
|
|
|
11
11
|
return gateway.createCircleGatewayBrowserAdapter(options);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const
|
|
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 =
|
|
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
|
+
}
|
package/src/browser/gateway.js
CHANGED
|
@@ -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
|
|
51
|
-
|
|
52
|
-
|
|
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) {
|
package/src/browser/index.js
CHANGED
|
@@ -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;
|