@chainpayhq/sdk 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -2
  3. package/dist/accounts.d.ts +23 -0
  4. package/dist/accounts.js +148 -0
  5. package/dist/cards/accounts.d.ts +156 -0
  6. package/dist/cards/accounts.js +346 -0
  7. package/dist/cards/api.d.ts +379 -0
  8. package/dist/cards/api.js +241 -0
  9. package/dist/cards/commitment.d.ts +86 -0
  10. package/dist/cards/commitment.js +288 -0
  11. package/dist/cards/constants.d.ts +160 -0
  12. package/dist/cards/constants.js +163 -0
  13. package/dist/cards/draft.d.ts +57 -0
  14. package/dist/cards/draft.js +131 -0
  15. package/dist/cards/evidence.d.ts +69 -0
  16. package/dist/cards/evidence.js +41 -0
  17. package/dist/cards/hash.d.ts +9 -0
  18. package/dist/cards/hash.js +47 -0
  19. package/dist/cards/index.d.ts +14 -0
  20. package/dist/cards/index.js +14 -0
  21. package/dist/cards/instructions.d.ts +302 -0
  22. package/dist/cards/instructions.js +474 -0
  23. package/dist/cards/layout.d.ts +33 -0
  24. package/dist/cards/layout.js +137 -0
  25. package/dist/cards/math.d.ts +67 -0
  26. package/dist/cards/math.js +138 -0
  27. package/dist/cards/merchants.d.ts +23 -0
  28. package/dist/cards/merchants.js +38 -0
  29. package/dist/cards/pda.d.ts +42 -0
  30. package/dist/cards/pda.js +93 -0
  31. package/dist/cards/private-repayment.d.ts +198 -0
  32. package/dist/cards/private-repayment.js +485 -0
  33. package/dist/cards/redact.d.ts +18 -0
  34. package/dist/cards/redact.js +103 -0
  35. package/dist/cards/tee.d.ts +253 -0
  36. package/dist/cards/tee.js +605 -0
  37. package/dist/cli.d.ts +31 -0
  38. package/dist/cli.js +351 -0
  39. package/dist/client.d.ts +80 -0
  40. package/dist/client.js +493 -0
  41. package/dist/constants.d.ts +44 -0
  42. package/dist/constants.js +43 -0
  43. package/dist/crossmint-adapt.d.ts +43 -0
  44. package/dist/crossmint-adapt.js +68 -0
  45. package/dist/crossmint-order.d.ts +220 -0
  46. package/dist/crossmint-order.js +638 -0
  47. package/dist/delivery.d.ts +66 -0
  48. package/dist/delivery.js +232 -0
  49. package/dist/encoding.d.ts +51 -0
  50. package/dist/encoding.js +128 -0
  51. package/dist/index.d.ts +27 -0
  52. package/dist/index.js +26 -0
  53. package/dist/known-assets.d.ts +30 -0
  54. package/dist/known-assets.js +49 -0
  55. package/dist/mandate-request.d.ts +123 -0
  56. package/dist/mandate-request.js +401 -0
  57. package/dist/mandate.d.ts +44 -0
  58. package/dist/mandate.js +157 -0
  59. package/dist/ops-snapshot.d.ts +157 -0
  60. package/dist/ops-snapshot.js +356 -0
  61. package/dist/payment-request.d.ts +37 -0
  62. package/dist/payment-request.js +218 -0
  63. package/dist/payment.d.ts +40 -0
  64. package/dist/payment.js +214 -0
  65. package/dist/pda.d.ts +13 -0
  66. package/dist/pda.js +36 -0
  67. package/dist/receipt-export.d.ts +55 -0
  68. package/dist/receipt-export.js +149 -0
  69. package/dist/receipt.d.ts +108 -0
  70. package/dist/receipt.js +213 -0
  71. package/dist/solana.d.ts +5 -0
  72. package/dist/solana.js +20 -0
  73. package/dist/token-capabilities.d.ts +9 -0
  74. package/dist/token-capabilities.js +139 -0
  75. package/dist/token.d.ts +18 -0
  76. package/dist/token.js +55 -0
  77. package/dist/transaction-reader.d.ts +10 -0
  78. package/dist/transaction-reader.js +18 -0
  79. package/dist/transaction-v1.d.ts +18 -0
  80. package/dist/transaction-v1.js +73 -0
  81. package/dist/types.d.ts +267 -0
  82. package/dist/types.js +1 -0
  83. package/dist/x402-adapt.d.ts +20 -0
  84. package/dist/x402-adapt.js +62 -0
  85. package/dist/x402-challenge.d.ts +116 -0
  86. package/dist/x402-challenge.js +385 -0
  87. package/dist/x402.d.ts +18 -0
  88. package/dist/x402.js +35 -0
  89. package/package.json +54 -4
