@okxweb3/app-x402-core 0.1.2 → 0.2.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/cjs/OKXFacilitatorClient-Bqyw9fzj.d.ts +69 -0
- package/dist/cjs/client/index.d.ts +1 -1
- package/dist/cjs/client/index.js +34 -0
- package/dist/cjs/client/index.js.map +1 -1
- package/dist/cjs/facilitator/index.d.ts +2 -2
- package/dist/cjs/facilitator/index.js +166 -4
- package/dist/cjs/facilitator/index.js.map +1 -1
- package/dist/cjs/http/index.d.ts +5 -3
- package/dist/cjs/http/index.js +1241 -7
- package/dist/cjs/http/index.js.map +1 -1
- package/dist/cjs/index-2gWfiUbK.d.ts +713 -0
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +166 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/{mechanisms-sojpSwWW.d.ts → mechanisms-LhI9qkRo.d.ts} +509 -1
- package/dist/cjs/server/index.d.ts +4 -2
- package/dist/cjs/server/index.js +1256 -7
- package/dist/cjs/server/index.js.map +1 -1
- package/dist/cjs/subscription/index.d.ts +3 -0
- package/dist/cjs/subscription/index.js +600 -0
- package/dist/cjs/subscription/index.js.map +1 -0
- package/dist/cjs/types/index.d.ts +1 -1
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/{x402HTTPResourceServer-CcsAkcgI.d.ts → x402HTTPResourceServer-B0mXzV8r.d.ts} +114 -1
- package/dist/esm/OKXFacilitatorClient-z-cCE5Db.d.mts +69 -0
- package/dist/esm/chunk-4KASWSSY.mjs +257 -0
- package/dist/esm/chunk-4KASWSSY.mjs.map +1 -0
- package/dist/esm/chunk-CKXR4QVD.mjs +274 -0
- package/dist/esm/chunk-CKXR4QVD.mjs.map +1 -0
- package/dist/esm/{chunk-XBQG2CDV.mjs → chunk-EYS4TWVA.mjs} +617 -9
- package/dist/esm/chunk-EYS4TWVA.mjs.map +1 -0
- package/dist/esm/client/index.d.mts +1 -1
- package/dist/esm/client/index.mjs +3 -2
- package/dist/esm/client/index.mjs.map +1 -1
- package/dist/esm/facilitator/index.d.mts +2 -2
- package/dist/esm/facilitator/index.mjs +2 -1
- package/dist/esm/facilitator/index.mjs.map +1 -1
- package/dist/esm/http/index.d.mts +5 -3
- package/dist/esm/http/index.mjs +3 -2
- package/dist/esm/index-DKbqlTu_.d.mts +713 -0
- package/dist/esm/index.d.mts +2 -2
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/{mechanisms-sojpSwWW.d.mts → mechanisms-LhI9qkRo.d.mts} +509 -1
- package/dist/esm/server/index.d.mts +4 -2
- package/dist/esm/server/index.mjs +3 -2
- package/dist/esm/subscription/index.d.mts +3 -0
- package/dist/esm/subscription/index.mjs +309 -0
- package/dist/esm/subscription/index.mjs.map +1 -0
- package/dist/esm/types/index.d.mts +1 -1
- package/dist/esm/utils/index.d.mts +1 -1
- package/dist/esm/{x402HTTPResourceServer-DBeutKxq.d.mts → x402HTTPResourceServer-56Tq3Jup.d.mts} +114 -1
- package/package.json +12 -1
- package/dist/cjs/OKXFacilitatorClient-BvyQB1QM.d.ts +0 -59
- package/dist/esm/OKXFacilitatorClient-D5E3LX50.d.mts +0 -59
- package/dist/esm/chunk-O3IYMTNT.mjs +0 -118
- package/dist/esm/chunk-O3IYMTNT.mjs.map +0 -1
- package/dist/esm/chunk-XBQG2CDV.mjs.map +0 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asSubscriptionPaymentInner,
|
|
3
|
+
parseChainIdFromNetwork
|
|
4
|
+
} from "./chunk-CKXR4QVD.mjs";
|
|
5
|
+
|
|
6
|
+
// src/facilitator/OKXFacilitatorClient.ts
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
var OKXFacilitatorClient = class {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = {
|
|
11
|
+
baseUrl: "https://web3.okx.com",
|
|
12
|
+
...config
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param method
|
|
18
|
+
* @param path
|
|
19
|
+
* @param body
|
|
20
|
+
*/
|
|
21
|
+
createHeaders(method, path, body) {
|
|
22
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
23
|
+
const prehash = timestamp + method + path + (body ?? "");
|
|
24
|
+
const sign = crypto.createHmac("sha256", this.config.secretKey).update(prehash).digest("base64");
|
|
25
|
+
return {
|
|
26
|
+
"OK-ACCESS-KEY": this.config.apiKey,
|
|
27
|
+
"OK-ACCESS-SIGN": sign,
|
|
28
|
+
"OK-ACCESS-TIMESTAMP": timestamp,
|
|
29
|
+
"OK-ACCESS-PASSPHRASE": this.config.passphrase,
|
|
30
|
+
"Content-Type": "application/json"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
async getSupported() {
|
|
37
|
+
const path = "/api/v6/pay/x402/supported";
|
|
38
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
39
|
+
headers: this.createHeaders("GET", path)
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) throw new Error(`OKX getSupported failed: ${res.status}`);
|
|
42
|
+
const json = await res.json();
|
|
43
|
+
const data = json.data ?? json;
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param payload
|
|
49
|
+
* @param requirements
|
|
50
|
+
*/
|
|
51
|
+
async verify(payload, requirements) {
|
|
52
|
+
const path = "/api/v6/pay/x402/verify";
|
|
53
|
+
const body = JSON.stringify({
|
|
54
|
+
x402Version: 2,
|
|
55
|
+
paymentPayload: payload,
|
|
56
|
+
paymentRequirements: requirements
|
|
57
|
+
});
|
|
58
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: this.createHeaders("POST", path, body),
|
|
61
|
+
body
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) throw new Error(`OKX verify failed: ${res.status}`);
|
|
64
|
+
const json = await res.json();
|
|
65
|
+
const data = json.data ?? json;
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param payload
|
|
71
|
+
* @param requirements
|
|
72
|
+
*/
|
|
73
|
+
async settle(payload, requirements) {
|
|
74
|
+
const path = "/api/v6/pay/x402/settle";
|
|
75
|
+
const bodyObj = {
|
|
76
|
+
x402Version: 2,
|
|
77
|
+
paymentPayload: payload,
|
|
78
|
+
paymentRequirements: requirements
|
|
79
|
+
};
|
|
80
|
+
if (this.config.syncSettle !== void 0) {
|
|
81
|
+
bodyObj.syncSettle = this.config.syncSettle;
|
|
82
|
+
}
|
|
83
|
+
const body = JSON.stringify(bodyObj);
|
|
84
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: this.createHeaders("POST", path, body),
|
|
87
|
+
body
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) throw new Error(`OKX settle failed: ${res.status}`);
|
|
90
|
+
const json = await res.json();
|
|
91
|
+
const data = json.data ?? json;
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Query on-chain settlement status by transaction hash.
|
|
96
|
+
*
|
|
97
|
+
* @param txHash - The transaction hash to query
|
|
98
|
+
* @returns Settlement status response
|
|
99
|
+
*/
|
|
100
|
+
async getSettleStatus(txHash) {
|
|
101
|
+
const path = `/api/v6/pay/x402/settle/status?txHash=${encodeURIComponent(txHash)}`;
|
|
102
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
103
|
+
headers: this.createHeaders("GET", path)
|
|
104
|
+
});
|
|
105
|
+
if (!res.ok) throw new Error(`OKX getSettleStatus failed: ${res.status}`);
|
|
106
|
+
const json = await res.json();
|
|
107
|
+
const data = json.data ?? json;
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
// ── SubscriptionFacilitatorClient (period) ─────────────
|
|
111
|
+
//
|
|
112
|
+
// Subscription endpoints share OKX's HMAC-SHA256 auth (createHeaders) so
|
|
113
|
+
// production sellers route ALL facilitator HTTP through this single
|
|
114
|
+
// client — exact/upto/aggr_deferred go via verify/settle, period
|
|
115
|
+
// goes via these five methods.
|
|
116
|
+
/**
|
|
117
|
+
* Build `{chainIndex, terms, permit, termsSig, permitSig, syncSettle}` —
|
|
118
|
+
* request body shared by subscribe / change endpoints.
|
|
119
|
+
*/
|
|
120
|
+
buildWriteBody(payload, requirements, syncSettle) {
|
|
121
|
+
const inner = asSubscriptionPaymentInner(payload);
|
|
122
|
+
return {
|
|
123
|
+
chainIndex: parseChainIdFromNetwork(requirements.network),
|
|
124
|
+
terms: inner.terms,
|
|
125
|
+
permit: inner.permitSingle,
|
|
126
|
+
termsSig: inner.termsSignature,
|
|
127
|
+
permitSig: inner.permitSingleSignature,
|
|
128
|
+
syncSettle: syncSettle ?? true
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async subscribe(paymentPayload, paymentRequirements, syncSettle) {
|
|
132
|
+
const path = "/api/v6/pay/x402/subscriptions";
|
|
133
|
+
const body = JSON.stringify(
|
|
134
|
+
this.buildWriteBody(paymentPayload, paymentRequirements, syncSettle)
|
|
135
|
+
);
|
|
136
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: this.createHeaders("POST", path, body),
|
|
139
|
+
body
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) throw new Error(`OKX subscribe failed: ${res.status}`);
|
|
142
|
+
return await res.json();
|
|
143
|
+
}
|
|
144
|
+
async changeSubscription(paymentPayload, paymentRequirements, oldSubId, syncSettle) {
|
|
145
|
+
const path = "/api/v6/pay/x402/subscriptions/change";
|
|
146
|
+
const inner = asSubscriptionPaymentInner(paymentPayload);
|
|
147
|
+
const body = JSON.stringify({
|
|
148
|
+
chainIndex: parseChainIdFromNetwork(paymentRequirements.network),
|
|
149
|
+
oldSubId,
|
|
150
|
+
newTerms: inner.terms,
|
|
151
|
+
permit: inner.permitSingle,
|
|
152
|
+
termsSig: inner.termsSignature,
|
|
153
|
+
permitSig: inner.permitSingleSignature,
|
|
154
|
+
syncSettle: syncSettle ?? true
|
|
155
|
+
});
|
|
156
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: this.createHeaders("POST", path, body),
|
|
159
|
+
body
|
|
160
|
+
});
|
|
161
|
+
if (!res.ok) throw new Error(`OKX changeSubscription failed: ${res.status}`);
|
|
162
|
+
return await res.json();
|
|
163
|
+
}
|
|
164
|
+
async cancelSubscription(subId, cancelAuth, syncSettle) {
|
|
165
|
+
const path = "/api/v6/pay/x402/subscriptions/cancel";
|
|
166
|
+
const body = JSON.stringify({ subId, cancelAuth, syncSettle: syncSettle ?? true });
|
|
167
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: this.createHeaders("POST", path, body),
|
|
170
|
+
body
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok) throw new Error(`OKX cancelSubscription failed: ${res.status}`);
|
|
173
|
+
return await res.json();
|
|
174
|
+
}
|
|
175
|
+
async cancelPendingChange(subId, cancelAuth, syncSettle) {
|
|
176
|
+
const path = "/api/v6/pay/x402/subscriptions/cancel-pending-change";
|
|
177
|
+
const body = JSON.stringify({ subId, cancelAuth, syncSettle: syncSettle ?? true });
|
|
178
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: this.createHeaders("POST", path, body),
|
|
181
|
+
body
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok) throw new Error(`OKX cancelPendingChange failed: ${res.status}`);
|
|
184
|
+
return await res.json();
|
|
185
|
+
}
|
|
186
|
+
async chargeSubscription(subId, syncSettle) {
|
|
187
|
+
const path = "/api/v6/pay/x402/subscriptions/charge";
|
|
188
|
+
const body = JSON.stringify({ subId, syncSettle: syncSettle ?? true });
|
|
189
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: this.createHeaders("POST", path, body),
|
|
192
|
+
body
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const errBody = await res.text().catch(() => "");
|
|
196
|
+
throw new Error(`OKX chargeSubscription failed: ${res.status} ${errBody}`);
|
|
197
|
+
}
|
|
198
|
+
return await res.json();
|
|
199
|
+
}
|
|
200
|
+
async finalizeExpired(subId, syncSettle) {
|
|
201
|
+
const path = "/api/v6/pay/x402/subscriptions/finalize-expired";
|
|
202
|
+
const body = JSON.stringify({ subId, syncSettle: syncSettle ?? true });
|
|
203
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: this.createHeaders("POST", path, body),
|
|
206
|
+
body
|
|
207
|
+
});
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
const errBody = await res.text().catch(() => "");
|
|
210
|
+
throw new Error(`OKX finalizeExpired failed: ${res.status} ${errBody}`);
|
|
211
|
+
}
|
|
212
|
+
return await res.json();
|
|
213
|
+
}
|
|
214
|
+
async getCharges(subId, limit = 50, offset = 0) {
|
|
215
|
+
const q = new URLSearchParams({ subId, limit: String(limit), offset: String(offset) });
|
|
216
|
+
const path = `/api/v6/pay/x402/subscriptions/charges?${q.toString()}`;
|
|
217
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
218
|
+
headers: this.createHeaders("GET", path)
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
const errBody = await res.text().catch(() => "");
|
|
222
|
+
throw new Error(`OKX getCharges failed: ${res.status} ${errBody}`);
|
|
223
|
+
}
|
|
224
|
+
return await res.json();
|
|
225
|
+
}
|
|
226
|
+
async getPendingChange(subId) {
|
|
227
|
+
const path = `/api/v6/pay/x402/subscriptions/pending?subId=${encodeURIComponent(subId)}`;
|
|
228
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
229
|
+
headers: this.createHeaders("GET", path)
|
|
230
|
+
});
|
|
231
|
+
if (!res.ok) {
|
|
232
|
+
const errBody = await res.text().catch(() => "");
|
|
233
|
+
throw new Error(`OKX getPendingChange failed: ${res.status} ${errBody}`);
|
|
234
|
+
}
|
|
235
|
+
return await res.json();
|
|
236
|
+
}
|
|
237
|
+
async getSubscription(subId) {
|
|
238
|
+
const path = `/api/v6/pay/x402/subscriptions/detail?subId=${encodeURIComponent(subId)}`;
|
|
239
|
+
const res = await fetch(this.config.baseUrl + path, {
|
|
240
|
+
headers: this.createHeaders("GET", path)
|
|
241
|
+
});
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
const body = await res.text().catch(() => "");
|
|
244
|
+
throw new Error(`OKX getSubscription failed: ${res.status} ${body}`);
|
|
245
|
+
}
|
|
246
|
+
return await res.json();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/index.ts
|
|
251
|
+
var x402Version = 2;
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
OKXFacilitatorClient,
|
|
255
|
+
x402Version
|
|
256
|
+
};
|
|
257
|
+
//# sourceMappingURL=chunk-4KASWSSY.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/facilitator/OKXFacilitatorClient.ts","../../src/index.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport type { FacilitatorClient } from \"../http/httpFacilitatorClient.js\";\nimport type {\n FacilitatorCancelData,\n FacilitatorCancelPendingData,\n FacilitatorChangeData,\n FacilitatorChargeData,\n FacilitatorEnvelope,\n FacilitatorFinalizeExpiredData,\n FacilitatorGetChargesData,\n FacilitatorGetSubscriptionData,\n FacilitatorPendingChangeRow,\n FacilitatorSubscribeData,\n} from \"../subscription/facilitator-client.js\";\nimport { parseChainIdFromNetwork } from \"../subscription/codec/typed-data.js\";\nimport { asSubscriptionPaymentInner } from \"../subscription/codec/payload.js\";\nimport type { CancelAuth, PendingChangeCancelAuth } from \"../subscription/types.js\";\nimport type {\n VerifyResponse,\n SettleResponse,\n SettleStatusResponse,\n SupportedResponse,\n} from \"../types/facilitator.js\";\nimport type { PaymentPayload, PaymentRequirements } from \"../types/payments.js\";\n\nexport interface OKXConfig {\n apiKey: string;\n secretKey: string;\n passphrase: string;\n baseUrl?: string;\n /**\n * OKX exact-scheme extension: when true, the settle call tells the facilitator to\n * wait for on-chain confirmation before responding (syncSettle=true in request body).\n * The facilitator then returns status=\"success\" directly (no polling needed).\n * When false (default), the facilitator responds with status=\"pending\" immediately.\n */\n syncSettle?: boolean;\n}\n\n/**\n * OKX facilitator client implementing the FacilitatorClient interface.\n * Uses HMAC-SHA256 signing per OKX REST API authentication spec.\n */\nexport class OKXFacilitatorClient implements FacilitatorClient {\n private config: Required<Omit<OKXConfig, \"syncSettle\">> & Pick<OKXConfig, \"syncSettle\">;\n\n constructor(config: OKXConfig) {\n this.config = {\n baseUrl: \"https://web3.okx.com\",\n ...config,\n };\n }\n\n /**\n *\n * @param method\n * @param path\n * @param body\n */\n private createHeaders(method: string, path: string, body?: string): Record<string, string> {\n const timestamp = new Date().toISOString();\n const prehash = timestamp + method + path + (body ?? \"\");\n const sign = crypto\n .createHmac(\"sha256\", this.config.secretKey)\n .update(prehash)\n .digest(\"base64\");\n\n return {\n \"OK-ACCESS-KEY\": this.config.apiKey,\n \"OK-ACCESS-SIGN\": sign,\n \"OK-ACCESS-TIMESTAMP\": timestamp,\n \"OK-ACCESS-PASSPHRASE\": this.config.passphrase,\n \"Content-Type\": \"application/json\",\n };\n }\n\n /**\n *\n */\n async getSupported(): Promise<SupportedResponse> {\n const path = \"/api/v6/pay/x402/supported\";\n const res = await fetch(this.config.baseUrl + path, {\n headers: this.createHeaders(\"GET\", path),\n });\n if (!res.ok) throw new Error(`OKX getSupported failed: ${res.status}`);\n const json = (await res.json()) as Record<string, unknown>;\n // OKX API wraps responses in { code, data, msg } envelope\n const data = (json.data ?? json) as SupportedResponse;\n return data;\n }\n\n /**\n *\n * @param payload\n * @param requirements\n */\n async verify(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n ): Promise<VerifyResponse> {\n const path = \"/api/v6/pay/x402/verify\";\n const body = JSON.stringify({\n x402Version: 2,\n paymentPayload: payload,\n paymentRequirements: requirements,\n });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX verify failed: ${res.status}`);\n const json = (await res.json()) as Record<string, unknown>;\n const data = (json.data ?? json) as VerifyResponse;\n return data;\n }\n\n /**\n *\n * @param payload\n * @param requirements\n */\n async settle(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n ): Promise<SettleResponse> {\n const path = \"/api/v6/pay/x402/settle\";\n // Include OKX-extension syncSettle field when configured:\n // syncSettle=true → facilitator waits for on-chain confirmation, returns status=\"success\"\n // syncSettle=false → facilitator returns immediately with status=\"pending\" (default)\n const bodyObj: Record<string, unknown> = {\n x402Version: 2,\n paymentPayload: payload,\n paymentRequirements: requirements,\n };\n if (this.config.syncSettle !== undefined) {\n bodyObj.syncSettle = this.config.syncSettle;\n }\n const body = JSON.stringify(bodyObj);\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX settle failed: ${res.status}`);\n const json = (await res.json()) as Record<string, unknown>;\n const data = (json.data ?? json) as SettleResponse;\n return data;\n }\n\n /**\n * Query on-chain settlement status by transaction hash.\n *\n * @param txHash - The transaction hash to query\n * @returns Settlement status response\n */\n async getSettleStatus(txHash: string): Promise<SettleStatusResponse> {\n const path = `/api/v6/pay/x402/settle/status?txHash=${encodeURIComponent(txHash)}`;\n const res = await fetch(this.config.baseUrl + path, {\n headers: this.createHeaders(\"GET\", path),\n });\n if (!res.ok) throw new Error(`OKX getSettleStatus failed: ${res.status}`);\n const json = (await res.json()) as Record<string, unknown>;\n const data = (json.data ?? json) as SettleStatusResponse;\n return data;\n }\n\n // ── SubscriptionFacilitatorClient (period) ─────────────\n //\n // Subscription endpoints share OKX's HMAC-SHA256 auth (createHeaders) so\n // production sellers route ALL facilitator HTTP through this single\n // client — exact/upto/aggr_deferred go via verify/settle, period\n // goes via these five methods.\n\n /**\n * Build `{chainIndex, terms, permit, termsSig, permitSig, syncSettle}` —\n * request body shared by subscribe / change endpoints.\n */\n private buildWriteBody(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n syncSettle?: boolean,\n ): Record<string, unknown> {\n const inner = asSubscriptionPaymentInner(payload);\n return {\n chainIndex: parseChainIdFromNetwork(requirements.network),\n terms: inner.terms,\n permit: inner.permitSingle,\n termsSig: inner.termsSignature,\n permitSig: inner.permitSingleSignature,\n syncSettle: syncSettle ?? true,\n };\n }\n\n async subscribe(\n paymentPayload: PaymentPayload,\n paymentRequirements: PaymentRequirements,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorSubscribeData>> {\n const path = \"/api/v6/pay/x402/subscriptions\";\n const body = JSON.stringify(\n this.buildWriteBody(paymentPayload, paymentRequirements, syncSettle),\n );\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX subscribe failed: ${res.status}`);\n return (await res.json()) as FacilitatorEnvelope<FacilitatorSubscribeData>;\n }\n\n async changeSubscription(\n paymentPayload: PaymentPayload,\n paymentRequirements: PaymentRequirements,\n oldSubId: string,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorChangeData>> {\n const path = \"/api/v6/pay/x402/subscriptions/change\";\n const inner = asSubscriptionPaymentInner(paymentPayload);\n const body = JSON.stringify({\n chainIndex: parseChainIdFromNetwork(paymentRequirements.network),\n oldSubId,\n newTerms: inner.terms,\n permit: inner.permitSingle,\n termsSig: inner.termsSignature,\n permitSig: inner.permitSingleSignature,\n syncSettle: syncSettle ?? true,\n });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX changeSubscription failed: ${res.status}`);\n return (await res.json()) as FacilitatorEnvelope<FacilitatorChangeData>;\n }\n\n async cancelSubscription(\n subId: string,\n cancelAuth: CancelAuth,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorCancelData>> {\n const path = \"/api/v6/pay/x402/subscriptions/cancel\";\n const body = JSON.stringify({ subId, cancelAuth, syncSettle: syncSettle ?? true });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX cancelSubscription failed: ${res.status}`);\n return (await res.json()) as FacilitatorEnvelope<FacilitatorCancelData>;\n }\n\n async cancelPendingChange(\n subId: string,\n cancelAuth: PendingChangeCancelAuth,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorCancelPendingData>> {\n const path = \"/api/v6/pay/x402/subscriptions/cancel-pending-change\";\n const body = JSON.stringify({ subId, cancelAuth, syncSettle: syncSettle ?? true });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) throw new Error(`OKX cancelPendingChange failed: ${res.status}`);\n return (await res.json()) as FacilitatorEnvelope<FacilitatorCancelPendingData>;\n }\n\n async chargeSubscription(\n subId: string,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorChargeData>> {\n const path = \"/api/v6/pay/x402/subscriptions/charge\";\n const body = JSON.stringify({ subId, syncSettle: syncSettle ?? true });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\");\n throw new Error(`OKX chargeSubscription failed: ${res.status} ${errBody}`);\n }\n return (await res.json()) as FacilitatorEnvelope<FacilitatorChargeData>;\n }\n\n async finalizeExpired(\n subId: string,\n syncSettle?: boolean,\n ): Promise<FacilitatorEnvelope<FacilitatorFinalizeExpiredData>> {\n const path = \"/api/v6/pay/x402/subscriptions/finalize-expired\";\n const body = JSON.stringify({ subId, syncSettle: syncSettle ?? true });\n const res = await fetch(this.config.baseUrl + path, {\n method: \"POST\",\n headers: this.createHeaders(\"POST\", path, body),\n body,\n });\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\");\n throw new Error(`OKX finalizeExpired failed: ${res.status} ${errBody}`);\n }\n return (await res.json()) as FacilitatorEnvelope<FacilitatorFinalizeExpiredData>;\n }\n\n async getCharges(\n subId: string,\n limit = 50,\n offset = 0,\n ): Promise<FacilitatorEnvelope<FacilitatorGetChargesData>> {\n const q = new URLSearchParams({ subId, limit: String(limit), offset: String(offset) });\n const path = `/api/v6/pay/x402/subscriptions/charges?${q.toString()}`;\n const res = await fetch(this.config.baseUrl + path, {\n headers: this.createHeaders(\"GET\", path),\n });\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\");\n throw new Error(`OKX getCharges failed: ${res.status} ${errBody}`);\n }\n return (await res.json()) as FacilitatorEnvelope<FacilitatorGetChargesData>;\n }\n\n async getPendingChange(\n subId: string,\n ): Promise<FacilitatorEnvelope<FacilitatorPendingChangeRow | null>> {\n const path = `/api/v6/pay/x402/subscriptions/pending?subId=${encodeURIComponent(subId)}`;\n const res = await fetch(this.config.baseUrl + path, {\n headers: this.createHeaders(\"GET\", path),\n });\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\");\n throw new Error(`OKX getPendingChange failed: ${res.status} ${errBody}`);\n }\n return (await res.json()) as FacilitatorEnvelope<FacilitatorPendingChangeRow | null>;\n }\n\n async getSubscription(\n subId: string,\n ): Promise<FacilitatorEnvelope<FacilitatorGetSubscriptionData>> {\n const path = `/api/v6/pay/x402/subscriptions/detail?subId=${encodeURIComponent(subId)}`;\n const res = await fetch(this.config.baseUrl + path, {\n headers: this.createHeaders(\"GET\", path),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`OKX getSubscription failed: ${res.status} ${body}`);\n }\n return (await res.json()) as FacilitatorEnvelope<FacilitatorGetSubscriptionData>;\n }\n}\n","export const x402Version = 2;\nexport { OKXFacilitatorClient } from \"./facilitator/OKXFacilitatorClient\";\nexport type { OKXConfig } from \"./facilitator/OKXFacilitatorClient\";"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/subscription/codec/typed-data.ts
|
|
2
|
+
import { encodeAbiParameters, keccak256 } from "viem";
|
|
3
|
+
var ZERO_BYTES32 = `0x${"0".repeat(64)}`;
|
|
4
|
+
var PERMIT2_TYPES = {
|
|
5
|
+
PermitSingle: [
|
|
6
|
+
{ name: "details", type: "PermitDetails" },
|
|
7
|
+
{ name: "spender", type: "address" },
|
|
8
|
+
{ name: "sigDeadline", type: "uint256" }
|
|
9
|
+
],
|
|
10
|
+
PermitDetails: [
|
|
11
|
+
{ name: "token", type: "address" },
|
|
12
|
+
{ name: "amount", type: "uint160" },
|
|
13
|
+
{ name: "expiration", type: "uint48" },
|
|
14
|
+
{ name: "nonce", type: "uint48" }
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
var SUBSCRIPTION_TERMS_TYPES = {
|
|
18
|
+
SubscriptionTerms: [
|
|
19
|
+
{ name: "payer", type: "address" },
|
|
20
|
+
{ name: "merchant", type: "address" },
|
|
21
|
+
{ name: "facilitator", type: "address" },
|
|
22
|
+
{ name: "token", type: "address" },
|
|
23
|
+
{ name: "amountPerPeriod", type: "uint160" },
|
|
24
|
+
{ name: "periodSec", type: "uint64" },
|
|
25
|
+
{ name: "maxPeriods", type: "uint32" },
|
|
26
|
+
{ name: "startAt", type: "uint64" },
|
|
27
|
+
{ name: "initialChargePeriods", type: "uint32" },
|
|
28
|
+
{ name: "initialChargeAmount", type: "uint160" },
|
|
29
|
+
{ name: "termsDeadline", type: "uint64" },
|
|
30
|
+
{ name: "permitHash", type: "bytes32" },
|
|
31
|
+
{ name: "salt", type: "bytes32" },
|
|
32
|
+
{ name: "planTier", type: "uint8" },
|
|
33
|
+
{ name: "changeFromSubId", type: "bytes32" },
|
|
34
|
+
{ name: "changeEffectiveAt", type: "uint8" },
|
|
35
|
+
{ name: "periodMode", type: "uint8" }
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
var CANCEL_AUTH_TYPES = {
|
|
39
|
+
CancelAuth: [
|
|
40
|
+
{ name: "action", type: "uint8" },
|
|
41
|
+
{ name: "subId", type: "bytes32" },
|
|
42
|
+
{ name: "initiator", type: "uint8" },
|
|
43
|
+
{ name: "nonce", type: "bytes32" },
|
|
44
|
+
{ name: "deadline", type: "uint64" }
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
var PENDING_CHANGE_CANCEL_AUTH_TYPES = {
|
|
48
|
+
PendingChangeCancelAuth: [
|
|
49
|
+
{ name: "subId", type: "bytes32" },
|
|
50
|
+
{ name: "newSubId", type: "bytes32" },
|
|
51
|
+
{ name: "nonce", type: "bytes32" },
|
|
52
|
+
{ name: "deadline", type: "uint64" }
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
var CANCEL_INITIATOR_TO_ENUM = {
|
|
56
|
+
payer: 0,
|
|
57
|
+
merchant: 1
|
|
58
|
+
};
|
|
59
|
+
function getSubscriptionExtra(req) {
|
|
60
|
+
const extra = req.extra;
|
|
61
|
+
if (!extra || !extra.contracts || !extra.plan || !extra.domain) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"subscription codec: PaymentRequirements.extra is missing contracts/plan/domain"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return extra;
|
|
67
|
+
}
|
|
68
|
+
function defaultPermitAmount(extra) {
|
|
69
|
+
const initialChargePeriods = BigInt(extra.initialCharge?.periodCount ?? 0);
|
|
70
|
+
const initialChargeAmount = BigInt(extra.initialCharge?.totalAmount ?? "0");
|
|
71
|
+
const remainingPeriods = BigInt(extra.maxPeriods) - initialChargePeriods;
|
|
72
|
+
const remainingAmount = remainingPeriods > 0n ? remainingPeriods * BigInt(extra.amountPerPeriod) : 0n;
|
|
73
|
+
return (initialChargeAmount + remainingAmount).toString();
|
|
74
|
+
}
|
|
75
|
+
function buildPermit2TypedData(input) {
|
|
76
|
+
const extra = getSubscriptionExtra(input.selected);
|
|
77
|
+
const amount = input.amount ?? defaultPermitAmount(extra);
|
|
78
|
+
const chainId = parseChainIdFromNetwork(input.selected.network);
|
|
79
|
+
return {
|
|
80
|
+
domain: {
|
|
81
|
+
name: "Permit2",
|
|
82
|
+
chainId,
|
|
83
|
+
verifyingContract: extra.contracts.permit2
|
|
84
|
+
},
|
|
85
|
+
types: PERMIT2_TYPES,
|
|
86
|
+
primaryType: "PermitSingle",
|
|
87
|
+
message: {
|
|
88
|
+
details: {
|
|
89
|
+
token: input.selected.asset,
|
|
90
|
+
amount,
|
|
91
|
+
expiration: input.expiration,
|
|
92
|
+
nonce: input.nonce
|
|
93
|
+
},
|
|
94
|
+
spender: extra.contracts.subscription,
|
|
95
|
+
sigDeadline: input.sigDeadline
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function computePermitSingleStructHash(permit) {
|
|
100
|
+
const PERMIT_DETAILS_TYPEHASH = keccak256(
|
|
101
|
+
new TextEncoder().encode(
|
|
102
|
+
"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
const detailsHash = keccak256(
|
|
106
|
+
encodeAbiParameters(
|
|
107
|
+
[
|
|
108
|
+
{ type: "bytes32" },
|
|
109
|
+
{ type: "address" },
|
|
110
|
+
{ type: "uint160" },
|
|
111
|
+
{ type: "uint48" },
|
|
112
|
+
{ type: "uint48" }
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
PERMIT_DETAILS_TYPEHASH,
|
|
116
|
+
permit.details.token,
|
|
117
|
+
BigInt(permit.details.amount),
|
|
118
|
+
Number(permit.details.expiration),
|
|
119
|
+
Number(permit.details.nonce)
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
const PERMIT_SINGLE_TYPEHASH = keccak256(
|
|
124
|
+
new TextEncoder().encode(
|
|
125
|
+
"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
return keccak256(
|
|
129
|
+
encodeAbiParameters(
|
|
130
|
+
[{ type: "bytes32" }, { type: "bytes32" }, { type: "address" }, { type: "uint256" }],
|
|
131
|
+
[PERMIT_SINGLE_TYPEHASH, detailsHash, permit.spender, BigInt(permit.sigDeadline)]
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function buildSubscriptionTermsTypedData(input) {
|
|
136
|
+
const extra = getSubscriptionExtra(input.selected);
|
|
137
|
+
const domain = input.domain ?? extra.domain;
|
|
138
|
+
let changeEffectiveAt = 0;
|
|
139
|
+
if (input.changeFrom?.effectiveAt === "immediate") changeEffectiveAt = 1;
|
|
140
|
+
else if (input.changeFrom?.effectiveAt === "period_end") changeEffectiveAt = 2;
|
|
141
|
+
const message = {
|
|
142
|
+
payer: input.payer,
|
|
143
|
+
merchant: input.selected.payTo,
|
|
144
|
+
facilitator: extra.facilitator,
|
|
145
|
+
token: input.selected.asset,
|
|
146
|
+
amountPerPeriod: extra.amountPerPeriod,
|
|
147
|
+
periodSec: extra.periodSec,
|
|
148
|
+
maxPeriods: extra.maxPeriods,
|
|
149
|
+
startAt: input.startAt,
|
|
150
|
+
initialChargePeriods: extra.initialCharge?.periodCount ?? 0,
|
|
151
|
+
initialChargeAmount: extra.initialCharge?.totalAmount ?? "0",
|
|
152
|
+
termsDeadline: input.termsDeadline,
|
|
153
|
+
permitHash: input.permitHash,
|
|
154
|
+
salt: input.salt,
|
|
155
|
+
planTier: extra.plan.tier,
|
|
156
|
+
changeFromSubId: input.changeFrom?.fromSubId ?? ZERO_BYTES32,
|
|
157
|
+
changeEffectiveAt,
|
|
158
|
+
periodMode: extra.periodMode ?? 0
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
domain,
|
|
162
|
+
types: SUBSCRIPTION_TERMS_TYPES,
|
|
163
|
+
primaryType: "SubscriptionTerms",
|
|
164
|
+
message
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function buildCancelAuthTypedData(input) {
|
|
168
|
+
return {
|
|
169
|
+
domain: input.domain,
|
|
170
|
+
types: CANCEL_AUTH_TYPES,
|
|
171
|
+
primaryType: "CancelAuth",
|
|
172
|
+
message: {
|
|
173
|
+
action: 0,
|
|
174
|
+
subId: input.subId,
|
|
175
|
+
initiator: CANCEL_INITIATOR_TO_ENUM[input.initiator],
|
|
176
|
+
nonce: input.nonce,
|
|
177
|
+
deadline: input.deadline
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function buildPendingChangeCancelAuthTypedData(input) {
|
|
182
|
+
return {
|
|
183
|
+
domain: input.domain,
|
|
184
|
+
types: PENDING_CHANGE_CANCEL_AUTH_TYPES,
|
|
185
|
+
primaryType: "PendingChangeCancelAuth",
|
|
186
|
+
message: {
|
|
187
|
+
subId: input.subId,
|
|
188
|
+
newSubId: input.newSubId,
|
|
189
|
+
nonce: input.nonce,
|
|
190
|
+
deadline: input.deadline
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function parseChainIdFromNetwork(network) {
|
|
195
|
+
const parts = network.split(":");
|
|
196
|
+
if (parts.length !== 2 || parts[0] !== "eip155") {
|
|
197
|
+
throw new Error(`parseChainIdFromNetwork: expected "eip155:<chainId>", got "${network}"`);
|
|
198
|
+
}
|
|
199
|
+
const id = Number(parts[1]);
|
|
200
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
201
|
+
throw new Error(`parseChainIdFromNetwork: invalid chainId "${parts[1]}"`);
|
|
202
|
+
}
|
|
203
|
+
return id;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/subscription/codec/base64.ts
|
|
207
|
+
var hasBuffer = typeof Buffer !== "undefined";
|
|
208
|
+
function base64EncodeUtf8(value) {
|
|
209
|
+
if (hasBuffer) return Buffer.from(value, "utf8").toString("base64");
|
|
210
|
+
const binary = unescape(encodeURIComponent(value));
|
|
211
|
+
return globalThis.btoa(binary);
|
|
212
|
+
}
|
|
213
|
+
function base64DecodeUtf8(value) {
|
|
214
|
+
if (hasBuffer) return Buffer.from(value, "base64").toString("utf8");
|
|
215
|
+
const binary = globalThis.atob(value);
|
|
216
|
+
return decodeURIComponent(escape(binary));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/subscription/codec/payload.ts
|
|
220
|
+
function parsePaymentRequired(headerValue) {
|
|
221
|
+
const json = base64DecodeUtf8(headerValue);
|
|
222
|
+
const parsed = JSON.parse(json);
|
|
223
|
+
if (!parsed || !Array.isArray(parsed.accepts)) {
|
|
224
|
+
throw new Error("parsePaymentRequired: missing or invalid `accepts` array");
|
|
225
|
+
}
|
|
226
|
+
return parsed.accepts;
|
|
227
|
+
}
|
|
228
|
+
function encodePaymentPayload(input) {
|
|
229
|
+
const payload = {
|
|
230
|
+
x402Version: 2,
|
|
231
|
+
accepted: input.selected,
|
|
232
|
+
payload: {
|
|
233
|
+
permitSingle: input.permitSingle,
|
|
234
|
+
permitSingleSignature: input.permitSingleSignature,
|
|
235
|
+
terms: input.terms,
|
|
236
|
+
termsSignature: input.termsSignature
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return base64EncodeUtf8(JSON.stringify(payload));
|
|
240
|
+
}
|
|
241
|
+
function decodePaymentPayload(headerValue) {
|
|
242
|
+
const json = base64DecodeUtf8(headerValue);
|
|
243
|
+
return JSON.parse(json);
|
|
244
|
+
}
|
|
245
|
+
function asSubscriptionPaymentInner(payload) {
|
|
246
|
+
const inner = payload.payload;
|
|
247
|
+
if (!inner || !inner.permitSingle || !inner.terms || !inner.permitSingleSignature || !inner.termsSignature) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
"asSubscriptionPaymentInner: payload.payload is missing required permitSingle/terms fields"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return inner;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export {
|
|
256
|
+
ZERO_BYTES32,
|
|
257
|
+
PERMIT2_TYPES,
|
|
258
|
+
SUBSCRIPTION_TERMS_TYPES,
|
|
259
|
+
CANCEL_AUTH_TYPES,
|
|
260
|
+
PENDING_CHANGE_CANCEL_AUTH_TYPES,
|
|
261
|
+
buildPermit2TypedData,
|
|
262
|
+
computePermitSingleStructHash,
|
|
263
|
+
buildSubscriptionTermsTypedData,
|
|
264
|
+
buildCancelAuthTypedData,
|
|
265
|
+
buildPendingChangeCancelAuthTypedData,
|
|
266
|
+
parseChainIdFromNetwork,
|
|
267
|
+
base64EncodeUtf8,
|
|
268
|
+
base64DecodeUtf8,
|
|
269
|
+
parsePaymentRequired,
|
|
270
|
+
encodePaymentPayload,
|
|
271
|
+
decodePaymentPayload,
|
|
272
|
+
asSubscriptionPaymentInner
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=chunk-CKXR4QVD.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/subscription/codec/typed-data.ts","../../src/subscription/codec/base64.ts","../../src/subscription/codec/payload.ts"],"sourcesContent":["import { encodeAbiParameters, keccak256, type Hex, type TypedDataDomain } from \"viem\";\n\nimport type { PaymentRequirements } from \"../../types/payments\";\nimport type { CancelInitiator, PlanInitialCharge } from \"../types\";\nimport type { PermitSingleData, SubscriptionTerms } from \"./payload\";\n\n/** 32-byte zero hash, used as the sentinel for empty bytes32 fields. */\nexport const ZERO_BYTES32: Hex = `0x${\"0\".repeat(64)}`;\n\n/**\n * Permit2 PermitSingle (AllowanceTransfer) EIP-712 schema. Used for the\n * continuous-pull authorization signed by the buyer. The Permit2 domain has\n * NO `version` field — only `name=\"Permit2\"`, `chainId`, `verifyingContract`.\n */\nexport const PERMIT2_TYPES = {\n PermitSingle: [\n { name: \"details\", type: \"PermitDetails\" },\n { name: \"spender\", type: \"address\" },\n { name: \"sigDeadline\", type: \"uint256\" },\n ],\n PermitDetails: [\n { name: \"token\", type: \"address\" },\n { name: \"amount\", type: \"uint160\" },\n { name: \"expiration\", type: \"uint48\" },\n { name: \"nonce\", type: \"uint48\" },\n ],\n} as const;\n\n/**\n * SubscriptionTerms EIP-712 schema. 17 fields; field ORDER + integer widths\n * must mirror the contract. TypeHash:\n * 0xa5223de56e7694cf776c7d4f74c0323f42bf9e65655fe49affefbdfd40ec97ae\n * (verify against contract `SUBSCRIPTION_TERMS_TYPEHASH()`)\n */\nexport const SUBSCRIPTION_TERMS_TYPES = {\n SubscriptionTerms: [\n { name: \"payer\", type: \"address\" },\n { name: \"merchant\", type: \"address\" },\n { name: \"facilitator\", type: \"address\" },\n { name: \"token\", type: \"address\" },\n { name: \"amountPerPeriod\", type: \"uint160\" },\n { name: \"periodSec\", type: \"uint64\" },\n { name: \"maxPeriods\", type: \"uint32\" },\n { name: \"startAt\", type: \"uint64\" },\n { name: \"initialChargePeriods\", type: \"uint32\" },\n { name: \"initialChargeAmount\", type: \"uint160\" },\n { name: \"termsDeadline\", type: \"uint64\" },\n { name: \"permitHash\", type: \"bytes32\" },\n { name: \"salt\", type: \"bytes32\" },\n { name: \"planTier\", type: \"uint8\" },\n { name: \"changeFromSubId\", type: \"bytes32\" },\n { name: \"changeEffectiveAt\", type: \"uint8\" },\n { name: \"periodMode\", type: \"uint8\" },\n ],\n} as const;\n\n/**\n * CancelAuth EIP-712 schema (subscription contract domain):\n * `CancelAuth(uint8 action, bytes32 subId, uint8 initiator, bytes32 nonce, uint64 deadline)`\n */\nexport const CANCEL_AUTH_TYPES = {\n CancelAuth: [\n { name: \"action\", type: \"uint8\" },\n { name: \"subId\", type: \"bytes32\" },\n { name: \"initiator\", type: \"uint8\" },\n { name: \"nonce\", type: \"bytes32\" },\n { name: \"deadline\", type: \"uint64\" },\n ],\n} as const;\n\n/**\n * PendingChangeCancelAuth EIP-712 schema (subscription contract domain):\n * `PendingChangeCancelAuth(bytes32 subId, bytes32 newSubId, bytes32 nonce, uint64 deadline)`\n */\nexport const PENDING_CHANGE_CANCEL_AUTH_TYPES = {\n PendingChangeCancelAuth: [\n { name: \"subId\", type: \"bytes32\" },\n { name: \"newSubId\", type: \"bytes32\" },\n { name: \"nonce\", type: \"bytes32\" },\n { name: \"deadline\", type: \"uint64\" },\n ],\n} as const;\n\nexport interface SubscriptionPlanExtra {\n id: string;\n tier: number;\n name?: string;\n}\n\n/**\n * Buyer's intent to switch from `fromSubId` to a new plan. Present on\n * PaymentRequirements.extra ONLY when the requirements are a change offer\n * for an upgrade/downgrade.\n */\nexport interface ChangeFromExtra {\n fromSubId: Hex;\n fromPlanId: Hex;\n fromPlanTier: number;\n direction: \"upgrade\" | \"downgrade\";\n effectiveAt: \"immediate\" | \"period_end\";\n}\n\nexport interface SubscriptionContractAddresses {\n subscription: string;\n permit2: string;\n}\n\n/**\n * Typed view of the `extra` field on subscription PaymentRequirements. The\n * evm scheme is responsible for producing PaymentRequirements whose `extra`\n * matches this shape; codec helpers consume it via this type rather than\n * Record<string, unknown> to keep field access type-safe.\n *\n * Index signature lets this be assigned directly to\n * `PaymentRequirements.extra: Record<string, unknown>` without a cast.\n */\nexport interface SubscriptionRequirementsExtra {\n contracts: SubscriptionContractAddresses;\n facilitator: string;\n amountPerPeriod: string;\n periodSec: number;\n /** uint8 — 0 fixed_seconds (default) / 1 calendar_month. */\n periodMode?: number;\n maxPeriods: number;\n startAt?: number;\n initialCharge?: PlanInitialCharge | null;\n plan: SubscriptionPlanExtra;\n changeFrom?: ChangeFromExtra;\n domain: TypedDataDomain;\n [key: string]: unknown;\n}\n\nexport interface TypedDataEnvelope<TPrimary extends string, TMessage> {\n domain: TypedDataDomain;\n types: Record<string, ReadonlyArray<{ name: string; type: string }>>;\n primaryType: TPrimary;\n message: TMessage;\n}\n\nexport interface BuildPermit2TypedDataInput {\n /** PaymentRequirements with subscription `extra`. */\n selected: PaymentRequirements;\n /** uint48 — current nonce from allowance-status (NOT +1). */\n nonce: number;\n /** uint48 unix seconds — Permit2 allowance expiration. */\n expiration: number;\n /** uint256 decimal — outer signature expiry. */\n sigDeadline: string;\n /** Override amount; default = initialChargeAmount + (maxPeriods - initialChargePeriods) * amountPerPeriod. */\n amount?: string;\n}\n\nexport interface BuildSubscriptionTermsTypedDataInput {\n selected: PaymentRequirements;\n payer: string;\n startAt: number;\n /** uint64 unix seconds — terms signature validity. */\n termsDeadline: number;\n /** bytes32 — random anti-replay; reuse the same hex string ≤ once. */\n salt: Hex;\n /** bytes32 — keccak256 of the PermitSingle struct hash; bind permit. */\n permitHash: Hex;\n changeFrom?: ChangeFromExtra;\n /** Override domain for special cases; defaults to `selected.extra.domain`. */\n domain?: TypedDataDomain;\n}\n\nexport interface BuildCancelAuthTypedDataInput {\n subId: Hex;\n initiator: CancelInitiator;\n /** uint64 unix seconds. */\n deadline: number;\n /** bytes32. */\n nonce: Hex;\n domain: TypedDataDomain;\n}\n\nexport interface BuildPendingChangeCancelAuthTypedDataInput {\n subId: Hex;\n /** The pending change's target newSubId — must match `pendingPlanChange.newSubId`. */\n newSubId: Hex;\n deadline: number;\n nonce: Hex;\n domain: TypedDataDomain;\n}\n\nconst CANCEL_INITIATOR_TO_ENUM: Record<CancelInitiator, 0 | 1> = {\n payer: 0,\n merchant: 1,\n};\n\nfunction getSubscriptionExtra(req: PaymentRequirements): SubscriptionRequirementsExtra {\n const extra = req.extra as Partial<SubscriptionRequirementsExtra> | undefined;\n if (!extra || !extra.contracts || !extra.plan || !extra.domain) {\n throw new Error(\n \"subscription codec: PaymentRequirements.extra is missing contracts/plan/domain\",\n );\n }\n return extra as SubscriptionRequirementsExtra;\n}\n\n/**\n * Default permit.amount:\n * amount ≥ reservedAmount + newCommit\n * newCommit = initialChargeAmount + (maxPeriods - initialChargePeriods) * amountPerPeriod\n *\n * `reservedAmount` (existing on-chain commitments) is NOT included here —\n * Buyer SDK should add it on top from `allowance-status.reservedAmount` if a\n * concurrent sub already holds budget against the same (payer, token).\n */\nfunction defaultPermitAmount(extra: SubscriptionRequirementsExtra): string {\n const initialChargePeriods = BigInt(extra.initialCharge?.periodCount ?? 0);\n const initialChargeAmount = BigInt(extra.initialCharge?.totalAmount ?? \"0\");\n const remainingPeriods = BigInt(extra.maxPeriods) - initialChargePeriods;\n const remainingAmount =\n remainingPeriods > 0n ? remainingPeriods * BigInt(extra.amountPerPeriod) : 0n;\n return (initialChargeAmount + remainingAmount).toString();\n}\n\n/**\n * Build the Permit2 PermitSingle (AllowanceTransfer) typed data the wallet\n * signs. `spender = subscription contract`; `amount` covers the entire\n * remaining commitment (default), so the subscription contract can pull from\n * the same Permit2 allowance on every charge without re-signing.\n *\n * Permit2's EIP-712 domain has NO `version` field.\n */\nexport function buildPermit2TypedData(\n input: BuildPermit2TypedDataInput,\n): TypedDataEnvelope<\"PermitSingle\", Record<string, unknown>> {\n const extra = getSubscriptionExtra(input.selected);\n const amount = input.amount ?? defaultPermitAmount(extra);\n const chainId = parseChainIdFromNetwork(input.selected.network);\n\n return {\n domain: {\n name: \"Permit2\",\n chainId,\n verifyingContract: extra.contracts.permit2 as Hex,\n },\n types: PERMIT2_TYPES as unknown as TypedDataEnvelope<\n \"PermitSingle\",\n Record<string, unknown>\n >[\"types\"],\n primaryType: \"PermitSingle\",\n message: {\n details: {\n token: input.selected.asset,\n amount,\n expiration: input.expiration,\n nonce: input.nonce,\n },\n spender: extra.contracts.subscription,\n sigDeadline: input.sigDeadline,\n },\n };\n}\n\n/**\n * Compute the EIP-712 struct hash of a PermitSingle message — what\n * `terms.permitHash` must equal byte-for-byte. The facilitator verifies this\n * via `permit_hash_mismatch`.\n *\n * permitDetailsHash = keccak256(\n * PERMIT_DETAILS_TYPEHASH ‖ token ‖ amount ‖ expiration ‖ nonce\n * )\n * permitSingleHash = keccak256(\n * PERMIT_SINGLE_TYPEHASH ‖ permitDetailsHash ‖ spender ‖ sigDeadline\n * )\n */\nexport function computePermitSingleStructHash(permit: PermitSingleData): Hex {\n // PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)\n const PERMIT_DETAILS_TYPEHASH = keccak256(\n new TextEncoder().encode(\n \"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)\",\n ),\n );\n const detailsHash = keccak256(\n encodeAbiParameters(\n [\n { type: \"bytes32\" },\n { type: \"address\" },\n { type: \"uint160\" },\n { type: \"uint48\" },\n { type: \"uint48\" },\n ],\n [\n PERMIT_DETAILS_TYPEHASH,\n permit.details.token as Hex,\n BigInt(permit.details.amount),\n Number(permit.details.expiration),\n Number(permit.details.nonce),\n ],\n ),\n );\n\n // PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(...)\n const PERMIT_SINGLE_TYPEHASH = keccak256(\n new TextEncoder().encode(\n \"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)\" +\n \"PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)\",\n ),\n );\n return keccak256(\n encodeAbiParameters(\n [{ type: \"bytes32\" }, { type: \"bytes32\" }, { type: \"address\" }, { type: \"uint256\" }],\n [PERMIT_SINGLE_TYPEHASH, detailsHash, permit.spender as Hex, BigInt(permit.sigDeadline)],\n ),\n );\n}\n\n/**\n * Build the EIP-712 typed-data object the wallet signs to commit to the\n * subscription terms (17 fields). `permitHash` MUST be the struct hash of\n * the PermitSingle the buyer signed for permit2 (compute it via\n * `computePermitSingleStructHash`) — the contract verifies this binding to\n * defeat detached-signature swap attacks.\n */\nexport function buildSubscriptionTermsTypedData(\n input: BuildSubscriptionTermsTypedDataInput,\n): TypedDataEnvelope<\"SubscriptionTerms\", SubscriptionTerms> {\n const extra = getSubscriptionExtra(input.selected);\n const domain = input.domain ?? extra.domain;\n\n // changeEffectiveAt: 0=none(create), 1=immediate(upgrade), 2=period_end(downgrade)\n let changeEffectiveAt = 0;\n if (input.changeFrom?.effectiveAt === \"immediate\") changeEffectiveAt = 1;\n else if (input.changeFrom?.effectiveAt === \"period_end\") changeEffectiveAt = 2;\n\n const message: SubscriptionTerms = {\n payer: input.payer,\n merchant: input.selected.payTo,\n facilitator: extra.facilitator,\n token: input.selected.asset,\n amountPerPeriod: extra.amountPerPeriod,\n periodSec: extra.periodSec,\n maxPeriods: extra.maxPeriods,\n startAt: input.startAt,\n initialChargePeriods: extra.initialCharge?.periodCount ?? 0,\n initialChargeAmount: extra.initialCharge?.totalAmount ?? \"0\",\n termsDeadline: input.termsDeadline,\n permitHash: input.permitHash,\n salt: input.salt,\n planTier: extra.plan.tier,\n changeFromSubId: input.changeFrom?.fromSubId ?? ZERO_BYTES32,\n changeEffectiveAt,\n periodMode: extra.periodMode ?? 0,\n };\n\n return {\n domain,\n types: SUBSCRIPTION_TERMS_TYPES as unknown as TypedDataEnvelope<\n \"SubscriptionTerms\",\n SubscriptionTerms\n >[\"types\"],\n primaryType: \"SubscriptionTerms\",\n message,\n };\n}\n\nexport function buildCancelAuthTypedData(\n input: BuildCancelAuthTypedDataInput,\n): TypedDataEnvelope<\"CancelAuth\", Record<string, unknown>> {\n return {\n domain: input.domain,\n types: CANCEL_AUTH_TYPES as unknown as TypedDataEnvelope<\n \"CancelAuth\",\n Record<string, unknown>\n >[\"types\"],\n primaryType: \"CancelAuth\",\n message: {\n action: 0,\n subId: input.subId,\n initiator: CANCEL_INITIATOR_TO_ENUM[input.initiator],\n nonce: input.nonce,\n deadline: input.deadline,\n },\n };\n}\n\nexport function buildPendingChangeCancelAuthTypedData(\n input: BuildPendingChangeCancelAuthTypedDataInput,\n): TypedDataEnvelope<\"PendingChangeCancelAuth\", Record<string, unknown>> {\n return {\n domain: input.domain,\n types: PENDING_CHANGE_CANCEL_AUTH_TYPES as unknown as TypedDataEnvelope<\n \"PendingChangeCancelAuth\",\n Record<string, unknown>\n >[\"types\"],\n primaryType: \"PendingChangeCancelAuth\",\n message: {\n subId: input.subId,\n newSubId: input.newSubId,\n nonce: input.nonce,\n deadline: input.deadline,\n },\n };\n}\n\n/**\n * Parse the chainId from a CAIP-2 network identifier (`eip155:<chainId>`).\n * Throws if not an EVM CAIP — the subscription scheme is EVM-only.\n */\nexport function parseChainIdFromNetwork(network: string): number {\n const parts = network.split(\":\");\n if (parts.length !== 2 || parts[0] !== \"eip155\") {\n throw new Error(`parseChainIdFromNetwork: expected \"eip155:<chainId>\", got \"${network}\"`);\n }\n const id = Number(parts[1]);\n if (!Number.isInteger(id) || id <= 0) {\n throw new Error(`parseChainIdFromNetwork: invalid chainId \"${parts[1]}\"`);\n }\n return id;\n}\n","/**\n * Isomorphic UTF-8 ↔ base64 helpers.\n *\n * Buyers may run this code in a browser; Buffer is preferred on Node so the\n * built CJS bundle stays free of `globalThis.atob` polyfills, but we fall back\n * to atob/btoa when Buffer is unavailable.\n */\n\nconst hasBuffer = typeof Buffer !== \"undefined\";\n\nexport function base64EncodeUtf8(value: string): string {\n if (hasBuffer) return Buffer.from(value, \"utf8\").toString(\"base64\");\n const binary = unescape(encodeURIComponent(value));\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (globalThis as any).btoa(binary);\n}\n\nexport function base64DecodeUtf8(value: string): string {\n if (hasBuffer) return Buffer.from(value, \"base64\").toString(\"utf8\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const binary: string = (globalThis as any).atob(value);\n return decodeURIComponent(escape(binary));\n}\n","import type { Hex } from \"viem\";\n\nimport type { PaymentPayload, PaymentRequired, PaymentRequirements } from \"../../types/payments\";\nimport { base64DecodeUtf8, base64EncodeUtf8 } from \"./base64\";\n\n/**\n * Permit2 PermitSingle (AllowanceTransfer) — what the buyer signs to authorize\n * the subscription contract to pull tokens on every charge.\n *\n * PermitSingle(PermitDetails details, address spender, uint256 sigDeadline)\n * PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce)\n */\nexport interface PermitSingleData {\n details: {\n token: string;\n /** uint160 as decimal string — covers entire remaining commitment. */\n amount: string;\n /** uint48 unix seconds — Permit2 allowance expiration. */\n expiration: number;\n /** uint48 — Permit2 nonce (current value from allowance-status, NOT +1). */\n nonce: number;\n };\n /** Subscription contract address. */\n spender: string;\n /** uint256 as decimal string — outer signature expiry. */\n sigDeadline: string;\n}\n\n/**\n * SubscriptionTerms — what the buyer's EIP-712 signature commits to. 17\n * fields. Field ORDER MATTERS because it determines the EIP-712 TypeHash:\n * 0xa5223de56e7694cf776c7d4f74c0323f42bf9e65655fe49affefbdfd40ec97ae\n */\nexport interface SubscriptionTerms {\n payer: string;\n merchant: string;\n facilitator: string;\n token: string;\n /** uint160 (atomic units). */\n amountPerPeriod: string;\n /** uint64 seconds; MUST be 0 when periodMode=1 (calendar_month). */\n periodSec: number;\n /** uint32. */\n maxPeriods: number;\n /** uint64 unix seconds; 0 means \"use block.timestamp on-chain\". */\n startAt: number;\n /** uint32; 0 = no separate initial charge. */\n initialChargePeriods: number;\n /** uint160; atomic units. */\n initialChargeAmount: string;\n /** uint64 unix seconds — terms signature validity window. */\n termsDeadline: number;\n /** bytes32 — keccak256 of the PermitSingle EIP-712 struct hash, binds terms to permit. */\n permitHash: Hex;\n /** bytes32 — buyer-generated random anti-replay value. */\n salt: Hex;\n /** uint8 (>0) — plan tier for upgrade/downgrade comparison. */\n planTier: number;\n /** bytes32 — old subId on change, zero on create. */\n changeFromSubId: Hex;\n /** uint8 — 0 none(create) / 1 immediate(upgrade) / 2 period_end(downgrade). */\n changeEffectiveAt: number;\n /** uint8 — 0 fixed_seconds / 1 calendar_month. */\n periodMode: number;\n /**\n * bytes32 — keccak256(utf8(plan.id)). Buyer SDKs sign it as a terms field\n * and the facilitator persists it as the on-chain `planId`. Seller verify\n * MUST cross-check this hash equals `keccak256(utf8(accepted.extra.plan.id))`\n * to defeat plan-spoof attacks.\n */\n planId?: Hex;\n}\n\n/**\n * Concrete payload nested inside `PaymentPayload.payload` for the `period`\n * scheme.\n */\nexport interface SubscriptionPaymentInner {\n permitSingle: PermitSingleData;\n permitSingleSignature: Hex;\n terms: SubscriptionTerms;\n termsSignature: Hex;\n}\n\nexport interface EncodePaymentPayloadInput {\n selected: PaymentRequirements;\n permitSingle: PermitSingleData;\n permitSingleSignature: Hex;\n terms: SubscriptionTerms;\n termsSignature: Hex;\n}\n\n/**\n * Decode the `APP-PAYMENT-REQUIRED` header into the underlying\n * `PaymentRequired.accepts` array. Throws on invalid base64 / JSON.\n */\nexport function parsePaymentRequired(headerValue: string): PaymentRequirements[] {\n const json = base64DecodeUtf8(headerValue);\n const parsed = JSON.parse(json) as PaymentRequired;\n if (!parsed || !Array.isArray(parsed.accepts)) {\n throw new Error(\"parsePaymentRequired: missing or invalid `accepts` array\");\n }\n return parsed.accepts;\n}\n\n/**\n * Build the `PAYMENT-SIGNATURE` header value (base64-encoded JSON). Buyer side.\n */\nexport function encodePaymentPayload(input: EncodePaymentPayloadInput): string {\n const payload: PaymentPayload = {\n x402Version: 2,\n accepted: input.selected,\n payload: {\n permitSingle: input.permitSingle,\n permitSingleSignature: input.permitSingleSignature,\n terms: input.terms,\n termsSignature: input.termsSignature,\n },\n };\n return base64EncodeUtf8(JSON.stringify(payload));\n}\n\n/**\n * Decode the `PAYMENT-SIGNATURE` header value back into a typed PaymentPayload.\n * Throws on invalid base64 / JSON; does not validate field shapes (that is the\n * responsibility of the scheme verify step).\n */\nexport function decodePaymentPayload(headerValue: string): PaymentPayload {\n const json = base64DecodeUtf8(headerValue);\n return JSON.parse(json) as PaymentPayload;\n}\n\n/**\n * Narrow a generic PaymentPayload's `.payload` to the subscription inner.\n * Throws if required fields are missing — single chokepoint between base64\n * decode and verify.\n */\nexport function asSubscriptionPaymentInner(payload: PaymentPayload): SubscriptionPaymentInner {\n const inner = payload.payload as Partial<SubscriptionPaymentInner>;\n if (\n !inner ||\n !inner.permitSingle ||\n !inner.terms ||\n !inner.permitSingleSignature ||\n !inner.termsSignature\n ) {\n throw new Error(\n \"asSubscriptionPaymentInner: payload.payload is missing required permitSingle/terms fields\",\n );\n }\n return inner as SubscriptionPaymentInner;\n}\n"],"mappings":"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","names":[]}
|