@cloudpayx/x402-pay 0.2.4 → 0.2.6
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/AGENTS.md +53 -0
- package/README.md +62 -4
- package/README.pre-pay-earn-release.bak +96 -0
- package/docs/API.md +76 -0
- package/docs/QUICKSTART.md +72 -0
- package/docs/agent-manifest.json +51 -0
- package/docs/agent-onboarding.md +236 -0
- package/examples/earn-http-server.mjs +60 -0
- package/package.json +12 -3
- package/scripts/durable-purchase-lifecycle.mjs +34 -2
- package/scripts/persistent-spending-budget.mjs +101 -1
- package/scripts/public-node.mjs +2 -0
- package/scripts/secure-node-purchase.mjs +3 -1
- package/scripts/seller-earn.mjs +67 -0
- package/scripts/seller-http.mjs +287 -0
- package/scripts/seller-ledger-verifier.mjs +228 -0
- package/scripts/seller-node.mjs +180 -0
- package/scripts/seller-proof-guard.mjs +43 -0
- package/scripts/seller-transaction-guard.mjs +39 -0
- package/types/public-node.d.ts +19 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Client, decode } from "xrpl";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { MAINNET_LEDGER_URL } from "./trusted-xrpl-primitives.mjs";
|
|
4
|
+
import { getSellerTransactionHash } from "./seller-transaction-guard.mjs";
|
|
5
|
+
|
|
6
|
+
const HASH = /^[0-9A-F]{64}$/;
|
|
7
|
+
|
|
8
|
+
function fail(code) {
|
|
9
|
+
throw new Error(code);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function decimal(value) {
|
|
13
|
+
if (
|
|
14
|
+
typeof value !== "string" ||
|
|
15
|
+
!/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(value)
|
|
16
|
+
) {
|
|
17
|
+
fail("CPX_SELLER_LEDGER_AMOUNT_INVALID");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [whole, fraction = ""] = value.split(".");
|
|
21
|
+
return whole + "." + fraction.replace(/0+$/, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks ledger evidence against the signed transaction and the
|
|
26
|
+
* exact x402 requirements. No facilitator claim is trusted here.
|
|
27
|
+
*/
|
|
28
|
+
export function verifySellerLedgerEvidence({
|
|
29
|
+
ledgerResult,
|
|
30
|
+
signedTxBlob,
|
|
31
|
+
requirements
|
|
32
|
+
}) {
|
|
33
|
+
const expectedHash = getSellerTransactionHash(signedTxBlob);
|
|
34
|
+
|
|
35
|
+
let signed;
|
|
36
|
+
try {
|
|
37
|
+
signed = decode(signedTxBlob);
|
|
38
|
+
} catch {
|
|
39
|
+
fail("CPX_SELLER_SIGNED_TRANSACTION_INVALID");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
!requirements ||
|
|
44
|
+
requirements.network !== "xrpl:0" ||
|
|
45
|
+
typeof requirements.payTo !== "string" ||
|
|
46
|
+
typeof requirements.asset !== "string" ||
|
|
47
|
+
typeof requirements.amount !== "string"
|
|
48
|
+
) {
|
|
49
|
+
fail("CPX_SELLER_LEDGER_REQUIREMENTS_INVALID");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Independently enforce the requested currency on the
|
|
53
|
+
// ACTUAL signed XRPL transaction, not just ledger metadata.
|
|
54
|
+
if (
|
|
55
|
+
signed.TransactionType !== "Payment" ||
|
|
56
|
+
signed.Destination !== requirements.payTo ||
|
|
57
|
+
signed.Paths !== undefined ||
|
|
58
|
+
signed.DeliverMin !== undefined ||
|
|
59
|
+
(typeof signed.Flags === "number" &&
|
|
60
|
+
(signed.Flags & 0x00020000) !== 0)
|
|
61
|
+
) {
|
|
62
|
+
fail("CPX_SELLER_SIGNED_PAYMENT_MISMATCH");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const signedAmount = signed.DeliverMax ?? signed.Amount;
|
|
66
|
+
|
|
67
|
+
if (requirements.asset === "XRP") {
|
|
68
|
+
if (
|
|
69
|
+
typeof signedAmount !== "string" ||
|
|
70
|
+
!/^[0-9]+$/.test(signedAmount) ||
|
|
71
|
+
!/^[0-9]+$/.test(requirements.amount) ||
|
|
72
|
+
BigInt(signedAmount) !== BigInt(requirements.amount) ||
|
|
73
|
+
signed.SendMax !== undefined
|
|
74
|
+
) {
|
|
75
|
+
fail("CPX_SELLER_SIGNED_ASSET_MISMATCH");
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
const expectedIssuer = requirements.extra?.issuer;
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
!signedAmount ||
|
|
82
|
+
typeof signedAmount !== "object" ||
|
|
83
|
+
signedAmount.currency !== requirements.asset ||
|
|
84
|
+
signedAmount.issuer !== expectedIssuer ||
|
|
85
|
+
decimal(signedAmount.value) !==
|
|
86
|
+
decimal(requirements.amount) ||
|
|
87
|
+
!signed.SendMax ||
|
|
88
|
+
typeof signed.SendMax !== "object" ||
|
|
89
|
+
signed.SendMax.currency !== requirements.asset ||
|
|
90
|
+
signed.SendMax.issuer !== expectedIssuer ||
|
|
91
|
+
decimal(signed.SendMax.value) !==
|
|
92
|
+
decimal(requirements.amount)
|
|
93
|
+
) {
|
|
94
|
+
fail("CPX_SELLER_SIGNED_ASSET_MISMATCH");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const invoiceId = requirements.extra?.invoiceId;
|
|
99
|
+
const destinationTag = requirements.extra?.destinationTag;
|
|
100
|
+
|
|
101
|
+
if (typeof invoiceId !== "string" ||
|
|
102
|
+
invoiceId.length < 1 || invoiceId.length > 256 ||
|
|
103
|
+
!Number.isSafeInteger(destinationTag) ||
|
|
104
|
+
destinationTag < 0 || destinationTag > 0xffffffff) {
|
|
105
|
+
fail("CPX_SELLER_LEDGER_INVOICE_OR_TAG_REQUIRED");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const expectedInvoice = createHash("sha256")
|
|
109
|
+
.update(invoiceId, "utf8")
|
|
110
|
+
.digest("hex")
|
|
111
|
+
.toUpperCase();
|
|
112
|
+
|
|
113
|
+
if (signed.InvoiceID?.toUpperCase() !== expectedInvoice ||
|
|
114
|
+
signed.DestinationTag !== destinationTag) {
|
|
115
|
+
fail("CPX_SELLER_SIGNED_INVOICE_OR_TAG_MISMATCH");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
requirements.extra?.sourceTag !== undefined &&
|
|
120
|
+
signed.SourceTag !== requirements.extra.sourceTag
|
|
121
|
+
) {
|
|
122
|
+
fail("CPX_SELLER_SIGNED_SOURCE_TAG_MISMATCH");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const tx = ledgerResult?.tx_json ?? ledgerResult?.tx;
|
|
126
|
+
const meta = ledgerResult?.meta ?? ledgerResult?.metaData;
|
|
127
|
+
|
|
128
|
+
const reportedHash = ledgerResult?.hash ?? tx?.hash;
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
ledgerResult?.validated !== true ||
|
|
132
|
+
typeof reportedHash !== "string" ||
|
|
133
|
+
!HASH.test(reportedHash.toUpperCase()) ||
|
|
134
|
+
reportedHash.toUpperCase() !== expectedHash ||
|
|
135
|
+
!Number.isSafeInteger(ledgerResult.ledger_index) ||
|
|
136
|
+
ledgerResult.ledger_index <= 0 ||
|
|
137
|
+
meta?.TransactionResult !== "tesSUCCESS" ||
|
|
138
|
+
tx?.TransactionType !== "Payment" ||
|
|
139
|
+
tx?.Destination !== requirements.payTo
|
|
140
|
+
) {
|
|
141
|
+
fail("CPX_SELLER_LEDGER_PAYMENT_NOT_CONFIRMED");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// A successful Payment result must contain a concrete delivered
|
|
145
|
+
// amount. Do not infer delivery solely from the transaction request.
|
|
146
|
+
if (
|
|
147
|
+
requirements.extra?.sourceTag !== undefined &&
|
|
148
|
+
tx.SourceTag !== requirements.extra.sourceTag
|
|
149
|
+
) {
|
|
150
|
+
fail("CPX_SELLER_LEDGER_SOURCE_TAG_MISMATCH");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const delivered = meta.delivered_amount;
|
|
154
|
+
|
|
155
|
+
if (requirements.asset === "XRP") {
|
|
156
|
+
if (
|
|
157
|
+
typeof delivered !== "string" ||
|
|
158
|
+
!/^[0-9]+$/.test(delivered) ||
|
|
159
|
+
!/^[0-9]+$/.test(requirements.amount) ||
|
|
160
|
+
BigInt(delivered) !== BigInt(requirements.amount)
|
|
161
|
+
) {
|
|
162
|
+
fail("CPX_SELLER_LEDGER_AMOUNT_MISMATCH");
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
if (
|
|
166
|
+
!delivered ||
|
|
167
|
+
typeof delivered !== "object" ||
|
|
168
|
+
delivered.currency !== requirements.asset ||
|
|
169
|
+
delivered.issuer !== requirements.extra?.issuer ||
|
|
170
|
+
decimal(delivered.value) !== decimal(requirements.amount)
|
|
171
|
+
) {
|
|
172
|
+
fail("CPX_SELLER_LEDGER_ASSET_MISMATCH");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (requirements.extra?.destinationTag !== undefined &&
|
|
177
|
+
tx.DestinationTag !== requirements.extra.destinationTag) {
|
|
178
|
+
fail("CPX_SELLER_LEDGER_DESTINATION_TAG_MISMATCH");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (tx.InvoiceID?.toUpperCase() !== expectedInvoice ||
|
|
182
|
+
tx.DestinationTag !== destinationTag ||
|
|
183
|
+
tx.Account !== signed.Account ||
|
|
184
|
+
tx.Fee !== signed.Fee ||
|
|
185
|
+
(tx.SourceTag ?? undefined) !==
|
|
186
|
+
(signed.SourceTag ?? undefined)) {
|
|
187
|
+
fail("CPX_SELLER_LEDGER_TRANSACTION_FIELDS_MISMATCH");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
validated: true,
|
|
192
|
+
transaction: expectedHash,
|
|
193
|
+
network: "xrpl:0",
|
|
194
|
+
ledgerIndex: ledgerResult.ledger_index
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Production verifier uses the fixed Ripple-operated XRPL mainnet
|
|
200
|
+
* endpoint. This is a single-provider trust dependency, not quorum.
|
|
201
|
+
* Never supplies signing keys or submits a transaction.
|
|
202
|
+
*/
|
|
203
|
+
export async function confirmSellerLedgerPayment({
|
|
204
|
+
signedTxBlob,
|
|
205
|
+
requirements
|
|
206
|
+
}) {
|
|
207
|
+
const hash = getSellerTransactionHash(signedTxBlob);
|
|
208
|
+
const client = new Client(MAINNET_LEDGER_URL);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await client.connect();
|
|
212
|
+
|
|
213
|
+
const response = await client.request({
|
|
214
|
+
command: "tx",
|
|
215
|
+
transaction: hash,
|
|
216
|
+
binary: false,
|
|
217
|
+
api_version: 2
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return verifySellerLedgerEvidence({
|
|
221
|
+
ledgerResult: response.result,
|
|
222
|
+
signedTxBlob,
|
|
223
|
+
requirements
|
|
224
|
+
});
|
|
225
|
+
} finally {
|
|
226
|
+
await client.disconnect();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
x402ResourceServer,
|
|
3
|
+
x402HTTPResourceServer,
|
|
4
|
+
HTTPFacilitatorClient
|
|
5
|
+
} from "@x402/core/server";
|
|
6
|
+
import { ExactXrplScheme } from "@x402/xrpl/exact/server";
|
|
7
|
+
import { isValidClassicAddress } from "xrpl";
|
|
8
|
+
|
|
9
|
+
const ASSETS = Object.freeze({
|
|
10
|
+
XRP: {
|
|
11
|
+
asset: "XRP"
|
|
12
|
+
},
|
|
13
|
+
RLUSD: {
|
|
14
|
+
asset: "524C555344000000000000000000000000000000",
|
|
15
|
+
issuer: "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De"
|
|
16
|
+
},
|
|
17
|
+
USDC: {
|
|
18
|
+
asset: "5553444300000000000000000000000000000000",
|
|
19
|
+
issuer: "rGm7WCVp9gb4jZHWTEtGUr4dd74z2XuWhE"
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function requirePrice(asset, amount) {
|
|
24
|
+
if (typeof amount !== "string") {
|
|
25
|
+
throw new TypeError("CPX_SELLER_AMOUNT_MUST_BE_STRING");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (asset === "XRP") {
|
|
29
|
+
if (!/^[1-9][0-9]*$/.test(amount)) {
|
|
30
|
+
throw new Error("CPX_SELLER_XRP_REQUIRES_POSITIVE_DROPS");
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
!/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(amount) ||
|
|
37
|
+
!/[1-9]/.test(amount)
|
|
38
|
+
) {
|
|
39
|
+
throw new Error("CPX_SELLER_INVALID_ISSUED_AMOUNT");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createCpXSellerConfig({
|
|
44
|
+
walletAddress,
|
|
45
|
+
asset,
|
|
46
|
+
amount,
|
|
47
|
+
method = "GET",
|
|
48
|
+
path = "/paid",
|
|
49
|
+
description = "Paid XRPL agent service",
|
|
50
|
+
invoiceId,
|
|
51
|
+
destinationTag
|
|
52
|
+
}) {
|
|
53
|
+
if (!isValidClassicAddress(walletAddress)) {
|
|
54
|
+
throw new Error("CPX_SELLER_INVALID_WALLET_ADDRESS");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!Object.hasOwn(ASSETS, asset)) {
|
|
58
|
+
throw new Error("CPX_SELLER_UNSUPPORTED_ASSET");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
requirePrice(asset, amount);
|
|
62
|
+
|
|
63
|
+
if (typeof invoiceId !== "string" ||
|
|
64
|
+
invoiceId.length < 1 || invoiceId.length > 256) {
|
|
65
|
+
throw new Error("CPX_SELLER_INVOICE_REQUIRED");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!Number.isSafeInteger(destinationTag) ||
|
|
69
|
+
destinationTag < 0 || destinationTag > 0xffffffff) {
|
|
70
|
+
throw new Error("CPX_SELLER_DESTINATION_TAG_REQUIRED");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!["GET", "POST"].includes(method)) {
|
|
74
|
+
throw new Error("CPX_SELLER_UNSUPPORTED_METHOD");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
typeof path !== "string" ||
|
|
79
|
+
!new RegExp("^/[a-zA-Z0-9/_-]+$").test(path) ||
|
|
80
|
+
path.includes("//") ||
|
|
81
|
+
path.includes("..")
|
|
82
|
+
) {
|
|
83
|
+
throw new Error("CPX_SELLER_INVALID_ROUTE");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const token = ASSETS[asset];
|
|
87
|
+
|
|
88
|
+
const paymentOption = {
|
|
89
|
+
scheme: "exact",
|
|
90
|
+
network: "xrpl:0",
|
|
91
|
+
maxTimeoutSeconds: 120,
|
|
92
|
+
payTo: walletAddress,
|
|
93
|
+
price: {
|
|
94
|
+
asset: token.asset,
|
|
95
|
+
amount,
|
|
96
|
+
...(token.issuer
|
|
97
|
+
? { extra: { issuer: token.issuer } }
|
|
98
|
+
: {})
|
|
99
|
+
},
|
|
100
|
+
extra: {
|
|
101
|
+
...(token.issuer ? { issuer: token.issuer } : {}),
|
|
102
|
+
invoiceId,
|
|
103
|
+
destinationTag,
|
|
104
|
+
...((asset === "RLUSD" || asset === "USDC") ? { areFeesSponsored: false } : {}),
|
|
105
|
+
...(asset === "XRP" || asset === "RLUSD" || asset === "USDC"
|
|
106
|
+
? { sourceTag: 804681468 }
|
|
107
|
+
: {})
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
[`${method} ${path}`]: {
|
|
113
|
+
accepts: [paymentOption],
|
|
114
|
+
description,
|
|
115
|
+
mimeType: "application/json"
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createCpXSellerServer({
|
|
121
|
+
facilitatorUrl,
|
|
122
|
+
routes
|
|
123
|
+
}) {
|
|
124
|
+
if (
|
|
125
|
+
typeof facilitatorUrl !== "string" ||
|
|
126
|
+
!facilitatorUrl.startsWith("https://")
|
|
127
|
+
) {
|
|
128
|
+
throw new Error("CPX_SELLER_HTTPS_FACILITATOR_REQUIRED");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
!routes ||
|
|
133
|
+
typeof routes !== "object" ||
|
|
134
|
+
Array.isArray(routes)
|
|
135
|
+
) {
|
|
136
|
+
throw new Error("CPX_SELLER_ROUTES_REQUIRED");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
140
|
+
url: facilitatorUrl
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Observe official verification results without changing them.
|
|
144
|
+
const facilitator = new Proxy(facilitatorClient, {
|
|
145
|
+
get(target, property) {
|
|
146
|
+
if (property === "verify") {
|
|
147
|
+
return async (...args) => {
|
|
148
|
+
try {
|
|
149
|
+
const result = await target.verify(...args);
|
|
150
|
+
|
|
151
|
+
if (result?.isValid === false) {
|
|
152
|
+
console.error("[CPX_T54_VERIFY]", {
|
|
153
|
+
isValid: false,
|
|
154
|
+
invalidReason: result.invalidReason ?? null,
|
|
155
|
+
invalidMessage: result.invalidMessage ?? null
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error("[CPX_T54_VERIFY_EXCEPTION]", {
|
|
162
|
+
name: error?.name ?? "Error"
|
|
163
|
+
});
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const value = Reflect.get(target, property, target);
|
|
170
|
+
return typeof value === "function"
|
|
171
|
+
? value.bind(target)
|
|
172
|
+
: value;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const resourceServer = new x402ResourceServer(facilitator)
|
|
177
|
+
.register("xrpl:*", new ExactXrplScheme());
|
|
178
|
+
|
|
179
|
+
return new x402HTTPResourceServer(resourceServer, routes);
|
|
180
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Durable, atomic replay guard for exact payment-proof bytes.
|
|
7
|
+
*
|
|
8
|
+
* This is a supporting control, not a substitute for XRPL
|
|
9
|
+
* transaction-hash replay detection and validated-ledger checks.
|
|
10
|
+
*/
|
|
11
|
+
export function createSellerProofGuard({ journalDir }) {
|
|
12
|
+
if (typeof journalDir !== "string" || !journalDir.trim()) {
|
|
13
|
+
throw new Error("CPX_SELLER_JOURNAL_REQUIRED");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const directory = resolve(journalDir);
|
|
17
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
claim(proof) {
|
|
21
|
+
if (typeof proof !== "string" || !proof.trim()) {
|
|
22
|
+
throw new Error("CPX_SELLER_PROOF_REQUIRED");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const digest = createHash("sha256")
|
|
26
|
+
.update(proof, "utf8")
|
|
27
|
+
.digest("hex");
|
|
28
|
+
|
|
29
|
+
const file = join(directory, digest + ".claim");
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
writeFileSync(file, "CLAIMED\n", {
|
|
33
|
+
flag: "wx",
|
|
34
|
+
mode: 0o600
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error?.code === "EEXIST") return false;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hashes } from "xrpl";
|
|
2
|
+
import { createSellerProofGuard } from "./seller-proof-guard.mjs";
|
|
3
|
+
|
|
4
|
+
const HEX = /^(?:[0-9a-fA-F]{2})+$/;
|
|
5
|
+
|
|
6
|
+
export function getSellerTransactionHash(signedTxBlob) {
|
|
7
|
+
if (
|
|
8
|
+
typeof signedTxBlob !== "string" ||
|
|
9
|
+
!HEX.test(signedTxBlob)
|
|
10
|
+
) {
|
|
11
|
+
throw new Error("CPX_SELLER_SIGNED_TRANSACTION_REQUIRED");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const hash = hashes.hashSignedTx(signedTxBlob);
|
|
15
|
+
|
|
16
|
+
if (
|
|
17
|
+
typeof hash !== "string" ||
|
|
18
|
+
!/^[0-9A-Fa-f]{64}$/.test(hash)
|
|
19
|
+
) {
|
|
20
|
+
throw new Error("CPX_SELLER_INVALID_TRANSACTION_HASH");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return hash.toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createSellerTransactionGuard({ journalDir }) {
|
|
27
|
+
const storage = createSellerProofGuard({ journalDir });
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
claimSignedTransaction(signedTxBlob) {
|
|
31
|
+
const hash = getSellerTransactionHash(signedTxBlob);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
hash,
|
|
35
|
+
claimed: storage.claim("xrpl:0:transaction:" + hash)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
package/types/public-node.d.ts
CHANGED
|
@@ -53,3 +53,22 @@ export declare function reconcileSecureNodePurchase(options: {journalDir: string
|
|
|
53
53
|
feeDrops: string;
|
|
54
54
|
invoiceID: string;
|
|
55
55
|
}>;
|
|
56
|
+
|
|
57
|
+
export declare function createCpXAgentEarn(options: {
|
|
58
|
+
walletAddress: string;
|
|
59
|
+
asset: "XRP" | "RLUSD" | "USDC";
|
|
60
|
+
amount: string;
|
|
61
|
+
invoiceId: string;
|
|
62
|
+
destinationTag: number;
|
|
63
|
+
facilitatorUrl: string;
|
|
64
|
+
journalDir: string;
|
|
65
|
+
method?: "GET" | "POST";
|
|
66
|
+
path?: string;
|
|
67
|
+
onPaid: (
|
|
68
|
+
request: Request,
|
|
69
|
+
settlement: unknown
|
|
70
|
+
) => Promise<Response> | Response;
|
|
71
|
+
}): {
|
|
72
|
+
initialize(): Promise<unknown>;
|
|
73
|
+
handle(request: Request): Promise<Response>;
|
|
74
|
+
};
|