@agentwonderland/mcp 0.1.38 → 0.1.39

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Credential, Method, Mppx, z } from "../mpp-client.js";
3
+ describe("local MPP client", () => {
4
+ it("handles a 402 challenge and retries with a payment credential", async () => {
5
+ const challengeRequest = Buffer.from(JSON.stringify({
6
+ amount: "1000000",
7
+ currency: "USDC",
8
+ recipient: "0x0000000000000000000000000000000000000001",
9
+ })).toString("base64url");
10
+ const fetchMock = vi
11
+ .fn()
12
+ .mockResolvedValueOnce(new Response("payment required", {
13
+ status: 402,
14
+ headers: {
15
+ "WWW-Authenticate": `Payment id="test-id", realm="api.test", method="base", intent="charge", request="${challengeRequest}"`,
16
+ },
17
+ }))
18
+ .mockResolvedValueOnce(new Response("ok", { status: 200 }));
19
+ const method = Method.toClient(Method.from({
20
+ name: "base",
21
+ intent: "charge",
22
+ schema: {
23
+ credential: { payload: z.object({ type: z.literal("hash"), hash: z.string() }) },
24
+ request: z.object({ amount: z.string() }),
25
+ },
26
+ }), {
27
+ createCredential({ challenge }) {
28
+ return Credential.serialize({
29
+ challenge,
30
+ payload: { type: "hash", hash: "0xabc" },
31
+ source: "did:pkh:eip155:8453:0x0000000000000000000000000000000000000002",
32
+ });
33
+ },
34
+ });
35
+ const client = Mppx.create({ fetch: fetchMock, methods: [method], polyfill: false });
36
+ const response = await client.fetch("https://api.test/agents/example/run", {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: "{}",
40
+ });
41
+ expect(response.status).toBe(200);
42
+ expect(fetchMock).toHaveBeenCalledTimes(2);
43
+ const retryInit = fetchMock.mock.calls[1]?.[1];
44
+ expect(new Headers(retryInit?.headers).get("Authorization")).toMatch(/^Payment\s+/);
45
+ });
46
+ });
@@ -22,7 +22,7 @@ vi.mock("../config.js", () => ({
22
22
  getWallets: () => currentWallets,
23
23
  resolveWalletAndChain: () => currentResolvedMethod,
24
24
  }));
