mppx 0.8.2 → 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 +14 -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/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +1 -0
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/utils.d.ts +2 -2
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +8 -2
- 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/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +7 -1
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +21 -6
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/internal/fee-token.js +1 -1
- package/dist/tempo/internal/fee-token.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 +2 -2
- 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/plugins/tempo.ts +1 -0
- package/src/cli/utils.test.ts +36 -2
- package/src/cli/utils.ts +16 -3
- 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/mcp/client/McpClient.integration.test.ts +12 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/client/Charge.test.ts +56 -0
- package/src/tempo/client/Charge.ts +20 -2
- package/src/tempo/internal/auto-swap.test.ts +85 -1
- package/src/tempo/internal/auto-swap.ts +52 -6
- package/src/tempo/internal/fee-token.ts +1 -1
- package/src/tempo/server/Charge.test.ts +31 -16
- package/src/tempo/server/internal/html/package.json +1 -1
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mppx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.4",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/wevm/mppx#readme",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"elysia": ">=1",
|
|
141
141
|
"express": ">=5",
|
|
142
142
|
"hono": ">=4.12.25",
|
|
143
|
-
"viem": ">=2.
|
|
143
|
+
"viem": ">=2.54.0"
|
|
144
144
|
},
|
|
145
145
|
"peerDependenciesMeta": {
|
|
146
146
|
"@modelcontextprotocol/sdk": {
|
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/plugins/tempo.ts
CHANGED
|
@@ -883,6 +883,7 @@ async function scoreTempoChargeChallenge(
|
|
|
883
883
|
account: probe.account,
|
|
884
884
|
token: currency as Address,
|
|
885
885
|
})
|
|
886
|
+
.then((value) => value.amount)
|
|
886
887
|
.catch(() => undefined)
|
|
887
888
|
if (balance === undefined) return 'unknownBalance'
|
|
888
889
|
if (balance >= amountRaw) return 'payable'
|
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
|
@@ -287,6 +287,10 @@ export const testnetTokens = [
|
|
|
287
287
|
'0x20c0000000000000000000000000000000000003',
|
|
288
288
|
] as const
|
|
289
289
|
|
|
290
|
+
function toBaseUnitAmount(value: bigint | { amount: bigint }) {
|
|
291
|
+
return typeof value === 'bigint' ? value : value.amount
|
|
292
|
+
}
|
|
293
|
+
|
|
290
294
|
export function isTestnet(chain: Chain) {
|
|
291
295
|
return chain.id !== tempoMainnet.id
|
|
292
296
|
}
|
|
@@ -295,10 +299,18 @@ export async function fetchTokenInfo(
|
|
|
295
299
|
client: ReturnType<typeof createClient>,
|
|
296
300
|
token: Address,
|
|
297
301
|
account: Address,
|
|
298
|
-
) {
|
|
302
|
+
): Promise<{
|
|
303
|
+
balance: bigint
|
|
304
|
+
decimals: number
|
|
305
|
+
symbol: string
|
|
306
|
+
token: Address
|
|
307
|
+
}> {
|
|
299
308
|
const { Actions } = await import('viem/tempo')
|
|
300
309
|
const [balance, metadata] = await Promise.all([
|
|
301
|
-
Actions.token
|
|
310
|
+
Actions.token
|
|
311
|
+
.getBalance(client, { account, token })
|
|
312
|
+
.then(toBaseUnitAmount)
|
|
313
|
+
.catch(() => 0n),
|
|
302
314
|
Actions.token.getMetadata(client, { token }).catch(() => ({ symbol: token as string })),
|
|
303
315
|
])
|
|
304
316
|
const knownSymbols: Record<string, string> = {
|
|
@@ -306,7 +318,8 @@ export async function fetchTokenInfo(
|
|
|
306
318
|
[usdc]: 'USDC',
|
|
307
319
|
}
|
|
308
320
|
const symbol = knownSymbols[token] ?? metadata.symbol
|
|
309
|
-
const decimals =
|
|
321
|
+
const decimals =
|
|
322
|
+
'decimals' in metadata && typeof metadata.decimals === 'number' ? metadata.decimals : 6
|
|
310
323
|
return { balance, symbol, decimals, token }
|
|
311
324
|
}
|
|
312
325
|
|