@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/CHANGELOG.md +49 -0
- package/README.md +214 -58
- package/THIRD-PARTY-NOTICES.txt +45 -0
- package/dist/cli/payment-status.js +157 -46
- package/dist/cli/quote-status.js +117 -45
- package/dist/cli/request-quote.js +118 -46
- package/dist/cli/send-payment.js +332 -94
- package/dist/index.d.ts +180 -29
- package/dist/index.js +268 -71
- package/docs/idempotency.md +142 -0
- package/package.json +14 -4
- /package/{LICENSE.md → LICENSE} +0 -0
package/dist/cli/send-payment.js
CHANGED
|
@@ -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;
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
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
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
@@ -4646,7 +4733,7 @@ var Interceptors = class {
|
|
|
4646
4733
|
}
|
|
4647
4734
|
};
|
|
4648
4735
|
var OpenAPI = {
|
|
4649
|
-
BASE: "https://
|
|
4736
|
+
BASE: "https://payment-gw.production-mainnet.atum.xyz",
|
|
4650
4737
|
CREDENTIALS: "include",
|
|
4651
4738
|
ENCODE_PATH: void 0,
|
|
4652
4739
|
HEADERS: void 0,
|
|
@@ -4663,8 +4750,8 @@ var OpenAPI = {
|
|
|
4663
4750
|
|
|
4664
4751
|
// src/generated/core/BaseHttpRequest.ts
|
|
4665
4752
|
var BaseHttpRequest = class {
|
|
4666
|
-
constructor(
|
|
4667
|
-
this.config =
|
|
4753
|
+
constructor(config2) {
|
|
4754
|
+
this.config = config2;
|
|
4668
4755
|
}
|
|
4669
4756
|
};
|
|
4670
4757
|
|
|
@@ -4815,15 +4902,15 @@ var getQueryString = (params) => {
|
|
|
4815
4902
|
Object.entries(params).forEach(([key, value]) => encodePair(key, value));
|
|
4816
4903
|
return qs.length ? `?${qs.join("&")}` : "";
|
|
4817
4904
|
};
|
|
4818
|
-
var getUrl = (
|
|
4819
|
-
const encoder =
|
|
4820
|
-
const path = options.url.replace("{api-version}",
|
|
4905
|
+
var getUrl = (config2, options) => {
|
|
4906
|
+
const encoder = config2.ENCODE_PATH || encodeURI;
|
|
4907
|
+
const path = options.url.replace("{api-version}", config2.VERSION).replace(/{(.*?)}/g, (substring, group) => {
|
|
4821
4908
|
if (options.path?.hasOwnProperty(group)) {
|
|
4822
4909
|
return encoder(String(options.path[group]));
|
|
4823
4910
|
}
|
|
4824
4911
|
return substring;
|
|
4825
4912
|
});
|
|
4826
|
-
const url =
|
|
4913
|
+
const url = config2.BASE + path;
|
|
4827
4914
|
return options.query ? url + getQueryString(options.query) : url;
|
|
4828
4915
|
};
|
|
4829
4916
|
var getFormData = (options) => {
|
|
@@ -4853,12 +4940,12 @@ var resolve = async (options, resolver) => {
|
|
|
4853
4940
|
}
|
|
4854
4941
|
return resolver;
|
|
4855
4942
|
};
|
|
4856
|
-
var getHeaders = async (
|
|
4943
|
+
var getHeaders = async (config2, options) => {
|
|
4857
4944
|
const [token, username, password, additionalHeaders] = await Promise.all([
|
|
4858
|
-
resolve(options,
|
|
4859
|
-
resolve(options,
|
|
4860
|
-
resolve(options,
|
|
4861
|
-
resolve(options,
|
|
4945
|
+
resolve(options, config2.TOKEN),
|
|
4946
|
+
resolve(options, config2.USERNAME),
|
|
4947
|
+
resolve(options, config2.PASSWORD),
|
|
4948
|
+
resolve(options, config2.HEADERS)
|
|
4862
4949
|
]);
|
|
4863
4950
|
const headers = Object.entries({
|
|
4864
4951
|
Accept: "application/json",
|
|
@@ -4898,7 +4985,7 @@ var getRequestBody = (options) => {
|
|
|
4898
4985
|
}
|
|
4899
4986
|
return void 0;
|
|
4900
4987
|
};
|
|
4901
|
-
var sendRequest = async (
|
|
4988
|
+
var sendRequest = async (config2, options, url, body, formData, headers, onCancel, axiosClient) => {
|
|
4902
4989
|
const controller = new AbortController();
|
|
4903
4990
|
let requestConfig = {
|
|
4904
4991
|
data: body ?? formData,
|
|
@@ -4906,10 +4993,10 @@ var sendRequest = async (config, options, url, body, formData, headers, onCancel
|
|
|
4906
4993
|
method: options.method,
|
|
4907
4994
|
signal: controller.signal,
|
|
4908
4995
|
url,
|
|
4909
|
-
withCredentials:
|
|
4996
|
+
withCredentials: config2.WITH_CREDENTIALS
|
|
4910
4997
|
};
|
|
4911
4998
|
onCancel(() => controller.abort());
|
|
4912
|
-
for (const fn of
|
|
4999
|
+
for (const fn of config2.interceptors.request._fns) {
|
|
4913
5000
|
requestConfig = await fn(requestConfig);
|
|
4914
5001
|
}
|
|
4915
5002
|
try {
|
|
@@ -5002,16 +5089,16 @@ var catchErrorCodes = (options, result) => {
|
|
|
5002
5089
|
);
|
|
5003
5090
|
}
|
|
5004
5091
|
};
|
|
5005
|
-
var request = (
|
|
5092
|
+
var request = (config2, options, axiosClient = import_axios.default) => {
|
|
5006
5093
|
return new CancelablePromise(async (resolve2, reject, onCancel) => {
|
|
5007
5094
|
try {
|
|
5008
|
-
const url = getUrl(
|
|
5095
|
+
const url = getUrl(config2, options);
|
|
5009
5096
|
const formData = getFormData(options);
|
|
5010
5097
|
const body = getRequestBody(options);
|
|
5011
|
-
const headers = await getHeaders(
|
|
5098
|
+
const headers = await getHeaders(config2, options);
|
|
5012
5099
|
if (!onCancel.isCancelled) {
|
|
5013
|
-
let response = await sendRequest(
|
|
5014
|
-
for (const fn of
|
|
5100
|
+
let response = await sendRequest(config2, options, url, body, formData, headers, onCancel, axiosClient);
|
|
5101
|
+
for (const fn of config2.interceptors.response._fns) {
|
|
5015
5102
|
response = await fn(response);
|
|
5016
5103
|
}
|
|
5017
5104
|
const responseBody = getResponseBody(response);
|
|
@@ -5034,8 +5121,8 @@ var request = (config, options, axiosClient = import_axios.default) => {
|
|
|
5034
5121
|
|
|
5035
5122
|
// src/generated/core/AxiosHttpRequest.ts
|
|
5036
5123
|
var AxiosHttpRequest = class extends BaseHttpRequest {
|
|
5037
|
-
constructor(
|
|
5038
|
-
super(
|
|
5124
|
+
constructor(config2) {
|
|
5125
|
+
super(config2);
|
|
5039
5126
|
}
|
|
5040
5127
|
/**
|
|
5041
5128
|
* Request method
|
|
@@ -5062,8 +5149,26 @@ var PaymentsService = class {
|
|
|
5062
5149
|
* 2. Find optimal settlement routes across chains/assets
|
|
5063
5150
|
* 3. Secure funds in escrow
|
|
5064
5151
|
* 4. Coordinate with settlement agents for execution
|
|
5065
|
-
* 5. Return a real-time result if
|
|
5066
|
-
* otherwise return a payment ID
|
|
5152
|
+
* 5. Return a real-time result if settlement completes inside the synchronous window,
|
|
5153
|
+
* otherwise return a payment ID and status for polling
|
|
5154
|
+
*
|
|
5155
|
+
* **Idempotency.** `request_id` is the idempotency key, scoped to the source account
|
|
5156
|
+
* and originator. Submitting the same `request_id` again returns the ORIGINAL payment
|
|
5157
|
+
* instead of charging a second time, so a client whose request timed out or failed
|
|
5158
|
+
* in transit can safely re-submit.
|
|
5159
|
+
*
|
|
5160
|
+
* Because a re-submission must be admissible on its own, some fields legitimately
|
|
5161
|
+
* differ between attempts and are excluded from the comparison: `quote_deadline` and
|
|
5162
|
+
* `fulfillment_deadline` (both must be in the future, so a later attempt has to move
|
|
5163
|
+
* them), and the `sender_auth` signature they are signed into.
|
|
5164
|
+
*
|
|
5165
|
+
* What must NOT differ is the payment's economics — source account and asset,
|
|
5166
|
+
* destination accounts and assets, `fulfillment_amount`, and `max_source_amount` (the
|
|
5167
|
+
* payer's own risk cap; lowering it on a retry describes a different payment, not the
|
|
5168
|
+
* same one with less headroom). Re-using one `request_id` for a genuinely different
|
|
5169
|
+
* payment is a client bug that would otherwise be silently collapsed onto the first
|
|
5170
|
+
* payment, so it is rejected with `409` and `IDEMPOTENCY_TERMS_MISMATCH`. Use a
|
|
5171
|
+
* distinct `request_id` per payment.
|
|
5067
5172
|
*
|
|
5068
5173
|
* @param data The data for the request.
|
|
5069
5174
|
* @param data.requestBody
|
|
@@ -5080,6 +5185,11 @@ var PaymentsService = class {
|
|
|
5080
5185
|
400: "Invalid request",
|
|
5081
5186
|
401: "Signature or sender authorization could not be verified",
|
|
5082
5187
|
403: "Payment declined during counterparty authorization",
|
|
5188
|
+
409: `The \`request_id\` has already been used for a payment with different economics
|
|
5189
|
+
(\`IDEMPOTENCY_TERMS_MISMATCH\`). Re-submitting one \`request_id\` is how a retry is
|
|
5190
|
+
made safe, so it must identify one payment; use a distinct \`request_id\` per
|
|
5191
|
+
payment.
|
|
5192
|
+
`,
|
|
5083
5193
|
413: "Payment request too large to process",
|
|
5084
5194
|
500: "Internal server error",
|
|
5085
5195
|
503: "A dependency is temporarily unavailable"
|
|
@@ -5367,20 +5477,20 @@ var HealthService = class {
|
|
|
5367
5477
|
|
|
5368
5478
|
// src/generated/PaymentGatewayClient.ts
|
|
5369
5479
|
var PaymentGatewayClient = class {
|
|
5370
|
-
constructor(
|
|
5480
|
+
constructor(config2, HttpRequest = AxiosHttpRequest) {
|
|
5371
5481
|
this.request = new HttpRequest({
|
|
5372
|
-
BASE:
|
|
5373
|
-
VERSION:
|
|
5374
|
-
WITH_CREDENTIALS:
|
|
5375
|
-
CREDENTIALS:
|
|
5376
|
-
TOKEN:
|
|
5377
|
-
USERNAME:
|
|
5378
|
-
PASSWORD:
|
|
5379
|
-
HEADERS:
|
|
5380
|
-
ENCODE_PATH:
|
|
5482
|
+
BASE: config2?.BASE ?? "https://payment-gw.production-mainnet.atum.xyz",
|
|
5483
|
+
VERSION: config2?.VERSION ?? "1.0.0",
|
|
5484
|
+
WITH_CREDENTIALS: config2?.WITH_CREDENTIALS ?? false,
|
|
5485
|
+
CREDENTIALS: config2?.CREDENTIALS ?? "include",
|
|
5486
|
+
TOKEN: config2?.TOKEN,
|
|
5487
|
+
USERNAME: config2?.USERNAME,
|
|
5488
|
+
PASSWORD: config2?.PASSWORD,
|
|
5489
|
+
HEADERS: config2?.HEADERS,
|
|
5490
|
+
ENCODE_PATH: config2?.ENCODE_PATH,
|
|
5381
5491
|
interceptors: {
|
|
5382
|
-
request:
|
|
5383
|
-
response:
|
|
5492
|
+
request: config2?.interceptors?.request ?? new Interceptors(),
|
|
5493
|
+
response: config2?.interceptors?.response ?? new Interceptors()
|
|
5384
5494
|
}
|
|
5385
5495
|
});
|
|
5386
5496
|
this.authorizations = new AuthorizationsService(this.request);
|
|
@@ -5647,7 +5757,8 @@ function createSenderSigner(sourceChainId, opts) {
|
|
|
5647
5757
|
|
|
5648
5758
|
// src/index.ts
|
|
5649
5759
|
var PAYMENT_REQUEST_VERSION = "1.0";
|
|
5650
|
-
var defaultGateway = "https://payment-
|
|
5760
|
+
var defaultGateway = "https://payment-gw.production-testnet.atum.xyz";
|
|
5761
|
+
var OpenAPI2 = { ...OpenAPI, BASE: defaultGateway };
|
|
5651
5762
|
var DEFAULT_QUOTE_DEADLINE_SECONDS = 5;
|
|
5652
5763
|
var DEFAULT_FULFILLMENT_DEADLINE_SECONDS = 3600;
|
|
5653
5764
|
var DEFAULT_PAYMENT_QUOTE_DEADLINE_SECONDS = 10;
|
|
@@ -5735,9 +5846,11 @@ async function assemblePaymentRequest(params) {
|
|
|
5735
5846
|
};
|
|
5736
5847
|
}
|
|
5737
5848
|
var PaymentGatewayClient2 = class extends PaymentGatewayClient {
|
|
5738
|
-
constructor(
|
|
5739
|
-
|
|
5740
|
-
|
|
5849
|
+
constructor(config2, HttpRequest = AxiosHttpRequest) {
|
|
5850
|
+
const envBase = (typeof process !== "undefined" ? process.env.GATEWAY_URL : void 0)?.trim();
|
|
5851
|
+
const base = config2?.BASE ?? (envBase || defaultGateway);
|
|
5852
|
+
super({ ...config2, BASE: base }, HttpRequest);
|
|
5853
|
+
this.baseUrl = base;
|
|
5741
5854
|
}
|
|
5742
5855
|
/**
|
|
5743
5856
|
* Fetches chain defaults from the payment gateway.
|
|
@@ -5962,8 +6075,87 @@ function parseSeconds(optionName) {
|
|
|
5962
6075
|
};
|
|
5963
6076
|
}
|
|
5964
6077
|
|
|
6078
|
+
// src/version.ts
|
|
6079
|
+
var PACKAGE_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
6080
|
+
|
|
6081
|
+
// src/cli/payment-outcome.ts
|
|
6082
|
+
var EXIT_COMPLETED = 0;
|
|
6083
|
+
var EXIT_ERROR = 1;
|
|
6084
|
+
var EXIT_PENDING = 2;
|
|
6085
|
+
var EXIT_TERMINAL_FAILURE = 3;
|
|
6086
|
+
function isTerminalStatus(status) {
|
|
6087
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
6088
|
+
}
|
|
6089
|
+
function describePaymentOutcome(input) {
|
|
6090
|
+
const { status, paymentId, requestId, idempotentReplay, errorCode } = input;
|
|
6091
|
+
const payment = paymentId ? `payment ${paymentId}` : "the payment";
|
|
6092
|
+
const replay = idempotentReplay ? " This is the payment your earlier attempt created \u2014 nothing additional was charged." : "";
|
|
6093
|
+
if (status === "completed") {
|
|
6094
|
+
return {
|
|
6095
|
+
exitCode: EXIT_COMPLETED,
|
|
6096
|
+
summary: `completed: ${payment} settled.${replay}`
|
|
6097
|
+
};
|
|
6098
|
+
}
|
|
6099
|
+
if (status === "failed" || status === "cancelled") {
|
|
6100
|
+
const reason = errorCode ? ` (${errorCode})` : "";
|
|
6101
|
+
return {
|
|
6102
|
+
exitCode: EXIT_TERMINAL_FAILURE,
|
|
6103
|
+
summary: `${status}: ${payment} ended without settling${reason}. This is final \u2014 re-submitting ${requestId ? `request id ${requestId}` : "the same request id"} returns this same failed payment, so a fresh attempt needs a NEW request id.`
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
const resubmit = requestId ? `request id ${requestId}` : "the same request id";
|
|
6107
|
+
return {
|
|
6108
|
+
exitCode: EXIT_PENDING,
|
|
6109
|
+
summary: `pending: ${payment} was accepted and is still settling.${replay} To collect the result, keep checking its status, or re-submit ${resubmit}. Do not start a new payment under a different request id \u2014 that would pay twice.`
|
|
6110
|
+
};
|
|
6111
|
+
}
|
|
6112
|
+
|
|
6113
|
+
// src/cli/wait-for-terminal.ts
|
|
6114
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
6115
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
6116
|
+
async function waitForTerminalStatus(params) {
|
|
6117
|
+
const {
|
|
6118
|
+
fetchStatus,
|
|
6119
|
+
budgetMs,
|
|
6120
|
+
intervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
6121
|
+
now = () => Date.now(),
|
|
6122
|
+
sleep = defaultSleep,
|
|
6123
|
+
onError
|
|
6124
|
+
} = params;
|
|
6125
|
+
const deadline = now() + budgetMs;
|
|
6126
|
+
let snapshot;
|
|
6127
|
+
let errorCount = 0;
|
|
6128
|
+
while (now() < deadline) {
|
|
6129
|
+
const remaining = deadline - now();
|
|
6130
|
+
await sleep(Math.min(intervalMs, remaining));
|
|
6131
|
+
try {
|
|
6132
|
+
snapshot = await fetchStatus();
|
|
6133
|
+
} catch (error) {
|
|
6134
|
+
errorCount += 1;
|
|
6135
|
+
onError?.(error);
|
|
6136
|
+
continue;
|
|
6137
|
+
}
|
|
6138
|
+
if (isTerminalStatus(snapshot.status)) {
|
|
6139
|
+
return { snapshot, timedOut: false, errorCount };
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
return { snapshot, timedOut: true, errorCount };
|
|
6143
|
+
}
|
|
6144
|
+
function defaultSleep(ms) {
|
|
6145
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
6146
|
+
}
|
|
6147
|
+
|
|
5965
6148
|
// src/cli/send-payment.ts
|
|
5966
6149
|
var readline = __toESM(require("readline"));
|
|
6150
|
+
|
|
6151
|
+
// src/cli/load-env.ts
|
|
6152
|
+
var import_dotenv = require("dotenv");
|
|
6153
|
+
function loadCliEnv() {
|
|
6154
|
+
(0, import_dotenv.config)({ override: false, quiet: true });
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
// src/cli/send-payment.ts
|
|
6158
|
+
loadCliEnv();
|
|
5967
6159
|
function collect(value, previous) {
|
|
5968
6160
|
return previous.concat([value]);
|
|
5969
6161
|
}
|
|
@@ -5983,7 +6175,7 @@ function loadTurnkeyConfigFromEnv() {
|
|
|
5983
6175
|
};
|
|
5984
6176
|
}
|
|
5985
6177
|
var program = new import_commander.Command();
|
|
5986
|
-
program.name("send-payment").description("Send a cross-chain payment through Atum Payment Gateway").version(
|
|
6178
|
+
program.name("send-payment").description("Send a cross-chain payment through Atum Payment Gateway").version(PACKAGE_VERSION).argument("<sender>", "Depositor address (e.g., 0xF0D0...)").argument("<amount>", "Amount to send (in smallest units, e.g., 100000 for 0.10 USDC)").argument("<sourceAsset>", "Source asset in CAIP-19 format").argument("<receiver>", "Destination account address").argument("<destinationAsset>", "Destination asset in CAIP-19 format").option("--gateway <url>", "Payment gateway URL (overrides GATEWAY_URL env; default: production-testnet)").option("--request-id <id>", "Idempotency key; seeds the on-chain deposit nonce. Re-run with the SAME id to re-attempt one payment \u2014 it resolves to the original instead of paying twice. Omitted, an id is generated and printed, which makes every bare re-run a SEPARATE payment (a second charge).").option("--request-id-prefix <prefix>", "Custom request ID prefix (default: pmt-gw-cli)").option("--quote-deadline-seconds <n>", `Quote collection window in seconds (default: ${DEFAULT_PAYMENT_QUOTE_DEADLINE_SECONDS})`, parseSeconds("--quote-deadline-seconds")).option("--fulfillment-deadline-seconds <n>", `Settlement horizon in seconds (default: ${DEFAULT_FULFILLMENT_DEADLINE_SECONDS})`, parseSeconds("--fulfillment-deadline-seconds")).option("--whitelistSettler <did>", "Settler DID to whitelist (can be specified multiple times)", collect, []).option("--provider <raw|turnkey>", "Sender signing provider", "raw").option("--wait", "After submitting, keep checking the payment until it settles or fails instead of reporting it as still pending").option("--wait-seconds <n>", `How long --wait keeps checking (default: ${DEFAULT_WAIT_SECONDS}). A convenience bound only \u2014 giving up does not affect the payment`, parseSeconds("--wait-seconds")).option("--solana-rpc-url <url>", "Solana RPC used to read the cluster clock for issued_at (falls back to the SOLANA_RPC_URL env var, then the public endpoint for the cluster)").addHelpText("after", `
|
|
5987
6179
|
CAIP-19 Examples:
|
|
5988
6180
|
EVM ERC20: eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831
|
|
5989
6181
|
Tron TRC20: tron:shasta/trc20:TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs
|
|
@@ -5998,6 +6190,23 @@ Signing (default --provider raw):
|
|
|
5998
6190
|
Output:
|
|
5999
6191
|
PaymentRequest JSON \u2192 stderr (for debugging/logging)
|
|
6000
6192
|
PaymentResponse JSON \u2192 stdout (for parsing/automation)
|
|
6193
|
+
Outcome summary \u2192 stderr
|
|
6194
|
+
|
|
6195
|
+
With --wait the JSON is printed once the wait ends, reporting the payment's state
|
|
6196
|
+
as of the last check rather than as of the submit.
|
|
6197
|
+
|
|
6198
|
+
Exit codes:
|
|
6199
|
+
0 completed \u2014 settled
|
|
6200
|
+
2 pending \u2014 accepted, no result yet; follow it with payment-status <payment_id>,
|
|
6201
|
+
or use --wait
|
|
6202
|
+
3 failed \u2014 terminal; a fresh attempt needs a NEW --request-id
|
|
6203
|
+
1 no outcome obtained (rejected, unreachable gateway, bad arguments)
|
|
6204
|
+
|
|
6205
|
+
So \`send-payment ... && <next step>\` runs the next step only for a settled payment.
|
|
6206
|
+
|
|
6207
|
+
Exit 1 means unknown, not "nothing happened" \u2014 a request that timed out may still
|
|
6208
|
+
have been accepted, and you may have no payment_id to look up. Re-run with the same
|
|
6209
|
+
--request-id, which resolves to the payment if one was created and creates it if not.
|
|
6001
6210
|
|
|
6002
6211
|
After submission, you can check payment status at eg http://PAYMENT_GW/v1/payments/PAYMENT_ID/status
|
|
6003
6212
|
|
|
@@ -6052,8 +6261,37 @@ TPUqJPASUn1zLvbLBgRZ5pBYrx7WSe5ahp on Shasta)`).action(async (sender, amount, so
|
|
|
6052
6261
|
const response = await client.payments.submitPayment({
|
|
6053
6262
|
requestBody: paymentRequest
|
|
6054
6263
|
});
|
|
6055
|
-
|
|
6056
|
-
|
|
6264
|
+
let reported = response;
|
|
6265
|
+
if (options.wait && response.payment_id && !isTerminalStatus(response.status)) {
|
|
6266
|
+
const waitSeconds = options.waitSeconds ?? DEFAULT_WAIT_SECONDS;
|
|
6267
|
+
console.error(`waiting up to ${waitSeconds}s for ${response.payment_id} to settle...`);
|
|
6268
|
+
const paymentId = response.payment_id;
|
|
6269
|
+
const waited = await waitForTerminalStatus({
|
|
6270
|
+
budgetMs: waitSeconds * 1e3,
|
|
6271
|
+
fetchStatus: () => client.payments.getPaymentStatus({ paymentId }),
|
|
6272
|
+
// A blip mid-settlement is the worst moment to give up, so report and keep checking.
|
|
6273
|
+
onError: (err) => console.error(`status check failed, still waiting: ${err instanceof Error ? err.message : String(err)}`)
|
|
6274
|
+
});
|
|
6275
|
+
if (waited.snapshot) {
|
|
6276
|
+
reported = { ...reported, ...waited.snapshot };
|
|
6277
|
+
}
|
|
6278
|
+
if (waited.timedOut) {
|
|
6279
|
+
console.error(`still not settled after ${waitSeconds}s \u2014 reporting it as pending.`);
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
console.log(JSON.stringify(reported, null, 2));
|
|
6283
|
+
const outcome = {
|
|
6284
|
+
status: reported.status,
|
|
6285
|
+
paymentId: reported.payment_id,
|
|
6286
|
+
requestId,
|
|
6287
|
+
idempotentReplay: reported.idempotent_replay,
|
|
6288
|
+
// Present when the gateway has recorded a reason — which includes the case where this
|
|
6289
|
+
// submission was deduplicated onto a payment that had already failed.
|
|
6290
|
+
errorCode: reported.error?.code
|
|
6291
|
+
};
|
|
6292
|
+
const { exitCode, summary } = describePaymentOutcome(outcome);
|
|
6293
|
+
console.error(summary);
|
|
6294
|
+
process.exit(exitCode);
|
|
6057
6295
|
} catch (error) {
|
|
6058
6296
|
if (error instanceof ApiError && error.body) {
|
|
6059
6297
|
console.log(JSON.stringify(error.body, null, 2));
|
|
@@ -6067,7 +6305,7 @@ TPUqJPASUn1zLvbLBgRZ5pBYrx7WSe5ahp on Shasta)`).action(async (sender, amount, so
|
|
|
6067
6305
|
if (requestId) {
|
|
6068
6306
|
console.error(`request_id: ${requestId} (re-run with --request-id ${requestId} to retry this same payment)`);
|
|
6069
6307
|
}
|
|
6070
|
-
process.exit(
|
|
6308
|
+
process.exit(EXIT_ERROR);
|
|
6071
6309
|
}
|
|
6072
6310
|
});
|
|
6073
6311
|
async function promptPrivateKey() {
|