@atumlabs/mppx-atum-escrow 0.2.2 → 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.
@@ -1,5 +1,6 @@
1
1
  import { createRequire as __atumCreateRequire } from 'module'; const require = __atumCreateRequire(import.meta.url);
2
2
  import {
3
+ INTENT_ID_META_KEY,
3
4
  __commonJS,
4
5
  __esm,
5
6
  __export,
@@ -9,9 +10,12 @@ import {
9
10
  assetIdentifier,
10
11
  atumEscrowChargeMethod,
11
12
  chainDefaultsFromExtra,
13
+ chargeRequestId,
14
+ intentIdOf,
12
15
  namespaceOf,
13
- require_dist
14
- } from "./chunk-4P34CLTO.js";
16
+ require_dist,
17
+ require_dist2
18
+ } from "./chunk-Z3AUNEU5.js";
15
19
 
16
20
  // ../node_modules/.pnpm/delayed-stream@1.0.0/node_modules/delayed-stream/lib/delayed_stream.js
17
21
  var require_delayed_stream = __commonJS({
@@ -15185,7 +15189,7 @@ var require_axios = __commonJS({
15185
15189
  });
15186
15190
 
15187
15191
  // ../payment-gateway-client/dist/index.js
15188
- var require_dist2 = __commonJS({
15192
+ var require_dist3 = __commonJS({
15189
15193
  "../payment-gateway-client/dist/index.js"(exports, module) {
15190
15194
  "use strict";
15191
15195
  var __create = Object.create;
@@ -15380,7 +15384,7 @@ var require_dist2 = __commonJS({
15380
15384
  }
15381
15385
  }
15382
15386
  });
15383
- var require_dist3 = __commonJS2({
15387
+ var require_dist4 = __commonJS2({
15384
15388
  "../contracts/tvm/escrow-encoding/typescript/dist/index.js"(exports2) {
15385
15389
  "use strict";
15386
15390
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -15423,12 +15427,14 @@ var require_dist2 = __commonJS({
15423
15427
  "../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/utils.js"(exports2) {
15424
15428
  "use strict";
15425
15429
  Object.defineProperty(exports2, "__esModule", { value: true });
15426
- exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
15430
+ exports2.REQUEST_ID_DOMAIN = exports2.SOLANA_REPLAY_SLOTS = exports2.PERMIT_NONCE_DOMAIN = exports2.REPLAY_HORIZON_SECS = void 0;
15427
15431
  exports2.resolvePermitDeadline = resolvePermitDeadline;
15428
15432
  exports2.generatePermitNonce = generatePermitNonce;
15429
15433
  exports2.resolvePermitNonce = resolvePermitNonce;
15434
+ exports2.canonicalDepositor = canonicalDepositor2;
15430
15435
  exports2.deterministicPermitNonce = deterministicPermitNonce2;
15431
15436
  exports2.deterministicSolanaNonce = deterministicSolanaNonce2;
15437
+ exports2.deterministicRequestId = deterministicRequestId;
15432
15438
  exports2.sameAddress = sameAddress;
15433
15439
  exports2.assertKeyMatchesSender = assertKeyMatchesSender2;
15434
15440
  exports2.assertToken = assertToken;
@@ -15454,7 +15460,7 @@ var require_dist2 = __commonJS({
15454
15460
  exports2.PERMIT_NONCE_DOMAIN = "atum-permit:v1:";
15455
15461
  exports2.SOLANA_REPLAY_SLOTS = 235n;
15456
15462
  var SOLANA_BUCKET_BITS = 17n;
15457
- function canonicalDepositor(depositor) {
15463
+ function canonicalDepositor2(depositor) {
15458
15464
  const lower = depositor.toLowerCase();
15459
15465
  return lower.startsWith("0x") ? lower : depositor;
15460
15466
  }
@@ -15462,7 +15468,7 @@ var require_dist2 = __commonJS({
15462
15468
  if (!requestId) {
15463
15469
  throw new Error("sender_auth: request_id is required to derive a deterministic permit nonce");
15464
15470
  }
15465
- return BigInt((0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.PERMIT_NONCE_DOMAIN}${requestId}:${canonicalDepositor(depositor)}`)));
15471
+ return BigInt((0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.PERMIT_NONCE_DOMAIN}${requestId}:${canonicalDepositor2(depositor)}`)));
15466
15472
  }
15467
15473
  function deterministicPermitNonce2(params) {
15468
15474
  return permitNonceSeed(params.requestId, params.depositor);
@@ -15473,6 +15479,20 @@ var require_dist2 = __commonJS({
15473
15479
  const bucket = (seed >> 8n) % (1n << SOLANA_BUCKET_BITS);
15474
15480
  return bucket << 32n | slot;
15475
15481
  }
15482
+ exports2.REQUEST_ID_DOMAIN = "atum-request:v1:";
15483
+ function deterministicRequestId(params) {
15484
+ if (!params.anchor) {
15485
+ 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)");
15486
+ }
15487
+ if (!params.account) {
15488
+ throw new Error("sender_auth: account is required to derive a deterministic request_id");
15489
+ }
15490
+ if (!/^[A-Za-z0-9_-]*$/.test(params.prefix)) {
15491
+ throw new Error(`sender_auth: request_id prefix must contain only letters, digits, '_' or '-' (got ${JSON.stringify(params.prefix)})`);
15492
+ }
15493
+ const digest = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(`${exports2.REQUEST_ID_DOMAIN}${params.anchor}:${canonicalDepositor2(params.account)}`));
15494
+ return `${params.prefix}${digest.slice(2, 26)}`;
15495
+ }
15476
15496
  function sameAddress(a, b) {
15477
15497
  if (a.startsWith("0x") && b.startsWith("0x")) {
15478
15498
  return a.toLowerCase() === b.toLowerCase();
@@ -16044,7 +16064,7 @@ var require_dist2 = __commonJS({
16044
16064
  exports2.evmAdapter = {
16045
16065
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
16046
16066
  if (nonce === void 0) {
16047
- 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");
16067
+ 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");
16048
16068
  }
16049
16069
  if (deadline === void 0) {
16050
16070
  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)");
@@ -16072,6 +16092,10 @@ var require_dist2 = __commonJS({
16072
16092
  throw new Error("EVM adapter sign(): signed_messages[i].message_prehash is required");
16073
16093
  }
16074
16094
  const prehash = JSON.parse(signedMessage.message_prehash);
16095
+ const prehashDigest = (0, evm_escrow_encoding_1.hashPermitWitnessTransferFrom)(prehash);
16096
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
16097
+ throw new Error(`EVM adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
16098
+ }
16075
16099
  const wallet = new ethers_1.Wallet(privateKey);
16076
16100
  return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
16077
16101
  },
@@ -16149,14 +16173,14 @@ var require_dist2 = __commonJS({
16149
16173
  Object.defineProperty(exports2, "__esModule", { value: true });
16150
16174
  exports2.tronAdapter = void 0;
16151
16175
  var ethers_1 = __require("ethers");
16152
- var tvm_escrow_encoding_1 = require_dist3();
16176
+ var tvm_escrow_encoding_1 = require_dist4();
16153
16177
  var utils_js_1 = require_utils();
16154
16178
  var errors_js_1 = require_errors();
16155
16179
  var chain_id_js_1 = require_chain_id2();
16156
16180
  exports2.tronAdapter = {
16157
16181
  async buildAuthMessage({ sourceAsset, maxSourceAmount, sourceDefaults, deadline, nonce }) {
16158
16182
  if (nonce === void 0) {
16159
- 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");
16183
+ 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");
16160
16184
  }
16161
16185
  if (deadline === void 0) {
16162
16186
  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)");
@@ -16179,9 +16203,19 @@ var require_dist2 = __commonJS({
16179
16203
  const messageHash = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(messagePrehash);
16180
16204
  return { messageScheme: "EVM_PERMIT2", messageHash, messagePrehash: JSON.stringify(messagePrehash) };
16181
16205
  },
16206
+ // Signs the TIP-712 typed data, not the bare digest, so a signer can inspect the fields it is
16207
+ // authorizing (Turnkey policies read `eth.eip_712.message[...]`).
16182
16208
  async sign(signedMessage, privateKey) {
16209
+ if (!signedMessage.message_prehash) {
16210
+ throw new Error("Tron adapter sign(): signed_messages[i].message_prehash is required");
16211
+ }
16212
+ const prehash = JSON.parse(signedMessage.message_prehash);
16213
+ const prehashDigest = (0, tvm_escrow_encoding_1.hashTvmPermitWitnessTransferFrom)(prehash);
16214
+ if (prehashDigest.toLowerCase() !== signedMessage.message.toLowerCase()) {
16215
+ throw new Error(`Tron adapter sign(): message_prehash implies digest ${prehashDigest} but message is ${signedMessage.message}`);
16216
+ }
16183
16217
  const wallet = new ethers_1.Wallet(privateKey.startsWith("0x") ? privateKey : "0x" + privateKey);
16184
- return wallet.signingKey.sign(signedMessage.message).serialized;
16218
+ return wallet.signTypedData(prehash.domain, prehash.types, prehash.message);
16185
16219
  },
16186
16220
  recoverSigner(signed, source) {
16187
16221
  let recovered;
@@ -17497,7 +17531,7 @@ var require_dist2 = __commonJS({
17497
17531
  }
17498
17532
  }
17499
17533
  });
17500
- var require_dist4 = __commonJS2({
17534
+ var require_dist42 = __commonJS2({
17501
17535
  "../schemas/declarations/PaymentRequest/v1/bindings/typescript/sender-auth/dist/index.js"(exports2) {
17502
17536
  "use strict";
17503
17537
  var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
@@ -17782,6 +17816,7 @@ var require_dist2 = __commonJS({
17782
17816
  retryCounter;
17783
17817
  backoffHistogram;
17784
17818
  activityPendingCounter;
17819
+ signDurationHistogram;
17785
17820
  // The private API scalar is held only long enough to construct the stamper. After
17786
17821
  // `getStamper()` runs, this is nulled out so the hex string doesn't survive on the heap
17787
17822
  // for the client's lifetime. Web Crypto holds the imported key as an opaque CryptoKey.
@@ -17810,6 +17845,10 @@ var require_dist2 = __commonJS({
17810
17845
  this.activityPendingCounter = meter.createCounter("turnkey.activity_pending_total", {
17811
17846
  description: "Activities that entered async polling (did not complete synchronously in the submit response), labeled by activity type"
17812
17847
  });
17848
+ this.signDurationHistogram = meter.createHistogram("turnkey.sign_duration_ms", {
17849
+ description: "Full Turnkey activity round-trip latency (concurrency-semaphore wait + submit + poll), labeled by activity type and outcome (success|error)",
17850
+ unit: "ms"
17851
+ });
17813
17852
  }
17814
17853
  emitRetry(reason, path) {
17815
17854
  try {
@@ -17829,6 +17868,12 @@ var require_dist2 = __commonJS({
17829
17868
  } catch {
17830
17869
  }
17831
17870
  }
17871
+ emitSignDuration(durationMs, activityType, outcome) {
17872
+ try {
17873
+ this.signDurationHistogram.record(durationMs, { activity_type: activityType, outcome });
17874
+ } catch {
17875
+ }
17876
+ }
17832
17877
  // ── public surface ─────────────────────────────────────────────────────────
17833
17878
  /** Boot-time reachability + auth probe. Throws TURNKEY_AUTH on bad credentials. */
17834
17879
  async whoami() {
@@ -17878,39 +17923,46 @@ var require_dist2 = __commonJS({
17878
17923
  }
17879
17924
  // ── activity submission ────────────────────────────────────────────────────
17880
17925
  async submitActivity(activityType, endpoint, parameters) {
17881
- const body = {
17882
- type: activityType,
17883
- timestampMs: String(Date.now()),
17884
- organizationId: this.opts.organizationId,
17885
- parameters
17886
- };
17887
- const signWith = typeof parameters.signWith === "string" ? parameters.signWith : void 0;
17888
- const ctx = `activity=${activityType}${signWith ? ` signWith=${signWith}` : ""}`;
17889
- const resp = await this.queryPost(`/public/v1/submit/${endpoint}`, body);
17890
- let activity = resp.activity;
17891
- const deadline = Date.now() + POLL_TIMEOUT_MS;
17892
- let pollAttempt = 0;
17893
- if (activity?.status === "ACTIVITY_STATUS_PENDING") {
17894
- this.emitActivityPending(activityType);
17895
- }
17896
- while (activity?.status === "ACTIVITY_STATUS_PENDING") {
17897
- if (Date.now() > deadline) {
17898
- 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`);
17926
+ const start = Date.now();
17927
+ let outcome = "error";
17928
+ try {
17929
+ const body = {
17930
+ type: activityType,
17931
+ timestampMs: String(Date.now()),
17932
+ organizationId: this.opts.organizationId,
17933
+ parameters
17934
+ };
17935
+ const signWith = typeof parameters.signWith === "string" ? parameters.signWith : void 0;
17936
+ const ctx = `activity=${activityType}${signWith ? ` signWith=${signWith}` : ""}`;
17937
+ const resp = await this.queryPost(`/public/v1/submit/${endpoint}`, body);
17938
+ let activity = resp.activity;
17939
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
17940
+ let pollAttempt = 0;
17941
+ if (activity?.status === "ACTIVITY_STATUS_PENDING") {
17942
+ this.emitActivityPending(activityType);
17899
17943
  }
17900
- const sleepIdx = Math.min(pollAttempt, POLL_BACKOFF_MS.length - 1);
17901
- await (0, promises_1.setTimeout)(POLL_BACKOFF_MS[sleepIdx]);
17902
- pollAttempt++;
17903
- const polled = await this.queryPost("/public/v1/query/get_activity", { organizationId: this.opts.organizationId, activityId: activity.id });
17904
- activity = polled.activity;
17905
- }
17906
- if (activity?.status !== "ACTIVITY_STATUS_COMPLETED") {
17907
- const failure = activity?.failure ?? {};
17908
- 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)}`);
17909
- }
17910
- if (activity.result == null) {
17911
- throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `Turnkey ${ctx} activityId=${activity.id} COMPLETED but result was missing`);
17944
+ while (activity?.status === "ACTIVITY_STATUS_PENDING") {
17945
+ if (Date.now() > deadline) {
17946
+ 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`);
17947
+ }
17948
+ const sleepIdx = Math.min(pollAttempt, POLL_BACKOFF_MS.length - 1);
17949
+ await (0, promises_1.setTimeout)(POLL_BACKOFF_MS[sleepIdx]);
17950
+ pollAttempt++;
17951
+ const polled = await this.queryPost("/public/v1/query/get_activity", { organizationId: this.opts.organizationId, activityId: activity.id });
17952
+ activity = polled.activity;
17953
+ }
17954
+ if (activity?.status !== "ACTIVITY_STATUS_COMPLETED") {
17955
+ const failure = activity?.failure ?? {};
17956
+ 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)}`);
17957
+ }
17958
+ if (activity.result == null) {
17959
+ throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `Turnkey ${ctx} activityId=${activity.id} COMPLETED but result was missing`);
17960
+ }
17961
+ outcome = "success";
17962
+ return activity.result;
17963
+ } finally {
17964
+ this.emitSignDuration(Date.now() - start, activityType, outcome);
17912
17965
  }
17913
- return activity.result;
17914
17966
  }
17915
17967
  // ── HTTP layer ────────────────────────────────────────────────────────────
17916
17968
  /**
@@ -18178,17 +18230,33 @@ var require_dist2 = __commonJS({
18178
18230
  "use strict";
18179
18231
  Object.defineProperty(exports2, "__esModule", { value: true });
18180
18232
  exports2.signSecp256k1PreimageAndVerify = signSecp256k1PreimageAndVerify;
18233
+ exports2.signSecp256k1TypedDataAndVerify = signSecp256k1TypedDataAndVerify;
18181
18234
  exports2.bytesFromHexOrBytes = bytesFromHexOrBytes;
18182
18235
  var ethers_1 = __require("ethers");
18183
18236
  var km_core_1 = require_dist5();
18237
+ var EIP712_HASH_FUNCTION = "HASH_FUNCTION_NOT_APPLICABLE";
18184
18238
  async function signSecp256k1PreimageAndVerify(client, signWith, preimage, expectedEvmAddrLower) {
18185
18239
  const preimageHex = (0, ethers_1.hexlify)(preimage).slice(2);
18186
- const { r, s, v } = await client.signRawPayload({
18240
+ const parts = await client.signRawPayload({
18187
18241
  signWith,
18188
18242
  payload: preimageHex,
18189
18243
  encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
18190
18244
  hashFunction: "HASH_FUNCTION_KECCAK256"
18191
18245
  });
18246
+ return recoverAndVerify(parts, (0, ethers_1.keccak256)(preimage), expectedEvmAddrLower);
18247
+ }
18248
+ async function signSecp256k1TypedDataAndVerify(client, signWith, domain, types, message, expectedEvmAddrLower) {
18249
+ const payload = ethers_1.TypedDataEncoder.getPayload(domain, types, message);
18250
+ const parts = await client.signRawPayload({
18251
+ signWith,
18252
+ payload: JSON.stringify(payload),
18253
+ encoding: "PAYLOAD_ENCODING_EIP712",
18254
+ hashFunction: EIP712_HASH_FUNCTION
18255
+ });
18256
+ const digestHex = ethers_1.TypedDataEncoder.hash(domain, types, message);
18257
+ return recoverAndVerify(parts, digestHex, expectedEvmAddrLower);
18258
+ }
18259
+ function recoverAndVerify({ r, s, v }, digestHex, expectedEvmAddrLower) {
18192
18260
  let sig;
18193
18261
  try {
18194
18262
  sig = ethers_1.Signature.from({
@@ -18199,7 +18267,6 @@ var require_dist2 = __commonJS({
18199
18267
  } catch (err) {
18200
18268
  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);
18201
18269
  }
18202
- const digestHex = (0, ethers_1.keccak256)(preimage);
18203
18270
  const recovered = (0, ethers_1.recoverAddress)(digestHex, sig).toLowerCase();
18204
18271
  if (recovered !== expectedEvmAddrLower) {
18205
18272
  throw (0, km_core_1.signerTerminal)(km_core_1.SignerErrorCode.TURNKEY_BAD_RESPONSE, `secp256k1 sign-then-recover mismatch: recovered ${recovered}, expected ${expectedEvmAddrLower}`);
@@ -18261,8 +18328,7 @@ ${msgBytes.length}`);
18261
18328
  }
18262
18329
  async signTypedData(domain, types, message) {
18263
18330
  const { signWith, lower } = this.requireAddresses();
18264
- const preimage = (0, ethers_1.getBytes)(ethers_1.TypedDataEncoder.encode(domain, types, message));
18265
- return (0, secp256k1_helpers_1.signSecp256k1PreimageAndVerify)(this.client, signWith, preimage, lower);
18331
+ return (0, secp256k1_helpers_1.signSecp256k1TypedDataAndVerify)(this.client, signWith, domain, types, message, lower);
18266
18332
  }
18267
18333
  async signTransaction(tx) {
18268
18334
  const { signWith, lower } = this.requireAddresses();
@@ -18377,6 +18443,7 @@ ${msgBytes.length}`);
18377
18443
  };
18378
18444
  Object.defineProperty(exports2, "__esModule", { value: true });
18379
18445
  exports2.TurnkeySolanaSigner = void 0;
18446
+ exports2.encodePayload = encodePayload;
18380
18447
  var web3_js_1 = __require("@solana/web3.js");
18381
18448
  var bs58_1 = __importDefault(__require("bs58"));
18382
18449
  var km_core_1 = require_dist5();
@@ -18410,8 +18477,7 @@ ${msgBytes.length}`);
18410
18477
  }
18411
18478
  const { r, s } = await this.client.signRawPayload({
18412
18479
  signWith: this.address,
18413
- payload: Buffer.from(bytes).toString("hex"),
18414
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
18480
+ ...encodePayload(bytes),
18415
18481
  hashFunction: "HASH_FUNCTION_NOT_APPLICABLE"
18416
18482
  });
18417
18483
  const sig = Buffer.concat([Buffer.from(stripHex(r), "hex"), Buffer.from(stripHex(s), "hex")]);
@@ -18452,6 +18518,15 @@ ${msgBytes.length}`);
18452
18518
  }
18453
18519
  };
18454
18520
  exports2.TurnkeySolanaSigner = TurnkeySolanaSigner2;
18521
+ function encodePayload(bytes) {
18522
+ const buf = Buffer.from(bytes);
18523
+ const text = buf.toString("utf8");
18524
+ const textual = buf.every((b) => b >= 32 && b <= 126 || b === 9 || b === 10 || b === 13);
18525
+ if (textual && Buffer.from(text, "utf8").equals(buf)) {
18526
+ return { payload: text, encoding: "PAYLOAD_ENCODING_TEXT_UTF8" };
18527
+ }
18528
+ return { payload: buf.toString("hex"), encoding: "PAYLOAD_ENCODING_HEXADECIMAL" };
18529
+ }
18455
18530
  function stripHex(s) {
18456
18531
  return s.startsWith("0x") ? s.slice(2) : s;
18457
18532
  }
@@ -18517,8 +18592,7 @@ ${messageBytes.length}`);
18517
18592
  }
18518
18593
  async signTypedData(domain, types, message) {
18519
18594
  const { address, evmAddr } = this.requireReady();
18520
- const preimage = (0, ethers_1.getBytes)(ethers_1.TypedDataEncoder.encode(domain, types, message));
18521
- return (0, secp256k1_helpers_1.signSecp256k1PreimageAndVerify)(this.client, address, preimage, evmAddr);
18595
+ return (0, secp256k1_helpers_1.signSecp256k1TypedDataAndVerify)(this.client, address, domain, types, message, evmAddr);
18522
18596
  }
18523
18597
  async signTransaction(tx) {
18524
18598
  const { address, evmAddr } = this.requireReady();
@@ -18889,7 +18963,7 @@ ${messageBytes.length}`);
18889
18963
  policyName: "allowSignRawPayload",
18890
18964
  effect: "EFFECT_ALLOW",
18891
18965
  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']`,
18892
- notes: "Allows signRawPayload with the hash functions the agent runtime uses."
18966
+ 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)."
18893
18967
  };
18894
18968
  }
18895
18969
  function buildDenyApproveToNonAllowedSpenderEvm(args) {
@@ -19526,7 +19600,7 @@ ${detail}`);
19526
19600
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
19527
19601
  };
19528
19602
  Object.defineProperty(exports2, "__esModule", { value: true });
19529
- 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;
19603
+ 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;
19530
19604
  var client_1 = require_client();
19531
19605
  Object.defineProperty(exports2, "TurnkeyClient", { enumerable: true, get: function() {
19532
19606
  return client_1.TurnkeyClient;
@@ -19569,6 +19643,9 @@ ${detail}`);
19569
19643
  Object.defineProperty(exports2, "TurnkeySolanaSigner", { enumerable: true, get: function() {
19570
19644
  return solana_1.TurnkeySolanaSigner;
19571
19645
  } });
19646
+ Object.defineProperty(exports2, "encodePayload", { enumerable: true, get: function() {
19647
+ return solana_1.encodePayload;
19648
+ } });
19572
19649
  var tron_1 = require_tron2();
19573
19650
  Object.defineProperty(exports2, "TurnkeyTronSigner", { enumerable: true, get: function() {
19574
19651
  return tron_1.TurnkeyTronSigner;
@@ -19628,13 +19705,13 @@ ${detail}`);
19628
19705
  };
19629
19706
  switch (chainPrefix) {
19630
19707
  case "eip155": {
19631
- const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "EVM");
19708
+ const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "EVM", message);
19632
19709
  const signer = new import_turnkey_signer.TurnkeyEvmSigner(client, deps);
19633
19710
  await signer.initialize();
19634
19711
  return { signature: await signer.signTypedData(domain, types, typedMessage) };
19635
19712
  }
19636
19713
  case "tron": {
19637
- const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "Tron");
19714
+ const { domain, types, message: typedMessage } = parsePrehash(messagePrehash, "Tron", message);
19638
19715
  const signer = new import_turnkey_signer.TurnkeyTronSigner(client, deps);
19639
19716
  await signer.initialize();
19640
19717
  return { signature: await signer.signTypedData(domain, types, typedMessage) };
@@ -19662,7 +19739,7 @@ ${detail}`);
19662
19739
  }
19663
19740
  return Uint8Array.from(Buffer.from(hex, "hex"));
19664
19741
  }
19665
- function parsePrehash(messagePrehash, label) {
19742
+ function parsePrehash(messagePrehash, label, advertisedDigest) {
19666
19743
  if (!messagePrehash) {
19667
19744
  throw new Error(`Turnkey ${label} signing requires signed_messages[0].message_prehash`);
19668
19745
  }
@@ -19676,15 +19753,24 @@ ${detail}`);
19676
19753
  if (!isObject(parsed) || !isObject(parsed.domain) || !isObject(parsed.types) || !isObject(parsed.message)) {
19677
19754
  throw new Error(`Turnkey ${label} signing: message_prehash must be a JSON object with domain, types and message`);
19678
19755
  }
19679
- return { domain: parsed.domain, types: parsed.types, message: parsed.message };
19756
+ const { EIP712Domain: _derived, ...structTypes } = parsed.types;
19757
+ const prehashDigest = import_ethers5.TypedDataEncoder.hash(parsed.domain, structTypes, parsed.message);
19758
+ if (prehashDigest.toLowerCase() !== advertisedDigest.toLowerCase()) {
19759
+ throw new Error(
19760
+ `Turnkey ${label} signing: message_prehash implies digest ${prehashDigest} but message is ${advertisedDigest}`
19761
+ );
19762
+ }
19763
+ return { domain: parsed.domain, types: structTypes, message: parsed.message };
19680
19764
  }
19681
19765
  var import_turnkey_signer;
19766
+ var import_ethers5;
19682
19767
  var TURNKEY_API_URL;
19683
19768
  var DEFAULTS;
19684
19769
  var init_turnkey_impl = __esm2({
19685
19770
  "src/signers/turnkey-impl.ts"() {
19686
19771
  "use strict";
19687
19772
  import_turnkey_signer = __toESM2(require_dist6());
19773
+ import_ethers5 = __require("ethers");
19688
19774
  TURNKEY_API_URL = "https://api.turnkey.com";
19689
19775
  DEFAULTS = {
19690
19776
  concurrencyLimit: 10,
@@ -20189,8 +20275,26 @@ ${detail}`);
20189
20275
  * 2. Find optimal settlement routes across chains/assets
20190
20276
  * 3. Secure funds in escrow
20191
20277
  * 4. Coordinate with settlement agents for execution
20192
- * 5. Return a real-time result if processing completes within 30 seconds,
20193
- * otherwise return a payment ID for status polling
20278
+ * 5. Return a real-time result if settlement completes inside the synchronous window,
20279
+ * otherwise return a payment ID and status for polling
20280
+ *
20281
+ * **Idempotency.** `request_id` is the idempotency key, scoped to the source account
20282
+ * and originator. Submitting the same `request_id` again returns the ORIGINAL payment
20283
+ * instead of charging a second time, so a client whose request timed out or failed
20284
+ * in transit can safely re-submit.
20285
+ *
20286
+ * Because a re-submission must be admissible on its own, some fields legitimately
20287
+ * differ between attempts and are excluded from the comparison: `quote_deadline` and
20288
+ * `fulfillment_deadline` (both must be in the future, so a later attempt has to move
20289
+ * them), and the `sender_auth` signature they are signed into.
20290
+ *
20291
+ * What must NOT differ is the payment's economics — source account and asset,
20292
+ * destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
20293
+ * payer's own risk cap; lowering it on a retry describes a different payment, not the
20294
+ * same one with less headroom). Re-using one `request_id` for a genuinely different
20295
+ * payment is a client bug that would otherwise be silently collapsed onto the first
20296
+ * payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
20297
+ * distinct `request_id` per payment.
20194
20298
  *
20195
20299
  * @param data The data for the request.
20196
20300
  * @param data.requestBody
@@ -20207,6 +20311,11 @@ ${detail}`);
20207
20311
  400: "Invalid request",
20208
20312
  401: "Signature or sender authorization could not be verified",
20209
20313
  403: "Payment declined during counterparty authorization",
20314
+ 409: `The \`request_id\` has already been used for a payment with different economics
20315
+ (\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
20316
+ made safe, so it must identify one payment; use a distinct \`request_id\` per
20317
+ payment.
20318
+ `,
20210
20319
  413: "Payment request too large to process",
20211
20320
  500: "Internal server error",
20212
20321
  503: "A dependency is temporarily unavailable"
@@ -20539,7 +20648,7 @@ ${detail}`);
20539
20648
  }
20540
20649
  };
20541
20650
  var import_ethers22 = __require("ethers");
20542
- var import_tvm_escrow_encoding = __toESM2(require_dist3());
20651
+ var import_tvm_escrow_encoding = __toESM2(require_dist4());
20543
20652
  var tronAdapter = {
20544
20653
  turnkeyAddressFormat: "ADDRESS_FORMAT_TRON",
20545
20654
  resolveContractOverrides(sourceDefaults) {
@@ -20685,7 +20794,7 @@ ${detail}`);
20685
20794
  }
20686
20795
  return BigInt(Math.floor(Date.now() / 1e3)) - SOLANA_ISSUED_AT_FALLBACK_SKEW_SECS;
20687
20796
  }
20688
- var import_payment_request_sender_auth2 = __toESM2(require_dist4());
20797
+ var import_payment_request_sender_auth2 = __toESM2(require_dist42());
20689
20798
  var $AuthorizationResult = {
20690
20799
  type: "object",
20691
20800
  description: `The result of a counterparty authorization request. Contains either
@@ -21177,11 +21286,18 @@ Required when regulatory compliance or KYC verification is needed for the paymen
21177
21286
  };
21178
21287
  var $PaymentResponse = {
21179
21288
  type: "object",
21180
- description: `Response from submitting a payment request. If processing completes within 30 seconds,
21181
- the response will include a fulfillment_confirmation with full settlement details.
21182
- Otherwise, you'll receive a payment_id that you can use to poll the
21183
- /v1/payments/{paymentId}/status endpoint for updates.
21289
+ description: `Response from submitting a payment request. If settlement completes inside the
21290
+ gateway's synchronous window, the response includes a fulfillment_confirmation with
21291
+ full settlement details. Otherwise you receive a payment_id plus the payment's
21292
+ current \`status\`, and can poll /v1/payments/{paymentId}/status for updates.
21293
+
21294
+ Read \`status\` before deciding what to do next: a \`pending\` payment is in flight and
21295
+ must NOT be re-submitted under a new request_id (that would be a second payment),
21296
+ whereas a \`failed\` or \`cancelled\` one is terminal \u2014 re-submitting it under the SAME
21297
+ request_id resolves to the same terminal payment, so a genuine re-attempt needs a
21298
+ new request_id.
21184
21299
  `,
21300
+ required: ["payment_id", "status", "idempotent_replay"],
21185
21301
  properties: {
21186
21302
  payment_id: {
21187
21303
  type: "string",
@@ -21189,8 +21305,60 @@ Otherwise, you'll receive a payment_id that you can use to poll the
21189
21305
  `,
21190
21306
  example: "550e8400-e29b-41d4-a716-446655440000"
21191
21307
  },
21308
+ status: {
21309
+ type: "string",
21310
+ enum: ["pending", "completed", "failed", "cancelled"],
21311
+ description: `Current status of the payment, with the same meaning as on
21312
+ /v1/payments/{paymentId}/status:
21313
+ - \`pending\`: accepted and being processed
21314
+ - \`completed\`: successfully delivered
21315
+ - \`failed\`: failed (funds returned if escrowed)
21316
+ - \`cancelled\`: cancelled before completion
21317
+
21318
+ Present on every response. \`completed\` is always accompanied by
21319
+ fulfillment_confirmation; \`failed\` is accompanied by \`error\` where a reason has
21320
+ been recorded.
21321
+ `
21322
+ },
21323
+ idempotent_replay: {
21324
+ type: "boolean",
21325
+ description: `Whether this response describes a payment that already existed, rather than one
21326
+ created by this request. \`true\` means the request_id had been used before and you
21327
+ are being handed the ORIGINAL payment \u2014 no second payment was created and nothing
21328
+ additional was charged.
21329
+
21330
+ **Re-attempting a payment.** Re-submit the same request and you get the same payment
21331
+ back. You may re-send the original bytes unchanged: the deadlines will have expired by
21332
+ then \u2014 \`quote_deadline\` bounds the auction and is short, while this endpoint may hold a
21333
+ submission for up to 30 seconds \u2014 and an expired deadline on a re-submission is not an
21334
+ error, because the request is resolved onto the payment that already exists rather than
21335
+ used to create one.
21336
+
21337
+ Re-signing with fresh deadlines is equally fine and returns the same payment. What you
21338
+ must NOT change is the request_id, and what you must not change under it is the
21339
+ payment's economics \u2014 source, destination, fulfillment_amount, max_source_amount \u2014 which
21340
+ are rejected with 409 IDEMPOTENCY_TERMS_MISMATCH. Deadlines and the signature are
21341
+ exempt precisely so that a re-attempt is possible.
21342
+
21343
+ A genuinely new payment needs a NEW request_id. In particular a \`failed\` payment is
21344
+ terminal: re-submitting its request_id keeps returning that dead payment, so recovering
21345
+ means starting a new one under a new key.
21346
+
21347
+ Present on every response, \`false\` for a newly created payment. A response without
21348
+ the field is from a gateway predating it, not a newly created payment.
21349
+
21350
+ This is the field to read when reconciling a suspected double charge: two
21351
+ submissions where the second reports \`true\` are one payment, whereas two both
21352
+ reporting \`false\` are two. It says nothing about how far the payment has got \u2014
21353
+ read \`status\` for that.
21354
+ `,
21355
+ example: false
21356
+ },
21192
21357
  fulfillment_confirmation: {
21193
21358
  "$ref": "#/components/schemas/FulfillmentConfirmation"
21359
+ },
21360
+ error: {
21361
+ "$ref": "#/components/schemas/PaymentError"
21194
21362
  }
21195
21363
  }
21196
21364
  };
@@ -22280,12 +22448,12 @@ payment-status endpoint.
22280
22448
  function generateSolanaNonce() {
22281
22449
  return (0, import_solana_escrow_encoding.generateNonce)();
22282
22450
  }
22283
- var import_payment_request_sender_auth = __toESM2(require_dist4());
22451
+ var import_payment_request_sender_auth3 = __toESM2(require_dist42());
22284
22452
  var RawSenderSigner = class {
22285
22453
  constructor(network, privateKey) {
22286
22454
  const adapter = getChainAdapter(network);
22287
22455
  adapter.validatePrivateKey?.(privateKey);
22288
- this.inner = (0, import_payment_request_sender_auth.localKeySigner)(network, adapter.formatPrivateKey(privateKey));
22456
+ this.inner = (0, import_payment_request_sender_auth3.localKeySigner)(network, adapter.formatPrivateKey(privateKey));
22289
22457
  }
22290
22458
  sign(signedMessage) {
22291
22459
  return this.inner.sign(signedMessage);
@@ -22679,8 +22847,9 @@ payment-status endpoint.
22679
22847
 
22680
22848
  // src/client.ts
22681
22849
  var import_evm_escrow_encoding = __toESM(require_dist(), 1);
22682
- var import_payment_gateway_client = __toESM(require_dist2(), 1);
22683
- import { keccak256, toUtf8Bytes, Contract, MaxUint256 } from "ethers";
22850
+ var import_payment_request_sender_auth = __toESM(require_dist2(), 1);
22851
+ var import_payment_gateway_client = __toESM(require_dist3(), 1);
22852
+ import { Contract, MaxUint256 } from "ethers";
22684
22853
  import { Credential, Method } from "mppx";
22685
22854
  function registerClient(config) {
22686
22855
  const now = config.now ?? (() => Date.now());
@@ -22693,14 +22862,16 @@ function registerClient(config) {
22693
22862
  const nowMs = now();
22694
22863
  if (!(extra.quoteDeadlineSeconds > 0 && extra.quoteDeadlineSeconds < extra.fulfillmentDeadlineSeconds)) {
22695
22864
  throw new Error(
22696
- `atum-escrow: deadline ordering violated (now < quote_deadline < fulfillment_deadline); quoteDeadlineSeconds=${extra.quoteDeadlineSeconds}, fulfillmentDeadlineSeconds=${extra.fulfillmentDeadlineSeconds}`
22865
+ `atum-escrow: the challenge's deadline budgets cannot produce a settleable payment. quoteDeadlineSeconds must be above zero and below fulfillmentDeadlineSeconds, because quote collection has to close before settlement is due. Got quoteDeadlineSeconds=${extra.quoteDeadlineSeconds}, fulfillmentDeadlineSeconds=${extra.fulfillmentDeadlineSeconds}. These come from the corridor the resource server offered, so this is a merchant-side configuration problem rather than anything a payer can correct.`
22697
22866
  );
22698
22867
  }
22699
- const anchorAccount = namespace === "eip155" ? account.toLowerCase() : account;
22700
- const idempotencyAnchor = keccak256(
22701
- toUtf8Bytes(`atum-escrow:${challenge.id}:${anchorAccount}`)
22702
- );
22703
- const requestId = `req_mpp_${idempotencyAnchor.slice(2, 26)}`;
22868
+ const intentId = intentIdOf(challenge);
22869
+ if (intentId === void 0) {
22870
+ throw new Error(
22871
+ `atum-escrow: the challenge carries no '${INTENT_ID_META_KEY}' metadata, so this payment cannot be made retry-safe and is refused. The merchant must stamp a stable per-purchase identifier into the challenge metadata (see buildChargeChallenge); without it a retry would be charged as a second payment.`
22872
+ );
22873
+ }
22874
+ const requestId = chargeRequestId(intentId, account);
22704
22875
  const sourceDefaults = chainDefaultsFromExtra(extra, source.network);
22705
22876
  const sourceAssetId = assetIdentifier(source.network, source.asset);
22706
22877
  const isSolana = namespace === "solana";
@@ -22733,7 +22904,12 @@ function registerClient(config) {
22733
22904
  solanaRpcUrl: "atum-escrow:offline",
22734
22905
  solanaClockReader: solanaIssuedAt != null ? async () => BigInt(solanaIssuedAt) : config.solanaClockReader ?? (async () => BigInt(Math.floor(nowMs / 1e3)))
22735
22906
  } : {
22736
- nonce: BigInt(idempotencyAnchor),
22907
+ // No explicit nonce: `preparePaymentRequest` derives it from
22908
+ // (request_id, depositor) via the shared sender_auth SDK — the same
22909
+ // Go-parity-locked derivation every consumer uses. Since request_id is itself
22910
+ // anchored on the intentId above, a rebuild reproduces the same single-use
22911
+ // permit. Overriding it here would fork a second nonce recipe for no gain.
22912
+ //
22737
22913
  // Keep the deposit deadline at least as late as fulfillment. pgc converts this
22738
22914
  // relative budget to an absolute permit deadline against the same clock; round
22739
22915
  // the budget up and add a second so it never truncates below fulfillment_deadline.
@@ -22746,7 +22922,7 @@ function registerClient(config) {
22746
22922
  return Credential.serialize({
22747
22923
  challenge,
22748
22924
  payload: { paymentRequest },
22749
- source: `did:pkh:${source.network}:${anchorAccount}`
22925
+ source: `did:pkh:${source.network}:${(0, import_payment_request_sender_auth.canonicalDepositor)(account)}`
22750
22926
  });
22751
22927
  }
22752
22928
  });
@@ -22758,14 +22934,8 @@ function buildSenderSigner(network, signer, account) {
22758
22934
  if (isSenderSigner(signer)) {
22759
22935
  return signer;
22760
22936
  }
22761
- if (signer.provider === "turnkey") {
22762
- throw new Error(
22763
- "atum-escrow: construct a Turnkey SenderSigner with the payment-gateway client and pass it as `signer`"
22764
- );
22765
- }
22766
22937
  return (0, import_payment_gateway_client.createSenderSigner)(network, {
22767
- provider: "raw",
22768
- privateKey: signer.privateKey,
22938
+ ...signer,
22769
22939
  pinnedAddress: signer.pinnedAddress ?? account
22770
22940
  });
22771
22941
  }