@@ -0,0 +1,385 @@
1
+ import { publicKey } from "./encoding.js";
2
+ export const CUSTOM_X402_VERSION = "x402/1.0";
3
+ export const SOLANA_DEVNET_CAIP2 = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
4
+ export const CUSTOM_NETWORK = "solana-devnet";
5
+ export const CUSTOM_PROTOCOL = "chainpay-custom-x402/1.0";
6
+ export const STANDARD_V2_PROTOCOL = "x402-v2";
7
+ export const CUSTOM_PROTOCOL_LABEL = "ChainPay custom receipt-proof flow (x402/1.0). Proof is a settled signature and receipt PDA, not a standard x402 v2 sponsor countersign.";
8
+ export const STANDARD_V2_PROTOCOL_LABEL = "Standard x402 v2 exact SVM. Proof is a partially signed transaction for sponsor countersign. ChainPay does not operate a facilitator.";
9
+ const MAX_HEADER_CHARS = 16 * 1024;
10
+ const MAX_DECODED_CHARS = 16 * 1024;
11
+ const MAX_ACCEPTS = 16;
12
+ const MAX_RESOURCE_CHARS = 2048;
13
+ const MAX_NONCE_CHARS = 128;
14
+ const MAX_U64 = 18446744073709551615n;
15
+ export class X402ProtocolError extends Error {
16
+ code;
17
+ constructor(code, message) {
18
+ super(message);
19
+ this.name = "X402ProtocolError";
20
+ this.code = code;
21
+ }
22
+ }
23
+ function isRecord(value) {
24
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
25
+ }
26
+ function fail(code, message) {
27
+ throw new X402ProtocolError(code, message);
28
+ }
29
+ export function canonicalU64String(value, name, allowZero = false) {
30
+ if (typeof value !== "string") {
31
+ fail("malformed", `${name} must be a canonical decimal u64 string`);
32
+ }
33
+ const pattern = allowZero ? /^(0|[1-9]\d*)$/ : /^[1-9]\d*$/;
34
+ if (!pattern.test(value)) {
35
+ fail("malformed", `${name} must be a canonical decimal u64 string`);
36
+ }
37
+ const parsed = BigInt(value);
38
+ if (parsed > MAX_U64 || parsed.toString() !== value) {
39
+ fail("malformed", `${name} must fit in an unsigned 64-bit integer`);
40
+ }
41
+ return value;
42
+ }
43
+ function canonicalAddress(value, name) {
44
+ if (typeof value !== "string" || value.trim() === "") {
45
+ fail("malformed", `${name} is required`);
46
+ }
47
+ let encoded;
48
+ try {
49
+ encoded = publicKey(value).toBase58();
50
+ }
51
+ catch {
52
+ fail("malformed", `${name} must be a canonical Solana address`);
53
+ }
54
+ if (encoded !== value)
55
+ fail("malformed", `${name} must be a canonical Solana address`);
56
+ return encoded;
57
+ }
58
+ function boundedString(value, name, max) {
59
+ if (typeof value !== "string" || value.trim() === "")
60
+ fail("malformed", `${name} is required`);
61
+ if (value.length > max)
62
+ fail("malformed", `${name} is too long`);
63
+ return value;
64
+ }
65
+ export function decodePaymentRequiredWire(value) {
66
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_HEADER_CHARS) {
67
+ fail("malformed", "x402 payment-required payload is missing or too large");
68
+ }
69
+ try {
70
+ const parsed = JSON.parse(value);
71
+ if (value.length > MAX_DECODED_CHARS)
72
+ fail("malformed", "x402 payment-required payload is too large");
73
+ return parsed;
74
+ }
75
+ catch (error) {
76
+ if (error instanceof X402ProtocolError)
77
+ throw error;
78
+ }
79
+ let decoded;
80
+ try {
81
+ decoded = Buffer.from(value, "base64").toString("utf8");
82
+ }
83
+ catch {
84
+ fail("malformed", "x402 payment-required header is neither JSON nor base64 JSON");
85
+ }
86
+ if (!decoded || decoded.length > MAX_DECODED_CHARS) {
87
+ fail("malformed", "x402 payment-required payload is too large");
88
+ }
89
+ try {
90
+ return JSON.parse(decoded);
91
+ }
92
+ catch {
93
+ fail("malformed", "x402 payment-required header is neither JSON nor base64 JSON");
94
+ }
95
+ }
96
+ export function paymentRequiredDocumentsFromResponse(headers, body) {
97
+ const documents = [];
98
+ const seen = new Set();
99
+ for (const name of ["payment-required", "x-payment-required"]) {
100
+ const header = headers.get(name);
101
+ if (!header)
102
+ continue;
103
+ const document = decodePaymentRequiredWire(header);
104
+ const key = JSON.stringify(document);
105
+ if (!seen.has(key)) {
106
+ seen.add(key);
107
+ documents.push(document);
108
+ }
109
+ }
110
+ if (body && "parsed" in body && body.parsed !== undefined) {
111
+ const key = JSON.stringify(body.parsed);
112
+ if (!seen.has(key))
113
+ documents.push(body.parsed);
114
+ }
115
+ return documents;
116
+ }
117
+ function resourceBinding(value, expectedResource) {
118
+ const resource = boundedString(value, "resource", MAX_RESOURCE_CHARS);
119
+ let url;
120
+ try {
121
+ url = new URL(resource);
122
+ }
123
+ catch {
124
+ fail("malformed", "resource must be an absolute URL");
125
+ }
126
+ if (url.username || url.password) {
127
+ fail("malformed", "x402 resource URLs must not contain credentials");
128
+ }
129
+ const normalized = url.toString();
130
+ if (expectedResource && normalized !== expectedResource && resource !== expectedResource) {
131
+ fail("resource_mismatch", "x402 challenge resource does not match the requested resource URL");
132
+ }
133
+ return resource;
134
+ }
135
+ function customAmount(option) {
136
+ const amount = option.amount;
137
+ const maxAmountRequired = option.maxAmountRequired;
138
+ if (amount !== undefined && maxAmountRequired !== undefined && amount !== maxAmountRequired) {
139
+ fail("malformed", "custom x402 amount and maxAmountRequired must be identical canonical strings");
140
+ }
141
+ return canonicalU64String(amount ?? maxAmountRequired, "amount");
142
+ }
143
+ function parseCustomOption(option, expectedResource) {
144
+ if (option.scheme !== "exact")
145
+ fail("unsupported_scheme", "Only the x402 exact scheme is enabled");
146
+ if (option.network !== CUSTOM_NETWORK) {
147
+ fail("unsupported_network", "Custom ChainPay x402/1.0 requires network solana-devnet");
148
+ }
149
+ const mintValue = option.asset ?? option.mint;
150
+ if (option.asset !== undefined && option.mint !== undefined && option.asset !== option.mint) {
151
+ fail("malformed", "custom x402 asset and mint must match");
152
+ }
153
+ const payTo = option.payTo ?? option.recipient;
154
+ if (option.payTo !== undefined && option.recipient !== undefined && option.payTo !== option.recipient) {
155
+ fail("malformed", "custom x402 payTo and recipient must match");
156
+ }
157
+ const resource = resourceBinding(typeof option.resource === "string" ? option.resource : "", expectedResource);
158
+ const parsed = {
159
+ protocol: CUSTOM_PROTOCOL,
160
+ protocolLabel: CUSTOM_PROTOCOL_LABEL,
161
+ proofKind: "settled-receipt-pda",
162
+ network: CUSTOM_NETWORK,
163
+ scheme: "exact",
164
+ mint: canonicalAddress(mintValue, "asset"),
165
+ recipient: canonicalAddress(payTo, "payTo"),
166
+ amount: customAmount(option),
167
+ resource,
168
+ };
169
+ if (option.tokenProgram !== undefined) {
170
+ if (option.tokenProgram !== "spl-token" && option.tokenProgram !== "token-2022") {
171
+ fail("malformed", "tokenProgram must be spl-token or token-2022");
172
+ }
173
+ parsed.tokenProgram = option.tokenProgram;
174
+ }
175
+ if (option.nonce !== undefined) {
176
+ parsed.nonce = boundedString(option.nonce, "nonce", MAX_NONCE_CHARS).trim();
177
+ }
178
+ if (option.expiresAtSlot !== undefined) {
179
+ parsed.expiresAtSlot = canonicalU64String(option.expiresAtSlot, "expiresAtSlot", true);
180
+ }
181
+ return parsed;
182
+ }
183
+ function v2Resource(envelope, option, expectedResource) {
184
+ const envelopeUrl = isRecord(envelope.resource) && typeof envelope.resource.url === "string"
185
+ ? envelope.resource.url
186
+ : undefined;
187
+ const optionUrl = typeof option.resource === "string" ? option.resource : undefined;
188
+ if (envelopeUrl && optionUrl && envelopeUrl !== optionUrl) {
189
+ fail("resource_mismatch", "x402 v2 resource.url does not match the selected requirement resource");
190
+ }
191
+ const resource = envelopeUrl ?? optionUrl;
192
+ if (!resource)
193
+ fail("malformed", "standard x402 v2 resource.url is required");
194
+ return resourceBinding(resource, expectedResource);
195
+ }
196
+ function parseV2Option(option, resource) {
197
+ if (option.scheme !== "exact")
198
+ fail("unsupported_scheme", "Only the x402 exact scheme is enabled");
199
+ if (option.network !== SOLANA_DEVNET_CAIP2) {
200
+ fail("unsupported_network", `Standard x402 v2 Solana Devnet must use CAIP-2 ${SOLANA_DEVNET_CAIP2}`);
201
+ }
202
+ if (option.maxAmountRequired !== undefined && option.amount !== undefined && option.maxAmountRequired !== option.amount) {
203
+ fail("malformed", "standard x402 v2 amount fields must be identical canonical strings");
204
+ }
205
+ if (option.amount === undefined)
206
+ fail("malformed", "standard x402 v2 amount is required");
207
+ const extra = option.extra === undefined ? undefined : option.extra;
208
+ if (extra !== undefined && !isRecord(extra))
209
+ fail("malformed", "standard x402 v2 extra must be an object");
210
+ const parsed = {
211
+ protocol: STANDARD_V2_PROTOCOL,
212
+ protocolLabel: STANDARD_V2_PROTOCOL_LABEL,
213
+ proofKind: "partially-signed-sponsored-transaction",
214
+ network: SOLANA_DEVNET_CAIP2,
215
+ scheme: "exact",
216
+ amount: canonicalU64String(option.amount, "amount"),
217
+ asset: canonicalAddress(option.asset, "asset"),
218
+ merchantOwner: canonicalAddress(option.payTo, "payTo"),
219
+ resource,
220
+ };
221
+ if (option.maxTimeoutSeconds !== undefined) {
222
+ if (typeof option.maxTimeoutSeconds !== "number" || !Number.isInteger(option.maxTimeoutSeconds)
223
+ || option.maxTimeoutSeconds < 1 || option.maxTimeoutSeconds > 86_400) {
224
+ fail("malformed", "maxTimeoutSeconds must be an integer between 1 and 86400");
225
+ }
226
+ parsed.maxTimeoutSeconds = option.maxTimeoutSeconds;
227
+ }
228
+ if (extra && extra.feePayer !== undefined) {
229
+ parsed.feePayer = canonicalAddress(extra.feePayer, "extra.feePayer");
230
+ }
231
+ return parsed;
232
+ }
233
+ function acceptRecords(value) {
234
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_ACCEPTS) {
235
+ fail("malformed", "x402 accepts must be a bounded non-empty array");
236
+ }
237
+ if (!value.every(isRecord))
238
+ fail("malformed", "x402 accepts entries must be objects");
239
+ return value;
240
+ }
241
+ function selectCustomAccept(accepts) {
242
+ const selected = accepts.find((option) => option.network === CUSTOM_NETWORK && option.scheme === "exact");
243
+ if (selected)
244
+ return selected;
245
+ if (accepts.some((option) => option.scheme === "exact" && typeof option.network === "string")) {
246
+ fail("unsupported_network", "Custom ChainPay x402/1.0 requires network solana-devnet");
247
+ }
248
+ fail("malformed", "x402/1.0 response has no Solana Devnet exact payment option");
249
+ }
250
+ function selectV2Accept(accepts) {
251
+ const selected = accepts.find((option) => option.network === SOLANA_DEVNET_CAIP2 && option.scheme === "exact");
252
+ if (selected)
253
+ return selected;
254
+ if (accepts.some((option) => typeof option.network === "string")) {
255
+ fail("unsupported_network", `Standard x402 v2 Solana Devnet must use CAIP-2 ${SOLANA_DEVNET_CAIP2}`);
256
+ }
257
+ fail("malformed", "standard x402 v2 response has no Solana Devnet exact payment option");
258
+ }
259
+ function parseCustomEnvelope(value, expectedResource) {
260
+ const option = Array.isArray(value.accepts)
261
+ ? selectCustomAccept(acceptRecords(value.accepts))
262
+ : value;
263
+ return { kind: "custom", option: parseCustomOption(option, expectedResource), envelope: value };
264
+ }
265
+ function parseV2Envelope(value, expectedResource) {
266
+ const option = Array.isArray(value.accepts)
267
+ ? selectV2Accept(acceptRecords(value.accepts))
268
+ : isRecord(value.accepted)
269
+ ? value.accepted
270
+ : value;
271
+ const resource = v2Resource(value, option, expectedResource);
272
+ return { kind: "standard-v2", option: parseV2Option(option, resource), envelope: value };
273
+ }
274
+ function parseBareOption(value, expectedResource) {
275
+ if (value.network === SOLANA_DEVNET_CAIP2) {
276
+ return parseV2Envelope(value, expectedResource);
277
+ }
278
+ if (value.network === CUSTOM_NETWORK) {
279
+ fail("malformed", `Custom ChainPay x402 requires an explicit version ${CUSTOM_X402_VERSION}; refusing to infer it from the network name`);
280
+ }
281
+ if (typeof value.network === "string") {
282
+ fail("unsupported_network", "Unsupported x402 network");
283
+ }
284
+ fail("malformed", "x402 challenge is missing an explicit protocol version or network");
285
+ }
286
+ export function parsePaymentRequiredDocument(value, expectedResource) {
287
+ if (!isRecord(value))
288
+ fail("malformed", "x402 payment requirements must be an object");
289
+ if (value.x402Version === 2 && value.version === CUSTOM_X402_VERSION) {
290
+ fail("malformed", "x402 document must not mix x402Version 2 with custom x402/1.0");
291
+ }
292
+ if (value.paymentRequired !== undefined) {
293
+ return parsePaymentRequiredDocument(value.paymentRequired, expectedResource);
294
+ }
295
+ if (value.x402Version === 2)
296
+ return parseV2Envelope(value, expectedResource);
297
+ if (value.version === CUSTOM_X402_VERSION)
298
+ return parseCustomEnvelope(value, expectedResource);
299
+ if (value.x402Version !== undefined)
300
+ fail("malformed", "unsupported x402Version");
301
+ if (value.version !== undefined)
302
+ fail("malformed", "unsupported x402 version");
303
+ return parseBareOption(value, expectedResource);
304
+ }
305
+ export function detectAndParsePaymentRequired(documents, expectedResource) {
306
+ if (documents.length === 0) {
307
+ fail("malformed", "HTTP 402 response did not include payment requirements");
308
+ }
309
+ const parsed = [];
310
+ const errors = [];
311
+ for (const document of documents) {
312
+ try {
313
+ parsed.push(parsePaymentRequiredDocument(document, expectedResource));
314
+ }
315
+ catch (error) {
316
+ if (error instanceof X402ProtocolError)
317
+ errors.push(error);
318
+ else
319
+ throw error;
320
+ }
321
+ }
322
+ if (parsed.length === 0) {
323
+ throw errors[0] ?? new X402ProtocolError("malformed", "HTTP 402 response did not include payment requirements");
324
+ }
325
+ const kinds = new Set(parsed.map((item) => item.kind));
326
+ if (kinds.size > 1) {
327
+ fail("conflicting_protocols", "PAYMENT-REQUIRED sources disagree on custom vs standard v2");
328
+ }
329
+ return parsed[0];
330
+ }
331
+ /** Alias for agent/SDK callers: parse HTTP 402 headers and body into a detected challenge. */
332
+ export function detectPaymentChallenge(headers, body, expectedResource) {
333
+ return detectAndParsePaymentRequired(paymentRequiredDocumentsFromResponse(headers, body), expectedResource);
334
+ }
335
+ export function parsePaymentRequiredFromResponse(headers, body, expectedResource) {
336
+ return detectPaymentChallenge(headers, body, expectedResource);
337
+ }
338
+ export function customReceiptProofDocument(signature, receiptPDA) {
339
+ return {
340
+ version: CUSTOM_X402_VERSION,
341
+ scheme: "exact",
342
+ network: CUSTOM_NETWORK,
343
+ payload: { signature, receiptPDA },
344
+ };
345
+ }
346
+ export function unsupportedSponsorResult(option, extras) {
347
+ return {
348
+ action: "x402_unsupported_sponsor",
349
+ mode: "unsupported-sponsor",
350
+ protocol: option.protocol,
351
+ protocolLabel: option.protocolLabel,
352
+ proofKind: option.proofKind,
353
+ network: option.network,
354
+ scheme: option.scheme,
355
+ amount: option.amount,
356
+ asset: option.asset,
357
+ merchantOwner: option.merchantOwner,
358
+ resource: option.resource,
359
+ sponsorAvailable: false,
360
+ wouldSettle: false,
361
+ reason: "facilitator_required",
362
+ message: "Standard x402 v2 exact SVM requires a sponsor or facilitator to countersign a partially signed transaction. ChainPay settles through an on-chain mandate and returns a receipt-PDA proof. This challenge is quoted against your mandate; use pay.sh for facilitator merchants, or settleIfReceiptMerchant on allowlisted receipt merchants.",
363
+ ...(extras?.derivedRecipientTokenAccount
364
+ ? { derivedRecipientTokenAccount: extras.derivedRecipientTokenAccount }
365
+ : {}),
366
+ ...(extras?.mandateQuote ? { mandateQuote: extras.mandateQuote } : {}),
367
+ };
368
+ }
369
+ export function parseMppWwwAuthenticate(value) {
370
+ if (!value || !value.trim().toLowerCase().startsWith("payment"))
371
+ return null;
372
+ const trimmed = value.trim();
373
+ const intent = trimmed.match(/\bintent="([^"]+)"/i)?.[1];
374
+ const method = trimmed.match(/\bmethod="([^"]+)"/i)?.[1];
375
+ const challengeId = trimmed.match(/\bid="([^"]+)"/i)?.[1];
376
+ return {
377
+ action: "mpp_unsupported",
378
+ mode: "unsupported-mpp",
379
+ message: "Machine Payments Protocol (MPP) uses WWW-Authenticate: Payment, not x402 PAYMENT-REQUIRED. ChainPay does not settle MPP sessions or Tempo/Stripe methods. Use pay.sh for MPP-gated APIs.",
380
+ wwwAuthenticate: trimmed,
381
+ ...(intent ? { intent } : {}),
382
+ ...(method ? { method } : {}),
383
+ ...(challengeId ? { challengeId } : {}),
384
+ };
385
+ }
package/dist/x402.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { Address, TokenProgram } from "./types.js";
2
+ export type X402ExactPayment = {
3
+ mint: Address;
4
+ recipient: Address;
5
+ amount: string;
6
+ resource: string;
7
+ tokenProgram: TokenProgram;
8
+ nonce?: string;
9
+ expiresAtSlot?: string;
10
+ };
11
+ export type X402PaymentReferences = {
12
+ nonce: string;
13
+ invoiceHash: string;
14
+ paymentId: string;
15
+ signatureReference: string;
16
+ };
17
+ /** Canonical references shared by the x402 client and independent merchant verifier. */
18
+ export declare function deriveX402PaymentReferences(input: X402ExactPayment): Promise<X402PaymentReferences>;
package/dist/x402.js ADDED
@@ -0,0 +1,35 @@
1
+ async function sha256Hex(value) {
2
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(value).slice().buffer);
3
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
4
+ }
5
+ /** Canonical references shared by the x402 client and independent merchant verifier. */
6
+ export async function deriveX402PaymentReferences(input) {
7
+ const nonceSeed = JSON.stringify({
8
+ network: "solana-devnet",
9
+ scheme: "exact",
10
+ mint: input.mint,
11
+ recipient: input.recipient,
12
+ amount: input.amount,
13
+ resource: input.resource,
14
+ expiresAtSlot: input.expiresAtSlot,
15
+ });
16
+ const nonce = input.nonce?.trim() || (await sha256Hex(`nonce:${nonceSeed}`)).slice(0, 32);
17
+ const canonical = JSON.stringify({
18
+ network: "solana-devnet",
19
+ scheme: "exact",
20
+ asset: input.mint,
21
+ payTo: input.recipient,
22
+ amount: input.amount,
23
+ resource: input.resource,
24
+ nonce,
25
+ tokenProgram: input.tokenProgram,
26
+ ...(input.expiresAtSlot ? { expiresAtSlot: input.expiresAtSlot } : {}),
27
+ });
28
+ const invoiceHash = await sha256Hex(canonical);
29
+ return {
30
+ nonce,
31
+ invoiceHash,
32
+ paymentId: await sha256Hex(`payment:${invoiceHash}`),
33
+ signatureReference: await sha256Hex(`x402:${invoiceHash}`),
34
+ };
35
+ }
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@chainpayhq/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Policy-controlled Solana Devnet payments and receipt verification for ChainPay integrations",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/stawuah/chainpay-mcp-sdk.git",
9
+ "directory": "sdk"
10
+ },
11
+ "homepage": "https://github.com/stawuah/chainpay-mcp-sdk/tree/master/sdk#readme",
12
+ "bugs": "https://github.com/stawuah/chainpay-mcp-sdk/issues",
13
+ "keywords": ["chainpay", "solana", "devnet", "stablecoin", "payments", "receipts"],
14
+ "files": ["dist", "README.md", "LICENSE"],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "dist/index.js",
20
+ "types": "dist/index.d.ts",
21
+ "bin": {
22
+ "chainpay": "./dist/cli.js"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./known-assets": {
30
+ "types": "./dist/known-assets.d.ts",
31
+ "import": "./dist/known-assets.js"
32
+ },
33
+ "./cards/private-repayment": {
34
+ "types": "./dist/cards/private-repayment.d.ts",
35
+ "import": "./dist/cards/private-repayment.js"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "typecheck": "tsc --noEmit",
40
+ "build": "tsc",
41
+ "prepack": "npm run build",
42
+ "test": "npm run build && node --test test/*.test.mjs"
43
+ },
44
+ "dependencies": {
45
+ "@solana/addresses": "^8.3.0",
46
+ "@solana/instructions": "^8.3.0",
47
+ "@solana/rpc-types": "^8.3.0",
48
+ "@solana/spl-token": "^0.4.15",
49
+ "@solana/transaction-messages": "^8.3.0",
50
+ "@solana/transactions": "^8.3.0",
51
+ "@solana/web3.js": "^1.98.4"
52
+ },
53
+ "devDependencies": {
54
+ "typescript": "^5.0.0"
55
+ }
56
+ }