mppx 0.4.1 → 0.4.3

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 (63) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/README.md +30 -6
  3. package/dist/bin.js +2 -2
  4. package/dist/bin.js.map +1 -1
  5. package/dist/cli/account.d.ts +53 -0
  6. package/dist/cli/account.d.ts.map +1 -0
  7. package/dist/cli/account.js +156 -0
  8. package/dist/cli/account.js.map +1 -0
  9. package/dist/{cli.d.ts → cli/cli.d.ts} +4 -3
  10. package/dist/cli/cli.d.ts.map +1 -0
  11. package/dist/cli/cli.js +852 -0
  12. package/dist/cli/cli.js.map +1 -0
  13. package/dist/cli/config.d.ts +39 -0
  14. package/dist/cli/config.d.ts.map +1 -0
  15. package/dist/cli/config.js +30 -0
  16. package/dist/cli/config.js.map +1 -0
  17. package/dist/cli/internal.d.ts +16 -0
  18. package/dist/cli/internal.d.ts.map +1 -0
  19. package/dist/cli/internal.js +58 -0
  20. package/dist/cli/internal.js.map +1 -0
  21. package/dist/cli/plugins/index.d.ts +4 -0
  22. package/dist/cli/plugins/index.d.ts.map +1 -0
  23. package/dist/cli/plugins/index.js +4 -0
  24. package/dist/cli/plugins/index.js.map +1 -0
  25. package/dist/cli/plugins/plugin.d.ts +68 -0
  26. package/dist/cli/plugins/plugin.d.ts.map +1 -0
  27. package/dist/cli/plugins/plugin.js +4 -0
  28. package/dist/cli/plugins/plugin.js.map +1 -0
  29. package/dist/cli/plugins/stripe.d.ts +2 -0
  30. package/dist/cli/plugins/stripe.d.ts.map +1 -0
  31. package/dist/cli/plugins/stripe.js +118 -0
  32. package/dist/cli/plugins/stripe.js.map +1 -0
  33. package/dist/cli/plugins/tempo.d.ts +11 -0
  34. package/dist/cli/plugins/tempo.d.ts.map +1 -0
  35. package/dist/cli/plugins/tempo.js +706 -0
  36. package/dist/cli/plugins/tempo.js.map +1 -0
  37. package/dist/cli/utils.d.ts +93 -0
  38. package/dist/cli/utils.d.ts.map +1 -0
  39. package/dist/cli/utils.js +274 -0
  40. package/dist/cli/utils.js.map +1 -0
  41. package/dist/tempo/client/Methods.d.ts +1 -1
  42. package/dist/tempo/client/Session.d.ts +2 -2
  43. package/dist/tempo/internal/defaults.d.ts +1 -1
  44. package/dist/tempo/internal/defaults.js +1 -1
  45. package/package.json +12 -1
  46. package/src/bin.ts +2 -2
  47. package/src/cli/account.ts +157 -0
  48. package/src/{cli.test.ts → cli/cli.test.ts} +107 -51
  49. package/src/cli/cli.ts +907 -0
  50. package/src/cli/config.test.ts +82 -0
  51. package/src/cli/config.ts +44 -0
  52. package/src/cli/internal.ts +72 -0
  53. package/src/cli/plugins/index.ts +3 -0
  54. package/src/cli/plugins/plugin.ts +73 -0
  55. package/src/cli/plugins/stripe.ts +143 -0
  56. package/src/cli/plugins/tempo.ts +842 -0
  57. package/src/cli/utils.ts +336 -0
  58. package/src/tempo/internal/defaults.test.ts +1 -1
  59. package/src/tempo/internal/defaults.ts +1 -1
  60. package/dist/cli.d.ts.map +0 -1
  61. package/dist/cli.js +0 -1992
  62. package/dist/cli.js.map +0 -1
  63. package/src/cli.ts +0 -2178
