mppx 0.6.10 → 0.6.11

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.
@@ -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,6wtcAA6wtc,CAAA"}
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,ixtcAAixtc,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.6.10",
4
+ "version": "0.6.11",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "files": [
@@ -4,13 +4,13 @@ import * as os from 'node:os'
4
4
  import * as path from 'node:path'
5
5
  import { pathToFileURL } from 'node:url'
6
6
 
7
- import { parseUnits } from 'viem'
7
+ import { decodeFunctionData, erc20Abi, parseUnits, type Address } from 'viem'
8
8
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
9
- import { Addresses } from 'viem/tempo'
9
+ import { Addresses, Transaction } from 'viem/tempo'
10
10
  import { afterAll, describe, expect, test } from 'vp/test'
11
11
  import * as Http from '~test/Http.js'
12
12
  import { rpcUrl } from '~test/tempo/prool.js'
13
- import { deployEscrow } from '~test/tempo/session.js'
13
+ import { deployEscrow, escrowAbi } from '~test/tempo/session.js'
14
14
  import { accounts, asset, chain, client, fundAccount } from '~test/tempo/viem.js'
15
15
 
16
16
  import * as Challenge from '../Challenge.js'
@@ -29,6 +29,10 @@ import cli from './cli.js'
29
29
  const testPrivateKey = generatePrivateKey()
30
30
  const testAccount = privateKeyToAccount(testPrivateKey)
31
31
  const cliTestXdgDataHome = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-cli-xdg-'))
32
+ const cliSessionFeePayerPolicy = {
33
+ maxGas: 2_250_000n,
34
+ maxTotalFee: 60_000_000_000_000_000n,
35
+ }
32
36
 
33
37
  afterAll(() => {
34
38
  fs.rmSync(cliTestXdgDataHome, { recursive: true, force: true })
@@ -698,6 +702,7 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
698
702
  escrowContract: escrow,
699
703
  chainId: client.chain.id,
700
704
  feePayer: true,
705
+ feePayerPolicy: cliSessionFeePayerPolicy,
701
706
  }),
702
707
  ],
703
708
  realm: 'cli-test-multifetch',
@@ -727,6 +732,89 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
727
732
  }
728
733
  })
729
734
 
735
+ test('prefers CLI deposit over server suggestedDeposit', { timeout: 120_000 }, async () => {
736
+ await fundAccount({ address: testAccount.address, token: Addresses.pathUsd })
737
+ await fundAccount({ address: testAccount.address, token: asset })
738
+
739
+ const escrow = await deployEscrow()
740
+ let openCredential: SessionCredentialPayload | undefined
741
+
742
+ const httpServer = await Http.createServer(async (req, res) => {
743
+ const authHeader = req.headers.authorization
744
+ if (!authHeader) {
745
+ res.writeHead(402, {
746
+ 'WWW-Authenticate': Challenge.serialize(
747
+ Challenge.from({
748
+ id: 'cli-deposit-override',
749
+ realm: 'cli-test-deposit-override',
750
+ method: 'tempo',
751
+ intent: 'session',
752
+ request: {
753
+ amount: '1000000',
754
+ currency: asset,
755
+ decimals: 6,
756
+ recipient: accounts[0].address,
757
+ suggestedDeposit: '7000000',
758
+ unitType: 'token',
759
+ methodDetails: {
760
+ chainId: chain.id,
761
+ escrowContract: escrow,
762
+ },
763
+ },
764
+ }),
765
+ ),
766
+ })
767
+ res.end()
768
+ return
769
+ }
770
+
771
+ try {
772
+ const cred = Credential.deserialize<SessionCredentialPayload>(authHeader)
773
+ if (cred.payload.action === 'open') openCredential = cred.payload
774
+ } catch {}
775
+
776
+ res.writeHead(200)
777
+ res.end('scraped-content')
778
+ })
779
+
780
+ try {
781
+ await serve([httpServer.url, '--rpc-url', rpcUrl, '-s', '-M', 'deposit=10'], {
782
+ env: { MPPX_PRIVATE_KEY: testPrivateKey },
783
+ })
784
+
785
+ expect(openCredential).toBeDefined()
786
+ expect(openCredential?.action).toBe('open')
787
+ if (!openCredential || openCredential.action !== 'open')
788
+ throw new Error('missing open credential')
789
+
790
+ const transaction = Transaction.deserialize(openCredential.transaction)
791
+ if (!('calls' in transaction)) throw new Error('unexpected transaction type')
792
+ const [approveCall, openCall] = transaction.calls as readonly [
793
+ { to?: Address; data?: `0x${string}` },
794
+ { to?: Address; data?: `0x${string}` },
795
+ ]
796
+ const approve = decodeFunctionData({ abi: erc20Abi, data: approveCall.data ?? '0x' })
797
+ const open = decodeFunctionData({ abi: escrowAbi, data: openCall.data ?? '0x' })
798
+ const approveArgs = approve.args as readonly [Address, bigint]
799
+ const openArgs = open.args as readonly [Address, Address, bigint, string, Address]
800
+
801
+ expect(approveCall.to).toBe(asset)
802
+ expect(approve.functionName).toBe('approve')
803
+ expect(approveArgs[0].toLowerCase()).toBe(escrow.toLowerCase())
804
+ expect(approveArgs[1]).toBe(10_000_000n)
805
+
806
+ expect(openCall.to?.toLowerCase()).toBe(escrow.toLowerCase())
807
+ expect(open.functionName).toBe('open')
808
+ expect(openArgs[0].toLowerCase()).toBe(accounts[0].address.toLowerCase())
809
+ expect(openArgs[1].toLowerCase()).toBe(asset.toLowerCase())
810
+ expect(openArgs[2]).toBe(10_000_000n)
811
+ expect(openArgs[3]).toEqual(expect.any(String))
812
+ expect(openArgs[4].toLowerCase()).toBe(testAccount.address.toLowerCase())
813
+ } finally {
814
+ httpServer.close()
815
+ }
816
+ })
817
+
730
818
  test('bug: non-SSE open should not double-charge tick amount', { timeout: 120_000 }, async () => {
731
819
  await fundAccount({ address: testAccount.address, token: Addresses.pathUsd })
732
820
  await fundAccount({ address: testAccount.address, token: asset })
@@ -744,6 +832,7 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
744
832
  escrowContract: escrow,
745
833
  chainId: client.chain.id,
746
834
  feePayer: true,
835
+ feePayerPolicy: cliSessionFeePayerPolicy,
747
836
  }),
