mppx 0.8.3 → 0.8.4
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/CHANGELOG.md +8 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +2 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js +32 -12
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/challenge.d.ts +14 -0
- package/dist/cli/validate/challenge.d.ts.map +1 -0
- package/dist/cli/validate/challenge.js +211 -0
- package/dist/cli/validate/challenge.js.map +1 -0
- package/dist/cli/validate/discovery.d.ts +10 -0
- package/dist/cli/validate/discovery.d.ts.map +1 -0
- package/dist/cli/validate/discovery.js +128 -0
- package/dist/cli/validate/discovery.js.map +1 -0
- package/dist/cli/validate/helpers.d.ts +33 -0
- package/dist/cli/validate/helpers.d.ts.map +1 -0
- package/dist/cli/validate/helpers.js +136 -0
- package/dist/cli/validate/helpers.js.map +1 -0
- package/dist/cli/validate/index.d.ts +17 -0
- package/dist/cli/validate/index.d.ts.map +1 -0
- package/dist/cli/validate/index.js +224 -0
- package/dist/cli/validate/index.js.map +1 -0
- package/dist/cli/validate/payment.d.ts +7 -0
- package/dist/cli/validate/payment.d.ts.map +1 -0
- package/dist/cli/validate/payment.js +381 -0
- package/dist/cli/validate/payment.js.map +1 -0
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +6 -1
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/cli.test.ts +134 -29
- package/src/cli/cli.ts +2 -0
- package/src/cli/mcp.test.ts +1 -0
- package/src/cli/plugins/stripe.ts +31 -12
- package/src/cli/utils.test.ts +36 -2
- package/src/cli/utils.ts +2 -1
- package/src/cli/validate/challenge.ts +335 -0
- package/src/cli/validate/discovery.ts +120 -0
- package/src/cli/validate/helpers.ts +154 -0
- package/src/cli/validate/index.ts +348 -0
- package/src/cli/validate/payment.ts +488 -0
- package/src/cli/validate.test.ts +539 -0
- package/src/tempo/internal/auto-swap.test.ts +26 -9
- package/src/tempo/internal/auto-swap.ts +15 -2
- package/src/tempo/server/internal/html.gen.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,swrpBAAswrpB,CAAA"}
|
package/package.json
CHANGED
package/src/cli/cli.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import * as Receipt from '../Receipt.js'
|
|
|
20
20
|
import * as Mppx_server from '../server/Mppx.js'
|
|
21
21
|
import { toNodeListener } from '../server/Mppx.js'
|
|
22
22
|
import * as Store from '../Store.js'
|
|
23
|
+
import { stripePreviewVersion } from '../stripe/internal/constants.js'
|
|
23
24
|
import { stripe as stripe_server } from '../stripe/server/Methods.js'
|
|
24
25
|
import { tempo } from '../tempo/server/Methods.js'
|
|
25
26
|
import { escrowAbi } from '../tempo/session/precompile/escrow.abi.js'
|
|
@@ -1403,40 +1404,144 @@ describe('stripe charge', () => {
|
|
|
1403
1404
|
}
|
|
1404
1405
|
})
|
|
1405
1406
|
|
|
1406
|
-
test(
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
realm: 'cli-test-stripe-live',
|
|
1416
|
-
secretKey: 'cli-test-secret-cli-test-secret-32',
|
|
1417
|
-
})
|
|
1407
|
+
test(
|
|
1408
|
+
'happy path: live key uses production SPT endpoint and seller business profile',
|
|
1409
|
+
{ timeout: 60_000 },
|
|
1410
|
+
async () => {
|
|
1411
|
+
const mockStripeClient = {
|
|
1412
|
+
paymentIntents: {
|
|
1413
|
+
create: async () => ({ id: 'pi_mock_cli_live_123', status: 'succeeded' }),
|
|
1414
|
+
},
|
|
1415
|
+
}
|
|
1418
1416
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1417
|
+
const server = Mppx_server.create({
|
|
1418
|
+
methods: [
|
|
1419
|
+
stripe_server.charge({
|
|
1420
|
+
client: mockStripeClient,
|
|
1421
|
+
networkId: 'profile_live_mock',
|
|
1422
|
+
paymentMethodTypes: ['card'],
|
|
1423
|
+
}),
|
|
1424
|
+
],
|
|
1425
|
+
realm: 'cli-test-stripe-live',
|
|
1426
|
+
secretKey: 'cli-test-secret-cli-test-secret-32',
|
|
1427
|
+
})
|
|
1426
1428
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1429
|
+
const appServer = await Http.createServer(async (req, res) => {
|
|
1430
|
+
const result = await Mppx_server.toNodeListener(
|
|
1431
|
+
server.charge({ amount: '1', currency: 'usd', decimals: 2 }),
|
|
1432
|
+
)(req, res)
|
|
1433
|
+
if (result.status === 402) return
|
|
1434
|
+
res.end('paid')
|
|
1435
|
+
})
|
|
1436
|
+
|
|
1437
|
+
const { restore, stripeRequests } = mockSptFetch(
|
|
1438
|
+
'https://api.stripe.com/v1/shared_payment/issued_tokens',
|
|
1431
1439
|
)
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1440
|
+
|
|
1441
|
+
try {
|
|
1442
|
+
const { output } = await serve([appServer.url, '-s', '-M', 'paymentMethod=pm_card_visa'], {
|
|
1443
|
+
env: { MPPX_STRIPE_SECRET_KEY: 'sk_live_fake', MPPX_STRIPE_SPT_URL: undefined },
|
|
1444
|
+
})
|
|
1445
|
+
expect(output).toContain('paid')
|
|
1446
|
+
const stripeRequest = stripeRequests[0]
|
|
1447
|
+
expect(stripeRequest?.url).toBe('https://api.stripe.com/v1/shared_payment/issued_tokens')
|
|
1448
|
+
expect(stripeRequest?.body.get('seller_details[network_business_profile]')).toBe(
|
|
1449
|
+
'profile_live_mock',
|
|
1450
|
+
)
|
|
1451
|
+
expect(stripeRequest?.body.has('seller_details[network_id]')).toBe(false)
|
|
1452
|
+
expect(stripeRequest?.headers['Stripe-Version']).toBe(stripePreviewVersion)
|
|
1453
|
+
} finally {
|
|
1454
|
+
restore()
|
|
1455
|
+
appServer.close()
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
test(
|
|
1461
|
+
'happy path: test key uses test-helper SPT endpoint and network ID',
|
|
1462
|
+
{ timeout: 60_000 },
|
|
1463
|
+
async () => {
|
|
1464
|
+
const mockStripeClient = {
|
|
1465
|
+
paymentIntents: {
|
|
1466
|
+
create: async () => ({ id: 'pi_mock_cli_test_123', status: 'succeeded' }),
|
|
1467
|
+
},
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const server = Mppx_server.create({
|
|
1471
|
+
methods: [
|
|
1472
|
+
stripe_server.charge({
|
|
1473
|
+
client: mockStripeClient,
|
|
1474
|
+
networkId: 'internal',
|
|
1475
|
+
paymentMethodTypes: ['card'],
|
|
1476
|
+
}),
|
|
1477
|
+
],
|
|
1478
|
+
realm: 'cli-test-stripe-testmode',
|
|
1479
|
+
secretKey: 'cli-test-secret-cli-test-secret-32',
|
|
1480
|
+
})
|
|
1481
|
+
|
|
1482
|
+
const appServer = await Http.createServer(async (req, res) => {
|
|
1483
|
+
const result = await Mppx_server.toNodeListener(
|
|
1484
|
+
server.charge({ amount: '1', currency: 'usd', decimals: 2 }),
|
|
1485
|
+
)(req, res)
|
|
1486
|
+
if (result.status === 402) return
|
|
1487
|
+
res.end('paid')
|
|
1488
|
+
})
|
|
1489
|
+
|
|
1490
|
+
const { restore, stripeRequests } = mockSptFetch(
|
|
1491
|
+
'https://api.stripe.com/v1/test_helpers/shared_payment/granted_tokens',
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
try {
|
|
1495
|
+
const { output } = await serve([appServer.url, '-s', '-M', 'paymentMethod=pm_card_visa'], {
|
|
1496
|
+
env: { MPPX_STRIPE_SECRET_KEY: 'sk_test_fake', MPPX_STRIPE_SPT_URL: undefined },
|
|
1497
|
+
})
|
|
1498
|
+
expect(output).toContain('paid')
|
|
1499
|
+
const stripeRequest = stripeRequests[0]
|
|
1500
|
+
expect(stripeRequest?.url).toBe(
|
|
1501
|
+
'https://api.stripe.com/v1/test_helpers/shared_payment/granted_tokens',
|
|
1502
|
+
)
|
|
1503
|
+
expect(stripeRequest?.body.get('seller_details[network_id]')).toBe('internal')
|
|
1504
|
+
expect(stripeRequest?.body.has('seller_details[network_business_profile]')).toBe(false)
|
|
1505
|
+
expect(stripeRequest?.headers['Stripe-Version']).toBe(stripePreviewVersion)
|
|
1506
|
+
} finally {
|
|
1507
|
+
restore()
|
|
1508
|
+
appServer.close()
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
)
|
|
1438
1512
|
})
|
|
1439
1513
|
|
|
1514
|
+
/**
|
|
1515
|
+
* Intercepts `globalThis.fetch` calls to `sptUrl`, recording each request and
|
|
1516
|
+
* responding with a mock SPT. Returns the recorded requests and a `restore`
|
|
1517
|
+
* function that reinstates the original fetch.
|
|
1518
|
+
*/
|
|
1519
|
+
function mockSptFetch(sptUrl: string) {
|
|
1520
|
+
const originalFetch = globalThis.fetch
|
|
1521
|
+
const stripeRequests: {
|
|
1522
|
+
body: URLSearchParams
|
|
1523
|
+
headers: Record<string, string>
|
|
1524
|
+
url: string
|
|
1525
|
+
}[] = []
|
|
1526
|
+
globalThis.fetch = (async (input, init) => {
|
|
1527
|
+
const url =
|
|
1528
|
+
typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
|
|
1529
|
+
if (url === sptUrl) {
|
|
1530
|
+
stripeRequests.push({
|
|
1531
|
+
body: new URLSearchParams(init?.body as string),
|
|
1532
|
+
headers: (init?.headers ?? {}) as Record<string, string>,
|
|
1533
|
+
url,
|
|
1534
|
+
})
|
|
1535
|
+
return new Response(JSON.stringify({ id: 'spt_mock_cli_test' }), {
|
|
1536
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1537
|
+
status: 200,
|
|
1538
|
+
})
|
|
1539
|
+
}
|
|
1540
|
+
return originalFetch(input, init)
|
|
1541
|
+
}) as typeof fetch
|
|
1542
|
+
return { restore: () => (globalThis.fetch = originalFetch), stripeRequests }
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1440
1545
|
// ---------------------------------------------------------------------------
|
|
1441
1546
|
// account [action]
|
|
1442
1547
|
// Keychain tests are opt-in because macOS/Linux keychain commands can block on
|
package/src/cli/cli.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
resolveFundingNetwork,
|
|
41
41
|
resolveRpcUrl,
|
|
42
42
|
} from './utils.js'
|
|
43
|
+
import validate from './validate/index.js'
|
|
43
44
|
|
|
44
45
|
const packageJson = createRequire(import.meta.url)('../../package.json') as {
|
|
45
46
|
name: string
|
|
@@ -1575,5 +1576,6 @@ cli.command(discover)
|
|
|
1575
1576
|
cli.command(init)
|
|
1576
1577
|
cli.command(services)
|
|
1577
1578
|
cli.command(sign)
|
|
1579
|
+
cli.command(validate)
|
|
1578
1580
|
|
|
1579
1581
|
export default cli
|
package/src/cli/mcp.test.ts
CHANGED
|
@@ -194,6 +194,7 @@ test('tools/list exposes mppx commands with input and output schemas', async ()
|
|
|
194
194
|
'services_list',
|
|
195
195
|
'services_show',
|
|
196
196
|
'sign',
|
|
197
|
+
'validate',
|
|
197
198
|
])
|
|
198
199
|
expect(tools.find((tool: { name: string }) => tool.name === 'account_list').outputSchema).toEqual(
|
|
199
200
|
expect.objectContaining({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Errors, z } from 'incur'
|
|
2
2
|
|
|
3
3
|
import { stripe as stripeMethods } from '../../stripe/client/index.js'
|
|
4
|
+
import { stripePreviewVersion } from '../../stripe/internal/constants.js'
|
|
4
5
|
import { pc } from '../utils.js'
|
|
5
6
|
import { createPlugin } from './plugin.js'
|
|
6
7
|
|
|
@@ -30,13 +31,6 @@ export function stripe() {
|
|
|
30
31
|
message: 'MPPX_STRIPE_SECRET_KEY environment variable is required for Stripe payments.',
|
|
31
32
|
exitCode: 2,
|
|
32
33
|
})
|
|
33
|
-
if (!stripeSecretKey.startsWith('sk_test_'))
|
|
34
|
-
throw new Errors.IncurError({
|
|
35
|
-
code: 'UNSUPPORTED_MODE',
|
|
36
|
-
message:
|
|
37
|
-
'Stripe CLI payments are currently only supported in test mode (sk_test_... keys).',
|
|
38
|
-
exitCode: 2,
|
|
39
|
-
})
|
|
40
34
|
|
|
41
35
|
return {
|
|
42
36
|
tokenSymbol: currency?.toUpperCase() ?? '',
|
|
@@ -58,19 +52,35 @@ export function stripe() {
|
|
|
58
52
|
'usage_limits[max_amount]': amount,
|
|
59
53
|
'usage_limits[expires_at]': expiresAt.toString(),
|
|
60
54
|
})
|
|
61
|
-
|
|
55
|
+
const testMode = isTestKey(stripeSecretKey)
|
|
56
|
+
const sptUrl = (() => {
|
|
57
|
+
// Allow tests and custom deployments to target a specific SPT endpoint.
|
|
58
|
+
if (process.env.MPPX_STRIPE_SPT_URL) return process.env.MPPX_STRIPE_SPT_URL
|
|
59
|
+
// Preserve test-key behavior against Stripe's test-helper SPT endpoint.
|
|
60
|
+
if (testMode)
|
|
61
|
+
return 'https://api.stripe.com/v1/test_helpers/shared_payment/granted_tokens'
|
|
62
|
+
// Live keys mint Shared Payment Tokens through the agent-side issued tokens API.
|
|
63
|
+
return 'https://api.stripe.com/v1/shared_payment/issued_tokens'
|
|
64
|
+
})()
|
|
65
|
+
if (networkId) {
|
|
66
|
+
// Live SPT issuance grants the token to the seller's Stripe business
|
|
67
|
+
// profile (`profile_...`); the test helper uses a plain network ID.
|
|
68
|
+
const sellerDetailsField = testMode
|
|
69
|
+
? 'seller_details[network_id]'
|
|
70
|
+
: 'seller_details[network_business_profile]'
|
|
71
|
+
body.set(sellerDetailsField, networkId)
|
|
72
|
+
}
|
|
62
73
|
if (metadata) {
|
|
63
74
|
for (const [key, value] of Object.entries(metadata)) {
|
|
64
75
|
body.set(`metadata[${key}]`, value)
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
const sptUrl =
|
|
69
|
-
process.env.MPPX_STRIPE_SPT_URL ??
|
|
70
|
-
'https://api.stripe.com/v1/test_helpers/shared_payment/granted_tokens'
|
|
71
79
|
const sptHeaders = {
|
|
72
80
|
Authorization: `Basic ${btoa(`${stripeSecretKey}:`)}`,
|
|
73
81
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
82
|
+
// SPT endpoints are in preview and require an explicit preview API version.
|
|
83
|
+
'Stripe-Version': stripePreviewVersion,
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
let response = await globalThis.fetch(sptUrl, {
|
|
@@ -80,7 +90,11 @@ export function stripe() {
|
|
|
80
90
|
})
|
|
81
91
|
if (!response.ok) {
|
|
82
92
|
const errorBody = (await response.json()) as { error: { message: string } }
|
|
93
|
+
// Retry without optional parameters only in test mode: the live
|
|
94
|
+
// issued-tokens endpoint requires `seller_details`, so a retry
|
|
95
|
+
// without it can never succeed.
|
|
83
96
|
if (
|
|
97
|
+
testMode &&
|
|
84
98
|
(metadata || networkId) &&
|
|
85
99
|
errorBody.error.message.includes('Received unknown parameter')
|
|
86
100
|
) {
|
|
@@ -122,7 +136,8 @@ export function stripe() {
|
|
|
122
136
|
|
|
123
137
|
formatReceiptField(key, value) {
|
|
124
138
|
if (key === 'reference' && typeof value === 'string' && value.startsWith('pi_')) {
|
|
125
|
-
const
|
|
139
|
+
const secretKey = process.env.MPPX_STRIPE_SECRET_KEY
|
|
140
|
+
const isTest = secretKey !== undefined && isTestKey(secretKey)
|
|
126
141
|
const url = `https://dashboard.stripe.com${isTest ? '/test' : ''}/payments/${value}`
|
|
127
142
|
return pc.link(url, value, true)
|
|
128
143
|
}
|
|
@@ -132,6 +147,10 @@ export function stripe() {
|
|
|
132
147
|
|
|
133
148
|
// --- Helpers ---
|
|
134
149
|
|
|
150
|
+
function isTestKey(secretKey: string) {
|
|
151
|
+
return secretKey.startsWith('sk_test_') || secretKey.startsWith('rk_test_')
|
|
152
|
+
}
|
|
153
|
+
|
|
135
154
|
function parseOptions<const schema extends z.ZodType>(
|
|
136
155
|
schema: schema,
|
|
137
156
|
rawOptions: unknown,
|
package/src/cli/utils.test.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { tempo as tempoMainnet, tempoModerato } from 'viem/tempo/chains'
|
|
2
|
-
import { afterEach, describe, expect, test } from 'vp/test'
|
|
2
|
+
import { afterEach, describe, expect, test, vi } from 'vp/test'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
fetchTokenInfo,
|
|
6
|
+
networkRpcUrls,
|
|
7
|
+
resolveChain,
|
|
8
|
+
resolveFundingNetwork,
|
|
9
|
+
resolveRpcUrl,
|
|
10
|
+
} from './utils.js'
|
|
5
11
|
|
|
6
12
|
describe('resolveRpcUrl', () => {
|
|
7
13
|
afterEach(() => {
|
|
@@ -97,3 +103,31 @@ describe('resolveChain', () => {
|
|
|
97
103
|
expect(chain.id).not.toBe(tempoModerato.id)
|
|
98
104
|
})
|
|
99
105
|
})
|
|
106
|
+
|
|
107
|
+
describe('fetchTokenInfo', () => {
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
vi.doUnmock('viem/tempo')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('uses 6 decimals when token metadata omits a numeric decimals value', async () => {
|
|
113
|
+
const token = '0x1111111111111111111111111111111111111111'
|
|
114
|
+
const account = '0x2222222222222222222222222222222222222222'
|
|
115
|
+
vi.doMock('viem/tempo', () => ({
|
|
116
|
+
Actions: {
|
|
117
|
+
token: {
|
|
118
|
+
getBalance: vi.fn(async () => ({ amount: 123n })),
|
|
119
|
+
getMetadata: vi.fn(async () => ({ decimals: undefined, symbol: 'TEST' })),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
const info = await fetchTokenInfo({} as never, token, account)
|
|
125
|
+
|
|
126
|
+
expect(info).toEqual({
|
|
127
|
+
balance: 123n,
|
|
128
|
+
decimals: 6,
|
|
129
|
+
symbol: 'TEST',
|
|
130
|
+
token,
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|
package/src/cli/utils.ts
CHANGED
|
@@ -318,7 +318,8 @@ export async function fetchTokenInfo(
|
|
|
318
318
|
[usdc]: 'USDC',
|
|
319
319
|
}
|
|
320
320
|
const symbol = knownSymbols[token] ?? metadata.symbol
|
|
321
|
-
const decimals =
|
|
321
|
+
const decimals =
|
|
322
|
+
'decimals' in metadata && typeof metadata.decimals === 'number' ? metadata.decimals : 6
|
|
322
323
|
return { balance, symbol, decimals, token }
|
|
323
324
|
}
|
|
324
325
|
|