mppx 0.3.3 → 0.3.5
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/README.md +0 -52
- package/dist/Challenge.d.ts +8 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +20 -4
- package/dist/Challenge.js.map +1 -1
- package/dist/Errors.d.ts +7 -7
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +7 -7
- package/dist/Errors.js.map +1 -1
- package/dist/cli.js +280 -119
- package/dist/cli.js.map +1 -1
- package/dist/internal/env.js +2 -2
- package/dist/internal/env.js.map +1 -1
- package/dist/server/Mppx.d.ts +2 -0
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +4 -3
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts +5 -5
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +3 -3
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Session.d.ts +2 -2
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +3 -3
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +4 -4
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +4 -4
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +1 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Session.d.ts +8 -8
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +24 -24
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +2 -2
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +2 -2
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +4 -4
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +3 -3
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -0
- package/dist/tempo/session/Chain.js.map +1 -0
- package/dist/tempo/session/Channel.d.ts.map +1 -0
- package/dist/tempo/session/Channel.js.map +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/ChannelStore.js.map +1 -0
- package/dist/tempo/session/Receipt.d.ts +22 -0
- package/dist/tempo/session/Receipt.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Receipt.js +6 -6
- package/dist/tempo/session/Receipt.js.map +1 -0
- package/dist/tempo/{stream → session}/Sse.d.ts +7 -7
- package/dist/tempo/session/Sse.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Sse.js +4 -4
- package/dist/tempo/session/Sse.js.map +1 -0
- package/dist/tempo/{stream → session}/Types.d.ts +4 -4
- package/dist/tempo/session/Types.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Types.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -0
- package/dist/tempo/session/Voucher.js.map +1 -0
- package/dist/tempo/{stream → session}/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/session/escrow.abi.js.map +1 -0
- package/dist/tempo/session/index.d.ts.map +1 -0
- package/dist/tempo/session/index.js.map +1 -0
- package/package.json +1 -1
- package/src/Challenge.test.ts +201 -11
- package/src/Challenge.ts +34 -4
- package/src/Errors.test.ts +10 -10
- package/src/Errors.ts +7 -7
- package/src/Store.test.ts +93 -0
- package/src/cli.test.ts +234 -38
- package/src/cli.ts +340 -135
- package/src/client/Transport.test.ts +4 -4
- package/src/internal/env.test.ts +42 -0
- package/src/internal/env.ts +2 -2
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/server/Mppx.test.ts +173 -0
- package/src/server/Mppx.ts +6 -3
- package/src/server/Transport.test.ts +6 -6
- package/src/tempo/client/ChannelOps.test.ts +2 -2
- package/src/tempo/client/ChannelOps.ts +8 -8
- package/src/tempo/client/Session.test.ts +3 -3
- package/src/tempo/client/Session.ts +9 -9
- package/src/tempo/client/SessionManager.test.ts +3 -3
- package/src/tempo/client/SessionManager.ts +9 -9
- package/src/tempo/index.ts +1 -1
- package/src/tempo/server/Charge.ts +1 -1
- package/src/tempo/server/Session.test.ts +61 -9
- package/src/tempo/server/Session.ts +47 -47
- package/src/tempo/server/Sse.test.ts +3 -3
- package/src/tempo/server/index.ts +2 -2
- package/src/tempo/server/internal/transport.test.ts +285 -0
- package/src/tempo/server/internal/transport.ts +6 -6
- package/src/tempo/{stream → session}/Chain.test.ts +1 -1
- package/src/tempo/{stream → session}/Receipt.test.ts +16 -12
- package/src/tempo/{stream → session}/Receipt.ts +9 -9
- package/src/tempo/{stream → session}/Sse.test.ts +5 -5
- package/src/tempo/{stream → session}/Sse.ts +11 -11
- package/src/tempo/{stream → session}/Types.ts +4 -4
- package/dist/tempo/stream/Chain.d.ts.map +0 -1
- package/dist/tempo/stream/Chain.js.map +0 -1
- package/dist/tempo/stream/Channel.d.ts.map +0 -1
- package/dist/tempo/stream/Channel.js.map +0 -1
- package/dist/tempo/stream/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/stream/ChannelStore.js.map +0 -1
- package/dist/tempo/stream/Receipt.d.ts +0 -22
- package/dist/tempo/stream/Receipt.d.ts.map +0 -1
- package/dist/tempo/stream/Receipt.js.map +0 -1
- package/dist/tempo/stream/Sse.d.ts.map +0 -1
- package/dist/tempo/stream/Sse.js.map +0 -1
- package/dist/tempo/stream/Types.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.js.map +0 -1
- package/dist/tempo/stream/escrow.abi.js.map +0 -1
- package/dist/tempo/stream/index.d.ts.map +0 -1
- package/dist/tempo/stream/index.js.map +0 -1
- /package/dist/tempo/{stream → session}/Chain.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Chain.js +0 -0
- /package/dist/tempo/{stream → session}/Channel.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Channel.js +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.d.ts +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.js +0 -0
- /package/dist/tempo/{stream → session}/Types.js +0 -0
- /package/dist/tempo/{stream → session}/Voucher.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Voucher.js +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.d.ts +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.js +0 -0
- /package/dist/tempo/{stream → session}/index.d.ts +0 -0
- /package/dist/tempo/{stream → session}/index.js +0 -0
- /package/src/tempo/{stream → session}/Chain.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.test.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.test.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.test.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.ts +0 -0
- /package/src/tempo/{stream → session}/escrow.abi.ts +0 -0
- /package/src/tempo/{stream → session}/index.ts +0 -0
package/src/cli.ts
CHANGED
|
@@ -15,9 +15,10 @@ import { type ZodMiniType, z } from 'zod/mini'
|
|
|
15
15
|
import * as Challenge from './Challenge.js'
|
|
16
16
|
import * as Credential from './Credential.js'
|
|
17
17
|
import * as Mppx from './client/Mppx.js'
|
|
18
|
+
import { stripe } from './stripe/client/index.js'
|
|
18
19
|
import { tempo } from './tempo/client/index.js'
|
|
19
|
-
import type {
|
|
20
|
-
import { signVoucher } from './tempo/
|
|
20
|
+
import type { SessionCredentialPayload } from './tempo/session/Types.js'
|
|
21
|
+
import { signVoucher } from './tempo/session/Voucher.js'
|
|
21
22
|
|
|
22
23
|
const require = createRequire(import.meta.url)
|
|
23
24
|
const { name, version } = require('../package.json') as { name: string; version: string }
|
|
@@ -41,9 +42,8 @@ cli
|
|
|
41
42
|
.option('-H, --header <header>', 'Add header (repeatable)')
|
|
42
43
|
.option('-L, --location', 'Follow redirects')
|
|
43
44
|
.option('-X, --method <method>', 'HTTP method')
|
|
44
|
-
.option('--
|
|
45
|
+
.option('-M, --method-opt <opt>', 'Method-specific option (key=value, repeatable)')
|
|
45
46
|
.option('--confirm', 'Show confirmation prompts')
|
|
46
|
-
.option('--deposit <amount>', 'Deposit amount for stream payments (human-readable units)')
|
|
47
47
|
.option('--json <json>', 'Send JSON body (sets Content-Type and Accept, implies POST)')
|
|
48
48
|
.example(`${name} example.com/content`)
|
|
49
49
|
.example(`${name} example.com/api --json '{"key":"value"}'`)
|
|
@@ -51,10 +51,8 @@ cli
|
|
|
51
51
|
const options = parseOptions(
|
|
52
52
|
z.object({
|
|
53
53
|
account: z.optional(z.string()),
|
|
54
|
-
channel: z.optional(z.coerce.string()),
|
|
55
54
|
confirm: z.optional(z.boolean()),
|
|
56
55
|
data: z.optional(z.string()),
|
|
57
|
-
deposit: z.optional(z.union([z.string(), z.number()])),
|
|
58
56
|
fail: z.optional(z.boolean()),
|
|
59
57
|
header: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
60
58
|
include: z.optional(z.boolean()),
|
|
@@ -62,6 +60,7 @@ cli
|
|
|
62
60
|
json: z.optional(z.string()),
|
|
63
61
|
location: z.optional(z.boolean()),
|
|
64
62
|
method: z.optional(z.string()),
|
|
63
|
+
methodOpt: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
65
64
|
rpcUrl: z.optional(z.string()),
|
|
66
65
|
silent: z.optional(z.boolean()),
|
|
67
66
|
userAgent: z.optional(z.string()),
|
|
@@ -69,6 +68,7 @@ cli
|
|
|
69
68
|
}),
|
|
70
69
|
rawOptions,
|
|
71
70
|
)
|
|
71
|
+
const methodOpts = parseMethodOpts(options.methodOpt)
|
|
72
72
|
if (!rawUrl) {
|
|
73
73
|
cli.outputHelp()
|
|
74
74
|
return
|
|
@@ -79,12 +79,6 @@ cli
|
|
|
79
79
|
if (silent) options.confirm = false
|
|
80
80
|
|
|
81
81
|
const accountName = resolveAccountName(options.account)
|
|
82
|
-
const privateKey = process.env.MPPX_PRIVATE_KEY ?? (await createKeychain(accountName).get())
|
|
83
|
-
if (!privateKey) {
|
|
84
|
-
if (options.account) console.log(`Account "${accountName}" not found.`)
|
|
85
|
-
else console.log(`No account found.`)
|
|
86
|
-
process.exit(1)
|
|
87
|
-
}
|
|
88
82
|
|
|
89
83
|
const headers: Record<string, string> = {}
|
|
90
84
|
if (options.header) {
|
|
@@ -157,24 +151,43 @@ cli
|
|
|
157
151
|
return
|
|
158
152
|
}
|
|
159
153
|
|
|
160
|
-
const account = privateKeyToAccount(privateKey as `0x${string}`)
|
|
161
|
-
const rpcUrl = options.rpcUrl ?? process.env.MPPX_RPC_URL
|
|
162
|
-
const client = createClient({
|
|
163
|
-
chain: await resolveChain({ ...options, rpcUrl }),
|
|
164
|
-
transport: http(rpcUrl),
|
|
165
|
-
})
|
|
166
|
-
|
|
167
154
|
const challenge = Challenge.fromResponse(challengeResponse)
|
|
168
|
-
const explorerUrl = client.chain?.blockExplorers?.default?.url
|
|
169
|
-
const shownKeys = new Set<string>()
|
|
170
155
|
const challengeRequest = challenge.request as Record<string, unknown>
|
|
171
156
|
const currency = challengeRequest.currency as string | undefined
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
157
|
+
const shownKeys = new Set<string>()
|
|
158
|
+
|
|
159
|
+
let tokenSymbol =
|
|
160
|
+
challenge.method === 'stripe' ? (currency?.toUpperCase() ?? '') : (currency ?? '')
|
|
161
|
+
let tokenDecimals =
|
|
162
|
+
(challengeRequest.decimals as number | undefined) ?? (challenge.method === 'stripe' ? 2 : 6)
|
|
163
|
+
let explorerUrl: string | undefined
|
|
164
|
+
|
|
165
|
+
// Tempo-specific setup (private key, viem account/client, token info)
|
|
166
|
+
let account: ReturnType<typeof privateKeyToAccount> | undefined
|
|
167
|
+
let client: ReturnType<typeof createClient> | undefined
|
|
168
|
+
if (challenge.method === 'tempo') {
|
|
169
|
+
const privateKey = process.env.MPPX_PRIVATE_KEY ?? (await createKeychain(accountName).get())
|
|
170
|
+
if (!privateKey) {
|
|
171
|
+
if (options.account) console.error(`Account "${accountName}" not found.`)
|
|
172
|
+
else console.error(`No account found.`)
|
|
173
|
+
process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
account = privateKeyToAccount(privateKey as `0x${string}`)
|
|
176
|
+
const rpcUrl = options.rpcUrl ?? process.env.RPC_URL
|
|
177
|
+
client = createClient({
|
|
178
|
+
chain: await resolveChain({ ...options, rpcUrl }),
|
|
179
|
+
transport: http(rpcUrl),
|
|
180
|
+
})
|
|
181
|
+
explorerUrl = client.chain?.blockExplorers?.default?.url
|
|
182
|
+
const tokenInfo = currency
|
|
183
|
+
? await fetchTokenInfo(client, currency as Address, account.address).catch(
|
|
184
|
+
() => undefined,
|
|
185
|
+
)
|
|
186
|
+
: undefined
|
|
187
|
+
tokenSymbol = tokenInfo?.symbol ?? currency ?? ''
|
|
188
|
+
tokenDecimals =
|
|
189
|
+
tokenInfo?.decimals ?? (challengeRequest.decimals as number | undefined) ?? 6
|
|
190
|
+
}
|
|
178
191
|
|
|
179
192
|
{
|
|
180
193
|
printResponseHeaders(challengeResponse)
|
|
@@ -276,66 +289,178 @@ cli
|
|
|
276
289
|
}
|
|
277
290
|
}
|
|
278
291
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
let credential: string
|
|
293
|
+
if (challenge.method === 'tempo') {
|
|
294
|
+
if (!account || !client) {
|
|
295
|
+
console.error('Tempo requires a configured account.')
|
|
296
|
+
process.exit(1)
|
|
297
|
+
}
|
|
298
|
+
const tempoOpts = parseOptions(
|
|
299
|
+
z.object({
|
|
300
|
+
channel: z.optional(z.coerce.string()),
|
|
301
|
+
deposit: z.optional(z.union([z.string(), z.number()])),
|
|
302
|
+
}),
|
|
303
|
+
methodOpts,
|
|
304
|
+
)
|
|
305
|
+
const mppx = Mppx.create({
|
|
306
|
+
methods: tempo({
|
|
307
|
+
account,
|
|
308
|
+
getClient: () => client!,
|
|
309
|
+
deposit: (() => {
|
|
310
|
+
if (challenge.intent !== 'session') return undefined
|
|
311
|
+
const suggestedDeposit = (challenge.request as Record<string, unknown>)
|
|
312
|
+
.suggestedDeposit as string | undefined
|
|
313
|
+
const cliDeposit =
|
|
314
|
+
tempoOpts.deposit !== undefined ? String(tempoOpts.deposit) : undefined
|
|
315
|
+
const resolved =
|
|
316
|
+
suggestedDeposit ?? cliDeposit ?? (isTestnet(client!.chain!) ? '10' : undefined)
|
|
317
|
+
if (!resolved) {
|
|
318
|
+
console.error(
|
|
319
|
+
'Session payment requires a deposit. Use -M deposit=<amount> or connect to testnet.',
|
|
320
|
+
)
|
|
321
|
+
process.exit(1)
|
|
322
|
+
}
|
|
323
|
+
return resolved
|
|
324
|
+
})(),
|
|
325
|
+
}),
|
|
326
|
+
polyfill: false,
|
|
327
|
+
})
|
|
328
|
+
credential = await mppx.createCredential(
|
|
329
|
+
challengeResponse,
|
|
330
|
+
(() => {
|
|
331
|
+
if (!tempoOpts.channel) return undefined
|
|
332
|
+
const channelId = tempoOpts.channel
|
|
333
|
+
const saved = readChannelCumulative(channelId)
|
|
334
|
+
return {
|
|
335
|
+
channelId,
|
|
336
|
+
...(saved !== undefined && { cumulativeAmountRaw: saved.toString() }),
|
|
295
337
|
}
|
|
296
|
-
return resolved
|
|
297
338
|
})(),
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
339
|
+
)
|
|
340
|
+
} else if (challenge.method === 'stripe') {
|
|
341
|
+
const stripeOpts = parseOptions(
|
|
342
|
+
z.object({
|
|
343
|
+
paymentMethod: z.string(),
|
|
344
|
+
}),
|
|
345
|
+
methodOpts,
|
|
346
|
+
)
|
|
347
|
+
const stripeSecretKey = process.env.MPPX_STRIPE_SECRET_KEY
|
|
348
|
+
if (!stripeSecretKey) {
|
|
349
|
+
console.error(
|
|
350
|
+
'\nMPPX_STRIPE_SECRET_KEY environment variable is required for Stripe payments.',
|
|
351
|
+
)
|
|
352
|
+
process.exit(1)
|
|
353
|
+
}
|
|
354
|
+
if (!stripeSecretKey.startsWith('sk_test_')) {
|
|
355
|
+
console.error(
|
|
356
|
+
'\nStripe CLI payments are currently only supported in test mode (sk_test_... keys).',
|
|
357
|
+
)
|
|
358
|
+
process.exit(1)
|
|
359
|
+
}
|
|
360
|
+
const mppx = Mppx.create({
|
|
361
|
+
methods: [
|
|
362
|
+
stripe.charge({
|
|
363
|
+
paymentMethod: stripeOpts.paymentMethod,
|
|
364
|
+
createToken: async ({
|
|
365
|
+
paymentMethod,
|
|
366
|
+
amount,
|
|
367
|
+
currency,
|
|
368
|
+
networkId,
|
|
369
|
+
expiresAt,
|
|
370
|
+
metadata,
|
|
371
|
+
}) => {
|
|
372
|
+
const body = new URLSearchParams({
|
|
373
|
+
payment_method: paymentMethod!,
|
|
374
|
+
'usage_limits[currency]': currency,
|
|
375
|
+
'usage_limits[max_amount]': amount,
|
|
376
|
+
'usage_limits[expires_at]': expiresAt.toString(),
|
|
377
|
+
})
|
|
378
|
+
if (networkId) body.set('seller_details[network_id]', networkId)
|
|
379
|
+
if (metadata) {
|
|
380
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
381
|
+
body.set(`metadata[${key}]`, value)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const sptUrl =
|
|
385
|
+
process.env.MPPX_STRIPE_SPT_URL ??
|
|
386
|
+
'https://api.stripe.com/v1/test_helpers/shared_payment/granted_tokens'
|
|
387
|
+
const sptHeaders = {
|
|
388
|
+
Authorization: `Basic ${btoa(`${stripeSecretKey}:`)}`,
|
|
389
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
390
|
+
}
|
|
391
|
+
let response = await globalThis.fetch(sptUrl, {
|
|
392
|
+
method: 'POST',
|
|
393
|
+
headers: sptHeaders,
|
|
394
|
+
body,
|
|
395
|
+
})
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
const errorBody = (await response.json()) as { error: { message: string } }
|
|
398
|
+
if (
|
|
399
|
+
(metadata || networkId) &&
|
|
400
|
+
errorBody.error.message.includes('Received unknown parameter')
|
|
401
|
+
) {
|
|
402
|
+
const fallbackBody = new URLSearchParams({
|
|
403
|
+
payment_method: paymentMethod!,
|
|
404
|
+
'usage_limits[currency]': currency,
|
|
405
|
+
'usage_limits[max_amount]': amount,
|
|
406
|
+
'usage_limits[expires_at]': expiresAt.toString(),
|
|
407
|
+
})
|
|
408
|
+
response = await globalThis.fetch(sptUrl, {
|
|
409
|
+
method: 'POST',
|
|
410
|
+
headers: sptHeaders,
|
|
411
|
+
body: fallbackBody,
|
|
412
|
+
})
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
const fallbackError = (await response.json()) as {
|
|
415
|
+
error: { message: string }
|
|
416
|
+
}
|
|
417
|
+
throw new Error(`Failed to create SPT: ${fallbackError.error.message}`)
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
throw new Error(`Failed to create SPT: ${errorBody.error.message}`)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const { id } = (await response.json()) as { id: string }
|
|
424
|
+
return id
|
|
425
|
+
},
|
|
426
|
+
}),
|
|
427
|
+
],
|
|
428
|
+
polyfill: false,
|
|
429
|
+
})
|
|
430
|
+
credential = await mppx.createCredential(challengeResponse)
|
|
431
|
+
} else {
|
|
432
|
+
console.error(`Unsupported payment method: ${challenge.method}`)
|
|
433
|
+
process.exit(1)
|
|
434
|
+
}
|
|
315
435
|
|
|
316
|
-
const
|
|
436
|
+
const sessionMd = challenge.request.methodDetails as
|
|
317
437
|
| { escrowContract?: string; chainId?: number }
|
|
318
438
|
| undefined
|
|
319
|
-
let
|
|
320
|
-
let
|
|
321
|
-
let
|
|
322
|
-
let
|
|
439
|
+
let sessionChannelId: `0x${string}` | undefined
|
|
440
|
+
let sessionEscrowContract: Address | undefined
|
|
441
|
+
let sessionChainId = 0
|
|
442
|
+
let sessionCumulativeAmount = 0n
|
|
323
443
|
|
|
324
444
|
if (challenge.intent === 'session') {
|
|
325
|
-
const parsed = Credential.deserialize<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
445
|
+
const parsed = Credential.deserialize<SessionCredentialPayload>(credential)
|
|
446
|
+
sessionChannelId = parsed.payload.channelId
|
|
447
|
+
sessionChainId = sessionMd?.chainId ?? client?.chain?.id ?? 0
|
|
448
|
+
sessionEscrowContract = sessionMd?.escrowContract as Address | undefined
|
|
329
449
|
if ('cumulativeAmount' in parsed.payload && parsed.payload.cumulativeAmount)
|
|
330
|
-
|
|
450
|
+
sessionCumulativeAmount = BigInt(parsed.payload.cumulativeAmount)
|
|
331
451
|
|
|
332
452
|
if (parsed.payload.action === 'open') {
|
|
333
|
-
const depositRaw =
|
|
334
|
-
(challengeRequest.suggestedDeposit as string | undefined) ?? options.deposit
|
|
453
|
+
const depositRaw = challengeRequest.suggestedDeposit as string | undefined
|
|
335
454
|
const depositDisplay = depositRaw
|
|
336
455
|
? ` ${pc.dim(`(deposit ${depositRaw} ${tokenSymbol})`)}`
|
|
337
456
|
: ''
|
|
338
|
-
|
|
457
|
+
const prefix = options.confirm ? '' : '\n'
|
|
458
|
+
info(
|
|
459
|
+
`${prefix}${pc.dim(`Channel opened ${parsed.payload.channelId}`)}${depositDisplay}\n`,
|
|
460
|
+
)
|
|
461
|
+
} else {
|
|
462
|
+
const prefix = options.confirm ? '' : '\n'
|
|
463
|
+
info(`${prefix}${pc.dim(`Channel reused ${parsed.payload.channelId}`)}\n`)
|
|
339
464
|
}
|
|
340
465
|
}
|
|
341
466
|
|
|
@@ -379,8 +504,9 @@ cli
|
|
|
379
504
|
typeof receiptJson.acceptedCumulative === 'string' &&
|
|
380
505
|
receiptJson.acceptedCumulative
|
|
381
506
|
) {
|
|
382
|
-
|
|
383
|
-
if (
|
|
507
|
+
sessionCumulativeAmount = BigInt(receiptJson.acceptedCumulative)
|
|
508
|
+
if (sessionChannelId)
|
|
509
|
+
writeChannelCumulative(sessionChannelId, sessionCumulativeAmount)
|
|
384
510
|
}
|
|
385
511
|
info(`\n${pc.bold(pc.green('Payment Receipt'))}\n`)
|
|
386
512
|
const rows: [string, string][] = []
|
|
@@ -402,6 +528,15 @@ cli
|
|
|
402
528
|
explorerUrl
|
|
403
529
|
) {
|
|
404
530
|
rows.push([key, pc.link(`${explorerUrl}/tx/${value}`, value)])
|
|
531
|
+
} else if (
|
|
532
|
+
key === 'reference' &&
|
|
533
|
+
typeof value === 'string' &&
|
|
534
|
+
challenge.method === 'stripe' &&
|
|
535
|
+
value.startsWith('pi_')
|
|
536
|
+
) {
|
|
537
|
+
const isTest = process.env.MPPX_STRIPE_SECRET_KEY?.startsWith('sk_test_')
|
|
538
|
+
const dashboardUrl = `https://dashboard.stripe.com${isTest ? '/test' : ''}/payments/${value}`
|
|
539
|
+
rows.push([key, pc.link(dashboardUrl, value)])
|
|
405
540
|
} else rows.push([key, String(value)])
|
|
406
541
|
}
|
|
407
542
|
rows.sort(([a], [b]) => a.localeCompare(b))
|
|
@@ -421,21 +556,21 @@ cli
|
|
|
421
556
|
let buffer = ''
|
|
422
557
|
let currentEvent = ''
|
|
423
558
|
|
|
424
|
-
const
|
|
559
|
+
const sessionCred =
|
|
425
560
|
challenge.intent === 'session'
|
|
426
|
-
? Credential.deserialize<
|
|
561
|
+
? Credential.deserialize<SessionCredentialPayload>(credential)
|
|
427
562
|
: undefined
|
|
428
|
-
const channelId =
|
|
563
|
+
const channelId = sessionCred?.payload.channelId
|
|
429
564
|
const md = challenge.request.methodDetails as
|
|
430
565
|
| { escrowContract?: string; chainId?: number }
|
|
431
566
|
| undefined
|
|
432
|
-
const
|
|
567
|
+
const sessionChainId = md?.chainId ?? client?.chain?.id ?? 0
|
|
433
568
|
const escrowContract = md?.escrowContract as Address | undefined
|
|
434
569
|
let cumulativeAmount =
|
|
435
|
-
|
|
436
|
-
'cumulativeAmount' in
|
|
437
|
-
|
|
438
|
-
? BigInt(
|
|
570
|
+
sessionCred?.payload &&
|
|
571
|
+
'cumulativeAmount' in sessionCred.payload &&
|
|
572
|
+
sessionCred.payload.cumulativeAmount
|
|
573
|
+
? BigInt(sessionCred.payload.cumulativeAmount)
|
|
439
574
|
: 0n
|
|
440
575
|
let _voucherSeq = 0
|
|
441
576
|
|
|
@@ -481,7 +616,7 @@ cli
|
|
|
481
616
|
currentEvent === 'payment-need-voucher' &&
|
|
482
617
|
channelId &&
|
|
483
618
|
escrowContract &&
|
|
484
|
-
|
|
619
|
+
sessionChainId
|
|
485
620
|
) {
|
|
486
621
|
try {
|
|
487
622
|
const event = JSON.parse(data) as {
|
|
@@ -492,11 +627,11 @@ cli
|
|
|
492
627
|
cumulativeAmount = cumulativeAmount > required ? cumulativeAmount : required
|
|
493
628
|
|
|
494
629
|
const signature = await signVoucher(
|
|
495
|
-
client
|
|
496
|
-
account
|
|
630
|
+
client!,
|
|
631
|
+
account!,
|
|
497
632
|
{ channelId, cumulativeAmount },
|
|
498
633
|
escrowContract,
|
|
499
|
-
|
|
634
|
+
sessionChainId,
|
|
500
635
|
)
|
|
501
636
|
const voucherCred = Credential.serialize({
|
|
502
637
|
challenge,
|
|
@@ -506,7 +641,7 @@ cli
|
|
|
506
641
|
cumulativeAmount: cumulativeAmount.toString(),
|
|
507
642
|
signature,
|
|
508
643
|
},
|
|
509
|
-
source: `did:pkh:eip155:${
|
|
644
|
+
source: `did:pkh:eip155:${sessionChainId}:${account!.address}`,
|
|
510
645
|
})
|
|
511
646
|
await globalThis.fetch(url, {
|
|
512
647
|
method: 'POST',
|
|
@@ -583,15 +718,15 @@ cli
|
|
|
583
718
|
}
|
|
584
719
|
if (buffer.trim()) await processLines([buffer])
|
|
585
720
|
|
|
586
|
-
if (channelId && escrowContract &&
|
|
721
|
+
if (channelId && escrowContract && sessionChainId) {
|
|
587
722
|
const signature = await signVoucher(
|
|
588
|
-
client
|
|
589
|
-
account
|
|
723
|
+
client!,
|
|
724
|
+
account!,
|
|
590
725
|
{ channelId, cumulativeAmount },
|
|
591
726
|
escrowContract,
|
|
592
|
-
|
|
727
|
+
sessionChainId,
|
|
593
728
|
)
|
|
594
|
-
const closePayload:
|
|
729
|
+
const closePayload: SessionCredentialPayload = {
|
|
595
730
|
action: 'close',
|
|
596
731
|
channelId,
|
|
597
732
|
cumulativeAmount: cumulativeAmount.toString(),
|
|
@@ -600,7 +735,7 @@ cli
|
|
|
600
735
|
const closeCred = Credential.serialize({
|
|
601
736
|
challenge,
|
|
602
737
|
payload: closePayload,
|
|
603
|
-
source: `did:pkh:eip155:${
|
|
738
|
+
source: `did:pkh:eip155:${sessionChainId}:${account!.address}`,
|
|
604
739
|
})
|
|
605
740
|
const closeRes = await globalThis.fetch(url, {
|
|
606
741
|
method: 'POST',
|
|
@@ -638,9 +773,9 @@ cli
|
|
|
638
773
|
const shouldClose =
|
|
639
774
|
challenge.intent === 'session' &&
|
|
640
775
|
credentialResponse.ok &&
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
776
|
+
sessionChannelId &&
|
|
777
|
+
sessionEscrowContract &&
|
|
778
|
+
sessionChainId
|
|
644
779
|
if (shouldClose && options.confirm) {
|
|
645
780
|
info('\n')
|
|
646
781
|
}
|
|
@@ -648,22 +783,22 @@ cli
|
|
|
648
783
|
info(`${pc.dim('Kept channel open.')}\n`)
|
|
649
784
|
} else if (shouldClose) {
|
|
650
785
|
const signature = await signVoucher(
|
|
651
|
-
client
|
|
652
|
-
account
|
|
653
|
-
{ channelId:
|
|
654
|
-
|
|
655
|
-
|
|
786
|
+
client!,
|
|
787
|
+
account!,
|
|
788
|
+
{ channelId: sessionChannelId!, cumulativeAmount: sessionCumulativeAmount },
|
|
789
|
+
sessionEscrowContract!,
|
|
790
|
+
sessionChainId,
|
|
656
791
|
)
|
|
657
|
-
const closePayload:
|
|
792
|
+
const closePayload: SessionCredentialPayload = {
|
|
658
793
|
action: 'close',
|
|
659
|
-
channelId:
|
|
660
|
-
cumulativeAmount:
|
|
794
|
+
channelId: sessionChannelId!,
|
|
795
|
+
cumulativeAmount: sessionCumulativeAmount.toString(),
|
|
661
796
|
signature,
|
|
662
797
|
}
|
|
663
798
|
const closeCred = Credential.serialize({
|
|
664
799
|
challenge,
|
|
665
800
|
payload: closePayload,
|
|
666
|
-
source: `did:pkh:eip155:${
|
|
801
|
+
source: `did:pkh:eip155:${sessionChainId}:${account!.address}`,
|
|
667
802
|
})
|
|
668
803
|
const closeRes = await globalThis.fetch(url, {
|
|
669
804
|
...fetchInit,
|
|
@@ -673,7 +808,7 @@ cli
|
|
|
673
808
|
},
|
|
674
809
|
})
|
|
675
810
|
if (closeRes.ok) {
|
|
676
|
-
deleteChannelState(
|
|
811
|
+
deleteChannelState(sessionChannelId!)
|
|
677
812
|
const closeReceiptHeader = closeRes.headers.get('Payment-Receipt')
|
|
678
813
|
let closeTxHash: string | undefined
|
|
679
814
|
if (closeReceiptHeader) {
|
|
@@ -689,20 +824,21 @@ cli
|
|
|
689
824
|
closeTxHash && explorerUrl
|
|
690
825
|
? ` ${pc.dim(pc.link(`${explorerUrl}/tx/${closeTxHash}`, closeTxHash))}`
|
|
691
826
|
: ''
|
|
827
|
+
const closePrefix = options.confirm ? '' : '\n'
|
|
692
828
|
info(
|
|
693
|
-
|
|
829
|
+
`${closePrefix}${pc.dim('Channel closed.')} ${pc.dim(`Spent ${fmtBalance(sessionCumulativeAmount, tokenSymbol, tokenDecimals)}.`)}${txInfo}\n`,
|
|
694
830
|
)
|
|
695
831
|
} else {
|
|
696
832
|
const closeBody = await closeRes.text().catch(() => '')
|
|
697
833
|
info(
|
|
698
|
-
|
|
834
|
+
`\n${pc.dim(pc.yellow('Channel close failed'))} ${pc.dim(`(${closeRes.status})`)}\n`,
|
|
699
835
|
)
|
|
700
836
|
info(
|
|
701
|
-
`${pc.dim(` channelId: ${
|
|
702
|
-
`${pc.dim(` cumulativeAmount: ${
|
|
703
|
-
`${pc.dim(` escrowContract: ${
|
|
704
|
-
`${pc.dim(` chainId: ${
|
|
705
|
-
`${pc.dim(` account: ${account
|
|
837
|
+
`${pc.dim(` channelId: ${sessionChannelId}`)}\n` +
|
|
838
|
+
`${pc.dim(` cumulativeAmount: ${sessionCumulativeAmount}`)}\n` +
|
|
839
|
+
`${pc.dim(` escrowContract: ${sessionEscrowContract}`)}\n` +
|
|
840
|
+
`${pc.dim(` chainId: ${sessionChainId}`)}\n` +
|
|
841
|
+
`${pc.dim(` account: ${account?.address}`)}\n` +
|
|
706
842
|
`${pc.dim(` response: ${closeBody || '(empty)'}`)}\n`,
|
|
707
843
|
)
|
|
708
844
|
}
|
|
@@ -784,7 +920,11 @@ cli
|
|
|
784
920
|
const accounts = await createKeychain().list()
|
|
785
921
|
if (accounts.length === 1) createDefaultStore().set(resolvedName)
|
|
786
922
|
console.log(`Account "${resolvedName}" saved to keychain.`)
|
|
787
|
-
|
|
923
|
+
const explorerUrl = tempoMainnet.blockExplorers?.default?.url
|
|
924
|
+
const addrDisplay = explorerUrl
|
|
925
|
+
? pc.link(`${explorerUrl}/address/${account.address}`, account.address)
|
|
926
|
+
: account.address
|
|
927
|
+
console.log(pc.dim(`Address ${addrDisplay}`))
|
|
788
928
|
resolveChain(options)
|
|
789
929
|
.then((chain) => createClient({ chain, transport: http(options.rpcUrl) }))
|
|
790
930
|
.then((client) =>
|
|
@@ -823,8 +963,12 @@ cli
|
|
|
823
963
|
const account = privateKeyToAccount(key as `0x${string}`)
|
|
824
964
|
const balanceLines = await fetchBalanceLines(account.address, { includeTestnet: false })
|
|
825
965
|
if (!options.yes) {
|
|
966
|
+
const explorerUrl = tempoMainnet.blockExplorers?.default?.url
|
|
967
|
+
const addrDisplay = explorerUrl
|
|
968
|
+
? pc.link(`${explorerUrl}/address/${account.address}`, account.address)
|
|
969
|
+
: account.address
|
|
826
970
|
process.stderr.write(pc.dim(`Delete account "${options.account}"\n`))
|
|
827
|
-
process.stderr.write(pc.dim(` Address ${
|
|
971
|
+
process.stderr.write(pc.dim(` Address ${addrDisplay}\n`))
|
|
828
972
|
for (let i = 0; i < balanceLines.length; i++)
|
|
829
973
|
process.stderr.write(
|
|
830
974
|
pc.dim(` ${i === 0 ? 'Balance' : ' '} ${balanceLines[i]}\n`),
|
|
@@ -897,6 +1041,7 @@ cli
|
|
|
897
1041
|
}),
|
|
898
1042
|
)
|
|
899
1043
|
const resolved = entries.filter((e) => e !== undefined)
|
|
1044
|
+
const explorerUrl = tempoMainnet.blockExplorers?.default?.url
|
|
900
1045
|
const maxWidth = Math.max(
|
|
901
1046
|
...resolved.map((e) => e.name.length + (e.name === currentDefault ? 1 : 0)),
|
|
902
1047
|
)
|
|
@@ -904,7 +1049,10 @@ cli
|
|
|
904
1049
|
const isDefault = entry.name === currentDefault
|
|
905
1050
|
const label = isDefault ? `${entry.name}${pc.dim('*')}` : entry.name
|
|
906
1051
|
const width = entry.name.length + (isDefault ? 1 : 0)
|
|
907
|
-
|
|
1052
|
+
const addrDisplay = explorerUrl
|
|
1053
|
+
? pc.link(`${explorerUrl}/address/${entry.address}`, entry.address)
|
|
1054
|
+
: entry.address
|
|
1055
|
+
console.log(`${label}${' '.repeat(maxWidth - width + 2)}${pc.dim(addrDisplay)}`)
|
|
908
1056
|
}
|
|
909
1057
|
return
|
|
910
1058
|
}
|
|
@@ -918,10 +1066,14 @@ cli
|
|
|
918
1066
|
process.exit(1)
|
|
919
1067
|
}
|
|
920
1068
|
const account = privateKeyToAccount(key as `0x${string}`)
|
|
921
|
-
|
|
1069
|
+
const rpcUrl = options.rpcUrl ?? (process.env.MPPX_RPC_URL || undefined)
|
|
1070
|
+
const chain = rpcUrl ? await resolveChain({ rpcUrl }) : tempoMainnet
|
|
1071
|
+
const explorerUrl = chain.blockExplorers?.default?.url
|
|
1072
|
+
const addrDisplay = explorerUrl
|
|
1073
|
+
? pc.link(`${explorerUrl}/address/${account.address}`, account.address)
|
|
1074
|
+
: account.address
|
|
1075
|
+
console.log(`${pc.dim('Address')} ${addrDisplay}`)
|
|
922
1076
|
|
|
923
|
-
const rpcUrl = options.rpcUrl ?? process.env.MPPX_RPC_URL
|
|
924
|
-
const chain = rpcUrl ? await resolveChain({ rpcUrl }) : undefined
|
|
925
1077
|
const balanceLines = await fetchBalanceLines(
|
|
926
1078
|
account.address,
|
|
927
1079
|
chain && rpcUrl ? { chain, rpcUrl } : undefined,
|
|
@@ -971,6 +1123,21 @@ try {
|
|
|
971
1123
|
|
|
972
1124
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
973
1125
|
|
|
1126
|
+
function parseMethodOpts(raw: string | string[] | undefined): Record<string, string> {
|
|
1127
|
+
if (!raw) return {}
|
|
1128
|
+
const list = Array.isArray(raw) ? raw : [raw]
|
|
1129
|
+
const result: Record<string, string> = {}
|
|
1130
|
+
for (const item of list) {
|
|
1131
|
+
const idx = item.indexOf('=')
|
|
1132
|
+
if (idx === -1) {
|
|
1133
|
+
console.error(`Invalid method option format: ${item} (expected key=value)`)
|
|
1134
|
+
process.exit(1)
|
|
1135
|
+
}
|
|
1136
|
+
result[item.slice(0, idx)] = item.slice(idx + 1)
|
|
1137
|
+
}
|
|
1138
|
+
return result
|
|
1139
|
+
}
|
|
1140
|
+
|
|
974
1141
|
function parseOptions<const schema extends ZodMiniType>(
|
|
975
1142
|
schema: schema,
|
|
976
1143
|
rawOptions: unknown,
|
|
@@ -1054,7 +1221,7 @@ function createDefaultStore() {
|
|
|
1054
1221
|
|
|
1055
1222
|
function resolveAccountName(explicit?: string): string {
|
|
1056
1223
|
if (explicit) return explicit
|
|
1057
|
-
if (process.env.MPPX_ACCOUNT) return process.env.MPPX_ACCOUNT
|
|
1224
|
+
if (process.env.MPPX_ACCOUNT?.trim()) return process.env.MPPX_ACCOUNT
|
|
1058
1225
|
return createDefaultStore().get()
|
|
1059
1226
|
}
|
|
1060
1227
|
|
|
@@ -1238,9 +1405,9 @@ const pc = (() => {
|
|
|
1238
1405
|
bgMagentaBright: f('\x1b[105m', '\x1b[49m'),
|
|
1239
1406
|
bgCyanBright: f('\x1b[106m', '\x1b[49m'),
|
|
1240
1407
|
bgWhiteBright: f('\x1b[107m', '\x1b[49m'),
|
|
1241
|
-
link(url: string, text: string) {
|
|
1408
|
+
link(url: string, text: string, noUnderline?: boolean) {
|
|
1242
1409
|
if (!isColorSupported) return text
|
|
1243
|
-
return `\x1b]8;;${url}\x07${pc.underline(text)}\x1b]8;;\x07`
|
|
1410
|
+
return `\x1b]8;;${url}\x07${noUnderline ? text : pc.underline(text)}\x1b]8;;\x07`
|
|
1244
1411
|
},
|
|
1245
1412
|
}
|
|
1246
1413
|
})()
|
|
@@ -1268,6 +1435,8 @@ function chainName(chain: { id: number; name: string }) {
|
|
|
1268
1435
|
}
|
|
1269
1436
|
|
|
1270
1437
|
const pathUsd = '0x20c0000000000000000000000000000000000000' as Address
|
|
1438
|
+
const usdc = '0x20C000000000000000000000b9537d11c60E8b50' as Address
|
|
1439
|
+
const mainnetTokens = [pathUsd, usdc] as const
|
|
1271
1440
|
const testnetTokens = [
|
|
1272
1441
|
'0x20c0000000000000000000000000000000000000',
|
|
1273
1442
|
'0x20c0000000000000000000000000000000000001',
|
|
@@ -1275,11 +1444,20 @@ const testnetTokens = [
|
|
|
1275
1444
|
'0x20c0000000000000000000000000000000000003',
|
|
1276
1445
|
] as const
|
|
1277
1446
|
|
|
1278
|
-
function fmtBalance(
|
|
1447
|
+
function fmtBalance(
|
|
1448
|
+
b: bigint,
|
|
1449
|
+
symbol: string,
|
|
1450
|
+
decimals = 6,
|
|
1451
|
+
opts?: { explorerUrl?: string | undefined; token?: string | undefined },
|
|
1452
|
+
) {
|
|
1279
1453
|
const value = Number(b) / 10 ** decimals
|
|
1280
1454
|
const [int, dec] = value.toString().split('.')
|
|
1281
1455
|
const formatted = int!.replace(/\B(?=(\d{3})+(?!\d))/g, '_')
|
|
1282
|
-
|
|
1456
|
+
const sym =
|
|
1457
|
+
opts?.explorerUrl && opts.token
|
|
1458
|
+
? pc.dim(pc.link(`${opts.explorerUrl}/token/${opts.token}`, symbol, true))
|
|
1459
|
+
: pc.dim(symbol)
|
|
1460
|
+
return `${dec ? `${formatted}.${dec}` : formatted} ${sym}`
|
|
1283
1461
|
}
|
|
1284
1462
|
|
|
1285
1463
|
function isTestnet(chain: Chain) {
|
|
@@ -1296,9 +1474,13 @@ async function fetchTokenInfo(
|
|
|
1296
1474
|
Actions.token.getBalance(client, { account, token }).catch(() => 0n),
|
|
1297
1475
|
Actions.token.getMetadata(client, { token }).catch(() => ({ symbol: token as string })),
|
|
1298
1476
|
])
|
|
1299
|
-
const
|
|
1477
|
+
const knownSymbols: Record<string, string> = {
|
|
1478
|
+
[pathUsd]: 'PathUSD',
|
|
1479
|
+
[usdc]: 'USDC',
|
|
1480
|
+
}
|
|
1481
|
+
const symbol = knownSymbols[token] ?? metadata.symbol
|
|
1300
1482
|
const decimals = 'decimals' in metadata ? metadata.decimals : 6
|
|
1301
|
-
return { balance, symbol, decimals }
|
|
1483
|
+
return { balance, symbol, decimals, token }
|
|
1302
1484
|
}
|
|
1303
1485
|
|
|
1304
1486
|
function detectTerminalBg(
|
|
@@ -1340,6 +1522,7 @@ async function fetchBalanceLines(
|
|
|
1340
1522
|
): Promise<string[]> {
|
|
1341
1523
|
if (opts?.chain) {
|
|
1342
1524
|
const client = createClient({ chain: opts.chain, transport: http(opts.rpcUrl) })
|
|
1525
|
+
const explorerUrl = opts.chain.blockExplorers?.default?.url
|
|
1343
1526
|
const label = pc.dim(`(${chainName(opts.chain)})`)
|
|
1344
1527
|
if (isTestnet(opts.chain)) {
|
|
1345
1528
|
const results = await Promise.all(
|
|
@@ -1347,24 +1530,46 @@ async function fetchBalanceLines(
|
|
|
1347
1530
|
)
|
|
1348
1531
|
return results
|
|
1349
1532
|
.filter((t) => t.balance > 0n)
|
|
1350
|
-
.map(
|
|
1533
|
+
.map(
|
|
1534
|
+
(t) =>
|
|
1535
|
+
`${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl, token: t.token })} ${label}`,
|
|
1536
|
+
)
|
|
1351
1537
|
}
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1538
|
+
const results = await Promise.all(
|
|
1539
|
+
mainnetTokens.map((token) => fetchTokenInfo(client, token, address)),
|
|
1540
|
+
)
|
|
1541
|
+
return results.map(
|
|
1542
|
+
(t) =>
|
|
1543
|
+
`${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl, token: t.token })} ${label}`,
|
|
1544
|
+
)
|
|
1354
1545
|
}
|
|
1355
1546
|
|
|
1356
|
-
const mainnetClient = createClient({
|
|
1357
|
-
|
|
1358
|
-
|
|
1547
|
+
const mainnetClient = createClient({
|
|
1548
|
+
chain: tempoMainnet,
|
|
1549
|
+
transport: http(process.env.MPPX_RPC_URL || undefined),
|
|
1550
|
+
})
|
|
1551
|
+
const mainnetExplorerUrl = tempoMainnet.blockExplorers?.default?.url
|
|
1552
|
+
const mainnetResults = await Promise.all(
|
|
1553
|
+
mainnetTokens.map((token) => fetchTokenInfo(mainnetClient, token, address)),
|
|
1554
|
+
)
|
|
1555
|
+
const lines = mainnetResults.map((t) =>
|
|
1556
|
+
fmtBalance(t.balance, t.symbol, t.decimals, {
|
|
1557
|
+
explorerUrl: mainnetExplorerUrl,
|
|
1558
|
+
token: t.token,
|
|
1559
|
+
}),
|
|
1560
|
+
)
|
|
1359
1561
|
|
|
1360
1562
|
if (opts?.includeTestnet !== false) {
|
|
1361
1563
|
const testnetClient = createClient({ chain: tempoModerato, transport: http() })
|
|
1564
|
+
const testnetExplorerUrl = tempoModerato.blockExplorers?.default?.url
|
|
1362
1565
|
const testnetResults = await Promise.all(
|
|
1363
1566
|
testnetTokens.map((token) => fetchTokenInfo(testnetClient, token, address)),
|
|
1364
1567
|
)
|
|
1365
1568
|
for (const t of testnetResults) {
|
|
1366
1569
|
if (t.balance > 0n)
|
|
1367
|
-
lines.push(
|
|
1570
|
+
lines.push(
|
|
1571
|
+
`${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl: testnetExplorerUrl, token: t.token })} ${pc.dim('(testnet)')}`,
|
|
1572
|
+
)
|
|
1368
1573
|
}
|
|
1369
1574
|
}
|
|
1370
1575
|
|