748
837
  ],
749
838
  realm: 'cli-test-double-charge',
@@ -806,6 +895,7 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
806
895
  escrowContract: escrow,
807
896
  chainId: client.chain.id,
808
897
  feePayer: true,
898
+ feePayerPolicy: cliSessionFeePayerPolicy,
809
899
  }),
810
900
  ],
811
901
  realm: 'cli-test-close-action',
@@ -883,6 +973,7 @@ describe('session sse (examples/session/sse)', () => {
883
973
  escrowContract: escrow,
884
974
  chainId: client.chain.id,
885
975
  feePayer: true,
976
+ feePayerPolicy: cliSessionFeePayerPolicy,
886
977
  }),
887
978
  ],
888
979
  realm: 'cli-test-sse',
@@ -164,7 +164,7 @@ export function tempo() {
164
164
  .suggestedDeposit as string | undefined
165
165
  const cliDeposit = tempoOpts.deposit !== undefined ? String(tempoOpts.deposit) : undefined
166
166
  const resolved =
167
- suggestedDeposit ?? cliDeposit ?? (isTestnet(client!.chain!) ? '10' : undefined)
167
+ cliDeposit ?? suggestedDeposit ?? (isTestnet(client!.chain!) ? '10' : undefined)
168
168
  if (!resolved) {
169
169
  throw new Errors.IncurError({
170
170
  code: 'MISSING_DEPOSIT',
@@ -51,12 +51,12 @@ function makeChallenge(overrides?: Partial<Challenge>): Challenge {
51
51
  }
52
52
 
53
53
  describe('resolveEscrow', () => {
54
- test('prefers challenge.request.methodDetails.escrowContract', () => {
54
+ test('prefers escrowContractOverride over challenge.request.methodDetails.escrowContract', () => {
55
55
  const challenge = {
56
56
  request: { methodDetails: { escrowContract: '0xChallengeEscrow' } },
57
57
  }
58
58
  const result = resolveEscrow(challenge, 42431, '0xOverride' as Address)
59
- expect(result).toBe('0xChallengeEscrow')
59
+ expect(result).toBe('0xOverride')
60
60
  })
61
61
 
62
62
  test('falls back to escrowContractOverride', () => {
@@ -46,8 +46,8 @@ export function resolveEscrow(
46
46
  const challengeEscrow = (challenge.request.methodDetails as { escrowContract?: string })
47
47
  ?.escrowContract as Address | undefined
48
48
  const escrow =
49
- challengeEscrow ??
50
49
  escrowContractOverride ??
50
+ challengeEscrow ??
51
51
  defaults.escrowContract[chainId as keyof typeof defaults.escrowContract]
52
52
  if (!escrow)
53
53
  throw new Error(
@@ -1,6 +1,6 @@
1
- import { type Address, createClient, type Hex, http } from 'viem'
1
+ import { type Address, createClient, decodeFunctionData, erc20Abi, type Hex, http } from 'viem'
2
2
  import { privateKeyToAccount } from 'viem/accounts'
3
- import { Addresses } from 'viem/tempo'
3
+ import { Addresses, Transaction } from 'viem/tempo'
4
4
  import { beforeAll, describe, expect, test } from 'vp/test'
5
5
  import { nodeEnv } from '~test/config.js'
6
6
  import { deployEscrow, openChannel } from '~test/tempo/session.js'
@@ -11,6 +11,7 @@ const isLocalnet = nodeEnv === 'localnet'
11
11
  import * as Challenge from '../../Challenge.js'
12
12
  import * as Credential from '../../Credential.js'
13
13
  import { chainId, escrowContract as escrowContractDefaults } from '../internal/defaults.js'
14
+ import { escrowAbi } from '../session/Chain.js'
14
15
  import type { SessionCredentialPayload } from '../session/Types.js'
15
16
  import { session } from './Session.js'
16
17
 
@@ -287,11 +288,11 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
287
288
  expect(cred.source).toContain(`did:pkh:eip155:${chain.id}:`)
288
289
  })
289
290
 
290
- test('suggestedDeposit alone', async () => {
291
+ test('suggestedDeposit used when below maxDeposit', async () => {
291
292
  const method = session({
292
293
  getClient: () => client,
293
294
  account: payer,
294
- deposit: '99',
295
+ maxDeposit: '10',
295
296
  escrowContract,
296
297
  })
297
298
 
@@ -306,6 +307,61 @@ describe.runIf(isLocalnet)('session (on-chain)', () => {
306
307
  }
307
308
  })
308
309
 
310
+ test('prefers local escrowContract and deposit over server challenge overrides', async () => {
311
+ const maliciousEscrow = '0x4444444444444444444444444444444444444444' as Address
312
+ const method = session({
313
+ getClient: () => client,
314
+ account: payer,
315
+ deposit: '10',
316
+ escrowContract,
317
+ })
318
+
319
+ const result = await method.createCredential({
320
+ challenge: makeLiveChallenge({
321
+ suggestedDeposit: '7000000',
322
+ methodDetails: {
323
+ chainId: chain.id,
324
+ escrowContract: maliciousEscrow,
325
+ },
326
+ }),
327
+ context: {},
328
+ })
329
+
330
+ const cred = deserializePayload(result)
331
+ expect(cred.payload.action).toBe('open')
332
+ if (cred.payload.action !== 'open') throw new Error('unexpected action')
333
+
334
+ const transaction = Transaction.deserialize(cred.payload.transaction)
335
+ if (!('calls' in transaction)) throw new Error('unexpected transaction type')
336
+ const [approveCall, openCall] = transaction.calls as readonly [
337
+ { to?: Address; data?: Hex },
338
+ { to?: Address; data?: Hex },
339
+ ]
340
+ const approve = decodeFunctionData({
341
+ abi: erc20Abi,
342
+ data: approveCall.data ?? '0x',
343
+ })
344
+ const open = decodeFunctionData({
345
+ abi: escrowAbi,
346
+ data: openCall.data ?? '0x',
347
+ })
348
+ const approveArgs = approve.args as readonly [Address, bigint]
349
+ const openArgs = open.args as readonly [Address, Address, bigint, string, Address]
350
+
351
+ expect(approveCall.to).toBe(asset)
352
+ expect(approve.functionName).toBe('approve')
353
+ expect(approveArgs[0].toLowerCase()).toBe(escrowContract.toLowerCase())
354
+ expect(approveArgs[1]).toBe(10_000_000n)
355
+
356
+ expect(openCall.to?.toLowerCase()).toBe(escrowContract.toLowerCase())
357
+ expect(open.functionName).toBe('open')
358
+ expect(openArgs[0].toLowerCase()).toBe(payee.toLowerCase())
359
+ expect(openArgs[1].toLowerCase()).toBe(asset.toLowerCase())
360
+ expect(openArgs[2]).toBe(10_000_000n)
361
+ expect(openArgs[3]).toEqual(expect.any(String))
362
+ expect(openArgs[4].toLowerCase()).toBe(payer.address.toLowerCase())
363
+ })
364
+
309
365
  test('maxDeposit alone', async () => {
310
366
  const method = session({
311
367
  getClient: () => client,
@@ -131,11 +131,11 @@ export function session(parameters: session.Parameters = {}) {
131
131
 
132
132
  const deposit = (() => {
133
133
  if (context?.depositRaw) return BigInt(context.depositRaw)
134
+ if (parameters.deposit !== undefined) return parseUnits(parameters.deposit, decimals)
134
135
  if (suggestedDeposit !== undefined && maxDeposit !== undefined)
135
136
  return suggestedDeposit < maxDeposit ? suggestedDeposit : maxDeposit
136
- if (suggestedDeposit !== undefined) return suggestedDeposit
137
137
  if (maxDeposit !== undefined) return maxDeposit
138
- if (parameters.deposit !== undefined) return parseUnits(parameters.deposit, decimals)
138
+ if (suggestedDeposit !== undefined) return suggestedDeposit
139
139
  throw new Error(
140
140
  'No deposit amount available. Set `deposit`, `maxDeposit`, or ensure the server challenge includes `suggestedDeposit`.',
141
141
  )