@nibgate/sdk 0.2.8 → 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/dist/nibgate.js +151 -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 +4 -4
- package/src/browser/gateway.js +4 -33
- package/src/browser/schemes/batch-scheme.js +161 -0
- package/src/index.d.ts +5 -1
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nibgate/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Framework-agnostic browser and server package for creator-owned gated content, unlock events, and receipts.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"sideEffects": [
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./src/browser/client.js",
|
|
8
|
+
"./src/browser/events.js",
|
|
9
|
+
"./src/browser/index.js"
|
|
10
|
+
],
|
|
7
11
|
"main": "./src/index.js",
|
|
8
12
|
"module": "./src/index.js",
|
|
9
13
|
"types": "./src/index.d.ts",
|
|
@@ -11,7 +11,6 @@ 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';
|
|
15
14
|
const HOSTED_PAY_URL = 'https://api.nibgate.xyz/api/hub/pay';
|
|
16
15
|
|
|
17
16
|
function resolveAccessPath(resource, options) {
|
|
@@ -121,8 +120,7 @@ export function createEvmGatewayUnlock(resource, options = {}) {
|
|
|
121
120
|
address: walletAddress,
|
|
122
121
|
signTypedData: (typedData) => evm.request({ method: 'eth_signTypedData_v4', params: [walletAddress, stringifyJson(typedData)] })
|
|
123
122
|
},
|
|
124
|
-
clientModule: options.circleClientModule
|
|
125
|
-
clientModuleUrl: options.circleClientModuleUrl || DEFAULT_CIRCLE_CDN
|
|
123
|
+
clientModule: options.circleClientModule
|
|
126
124
|
});
|
|
127
125
|
return gatewayWallet.pay(input);
|
|
128
126
|
}
|
|
@@ -139,6 +137,9 @@ export function createEvmGatewayUnlock(resource, options = {}) {
|
|
|
139
137
|
challengeMessage: options.challengeMessage || 'Gateway payment required. Connect your wallet to continue...',
|
|
140
138
|
paymentMessage: options.paymentMessage || 'Approve the Gateway payment proof in your wallet...',
|
|
141
139
|
successMessage: options.successMessage || `Unlocked ${item.resource.title || 'content'}.`,
|
|
140
|
+
method: options.method,
|
|
141
|
+
headers: options.headers,
|
|
142
|
+
body: options.body,
|
|
142
143
|
checkout, onStatus: setStatus
|
|
143
144
|
});
|
|
144
145
|
if (result.ok) {
|
|
@@ -191,7 +192,6 @@ export function createHostedUnlock(resource, options = {}) {
|
|
|
191
192
|
return createEvmGatewayUnlock(resource, {
|
|
192
193
|
...options,
|
|
193
194
|
hosted: true,
|
|
194
|
-
circleClientModuleUrl: options.circleClientModuleUrl || DEFAULT_CIRCLE_CDN,
|
|
195
195
|
noWalletMessage: options.noWalletMessage || 'Install MetaMask or another EVM wallet to unlock premium content.',
|
|
196
196
|
});
|
|
197
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) {
|
|
@@ -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
|
@@ -217,7 +217,6 @@ export type NibgateCircleGatewayBrowserAdapterOptions = {
|
|
|
217
217
|
network?: string;
|
|
218
218
|
chainId?: number | string;
|
|
219
219
|
clientModule?: Record<string, unknown>;
|
|
220
|
-
clientModuleUrl?: string;
|
|
221
220
|
};
|
|
222
221
|
|
|
223
222
|
export type NibgateCircleGatewayBrowserAdapter = {
|
|
@@ -230,10 +229,15 @@ export type NibgateEvmGatewayUnlockOptions = {
|
|
|
230
229
|
source?: string;
|
|
231
230
|
hosted?: boolean;
|
|
232
231
|
accessPath?: string;
|
|
232
|
+
method?: string;
|
|
233
|
+
headers?: Record<string, string>;
|
|
234
|
+
body?: BodyInit | null;
|
|
233
235
|
network?: string;
|
|
234
236
|
paymentProvider?: string;
|
|
235
237
|
circleClientModule?: Record<string, unknown>;
|
|
238
|
+
/** @deprecated The SDK now ships a native BatchEvmScheme — no CDN needed */
|
|
236
239
|
circleClientModuleUrl?: string;
|
|
240
|
+
|
|
237
241
|
provider?: {
|
|
238
242
|
request(args: { method: string; params?: unknown[] }): Promise<unknown>;
|
|
239
243
|
};
|