@agentwonderland/mcp 0.1.23 → 0.1.25

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 (95) hide show
  1. package/dist/core/__tests__/amount-utils.test.d.ts +1 -0
  2. package/dist/core/__tests__/amount-utils.test.js +11 -0
  3. package/dist/core/__tests__/card-setup.test.d.ts +1 -0
  4. package/dist/core/__tests__/card-setup.test.js +99 -0
  5. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  6. package/dist/core/__tests__/formatters.test.js +15 -0
  7. package/dist/core/__tests__/passes.test.d.ts +1 -0
  8. package/dist/core/__tests__/passes.test.js +82 -0
  9. package/dist/core/__tests__/payments.test.d.ts +1 -0
  10. package/dist/core/__tests__/payments.test.js +101 -0
  11. package/dist/core/__tests__/principal.test.d.ts +1 -0
  12. package/dist/core/__tests__/principal.test.js +67 -0
  13. package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
  14. package/dist/core/__tests__/spend-policy.test.js +40 -0
  15. package/dist/core/amount-utils.d.ts +1 -0
  16. package/dist/core/amount-utils.js +4 -0
  17. package/dist/core/api-client.d.ts +9 -4
  18. package/dist/core/api-client.js +52 -22
  19. package/dist/core/base-charge.js +3 -2
  20. package/dist/core/card-setup.d.ts +20 -13
  21. package/dist/core/card-setup.js +85 -29
  22. package/dist/core/config.d.ts +22 -0
  23. package/dist/core/config.js +46 -2
  24. package/dist/core/formatters.d.ts +4 -3
  25. package/dist/core/formatters.js +10 -8
  26. package/dist/core/index.d.ts +2 -0
  27. package/dist/core/index.js +2 -0
  28. package/dist/core/ows-adapter.d.ts +10 -2
  29. package/dist/core/ows-adapter.js +54 -10
  30. package/dist/core/passes.d.ts +40 -0
  31. package/dist/core/passes.js +32 -0
  32. package/dist/core/payments.d.ts +8 -0
  33. package/dist/core/payments.js +111 -17
  34. package/dist/core/principal.d.ts +2 -0
  35. package/dist/core/principal.js +109 -0
  36. package/dist/core/solana-charge.d.ts +9 -0
  37. package/dist/core/solana-charge.js +96 -0
  38. package/dist/core/spend-policy.d.ts +12 -0
  39. package/dist/core/spend-policy.js +53 -0
  40. package/dist/core/types.d.ts +11 -2
  41. package/dist/index.js +11 -3
  42. package/dist/prompts/index.js +4 -2
  43. package/dist/resources/agents.js +1 -1
  44. package/dist/resources/wallet.js +8 -1
  45. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  46. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  47. package/dist/tools/_payment-confirmation.d.ts +6 -0
  48. package/dist/tools/_payment-confirmation.js +28 -0
  49. package/dist/tools/agent-info.js +16 -2
  50. package/dist/tools/favorites.js +1 -1
  51. package/dist/tools/index.d.ts +1 -0
  52. package/dist/tools/index.js +1 -0
  53. package/dist/tools/observability.d.ts +2 -0
  54. package/dist/tools/observability.js +20 -0
  55. package/dist/tools/passes.d.ts +2 -0
  56. package/dist/tools/passes.js +157 -0
  57. package/dist/tools/run.js +127 -53
  58. package/dist/tools/solve.js +115 -51
  59. package/dist/tools/wallet.js +110 -59
  60. package/package.json +3 -1
  61. package/src/core/__tests__/amount-utils.test.ts +13 -0
  62. package/src/core/__tests__/card-setup.test.ts +118 -0
  63. package/src/core/__tests__/formatters.test.ts +17 -0
  64. package/src/core/__tests__/passes.test.ts +94 -0
  65. package/src/core/__tests__/payments.test.ts +122 -0
  66. package/src/core/__tests__/principal.test.ts +87 -0
  67. package/src/core/__tests__/spend-policy.test.ts +58 -0
  68. package/src/core/amount-utils.ts +5 -0
  69. package/src/core/api-client.ts +70 -23
  70. package/src/core/base-charge.ts +3 -2
  71. package/src/core/card-setup.ts +109 -34
  72. package/src/core/config.ts +74 -3
  73. package/src/core/formatters.ts +13 -9
  74. package/src/core/index.ts +2 -0
  75. package/src/core/ows-adapter.ts +74 -8
  76. package/src/core/passes.ts +74 -0
  77. package/src/core/payments.ts +130 -17
  78. package/src/core/principal.ts +128 -0
  79. package/src/core/solana-charge.ts +150 -0
  80. package/src/core/spend-policy.ts +69 -0
  81. package/src/core/types.ts +11 -2
  82. package/src/index.ts +11 -3
  83. package/src/prompts/index.ts +4 -2
  84. package/src/resources/agents.ts +1 -1
  85. package/src/resources/wallet.ts +8 -1
  86. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  87. package/src/tools/_payment-confirmation.ts +52 -0
  88. package/src/tools/agent-info.ts +25 -2
  89. package/src/tools/favorites.ts +1 -4
  90. package/src/tools/index.ts +1 -0
  91. package/src/tools/observability.ts +43 -0
  92. package/src/tools/passes.ts +228 -0
  93. package/src/tools/run.ts +174 -57
  94. package/src/tools/solve.ts +147 -59
  95. package/src/tools/wallet.ts +132 -62
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { toAtomicAmount } from "../amount-utils.js";
3
+ describe("toAtomicAmount", () => {
4
+ it("converts decimal USDC challenge amounts into atomic units", () => {
5
+ expect(toAtomicAmount("0.020000")).toBe(20000n);
6
+ expect(toAtomicAmount("1.000000")).toBe(1000000n);
7
+ });
8
+ it("supports different decimal precisions", () => {
9
+ expect(toAtomicAmount("0.50", 2)).toBe(50n);
10
+ });
11
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,99 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ const mockApiGet = vi.fn();
3
+ const mockApiPost = vi.fn();
4
+ class MockApiError extends Error {
5
+ status;
6
+ constructor(status, message) {
7
+ super(message);
8
+ this.status = status;
9
+ this.name = "ApiError";
10
+ }
11
+ }
12
+ let pendingToken = null;
13
+ const savedCards = [];
14
+ vi.mock("../api-client.js", () => ({
15
+ ApiError: MockApiError,
16
+ apiGet: (...args) => mockApiGet(...args),
17
+ apiPost: (...args) => mockApiPost(...args),
18
+ }));
19
+ vi.mock("../config.js", () => ({
20
+ getApiUrl: () => "http://api.test",
21
+ getPendingCardSetupToken: () => pendingToken,
22
+ setCardConfig: (card) => {
23
+ savedCards.push(card);
24
+ },
25
+ setPendingCardSetupToken: (token) => {
26
+ pendingToken = token;
27
+ },
28
+ }));
29
+ describe("card setup helpers", () => {
30
+ beforeEach(() => {
31
+ vi.clearAllMocks();
32
+ pendingToken = null;
33
+ savedCards.length = 0;
34
+ });
35
+ it("reuses the existing pending setup token", async () => {
36
+ pendingToken = "tok_existing";
37
+ const { getOrCreatePendingCardSetup } = await import("../card-setup.js");
38
+ const result = await getOrCreatePendingCardSetup();
39
+ expect(mockApiPost).not.toHaveBeenCalled();
40
+ expect(result).toMatchObject({
41
+ token: "tok_existing",
42
+ url: "http://api.test/card/handoff/tok_existing",
43
+ isNew: false,
44
+ });
45
+ });
46
+ it("stores a newly created setup token for later resume", async () => {
47
+ mockApiPost.mockResolvedValueOnce({ token: "tok_new" });
48
+ const { getOrCreatePendingCardSetup } = await import("../card-setup.js");
49
+ const result = await getOrCreatePendingCardSetup();
50
+ expect(mockApiPost).toHaveBeenCalledWith("/card/setup", {});
51
+ expect(pendingToken).toBe("tok_new");
52
+ expect(result).toMatchObject({
53
+ token: "tok_new",
54
+ url: "http://api.test/card/handoff/tok_new",
55
+ isNew: true,
56
+ });
57
+ });
58
+ it("persists the card and clears pending setup on successful completion", async () => {
59
+ pendingToken = "tok_ready";
60
+ mockApiGet.mockResolvedValueOnce({
61
+ status: "complete",
62
+ card_last4: "4242",
63
+ card_brand: "visa",
64
+ consumer_token: "consumer_123",
65
+ payment_method_id: "pm_123",
66
+ });
67
+ const { pollCardSetup } = await import("../card-setup.js");
68
+ const result = await pollCardSetup("tok_ready", 50);
69
+ expect(result).toEqual({
70
+ last4: "4242",
71
+ brand: "visa",
72
+ consumerToken: "consumer_123",
73
+ });
74
+ expect(savedCards).toEqual([{
75
+ consumerToken: "consumer_123",
76
+ paymentMethodId: "pm_123",
77
+ last4: "4242",
78
+ brand: "visa",
79
+ }]);
80
+ expect(pendingToken).toBeNull();
81
+ });
82
+ it("clears expired pending setup tokens", async () => {
83
+ pendingToken = "tok_expired";
84
+ mockApiGet.mockRejectedValueOnce(new MockApiError(404, "Setup not found"));
85
+ const { pollCardSetup } = await import("../card-setup.js");
86
+ const result = await pollCardSetup("tok_expired", 50);
87
+ expect(result).toBeNull();
88
+ expect(pendingToken).toBeNull();
89
+ });
90
+ it("formats card setup as a single visible message with the setup page URL", async () => {
91
+ const { formatCardSetupBlocks } = await import("../card-setup.js");
92
+ const blocks = formatCardSetupBlocks("http://api.test/card/handoff/tok");
93
+ expect(blocks).toHaveLength(1);
94
+ expect(blocks[0]).toContain("Open this setup page to connect your card:");
95
+ expect(blocks[0]).toContain("http://api.test/card/handoff/tok");
96
+ expect(blocks[0]).toContain("securely save the card");
97
+ expect(blocks[0]).not.toContain("QR code above");
98
+ });
99
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatRunResult } from "../formatters.js";
3
+ describe("formatRunResult", () => {
4
+ it("does not show a paid line for credit-pack-backed runs with zero cost", () => {
5
+ const output = formatRunResult({
6
+ agent_name: "Credit Pack Agent",
7
+ status: "success",
8
+ cost: 0,
9
+ consumption_mode: "credit_pack",
10
+ credit_pack_id: "pack-123",
11
+ }, { paymentMethod: "card" });
12
+ expect(output).toContain("Covered by credit pack (pack-123)");
13
+ expect(output).not.toContain("Paid:");
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatCreditPack, formatCreditPackOffer, getActiveCreditPack, getCreditPackProgram, } from "../passes.js";
3
+ const baseAgent = {
4
+ id: "agent-1",
5
+ name: "Test Agent",
6
+ payment: {
7
+ credit_packs: {
8
+ unit_type: "run",
9
+ packs: [
10
+ { key: "starter", name: "Starter Pack", included_units: 5, price_usd: "1.00" },
11
+ { key: "growth", name: "Growth Pack", included_units: 20, price_usd: "3.00" },
12
+ ],
13
+ },
14
+ },
15
+ };
16
+ describe("getCreditPackProgram", () => {
17
+ it("returns the configured credit-pack program from agent metadata", () => {
18
+ expect(getCreditPackProgram(baseAgent)).toEqual({
19
+ unit_type: "run",
20
+ packs: [
21
+ { key: "starter", name: "Starter Pack", included_units: 5, price_usd: "1.00" },
22
+ { key: "growth", name: "Growth Pack", included_units: 20, price_usd: "3.00" },
23
+ ],
24
+ });
25
+ });
26
+ });
27
+ describe("formatCreditPackOffer", () => {
28
+ it("shows units and per-unit price", () => {
29
+ expect(formatCreditPackOffer({
30
+ pack_id: "starter",
31
+ label: "Starter Pack",
32
+ included_units: 5,
33
+ price_usd: "1.00",
34
+ effective_price_per_unit_usd: "0.200000",
35
+ })).toContain("Starter Pack");
36
+ });
37
+ });
38
+ describe("formatCreditPack", () => {
39
+ it("includes the pack status when it is no longer active", () => {
40
+ expect(formatCreditPack({
41
+ id: "pack-1",
42
+ status: "depleted",
43
+ unit_type: "run",
44
+ included_units: 5,
45
+ remaining_units: 0,
46
+ price_usd: "1.00",
47
+ purchased_at: "2026-05-04T15:08:28.700Z",
48
+ pack: { key: "starter", name: "Starter Pack" },
49
+ })).toContain("depleted");
50
+ });
51
+ });
52
+ describe("getActiveCreditPack", () => {
53
+ it("returns the oldest active balance with remaining units", () => {
54
+ const pack = getActiveCreditPack({
55
+ consumer_principal: "did:pkh:eip155:8453:0x1111111111111111111111111111111111111111",
56
+ offers: [],
57
+ balances: [
58
+ {
59
+ id: "newer",
60
+ status: "active",
61
+ unit_type: "run",
62
+ included_units: 20,
63
+ remaining_units: 20,
64
+ price_usd: "3.00",
65
+ purchased_at: "2026-04-10T00:00:00Z",
66
+ pack: { key: "growth", name: "Growth Pack" },
67
+ },
68
+ {
69
+ id: "older",
70
+ status: "active",
71
+ unit_type: "run",
72
+ included_units: 5,
73
+ remaining_units: 2,
74
+ price_usd: "1.00",
75
+ purchased_at: "2026-04-01T00:00:00Z",
76
+ pack: { key: "starter", name: "Starter Pack" },
77
+ },
78
+ ],
79
+ });
80
+ expect(pack?.id).toBe("older");
81
+ });
82
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,101 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ const TEST_KEY = "1111111111111111111111111111111111111111111111111111111111111111";
3
+ let currentCard = {
4
+ consumerToken: "consumer_one",
5
+ paymentMethodId: "pm_one",
6
+ last4: "1111",
7
+ brand: "visa",
8
+ };
9
+ let currentDefaultWallet;
10
+ let currentWallets = [];
11
+ let currentResolvedMethod = null;
12
+ const createdFetches = [vi.fn(), vi.fn(), vi.fn(), vi.fn()];
13
+ const mockMppxCreate = vi.fn();
14
+ const mockStripe = vi.fn((opts) => opts);
15
+ const mockTempo = vi.fn((..._args) => "tempo_method");
16
+ const mockBaseChargeClient = vi.fn((..._args) => "base_method");
17
+ vi.mock("../config.js", () => ({
18
+ getApiUrl: () => "http://api.test",
19
+ getCardConfig: () => currentCard,
20
+ getConfig: () => ({ defaultPaymentMethod: "card" }),
21
+ getDefaultWallet: () => currentDefaultWallet,
22
+ getWallets: () => currentWallets,
23
+ resolveWalletAndChain: () => currentResolvedMethod,
24
+ }));
25
+ vi.mock("mppx/client", () => ({
26
+ Mppx: {
27
+ create: (config) => mockMppxCreate(config),
28
+ },
29
+ stripe: (config) => mockStripe(config),
30
+ tempo: (config) => mockTempo(config),
31
+ }));
32
+ vi.mock("../base-charge.js", () => ({
33
+ baseChargeClient: (config) => mockBaseChargeClient(config),
34
+ }));
35
+ describe("payment method initialization", () => {
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ vi.resetModules();
39
+ currentCard = {
40
+ consumerToken: "consumer_one",
41
+ paymentMethodId: "pm_one",
42
+ last4: "1111",
43
+ brand: "visa",
44
+ };
45
+ currentDefaultWallet = undefined;
46
+ currentWallets = [];
47
+ currentResolvedMethod = null;
48
+ mockMppxCreate
49
+ .mockReturnValueOnce({ fetch: createdFetches[0] })
50
+ .mockReturnValueOnce({ fetch: createdFetches[1] })
51
+ .mockReturnValueOnce({ fetch: createdFetches[2] })
52
+ .mockReturnValueOnce({ fetch: createdFetches[3] });
53
+ });
54
+ it("rebuilds the cached card fetch when the card config changes", async () => {
55
+ const { getPaymentFetch } = await import("../payments.js");
56
+ const firstFetch = await getPaymentFetch("card");
57
+ currentCard = {
58
+ consumerToken: "consumer_two",
59
+ paymentMethodId: "pm_two",
60
+ last4: "2222",
61
+ brand: "mastercard",
62
+ };
63
+ const secondFetch = await getPaymentFetch("card");
64
+ expect(firstFetch).not.toBe(secondFetch);
65
+ expect(mockMppxCreate).toHaveBeenCalledTimes(2);
66
+ });
67
+ it("initializes only the Base method when base is requested", async () => {
68
+ const wallet = {
69
+ id: "aw-main",
70
+ keyType: "raw",
71
+ key: TEST_KEY,
72
+ chains: ["tempo", "base"],
73
+ defaultChain: "base",
74
+ };
75
+ currentDefaultWallet = wallet;
76
+ currentWallets = [wallet];
77
+ currentResolvedMethod = { wallet, chain: "base" };
78
+ const { getPaymentFetch } = await import("../payments.js");
79
+ await getPaymentFetch("base");
80
+ expect(mockBaseChargeClient).toHaveBeenCalledTimes(1);
81
+ expect(mockTempo).not.toHaveBeenCalled();
82
+ expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({ methods: ["base_method"] }));
83
+ });
84
+ it("initializes only the Tempo method when tempo is requested", async () => {
85
+ const wallet = {
86
+ id: "aw-main",
87
+ keyType: "raw",
88
+ key: TEST_KEY,
89
+ chains: ["tempo", "base"],
90
+ defaultChain: "tempo",
91
+ };
92
+ currentDefaultWallet = wallet;
93
+ currentWallets = [wallet];
94
+ currentResolvedMethod = { wallet, chain: "tempo" };
95
+ const { getPaymentFetch } = await import("../payments.js");
96
+ await getPaymentFetch("tempo");
97
+ expect(mockTempo).toHaveBeenCalledTimes(1);
98
+ expect(mockBaseChargeClient).not.toHaveBeenCalled();
99
+ expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({ methods: ["tempo_method"] }));
100
+ });
101
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ const state = vi.hoisted(() => ({
3
+ wallets: [],
4
+ addresses: {},
5
+ addedWallets: [],
6
+ owsAvailable: false,
7
+ owsWallets: [],
8
+ }));
9
+ vi.mock("../config.js", () => ({
10
+ addWallet: (wallet) => {
11
+ state.addedWallets.push(wallet);
12
+ state.wallets.push(wallet);
13
+ },
14
+ getDefaultWallet: () => state.wallets[0],
15
+ getWallets: () => state.wallets,
16
+ }));
17
+ vi.mock("../payments.js", () => ({
18
+ getWalletAddress: async (walletId) => {
19
+ if (walletId in state.addresses) {
20
+ return state.addresses[walletId] ?? null;
21
+ }
22
+ const wallet = state.wallets.find((entry) => entry.id === walletId);
23
+ if (wallet?.key) {
24
+ const { privateKeyToAccount } = await import("viem/accounts");
25
+ return privateKeyToAccount(wallet.key).address;
26
+ }
27
+ return null;
28
+ },
29
+ }));
30
+ vi.mock("../ows-adapter.js", () => ({
31
+ createOwsWallet: async (name) => ({
32
+ walletId: `ows-${name}`,
33
+ address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
34
+ }),
35
+ isOwsAvailable: async () => state.owsAvailable,
36
+ listOwsWallets: async () => state.owsWallets,
37
+ }));
38
+ describe("consumer principal helpers", () => {
39
+ beforeEach(() => {
40
+ vi.resetModules();
41
+ state.wallets = [];
42
+ state.addresses = {};
43
+ state.addedWallets = [];
44
+ state.owsAvailable = false;
45
+ state.owsWallets = [];
46
+ });
47
+ it("prefers an EVM-backed principal when both EVM and Solana wallets exist", async () => {
48
+ state.wallets = [
49
+ { id: "sol-wallet", keyType: "ows", owsWalletId: "ows-sol", chains: ["solana"], defaultChain: "solana" },
50
+ { id: "evm-wallet", keyType: "evm", key: "0x1234", chains: ["tempo", "base"], defaultChain: "base" },
51
+ ];
52
+ state.addresses = {
53
+ "sol-wallet": "So1ana11111111111111111111111111111111111111",
54
+ "evm-wallet": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
55
+ };
56
+ const { getConsumerPrincipal } = await import("../principal.js");
57
+ const principal = await getConsumerPrincipal();
58
+ expect(principal).toBe("did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
59
+ });
60
+ it("creates a fallback EVM identity wallet when none exists", async () => {
61
+ const { ensureConsumerPrincipal } = await import("../principal.js");
62
+ const principal = await ensureConsumerPrincipal();
63
+ expect(principal).toMatch(/^did:pkh:eip155:8453:0x[a-f0-9]{40}$/);
64
+ expect(state.addedWallets).toHaveLength(1);
65
+ expect(state.addedWallets[0]?.chains).toEqual(["tempo", "base"]);
66
+ });
67
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ const state = {
3
+ policies: {},
4
+ ledger: [],
5
+ };
6
+ vi.mock("../config.js", () => ({
7
+ getSpendPolicy: (method) => state.policies[method] ?? null,
8
+ getSpendLedger: () => state.ledger,
9
+ saveSpendLedger: (entries) => {
10
+ state.ledger = entries;
11
+ },
12
+ }));
13
+ import { canSpend, getDailySpend, recordSpend, requiresPolicyConfirmation, } from "../spend-policy.js";
14
+ describe("spend-policy", () => {
15
+ beforeEach(() => {
16
+ state.policies = {};
17
+ state.ledger = [];
18
+ });
19
+ it("allows spend when no policy is set", () => {
20
+ expect(canSpend({ method: "wallet-1", amountUsd: 5 })).toEqual({ ok: true });
21
+ });
22
+ it("blocks spends above max_per_tx", () => {
23
+ state.policies["wallet-1"] = { maxPerTxUsd: 2 };
24
+ const result = canSpend({ method: "wallet-1", amountUsd: 3 });
25
+ expect(result.ok).toBe(false);
26
+ });
27
+ it("tracks daily spend totals per method", () => {
28
+ const now = new Date("2026-04-08T12:00:00.000Z");
29
+ recordSpend("wallet-1", 1.25, now);
30
+ recordSpend("wallet-1", 0.75, now);
31
+ recordSpend("wallet-2", 10, now);
32
+ expect(getDailySpend("wallet-1", now)).toBeCloseTo(2);
33
+ expect(getDailySpend("wallet-2", now)).toBeCloseTo(10);
34
+ });
35
+ it("requires explicit confirmation above configured threshold", () => {
36
+ state.policies["wallet-1"] = { requireConfirmationAboveUsd: 2 };
37
+ expect(requiresPolicyConfirmation("wallet-1", 1.99)).toBe(false);
38
+ expect(requiresPolicyConfirmation("wallet-1", 2)).toBe(true);
39
+ });
40
+ });
@@ -0,0 +1 @@
1
+ export declare function toAtomicAmount(amount: string, decimals?: number): bigint;
@@ -0,0 +1,4 @@
1
+ import { parseUnits } from "viem";
2
+ export function toAtomicAmount(amount, decimals = 6) {
3
+ return parseUnits(amount, decimals);
4
+ }
@@ -3,12 +3,17 @@ export declare class ApiError extends Error {
3
3
  readonly body?: unknown | undefined;
4
4
  constructor(status: number, message: string, body?: unknown | undefined);
5
5
  }
6
- export declare function apiGet<T>(path: string): Promise<T>;
7
- export declare function apiPost<T>(path: string, body: unknown): Promise<T>;
6
+ interface RequestOptions {
7
+ ensureConsumerPrincipal?: boolean;
8
+ extraHeaders?: Record<string, string>;
9
+ }
10
+ export declare function apiGet<T>(path: string, options?: RequestOptions): Promise<T>;
11
+ export declare function apiPost<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
8
12
  /**
9
13
  * POST with payment support. Uses the configured payment method to
10
14
  * auto-handle 402 → sign → retry for paid endpoints.
11
15
  * Pass `payWith` to specify a method, or omit for auto-detection.
12
16
  */
13
- export declare function apiPostWithPayment<T>(path: string, body: unknown, payWith?: string): Promise<T>;
14
- export declare function apiPut<T>(path: string, body: unknown): Promise<T>;
17
+ export declare function apiPostWithPayment<T>(path: string, body: unknown, payWith?: string, options?: RequestOptions): Promise<T>;
18
+ export declare function apiPut<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
19
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { getApiUrl, getApiKey } from "./config.js";
2
2
  import { getPaymentFetch } from "./payments.js";
3
+ import { ensureConsumerPrincipal, getConsumerPrincipal } from "./principal.js";
3
4
  // ── Error class ────────────────────────────────────────────────────
4
5
  export class ApiError extends Error {
5
6
  status;
@@ -11,8 +12,7 @@ export class ApiError extends Error {
11
12
  this.name = "ApiError";
12
13
  }
13
14
  }
14
- // ── Internal helpers ───────────────────────────────────────────────
15
- function buildHeaders() {
15
+ async function buildHeaders(options) {
16
16
  const headers = {
17
17
  "Content-Type": "application/json",
18
18
  Accept: "application/json",
@@ -21,6 +21,15 @@ function buildHeaders() {
21
21
  if (apiKey) {
22
22
  headers["Authorization"] = `Bearer ${apiKey}`;
23
23
  }
24
+ const principal = options?.ensureConsumerPrincipal
25
+ ? await ensureConsumerPrincipal()
26
+ : await getConsumerPrincipal();
27
+ if (principal) {
28
+ headers["X-AW-Consumer-Principal"] = principal;
29
+ }
30
+ if (options?.extraHeaders) {
31
+ Object.assign(headers, options.extraHeaders);
32
+ }
24
33
  return headers;
25
34
  }
26
35
  async function handleResponse(response) {
@@ -45,54 +54,75 @@ async function handleResponse(response) {
45
54
  }
46
55
  return body;
47
56
  }
57
+ function attachResponseMetadata(result, response) {
58
+ if (!result || typeof result !== "object") {
59
+ return result;
60
+ }
61
+ const record = result;
62
+ const headerToken = response.headers.get("x-feedback-token");
63
+ if (headerToken && !("feedback_token" in record)) {
64
+ record.feedback_token = headerToken;
65
+ }
66
+ const consumptionMode = response.headers.get("x-aw-consumption-mode");
67
+ if (consumptionMode) {
68
+ record.consumption_mode = consumptionMode;
69
+ }
70
+ const creditPackId = response.headers.get("x-aw-credit-pack-id");
71
+ if (creditPackId) {
72
+ record.credit_pack_id = creditPackId;
73
+ }
74
+ const principal = response.headers.get("x-aw-consumer-principal");
75
+ if (principal) {
76
+ record.consumer_principal = principal;
77
+ }
78
+ return result;
79
+ }
48
80
  // ── Public API ─────────────────────────────────────────────────────
49
- export async function apiGet(path) {
81
+ export async function apiGet(path, options) {
50
82
  const url = `${getApiUrl()}${path}`;
51
83
  const response = await fetch(url, {
52
84
  method: "GET",
53
- headers: buildHeaders(),
85
+ headers: await buildHeaders(options),
54
86
  });
55
- return handleResponse(response);
87
+ const result = await handleResponse(response);
88
+ return attachResponseMetadata(result, response);
56
89
  }
57
- export async function apiPost(path, body) {
90
+ export async function apiPost(path, body, options) {
58
91
  const url = `${getApiUrl()}${path}`;
59
92
  const response = await fetch(url, {
60
93
  method: "POST",
61
- headers: buildHeaders(),
94
+ headers: await buildHeaders(options),
62
95
  body: JSON.stringify(body),
63
96
  });
64
- return handleResponse(response);
97
+ const result = await handleResponse(response);
98
+ return attachResponseMetadata(result, response);
65
99
  }
66
100
  /**
67
101
  * POST with payment support. Uses the configured payment method to
68
102
  * auto-handle 402 → sign → retry for paid endpoints.
69
103
  * Pass `payWith` to specify a method, or omit for auto-detection.
70
104
  */
71
- export async function apiPostWithPayment(path, body, payWith) {
105
+ export async function apiPostWithPayment(path, body, payWith, options) {
72
106
  const url = `${getApiUrl()}${path}`;
73
107
  const paymentFetch = await getPaymentFetch(payWith);
74
108
  const response = await paymentFetch(url, {
75
109
  method: "POST",
76
- headers: buildHeaders(),
110
+ headers: await buildHeaders({
111
+ ensureConsumerPrincipal: true,
112
+ ...options,
113
+ }),
77
114
  body: JSON.stringify(body),
78
115
  });
79
116
  const result = await handleResponse(response);
80
- // mppx may strip extra fields from the response body during receipt processing.
81
- // The gateway also sends feedback_token as a header to survive this.
82
- if (result && typeof result === "object" && !("feedback_token" in result)) {
83
- const headerToken = response.headers.get("x-feedback-token");
84
- if (headerToken) {
85
- result.feedback_token = headerToken;
86
- }
87
- }
88
- return result;
117
+ return attachResponseMetadata(result, response);
89
118
  }
90
- export async function apiPut(path, body) {
119
+ export async function apiPut(path, body, options) {
91
120
  const url = `${getApiUrl()}${path}`;
92
121
  const response = await fetch(url, {
93
122
  method: "PUT",
94
- headers: buildHeaders(),
123
+ headers: await buildHeaders(options),
95
124
  body: JSON.stringify(body),
96
125
  });
97
- return handleResponse(response);
126
+ const result = await handleResponse(response);
127
+ return attachResponseMetadata(result, response);
98
128
  }
@@ -5,6 +5,7 @@
5
5
  * the tx hash as a credential. Plugs into mppx's compose/dispatch system.
6
6
  */
7
7
  import { Method, Credential, z } from "mppx";
8
+ import { toAtomicAmount } from "./amount-utils.js";
8
9
  // Base USDC (Circle native)
9
10
  const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
10
11
  const BASE_CHAIN_ID = 8453;
@@ -50,11 +51,11 @@ export function baseChargeClient(config) {
50
51
  return Method.toClient(baseChargeMethod, {
51
52
  async createCredential({ challenge }) {
52
53
  const { request } = challenge;
53
- const amount = BigInt(request.amount);
54
+ const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
54
55
  const recipient = request.recipient;
55
56
  const currency = (request.currency ?? BASE_USDC);
56
57
  // Dynamic imports to keep the module lightweight
57
- const { createWalletClient, createPublicClient, http, encodeFunctionData } = await import("viem");
58
+ const { createWalletClient, createPublicClient, http } = await import("viem");
58
59
  const { base } = await import("viem/chains");
59
60
  const rpcUrl = config.rpcUrl ?? "https://mainnet.base.org";
60
61
  const walletClient = createWalletClient({