@atumlabs/payment-gateway-client 2.0.0 → 2.2.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.
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2026 Atum Labs, Inc. All rights reserved.
4
+ * Proprietary and confidential.
5
+ * Access and use are governed by the LICENSE file distributed with this package.
6
+ * Do not remove, alter, or obscure this notice.
7
+ */
2
8
  "use strict";
3
9
  var __create = Object.create;
4
10
  var __defProp = Object.defineProperty;
@@ -243,12 +249,14 @@ var require_utils = __commonJS({
243
249
  "../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
244
250
  "use strict";
245
251
  Object.defineProperty(exports2, "__esModule", { value: true });
246
- exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
252
+ exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
247
253
  exports2.resolvePermitDeadline = resolvePermitDeadline;
248
254
  exports2.generatePermitNonce = generatePermitNonce;
249
255
  exports2.resolvePermitNonce = resolvePermitNonce;
256
+ exports2.canonicalDepositor = canonicalDepositor;
250
257
  exports2.deterministicPermitNonce = deterministicPermitNonce2;
251
258
  exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
259
+ exports2.deterministicRequestId = deterministicRequestId;
252
260
  exports2.sameAddress = sameAddress;
253
261
  exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
254
262
  exports2.assertToken = assertToken;
@@ -293,6 +301,20 @@ var require_utils = __commonJS({
293
301
  const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
294
302
  return bucket << 32n | slot;
295
303
  }
304
+ exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
305
+ function deterministicRequestId(params) {
306
+ if (!params.anchor) {
307
+ throw new Error("sender_auth: a per-intent anchor is required to derive a deterministic request_id (a random one would silently break idempotency and let a retry double-charge)");
308
+ }
309
+ if (!params.account) {
310
+ throw new Error("sender_auth: account is required to derive a deterministic request_id");
311
+ }
312
+ if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
313
+ throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
314
+ }
315
+ const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor(params.account)}`));
316
+ return `${params.prefix}${digest.slice(2, 26)}`;
317
+ }
296
318
  function sameAddress(a, b) {
297
319
  if (a.startsWith("0x") && b.startsWith("0x")) {
298
320
  return a.toLowerCase() === b.toLowerCase();
@@ -884,7 +906,7 @@ var require_evm = __commonJS({
884
906
  exports2.evmAdapter = {
885
907
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
886
908
  if (nonce === void 0) {
887
- throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. the challenge id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
909
+ throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
888
910
  }
889
911
  if (deadline === void 0) {
890
912
  throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
@@ -912,6 +934,10 @@ var require_evm = __commonJS({
912
934
  throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
913
935
  }
914
936
  const prehash = JSON.parse(signedMessage.message_prehash);
937
+ const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
938
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
939
+ throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
940
+ }
915
941
  const wallet = new ethers_1.Wallet(privateKey);
916
942
  return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
917
943
  },
@@ -998,7 +1024,7 @@ var require_tron = __commonJS({
998
1024
  exports2.tronAdapter = {
999
1025
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
1000
1026
  if (nonce === void 0) {
1001
- throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. the challenge id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
1027
+ throw new Error("sender_auth: buildAuthMessage requires an explicit nonce \u2014 derive it deterministically from a stable request id (e.g. an order id) so retries reproduce the same permit; pass generatePermitNonce() only for genuinely one-shot builds");
1002
1028
  }
1003
1029
  if (deadline === void 0) {
1004
1030
  throw new Error("sender_auth: buildAuthMessage requires an explicit permit deadline (absolute Unix seconds) \u2014 the low-level builder does not default it; derive it from the payment's fulfillment/settlement deadline in the calling layer (pgc/mppx)");
@@ -1021,9 +1047,19 @@ var require_tron = __commonJS({
1021
1047
  const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
1022
1048
  return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
1023
1049
  },
1050
+ // Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
1051
+ // authorizing (Turnkey policies read `eth.eip_712.message[...]`).
1024
1052
  async sign(signedMessage, privateKey) {
1053
+ if (!signedMessage.message_prehash) {
1054
+ throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
1055
+ }
1056
+ const prehash = JSON.parse(signedMessage.message_prehash);
1057
+ const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
1058
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
1059
+ throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
1060
+ }
1025
1061
  const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
1026
- return wallet.signingKey.sign(signedMessage.message).serialized;
1062
+ return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
1027
1063
  },
1028
1064
  recoverSigner(signed, source) {
1029
1065
  let recovered;
@@ -2445,7 +2481,7 @@ var Interceptors = class {
2445
2481
  }
2446
2482
  };
2447
2483
  var OpenAPI = {
2448
- BASE: "https://api.atum.network",
2484
+ BASE: "https://payment-gw.production-mainnet.atum.xyz",
2449
2485
  CREDENTIALS: "include",
2450
2486
  ENCODE_PATH: void 0,
2451
2487
  HEADERS: void 0,
@@ -2462,8 +2498,8 @@ var OpenAPI = {
2462
2498
 
2463
2499
  // src/generated/core/BaseHttpRequest.ts
2464
2500
  var BaseHttpRequest = class {
2465
- constructor(config) {
2466
- this.config = config;
2501
+ constructor(config2) {
2502
+ this.config = config2;
2467
2503
  }
2468
2504
  };
2469
2505
 
@@ -2614,15 +2650,15 @@ var getQueryString = (params) => {
2614
2650
  Object.entries(params).forEach(([key, value]) => encodePair(key, value));
2615
2651
  return qs.length ? `?${qs.join("&")}` : "";
2616
2652
  };
2617
- var getUrl = (config, options) => {
2618
- const encoder = config.ENCODE_PATH || encodeURI;
2619
- const path = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => {
2653
+ var getUrl = (config2, options) => {
2654
+ const encoder = config2.ENCODE_PATH || encodeURI;
2655
+ const path = options.url.replace("{api-version}", config2.VERSION).replace(/{(.*?)}/g, (substring, group) => {
2620
2656
  if (options.path?.hasOwnProperty(group)) {
2621
2657
  return encoder(String(options.path[group]));
2622
2658
  }
2623
2659
  return substring;
2624
2660
  });
2625
- const url = config.BASE + path;
2661
+ const url = config2.BASE + path;
2626
2662
  return options.query ? url + getQueryString(options.query) : url;
2627
2663
  };
2628
2664
  var getFormData = (options) => {
@@ -2652,12 +2688,12 @@ var resolve = async (options, resolver) => {
2652
2688
  }
2653
2689
  return resolver;
2654
2690
  };
2655
- var getHeaders = async (config, options) => {
2691
+ var getHeaders = async (config2, options) => {
2656
2692
  const [token, username, password, additionalHeaders] = await Promise.all([
2657
- resolve(options, config.TOKEN),
2658
- resolve(options, config.USERNAME),
2659
- resolve(options, config.PASSWORD),
2660
- resolve(options, config.HEADERS)
2693
+ resolve(options, config2.TOKEN),
2694
+ resolve(options, config2.USERNAME),
2695
+ resolve(options, config2.PASSWORD),
2696
+ resolve(options, config2.HEADERS)
2661
2697
  ]);
2662
2698
  const headers = Object.entries({
2663
2699
  Accept: "application/json",
@@ -2697,7 +2733,7 @@ var getRequestBody = (options) => {
2697
2733
  }
2698
2734
  return void 0;
2699
2735
  };
2700
- var sendRequest = async (config, options, url, body, formData, headers, onCancel, axiosClient) => {
2736
+ var sendRequest = async (config2, options, url, body, formData, headers, onCancel, axiosClient) => {
2701
2737
  const controller = new AbortController();
2702
2738
  let requestConfig = {
2703
2739
  data: body ?? formData,
@@ -2705,10 +2741,10 @@ var sendRequest = async (config, options, url, body, formData, headers, onCancel
2705
2741
  method: options.method,
2706
2742
  signal: controller.signal,
2707
2743
  url,
2708
- withCredentials: config.WITH_CREDENTIALS
2744
+ withCredentials: config2.WITH_CREDENTIALS
2709
2745
  };
2710
2746
  onCancel(() => controller.abort());
2711
- for (const fn of config.interceptors.request._fns) {
2747
+ for (const fn of config2.interceptors.request._fns) {
2712
2748
  requestConfig = await fn(requestConfig);
2713
2749
  }
2714
2750
  try {
@@ -2801,16 +2837,16 @@ var catchErrorCodes = (options, result) => {
2801
2837
  );
2802
2838
  }
2803
2839
  };
2804
- var request = (config, options, axiosClient = import_axios.default) => {
2840
+ var request = (config2, options, axiosClient = import_axios.default) => {
2805
2841
  return new CancelablePromise(async (resolve2, reject, onCancel) => {
2806
2842
  try {
2807
- const url = getUrl(config, options);
2843
+ const url = getUrl(config2, options);
2808
2844
  const formData = getFormData(options);
2809
2845
  const body = getRequestBody(options);
2810
- const headers = await getHeaders(config, options);
2846
+ const headers = await getHeaders(config2, options);
2811
2847
  if (!onCancel.isCancelled) {
2812
- let response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient);
2813
- for (const fn of config.interceptors.response._fns) {
2848
+ let response = await sendRequest(config2, options, url, body, formData, headers, onCancel, axiosClient);
2849
+ for (const fn of config2.interceptors.response._fns) {
2814
2850
  response = await fn(response);
2815
2851
  }
2816
2852
  const responseBody = getResponseBody(response);
@@ -2833,8 +2869,8 @@ var request = (config, options, axiosClient = import_axios.default) => {
2833
2869
 
2834
2870
  // src/generated/core/AxiosHttpRequest.ts
2835
2871
  var AxiosHttpRequest = class extends BaseHttpRequest {
2836
- constructor(config) {
2837
- super(config);
2872
+ constructor(config2) {
2873
+ super(config2);
2838
2874
  }
2839
2875
  /**
2840
2876
  * Request method
@@ -2861,8 +2897,26 @@ var PaymentsService = class {
2861
2897
  * 2. Find optimal settlement routes across chains/assets
2862
2898
  * 3. Secure funds in escrow
2863
2899
  * 4. Coordinate with settlement agents for execution
2864
- * 5. Return a real-time result if processing completes within 30 seconds,
2865
- * otherwise return a payment ID for status polling
2900
+ * 5. Return a real-time result if settlement completes inside the synchronous window,
2901
+ * otherwise return a payment ID and status for polling
2902
+ *
2903
+ * **Idempotency.** `request_id` is the idempotency key, scoped to the source account
2904
+ * and originator. Submitting the same `request_id` again returns the ORIGINAL payment
2905
+ * instead of charging a second time, so a client whose request timed out or failed
2906
+ * in transit can safely re-submit.
2907
+ *
2908
+ * Because a re-submission must be admissible on its own, some fields legitimately
2909
+ * differ between attempts and are excluded from the comparison: `quote_deadline` and
2910
+ * `fulfillment_deadline` (both must be in the future, so a later attempt has to move
2911
+ * them), and the `sender_auth` signature they are signed into.
2912
+ *
2913
+ * What must NOT differ is the payment's economics — source account and asset,
2914
+ * destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
2915
+ * payer's own risk cap; lowering it on a retry describes a different payment, not the
2916
+ * same one with less headroom). Re-using one `request_id` for a genuinely different
2917
+ * payment is a client bug that would otherwise be silently collapsed onto the first
2918
+ * payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
2919
+ * distinct `request_id` per payment.
2866
2920
  *
2867
2921
  * @param data The data for the request.
2868
2922
  * @param data.requestBody
@@ -2879,6 +2933,11 @@ var PaymentsService = class {
2879
2933
  400: "Invalid request",
2880
2934
  401: "Signature or sender authorization could not be verified",
2881
2935
  403: "Payment declined during counterparty authorization",
2936
+ 409: `The \`request_id\` has already been used for a payment with different economics
2937
+ (\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
2938
+ made safe, so it must identify one payment; use a distinct \`request_id\` per
2939
+ payment.
2940
+ `,
2882
2941
  413: "Payment request too large to process",
2883
2942
  500: "Internal server error",
2884
2943
  503: "A dependency is temporarily unavailable"
@@ -3166,20 +3225,20 @@ var HealthService = class {
3166
3225
 
3167
3226
  // src/generated/PaymentGatewayClient.ts
3168
3227
  var PaymentGatewayClient = class {
3169
- constructor(config, HttpRequest = AxiosHttpRequest) {
3228
+ constructor(config2, HttpRequest = AxiosHttpRequest) {
3170
3229
  this.request = new HttpRequest({
3171
- BASE: config?.BASE ?? "https://api.atum.network",
3172
- VERSION: config?.VERSION ?? "1.0.0",
3173
- WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
3174
- CREDENTIALS: config?.CREDENTIALS ?? "include",
3175
- TOKEN: config?.TOKEN,
3176
- USERNAME: config?.USERNAME,
3177
- PASSWORD: config?.PASSWORD,
3178
- HEADERS: config?.HEADERS,
3179
- ENCODE_PATH: config?.ENCODE_PATH,
3230
+ BASE: config2?.BASE ?? "https://payment-gw.production-mainnet.atum.xyz",
3231
+ VERSION: config2?.VERSION ?? "1.0.0",
3232
+ WITH_CREDENTIALS: config2?.WITH_CREDENTIALS ?? false,
3233
+ CREDENTIALS: config2?.CREDENTIALS ?? "include",
3234
+ TOKEN: config2?.TOKEN,
3235
+ USERNAME: config2?.USERNAME,
3236
+ PASSWORD: config2?.PASSWORD,
3237
+ HEADERS: config2?.HEADERS,
3238
+ ENCODE_PATH: config2?.ENCODE_PATH,
3180
3239
  interceptors: {
3181
- request: config?.interceptors?.request ?? new Interceptors(),
3182
- response: config?.interceptors?.response ?? new Interceptors()
3240
+ request: config2?.interceptors?.request ?? new Interceptors(),
3241
+ response: config2?.interceptors?.response ?? new Interceptors()
3183
3242
  }
3184
3243
  });
3185
3244
  this.authorizations = new AuthorizationsService(this.request);
@@ -3389,7 +3448,8 @@ var import_payment_request_sender_auth = __toESM(require_dist4());
3389
3448
 
3390
3449
  // src/index.ts
3391
3450
  var PAYMENT_REQUEST_VERSION = "1.0";
3392
- var defaultGateway = "https://payment-gateway-testnet.atumlabs.xyz";
3451
+ var defaultGateway = "https://payment-gw.production-testnet.atum.xyz";
3452
+ var OpenAPI2 = { ...OpenAPI, BASE: defaultGateway };
3393
3453
  var DEFAULT_QUOTE_DEADLINE_SECONDS = 5;
3394
3454
  var DEFAULT_FULFILLMENT_DEADLINE_SECONDS = 3600;
3395
3455
  var DEFAULT_PAYMENT_QUOTE_DEADLINE_SECONDS = 10;
@@ -3477,9 +3537,11 @@ async function assemblePaymentRequest(params) {
3477
3537
  };
3478
3538
  }
3479
3539
  var PaymentGatewayClient2 = class extends PaymentGatewayClient {
3480
- constructor(config, HttpRequest = AxiosHttpRequest) {
3481
- super(config, HttpRequest);
3482
- this.baseUrl = config?.BASE ?? defaultGateway;
3540
+ constructor(config2, HttpRequest = AxiosHttpRequest) {
3541
+ const envBase = (typeof process !== "undefined" ? process.env.GATEWAY_URL : void 0)?.trim();
3542
+ const base = config2?.BASE ?? (envBase || defaultGateway);
3543
+ super({ ...config2, BASE: base }, HttpRequest);
3544
+ this.baseUrl = base;
3483
3545
  }
3484
3546
  /**
3485
3547
  * Fetches chain defaults from the payment gateway.
@@ -3694,9 +3756,19 @@ var PaymentGatewayClient2 = class extends PaymentGatewayClient {
3694
3756
  }
3695
3757
  };
3696
3758
 
3759
+ // src/version.ts
3760
+ var PACKAGE_VERSION = true ? "2.2.0" : "0.0.0-dev";
3761
+
3762
+ // src/cli/load-env.ts
3763
+ var import_dotenv = require("dotenv");
3764
+ function loadCliEnv() {
3765
+ (0, import_dotenv.config)({ override: false, quiet: true });
3766
+ }
3767
+
3697
3768
  // src/cli/quote-status.ts
3769
+ loadCliEnv();
3698
3770
  var program = new import_commander.Command();
3699
- program.name("quote-status").description("Fetch the status of a quote-request batch (no signing required)").version("1.0.0").argument("<quoteRequestId>", "The quote_request_id (qr_...) returned by request-quote").option("--gateway <url>", "Payment gateway URL", defaultGateway).addHelpText("after", `
3771
+ program.name("quote-status").description("Fetch the status of a quote-request batch (no signing required)").version(PACKAGE_VERSION).argument("<quoteRequestId>", "The quote_request_id (qr_...) returned by request-quote").option("--gateway <url>", "Payment gateway URL (overrides GATEWAY_URL env; default: production-testnet)").addHelpText("after", `
3700
3772
  Example:
3701
3773
  quote-status --gateway http://localhost:8080 qr_019f27b9-420d-7353-8263-0cf310670a77
3702
3774