@crossmint/lobster.cash 0.1.1
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/README.md +121 -0
- package/index.ts +73 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +43 -0
- package/skills/crossmint/SKILL.md +274 -0
- package/src/amazon-order.test.ts +548 -0
- package/src/api.test.ts +439 -0
- package/src/api.ts +668 -0
- package/src/config.test.ts +53 -0
- package/src/config.ts +50 -0
- package/src/tools.test.ts +354 -0
- package/src/tools.ts +989 -0
- package/src/wallet.test.ts +367 -0
- package/src/wallet.ts +328 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { Keypair } from "@solana/web3.js";
|
|
2
|
+
import bs58 from "bs58";
|
|
3
|
+
import nacl from "tweetnacl";
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* End-to-end test for Amazon order purchase via Crossmint.
|
|
8
|
+
*
|
|
9
|
+
* This test demonstrates the complete delegated signer flow:
|
|
10
|
+
* 1. Create an Amazon order via Crossmint API
|
|
11
|
+
* 2. Create a Crossmint transaction with the serialized transaction
|
|
12
|
+
* 3. Sign the approval message with the delegated signer
|
|
13
|
+
* 4. Submit the approval to Crossmint
|
|
14
|
+
* 5. Wait for transaction to be broadcast and get txId
|
|
15
|
+
* 6. Submit txId to /payment endpoint (CRITICAL STEP!)
|
|
16
|
+
* 7. Poll for order completion
|
|
17
|
+
*
|
|
18
|
+
* Run with:
|
|
19
|
+
* CROSSMINT_API_KEY=your-key \
|
|
20
|
+
* PAYER_ADDRESS=your-smart-wallet-address \
|
|
21
|
+
* SIGNER_PRIVATE_KEY=your-delegated-signer-base58-private-key \
|
|
22
|
+
* pnpm test src/amazon-order.test.ts
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Configuration from environment
|
|
26
|
+
const API_KEY = process.env.CROSSMINT_API_KEY || "";
|
|
27
|
+
const PAYER_ADDRESS = process.env.PAYER_ADDRESS || ""; // Smart wallet address
|
|
28
|
+
const SIGNER_PRIVATE_KEY = process.env.PAYER_PRIVATE_KEY || ""; // Delegated signer private key
|
|
29
|
+
|
|
30
|
+
// Crossmint API base URLs - depends on environment
|
|
31
|
+
const CROSSMINT_ENV = process.env.CROSSMINT_ENV || "production";
|
|
32
|
+
const CROSSMINT_BASE_URL =
|
|
33
|
+
CROSSMINT_ENV === "staging"
|
|
34
|
+
? "https://staging.crossmint.com/api"
|
|
35
|
+
: "https://www.crossmint.com/api";
|
|
36
|
+
const CROSSMINT_ORDERS_API = `${CROSSMINT_BASE_URL}/2022-06-09`;
|
|
37
|
+
const CROSSMINT_WALLETS_API = `${CROSSMINT_BASE_URL}/2025-06-09`;
|
|
38
|
+
|
|
39
|
+
// Test product - can be overridden via environment variable
|
|
40
|
+
const TEST_AMAZON_ASIN = process.env.TEST_AMAZON_ASIN || "B00AATAHY0";
|
|
41
|
+
|
|
42
|
+
// Skip tests if credentials not provided
|
|
43
|
+
const LIVE = API_KEY && PAYER_ADDRESS && SIGNER_PRIVATE_KEY;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Step 1: Create the Amazon Order
|
|
47
|
+
*/
|
|
48
|
+
async function createAmazonOrder(
|
|
49
|
+
amazonASIN: string,
|
|
50
|
+
payerAddress: string,
|
|
51
|
+
currency: string = "sol"
|
|
52
|
+
) {
|
|
53
|
+
const response = await fetch(`${CROSSMINT_ORDERS_API}/orders`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"X-API-KEY": API_KEY,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
recipient: {
|
|
61
|
+
email: "buyer@example.com",
|
|
62
|
+
physicalAddress: {
|
|
63
|
+
name: "John Doe",
|
|
64
|
+
line1: "350 5th Ave",
|
|
65
|
+
line2: "Suite 400",
|
|
66
|
+
city: "New York",
|
|
67
|
+
state: "NY",
|
|
68
|
+
postalCode: "10118",
|
|
69
|
+
country: "US",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
payment: {
|
|
73
|
+
method: "solana",
|
|
74
|
+
currency: currency,
|
|
75
|
+
payerAddress: payerAddress,
|
|
76
|
+
receiptEmail: "buyer@example.com",
|
|
77
|
+
},
|
|
78
|
+
lineItems: [
|
|
79
|
+
{
|
|
80
|
+
productLocator: `amazon:${amazonASIN}`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const error = await response.text();
|
|
88
|
+
throw new Error(`Failed to create order: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return await response.json();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Step 2: Create Crossmint transaction from serialized transaction
|
|
96
|
+
* Returns transactionId and message to sign
|
|
97
|
+
*/
|
|
98
|
+
async function createCrossmintTransaction(
|
|
99
|
+
payerAddress: string,
|
|
100
|
+
serializedTransaction: string,
|
|
101
|
+
signerAddress: string
|
|
102
|
+
) {
|
|
103
|
+
const response = await fetch(
|
|
104
|
+
`${CROSSMINT_WALLETS_API}/wallets/${encodeURIComponent(payerAddress)}/transactions`,
|
|
105
|
+
{
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
"X-API-KEY": API_KEY,
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
params: {
|
|
113
|
+
transaction: serializedTransaction,
|
|
114
|
+
signer: `external-wallet:${signerAddress}`,
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = await response.text();
|
|
122
|
+
throw new Error(`Failed to create transaction: ${error}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return await response.json();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Step 3: Sign the approval message with delegated signer
|
|
130
|
+
*/
|
|
131
|
+
function signApprovalMessage(message: string, privateKeyBase58: string): string {
|
|
132
|
+
const secretKey = bs58.decode(privateKeyBase58);
|
|
133
|
+
const messageBytes = bs58.decode(message);
|
|
134
|
+
const signature = nacl.sign.detached(messageBytes, secretKey);
|
|
135
|
+
return bs58.encode(signature);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Step 4: Submit approval to Crossmint
|
|
140
|
+
*/
|
|
141
|
+
async function submitApproval(
|
|
142
|
+
payerAddress: string,
|
|
143
|
+
transactionId: string,
|
|
144
|
+
signerAddress: string,
|
|
145
|
+
signature: string
|
|
146
|
+
) {
|
|
147
|
+
const response = await fetch(
|
|
148
|
+
`${CROSSMINT_WALLETS_API}/wallets/${encodeURIComponent(payerAddress)}/transactions/${encodeURIComponent(transactionId)}/approvals`,
|
|
149
|
+
{
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
"X-API-KEY": API_KEY,
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
approvals: [
|
|
157
|
+
{
|
|
158
|
+
signer: `external-wallet:${signerAddress}`,
|
|
159
|
+
signature: signature,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
}),
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
const error = await response.text();
|
|
168
|
+
throw new Error(`Failed to submit approval: ${error}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return await response.json();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Step 5: Get transaction status from Crossmint Wallets API
|
|
176
|
+
* Poll until we get the on-chain txId
|
|
177
|
+
*/
|
|
178
|
+
async function getTransactionStatus(payerAddress: string, transactionId: string) {
|
|
179
|
+
const response = await fetch(
|
|
180
|
+
`${CROSSMINT_WALLETS_API}/wallets/${encodeURIComponent(payerAddress)}/transactions/${encodeURIComponent(transactionId)}`,
|
|
181
|
+
{
|
|
182
|
+
method: "GET",
|
|
183
|
+
headers: {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
"X-API-KEY": API_KEY,
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
const error = await response.text();
|
|
192
|
+
throw new Error(`Failed to get transaction status: ${error}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return await response.json();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Step 5b: Poll for transaction to be broadcast and get on-chain txId
|
|
200
|
+
*/
|
|
201
|
+
async function waitForTransactionBroadcast(
|
|
202
|
+
payerAddress: string,
|
|
203
|
+
transactionId: string,
|
|
204
|
+
timeoutMs: number = 30000
|
|
205
|
+
): Promise<string | null> {
|
|
206
|
+
const startTime = Date.now();
|
|
207
|
+
|
|
208
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
209
|
+
const txStatus = await getTransactionStatus(payerAddress, transactionId);
|
|
210
|
+
console.log("Transaction status:", txStatus.status);
|
|
211
|
+
|
|
212
|
+
// Check if transaction has been broadcast and we have the on-chain txId
|
|
213
|
+
if (txStatus.onChain?.txId) {
|
|
214
|
+
console.log("On-chain txId:", txStatus.onChain.txId);
|
|
215
|
+
return txStatus.onChain.txId;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Also check for txId directly on the response
|
|
219
|
+
if (txStatus.txId) {
|
|
220
|
+
console.log("txId from response:", txStatus.txId);
|
|
221
|
+
return txStatus.txId;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check if status indicates completion
|
|
225
|
+
if (txStatus.status === "success" || txStatus.status === "completed") {
|
|
226
|
+
// Try to find txId in various places
|
|
227
|
+
const txId = txStatus.onChain?.txId || txStatus.txId || txStatus.hash;
|
|
228
|
+
if (txId) {
|
|
229
|
+
return txId;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check for failure
|
|
234
|
+
if (txStatus.status === "failed") {
|
|
235
|
+
throw new Error(`Transaction failed: ${JSON.stringify(txStatus)}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Wait before polling again
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log("Timeout waiting for transaction broadcast");
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Step 6: Submit payment to Crossmint Orders API (CRITICAL!)
|
|
248
|
+
* This notifies Crossmint that the payment transaction has been submitted
|
|
249
|
+
*/
|
|
250
|
+
async function processPayment(orderId: string, txId: string, clientSecret?: string) {
|
|
251
|
+
const headers: Record<string, string> = {
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
"X-API-KEY": API_KEY,
|
|
254
|
+
};
|
|
255
|
+
if (clientSecret) {
|
|
256
|
+
headers["Authorization"] = clientSecret;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const response = await fetch(`${CROSSMINT_ORDERS_API}/orders/${orderId}/payment`, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers,
|
|
262
|
+
body: JSON.stringify({
|
|
263
|
+
type: "crypto-tx-id",
|
|
264
|
+
txId: txId,
|
|
265
|
+
}),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
const error = await response.text();
|
|
270
|
+
throw new Error(`Failed to process payment: ${error}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return await response.json();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Step 7: Poll for Order Completion
|
|
278
|
+
*/
|
|
279
|
+
async function pollOrderStatus(
|
|
280
|
+
orderId: string,
|
|
281
|
+
clientSecret: string,
|
|
282
|
+
timeoutMs: number = 60000
|
|
283
|
+
): Promise<{ paymentStatus: string; deliveryStatus: string }> {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
const intervalId = setInterval(async () => {
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch(`${CROSSMINT_ORDERS_API}/orders/${orderId}`, {
|
|
288
|
+
method: "GET",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/json",
|
|
291
|
+
"X-API-KEY": API_KEY,
|
|
292
|
+
Authorization: clientSecret,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
console.log("Failed to get order status:", await response.text());
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const orderStatus = await response.json();
|
|
302
|
+
const paymentStatus = orderStatus.payment?.status || "unknown";
|
|
303
|
+
const deliveryStatus = orderStatus.lineItems?.[0]?.delivery?.status || "pending";
|
|
304
|
+
|
|
305
|
+
console.log("Payment:", paymentStatus);
|
|
306
|
+
console.log("Delivery:", deliveryStatus);
|
|
307
|
+
|
|
308
|
+
if (paymentStatus === "completed") {
|
|
309
|
+
clearInterval(intervalId);
|
|
310
|
+
console.log("Payment completed! Amazon order is being fulfilled.");
|
|
311
|
+
resolve({ paymentStatus, deliveryStatus });
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error("Error polling order status:", error);
|
|
315
|
+
}
|
|
316
|
+
}, 2500);
|
|
317
|
+
|
|
318
|
+
setTimeout(() => {
|
|
319
|
+
clearInterval(intervalId);
|
|
320
|
+
console.log("Timeout - check order manually");
|
|
321
|
+
resolve({ paymentStatus: "timeout", deliveryStatus: "unknown" });
|
|
322
|
+
}, timeoutMs);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get order status (single call)
|
|
328
|
+
*/
|
|
329
|
+
async function getOrderStatus(orderId: string, clientSecret?: string) {
|
|
330
|
+
const headers: Record<string, string> = {
|
|
331
|
+
"Content-Type": "application/json",
|
|
332
|
+
"X-API-KEY": API_KEY,
|
|
333
|
+
};
|
|
334
|
+
if (clientSecret) {
|
|
335
|
+
headers["Authorization"] = clientSecret;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const response = await fetch(`${CROSSMINT_ORDERS_API}/orders/${orderId}`, {
|
|
339
|
+
method: "GET",
|
|
340
|
+
headers,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
const error = await response.text();
|
|
345
|
+
throw new Error(`Failed to get order: ${error}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return await response.json();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get signer public key from private key
|
|
353
|
+
*/
|
|
354
|
+
function getSignerAddress(privateKeyBase58: string): string {
|
|
355
|
+
const secretKey = bs58.decode(privateKeyBase58);
|
|
356
|
+
const keypair = Keypair.fromSecretKey(secretKey);
|
|
357
|
+
return keypair.publicKey.toBase58();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
describe("Amazon Order E2E Test", () => {
|
|
361
|
+
describe.skipIf(!LIVE)("live: delegated signer purchase flow", () => {
|
|
362
|
+
it("creates order and pays with delegated signer approval", async () => {
|
|
363
|
+
const signerAddress = getSignerAddress(SIGNER_PRIVATE_KEY);
|
|
364
|
+
|
|
365
|
+
console.log("\n=== Starting Amazon Order E2E Test (Delegated Signer Flow) ===\n");
|
|
366
|
+
console.log("Smart Wallet (Payer):", PAYER_ADDRESS);
|
|
367
|
+
console.log("Delegated Signer:", signerAddress);
|
|
368
|
+
console.log("Amazon ASIN:", TEST_AMAZON_ASIN);
|
|
369
|
+
|
|
370
|
+
// Step 1: Create the order
|
|
371
|
+
console.log("\n--- Step 1: Creating Amazon order ---");
|
|
372
|
+
const { order, clientSecret } = await createAmazonOrder(
|
|
373
|
+
TEST_AMAZON_ASIN,
|
|
374
|
+
PAYER_ADDRESS,
|
|
375
|
+
"sol"
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
console.log("Order ID:", order.orderId);
|
|
379
|
+
console.log("Order Phase:", order.phase);
|
|
380
|
+
console.log("Client Secret:", clientSecret ? "✓ received" : "✗ missing");
|
|
381
|
+
|
|
382
|
+
expect(order.orderId).toBeDefined();
|
|
383
|
+
expect(order.phase).toBeDefined();
|
|
384
|
+
|
|
385
|
+
// Check if we have a serialized transaction
|
|
386
|
+
const serializedTransaction = order.payment?.preparation?.serializedTransaction;
|
|
387
|
+
console.log(
|
|
388
|
+
"Serialized Transaction:",
|
|
389
|
+
serializedTransaction ? `✓ received (${serializedTransaction.length} chars)` : "✗ missing"
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
if (!serializedTransaction) {
|
|
393
|
+
console.log("\nOrder created but no serialized transaction returned.");
|
|
394
|
+
console.log("Full order response:", JSON.stringify(order, null, 2));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check for insufficient funds
|
|
399
|
+
if (order.payment?.failureReason?.code === "insufficient-funds") {
|
|
400
|
+
console.log("\n⚠️ Insufficient funds:", order.payment.failureReason.message);
|
|
401
|
+
console.log("Please fund the wallet and try again.");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Step 2: Create Crossmint transaction
|
|
406
|
+
console.log("\n--- Step 2: Creating Crossmint transaction ---");
|
|
407
|
+
const txResponse = await createCrossmintTransaction(
|
|
408
|
+
PAYER_ADDRESS,
|
|
409
|
+
serializedTransaction,
|
|
410
|
+
signerAddress
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
console.log("Transaction ID:", txResponse.id);
|
|
414
|
+
console.log("Transaction Status:", txResponse.status);
|
|
415
|
+
|
|
416
|
+
const messageToSign = txResponse.approvals?.pending?.[0]?.message;
|
|
417
|
+
console.log(
|
|
418
|
+
"Message to sign:",
|
|
419
|
+
messageToSign ? `✓ received (${messageToSign.length} chars)` : "✗ missing"
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (!messageToSign) {
|
|
423
|
+
console.log(
|
|
424
|
+
"\nNo message to sign. Transaction response:",
|
|
425
|
+
JSON.stringify(txResponse, null, 2)
|
|
426
|
+
);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Step 3: Sign the approval message
|
|
431
|
+
console.log("\n--- Step 3: Signing approval message ---");
|
|
432
|
+
const signature = signApprovalMessage(messageToSign, SIGNER_PRIVATE_KEY);
|
|
433
|
+
console.log("Signature:", `✓ generated (${signature.length} chars)`);
|
|
434
|
+
|
|
435
|
+
// Step 4: Submit approval
|
|
436
|
+
console.log("\n--- Step 4: Submitting approval to Crossmint ---");
|
|
437
|
+
const approvalResponse = await submitApproval(
|
|
438
|
+
PAYER_ADDRESS,
|
|
439
|
+
txResponse.id,
|
|
440
|
+
signerAddress,
|
|
441
|
+
signature
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
console.log("Approval Response:", JSON.stringify(approvalResponse, null, 2));
|
|
445
|
+
|
|
446
|
+
// Step 5: Wait for transaction to be broadcast and get on-chain txId
|
|
447
|
+
console.log("\n--- Step 5: Waiting for transaction broadcast ---");
|
|
448
|
+
const onChainTxId = await waitForTransactionBroadcast(PAYER_ADDRESS, txResponse.id, 30000);
|
|
449
|
+
|
|
450
|
+
if (!onChainTxId) {
|
|
451
|
+
console.log("Could not get on-chain txId. Checking approval response for txId...");
|
|
452
|
+
// Try to get txId from approval response
|
|
453
|
+
const fallbackTxId =
|
|
454
|
+
approvalResponse.onChain?.txId || approvalResponse.txId || approvalResponse.hash;
|
|
455
|
+
if (!fallbackTxId) {
|
|
456
|
+
console.log("No txId available. Cannot call /payment endpoint.");
|
|
457
|
+
console.log("Full approval response:", JSON.stringify(approvalResponse, null, 2));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const txIdToSubmit = onChainTxId || approvalResponse.onChain?.txId || approvalResponse.txId;
|
|
463
|
+
|
|
464
|
+
// Step 6: Submit payment to Crossmint (CRITICAL!)
|
|
465
|
+
console.log("\n--- Step 6: Submitting payment to Crossmint /payment endpoint ---");
|
|
466
|
+
console.log("Submitting txId:", txIdToSubmit);
|
|
467
|
+
|
|
468
|
+
const paymentResponse = await processPayment(order.orderId, txIdToSubmit!, clientSecret);
|
|
469
|
+
console.log("Payment Response:", JSON.stringify(paymentResponse, null, 2));
|
|
470
|
+
|
|
471
|
+
// Step 7: Poll for order completion
|
|
472
|
+
console.log("\n--- Step 7: Polling for order completion ---");
|
|
473
|
+
const { paymentStatus, deliveryStatus } = await pollOrderStatus(
|
|
474
|
+
order.orderId,
|
|
475
|
+
clientSecret,
|
|
476
|
+
60000
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
console.log("\n=== Final Status ===");
|
|
480
|
+
console.log("Payment Status:", paymentStatus);
|
|
481
|
+
console.log("Delivery Status:", deliveryStatus);
|
|
482
|
+
|
|
483
|
+
// Payment should eventually complete
|
|
484
|
+
expect(["completed", "timeout", "crypto-payer-insufficient-funds"]).toContain(paymentStatus);
|
|
485
|
+
}, 180000); // 3 minute timeout
|
|
486
|
+
|
|
487
|
+
it("creates order only (inspect response)", async () => {
|
|
488
|
+
console.log("\n=== Create Order Only Test ===\n");
|
|
489
|
+
|
|
490
|
+
const { order, clientSecret } = await createAmazonOrder(
|
|
491
|
+
TEST_AMAZON_ASIN,
|
|
492
|
+
PAYER_ADDRESS,
|
|
493
|
+
"sol"
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
console.log("Order ID:", order.orderId);
|
|
497
|
+
console.log("Order Phase:", order.phase);
|
|
498
|
+
console.log("Quote:", JSON.stringify(order.quote, null, 2));
|
|
499
|
+
console.log("Payment:", JSON.stringify(order.payment, null, 2));
|
|
500
|
+
|
|
501
|
+
expect(order.orderId).toBeDefined();
|
|
502
|
+
|
|
503
|
+
console.log("\n--- Full Response ---");
|
|
504
|
+
console.log(JSON.stringify({ order, clientSecret }, null, 2));
|
|
505
|
+
}, 30000);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
describe("unit tests", () => {
|
|
509
|
+
it("validates keypair creation from base58", () => {
|
|
510
|
+
const testKeypair = Keypair.generate();
|
|
511
|
+
const secretKeyBase58 = bs58.encode(testKeypair.secretKey);
|
|
512
|
+
const recreatedKeypair = Keypair.fromSecretKey(bs58.decode(secretKeyBase58));
|
|
513
|
+
expect(recreatedKeypair.publicKey.toBase58()).toBe(testKeypair.publicKey.toBase58());
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("validates signature generation", () => {
|
|
517
|
+
const testKeypair = Keypair.generate();
|
|
518
|
+
const secretKeyBase58 = bs58.encode(testKeypair.secretKey);
|
|
519
|
+
const testMessage = bs58.encode(Buffer.from("test message"));
|
|
520
|
+
|
|
521
|
+
const signature = signApprovalMessage(testMessage, secretKeyBase58);
|
|
522
|
+
expect(signature).toBeDefined();
|
|
523
|
+
expect(signature.length).toBeGreaterThan(0);
|
|
524
|
+
|
|
525
|
+
// Verify signature
|
|
526
|
+
const sigBytes = bs58.decode(signature);
|
|
527
|
+
const msgBytes = bs58.decode(testMessage);
|
|
528
|
+
const isValid = nacl.sign.detached.verify(
|
|
529
|
+
msgBytes,
|
|
530
|
+
sigBytes,
|
|
531
|
+
testKeypair.publicKey.toBytes()
|
|
532
|
+
);
|
|
533
|
+
expect(isValid).toBe(true);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
export {
|
|
539
|
+
createAmazonOrder,
|
|
540
|
+
createCrossmintTransaction,
|
|
541
|
+
signApprovalMessage,
|
|
542
|
+
submitApproval,
|
|
543
|
+
waitForTransactionBroadcast,
|
|
544
|
+
processPayment,
|
|
545
|
+
pollOrderStatus,
|
|
546
|
+
getOrderStatus,
|
|
547
|
+
getTransactionStatus,
|
|
548
|
+
};
|