25
- vi.mock("mppx/client", () => ({
25
+ vi.mock("../mpp-client.js", () => ({
26
26
  Mppx: {
27
27
  create: (config) => mockMppxCreate(config),
28
28
  },
@@ -4,7 +4,7 @@
4
4
  * Signs and sends a standard ERC-20 transfer on Base chain, then returns
5
5
  * the tx hash as a credential. Plugs into mppx's compose/dispatch system.
6
6
  */
7
- import { Method, Credential, z } from "mppx";
7
+ import { Method, Credential, z } from "./mpp-client.js";
8
8
  import { toAtomicAmount } from "./amount-utils.js";
9
9
  // Base USDC (Circle native)
10
10
  const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
@@ -0,0 +1,62 @@
1
+ import * as z from "zod/mini";
2
+ type Challenge = {
3
+ id: string;
4
+ realm: string;
5
+ method: string;
6
+ intent: string;
7
+ request: Record<string, unknown>;
8
+ description?: string;
9
+ digest?: string;
10
+ expires?: string;
11
+ opaque?: Record<string, string>;
12
+ };
13
+ type ClientMethod = {
14
+ name: string;
15
+ intent: string;
16
+ context?: {
17
+ parse(value: unknown): unknown;
18
+ };
19
+ createCredential(args: {
20
+ challenge: Challenge;
21
+ context?: unknown;
22
+ }): Promise<string> | string;
23
+ };
24
+ type MethodDefinition = {
25
+ name: string;
26
+ intent: string;
27
+ schema?: unknown;
28
+ };
29
+ type CreateConfig = {
30
+ methods: ClientMethod[];
31
+ fetch?: typeof fetch;
32
+ polyfill?: boolean;
33
+ };
34
+ type CredentialInput = {
35
+ challenge: Challenge;
36
+ payload: unknown;
37
+ source?: string;
38
+ };
39
+ export { z };
40
+ export declare const Method: {
41
+ from<T extends MethodDefinition>(method: T): T;
42
+ toClient<T extends MethodDefinition>(method: T, options: {
43
+ context?: {
44
+ parse(value: unknown): unknown;
45
+ };
46
+ createCredential(args: {
47
+ challenge: Challenge;
48
+ context?: unknown;
49
+ }): Promise<string> | string;
50
+ }): T & ClientMethod;
51
+ };
52
+ export declare const Credential: {
53
+ serialize(credential: CredentialInput): string;
54
+ };
55
+ export declare const Mppx: {
56
+ create(config: CreateConfig): {
57
+ fetch: typeof fetch;
58
+ methods: ClientMethod[];
59
+ rawFetch: typeof fetch;
60
+ };
61
+ };
62
+ export declare function stripe(_parameters?: unknown): ClientMethod;
@@ -0,0 +1,208 @@
1
+ import * as z from "zod/mini";
2
+ const PAYMENT_FETCH_WRAPPER = Symbol.for("agentwonderland.mpp.fetch.wrapper");
3
+ export { z };
4
+ export const Method = {
5
+ from(method) {
6
+ return method;
7
+ },
8
+ toClient(method, options) {
9
+ return {
10
+ ...method,
11
+ context: options.context,
12
+ createCredential: options.createCredential,
13
+ };
14
+ },
15
+ };
16
+ export const Credential = {
17
+ serialize(credential) {
18
+ const wire = {
19
+ challenge: {
20
+ ...credential.challenge,
21
+ request: serializePaymentRequest(credential.challenge.request),
22
+ },
23
+ payload: credential.payload,
24
+ ...(credential.source ? { source: credential.source } : {}),
25
+ };
26
+ return `Payment ${base64UrlEncode(JSON.stringify(wire))}`;
27
+ },
28
+ };
29
+ export const Mppx = {
30
+ create(config) {
31
+ const rawFetch = unwrapFetch(config.fetch ?? globalThis.fetch);
32
+ const methods = config.methods.flat();
33
+ const paymentFetch = createPaymentFetch(rawFetch, methods);
34
+ if (config.polyfill) {
35
+ globalThis.fetch = paymentFetch;
36
+ }
37
+ return {
38
+ fetch: paymentFetch,
39
+ methods,
40
+ rawFetch,
41
+ };
42
+ },
43
+ };
44
+ export function stripe(_parameters) {
45
+ throw new Error("Stripe card payments are temporarily unavailable.");
46
+ }
47
+ function createPaymentFetch(baseFetch, methods) {
48
+ const wrapped = (async (input, init) => {
49
+ const response = await baseFetch(input, init);
50
+ if (response.status !== 402) {
51
+ return response;
52
+ }
53
+ const challenges = challengesFromResponse(response);
54
+ const method = methods.find((candidate) => challenges.some((challenge) => challenge.method === candidate.name && challenge.intent === candidate.intent));
55
+ const challenge = method
56
+ ? challenges.find((candidate) => candidate.method === method.name && candidate.intent === method.intent)
57
+ : undefined;
58
+ if (!method || !challenge) {
59
+ throw new Error(`No method found for challenges: ${challenges.map((item) => `${item.method}.${item.intent}`).join(", ")}. ` +
60
+ `Available: ${methods.map((item) => `${item.name}.${item.intent}`).join(", ")}`);
61
+ }
62
+ const context = init?.context;
63
+ const parsedContext = method.context && context !== undefined ? method.context.parse(context) : undefined;
64
+ const credential = await method.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
65
+ return baseFetch(input, {
66
+ ...init,
67
+ headers: withAuthorizationHeader(init?.headers, credential),
68
+ });
69
+ });
70
+ wrapped[PAYMENT_FETCH_WRAPPER] = baseFetch;
71
+ return wrapped;
72
+ }
73
+ function challengesFromResponse(response) {
74
+ const header = response.headers.get("WWW-Authenticate");
75
+ if (!header) {
76
+ throw new Error("Missing WWW-Authenticate header.");
77
+ }
78
+ const starts = Array.from(header.matchAll(/Payment\s+/gi), (match) => match.index).filter((index) => index !== undefined);
79
+ if (starts.length === 0) {
80
+ throw new Error("No Payment schemes found.");
81
+ }
82
+ return starts.map((start, index) => {
83
+ const end = index + 1 < starts.length ? starts[index + 1] : header.length;
84
+ return deserializeChallenge(header.slice(start, end).replace(/,\s*$/, ""));
85
+ });
86
+ }
87
+ function deserializeChallenge(value) {
88
+ const paramsStart = value.search(/\s/);
89
+ if (!/^Payment\s+/i.test(value) || paramsStart < 0) {
90
+ throw new Error("Missing Payment scheme.");
91
+ }
92
+ const params = parseAuthParams(value.slice(paramsStart + 1));
93
+ if (!params.request) {
94
+ throw new Error("Missing request parameter.");
95
+ }
96
+ if (!params.id || !params.realm || !params.method || !params.intent) {
97
+ throw new Error("Malformed payment challenge.");
98
+ }
99
+ return {
100
+ id: params.id,
101
+ realm: params.realm,
102
+ method: params.method,
103
+ intent: params.intent,
104
+ request: deserializePaymentRequest(params.request),
105
+ ...(params.description ? { description: params.description } : {}),
106
+ ...(params.digest ? { digest: params.digest } : {}),
107
+ ...(params.expires ? { expires: params.expires } : {}),
108
+ ...(params.opaque ? { opaque: deserializePaymentRequest(params.opaque) } : {}),
109
+ };
110
+ }
111
+ function parseAuthParams(input) {
112
+ const result = {};
113
+ let index = 0;
114
+ while (index < input.length) {
115
+ while (index < input.length && /[\s,]/.test(input[index] ?? ""))
116
+ index++;
117
+ if (index >= input.length)
118
+ break;
119
+ const keyStart = index;
120
+ while (index < input.length && /[A-Za-z0-9_-]/.test(input[index] ?? ""))
121
+ index++;
122
+ const key = input.slice(keyStart, index);
123
+ if (!key)
124
+ throw new Error("Malformed auth-param.");
125
+ while (index < input.length && /\s/.test(input[index] ?? ""))
126
+ index++;
127
+ if (input[index] !== "=")
128
+ break;
129
+ index++;
130
+ while (index < input.length && /\s/.test(input[index] ?? ""))
131
+ index++;
132
+ const [value, nextIndex] = readAuthParamValue(input, index);
133
+ result[key] = value;
134
+ index = nextIndex;
135
+ }
136
+ return result;
137
+ }
138
+ function readAuthParamValue(input, start) {
139
+ if (input[start] !== "\"") {
140
+ let index = start;
141
+ while (index < input.length && input[index] !== ",")
142
+ index++;
143
+ return [input.slice(start, index).trim(), index];
144
+ }
145
+ let index = start + 1;
146
+ let value = "";
147
+ let escaped = false;
148
+ while (index < input.length) {
149
+ const char = input[index];
150
+ index++;
151
+ if (escaped) {
152
+ value += char;
153
+ escaped = false;
154
+ continue;
155
+ }
156
+ if (char === "\\") {
157
+ escaped = true;
158
+ continue;
159
+ }
160
+ if (char === "\"") {
161
+ return [value, index];
162
+ }
163
+ value += char;
164
+ }
165
+ throw new Error("Unterminated quoted-string.");
166
+ }
167
+ function withAuthorizationHeader(headers, credential) {
168
+ const next = new Headers(headers);
169
+ next.set("Authorization", credential);
170
+ return next;
171
+ }
172
+ function unwrapFetch(candidate) {
173
+ let current = candidate;
174
+ while (current[PAYMENT_FETCH_WRAPPER]) {
175
+ current = current[PAYMENT_FETCH_WRAPPER];
176
+ }
177
+ return current;
178
+ }
179
+ function deserializePaymentRequest(encoded) {
180
+ return JSON.parse(base64UrlDecode(encoded));
181
+ }
182
+ function serializePaymentRequest(request) {
183
+ return base64UrlEncode(stableStringify(request));
184
+ }
185
+ function stableStringify(value) {
186
+ if (value === null || typeof value !== "object") {
187
+ return JSON.stringify(value);
188
+ }
189
+ if (Array.isArray(value)) {
190
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
191
+ }
192
+ const record = value;
193
+ return `{${Object.keys(record)
194
+ .sort()
195
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
196
+ .join(",")}}`;
197
+ }
198
+ function base64UrlEncode(value) {
199
+ return Buffer.from(value, "utf8")
200
+ .toString("base64")
201
+ .replace(/=/g, "")
202
+ .replace(/\+/g, "-")
203
+ .replace(/\//g, "_");
204
+ }
205
+ function base64UrlDecode(value) {
206
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
207
+ return Buffer.from(padded, "base64").toString("utf8");
208
+ }
@@ -54,7 +54,7 @@ function clearStaleCardCache(activeKey) {
54
54
  // ── Per-protocol initializers ───────────────────────────────────
55
55
  async function initEvmMppForChain(wallet, chain) {
56
56
  try {
57
- const { Mppx } = await import("mppx/client");
57
+ const { Mppx } = await import("./mpp-client.js");
58
58
  let account;
59
59
  if (wallet.keyType === "ows" && wallet.owsWalletId) {
60
60
  const { owsAccountFromWalletId } = await import("./ows-adapter.js");
@@ -88,7 +88,7 @@ async function initSolanaMpp(wallet) {
88
88
  if (wallet.keyType !== "ows" && !wallet.key) {
89
89
  return null;
90
90
  }
91
- const { Mppx } = await import("mppx/client");
91
+ const { Mppx } = await import("./mpp-client.js");
92
92
  const { solanaChargeClient } = await import("./solana-charge.js");
93
93
  const mppx = Mppx.create({
94
94
  methods: [solanaChargeClient({ wallet })],
@@ -105,7 +105,7 @@ async function initCard() {
105
105
  if (!cardConfig)
106
106
  return null;
107
107
  try {
108
- const { Mppx, stripe } = await import("mppx/client");
108
+ const { Mppx, stripe } = await import("./mpp-client.js");
109
109
  const apiUrl = getApiUrl();
110
110
  const pmId = cardConfig.paymentMethodId ?? undefined;
111
111
  const mppx = Mppx.create({
@@ -4,7 +4,7 @@
4
4
  * Sends an SPL Token transfer on Solana mainnet and returns the transaction
5
5
  * signature as the payment credential.
6
6
  */
7
- import { Credential, Method, z } from "mppx";
7
+ import { Credential, Method, z } from "./mpp-client.js";
8
8
  import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
9
9
  import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddressSync, } from "@solana/spl-token";
10
10
  import { toAtomicAmount } from "./amount-utils.js";
@@ -4,7 +4,7 @@
4
4
  * Sends a standard ERC-20 transfer on Tempo and returns the tx hash as the
5
5
  * payment credential.
6
6
  */
7
- import { Method, Credential, z } from "mppx";
7
+ import { Method, Credential, z } from "./mpp-client.js";
8
8
  import { toAtomicAmount } from "./amount-utils.js";
9
9
  const TEMPO_USDC = "0x20c000000000000000000000b9537d11c60e8b50";
10
10
  const PATH_USD = "0x20c0000000000000000000000000000000000000";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -24,8 +24,7 @@
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.12.1",
26
26
  "@solana/spl-token": "^0.4.14",
27
- "@solana/web3.js": "^1.98.4",
28
- "mppx": "^0.5.10",
27
+ "@solana/web3.js": "1.95.8",
29
28
  "qrcode": "^1.5.4",
30
29
  "viem": "^2.47.6",
31
30
  "zod": "^4.3.6"
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Credential, Method, Mppx, z } from "../mpp-client.js";
3
+
4
+ describe("local MPP client", () => {
5
+ it("handles a 402 challenge and retries with a payment credential", async () => {
6
+ const challengeRequest = Buffer.from(JSON.stringify({
7
+ amount: "1000000",
8
+ currency: "USDC",
9
+ recipient: "0x0000000000000000000000000000000000000001",
10
+ })).toString("base64url");
11
+ const fetchMock = vi
12
+ .fn<typeof fetch>()
13
+ .mockResolvedValueOnce(new Response("payment required", {
14
+ status: 402,
15
+ headers: {
16
+ "WWW-Authenticate": `Payment id="test-id", realm="api.test", method="base", intent="charge", request="${challengeRequest}"`,
17
+ },
18
+ }))
19
+ .mockResolvedValueOnce(new Response("ok", { status: 200 }));
20
+
21
+ const method = Method.toClient(
22
+ Method.from({
23
+ name: "base",
24
+ intent: "charge",
25
+ schema: {
26
+ credential: { payload: z.object({ type: z.literal("hash"), hash: z.string() }) },
27
+ request: z.object({ amount: z.string() }),
28
+ },
29
+ }),
30
+ {
31
+ createCredential({ challenge }) {
32
+ return Credential.serialize({
33
+ challenge,
34
+ payload: { type: "hash", hash: "0xabc" },
35
+ source: "did:pkh:eip155:8453:0x0000000000000000000000000000000000000002",
36
+ });
37
+ },
38
+ },
39
+ );
40
+
41
+ const client = Mppx.create({ fetch: fetchMock, methods: [method], polyfill: false });
42
+ const response = await client.fetch("https://api.test/agents/example/run", {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: "{}",
46
+ });
47
+
48
+ expect(response.status).toBe(200);
49
+ expect(fetchMock).toHaveBeenCalledTimes(2);
50
+ const retryInit = fetchMock.mock.calls[1]?.[1];
51
+ expect(new Headers(retryInit?.headers).get("Authorization")).toMatch(/^Payment\s+/);
52
+ });
53
+ });
@@ -28,7 +28,7 @@ vi.mock("../config.js", () => ({
28
28
  resolveWalletAndChain: () => currentResolvedMethod,
29
29
  }));
30
30
 
31
- vi.mock("mppx/client", () => ({
31
+ vi.mock("../mpp-client.js", () => ({
32
32
  Mppx: {
33
33
  create: (config: unknown) => mockMppxCreate(config),
34
34
  },
@@ -4,7 +4,7 @@
4
4
  * Signs and sends a standard ERC-20 transfer on Base chain, then returns
5
5
  * the tx hash as a credential. Plugs into mppx's compose/dispatch system.
6
6
  */
7
- import { Method, Credential, z } from "mppx";
7
+ import { Method, Credential, z } from "./mpp-client.js";
8
8
  import type { LocalAccount } from "viem/accounts";
9
9
  import { toAtomicAmount } from "./amount-utils.js";
10
10
 
@@ -0,0 +1,286 @@
1
+ import * as z from "zod/mini";
2
+
3
+ type Challenge = {
4
+ id: string;
5
+ realm: string;
6
+ method: string;
7
+ intent: string;
8
+ request: Record<string, unknown>;
9
+ description?: string;
10
+ digest?: string;
11
+ expires?: string;
12
+ opaque?: Record<string, string>;
13
+ };
14
+
15
+ type ClientMethod = {
16
+ name: string;
17
+ intent: string;
18
+ context?: { parse(value: unknown): unknown };
19
+ createCredential(args: { challenge: Challenge; context?: unknown }): Promise<string> | string;
20
+ };
21
+
22
+ type MethodDefinition = {
23
+ name: string;
24
+ intent: string;
25
+ schema?: unknown;
26
+ };
27
+
28
+ type CreateConfig = {
29
+ methods: ClientMethod[];
30
+ fetch?: typeof fetch;
31
+ polyfill?: boolean;
32
+ };
33
+
34
+ type CredentialInput = {
35
+ challenge: Challenge;
36
+ payload: unknown;
37
+ source?: string;
38
+ };
39
+
40
+ const PAYMENT_FETCH_WRAPPER = Symbol.for("agentwonderland.mpp.fetch.wrapper");
41
+
42
+ export { z };
43
+
44
+ export const Method = {
45
+ from<T extends MethodDefinition>(method: T): T {
46
+ return method;
47
+ },
48
+
49
+ toClient<T extends MethodDefinition>(
50
+ method: T,
51
+ options: {
52
+ context?: { parse(value: unknown): unknown };
53
+ createCredential(args: { challenge: Challenge; context?: unknown }): Promise<string> | string;
54
+ },
55
+ ): T & ClientMethod {
56
+ return {
57
+ ...method,
58
+ context: options.context,
59
+ createCredential: options.createCredential,
60
+ };
61
+ },
62
+ };
63
+
64
+ export const Credential = {
65
+ serialize(credential: CredentialInput): string {
66
+ const wire = {
67
+ challenge: {
68
+ ...credential.challenge,
69
+ request: serializePaymentRequest(credential.challenge.request),
70
+ },
71
+ payload: credential.payload,
72
+ ...(credential.source ? { source: credential.source } : {}),
73
+ };
74
+ return `Payment ${base64UrlEncode(JSON.stringify(wire))}`;
75
+ },
76
+ };
77
+
78
+ export const Mppx = {
79
+ create(config: CreateConfig): { fetch: typeof fetch; methods: ClientMethod[]; rawFetch: typeof fetch } {
80
+ const rawFetch = unwrapFetch(config.fetch ?? globalThis.fetch);
81
+ const methods = config.methods.flat();
82
+ const paymentFetch = createPaymentFetch(rawFetch, methods);
83
+
84
+ if (config.polyfill) {
85
+ globalThis.fetch = paymentFetch;
86
+ }
87
+
88
+ return {
89
+ fetch: paymentFetch,
90
+ methods,
91
+ rawFetch,
92
+ };
93
+ },
94
+ };
95
+
96
+ export function stripe(_parameters?: unknown): ClientMethod {
97
+ throw new Error("Stripe card payments are temporarily unavailable.");
98
+ }
99
+
100
+ function createPaymentFetch(baseFetch: typeof fetch, methods: ClientMethod[]): typeof fetch {
101
+ const wrapped = (async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
102
+ const response = await baseFetch(input, init);
103
+ if (response.status !== 402) {
104
+ return response;
105
+ }
106
+
107
+ const challenges = challengesFromResponse(response);
108
+ const method = methods.find((candidate) =>
109
+ challenges.some((challenge) => challenge.method === candidate.name && challenge.intent === candidate.intent),
110
+ );
111
+ const challenge = method
112
+ ? challenges.find((candidate) => candidate.method === method.name && candidate.intent === method.intent)
113
+ : undefined;
114
+
115
+ if (!method || !challenge) {
116
+ throw new Error(
117
+ `No method found for challenges: ${challenges.map((item) => `${item.method}.${item.intent}`).join(", ")}. ` +
118
+ `Available: ${methods.map((item) => `${item.name}.${item.intent}`).join(", ")}`,
119
+ );
120
+ }
121
+
122
+ const context = (init as RequestInit & { context?: unknown } | undefined)?.context;
123
+ const parsedContext = method.context && context !== undefined ? method.context.parse(context) : undefined;
124
+ const credential = await method.createCredential(
125
+ parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge },
126
+ );
127
+
128
+ return baseFetch(input, {
129
+ ...init,
130
+ headers: withAuthorizationHeader(init?.headers, credential),
131
+ });
132
+ }) as typeof fetch & { [PAYMENT_FETCH_WRAPPER]?: typeof fetch };
133
+
134
+ wrapped[PAYMENT_FETCH_WRAPPER] = baseFetch;
135
+ return wrapped;
136
+ }
137
+
138
+ function challengesFromResponse(response: Response): Challenge[] {
139
+ const header = response.headers.get("WWW-Authenticate");
140
+ if (!header) {
141
+ throw new Error("Missing WWW-Authenticate header.");
142
+ }
143
+
144
+ const starts = Array.from(header.matchAll(/Payment\s+/gi), (match) => match.index).filter((index) => index !== undefined);
145
+ if (starts.length === 0) {
146
+ throw new Error("No Payment schemes found.");
147
+ }
148
+
149
+ return starts.map((start, index) => {
150
+ const end = index + 1 < starts.length ? starts[index + 1] : header.length;
151
+ return deserializeChallenge(header.slice(start, end).replace(/,\s*$/, ""));
152
+ });
153
+ }
154
+
155
+ function deserializeChallenge(value: string): Challenge {
156
+ const paramsStart = value.search(/\s/);
157
+ if (!/^Payment\s+/i.test(value) || paramsStart < 0) {
158
+ throw new Error("Missing Payment scheme.");
159
+ }
160
+
161
+ const params = parseAuthParams(value.slice(paramsStart + 1));
162
+ if (!params.request) {
163
+ throw new Error("Missing request parameter.");
164
+ }
165
+ if (!params.id || !params.realm || !params.method || !params.intent) {
166
+ throw new Error("Malformed payment challenge.");
167
+ }
168
+
169
+ return {
170
+ id: params.id,
171
+ realm: params.realm,
172
+ method: params.method,
173
+ intent: params.intent,
174
+ request: deserializePaymentRequest(params.request),
175
+ ...(params.description ? { description: params.description } : {}),
176
+ ...(params.digest ? { digest: params.digest } : {}),
177
+ ...(params.expires ? { expires: params.expires } : {}),
178
+ ...(params.opaque ? { opaque: deserializePaymentRequest(params.opaque) as Record<string, string> } : {}),
179
+ };
180
+ }
181
+
182
+ function parseAuthParams(input: string): Record<string, string> {
183
+ const result: Record<string, string> = {};
184
+ let index = 0;
185
+
186
+ while (index < input.length) {
187
+ while (index < input.length && /[\s,]/.test(input[index] ?? "")) index++;
188
+ if (index >= input.length) break;
189
+
190
+ const keyStart = index;
191
+ while (index < input.length && /[A-Za-z0-9_-]/.test(input[index] ?? "")) index++;
192
+ const key = input.slice(keyStart, index);
193
+ if (!key) throw new Error("Malformed auth-param.");
194
+
195
+ while (index < input.length && /\s/.test(input[index] ?? "")) index++;
196
+ if (input[index] !== "=") break;
197
+ index++;
198
+ while (index < input.length && /\s/.test(input[index] ?? "")) index++;
199
+
200
+ const [value, nextIndex] = readAuthParamValue(input, index);
201
+ result[key] = value;
202
+ index = nextIndex;
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ function readAuthParamValue(input: string, start: number): [string, number] {
209
+ if (input[start] !== "\"") {
210
+ let index = start;
211
+ while (index < input.length && input[index] !== ",") index++;
212
+ return [input.slice(start, index).trim(), index];
213
+ }
214
+
215
+ let index = start + 1;
216
+ let value = "";
217
+ let escaped = false;
218
+ while (index < input.length) {
219
+ const char = input[index];
220
+ index++;
221
+ if (escaped) {
222
+ value += char;
223
+ escaped = false;
224
+ continue;
225
+ }
226
+ if (char === "\\") {
227
+ escaped = true;
228
+ continue;
229
+ }
230
+ if (char === "\"") {
231
+ return [value, index];
232
+ }
233
+ value += char;
234
+ }
235
+ throw new Error("Unterminated quoted-string.");
236
+ }
237
+
238
+ function withAuthorizationHeader(headers: HeadersInit | undefined, credential: string): HeadersInit {
239
+ const next = new Headers(headers);
240
+ next.set("Authorization", credential);
241
+ return next;
242
+ }
243
+
244
+ function unwrapFetch(candidate: typeof fetch): typeof fetch {
245
+ let current = candidate as typeof fetch & { [PAYMENT_FETCH_WRAPPER]?: typeof fetch };
246
+ while (current[PAYMENT_FETCH_WRAPPER]) {
247
+ current = current[PAYMENT_FETCH_WRAPPER] as typeof fetch & { [PAYMENT_FETCH_WRAPPER]?: typeof fetch };
248
+ }
249
+ return current;
250
+ }
251
+
252
+ function deserializePaymentRequest(encoded: string): Record<string, unknown> {
253
+ return JSON.parse(base64UrlDecode(encoded)) as Record<string, unknown>;
254
+ }
255
+
256
+ function serializePaymentRequest(request: Record<string, unknown>): string {
257
+ return base64UrlEncode(stableStringify(request));
258
+ }
259
+
260
+ function stableStringify(value: unknown): string {
261
+ if (value === null || typeof value !== "object") {
262
+ return JSON.stringify(value);
263
+ }
264
+ if (Array.isArray(value)) {
265
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
266
+ }
267
+
268
+ const record = value as Record<string, unknown>;
269
+ return `{${Object.keys(record)
270
+ .sort()
271
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
272
+ .join(",")}}`;
273
+ }
274
+
275
+ function base64UrlEncode(value: string): string {
276
+ return Buffer.from(value, "utf8")
277
+ .toString("base64")
278
+ .replace(/=/g, "")
279
+ .replace(/\+/g, "-")
280
+ .replace(/\//g, "_");
281
+ }
282
+
283
+ function base64UrlDecode(value: string): string {
284
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
285
+ return Buffer.from(padded, "base64").toString("utf8");
286
+ }
@@ -77,7 +77,7 @@ async function initEvmMppForChain(
77
77
  chain: "tempo" | "base",
78
78
  ): Promise<typeof fetch | null> {
79
79
  try {
80
- const { Mppx } = await import("mppx/client");
80
+ const { Mppx } = await import("./mpp-client.js");
81
81
  let account;
82
82
 
83
83
  if (wallet.keyType === "ows" && wallet.owsWalletId) {
@@ -112,7 +112,7 @@ async function initSolanaMpp(wallet: WalletEntry): Promise<typeof fetch | null>
112
112
  return null;
113
113
  }
114
114
 
115
- const { Mppx } = await import("mppx/client");
115
+ const { Mppx } = await import("./mpp-client.js");
116
116
  const { solanaChargeClient } = await import("./solana-charge.js");
117
117
  const mppx = Mppx.create({
118
118
  methods: [solanaChargeClient({ wallet })] as any,
@@ -129,7 +129,7 @@ async function initCard(): Promise<typeof fetch | null> {
129
129
  if (!cardConfig) return null;
130
130
 
131
131
  try {
132
- const { Mppx, stripe } = await import("mppx/client");
132
+ const { Mppx, stripe } = await import("./mpp-client.js");
133
133
  const apiUrl = getApiUrl();
134
134
  const pmId = cardConfig.paymentMethodId ?? undefined;
135
135
  const mppx = Mppx.create({
@@ -4,7 +4,7 @@
4
4
  * Sends an SPL Token transfer on Solana mainnet and returns the transaction
5
5
  * signature as the payment credential.
6
6
  */
7
- import { Credential, Method, z } from "mppx";
7
+ import { Credential, Method, z } from "./mpp-client.js";
8
8
  import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
9
9
  import {
10
10
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -4,7 +4,7 @@
4
4
  * Sends a standard ERC-20 transfer on Tempo and returns the tx hash as the
5
5
  * payment credential.
6
6
  */
7
- import { Method, Credential, z } from "mppx";
7
+ import { Method, Credential, z } from "./mpp-client.js";
8
8
  import type { LocalAccount } from "viem/accounts";
9
9
  import { toAtomicAmount } from "./amount-utils.js";
10
10