@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.
package/dist/index.js CHANGED
@@ -1,3 +1,9 @@
1
+ /*
2
+ * Copyright (c) 2026 Atum Labs, Inc. All rights reserved.
3
+ * Proprietary and confidential.
4
+ * Access and use are governed by the LICENSE file distributed with this package.
5
+ * Do not remove, alter, or obscure this notice.
6
+ */
1
7
  "use strict";
2
8
  var __create = Object.create;
3
9
  var __defProp = Object.defineProperty;
@@ -250,12 +256,14 @@ var require_utils = __commonJS({
250
256
  "../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
251
257
  "use strict";
252
258
  Object.defineProperty(exports2, "__esModule", { value: true });
253
- exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
259
+ exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
254
260
  exports2.resolvePermitDeadline = resolvePermitDeadline;
255
261
  exports2.generatePermitNonce = generatePermitNonce;
256
262
  exports2.resolvePermitNonce = resolvePermitNonce;
263
+ exports2.canonicalDepositor = canonicalDepositor;
257
264
  exports2.deterministicPermitNonce = deterministicPermitNonce2;
258
265
  exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
266
+ exports2.deterministicRequestId = deterministicRequestId;
259
267
  exports2.sameAddress = sameAddress;
260
268
  exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
261
269
  exports2.assertToken = assertToken;
@@ -300,6 +308,20 @@ var require_utils = __commonJS({
300
308
  const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
301
309
  return bucket << 32n | slot;
302
310
  }
311
+ exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
312
+ function deterministicRequestId(params) {
313
+ if (!params.anchor) {
314
+ 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)");
315
+ }
316
+ if (!params.account) {
317
+ throw new Error("sender_auth: account is required to derive a deterministic request_id");
318
+ }
319
+ if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
320
+ throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
321
+ }
322
+ const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor(params.account)}`));
323
+ return `${params.prefix}${digest.slice(2, 26)}`;
324
+ }
303
325
  function sameAddress(a, b) {
304
326
  if (a.startsWith("0x") && b.startsWith("0x")) {
305
327
  return a.toLowerCase() === b.toLowerCase();
@@ -891,7 +913,7 @@ var require_evm = __commonJS({
891
913
  exports2.evmAdapter = {
892
914
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
893
915
  if (nonce === void 0) {
894
- 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");
916
+ 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");
895
917
  }
896
918
  if (deadline === void 0) {
897
919
  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)");
@@ -919,6 +941,10 @@ var require_evm = __commonJS({
919
941
  throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
920
942
  }
921
943
  const prehash = JSON.parse(signedMessage.message_prehash);
944
+ const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
945
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
946
+ throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
947
+ }
922
948
  const wallet = new ethers_1.Wallet(privateKey);
923
949
  return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
924
950
  },
@@ -1005,7 +1031,7 @@ var require_tron = __commonJS({
1005
1031
  exports2.tronAdapter = {
1006
1032
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
1007
1033
  if (nonce === void 0) {
1008
- 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");
1034
+ 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");
1009
1035
  }
1010
1036
  if (deadline === void 0) {
1011
1037
  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)");
@@ -1028,9 +1054,19 @@ var require_tron = __commonJS({
1028
1054
  const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
1029
1055
  return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
1030
1056
  },
1057
+ // Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
1058
+ // authorizing (Turnkey policies read `eth.eip_712.message[...]`).
1031
1059
  async sign(signedMessage, privateKey) {
1060
+ if (!signedMessage.message_prehash) {
1061
+ throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
1062
+ }
1063
+ const prehash = JSON.parse(signedMessage.message_prehash);
1064
+ const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
1065
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
1066
+ throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
1067
+ }
1032
1068
  const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
1033
- return wallet.signingKey.sign(signedMessage.message).serialized;
1069
+ return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
1034
1070
  },
1035
1071
  recoverSigner(signed, source) {
1036
1072
  let recovered;
@@ -2687,6 +2723,7 @@ var require_client = __commonJS({
2687
2723
  retryCounter;
2688
2724
  backoffHistogram;
2689
2725
  activityPendingCounter;
2726
+ signDurationHistogram;
2690
2727
  // The private API scalar is held only long enough to construct the stamper. After
2691
2728
  // `getStamper()` runs, this is nulled out so the hex string doesn't survive on the heap
2692
2729
  // for the client's lifetime. Web Crypto holds the imported key as an opaque CryptoKey.
@@ -2715,6 +2752,10 @@ var require_client = __commonJS({
2715
2752
  this.activityPendingCounter = meter.createCounter("turnkey.activity_pending_total", {
2716
2753
  description: "Activities that entered async polling (did not complete synchronously in the submit response), labeled by activity type"
2717
2754
  });
2755
+ this.signDurationHistogram = meter.createHistogram("turnkey.sign_duration_ms", {
2756
+ description: "Full Turnkey activity round-trip latency (concurrency-semaphore wait + submit + poll), labeled by activity type and outcome (success|error)",
2757
+ unit: "ms"
2758
+ });
2718
2759
  }
2719
2760
  emitRetry(reason, path) {
2720
2761
  try {
@@ -2734,6 +2775,12 @@ var require_client = __commonJS({
2734
2775
  } catch {
2735
2776
  }
2736
2777
  }
2778
+ emitSignDuration(durationMs, activityType, outcome) {
2779
+ try {
2780
+ this.signDurationHistogram.record(durationMs, { activity_type: activityType, outcome });
2781
+ } catch {
2782
+ }
2783
+ }
2737
2784
  // ── public surface ─────────────────────────────────────────────────────────
2738
2785
  /** Boot-time reachability + auth probe. Throws TURNKEY_AUTH on bad credentials. */
2739
2786
  async whoami() {
@@ -2783,39 +2830,46 @@ var require_client = __commonJS({
2783
2830
  }
2784
2831
  // ── activity submission ────────────────────────────────────────────────────
2785
2832
  async submitActivity(activityType, endpoint, parameters) {
2786
- const body = {
2787
- type: activityType,
2788
- timestampMs: String(Date.now()),
2789
- organizationId: this.opts.organizationId,
2790
- parameters
2791
- };
2792
- const signWith = typeof parameters.signWith === "string" ? parameters.signWith : void 0;
2793
- const ctx = `activity=${activityType}${signWith ? ` signWith=${signWith}` : ""}`;
2794
- const resp = await this.queryPost(`/public/v1/submit/${endpoint}`, body);
2795
- let activity = resp.activity;
2796
- const deadline = Date.now() + POLL_TIMEOUT_MS;
2797
- let pollAttempt = 0;
2798
- if (activity?.status === "ACTIVITY_STATUS_PENDING") {
2799
- this.emitActivityPending(activityType);
2800
- }
2801
- while (activity?.status === "ACTIVITY_STATUS_PENDING") {
2802
- if (Date.now() > deadline) {
2803
- throw (0, km_core_1.signerRetryable)(km_core_1.SignerErrorCode.TURNKEY_SIGNER_UNAVAILABLE, `Turnkey ${ctx} activityId=${activity.id} stayed PENDING past ${POLL_TIMEOUT_MS}ms`);
2833
+ const start = Date.now();
2834
+ let outcome = "error";
2835
+ try {
2836
+ const body = {
2837
+ type: activityType,
2838
+ timestampMs: String(Date.now()),
2839
+ organizationId: this.opts.organizationId,
2840
+ parameters
2841
+ };
2842
+ const signWith = typeof parameters.signWith === "string" ? parameters.signWith : void 0;
2843
+ const ctx = `activity=${activityType}${signWith ? ` signWith=${signWith}` : ""}`;
2844
+ const resp = await this.queryPost(`/public/v1/submit/${endpoint}`, body);
2845
+ let activity = resp.activity;
2846
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
2847
+ let pollAttempt = 0;
2848
+ if (activity?.status === "ACTIVITY_STATUS_PENDING") {
2849
+ this.emitActivityPending(activityType);
2804
2850
  }
2805
- const sleepIdx = Math.min(pollAttempt, POLL_BACKOFF_MS.length - 1);
2806
- await (0, promises_1.setTimeout)(POLL_BACKOFF_MS[sleepIdx]);
2807
- pollAttempt++;
2808
- const polled = await this.queryPost("/public/v1/query/get_activity", { organizationId: this.opts.organizationId, activityId: activity.id });
2809
- activity = polled.activity;
2810
- }
2811
- if (activity?.status !== "ACTIVITY_STATUS_COMPLETED") {
2812
- const failure = activity?.failure ?? {};
2813
- throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_ACTIVITY_FAILED, `Turnkey ${ctx} activityId=${activity?.id ?? "?"} status=${activity?.status ?? "unknown"}; ${JSON.stringify(failure)}`);
2814
- }
2815
- if (activity.result == null) {
2816
- throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `Turnkey ${ctx} activityId=${activity.id} COMPLETED but result was missing`);
2851
+ while (activity?.status === "ACTIVITY_STATUS_PENDING") {
2852
+ if (Date.now() > deadline) {
2853
+ throw (0, km_core_1.signerRetryable)(km_core_1.SignerErrorCode.TURNKEY_SIGNER_UNAVAILABLE, `Turnkey ${ctx} activityId=${activity.id} stayed PENDING past ${POLL_TIMEOUT_MS}ms`);
2854
+ }
2855
+ const sleepIdx = Math.min(pollAttempt, POLL_BACKOFF_MS.length - 1);
2856
+ await (0, promises_1.setTimeout)(POLL_BACKOFF_MS[sleepIdx]);
2857
+ pollAttempt++;
2858
+ const polled = await this.queryPost("/public/v1/query/get_activity", { organizationId: this.opts.organizationId, activityId: activity.id });
2859
+ activity = polled.activity;
2860
+ }
2861
+ if (activity?.status !== "ACTIVITY_STATUS_COMPLETED") {
2862
+ const failure = activity?.failure ?? {};
2863
+ throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_ACTIVITY_FAILED, `Turnkey ${ctx} activityId=${activity?.id ?? "?"} status=${activity?.status ?? "unknown"}; ${JSON.stringify(failure)}`);
2864
+ }
2865
+ if (activity.result == null) {
2866
+ throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `Turnkey ${ctx} activityId=${activity.id} COMPLETED but result was missing`);
2867
+ }
2868
+ outcome = "success";
2869
+ return activity.result;
2870
+ } finally {
2871
+ this.emitSignDuration(Date.now() - start, activityType, outcome);
2817
2872
  }
2818
- return activity.result;
2819
2873
  }
2820
2874
  // ── HTTP layer ────────────────────────────────────────────────────────────
2821
2875
  /**
@@ -3089,17 +3143,33 @@ var require_secp256k1_helpers = __commonJS({
3089
3143
  "use strict";
3090
3144
  Object.defineProperty(exports2, "__esModule", { value: true });
3091
3145
  exports2.signSecp256k1PreimageAndVerify = signSecp256k1PreimageAndVerify;
3146
+ exports2.signSecp256k1TypedDataAndVerify = signSecp256k1TypedDataAndVerify;
3092
3147
  exports2.bytesFromHexOrBytes = bytesFromHexOrBytes;
3093
3148
  var ethers_1 = require("ethers");
3094
3149
  var km_core_1 = require_dist5();
3150
+ var EIP712_HASH_FUNCTION = "HASH_FUNCTION_NOT_APPLICABLE";
3095
3151
  async function signSecp256k1PreimageAndVerify(client, signWith, preimage, expectedEvmAddrLower) {
3096
3152
  const preimageHex = (0, ethers_1.hexlify)(preimage).slice(2);
3097
- const { r, s, v } = await client.signRawPayload({
3153
+ const parts = await client.signRawPayload({
3098
3154
  signWith,
3099
3155
  payload: preimageHex,
3100
3156
  encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
3101
3157
  hashFunction: "HASH_FUNCTION_KECCAK256"
3102
3158
  });
3159
+ return recoverAndVerify(parts, (0, ethers_1.keccak256)(preimage), expectedEvmAddrLower);
3160
+ }
3161
+ async function signSecp256k1TypedDataAndVerify(client, signWith, domain, types, message, expectedEvmAddrLower) {
3162
+ const payload = ethers_1.TypedDataEncoder.getPayload(domain, types, message);
3163
+ const parts = await client.signRawPayload({
3164
+ signWith,
3165
+ payload: JSON.stringify(payload),
3166
+ encoding: "PAYLOAD_ENCODING_EIP712",
3167
+ hashFunction: EIP712_HASH_FUNCTION
3168
+ });
3169
+ const digestHex = ethers_1.TypedDataEncoder.hash(domain, types, message);
3170
+ return recoverAndVerify(parts, digestHex, expectedEvmAddrLower);
3171
+ }
3172
+ function recoverAndVerify({ r, s, v }, digestHex, expectedEvmAddrLower) {
3103
3173
  let sig;
3104
3174
  try {
3105
3175
  sig = ethers_1.Signature.from({
@@ -3110,7 +3180,6 @@ var require_secp256k1_helpers = __commonJS({
3110
3180
  } catch (err) {
3111
3181
  throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `Turnkey returned malformed secp256k1 signature parts r=${r} s=${s} v=${v}`, err instanceof Error ? err : void 0);
3112
3182
  }
3113
- const digestHex = (0, ethers_1.keccak256)(preimage);
3114
3183
  const recovered = (0, ethers_1.recoverAddress)(digestHex, sig).toLowerCase();
3115
3184
  if (recovered !== expectedEvmAddrLower) {
3116
3185
  throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `secp256k1 sign-then-recover mismatch: recovered ${recovered}, expected ${expectedEvmAddrLower}`);
@@ -3174,8 +3243,7 @@ ${msgBytes.length}`);
3174
3243
  }
