@parallel-protocol/cli 0.2.10 → 0.3.0

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 (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +193 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -339,12 +339,17 @@ parallel pay prepare \
339
339
 
340
340
  # Force which token the agent pays with
341
341
  parallel pay prepare --amount <n> --recipient <address> --chain <chain> --pay-with <usdp|usdc|susdp>
342
+
343
+ # Metered payment (scheme upto): sign --amount as a cap, the merchant charges its actual cost
344
+ parallel pay prepare --amount <cap> --recipient <address> --chain <chain> --scheme upto --valid-for 600 --wallet
342
345
  ```
343
346
 
344
347
  **`--token`** controls what the **merchant receives** (default: `usdp`). Accepted values: `usdp` · `usdc` · `susdp` · any backing collateral symbol (`susds`, `sfrxusd`, …) · a token contract address.
345
348
 
346
349
  **`--pay-with`** overrides which token the **agent pays with** (default: auto-detected from wallet balance). Accepted values: `usdp` · `usdc` · `susdp`.
347
350
 
351
+ **`--scheme`** selects the payment scheme (default: `exact`). With `upto`, `--amount` is a **cap**: the authorization is signed for the cap towards the `UptoEscrow` contract, the merchant settles the amount it actually charges and the escrow refunds the rest to the agent in the same transaction (Route U). Metered payments are USDp only — `--token` and `--pay-with` other than `usdp` are refused. **`--valid-for <seconds>`** (upto only, 1–3600, default 300) sets how long the authorization stays valid; pass the `maxTimeoutSeconds` the merchant announced in its 402. Available on chains where the escrow is deployed (see `parallel pay capabilities`).
352
+
348
353
  **Auto-route selection** (when `--wallet` is provided):
349
354
 
350
355
  | `--token` (merchant receives) | Agent wallet | Route | Method |
@@ -359,6 +364,7 @@ parallel pay prepare --amount <n> --recipient <address> --chain <chain> --pay-wi
359
364
  | `susdp` | sUSDp | J | `transferWithAuthorization` |
360
365
  | collateral (e.g. `susds`) | USDp | D | `swapExactOutputWithAuthorization` |
361
366
  | collateral (e.g. `susds`) | sUSDp | K | `redeemWithAuthorization` → collateral |
367
+ | `usdp` with `--scheme upto` | USDp ≥ cap | U | `uptoWithAuthorization` (cap signed, actual charged, rest refunded) |
362
368
 
363
369
  Without `--wallet`, the payload is unsigned and `tokenIn` defaults to `usdp`.
364
370
 
package/dist/index.js CHANGED
@@ -1635,7 +1635,7 @@ function suppressLogs() {
1635
1635
 
1636
1636
  // package.json
1637
1637
  var package_default = {
1638
- version: "0.2.10"};
1638
+ version: "0.3.0"};
1639
1639
  function findEnvFile(dir) {
1640
1640
  const candidate = join(dir, ".env");
1641
1641
  if (existsSync(candidate)) return candidate;
@@ -1730,7 +1730,8 @@ var CHAINS_STATIC = {
1730
1730
  susdp: "0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7",
1731
1731
  bridgeableUsdp: "0x4Dde0e308CFB60515218C6ad2DF1134Fc48531FC",
1732
1732
  flashParallel: "0x08417cdb7F52a5021bB4eb6E0deAf3f295c3f182",
1733
- harvester: "0xCa43eCFCDFBA1fED003649e946Ae6091646B410a"
1733
+ harvester: "0xCa43eCFCDFBA1fED003649e946Ae6091646B410a",
1734
+ uptoEscrow: "0xcCA03ed95Ab203a8566a8abfFDF03AEE4030B974"
1734
1735
  }
1735
1736
  },
1736
1737
  arbitrum: {
@@ -1994,6 +1995,9 @@ var PARALLELIZER_CHAINS = CHAIN_NAMES.filter(
1994
1995
  var SAVINGS_CHAINS = CHAIN_NAMES.filter(
1995
1996
  (c) => !!CHAINS_STATIC[c].contracts.susdp
1996
1997
  );
1998
+ CHAIN_NAMES.filter(
1999
+ (c) => !!CHAINS_STATIC[c].contracts.uptoEscrow
2000
+ );
1997
2001
  function isParallelizerChain(chainName) {
1998
2002
  return !!CHAINS_STATIC[chainName]?.contracts.parallelizer;
1999
2003
  }
@@ -2006,6 +2010,9 @@ function getParallelizerAddress(chainName) {
2006
2010
  function getSUSDpAddress(chainName) {
2007
2011
  return CHAINS_STATIC[chainName]?.contracts.susdp;
2008
2012
  }
2013
+ function getUptoEscrowAddress(chainName) {
2014
+ return CHAINS_STATIC[chainName]?.contracts.uptoEscrow;
2015
+ }
2009
2016
  var USDC_CHAIN_ADDRESSES = {
2010
2017
  ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2011
2018
  base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -5239,6 +5246,35 @@ var SWAP_EXACT_INPUT_TYPEHASH = keccak256(
5239
5246
  "SwapExactInputWithAuthorization(address from,address tokenIn,address tokenOut,uint256 amountIn,uint256 amountOutMin,address to,uint256 deadline,bytes32 userSalt)"
5240
5247
  )
5241
5248
  );
5249
+ var UPTO_WITH_AUTHORIZATION_TYPEHASH = keccak256(
5250
+ toHex(
5251
+ "UptoWithAuthorization(address from,address token,uint256 maxAmount,address payTo,uint256 validBefore,bytes32 userSalt)"
5252
+ )
5253
+ );
5254
+ function deriveUptoNonce(intent) {
5255
+ return keccak256(
5256
+ encodeAbiParameters(
5257
+ [
5258
+ { type: "bytes32" },
5259
+ { type: "address" },
5260
+ { type: "address" },
5261
+ { type: "uint256" },
5262
+ { type: "address" },
5263
+ { type: "uint256" },
5264
+ { type: "bytes32" }
5265
+ ],
5266
+ [
5267
+ UPTO_WITH_AUTHORIZATION_TYPEHASH,
5268
+ intent.from,
5269
+ intent.token,
5270
+ intent.maxAmount,
5271
+ intent.payTo,
5272
+ intent.validBefore,
5273
+ intent.userSalt
5274
+ ]
5275
+ )
5276
+ );
5277
+ }
5242
5278
  var REDEEM_WITH_AUTHORIZATION_TYPES = {
5243
5279
  RedeemWithAuthorization: [
5244
5280
  { name: "vault", type: "address" },
@@ -5297,10 +5333,15 @@ var hexBytes = (bytes) => z.string().refine(
5297
5333
  var Bytes32Schema = hexBytes(32);
5298
5334
  var SignatureSchema = hexBytes(65);
5299
5335
  z.object({
5336
+ // Absent means "exact" — the historical wire predates this field.
5337
+ scheme: z.enum(["exact", "upto"]).optional(),
5300
5338
  maxAmountRequired: z.string(),
5301
5339
  asset: AddressSchema,
5302
5340
  payTo: AddressSchema,
5303
- network: z.string()
5341
+ network: z.string(),
5342
+ // Settlement window (seconds) the merchant grants; drives the payer's
5343
+ // `validBefore`. Absent → the payer's default window applies.
5344
+ maxTimeoutSeconds: z.number().int().positive().optional()
5304
5345
  });
5305
5346
  var AuthorizationSchema = z.object({
5306
5347
  from: AddressSchema,
@@ -5324,6 +5365,9 @@ var DepositAuthorizationSchema = z.object({
5324
5365
  validBefore: z.string(),
5325
5366
  nonce: Bytes32Schema
5326
5367
  });
5368
+ var UptoParamsSchema = z.object({
5369
+ payTo: AddressSchema
5370
+ });
5327
5371
  var SwapParamsSchema = z.object({
5328
5372
  tokenOut: AddressSchema,
5329
5373
  // swapExactOutput (Routes B/D/I/K): exact collateral out + max USDp in
@@ -5335,7 +5379,7 @@ var SwapParamsSchema = z.object({
5335
5379
  recipient: AddressSchema
5336
5380
  });
5337
5381
  z.object({
5338
- scheme: z.literal("exact"),
5382
+ scheme: z.enum(["exact", "upto"]),
5339
5383
  network: z.string(),
5340
5384
  method: z.enum([
5341
5385
  "transferWithAuthorization",
@@ -5344,7 +5388,11 @@ z.object({
5344
5388
  "depositWithAuthorization",
5345
5389
  "redeemWithAuthorization",
5346
5390
  // Route L: agent holds USDp + sUSDp, neither sufficient alone.
5347
- "partialRedeemWithAuthorization"
5391
+ "partialRedeemWithAuthorization",
5392
+ // Scheme upto: ReceiveWithAuthorization(MAX) signed to the UptoEscrow;
5393
+ // the facilitator settles the merchant-decided actual ≤ MAX and the
5394
+ // escrow refunds the remainder atomically.
5395
+ "uptoWithAuthorization"
5348
5396
  ]),
5349
5397
  // Disambiguates routes that share a method but operate on different tokens
5350
5398
  // (e.g. transferWithAuthorization: USDp → A, USDC → F, sUSDp → J). Optional —
@@ -5363,7 +5411,8 @@ z.object({
5363
5411
  // Route G: DepositWithAuthorization signature on the sUSDp domain.
5364
5412
  depositSignature: SignatureSchema.optional()
5365
5413
  }),
5366
- swapParams: SwapParamsSchema.optional()
5414
+ swapParams: SwapParamsSchema.optional(),
5415
+ uptoParams: UptoParamsSchema.optional()
5367
5416
  });
5368
5417
  var AMOUNT_RE = /^\d{1,30}(\.\d{1,18})?$/;
5369
5418
  var isPlainPositiveDecimal = (v) => AMOUNT_RE.test(v) && parseFloat(v) > 0;
@@ -8578,9 +8627,9 @@ var PREVIEW_MINT_ABI = [
8578
8627
  outputs: [{ type: "uint256" }]
8579
8628
  }
8580
8629
  ];
8581
- function buildTiming() {
8630
+ function buildTiming(validitySeconds = 300n) {
8582
8631
  const now = BigInt(Math.floor(Date.now() / 1e3));
8583
- return { validAfter: now - 60n, validBefore: now + 300n };
8632
+ return { validAfter: now - 60n, validBefore: now + validitySeconds };
8584
8633
  }
8585
8634
  function slippageUp(amount) {
8586
8635
  return amount + amount * SLIPPAGE_BPS / 10000n;
@@ -8788,6 +8837,77 @@ async function prepareRouteA(chain, senderAddress, amount, recipient) {
8788
8837
  gasEstimate: 65e3
8789
8838
  };
8790
8839
  }
8840
+ var UPTO_MAX_VALIDITY_SECONDS = 3600;
8841
+ async function prepareRouteUpto(chain, senderAddress, maxAmount, recipient, validitySeconds = 300) {
8842
+ const usdpAddress = getUSDpAddress(chain);
8843
+ if (!usdpAddress) throw new Error(`USDp not deployed on ${chain}`);
8844
+ const escrow = getUptoEscrowAddress(chain);
8845
+ if (!escrow) {
8846
+ throw new Error(
8847
+ `[PAYMENT_METHOD_NOT_SUPPORTED] Metered payments (upto) are not available on ${chain}`
8848
+ );
8849
+ }
8850
+ const maxAmountWei = parseUnits(maxAmount, 18);
8851
+ const window2 = BigInt(
8852
+ Math.min(
8853
+ Math.max(1, Math.trunc(validitySeconds)),
8854
+ UPTO_MAX_VALIDITY_SECONDS
8855
+ )
8856
+ );
8857
+ const { validAfter, validBefore } = buildTiming(window2);
8858
+ const userSalt = generateNonce();
8859
+ const derived = deriveUptoNonce({
8860
+ from: senderAddress,
8861
+ token: usdpAddress,
8862
+ maxAmount: maxAmountWei,
8863
+ payTo: recipient,
8864
+ validBefore,
8865
+ userSalt
8866
+ });
8867
+ const authMsg = {
8868
+ from: senderAddress,
8869
+ to: escrow,
8870
+ value: maxAmountWei,
8871
+ validAfter,
8872
+ validBefore,
8873
+ nonce: derived
8874
+ };
8875
+ const domain = await readTokenDomain(chain, usdpAddress);
8876
+ const typedData = buildReceiveAuthorizationTypedData(domain, authMsg);
8877
+ const messageHash = hashTypedData({
8878
+ ...typedData,
8879
+ message: typedData.message
8880
+ });
8881
+ return {
8882
+ route: "U",
8883
+ steps: [
8884
+ {
8885
+ label: "signature",
8886
+ description: "ReceiveWithAuthorization on USDp domain to the UptoEscrow (derived nonce binds payTo and cap)",
8887
+ messageHash,
8888
+ typedData
8889
+ }
8890
+ ],
8891
+ paymentPayload: {
8892
+ scheme: "upto",
8893
+ network: chain,
8894
+ method: "uptoWithAuthorization",
8895
+ payload: {
8896
+ authorization: {
8897
+ from: senderAddress,
8898
+ to: escrow,
8899
+ value: maxAmountWei.toString(),
8900
+ validAfter: validAfter.toString(),
8901
+ validBefore: validBefore.toString(),
8902
+ nonce: userSalt
8903
+ }
8904
+ },
8905
+ uptoParams: { payTo: recipient }
8906
+ },
8907
+ typedData,
8908
+ gasEstimate: 13e4
8909
+ };
8910
+ }
8791
8911
  async function prepareRouteB(chain, senderAddress, amount, recipient, targetToken) {
8792
8912
  if (!isParallelizerChain(chain)) {
8793
8913
  throw new Error(
@@ -9714,6 +9834,22 @@ async function preparePayment(params) {
9714
9834
  const sender = params.senderAddress;
9715
9835
  const recv = params.recipient;
9716
9836
  const tokenIn = params.tokenIn ?? "usdp";
9837
+ if (params.scheme === "upto") {
9838
+ const usdp = getUSDpAddress(chain)?.toLowerCase();
9839
+ const wantsOtherToken = params.targetToken !== void 0 && params.targetToken.toLowerCase() !== usdp;
9840
+ if (tokenIn !== "usdp" || wantsOtherToken) {
9841
+ throw new Error(
9842
+ "[PAYMENT_METHOD_NOT_SUPPORTED] Metered payments (upto) are USDp \u2192 USDp only"
9843
+ );
9844
+ }
9845
+ return prepareRouteUpto(
9846
+ chain,
9847
+ sender,
9848
+ amount,
9849
+ recv,
9850
+ params.validitySeconds
9851
+ );
9852
+ }
9717
9853
  const usdpAddress = getUSDpAddress(chain);
9718
9854
  const target = params.targetToken ?? usdpAddress;
9719
9855
  const wantsUSDp = !target || !!usdpAddress && target.toLowerCase() === usdpAddress.toLowerCase();
@@ -10007,6 +10143,13 @@ function prepareCmd() {
10007
10143
  ).option(
10008
10144
  "--pay-with <usdp|usdc|susdp>",
10009
10145
  "force the input token (overrides balance-based auto-selection)"
10146
+ ).option(
10147
+ "--scheme <exact|upto>",
10148
+ "payment scheme: 'upto' signs --amount as a cap settled through the UptoEscrow (USDp only)",
10149
+ "exact"
10150
+ ).option(
10151
+ "--valid-for <seconds>",
10152
+ "upto only: settlement window announced by the merchant (max 3600)"
10010
10153
  ).option(
10011
10154
  "-w, --wallet [path|env]",
10012
10155
  "keystore path or 'env' (PARALLEL_PRIVATE_KEY) \u2014 signs and encodes the payload"
@@ -10029,6 +10172,26 @@ function prepareCmd() {
10029
10172
  `Invalid --pay-with value "${opts.payWith}". Must be one of: usdp, usdc, susdp`
10030
10173
  );
10031
10174
  }
10175
+ if (opts.scheme !== "exact" && opts.scheme !== "upto") {
10176
+ throw new Error(
10177
+ `Invalid --scheme value "${opts.scheme}". Must be one of: exact, upto`
10178
+ );
10179
+ }
10180
+ const isUpto = opts.scheme === "upto";
10181
+ if (isUpto && opts.token.toLowerCase() !== "usdp") {
10182
+ throw new Error(
10183
+ "--scheme upto pays USDp merchants only: use --token usdp"
10184
+ );
10185
+ }
10186
+ if (isUpto && opts.payWith && opts.payWith !== "usdp") {
10187
+ throw new Error(
10188
+ "--scheme upto is paid in USDp: use --pay-with usdp or omit it"
10189
+ );
10190
+ }
10191
+ const validitySeconds = opts.validFor !== void 0 ? assertValidInteger(opts.validFor, "--valid-for", 1, 3600) : void 0;
10192
+ if (!isUpto && validitySeconds !== void 0) {
10193
+ throw new Error("--valid-for only applies to --scheme upto");
10194
+ }
10032
10195
  const account = await loadWallet(walletFlag);
10033
10196
  const senderAddress = account?.address;
10034
10197
  if (!senderAddress && walletFlag) {
@@ -10064,6 +10227,7 @@ function prepareCmd() {
10064
10227
  tokenIn = hasRouteB ? "usdp" : "usdc";
10065
10228
  }
10066
10229
  }
10230
+ if (isUpto) tokenIn = "usdp";
10067
10231
  if (senderAddress) {
10068
10232
  if (tokenIn === "usdp") {
10069
10233
  await assertSufficientBalance(
@@ -10097,7 +10261,9 @@ function prepareCmd() {
10097
10261
  amount: opts.amount,
10098
10262
  recipient: opts.recipient,
10099
10263
  tokenIn,
10100
- targetToken
10264
+ targetToken,
10265
+ scheme: opts.scheme,
10266
+ validitySeconds
10101
10267
  });
10102
10268
  if (account) {
10103
10269
  const { signedPayload, encoded } = await signPayload(
@@ -10117,8 +10283,12 @@ function prepareCmd() {
10117
10283
  [
10118
10284
  ["Route", result.route],
10119
10285
  ["Method", result.paymentPayload.method],
10286
+ ["Scheme", opts.scheme],
10120
10287
  ["Chain", chain],
10121
- ["Amount", `${opts.amount} ${tokenLabel2(opts.token)}`],
10288
+ [
10289
+ isUpto ? "Max Amount" : "Amount",
10290
+ `${opts.amount} ${tokenLabel2(opts.token)}`
10291
+ ],
10122
10292
  ["Recipient", opts.recipient],
10123
10293
  ["Signatures", result.steps.map((s) => s.label).join(", ")],
10124
10294
  ["Gas Estimate", result.gasEstimate.toLocaleString()]
@@ -12452,9 +12622,9 @@ var envSchema = z.object({
12452
12622
  MCP_MAX_SESSIONS: z.coerce.number().default(500),
12453
12623
  MCP_SESSION_TTL_MS: z.coerce.number().default(10 * 60 * 1e3),
12454
12624
  ALCHEMY_API_KEY: z.string().optional(),
12455
- STAKE_API_URL: z.string().min(1).default("https://parallel-api-v2.vercel.app"),
12625
+ STAKE_API_URL: z.string().min(1).default("https://api.parallel.best"),
12456
12626
  STAKE_API_KEY: z.string().optional(),
12457
- SUPPLY_API_URL: z.string().min(1).default("https://parallel-api-v2.vercel.app"),
12627
+ SUPPLY_API_URL: z.string().min(1).default("https://api.parallel.best"),
12458
12628
  REDIS_URL: z.string().optional()
12459
12629
  });
12460
12630
  var env2 = envSchema.parse(process.env);
@@ -12465,9 +12635,12 @@ async function fetchAllRewards(address) {
12465
12635
  const baseUrl = env2.STAKE_API_URL;
12466
12636
  if (!baseUrl) return null;
12467
12637
  try {
12468
- const res = await fetch(`${baseUrl}/api/stake/rewards/all/${address}`, {
12469
- headers: env2.STAKE_API_KEY ? { "x-api-key": env2.STAKE_API_KEY } : {}
12470
- });
12638
+ const res = await fetchWithTimeout(
12639
+ `${baseUrl}/api/stake/rewards/all/${address}`,
12640
+ {
12641
+ headers: env2.STAKE_API_KEY ? { "x-api-key": env2.STAKE_API_KEY } : {}
12642
+ }
12643
+ );
12471
12644
  if (!res.ok) return null;
12472
12645
  const raw = await res.json();
12473
12646
  return (Array.isArray(raw) ? raw : raw.json) ?? null;
@@ -12653,7 +12826,7 @@ async function fetchEpochMeta() {
12653
12826
  const baseUrl = env2.STAKE_API_URL;
12654
12827
  if (!baseUrl) return null;
12655
12828
  try {
12656
- const res = await fetch(`${baseUrl}/api/stake/epoch/last`, {
12829
+ const res = await fetchWithTimeout(`${baseUrl}/api/stake/epoch/last`, {
12657
12830
  headers: env2.STAKE_API_KEY ? { "x-api-key": env2.STAKE_API_KEY } : {}
12658
12831
  });
12659
12832
  if (!res.ok) return null;
@@ -12714,7 +12887,9 @@ async function fetchPrlCirculatingSupply() {
12714
12887
  const supplyApiUrl = env2.SUPPLY_API_URL;
12715
12888
  if (!supplyApiUrl) return null;
12716
12889
  try {
12717
- const res = await fetch(`${supplyApiUrl}/public/supply/prl/circulating`);
12890
+ const res = await fetchWithTimeout(
12891
+ `${supplyApiUrl}/public/supply/prl/circulating`
12892
+ );
12718
12893
  if (!res.ok) return null;
12719
12894
  return (await res.text()).trim();
12720
12895
  } catch {
@@ -15342,7 +15517,7 @@ function swapCommand() {
15342
15517
 
15343
15518
  // ../mcp-server/package.json
15344
15519
  var package_default2 = {
15345
- version: "1.2.0"};
15520
+ version: "1.3.0"};
15346
15521
 
15347
15522
  // src/commands/version.ts
15348
15523
  function versionCommand() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parallel-protocol/cli",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [