cspr402 0.4.7

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +108 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/client.d.ts +207 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +400 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/commands/onboard.d.ts +4 -0
  12. package/dist/commands/onboard.d.ts.map +1 -0
  13. package/dist/commands/onboard.js +192 -0
  14. package/dist/commands/onboard.js.map +1 -0
  15. package/dist/commands/onboard.test.d.ts +2 -0
  16. package/dist/commands/onboard.test.d.ts.map +1 -0
  17. package/dist/commands/onboard.test.js +48 -0
  18. package/dist/commands/onboard.test.js.map +1 -0
  19. package/dist/commands/purchase.d.ts +2 -0
  20. package/dist/commands/purchase.d.ts.map +1 -0
  21. package/dist/commands/purchase.js +206 -0
  22. package/dist/commands/purchase.js.map +1 -0
  23. package/dist/commands/wallet.d.ts +2 -0
  24. package/dist/commands/wallet.d.ts.map +1 -0
  25. package/dist/commands/wallet.js +161 -0
  26. package/dist/commands/wallet.js.map +1 -0
  27. package/dist/config.d.ts +77 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +329 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/errors.d.ts +101 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +197 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +22 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mcp.d.ts +2 -0
  40. package/dist/mcp.d.ts.map +1 -0
  41. package/dist/mcp.js +337 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mpp.d.ts +57 -0
  44. package/dist/mpp.d.ts.map +1 -0
  45. package/dist/mpp.js +165 -0
  46. package/dist/mpp.js.map +1 -0
  47. package/dist/ows.d.ts +190 -0
  48. package/dist/ows.d.ts.map +1 -0
  49. package/dist/ows.js +565 -0
  50. package/dist/ows.js.map +1 -0
  51. package/dist/soroban.d.ts +92 -0
  52. package/dist/soroban.d.ts.map +1 -0
  53. package/dist/soroban.js +313 -0
  54. package/dist/soroban.js.map +1 -0
  55. package/dist/stellar.d.ts +53 -0
  56. package/dist/stellar.d.ts.map +1 -0
  57. package/dist/stellar.js +180 -0
  58. package/dist/stellar.js.map +1 -0
  59. package/dist/version-check.d.ts +5 -0
  60. package/dist/version-check.d.ts.map +1 -0
  61. package/dist/version-check.js +203 -0
  62. package/dist/version-check.js.map +1 -0
  63. package/package.json +80 -0
@@ -0,0 +1,92 @@
1
+ import { rpc, xdr, type Transaction } from '@stellar/stellar-sdk';
2
+ export declare function getSorobanRpcUrl(networkPassphrase: string): string;
3
+ export declare function getHorizonUrl(networkPassphrase?: string): string;
4
+ /**
5
+ * Convert a decimal string like "10.00" or "1.2345678" to a 7-decimal i128
6
+ * bigint (stroops / micro-USDC). Rejects inputs with >7 fractional digits.
7
+ */
8
+ export declare function decimalToStroops(decimal: string): bigint;
9
+ /**
10
+ * Thrown when Soroban rejects a transaction because the offered fee was
11
+ * too low. `requiredFee` is the minimum the network demanded (in stroops),
12
+ * taken directly from the error response — callers should use it as the
13
+ * floor when rebuilding the transaction.
14
+ */
15
+ export declare class InsufficientFeeError extends Error {
16
+ requiredFee: string;
17
+ constructor(requiredFee: string);
18
+ }
19
+ export type PaymentFn = 'pay_usdc' | 'pay_xlm';
20
+ export interface BuildContractTxOpts {
21
+ contractId: string;
22
+ fn: PaymentFn;
23
+ fromPublicKey: string;
24
+ amountStroops: bigint;
25
+ orderId: string;
26
+ networkPassphrase: string;
27
+ rpcUrl?: string;
28
+ /**
29
+ * Force the built tx to use this exact sequence number instead of
30
+ * whatever Soroban RPC reports as the account's current sequence.
31
+ *
32
+ * Used by payViaContractOWS's retry loop to guarantee mutual
33
+ * exclusion with a prior failed submit. If the original tx ends
34
+ * up landing (e.g. it was accepted by the network despite the
35
+ * client-side 120s deadline expiring), the retry — which shares
36
+ * the same sequence — will fail with `tx_bad_seq` rather than
37
+ * landing as a second payment. That gives us a free double-pay
38
+ * safety property: at most one of the two attempts can ever
39
+ * credit the order.
40
+ *
41
+ * Pass the sequence number that should appear in the built tx
42
+ * (i.e. the post-bump value captured from a prior Transaction's
43
+ * `.sequence` field). The builder internally converts that into
44
+ * a pre-bump Account so the TransactionBuilder's
45
+ * incrementSequenceNumber() call lands on exactly this value.
46
+ */
47
+ preservedSequence?: string;
48
+ /**
49
+ * Override the fee (stroops). Defaults to BASE_FEE. Set this to the
50
+ * `requiredFee` from an InsufficientFeeError to retry with the fee
51
+ * floor the network actually demanded.
52
+ */
53
+ fee?: string;
54
+ }
55
+ /**
56
+ * Build a Soroban transaction that invokes the receiver contract's pay_usdc
57
+ * or pay_xlm function. Returns an unsigned, simulation-prepared Transaction
58
+ * ready for the caller to sign (either via keypair or OWS) and submit.
59
+ */
60
+ export declare function buildContractPaymentTx(opts: BuildContractTxOpts): Promise<{
61
+ tx: Transaction;
62
+ server: rpc.Server;
63
+ }>;
64
+ /**
65
+ * Submit a signed Soroban transaction and poll until it reaches a terminal
66
+ * status. Returns the transaction hash on success.
67
+ *
68
+ * Important: a successful return only guarantees the tx has been accepted
69
+ * onto the ledger. The cards402 backend's contract watcher is the source
70
+ * of truth for "the order has been credited" — the SDK's purchaseCardOWS
71
+ * always follows up with waitForCard against the order id, so even if
72
+ * this function gives up before finalization, the watcher still has a
73
+ * chance to credit the order if the tx eventually lands.
74
+ */
75
+ export declare function submitSorobanTx(tx: Transaction, server: rpc.Server, horizonUrl?: string): Promise<string>;
76
+ /**
77
+ * Decide which contract function + asset amount to use based on the
78
+ * PaymentInstructions returned from POST /v1/orders.
79
+ */
80
+ export declare function selectContractCall(payment: {
81
+ usdc: {
82
+ amount: string;
83
+ };
84
+ xlm?: {
85
+ amount: string;
86
+ };
87
+ }, paymentAsset: 'usdc' | 'xlm'): {
88
+ fn: PaymentFn;
89
+ amountDecimal: string;
90
+ };
91
+ export { xdr };
92
+ //# sourceMappingURL=soroban.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soroban.d.ts","sourceRoot":"","sources":["../src/soroban.ts"],"names":[],"mappings":"AAKA,OAAO,EAQL,GAAG,EACH,GAAG,EACH,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAO9B,wBAAgB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAElE;AAOD,wBAAgB,aAAa,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqBxD;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,WAAW,EAAE,MAAM,CAAC;gBACR,WAAW,EAAE,MAAM;CAIhC;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,SAAS,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC;IAAE,EAAE,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAA;CAAE,CAAC,CAkClD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,GAAG,CAAC,MAAM,EAMlB,UAAU,GAAE,MAAwB,GACnC,OAAO,CAAC,MAAM,CAAC,CAoMjB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE;IAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EAC/D,YAAY,EAAE,MAAM,GAAG,KAAK,GAC3B;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAQ1C;AAID,OAAO,EAAE,GAAG,EAAE,CAAC"}
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ // Soroban contract payment helpers — shared by raw-keypair (stellar.ts) and
3
+ // OWS-wallet (ows.ts) payment paths. The receiver contract's pay_usdc/pay_xlm
4
+ // functions take (from: Address, amount: i128, order_id: Bytes) and emit a
5
+ // payment event that the cards402 backend watcher indexes.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.xdr = exports.InsufficientFeeError = void 0;
8
+ exports.getSorobanRpcUrl = getSorobanRpcUrl;
9
+ exports.getHorizonUrl = getHorizonUrl;
10
+ exports.decimalToStroops = decimalToStroops;
11
+ exports.buildContractPaymentTx = buildContractPaymentTx;
12
+ exports.submitSorobanTx = submitSorobanTx;
13
+ exports.selectContractCall = selectContractCall;
14
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
15
+ Object.defineProperty(exports, "xdr", { enumerable: true, get: function () { return stellar_sdk_1.xdr; } });
16
+ const MAINNET_RPC = 'https://mainnet.sorobanrpc.com';
17
+ const TESTNET_RPC = 'https://soroban-testnet.stellar.org';
18
+ const MAINNET_HORIZON = 'https://horizon.stellar.org';
19
+ const TESTNET_HORIZON = 'https://horizon-testnet.stellar.org';
20
+ function getSorobanRpcUrl(networkPassphrase) {
21
+ return networkPassphrase === stellar_sdk_1.Networks.TESTNET ? TESTNET_RPC : MAINNET_RPC;
22
+ }
23
+ // F3-soroban (2026-04-16): network-aware Horizon URL. Pre-fix, every
24
+ // Horizon fallback in submitSorobanTx was hardcoded to mainnet.
25
+ // Testnet agents got 404s from mainnet Horizon for their testnet txs,
26
+ // which the SDK interpreted as "tx never applied" with `dropped: true`
27
+ // — triggering the retry loop and risking a double-payment.
28
+ function getHorizonUrl(networkPassphrase) {
29
+ return networkPassphrase === stellar_sdk_1.Networks.TESTNET ? TESTNET_HORIZON : MAINNET_HORIZON;
30
+ }
31
+ /**
32
+ * Convert a decimal string like "10.00" or "1.2345678" to a 7-decimal i128
33
+ * bigint (stroops / micro-USDC). Rejects inputs with >7 fractional digits.
34
+ */
35
+ function decimalToStroops(decimal) {
36
+ if (!/^\d+(\.\d+)?$/.test(decimal)) {
37
+ throw new Error(`Invalid decimal amount: ${decimal}`);
38
+ }
39
+ const parts = decimal.split('.');
40
+ const whole = parts[0] ?? '0';
41
+ const frac = parts[1] ?? '';
42
+ if (frac.length > 7) {
43
+ throw new Error(`Amount has more than 7 decimal places: ${decimal}`);
44
+ }
45
+ const padded = frac.padEnd(7, '0');
46
+ const stroops = BigInt(whole) * 10000000n + BigInt(padded || '0');
47
+ // F1-soroban (2026-04-16): reject zero amounts early. A pay_usdc(0)
48
+ // or pay_xlm(0) invocation costs gas, clutters the ledger with a
49
+ // zero-value event, and the backend watcher dead-letters it (the
50
+ // F4-stellar audit rejects non-positive i128 at parse time). Catching
51
+ // it here saves the agent gas and avoids a confusing dead-letter row.
52
+ if (stroops <= 0n) {
53
+ throw new Error(`Amount must be positive: ${decimal}`);
54
+ }
55
+ return stroops;
56
+ }
57
+ /**
58
+ * Thrown when Soroban rejects a transaction because the offered fee was
59
+ * too low. `requiredFee` is the minimum the network demanded (in stroops),
60
+ * taken directly from the error response — callers should use it as the
61
+ * floor when rebuilding the transaction.
62
+ */
63
+ class InsufficientFeeError extends Error {
64
+ requiredFee;
65
+ constructor(requiredFee) {
66
+ super(`Soroban transaction rejected: fee too low (network requires ${requiredFee} stroops)`);
67
+ this.requiredFee = requiredFee;
68
+ }
69
+ }
70
+ exports.InsufficientFeeError = InsufficientFeeError;
71
+ /**
72
+ * Build a Soroban transaction that invokes the receiver contract's pay_usdc
73
+ * or pay_xlm function. Returns an unsigned, simulation-prepared Transaction
74
+ * ready for the caller to sign (either via keypair or OWS) and submit.
75
+ */
76
+ async function buildContractPaymentTx(opts) {
77
+ const server = new stellar_sdk_1.rpc.Server(opts.rpcUrl ?? getSorobanRpcUrl(opts.networkPassphrase));
78
+ // When preservedSequence is set, build the Account manually from
79
+ // the pre-bump value (N-1) so TransactionBuilder's implicit
80
+ // incrementSequenceNumber() call lands exactly on the requested N.
81
+ // Otherwise, load the current on-chain state.
82
+ const account = opts.preservedSequence
83
+ ? new stellar_sdk_1.Account(opts.fromPublicKey, (BigInt(opts.preservedSequence) - 1n).toString())
84
+ : await server.getAccount(opts.fromPublicKey);
85
+ const contract = new stellar_sdk_1.Contract(opts.contractId);
86
+ const orderIdBytes = Buffer.from(opts.orderId, 'utf-8');
87
+ const op = contract.call(opts.fn, new stellar_sdk_1.Address(opts.fromPublicKey).toScVal(), (0, stellar_sdk_1.nativeToScVal)(opts.amountStroops, { type: 'i128' }), (0, stellar_sdk_1.nativeToScVal)(orderIdBytes, { type: 'bytes' }));
88
+ const raw = new stellar_sdk_1.TransactionBuilder(account, {
89
+ fee: opts.fee ?? stellar_sdk_1.BASE_FEE,
90
+ networkPassphrase: opts.networkPassphrase,
91
+ })
92
+ .addOperation(op)
93
+ .setTimeout(180)
94
+ .build();
95
+ const sim = await server.simulateTransaction(raw);
96
+ if (stellar_sdk_1.rpc.Api.isSimulationError(sim)) {
97
+ throw new Error(`Soroban simulation failed: ${sim.error}`);
98
+ }
99
+ const prepared = stellar_sdk_1.rpc.assembleTransaction(raw, sim).build();
100
+ return { tx: prepared, server };
101
+ }
102
+ /**
103
+ * Submit a signed Soroban transaction and poll until it reaches a terminal
104
+ * status. Returns the transaction hash on success.
105
+ *
106
+ * Important: a successful return only guarantees the tx has been accepted
107
+ * onto the ledger. The cards402 backend's contract watcher is the source
108
+ * of truth for "the order has been credited" — the SDK's purchaseCardOWS
109
+ * always follows up with waitForCard against the order id, so even if
110
+ * this function gives up before finalization, the watcher still has a
111
+ * chance to credit the order if the tx eventually lands.
112
+ */
113
+ async function submitSorobanTx(tx, server,
114
+ // F3-soroban (2026-04-16): callers pass the network-aware Horizon URL
115
+ // so the fallback checks hit the correct network. Pre-fix, all three
116
+ // fetch sites below were hardcoded to mainnet. Default to mainnet for
117
+ // backward compat — callers that don't pass this parameter get the
118
+ // same behaviour as before.
119
+ horizonUrl = MAINNET_HORIZON) {
120
+ // sendTransaction is idempotent for the same envelope. Three cases we
121
+ // retry explicitly:
122
+ // - TRY_AGAIN_LATER: RPC is congested, re-send after a short wait
123
+ // - thrown network error (fetch/DNS/TCP): the request never reached
124
+ // the RPC, safe to retry
125
+ // - DUPLICATE is treated as "already in flight" — fall through to
126
+ // the polling loop below instead of looping forever
127
+ // Up to 5 total attempts with 1.5s spacing covers typical RPC drops.
128
+ const MAX_SEND_ATTEMPTS = 5;
129
+ let send;
130
+ let sendErr;
131
+ for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt++) {
132
+ try {
133
+ send = await server.sendTransaction(tx);
134
+ if (send.status === 'TRY_AGAIN_LATER') {
135
+ if (attempt < MAX_SEND_ATTEMPTS - 1) {
136
+ await new Promise((r) => setTimeout(r, 1500));
137
+ continue;
138
+ }
139
+ throw new Error(`Soroban network congested — sendTransaction returned TRY_AGAIN_LATER after ${MAX_SEND_ATTEMPTS} attempts. Retry the purchase in a minute.`);
140
+ }
141
+ if (send.status === 'ERROR') {
142
+ // txInsufficientFee — the network tells us what fee it needed.
143
+ // Surface it as InsufficientFeeError so callers can rebuild
144
+ // with the required fee as the floor and retry immediately.
145
+ try {
146
+ if (send.errorResult?.result().switch().name === 'txInsufficientFee') {
147
+ throw new InsufficientFeeError(send.errorResult.feeCharged().toString());
148
+ }
149
+ }
150
+ catch (feeErr) {
151
+ if (feeErr instanceof InsufficientFeeError)
152
+ throw feeErr;
153
+ // XDR parsing failed — fall through to the generic error.
154
+ }
155
+ throw new Error(`Soroban sendTransaction error: ${JSON.stringify(send.errorResult ?? send)}`);
156
+ }
157
+ // PENDING or DUPLICATE — envelope is now known to the network. Break
158
+ // out of the retry loop and poll for finalization.
159
+ break;
160
+ }
161
+ catch (err) {
162
+ sendErr = err;
163
+ // If the thrown error is structured (TRY_AGAIN_LATER, ERROR) let it
164
+ // propagate directly. Otherwise assume it was a transient network
165
+ // failure and retry.
166
+ if (err instanceof InsufficientFeeError ||
167
+ (err instanceof Error &&
168
+ (err.message.startsWith('Soroban network congested') ||
169
+ err.message.startsWith('Soroban sendTransaction error')))) {
170
+ throw err;
171
+ }
172
+ if (attempt >= MAX_SEND_ATTEMPTS - 1) {
173
+ throw new Error(`Soroban sendTransaction failed after ${MAX_SEND_ATTEMPTS} attempts: ${err?.message ?? String(err)}`);
174
+ }
175
+ await new Promise((r) => setTimeout(r, 1500));
176
+ }
177
+ }
178
+ if (!send) {
179
+ throw new Error(`Soroban sendTransaction produced no result: ${sendErr?.message ?? 'unknown error'}`);
180
+ }
181
+ // Poll for finalization. 120s deadline (was 60s) so we don't bail on
182
+ // mainnet RPC under modest backpressure. Each poll is cheap; the cost
183
+ // of a longer wait is much less than the cost of a stranded order.
184
+ // Some stellar-sdk versions can't parse newer Soroban RPC response XDR
185
+ // ("Bad union switch: 4"); when that happens, fall back to Horizon.
186
+ //
187
+ // Error-handling contract — changed 2026-04-14 after we shipped a
188
+ // stranded-order bug where FAILED statuses from getTransaction were
189
+ // silently caught by the inner try/catch and the loop kept polling
190
+ // until the deadline, at which point a txHash-attached error was
191
+ // thrown and purchaseCardOWS fell through to waitForCard on a tx
192
+ // that had actually failed on-chain. The new rules are:
193
+ //
194
+ // - Any throw whose message starts with "Soroban transaction " is
195
+ // one of OUR OWN terminal throws; the catch re-raises it out of
196
+ // the while loop unchanged.
197
+ // - A `txHash` is only attached to the final timeout error when we
198
+ // genuinely believe the tx MAY still land on the ledger later
199
+ // (Horizon unreachable). In every other terminal state — tx
200
+ // applied and failed, or Horizon confirms the tx never made a
201
+ // ledger — we throw a plain Error, which purchaseCardOWS will
202
+ // propagate to the caller instead of entering waitForCard.
203
+ const POLL_DEADLINE_MS = 120_000;
204
+ const deadline = Date.now() + POLL_DEADLINE_MS;
205
+ while (Date.now() < deadline) {
206
+ try {
207
+ const status = await server.getTransaction(send.hash);
208
+ if (status.status === 'SUCCESS')
209
+ return send.hash;
210
+ if (status.status === 'FAILED') {
211
+ throw new Error(`Soroban transaction ${send.hash} failed on-chain`);
212
+ }
213
+ // NOT_FOUND (or any transient non-terminal status): keep polling.
214
+ }
215
+ catch (pollErr) {
216
+ // Re-raise our own terminal throws so they escape the loop.
217
+ if (pollErr instanceof Error &&
218
+ pollErr.message.startsWith(`Soroban transaction ${send.hash}`)) {
219
+ throw pollErr;
220
+ }
221
+ // "Bad union switch" = XDR version mismatch between SDK and RPC.
222
+ // The TX was accepted by sendTransaction; confirm via Horizon instead.
223
+ if (String(pollErr?.message || '').includes('Bad union switch')) {
224
+ try {
225
+ const horizonResp = await fetch(`${horizonUrl}/transactions/${send.hash}`);
226
+ if (horizonResp.ok) {
227
+ const horizonData = (await horizonResp.json());
228
+ if (horizonData.successful)
229
+ return send.hash;
230
+ // Horizon can see it and it failed — terminal, no recovery.
231
+ throw new Error(`Soroban transaction ${send.hash} failed on-chain (Horizon)`);
232
+ }
233
+ }
234
+ catch (horizonErr) {
235
+ // Re-raise our own terminal Horizon throws
236
+ if (horizonErr instanceof Error &&
237
+ horizonErr.message.startsWith(`Soroban transaction ${send.hash}`)) {
238
+ throw horizonErr;
239
+ }
240
+ /* Horizon unreachable — keep polling Soroban RPC */
241
+ }
242
+ }
243
+ // Any other poll-layer error: treat as transient, keep polling.
244
+ }
245
+ await new Promise((r) => setTimeout(r, 2000));
246
+ }
247
+ // Deadline reached without a terminal status. Before giving up, ask
248
+ // Horizon what actually happened to the tx — it's the authoritative
249
+ // record of what hit a ledger, independent of Soroban RPC's state.
250
+ try {
251
+ const horizonResp = await fetch(`${horizonUrl}/transactions/${send.hash}`);
252
+ if (horizonResp.ok) {
253
+ const horizonData = (await horizonResp.json());
254
+ if (horizonData.successful)
255
+ return send.hash;
256
+ // Tx landed and failed. No recovery path — throw without txHash
257
+ // so the caller propagates the error instead of waiting on a card
258
+ // that will never come.
259
+ throw new Error(`Soroban transaction ${send.hash} applied on-chain but failed — no card will be credited. Retry the purchase.`);
260
+ }
261
+ if (horizonResp.status === 404) {
262
+ // Tx was accepted by Soroban RPC (we have a hash) but it never
263
+ // made it into a ledger on Horizon. This is the "network rejected
264
+ // at apply time" case — e.g., source account sequence drifted,
265
+ // host function errored before any effects landed. It is NOT
266
+ // going to "eventually land".
267
+ //
268
+ // Attach both txHash and a structured `dropped: true` marker.
269
+ // payViaContractOWS's retry loop uses the marker to decide whether
270
+ // to resubmit with the same sequence (guaranteeing mutual
271
+ // exclusion). Without the marker, the retry layer can't
272
+ // distinguish this case from "on-chain failure" (where retrying
273
+ // would be incorrect because the sequence was consumed by the
274
+ // failed tx).
275
+ const droppedErr = new Error(`Soroban transaction ${send.hash} was accepted by the RPC but never applied on the ledger within ${POLL_DEADLINE_MS / 1000}s — the network rejected it pre-apply. Retry the purchase.`);
276
+ droppedErr.txHash = send.hash;
277
+ droppedErr.dropped = true;
278
+ throw droppedErr;
279
+ }
280
+ }
281
+ catch (horizonErr) {
282
+ // Re-raise our own terminal Horizon throws; swallow network errors.
283
+ if (horizonErr instanceof Error &&
284
+ horizonErr.message.startsWith(`Soroban transaction ${send.hash}`)) {
285
+ throw horizonErr;
286
+ }
287
+ /* Horizon itself is unreachable — fall through to the timeout
288
+ error below, where we attach txHash as a last-resort safety net.
289
+ The watcher may still credit the order if the tx eventually
290
+ lands and Horizon comes back up. */
291
+ }
292
+ // Soroban RPC never saw a terminal state AND Horizon is unreachable
293
+ // (or the response was neither 2xx nor 404). We genuinely don't know
294
+ // whether the tx landed, so attach txHash and let purchaseCardOWS
295
+ // give the cards402 backend watcher a chance to credit the order.
296
+ const err = new Error(`Soroban transaction ${send.hash} did not finalize within ${POLL_DEADLINE_MS / 1000}s and Horizon is unreachable`);
297
+ err.txHash = send.hash;
298
+ throw err;
299
+ }
300
+ /**
301
+ * Decide which contract function + asset amount to use based on the
302
+ * PaymentInstructions returned from POST /v1/orders.
303
+ */
304
+ function selectContractCall(payment, paymentAsset) {
305
+ if (paymentAsset === 'xlm') {
306
+ if (!payment.xlm?.amount) {
307
+ throw new Error('Order response does not include an XLM quote');
308
+ }
309
+ return { fn: 'pay_xlm', amountDecimal: payment.xlm.amount };
310
+ }
311
+ return { fn: 'pay_usdc', amountDecimal: payment.usdc.amount };
312
+ }
313
+ //# sourceMappingURL=soroban.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soroban.js","sourceRoot":"","sources":["../src/soroban.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { Networks } from '@stellar/stellar-sdk';
2
+ import type { CardDetails, PaymentInstructions } from './client';
3
+ export interface WalletInfo {
4
+ publicKey: string;
5
+ secret: string;
6
+ }
7
+ export declare function createWallet(): WalletInfo;
8
+ export declare function getBalance(publicKey: string, networkPassphrase?: string): Promise<{
9
+ xlm: string;
10
+ usdc: string;
11
+ }>;
12
+ export declare function addUsdcTrustline(secret: string, networkPassphrase?: Networks): Promise<string>;
13
+ export interface PayOpts {
14
+ walletSecret: string;
15
+ payment: PaymentInstructions;
16
+ paymentAsset?: 'usdc' | 'xlm';
17
+ networkPassphrase?: string;
18
+ sorobanRpcUrl?: string;
19
+ }
20
+ /**
21
+ * Pay the cards402 receiver contract using a raw Stellar secret key.
22
+ * Invokes pay_usdc or pay_xlm with the agent's G-address, the quoted amount
23
+ * converted to 7-decimal stroops, and the order_id from the create-order
24
+ * response. Returns the Soroban transaction hash.
25
+ */
26
+ export declare function payViaContract(opts: PayOpts): Promise<string>;
27
+ /**
28
+ * Full purchase flow with a raw keypair: create order → invoke contract →
29
+ * wait for card. Pass `resume: { orderId, payment }` to re-enter a partially
30
+ * completed flow without creating a new order (S-9).
31
+ */
32
+ export declare function purchaseCard(opts: {
33
+ apiKey: string;
34
+ walletSecret: string;
35
+ amountUsdc: string;
36
+ paymentAsset?: 'usdc' | 'xlm';
37
+ baseUrl?: string;
38
+ networkPassphrase?: string;
39
+ sorobanRpcUrl?: string;
40
+ resume?: {
41
+ orderId: string;
42
+ payment: PaymentInstructions;
43
+ };
44
+ waitForCardOpts?: {
45
+ timeoutMs?: number;
46
+ intervalMs?: number;
47
+ };
48
+ }): Promise<CardDetails & {
49
+ order_id: string;
50
+ }>;
51
+ /** @deprecated Use `payViaContract` — this is the Soroban contract call, not a direct Stellar payment. */
52
+ export declare const payVCC: typeof payViaContract;
53
+ //# sourceMappingURL=stellar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stellar.d.ts","sourceRoot":"","sources":["../src/stellar.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,QAAQ,EAOT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAA8B,MAAM,UAAU,CAAC;AAuC7F,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,IAAI,UAAU,CAGzC;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAexC;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,iBAAiB,WAAkB,GAClC,OAAO,CAAC,MAAM,CAAC,CAWjB;AAID,MAAM,WAAW,OAAO;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA6CnE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,CAAC;IAC3D,eAAe,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D,GAAG,OAAO,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC9C;AAKD,0GAA0G;AAC1G,eAAO,MAAM,MAAM,uBAAiB,CAAC"}
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // Helpers for agents using a raw Stellar keypair (S...) to pay the cards402
3
+ // receiver contract on Soroban. For OWS-wallet custody, see ./ows.ts.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.payVCC = void 0;
39
+ exports.createWallet = createWallet;
40
+ exports.getBalance = getBalance;
41
+ exports.addUsdcTrustline = addUsdcTrustline;
42
+ exports.payViaContract = payViaContract;
43
+ exports.purchaseCard = purchaseCard;
44
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
45
+ const soroban_1 = require("./soroban");
46
+ const USDC_ISSUER = 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN';
47
+ const HORIZON_TIMEOUT_MS = 15000;
48
+ function assertSorobanPayment(payment) {
49
+ if (payment.type !== 'soroban_contract') {
50
+ throw new Error(`Expected soroban_contract payment instructions, got ${payment.type}`);
51
+ }
52
+ }
53
+ function getHorizonUrl(networkPassphrase) {
54
+ return networkPassphrase === stellar_sdk_1.Networks.TESTNET
55
+ ? 'https://horizon-testnet.stellar.org'
56
+ : 'https://horizon.stellar.org';
57
+ }
58
+ function getServer(networkPassphrase) {
59
+ return new stellar_sdk_1.Horizon.Server(getHorizonUrl(networkPassphrase));
60
+ }
61
+ function withTimeout(promise, ms = HORIZON_TIMEOUT_MS) {
62
+ return Promise.race([
63
+ promise,
64
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Horizon request timed out after ${ms}ms`)), ms)),
65
+ ]);
66
+ }
67
+ function createWallet() {
68
+ const keypair = stellar_sdk_1.Keypair.random();
69
+ return { publicKey: keypair.publicKey(), secret: keypair.secret() };
70
+ }
71
+ async function getBalance(publicKey, networkPassphrase) {
72
+ const server = getServer(networkPassphrase);
73
+ const account = await withTimeout(server.loadAccount(publicKey));
74
+ let xlm = '0', usdc = '0';
75
+ for (const b of account.balances) {
76
+ if (b.asset_type === 'native')
77
+ xlm = b.balance;
78
+ if (b.asset_type === 'credit_alphanum4' &&
79
+ b.asset_code === 'USDC' &&
80
+ b.asset_issuer === USDC_ISSUER)
81
+ usdc = b.balance;
82
+ }
83
+ return { xlm, usdc };
84
+ }
85
+ async function addUsdcTrustline(secret, networkPassphrase = stellar_sdk_1.Networks.PUBLIC) {
86
+ const server = getServer(networkPassphrase);
87
+ const keypair = stellar_sdk_1.Keypair.fromSecret(secret);
88
+ const account = await withTimeout(server.loadAccount(keypair.publicKey()));
89
+ const tx = new stellar_sdk_1.TransactionBuilder(account, { fee: stellar_sdk_1.BASE_FEE, networkPassphrase })
90
+ .addOperation(stellar_sdk_1.Operation.changeTrust({ asset: new stellar_sdk_1.Asset('USDC', USDC_ISSUER) }))
91
+ .setTimeout(300)
92
+ .build();
93
+ tx.sign(keypair);
94
+ const result = await server.submitTransaction(tx);
95
+ return result.hash;
96
+ }
97
+ /**
98
+ * Pay the cards402 receiver contract using a raw Stellar secret key.
99
+ * Invokes pay_usdc or pay_xlm with the agent's G-address, the quoted amount
100
+ * converted to 7-decimal stroops, and the order_id from the create-order
101
+ * response. Returns the Soroban transaction hash.
102
+ */
103
+ async function payViaContract(opts) {
104
+ const { walletSecret, payment, paymentAsset = 'usdc', networkPassphrase = stellar_sdk_1.Networks.PUBLIC, sorobanRpcUrl, } = opts;
105
+ assertSorobanPayment(payment);
106
+ if (!stellar_sdk_1.StrKey.isValidContract(payment.contract_id)) {
107
+ throw new Error(`Invalid contract_id in order response: ${payment.contract_id}`);
108
+ }
109
+ const keypair = stellar_sdk_1.Keypair.fromSecret(walletSecret);
110
+ const { fn, amountDecimal } = (0, soroban_1.selectContractCall)(payment, paymentAsset);
111
+ const amountStroops = (0, soroban_1.decimalToStroops)(amountDecimal);
112
+ // Fee retry: if the network rejects the fee, rebuild with the
113
+ // required fee as the floor. At most one retry — the network's
114
+ // suggested fee should be sufficient on the second attempt.
115
+ let fee;
116
+ for (let attempt = 0; attempt < 2; attempt++) {
117
+ const { tx, server } = await (0, soroban_1.buildContractPaymentTx)({
118
+ contractId: payment.contract_id,
119
+ fn,
120
+ fromPublicKey: keypair.publicKey(),
121
+ amountStroops,
122
+ orderId: payment.order_id,
123
+ networkPassphrase,
124
+ rpcUrl: sorobanRpcUrl,
125
+ fee,
126
+ });
127
+ tx.sign(keypair);
128
+ try {
129
+ return await (0, soroban_1.submitSorobanTx)(tx, server, getHorizonUrl(networkPassphrase));
130
+ }
131
+ catch (err) {
132
+ if (err instanceof soroban_1.InsufficientFeeError && attempt === 0) {
133
+ fee = err.requiredFee;
134
+ continue;
135
+ }
136
+ throw err;
137
+ }
138
+ }
139
+ throw new Error('payViaContract: fee retry exhausted');
140
+ }
141
+ /**
142
+ * Full purchase flow with a raw keypair: create order → invoke contract →
143
+ * wait for card. Pass `resume: { orderId, payment }` to re-enter a partially
144
+ * completed flow without creating a new order (S-9).
145
+ */
146
+ async function purchaseCard(opts) {
147
+ const { Cards402Client } = await Promise.resolve().then(() => __importStar(require('./client')));
148
+ const client = new Cards402Client({ apiKey: opts.apiKey, baseUrl: opts.baseUrl });
149
+ const paymentAsset = opts.paymentAsset ?? 'usdc';
150
+ let orderId;
151
+ let payment;
152
+ if (opts.resume) {
153
+ orderId = opts.resume.orderId;
154
+ payment = opts.resume.payment;
155
+ const status = await client.getOrder(orderId);
156
+ if (status.phase !== 'awaiting_payment') {
157
+ const card = await client.waitForCard(orderId, opts.waitForCardOpts);
158
+ return { ...card, order_id: orderId };
159
+ }
160
+ }
161
+ else {
162
+ const order = await client.createOrder({ amount_usdc: opts.amountUsdc });
163
+ orderId = order.order_id;
164
+ payment = order.payment;
165
+ }
166
+ await payViaContract({
167
+ walletSecret: opts.walletSecret,
168
+ payment,
169
+ paymentAsset,
170
+ networkPassphrase: opts.networkPassphrase,
171
+ sorobanRpcUrl: opts.sorobanRpcUrl,
172
+ });
173
+ const card = await client.waitForCard(orderId, opts.waitForCardOpts);
174
+ return { ...card, order_id: orderId };
175
+ }
176
+ // Back-compat aliases — the pre-V3 SDK exposed these names. Keep them around
177
+ // as deprecated exports so existing imports don't break on upgrade.
178
+ /** @deprecated Use `payViaContract` — this is the Soroban contract call, not a direct Stellar payment. */
179
+ exports.payVCC = payViaContract;
180
+ //# sourceMappingURL=stellar.js.map