@@ -0,0 +1,336 @@
1
+ import * as readline from 'node:readline'
2
+ import type { Chain } from 'viem'
3
+ import { type Address, createClient, http } from 'viem'
4
+ import { tempo as tempoMainnet, tempoModerato } from 'viem/chains'
5
+
6
+ // Inlined from https://github.com/alexeyraspopov/picocolors (ISC License)
7
+ export const pc = (() => {
8
+ const p = process || ({} as NodeJS.Process)
9
+ const argv = p.argv || []
10
+ const env = p.env || {}
11
+ const isColorSupported =
12
+ !(!!env.NO_COLOR || argv.includes('--no-color')) &&
13
+ (!!env.FORCE_COLOR ||
14
+ argv.includes('--color') ||
15
+ ((p.stdout || ({} as NodeJS.WriteStream)).isTTY && env.TERM !== 'dumb') ||
16
+ !!env.CI)
17
+
18
+ const replaceClose = (string: string, close: string, replace: string, index: number): string => {
19
+ let result = ''
20
+ let cursor = 0
21
+ let i = index
22
+ do {
23
+ result += string.substring(cursor, i) + replace
24
+ cursor = i + close.length
25
+ i = string.indexOf(close, cursor)
26
+ } while (~i)
27
+ return result + string.substring(cursor)
28
+ }
29
+
30
+ const formatter =
31
+ (open: string, close: string, replace = open) =>
32
+ (input: unknown) => {
33
+ const string = `${input}`
34
+ const index = string.indexOf(close, open.length)
35
+ return ~index
36
+ ? open + replaceClose(string, close, replace, index) + close
37
+ : open + string + close
38
+ }
39
+
40
+ const f = isColorSupported ? formatter : () => String
41
+ return {
42
+ isColorSupported,
43
+ reset: f('\x1b[0m', '\x1b[0m'),
44
+ bold: f('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
45
+ dim: f('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'),
46
+ italic: f('\x1b[3m', '\x1b[23m'),
47
+ underline: f('\x1b[4m', '\x1b[24m'),
48
+ inverse: f('\x1b[7m', '\x1b[27m'),
49
+ hidden: f('\x1b[8m', '\x1b[28m'),
50
+ strikethrough: f('\x1b[9m', '\x1b[29m'),
51
+ black: f('\x1b[30m', '\x1b[39m'),
52
+ red: f('\x1b[31m', '\x1b[39m'),
53
+ green: f('\x1b[32m', '\x1b[39m'),
54
+ yellow: f('\x1b[33m', '\x1b[39m'),
55
+ blue: f('\x1b[34m', '\x1b[39m'),
56
+ magenta: f('\x1b[35m', '\x1b[39m'),
57
+ cyan: f('\x1b[36m', '\x1b[39m'),
58
+ white: f('\x1b[37m', '\x1b[39m'),
59
+ gray: f('\x1b[90m', '\x1b[39m'),
60
+ bgBlack: f('\x1b[40m', '\x1b[49m'),
61
+ bgRed: f('\x1b[41m', '\x1b[49m'),
62
+ bgGreen: f('\x1b[42m', '\x1b[49m'),
63
+ bgYellow: f('\x1b[43m', '\x1b[49m'),
64
+ bgBlue: f('\x1b[44m', '\x1b[49m'),
65
+ bgMagenta: f('\x1b[45m', '\x1b[49m'),
66
+ bgCyan: f('\x1b[46m', '\x1b[49m'),
67
+ bgWhite: f('\x1b[47m', '\x1b[49m'),
68
+ blackBright: f('\x1b[90m', '\x1b[39m'),
69
+ redBright: f('\x1b[91m', '\x1b[39m'),
70
+ greenBright: f('\x1b[92m', '\x1b[39m'),
71
+ yellowBright: f('\x1b[93m', '\x1b[39m'),
72
+ blueBright: f('\x1b[94m', '\x1b[39m'),
73
+ magentaBright: f('\x1b[95m', '\x1b[39m'),
74
+ cyanBright: f('\x1b[96m', '\x1b[39m'),
75
+ whiteBright: f('\x1b[97m', '\x1b[39m'),
76
+ bgBlackBright: f('\x1b[100m', '\x1b[49m'),
77
+ bgRedBright: f('\x1b[101m', '\x1b[49m'),
78
+ bgGreenBright: f('\x1b[102m', '\x1b[49m'),
79
+ bgYellowBright: f('\x1b[103m', '\x1b[49m'),
80
+ bgBlueBright: f('\x1b[104m', '\x1b[49m'),
81
+ bgMagentaBright: f('\x1b[105m', '\x1b[49m'),
82
+ bgCyanBright: f('\x1b[106m', '\x1b[49m'),
83
+ bgWhiteBright: f('\x1b[107m', '\x1b[49m'),
84
+ link(url: string, text: string, noUnderline?: boolean) {
85
+ if (!isColorSupported) return text
86
+ return `\x1b]8;;${url}\x07${noUnderline ? text : pc.underline(text)}\x1b]8;;\x07`
87
+ },
88
+ }
89
+ })()
90
+
91
+ export function printRequestHeaders(
92
+ reqUrl: string,
93
+ init: RequestInit,
94
+ info: (msg: string) => void,
95
+ ) {
96
+ const { pathname, host } = new URL(reqUrl)
97
+ const method = (init.method ?? 'GET').toUpperCase()
98
+ info(`> ${method} ${pathname} HTTP/1.1\n`)
99
+ info(`> Host: ${host}\n`)
100
+ for (const [k, v] of Object.entries((init.headers ?? {}) as Record<string, string>))
101
+ info(`> ${k}: ${v}\n`)
102
+ info('>\n')
103
+ }
104
+
105
+ export function printResponseHeaders(
106
+ res: Response,
107
+ opts: { include: boolean; verbose: number; silent: boolean },
108
+ ) {
109
+ if (!opts.include && opts.verbose < 2) return
110
+ if (opts.silent) return
111
+ const status = `HTTP/1.1 ${res.status} ${res.statusText}`
112
+ const out = opts.verbose >= 2 ? process.stderr : process.stdout
113
+ const prefix = opts.verbose >= 2 ? '< ' : ''
114
+ out.write(`${prefix}${status}\n`)
115
+ for (const [k, v] of res.headers) out.write(`${prefix}${k}: ${v}\n`)
116
+ out.write(opts.verbose >= 2 ? '<\n' : '\n')
117
+ }
118
+
119
+ const balanceKeys = new Set(['amount', 'suggestedDeposit', 'minVoucherDelta'])
120
+
121
+ export function fmtRequestValue(
122
+ key: string,
123
+ value: unknown,
124
+ ctx: { tokenSymbol: string; tokenDecimals: number; explorerUrl?: string | undefined },
125
+ ): string {
126
+ if (balanceKeys.has(key) && typeof value === 'string') {
127
+ return `${value} ${pc.dim(`(${fmtBalance(BigInt(value), ctx.tokenSymbol, ctx.tokenDecimals)})`)}`
128
+ }
129
+ if (key === 'chainId' && typeof value === 'number') {
130
+ const name = chainName({ id: value, name: '' })
131
+ return name ? `${value} ${pc.dim(`(${name})`)}` : String(value)
132
+ }
133
+ if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value))
134
+ return ctx.explorerUrl ? link(`${ctx.explorerUrl}/address/${value}`, value) : value
135
+ if (typeof value === 'string' && /^https?:\/\//.test(value)) return pc.link(value, value)
136
+ return String(value)
137
+ }
138
+
139
+ export function decodeMemo(hex: string): string | undefined {
140
+ try {
141
+ const stripped = hex.replace(/^0x0*/, '')
142
+ if (!stripped) return undefined
143
+ const bytes = Uint8Array.from(stripped.match(/.{1,2}/g)!.map((b) => Number.parseInt(b, 16)))
144
+ const decoded = new TextDecoder().decode(bytes)
145
+ return /^[\x20-\x7e]+$/.test(decoded) ? decoded : undefined
146
+ } catch {
147
+ return undefined
148
+ }
149
+ }
150
+
151
+ export function fmtChallengeValue(key: string, value: unknown): string {
152
+ if (key === 'realm' && typeof value === 'string') {
153
+ try {
154
+ const realmUrl = new URL(value.includes('://') ? value : `https://${value}`)
155
+ return pc.link(realmUrl.href, value)
156
+ } catch {}
157
+ }
158
+ return String(value)
159
+ }
160
+
161
+ export function link(url: string, text: string): string {
162
+ return pc.link(url, text)
163
+ }
164
+
165
+ export function parseMethodOpts(raw: string | string[] | undefined): Record<string, string> {
166
+ if (!raw) return {}
167
+ const list = Array.isArray(raw) ? raw : [raw]
168
+ const result: Record<string, string> = {}
169
+ for (const item of list) {
170
+ const idx = item.indexOf('=')
171
+ if (idx === -1) {
172
+ throw new Error(`Invalid method option format: ${item} (expected key=value)`)
173
+ }
174
+ result[item.slice(0, idx)] = item.slice(idx + 1)
175
+ }
176
+ return result
177
+ }
178
+
179
+ export function isTempoAccount(accountName: string): boolean {
180
+ return accountName.startsWith('tempo:')
181
+ }
182
+
183
+ export function prompt(message: string): Promise<string | undefined> {
184
+ const reader = readline.createInterface({ input: process.stdin, output: process.stderr })
185
+ return new Promise((resolve) => {
186
+ reader.on('close', () => resolve(undefined))
187
+ reader.question(`${pc.bold(`▸ ${message}:`)} `, (answer) => {
188
+ reader.close()
189
+ const value = answer.trim()
190
+ resolve(value || undefined)
191
+ })
192
+ })
193
+ }
194
+
195
+ export function confirm(prompt: string, defaultYes = false): Promise<boolean> {
196
+ const reader = readline.createInterface({ input: process.stdin, output: process.stderr })
197
+ return new Promise((resolve) => {
198
+ const hint = defaultYes ? '(Y/n)' : '(y/N)'
199
+ reader.question(`${pc.bold(`▸ ${prompt}`)} ${pc.dim(hint)} `, (answer) => {
200
+ reader.close()
201
+ const trimmed = answer.trim().toLowerCase()
202
+ resolve(trimmed === '' ? defaultYes : trimmed === 'y')
203
+ })
204
+ })
205
+ }
206
+
207
+ export function fmtBalance(
208
+ b: bigint,
209
+ symbol: string,
210
+ decimals = 6,
211
+ opts?: { explorerUrl?: string | undefined; token?: string | undefined },
212
+ ) {
213
+ const value = Number(b) / 10 ** decimals
214
+ const [int, dec] = value.toString().split('.')
215
+ const formatted = int!.replace(/\B(?=(\d{3})+(?!\d))/g, '_')
216
+ const sym =
217
+ opts?.explorerUrl && opts.token
218
+ ? pc.dim(pc.link(`${opts.explorerUrl}/token/${opts.token}`, symbol, true))
219
+ : pc.dim(symbol)
220
+ return `${dec ? `${formatted}.${dec}` : formatted} ${sym}`
221
+ }
222
+
223
+ export async function resolveChain(opts: { rpcUrl?: string | undefined } = {}): Promise<Chain> {
224
+ if (!opts.rpcUrl) return tempoModerato
225
+ const { getChainId } = await import('viem/actions')
226
+ const chainId = await getChainId(createClient({ transport: http(opts.rpcUrl) }))
227
+ const allExports = Object.values(await import('viem/chains')) as unknown[]
228
+ const candidates = allExports.filter(
229
+ (c): c is Chain =>
230
+ typeof c === 'object' && c !== null && 'id' in c && (c as Chain).id === chainId,
231
+ )
232
+ const found = candidates.find((c) => 'serializers' in c && c.serializers) ?? candidates[0]
233
+ if (!found) throw new Error(`Unknown chain ID ${chainId} from RPC ${opts.rpcUrl}`)
234
+ return found
235
+ }
236
+
237
+ export function chainName(chain: { id: number; name: string }) {
238
+ const chainNames: Record<number, string> = {
239
+ [tempoMainnet.id]: 'mainnet',
240
+ [tempoModerato.id]: 'testnet',
241
+ }
242
+ return chainNames[chain.id] ?? chain.name
243
+ }
244
+
245
+ export const pathUsd = '0x20c0000000000000000000000000000000000000' as Address
246
+ export const usdc = '0x20C000000000000000000000b9537d11c60E8b50' as Address
247
+ export const mainnetTokens = [pathUsd, usdc] as const
248
+ export const testnetTokens = [
249
+ '0x20c0000000000000000000000000000000000000',
250
+ '0x20c0000000000000000000000000000000000001',
251
+ '0x20c0000000000000000000000000000000000002',
252
+ '0x20c0000000000000000000000000000000000003',
253
+ ] as const
254
+
255
+ export function isTestnet(chain: Chain) {
256
+ return chain.id !== tempoMainnet.id
257
+ }
258
+
259
+ export async function fetchTokenInfo(
260
+ client: ReturnType<typeof createClient>,
261
+ token: Address,
262
+ account: Address,
263
+ ) {
264
+ const { Actions } = await import('viem/tempo')
265
+ const [balance, metadata] = await Promise.all([
266
+ Actions.token.getBalance(client, { account, token }).catch(() => 0n),
267
+ Actions.token.getMetadata(client, { token }).catch(() => ({ symbol: token as string })),
268
+ ])
269
+ const knownSymbols: Record<string, string> = {
270
+ [pathUsd]: 'PathUSD',
271
+ [usdc]: 'USDC',
272
+ }
273
+ const symbol = knownSymbols[token] ?? metadata.symbol
274
+ const decimals = 'decimals' in metadata ? metadata.decimals : 6
275
+ return { balance, symbol, decimals, token }
276
+ }
277
+
278
+ export async function fetchBalanceLines(
279
+ address: Address,
280
+ opts?: { chain?: Chain; rpcUrl?: string; includeTestnet?: boolean },
281
+ ): Promise<string[]> {
282
+ if (opts?.chain) {
283
+ const client = createClient({ chain: opts.chain, transport: http(opts.rpcUrl) })
284
+ const explorerUrl = opts.chain.blockExplorers?.default?.url
285
+ const label = pc.dim(`(${chainName(opts.chain)})`)
286
+ if (isTestnet(opts.chain)) {
287
+ const results = await Promise.all(
288
+ testnetTokens.map((token) => fetchTokenInfo(client, token, address)),
289
+ )
290
+ return results
291
+ .filter((t) => t.balance > 0n)
292
+ .map(
293
+ (t) =>
294
+ `${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl, token: t.token })} ${label}`,
295
+ )
296
+ }
297
+ const results = await Promise.all(
298
+ mainnetTokens.map((token) => fetchTokenInfo(client, token, address)),
299
+ )
300
+ return results.map(
301
+ (t) =>
302
+ `${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl, token: t.token })} ${label}`,
303
+ )
304
+ }
305
+
306
+ const mainnetClient = createClient({
307
+ chain: tempoMainnet,
308
+ transport: http(process.env.MPPX_RPC_URL || undefined),
309
+ })
310
+ const mainnetExplorerUrl = tempoMainnet.blockExplorers?.default?.url
311
+ const mainnetResults = await Promise.all(
312
+ mainnetTokens.map((token) => fetchTokenInfo(mainnetClient, token, address)),
313
+ )
314
+ const lines = mainnetResults.map((t) =>
315
+ fmtBalance(t.balance, t.symbol, t.decimals, {
316
+ explorerUrl: mainnetExplorerUrl,
317
+ token: t.token,
318
+ }),
319
+ )
320
+
321
+ if (opts?.includeTestnet !== false) {
322
+ const testnetClient = createClient({ chain: tempoModerato, transport: http() })
323
+ const testnetExplorerUrl = tempoModerato.blockExplorers?.default?.url
324
+ const testnetResults = await Promise.all(
325
+ testnetTokens.map((token) => fetchTokenInfo(testnetClient, token, address)),
326
+ )
327
+ for (const t of testnetResults) {
328
+ if (t.balance > 0n)
329
+ lines.push(
330
+ `${fmtBalance(t.balance, t.symbol, t.decimals, { explorerUrl: testnetExplorerUrl, token: t.token })} ${pc.dim('(testnet)')}`,
331
+ )
332
+ }
333
+ }
334
+
335
+ return lines
336
+ }
@@ -49,7 +49,7 @@ describe('rpcUrl', () => {
49
49
 
50
50
  describe('escrowContract', () => {
51
51
  test('mainnet escrow contract', () => {
52
- expect(escrowContract[chainId.mainnet]).toBe('0x0901aED692C755b870F9605E56BAA66C35BEfF69')
52
+ expect(escrowContract[chainId.mainnet]).toBe('0x33b901018174DDabE4841042ab76ba85D4e24f25')
53
53
  })
54
54
 
55
55
  test('testnet escrow contract', () => {
@@ -31,7 +31,7 @@ export const decimals = 6
31
31
 
32
32
  /** Default payment-channel escrow contract addresses per chain. */
33
33
  export const escrowContract = {
34
- [chainId.mainnet]: '0x0901aED692C755b870F9605E56BAA66C35BEfF69',
34
+ [chainId.mainnet]: '0x33b901018174DDabE4841042ab76ba85D4e24f25',
35
35
  [chainId.testnet]: '0x542831e3E4Ace07559b7C8787395f4Fb99F70787',
36
36
  } as const satisfies Record<ChainId, string>
37
37
 
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,GAAG,EAAK,MAAM,OAAO,CAAA;AA6B9B,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;wBAgiCP,CAAA;AAqgBF,eAAe,GAAG,CAAA"}