3175
3244
  async signTypedData(domain, types, message) {
3176
3245
  const { signWith, lower } = this.requireAddresses();
3177
- const preimage = (0, ethers_1.getBytes)(ethers_1.TypedDataEncoder.encode(domain, types, message));
3178
- return (0, secp256k1_helpers_1.signSecp256k1PreimageAndVerify)(this.client, signWith, preimage, lower);
3246
+ return (0, secp256k1_helpers_1.signSecp256k1TypedDataAndVerify)(this.client, signWith, domain, types, message, lower);
3179
3247
  }
3180
3248
  async signTransaction(tx) {
3181
3249
  const { signWith, lower } = this.requireAddresses();
@@ -3292,6 +3360,7 @@ var require_solana2 = __commonJS({
3292
3360
  };
3293
3361
  Object.defineProperty(exports2, "__esModule", { value: true });
3294
3362
  exports2.TurnkeySolanaSigner = void 0;
3363
+ exports2.encodePayload = encodePayload;
3295
3364
  var web3_js_1 = require("@solana/web3.js");
3296
3365
  var bs58_1 = __importDefault(require("bs58"));
3297
3366
  var km_core_1 = require_dist5();
@@ -3325,8 +3394,7 @@ var require_solana2 = __commonJS({
3325
3394
  }
3326
3395
  const { r, s } = await this.client.signRawPayload({
3327
3396
  signWith: this.address,
3328
- payload: Buffer.from(bytes).toString("hex"),
3329
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
3397
+ ...encodePayload(bytes),
3330
3398
  hashFunction: "HASH_FUNCTION_NOT_APPLICABLE"
3331
3399
  });
3332
3400
  const sig = Buffer.concat([Buffer.from(stripHex(r), "hex"), Buffer.from(stripHex(s), "hex")]);
@@ -3367,6 +3435,15 @@ var require_solana2 = __commonJS({
3367
3435
  }
3368
3436
  };
3369
3437
  exports2.TurnkeySolanaSigner = TurnkeySolanaSigner2;
3438
+ function encodePayload(bytes) {
3439
+ const buf = Buffer.from(bytes);
3440
+ const text = buf.toString("utf8");
3441
+ const textual = buf.every((b) => b >= 32 && b <= 126 || b === 9 || b === 10 || b === 13);
3442
+ if (textual && Buffer.from(text, "utf8").equals(buf)) {
3443
+ return { payload: text, encoding: "PAYLOAD_ENCODING_TEXT_UTF8" };
3444
+ }
3445
+ return { payload: buf.toString("hex"), encoding: "PAYLOAD_ENCODING_HEXADECIMAL" };
3446
+ }
3370
3447
  function stripHex(s) {
3371
3448
  return s.startsWith("0x") ? s.slice(2) : s;
3372
3449
  }
@@ -3434,8 +3511,7 @@ ${messageBytes.length}`);
3434
3511
  }
3435
3512
  async signTypedData(domain, types, message) {
3436
3513
  const { address, evmAddr } = this.requireReady();
3437
- const preimage = (0, ethers_1.getBytes)(ethers_1.TypedDataEncoder.encode(domain, types, message));
3438
- return (0, secp256k1_helpers_1.signSecp256k1PreimageAndVerify)(this.client, address, preimage, evmAddr);
3514
+ return (0, secp256k1_helpers_1.signSecp256k1TypedDataAndVerify)(this.client, address, domain, types, message, evmAddr);
3439
3515
  }
3440
3516
  async signTransaction(tx) {
3441
3517
  const { address, evmAddr } = this.requireReady();
@@ -3814,7 +3890,7 @@ var require_policies = __commonJS({
3814
3890
  policyName: "allowSignRawPayload",
3815
3891
  effect: "EFFECT_ALLOW",
3816
3892
  condition: `activity.type in ['ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2', 'ACTIVITY_TYPE_SIGN_RAW_PAYLOADS'] && activity.params.hash_function in ['HASH_FUNCTION_KECCAK256', 'HASH_FUNCTION_NOT_APPLICABLE']`,
3817
- notes: "Allows signRawPayload with the hash functions the agent runtime uses."
3893
+ notes: "Allows signRawPayload with the hash functions the runtime uses: KECCAK256 (EIP-191/TIP-191/W3C proofs) and NOT_APPLICABLE (Solana ed25519 + EIP-712 typed data)."
3818
3894
  };
3819
3895
  }
3820
3896
  function buildDenyApproveToNonAllowedSpenderEvm(args) {
@@ -4459,7 +4535,7 @@ var require_dist6 = __commonJS({
4459
4535
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
4460
4536
  };
4461
4537
  Object.defineProperty(exports2, "__esModule", { value: true });
4462
- exports2.bytesFromHexOrBytes = exports2.signSecp256k1PreimageAndVerify = exports2.extractTronSignatureFromTransactionProtobuf = exports2.TurnkeyTronSigner = exports2.TurnkeySolanaSigner = exports2.TurnkeyEvmSigner = exports2.noopMeter = exports2.noopLogger = exports2.TURNKEY_RETRY_BACKOFF_MS = exports2.resolveTurnkeyAccountAddress = exports2.p1363ToDer = exports2.createTurnkeyStamper = exports2.createIsolatedTurnkeyClient = exports2.getOrCreateTurnkeyClient = exports2.TurnkeyClient = void 0;
4538
+ exports2.bytesFromHexOrBytes = exports2.signSecp256k1PreimageAndVerify = exports2.extractTronSignatureFromTransactionProtobuf = exports2.TurnkeyTronSigner = exports2.encodePayload = exports2.TurnkeySolanaSigner = exports2.TurnkeyEvmSigner = exports2.noopMeter = exports2.noopLogger = exports2.TURNKEY_RETRY_BACKOFF_MS = exports2.resolveTurnkeyAccountAddress = exports2.p1363ToDer = exports2.createTurnkeyStamper = exports2.createIsolatedTurnkeyClient = exports2.getOrCreateTurnkeyClient = exports2.TurnkeyClient = void 0;
4463
4539
  var client_1 = require_client();
4464
4540
  Object.defineProperty(exports2, "TurnkeyClient", { enumerable: true, get: function() {
4465
4541
  return client_1.TurnkeyClient;
@@ -4502,6 +4578,9 @@ var require_dist6 = __commonJS({
4502
4578
  Object.defineProperty(exports2, "TurnkeySolanaSigner", { enumerable: true, get: function() {
4503
4579
  return solana_1.TurnkeySolanaSigner;
4504
4580
  } });
4581
+ Object.defineProperty(exports2, "encodePayload", { enumerable: true, get: function() {
4582
+ return solana_1.encodePayload;
4583
+ } });
4505
4584
  var tron_1 = require_tron2();
4506
4585
  Object.defineProperty(exports2, "TurnkeyTronSigner", { enumerable: true, get: function() {
4507
4586
  return tron_1.TurnkeyTronSigner;
@@ -4563,13 +4642,13 @@ async function signWithTurnkeyClient(client, chainPrefix, cfg, message, messageP
4563
4642
  };
4564
4643
  switch (chainPrefix) {
4565
4644
  case "eip155": {
4566
- const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "EVM");
4645
+ const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "EVM", message);
4567
4646
  const signer = new import_turnkey_signer.TurnkeyEvmSigner(client, deps);
4568
4647
  await signer.initialize();
4569
4648
  return { signature: await signer.signTypedData(domain, types, typedMessage) };
4570
4649
  }
4571
4650
  case "tron": {
4572
- const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "Tron");
4651
+ const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "Tron", message);
4573
4652
  const signer = new import_turnkey_signer.TurnkeyTronSigner(client, deps);
4574
4653
  await signer.initialize();
4575
4654
  return { signature: await signer.signTypedData(domain, types, typedMessage) };
@@ -4597,7 +4676,7 @@ function decodeSolanaMessageHex(message) {
4597
4676
  }
4598
4677
  return Uint8Array.from(Buffer.from(hex, "hex"));
4599
4678
  }
4600
- function parsePrehash(messagePrehash, label) {
4679
+ function parsePrehash(messagePrehash, label, advertisedDigest) {
4601
4680
  if (!messagePrehash) {
4602
4681
  throw new Error(`Turnkey ${label} signing requires signed_messages[0].message_prehash`);
4603
4682
  }
@@ -4611,13 +4690,21 @@ function parsePrehash(messagePrehash, label) {
4611
4690
  if (!isObject(parsed) || !isObject(parsed.domain) || !isObject(parsed.types) || !isObject(parsed.message)) {
4612
4691
  throw new Error(`Turnkey ${label} signing: message_prehash must be a JSON object with domain, types and message`);
4613
4692
  }
4614
- return { domain: parsed.domain, types: parsed.types, message: parsed.message };
4693
+ const { EIP712Domain: _derived, ...structTypes } = parsed.types;
4694
+ const prehashDigest = import_ethers5.TypedDataEncoder.hash(parsed.domain, structTypes, parsed.message);
4695
+ if (prehashDigest.toLowerCase() !== advertisedDigest.toLowerCase()) {
4696
+ throw new Error(
4697
+ `Turnkey ${label} signing: message_prehash implies digest ${prehashDigest} but message is ${advertisedDigest}`
4698
+ );
4699
+ }
4700
+ return { domain: parsed.domain, types: structTypes, message: parsed.message };
4615
4701
  }
4616
- var import_turnkey_signer, TURNKEY_API_URL, DEFAULTS;
4702
+ var import_turnkey_signer, import_ethers5, TURNKEY_API_URL, DEFAULTS;
4617
4703
  var init_turnkey_impl = __esm({
4618
4704
  "src/signers/turnkey-impl.ts"() {
4619
4705
  "use strict";
4620
4706
  import_turnkey_signer = __toESM(require_dist6());
4707
+ import_ethers5 = require("ethers");
4621
4708
  TURNKEY_API_URL = "https://api.turnkey.com";
4622
4709
  DEFAULTS = {
4623
4710
  concurrencyLimit: 10,
@@ -4674,7 +4761,7 @@ __export(index_exports, {
4674
4761
  HealthService: () => HealthService,
4675
4762
  Interceptors: () => Interceptors,
4676
4763
  MAX_VALID_SLOT: () => import_solana_escrow_encoding.MAX_VALID_SLOT,
4677
- OpenAPI: () => OpenAPI,
4764
+ OpenAPI: () => OpenAPI2,
4678
4765
  PAYMENT_REQUEST_VERSION: () => PAYMENT_REQUEST_VERSION,
4679
4766
  PERMIT2_CONTRACT_ADDRESS: () => import_evm_escrow_encoding.PERMIT2_CONTRACT_ADDRESS,
4680
4767
  PaymentGatewayClient: () => PaymentGatewayClient2,
@@ -4694,11 +4781,13 @@ __export(index_exports, {
4694
4781
  encodeU64LE: () => import_solana_escrow_encoding.encodeU64LE,
4695
4782
  generateSolanaNonce: () => generateSolanaNonce,
4696
4783
  generateSolanaPaymentId: () => generateSolanaPaymentId,
4784
+ getErrorResponse: () => getErrorResponse,
4697
4785
  hashPermitWitnessTransferFrom: () => import_evm_escrow_encoding.hashPermitWitnessTransferFrom,
4698
4786
  hashRequestId: () => hashRequestId,
4699
4787
  hashSolanaDeposit: () => hashSolanaDeposit,
4700
4788
  hashTvmPermitWitnessTransferFrom: () => import_tvm_escrow_encoding.hashTvmPermitWitnessTransferFrom,
4701
4789
  hexToBytes: () => hexToBytes,
4790
+ isApiError: () => isApiError,
4702
4791
  maskChainId: () => import_tvm_escrow_encoding.maskChainId,
4703
4792
  normalizeAddress: () => import_tvm_escrow_encoding.normalizeAddress,
4704
4793
  resolveTurnkeyAddresses: () => resolveTurnkeyAddresses,
@@ -4722,7 +4811,7 @@ var Interceptors = class {
4722
4811
  }
4723
4812
  };
4724
4813
  var OpenAPI = {
4725
- BASE: "https://api.atum.network",
4814
+ BASE: "https://payment-gw.production-mainnet.atum.xyz",
4726
4815
  CREDENTIALS: "include",
4727
4816
  ENCODE_PATH: void 0,
4728
4817
  HEADERS: void 0,
@@ -4905,7 +4994,7 @@ var getUrl = (config, options) => {
4905
4994
  var getFormData = (options) => {
4906
4995
  if (options.formData) {
4907
4996
  const formData = new FormData();
4908
- const process = (key, value) => {
4997
+ const process2 = (key, value) => {
4909
4998
  if (isString(value) || isBlob(value)) {
4910
4999
  formData.append(key, value);
4911
5000
  } else {
@@ -4914,9 +5003,9 @@ var getFormData = (options) => {
4914
5003
  };
4915
5004
  Object.entries(options.formData).filter(([, value]) => value !== void 0 && value !== null).forEach(([key, value]) => {
4916
5005
  if (Array.isArray(value)) {
4917
- value.forEach((v) => process(key, v));
5006
+ value.forEach((v) => process2(key, v));
4918
5007
  } else {
4919
- process(key, value);
5008
+ process2(key, value);
4920
5009
  }
4921
5010
  });
4922
5011
  return formData;
@@ -5138,8 +5227,26 @@ var PaymentsService = class {
5138
5227
  * 2. Find optimal settlement routes across chains/assets
5139
5228
  * 3. Secure funds in escrow
5140
5229
  * 4. Coordinate with settlement agents for execution
5141
- * 5. Return a real-time result if processing completes within 30 seconds,
5142
- * otherwise return a payment ID for status polling
5230
+ * 5. Return a real-time result if settlement completes inside the synchronous window,
5231
+ * otherwise return a payment ID and status for polling
5232
+ *
5233
+ * **Idempotency.** `request_id` is the idempotency key, scoped to the source account
5234
+ * and originator. Submitting the same `request_id` again returns the ORIGINAL payment
5235
+ * instead of charging a second time, so a client whose request timed out or failed
5236
+ * in transit can safely re-submit.
5237
+ *
5238
+ * Because a re-submission must be admissible on its own, some fields legitimately
5239
+ * differ between attempts and are excluded from the comparison: `quote_deadline` and
5240
+ * `fulfillment_deadline` (both must be in the future, so a later attempt has to move
5241
+ * them), and the `sender_auth` signature they are signed into.
5242
+ *
5243
+ * What must NOT differ is the payment's economics — source account and asset,
5244
+ * destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
5245
+ * payer's own risk cap; lowering it on a retry describes a different payment, not the
5246
+ * same one with less headroom). Re-using one `request_id` for a genuinely different
5247
+ * payment is a client bug that would otherwise be silently collapsed onto the first
5248
+ * payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
5249
+ * distinct `request_id` per payment.
5143
5250
  *
5144
5251
  * @param data The data for the request.
5145
5252
  * @param data.requestBody
@@ -5156,6 +5263,11 @@ var PaymentsService = class {
5156
5263
  400: "Invalid request",
5157
5264
  401: "Signature or sender authorization could not be verified",
5158
5265
  403: "Payment declined during counterparty authorization",
5266
+ 409: `The \`request_id\` has already been used for a payment with different economics
5267
+ (\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
5268
+ made safe, so it must identify one payment; use a distinct \`request_id\` per
5269
+ payment.
5270
+ `,
5159
5271
  413: "Payment request too large to process",
5160
5272
  500: "Internal server error",
5161
5273
  503: "A dependency is temporarily unavailable"
@@ -5445,7 +5557,7 @@ var HealthService = class {
5445
5557
  var PaymentGatewayClient = class {
5446
5558
  constructor(config, HttpRequest = AxiosHttpRequest) {
5447
5559
  this.request = new HttpRequest({
5448
- BASE: config?.BASE ?? "https://api.atum.network",
5560
+ BASE: config?.BASE ?? "https://payment-gw.production-mainnet.atum.xyz",
5449
5561
  VERSION: config?.VERSION ?? "1.0.0",
5450
5562
  WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
5451
5563
  CREDENTIALS: config?.CREDENTIALS ?? "include",
@@ -5830,8 +5942,11 @@ Leave empty to use the default verifier.
5830
5942
  endpoint: {
5831
5943
  type: "string",
5832
5944
  format: "uri",
5833
- description: "The API endpoint URL where the verification service can be reached",
5834
- example: "https://verifier.atum.network/api/v1/verify"
5945
+ description: `Origin of the fulfillment verifier service, with no path \u2014 the caller
5946
+ appends the verifier's own API paths. The gateway's \`/defaults\` response
5947
+ supplies the default fulfillment verifier for each chain.
5948
+ `,
5949
+ example: "https://veri-fill.production-testnet.atum.xyz"
5835
5950
  }
5836
5951
  }
5837
5952
  },
@@ -6147,11 +6262,18 @@ var $VerifiableCredential = {
6147
6262
  };
6148
6263
  var $PaymentResponse = {
6149
6264
  type: "object",
6150
- description: `Response from submitting a payment request. If processing completes within 30 seconds,
6151
- the response will include a fulfillment_confirmation with full settlement details.
6152
- Otherwise, you'll receive a payment_id that you can use to poll the
6153
- /v1/payments/{paymentId}/status endpoint for updates.
6265
+ description: `Response from submitting a payment request. If settlement completes inside the
6266
+ gateway's synchronous window, the response includes a fulfillment_confirmation with
6267
+ full settlement details. Otherwise you receive a payment_id plus the payment's
6268
+ current \`status\`, and can poll /v1/payments/{paymentId}/status for updates.
6269
+
6270
+ Read \`status\` before deciding what to do next: a \`pending\` payment is in flight and
6271
+ must NOT be re-submitted under a new request_id (that would be a second payment),
6272
+ whereas a \`failed\` or \`cancelled\` one is terminal \u2014 re-submitting it under the SAME
6273
+ request_id resolves to the same terminal payment, so a genuine re-attempt needs a
6274
+ new request_id.
6154
6275
  `,
6276
+ required: ["payment_id", "status", "idempotent_replay"],
6155
6277
  properties: {
6156
6278
  payment_id: {
6157
6279
  type: "string",
@@ -6159,8 +6281,60 @@ Otherwise, you'll receive a payment_id that you can use to poll the
6159
6281
  `,
6160
6282
  example: "550e8400-e29b-41d4-a716-446655440000"
6161
6283
  },
6284
+ status: {
6285
+ type: "string",
6286
+ enum: ["pending", "completed", "failed", "cancelled"],
6287
+ description: `Current status of the payment, with the same meaning as on
6288
+ /v1/payments/{paymentId}/status:
6289
+ - \`pending\`: accepted and being processed
6290
+ - \`completed\`: successfully delivered
6291
+ - \`failed\`: failed (funds returned if escrowed)
6292
+ - \`cancelled\`: cancelled before completion
6293
+
6294
+ Present on every response. \`completed\` is always accompanied by
6295
+ fulfillment_confirmation; \`failed\` is accompanied by \`error\` where a reason has
6296
+ been recorded.
6297
+ `
6298
+ },
6299
+ idempotent_replay: {
6300
+ type: "boolean",
6301
+ description: `Whether this response describes a payment that already existed, rather than one
6302
+ created by this request. \`true\` means the request_id had been used before and you
6303
+ are being handed the ORIGINAL payment \u2014 no second payment was created and nothing
6304
+ additional was charged.
6305
+
6306
+ **Re-attempting a payment.** Re-submit the same request and you get the same payment
6307
+ back. You may re-send the original bytes unchanged: the deadlines will have expired by
6308
+ then \u2014 \`quote_deadline\` bounds the auction and is short, while this endpoint may hold a
6309
+ submission for up to 30 seconds \u2014 and an expired deadline on a re-submission is not an
6310
+ error, because the request is resolved onto the payment that already exists rather than
6311
+ used to create one.
6312
+
6313
+ Re-signing with fresh deadlines is equally fine and returns the same payment. What you
6314
+ must NOT change is the request_id, and what you must not change under it is the
6315
+ payment's economics \u2014 source, destination, fulfillment_amount, max_source_amount \u2014 which
6316
+ are rejected with 409 IDEMPOTENCY_TERMS_MISMATCH. Deadlines and the signature are
6317
+ exempt precisely so that a re-attempt is possible.
6318
+
6319
+ A genuinely new payment needs a NEW request_id. In particular a \`failed\` payment is
6320
+ terminal: re-submitting its request_id keeps returning that dead payment, so recovering
6321
+ means starting a new one under a new key.
6322
+
6323
+ Present on every response, \`false\` for a newly created payment. A response without
6324
+ the field is from a gateway predating it, not a newly created payment.
6325
+
6326
+ This is the field to read when reconciling a suspected double charge: two
6327
+ submissions where the second reports \`true\` are one payment, whereas two both
6328
+ reporting \`false\` are two. It says nothing about how far the payment has got \u2014
6329
+ read \`status\` for that.
6330
+ `,
6331
+ example: false
6332
+ },
6162
6333
  fulfillment_confirmation: {
6163
6334
  "$ref": "#/components/schemas/FulfillmentConfirmation"
6335
+ },
6336
+ error: {
6337
+ "$ref": "#/components/schemas/PaymentError"
6164
6338
  }
6165
6339
  }
6166
6340
  };
@@ -6699,8 +6873,11 @@ Leave empty to use the default selector.
6699
6873
  endpoint: {
6700
6874
  type: "string",
6701
6875
  format: "uri",
6702
- description: "The API endpoint URL where the verification service can be reached",
6703
- example: "https://verifier.atum.network/api/v1/verify"
6876
+ description: `Origin of the fulfillment verifier service, with no path \u2014 the caller
6877
+ appends the verifier's own API paths. The gateway's \`/defaults\` response
6878
+ supplies the default fulfillment verifier for each chain.
6879
+ `,
6880
+ example: "https://veri-fill.production-testnet.atum.xyz"
6704
6881
  }
6705
6882
  }
6706
6883
  },
@@ -6820,7 +6997,7 @@ bid_award.fulfillment_quote_json.
6820
6997
  fulfillment_quote: {
6821
6998
  type: "object",
6822
6999
  description: `The settler-signed FulfillmentQuote (structurally the canonical FulfillmentQuote/v1
6823
- declaration at https://schemas.atum.network/declarations/FulfillmentQuote/v1), returned
7000
+ declaration at https://schemas.atum.xyz/declarations/FulfillmentQuote/v1), returned
6824
7001
  verbatim as an opaque signed document. A client recomputes quote_hash over the returned
6825
7002
  bytes and verifies the settler signature, then re-embeds them into
6826
7003
  bid_award.fulfillment_quote_json at award. quote_hash is canonicalization-based
@@ -6846,7 +7023,7 @@ var $AwardedQuote = {
6846
7023
  fulfillment_quote: {
6847
7024
  type: "object",
6848
7025
  description: `The settler-signed FulfillmentQuote (structurally the canonical FulfillmentQuote/v1
6849
- declaration at https://schemas.atum.network/declarations/FulfillmentQuote/v1), returned
7026
+ declaration at https://schemas.atum.xyz/declarations/FulfillmentQuote/v1), returned
6850
7027
  verbatim as an opaque signed document. A client recomputes quote_hash over the returned
6851
7028
  bytes and verifies the settler signature, then re-embeds them into
6852
7029
  bid_award.fulfillment_quote_json at award. quote_hash is canonicalization-based
@@ -7160,6 +7337,21 @@ var $AwardAccepted = {
7160
7337
  }
7161
7338
  };
7162
7339
 
7340
+ // src/errors.ts
7341
+ function isApiError(err) {
7342
+ return err instanceof ApiError;
7343
+ }
7344
+ function getErrorResponse(err) {
7345
+ if (!(err instanceof ApiError)) {
7346
+ return void 0;
7347
+ }
7348
+ const body = err.body;
7349
+ if (body && typeof body === "object" && typeof body.code === "string" && typeof body.message === "string") {
7350
+ return body;
7351
+ }
7352
+ return void 0;
7353
+ }
7354
+
7163
7355
  // src/sender_auth_schemes/evm_permit2.ts
7164
7356
  var import_ethers3 = require("ethers");
7165
7357
  var import_evm_escrow_encoding = __toESM(require_dist2());
@@ -7333,7 +7525,8 @@ async function signPaymentRequest(request2, signer) {
7333
7525
  }
7334
7526
  return request2;
7335
7527
  }
7336
- var defaultGateway = "https://payment-gateway-testnet.atumlabs.xyz";
7528
+ var defaultGateway = "https://payment-gw.production-testnet.atum.xyz";
7529
+ var OpenAPI2 = { ...OpenAPI, BASE: defaultGateway };
7337
7530
  var DEFAULT_QUOTE_DEADLINE_SECONDS = 5;
7338
7531
  var DEFAULT_FULFILLMENT_DEADLINE_SECONDS = 3600;
7339
7532
  var DEFAULT_PAYMENT_QUOTE_DEADLINE_SECONDS = 10;
@@ -7422,8 +7615,10 @@ async function assemblePaymentRequest(params) {
7422
7615
  }
7423
7616
  var PaymentGatewayClient2 = class extends PaymentGatewayClient {
7424
7617
  constructor(config, HttpRequest = AxiosHttpRequest) {
7425
- super(config, HttpRequest);
7426
- this.baseUrl = config?.BASE ?? defaultGateway;
7618
+ const envBase = (typeof process !== "undefined" ? process.env.GATEWAY_URL : void 0)?.trim();
7619
+ const base = config?.BASE ?? (envBase || defaultGateway);
7620
+ super({ ...config, BASE: base }, HttpRequest);
7621
+ this.baseUrl = base;
7427
7622
  }
7428
7623
  /**
7429
7624
  * Fetches chain defaults from the payment gateway.
@@ -7703,11 +7898,13 @@ var PaymentGatewayClient2 = class extends PaymentGatewayClient {
7703
7898
  encodeU64LE,
7704
7899
  generateSolanaNonce,
7705
7900
  generateSolanaPaymentId,
7901
+ getErrorResponse,
7706
7902
  hashPermitWitnessTransferFrom,
7707
7903
  hashRequestId,
7708
7904
  hashSolanaDeposit,
7709
7905
  hashTvmPermitWitnessTransferFrom,
7710
7906
  hexToBytes,
7907
+ isApiError,
7711
7908
  maskChainId,
7712
7909
  normalizeAddress,
7713
7910
  resolveTurnkeyAddresses,