@mystars-tg/faas-wallet 0.1.2
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/index.cjs +291 -0
- package/dist/index.d.cts +327 -0
- package/dist/index.d.ts +327 -0
- package/dist/index.js +281 -0
- package/package.json +48 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import * as _ton_core from '@ton/core';
|
|
2
|
+
import { MessageRelaxed, SendMode, StateInit, Cell } from '@ton/core';
|
|
3
|
+
import { KeyPair } from '@ton/crypto';
|
|
4
|
+
import { PaymentInstruction, CreateOrderResult, CreateOrderParams, CreateOrderOptions, WaitForOrderOptions, Order } from '@mystars-tg/faas-sdk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The minimal TON RPC surface the wallet + payer need. Inject a custom one for
|
|
8
|
+
* tests (so nothing touches the network or moves funds); use `ToncenterRpc` in
|
|
9
|
+
* production.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The minimal TON RPC surface the wallet + payer depend on. Implement it (or
|
|
13
|
+
* use {@link ToncenterRpc}) to read balances/seqno, resolve jetton wallets, and
|
|
14
|
+
* broadcast. Inject a mock in tests so nothing touches the network or moves funds.
|
|
15
|
+
*/
|
|
16
|
+
interface TonRpc {
|
|
17
|
+
/** Account TON balance, in nanoTON. */
|
|
18
|
+
getBalance(address: string): Promise<bigint>;
|
|
19
|
+
/** The wallet's current seqno (0 if the wallet contract isn't deployed yet). */
|
|
20
|
+
getSeqno(address: string): Promise<number>;
|
|
21
|
+
/** Resolve an owner's jetton wallet address from the jetton master (get_wallet_address). */
|
|
22
|
+
resolveJettonWallet(owner: string, jettonMaster: string): Promise<string>;
|
|
23
|
+
/** A jetton wallet's token balance, in the jetton's smallest unit (get_wallet_data). */
|
|
24
|
+
getJettonBalance(jettonWallet: string): Promise<bigint>;
|
|
25
|
+
/** Broadcast a serialized external-message BoC. */
|
|
26
|
+
sendBoc(boc: Uint8Array): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/** Options for {@link ToncenterRpc}. */
|
|
29
|
+
interface ToncenterRpcOptions {
|
|
30
|
+
/** JSON-RPC endpoint, e.g. `https://toncenter.com/api/v2/jsonRPC` (or a testnet URL). */
|
|
31
|
+
endpoint: string;
|
|
32
|
+
/** toncenter API key (recommended to avoid tight rate limits). */
|
|
33
|
+
apiKey?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default {@link TonRpc} backed by toncenter via `@ton/ton`'s `TonClient`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const rpc = new ToncenterRpc({
|
|
41
|
+
* endpoint: "https://toncenter.com/api/v2/jsonRPC",
|
|
42
|
+
* apiKey: process.env.TONCENTER_KEY,
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare class ToncenterRpc implements TonRpc {
|
|
47
|
+
private readonly client;
|
|
48
|
+
/** @param opts - the {@link ToncenterRpcOptions} (endpoint + optional API key) */
|
|
49
|
+
constructor(opts: ToncenterRpcOptions);
|
|
50
|
+
/** {@inheritDoc TonRpc.getBalance} */
|
|
51
|
+
getBalance(address: string): Promise<bigint>;
|
|
52
|
+
/** {@inheritDoc TonRpc.getSeqno} */
|
|
53
|
+
getSeqno(address: string): Promise<number>;
|
|
54
|
+
/** {@inheritDoc TonRpc.resolveJettonWallet} */
|
|
55
|
+
resolveJettonWallet(owner: string, jettonMaster: string): Promise<string>;
|
|
56
|
+
/** {@inheritDoc TonRpc.getJettonBalance} */
|
|
57
|
+
getJettonBalance(jettonWallet: string): Promise<bigint>;
|
|
58
|
+
/** {@inheritDoc TonRpc.sendBoc} */
|
|
59
|
+
sendBoc(boc: Uint8Array): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Base error for the wallet module. */
|
|
63
|
+
declare class WalletError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
66
|
+
/** The wallet doesn't hold enough TON / jetton balance to cover a payment. */
|
|
67
|
+
declare class InsufficientBalanceError extends WalletError {
|
|
68
|
+
}
|
|
69
|
+
/** Arguments for {@link TonWallet.createTransfer} — the unsigned pieces of a wallet-v4 transfer. */
|
|
70
|
+
interface CreateTransferArgs {
|
|
71
|
+
/** The wallet's current seqno (fetch via {@link TonWallet.getSeqno}); 0 deploys the contract. */
|
|
72
|
+
seqno: number;
|
|
73
|
+
/** The internal messages to send in this transfer. */
|
|
74
|
+
messages: MessageRelaxed[];
|
|
75
|
+
/** The send mode flags (`@ton/core` `SendMode`). */
|
|
76
|
+
sendMode: SendMode;
|
|
77
|
+
/** Transfer validity window as a unix-seconds expiry; the wallet rejects it after this. */
|
|
78
|
+
timeout?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* An in-memory TON wallet (WalletContractV4). Construct via {@link TonWallet.generate} /
|
|
82
|
+
* {@link TonWallet.fromMnemonic} / {@link TonWallet.fromKeyPair}. Keys never leave memory and are
|
|
83
|
+
* redacted from serialization. See the module overview for the custody stance.
|
|
84
|
+
*/
|
|
85
|
+
declare class TonWallet {
|
|
86
|
+
/** The in-memory key pair. Treat `secretKey` as a secret — never log or persist it. */
|
|
87
|
+
readonly keyPair: KeyPair;
|
|
88
|
+
private readonly contract;
|
|
89
|
+
private constructor();
|
|
90
|
+
/** Redacted serialization — NEVER exposes the secret key. */
|
|
91
|
+
toJSON(): {
|
|
92
|
+
address: string;
|
|
93
|
+
publicKey: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Generate a NEW wallet. Returns the 24-word mnemonic ONCE — store it securely;
|
|
97
|
+
* it is never persisted.
|
|
98
|
+
*
|
|
99
|
+
* @returns the in-memory `wallet` and its `mnemonic` (the only time you see it)
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const { wallet, mnemonic } = await TonWallet.generate();
|
|
103
|
+
* await secureVault.store(mnemonic); // YOUR job — it is never written to disk by the SDK
|
|
104
|
+
* console.log("fund this:", wallet.address);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
static generate(): Promise<{
|
|
108
|
+
wallet: TonWallet;
|
|
109
|
+
mnemonic: string[];
|
|
110
|
+
}>;
|
|
111
|
+
/** Import a wallet from its 24-word mnemonic. */
|
|
112
|
+
static fromMnemonic(words: string[]): Promise<TonWallet>;
|
|
113
|
+
/** Import a wallet from a raw Ed25519 key pair. */
|
|
114
|
+
static fromKeyPair(publicKey: Uint8Array, secretKey: Uint8Array): TonWallet;
|
|
115
|
+
/** Friendly, non-bounceable (`UQ…`) address — FUND THIS to pay invoices. */
|
|
116
|
+
get address(): string;
|
|
117
|
+
/** Raw `0:hex` address form. */
|
|
118
|
+
get rawAddress(): string;
|
|
119
|
+
/** @internal The contract's StateInit, needed for the first (deploying) transfer. */
|
|
120
|
+
get init(): StateInit;
|
|
121
|
+
/** @internal The contract address as a TON `Address`. */
|
|
122
|
+
get tonAddress(): _ton_core.Address;
|
|
123
|
+
/** @internal Sign a transfer body. */
|
|
124
|
+
createTransfer(args: CreateTransferArgs): Cell;
|
|
125
|
+
/**
|
|
126
|
+
* This wallet's native TON balance.
|
|
127
|
+
*
|
|
128
|
+
* @param rpc - the RPC to query through
|
|
129
|
+
* @returns the balance in nanoTON
|
|
130
|
+
*/
|
|
131
|
+
getBalance(rpc: TonRpc): Promise<bigint>;
|
|
132
|
+
/**
|
|
133
|
+
* This wallet's current sequence number.
|
|
134
|
+
*
|
|
135
|
+
* @param rpc - the RPC to query through
|
|
136
|
+
* @returns the seqno (`0` when the wallet contract isn't deployed yet)
|
|
137
|
+
*/
|
|
138
|
+
getSeqno(rpc: TonRpc): Promise<number>;
|
|
139
|
+
/**
|
|
140
|
+
* This wallet's balance of a given jetton (e.g. USDT).
|
|
141
|
+
*
|
|
142
|
+
* @param rpc - the RPC to query through
|
|
143
|
+
* @param jettonMaster - the jetton master address (e.g. `DEFAULT_USDT_MASTER` from `@mystars-tg/faas-wallet`)
|
|
144
|
+
* @returns the token balance in the jetton's smallest unit (micro-USDT for USDT)
|
|
145
|
+
*/
|
|
146
|
+
getJettonBalance(rpc: TonRpc, jettonMaster: string): Promise<bigint>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* `OrderPayer` — pay a FaaS order invoice from your own `TonWallet`.
|
|
151
|
+
*
|
|
152
|
+
* Signs ONCE and broadcasts. For `ton` it sends an exact-amount transfer with the
|
|
153
|
+
* order memo as an op-0 comment; for `usdt_ton` it resolves the payer's own USDT
|
|
154
|
+
* jetton wallet and sends a TEP-74 transfer whose `destination` is the FaaS
|
|
155
|
+
* `pay_to_address` and whose forward payload carries the memo.
|
|
156
|
+
*
|
|
157
|
+
* NOTE: this moves the PARTNER's own funds from the partner's own wallet — never
|
|
158
|
+
* the MyStars treasury. Tests use a mock `TonRpc` so nothing broadcasts.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/** Mainnet Tether USDT jetton master on TON. Override via `PayOrderOptions.jettonMaster` for testnet. */
|
|
162
|
+
declare const DEFAULT_USDT_MASTER = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs";
|
|
163
|
+
/** TON attached to a USDT jetton transfer to cover its gas (excess refunds to the sender). Matches the core invoice builder. */
|
|
164
|
+
declare const DEFAULT_JETTON_GAS_TON = "0.05";
|
|
165
|
+
/** Options for {@link OrderPayer.payOrder} / {@link OrderPayer.planMessages}. */
|
|
166
|
+
interface PayOrderOptions {
|
|
167
|
+
/** The RPC used to resolve the jetton wallet, read balances, and broadcast. */
|
|
168
|
+
rpc: TonRpc;
|
|
169
|
+
/** USDT jetton master (defaults to mainnet Tether). */
|
|
170
|
+
jettonMaster?: string;
|
|
171
|
+
/** TON gas attached to a USDT transfer (default 0.05). */
|
|
172
|
+
jettonGasTon?: string;
|
|
173
|
+
/** Transfer validity window in seconds (default 120 — a non-landed send dies fast, safe to retry). */
|
|
174
|
+
validForSeconds?: number;
|
|
175
|
+
/** Skip the pre-sign balance check (saves 1-2 RPC calls; default false). */
|
|
176
|
+
skipBalanceCheck?: boolean;
|
|
177
|
+
/** Injectable clock (epoch ms) for deterministic tests. */
|
|
178
|
+
now?: number;
|
|
179
|
+
}
|
|
180
|
+
/** The outcome of a broadcast payment from {@link OrderPayer.payOrder}. */
|
|
181
|
+
interface PayOrderResult {
|
|
182
|
+
/** The order id paid. */
|
|
183
|
+
orderId?: string;
|
|
184
|
+
/** The paying wallet address. */
|
|
185
|
+
from: string;
|
|
186
|
+
/** The recipient (FaaS treasury owner) address. */
|
|
187
|
+
to: string;
|
|
188
|
+
/** The smallest-unit amount sent (nanoTON for ton, micro-USDT for usdt_ton). */
|
|
189
|
+
amountSmallestUnit: string;
|
|
190
|
+
}
|
|
191
|
+
/** An internal message the payer will sign into a transfer. Exposed for testing. */
|
|
192
|
+
interface PlannedMessage {
|
|
193
|
+
to: string;
|
|
194
|
+
value: bigint;
|
|
195
|
+
body: Cell;
|
|
196
|
+
bounce: boolean;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Pays a FaaS order invoice from a single {@link TonWallet}.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* const order = await client.createOrder(
|
|
204
|
+
* { type: "stars", recipient: { username: "durov" }, quantity: 100 },
|
|
205
|
+
* { idempotencyKey: `order-${myId}` },
|
|
206
|
+
* );
|
|
207
|
+
* const rpc = new ToncenterRpc({ endpoint: "https://toncenter.com/api/v2/jsonRPC" });
|
|
208
|
+
* const { from, to, amountSmallestUnit } = await new OrderPayer(wallet).payOrder(order, { rpc });
|
|
209
|
+
* const finished = await client.waitForOrder(order.order_id);
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
declare class OrderPayer {
|
|
213
|
+
private readonly wallet;
|
|
214
|
+
/** @param wallet - the funded {@link TonWallet} whose own funds will pay invoices */
|
|
215
|
+
constructor(wallet: TonWallet);
|
|
216
|
+
/**
|
|
217
|
+
* Plan the internal message(s) for an order WITHOUT signing or broadcasting.
|
|
218
|
+
* `ton` is pure; `usdt_ton` resolves the payer's jetton wallet via `rpc`.
|
|
219
|
+
*/
|
|
220
|
+
planMessages(payment: PaymentInstruction, opts: PayOrderOptions): Promise<PlannedMessage[]>;
|
|
221
|
+
/** Throw `InsufficientBalanceError` if the wallet can't cover the planned payment + gas. */
|
|
222
|
+
private assertFunded;
|
|
223
|
+
/**
|
|
224
|
+
* Build, sign, and broadcast the payment for an order. Signs exactly ONCE.
|
|
225
|
+
*
|
|
226
|
+
* MONEY: this broadcasts a real on-chain transfer of the wallet's OWN funds.
|
|
227
|
+
* It is not idempotent — calling it twice for the same order pays twice. Guard
|
|
228
|
+
* retries at the order layer (a stable `Idempotency-Key` on `createOrder`, or
|
|
229
|
+
* use `fulfill` which only pays an order still `awaiting_payment`).
|
|
230
|
+
*
|
|
231
|
+
* @param order - a `CreateOrderResult`, or any object carrying a `payment` block (and optional `order_id`)
|
|
232
|
+
* @param opts - the {@link PayOrderOptions} (at minimum `rpc`)
|
|
233
|
+
* @returns the {@link PayOrderResult} — `from`/`to` addresses and the smallest-unit amount sent
|
|
234
|
+
* @throws `WalletError` if the order's `payment` block is missing `pay_to_address`/`memo`
|
|
235
|
+
* @throws `MyStarsValidationError` if the amount is non-positive
|
|
236
|
+
* @throws {@link InsufficientBalanceError} if the wallet can't cover the payment + gas (unless `skipBalanceCheck`)
|
|
237
|
+
*/
|
|
238
|
+
payOrder(order: CreateOrderResult | {
|
|
239
|
+
payment: PaymentInstruction;
|
|
240
|
+
order_id?: string;
|
|
241
|
+
}, opts: PayOrderOptions): Promise<PayOrderResult>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* `fulfill()` — the one-call convenience: create an order, pay it from your
|
|
246
|
+
* wallet, and wait until it's delivered (or failed/reversed/expired).
|
|
247
|
+
*
|
|
248
|
+
* RETRY HAZARD (money): this broadcasts a REAL payment. A naive retry of a
|
|
249
|
+
* `fulfill` that already broadcast would create a SECOND order and pay it AGAIN.
|
|
250
|
+
* Two safeguards make retries safe:
|
|
251
|
+
* 1. A stable `idempotencyKey` is REQUIRED — re-running `fulfill` with the same
|
|
252
|
+
* key returns the SAME order from the server (idempotent replay) instead of
|
|
253
|
+
* minting a duplicate.
|
|
254
|
+
* 2. We only broadcast when the (possibly replayed) order is still
|
|
255
|
+
* `awaiting_payment`. If the replay shows the order already advanced
|
|
256
|
+
* (`paid`/`delivered`/…), we SKIP the payment and just wait — so a retry
|
|
257
|
+
* never double-pays an order whose first payment already landed.
|
|
258
|
+
* If anything throws after the order exists, the error carries `order_id` so you
|
|
259
|
+
* re-attach via `getOrder`/`waitForOrder` instead of re-paying.
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
/** The slice of `MyStarsClient` `fulfill` needs (structurally typed so it's easy to mock). */
|
|
263
|
+
interface FulfillClient {
|
|
264
|
+
createOrder(params: CreateOrderParams, opts?: CreateOrderOptions): Promise<CreateOrderResult>;
|
|
265
|
+
waitForOrder(orderId: string, opts?: WaitForOrderOptions): Promise<Order>;
|
|
266
|
+
}
|
|
267
|
+
/** Options for {@link fulfill} — the {@link PayOrderOptions} (e.g. `rpc`) plus order-create/wait wiring. */
|
|
268
|
+
interface FulfillOptions extends PayOrderOptions {
|
|
269
|
+
/**
|
|
270
|
+
* REQUIRED stable idempotency key — derive it from YOUR OWN order id so a retry
|
|
271
|
+
* of `fulfill` reuses the same key and the server returns the same order instead
|
|
272
|
+
* of creating a duplicate. May also be supplied via `createOptions.idempotencyKey`.
|
|
273
|
+
*/
|
|
274
|
+
idempotencyKey?: string;
|
|
275
|
+
/** Options forwarded to `createOrder` (e.g. a caller-supplied idempotency key). */
|
|
276
|
+
createOptions?: CreateOrderOptions;
|
|
277
|
+
/** Options forwarded to `waitForOrder`. */
|
|
278
|
+
wait?: WaitForOrderOptions;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* An error thrown AFTER the order was created carries the `order_id` so you can
|
|
282
|
+
* re-attach (`getOrder`/`waitForOrder`) instead of re-running `fulfill` (which
|
|
283
|
+
* would re-pay). The error may be any class (a `MyStarsApiError` from the wait, a
|
|
284
|
+
* `WalletError` from the payer, …), so the id rides as an optional property —
|
|
285
|
+
* read it with the type-safe {@link orderIdFromError} accessor.
|
|
286
|
+
*/
|
|
287
|
+
type ErrorWithOrderId = Error & {
|
|
288
|
+
order_id?: string;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Read the `order_id` that `fulfill` attaches to a post-create failure. Returns
|
|
292
|
+
* `undefined` when the error carries none. Use it to recover safely after a
|
|
293
|
+
* `fulfill` throw — re-attach with `client.waitForOrder(id)` / `getOrder(id)`
|
|
294
|
+
* rather than re-running `fulfill` (which would broadcast a second payment).
|
|
295
|
+
*/
|
|
296
|
+
declare function orderIdFromError(err: unknown): string | undefined;
|
|
297
|
+
/**
|
|
298
|
+
* create → pay → wait-until-terminal, in one call.
|
|
299
|
+
*
|
|
300
|
+
* Creates the order with the required stable `idempotencyKey`, broadcasts the
|
|
301
|
+
* payment from `wallet` ONLY if the (possibly idempotent-replayed) order is still
|
|
302
|
+
* `awaiting_payment`, then polls until the order is terminal. See the module note
|
|
303
|
+
* for the money-retry safeguards.
|
|
304
|
+
*
|
|
305
|
+
* @param client - the order-layer client (a `MyStarsClient`, or any {@link FulfillClient})
|
|
306
|
+
* @param wallet - the funded {@link TonWallet} that pays the invoice
|
|
307
|
+
* @param params - the order to create (`CreateOrderParams`)
|
|
308
|
+
* @param opts - {@link FulfillOptions} — MUST include a stable `idempotencyKey` (or `createOptions.idempotencyKey`) plus `rpc`
|
|
309
|
+
* @returns the final {@link Order} (terminal status — `delivered`/`failed`/`reversed`/`expired`)
|
|
310
|
+
* @throws `MyStarsValidationError` if no stable `idempotencyKey` was supplied
|
|
311
|
+
* @throws `RecipientIneligibleError` (422) if the recipient cannot receive the item (no order created)
|
|
312
|
+
* @throws `InsufficientBalanceError` if the wallet can't cover the payment
|
|
313
|
+
* @throws `OrderWaitTimeoutError` if the order doesn't finish before the wait deadline
|
|
314
|
+
* @throws `MyStarsApiError` on other API failures — after the order exists the thrown error carries `order_id` (read via {@link orderIdFromError}); re-attach with `client.waitForOrder(orderId)` instead of re-running `fulfill`
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* const order = await fulfill(
|
|
318
|
+
* client, wallet,
|
|
319
|
+
* { type: "stars", recipient: { username: "durov" }, quantity: 100 },
|
|
320
|
+
* { rpc, idempotencyKey: `order-${myOrderId}` },
|
|
321
|
+
* );
|
|
322
|
+
* if (order.status !== "delivered") console.warn("not delivered:", order.failure_reason);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
declare function fulfill(client: FulfillClient, wallet: TonWallet, params: CreateOrderParams, opts: FulfillOptions): Promise<Order>;
|
|
326
|
+
|
|
327
|
+
export { type CreateTransferArgs, DEFAULT_JETTON_GAS_TON, DEFAULT_USDT_MASTER, type ErrorWithOrderId, type FulfillClient, type FulfillOptions, InsufficientBalanceError, OrderPayer, type PayOrderOptions, type PayOrderResult, type PlannedMessage, type TonRpc, TonWallet, ToncenterRpc, type ToncenterRpcOptions, WalletError, fulfill, orderIdFromError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import * as _ton_core from '@ton/core';
|
|
2
|
+
import { MessageRelaxed, SendMode, StateInit, Cell } from '@ton/core';
|
|
3
|
+
import { KeyPair } from '@ton/crypto';
|
|
4
|
+
import { PaymentInstruction, CreateOrderResult, CreateOrderParams, CreateOrderOptions, WaitForOrderOptions, Order } from '@mystars-tg/faas-sdk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The minimal TON RPC surface the wallet + payer need. Inject a custom one for
|
|
8
|
+
* tests (so nothing touches the network or moves funds); use `ToncenterRpc` in
|
|
9
|
+
* production.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The minimal TON RPC surface the wallet + payer depend on. Implement it (or
|
|
13
|
+
* use {@link ToncenterRpc}) to read balances/seqno, resolve jetton wallets, and
|
|
14
|
+
* broadcast. Inject a mock in tests so nothing touches the network or moves funds.
|
|
15
|
+
*/
|
|
16
|
+
interface TonRpc {
|
|
17
|
+
/** Account TON balance, in nanoTON. */
|
|
18
|
+
getBalance(address: string): Promise<bigint>;
|
|
19
|
+
/** The wallet's current seqno (0 if the wallet contract isn't deployed yet). */
|
|
20
|
+
getSeqno(address: string): Promise<number>;
|
|
21
|
+
/** Resolve an owner's jetton wallet address from the jetton master (get_wallet_address). */
|
|
22
|
+
resolveJettonWallet(owner: string, jettonMaster: string): Promise<string>;
|
|
23
|
+
/** A jetton wallet's token balance, in the jetton's smallest unit (get_wallet_data). */
|
|
24
|
+
getJettonBalance(jettonWallet: string): Promise<bigint>;
|
|
25
|
+
/** Broadcast a serialized external-message BoC. */
|
|
26
|
+
sendBoc(boc: Uint8Array): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/** Options for {@link ToncenterRpc}. */
|
|
29
|
+
interface ToncenterRpcOptions {
|
|
30
|
+
/** JSON-RPC endpoint, e.g. `https://toncenter.com/api/v2/jsonRPC` (or a testnet URL). */
|
|
31
|
+
endpoint: string;
|
|
32
|
+
/** toncenter API key (recommended to avoid tight rate limits). */
|
|
33
|
+
apiKey?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default {@link TonRpc} backed by toncenter via `@ton/ton`'s `TonClient`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const rpc = new ToncenterRpc({
|
|
41
|
+
* endpoint: "https://toncenter.com/api/v2/jsonRPC",
|
|
42
|
+
* apiKey: process.env.TONCENTER_KEY,
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare class ToncenterRpc implements TonRpc {
|
|
47
|
+
private readonly client;
|
|
48
|
+
/** @param opts - the {@link ToncenterRpcOptions} (endpoint + optional API key) */
|
|
49
|
+
constructor(opts: ToncenterRpcOptions);
|
|
50
|
+
/** {@inheritDoc TonRpc.getBalance} */
|
|
51
|
+
getBalance(address: string): Promise<bigint>;
|
|
52
|
+
/** {@inheritDoc TonRpc.getSeqno} */
|
|
53
|
+
getSeqno(address: string): Promise<number>;
|
|
54
|
+
/** {@inheritDoc TonRpc.resolveJettonWallet} */
|
|
55
|
+
resolveJettonWallet(owner: string, jettonMaster: string): Promise<string>;
|
|
56
|
+
/** {@inheritDoc TonRpc.getJettonBalance} */
|
|
57
|
+
getJettonBalance(jettonWallet: string): Promise<bigint>;
|
|
58
|
+
/** {@inheritDoc TonRpc.sendBoc} */
|
|
59
|
+
sendBoc(boc: Uint8Array): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Base error for the wallet module. */
|
|
63
|
+
declare class WalletError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
66
|
+
/** The wallet doesn't hold enough TON / jetton balance to cover a payment. */
|
|
67
|
+
declare class InsufficientBalanceError extends WalletError {
|
|
68
|
+
}
|
|
69
|
+
/** Arguments for {@link TonWallet.createTransfer} — the unsigned pieces of a wallet-v4 transfer. */
|
|
70
|
+
interface CreateTransferArgs {
|
|
71
|
+
/** The wallet's current seqno (fetch via {@link TonWallet.getSeqno}); 0 deploys the contract. */
|
|
72
|
+
seqno: number;
|
|
73
|
+
/** The internal messages to send in this transfer. */
|
|
74
|
+
messages: MessageRelaxed[];
|
|
75
|
+
/** The send mode flags (`@ton/core` `SendMode`). */
|
|
76
|
+
sendMode: SendMode;
|
|
77
|
+
/** Transfer validity window as a unix-seconds expiry; the wallet rejects it after this. */
|
|
78
|
+
timeout?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* An in-memory TON wallet (WalletContractV4). Construct via {@link TonWallet.generate} /
|
|
82
|
+
* {@link TonWallet.fromMnemonic} / {@link TonWallet.fromKeyPair}. Keys never leave memory and are
|
|
83
|
+
* redacted from serialization. See the module overview for the custody stance.
|
|
84
|
+
*/
|
|
85
|
+
declare class TonWallet {
|
|
86
|
+
/** The in-memory key pair. Treat `secretKey` as a secret — never log or persist it. */
|
|
87
|
+
readonly keyPair: KeyPair;
|
|
88
|
+
private readonly contract;
|
|
89
|
+
private constructor();
|
|
90
|
+
/** Redacted serialization — NEVER exposes the secret key. */
|
|
91
|
+
toJSON(): {
|
|
92
|
+
address: string;
|
|
93
|
+
publicKey: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Generate a NEW wallet. Returns the 24-word mnemonic ONCE — store it securely;
|
|
97
|
+
* it is never persisted.
|
|
98
|
+
*
|
|
99
|
+
* @returns the in-memory `wallet` and its `mnemonic` (the only time you see it)
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const { wallet, mnemonic } = await TonWallet.generate();
|
|
103
|
+
* await secureVault.store(mnemonic); // YOUR job — it is never written to disk by the SDK
|
|
104
|
+
* console.log("fund this:", wallet.address);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
static generate(): Promise<{
|
|
108
|
+
wallet: TonWallet;
|
|
109
|
+
mnemonic: string[];
|
|
110
|
+
}>;
|
|
111
|
+
/** Import a wallet from its 24-word mnemonic. */
|
|
112
|
+
static fromMnemonic(words: string[]): Promise<TonWallet>;
|
|
113
|
+
/** Import a wallet from a raw Ed25519 key pair. */
|
|
114
|
+
static fromKeyPair(publicKey: Uint8Array, secretKey: Uint8Array): TonWallet;
|
|
115
|
+
/** Friendly, non-bounceable (`UQ…`) address — FUND THIS to pay invoices. */
|
|
116
|
+
get address(): string;
|
|
117
|
+
/** Raw `0:hex` address form. */
|
|
118
|
+
get rawAddress(): string;
|
|
119
|
+
/** @internal The contract's StateInit, needed for the first (deploying) transfer. */
|
|
120
|
+
get init(): StateInit;
|
|
121
|
+
/** @internal The contract address as a TON `Address`. */
|
|
122
|
+
get tonAddress(): _ton_core.Address;
|
|
123
|
+
/** @internal Sign a transfer body. */
|
|
124
|
+
createTransfer(args: CreateTransferArgs): Cell;
|
|
125
|
+
/**
|
|
126
|
+
* This wallet's native TON balance.
|
|
127
|
+
*
|
|
128
|
+
* @param rpc - the RPC to query through
|
|
129
|
+
* @returns the balance in nanoTON
|
|
130
|
+
*/
|
|
131
|
+
getBalance(rpc: TonRpc): Promise<bigint>;
|
|
132
|
+
/**
|
|
133
|
+
* This wallet's current sequence number.
|
|
134
|
+
*
|
|
135
|
+
* @param rpc - the RPC to query through
|
|
136
|
+
* @returns the seqno (`0` when the wallet contract isn't deployed yet)
|
|
137
|
+
*/
|
|
138
|
+
getSeqno(rpc: TonRpc): Promise<number>;
|
|
139
|
+
/**
|
|
140
|
+
* This wallet's balance of a given jetton (e.g. USDT).
|
|
141
|
+
*
|
|
142
|
+
* @param rpc - the RPC to query through
|
|
143
|
+
* @param jettonMaster - the jetton master address (e.g. `DEFAULT_USDT_MASTER` from `@mystars-tg/faas-wallet`)
|
|
144
|
+
* @returns the token balance in the jetton's smallest unit (micro-USDT for USDT)
|
|
145
|
+
*/
|
|
146
|
+
getJettonBalance(rpc: TonRpc, jettonMaster: string): Promise<bigint>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* `OrderPayer` — pay a FaaS order invoice from your own `TonWallet`.
|
|
151
|
+
*
|
|
152
|
+
* Signs ONCE and broadcasts. For `ton` it sends an exact-amount transfer with the
|
|
153
|
+
* order memo as an op-0 comment; for `usdt_ton` it resolves the payer's own USDT
|
|
154
|
+
* jetton wallet and sends a TEP-74 transfer whose `destination` is the FaaS
|
|
155
|
+
* `pay_to_address` and whose forward payload carries the memo.
|
|
156
|
+
*
|
|
157
|
+
* NOTE: this moves the PARTNER's own funds from the partner's own wallet — never
|
|
158
|
+
* the MyStars treasury. Tests use a mock `TonRpc` so nothing broadcasts.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/** Mainnet Tether USDT jetton master on TON. Override via `PayOrderOptions.jettonMaster` for testnet. */
|
|
162
|
+
declare const DEFAULT_USDT_MASTER = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs";
|
|
163
|
+
/** TON attached to a USDT jetton transfer to cover its gas (excess refunds to the sender). Matches the core invoice builder. */
|
|
164
|
+
declare const DEFAULT_JETTON_GAS_TON = "0.05";
|
|
165
|
+
/** Options for {@link OrderPayer.payOrder} / {@link OrderPayer.planMessages}. */
|
|
166
|
+
interface PayOrderOptions {
|
|
167
|
+
/** The RPC used to resolve the jetton wallet, read balances, and broadcast. */
|
|
168
|
+
rpc: TonRpc;
|
|
169
|
+
/** USDT jetton master (defaults to mainnet Tether). */
|
|
170
|
+
jettonMaster?: string;
|
|
171
|
+
/** TON gas attached to a USDT transfer (default 0.05). */
|
|
172
|
+
jettonGasTon?: string;
|
|
173
|
+
/** Transfer validity window in seconds (default 120 — a non-landed send dies fast, safe to retry). */
|
|
174
|
+
validForSeconds?: number;
|
|
175
|
+
/** Skip the pre-sign balance check (saves 1-2 RPC calls; default false). */
|
|
176
|
+
skipBalanceCheck?: boolean;
|
|
177
|
+
/** Injectable clock (epoch ms) for deterministic tests. */
|
|
178
|
+
now?: number;
|
|
179
|
+
}
|
|
180
|
+
/** The outcome of a broadcast payment from {@link OrderPayer.payOrder}. */
|
|
181
|
+
interface PayOrderResult {
|
|
182
|
+
/** The order id paid. */
|
|
183
|
+
orderId?: string;
|
|
184
|
+
/** The paying wallet address. */
|
|
185
|
+
from: string;
|
|
186
|
+
/** The recipient (FaaS treasury owner) address. */
|
|
187
|
+
to: string;
|
|
188
|
+
/** The smallest-unit amount sent (nanoTON for ton, micro-USDT for usdt_ton). */
|
|
189
|
+
amountSmallestUnit: string;
|
|
190
|
+
}
|
|
191
|
+
/** An internal message the payer will sign into a transfer. Exposed for testing. */
|
|
192
|
+
interface PlannedMessage {
|
|
193
|
+
to: string;
|
|
194
|
+
value: bigint;
|
|
195
|
+
body: Cell;
|
|
196
|
+
bounce: boolean;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Pays a FaaS order invoice from a single {@link TonWallet}.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* const order = await client.createOrder(
|
|
204
|
+
* { type: "stars", recipient: { username: "durov" }, quantity: 100 },
|
|
205
|
+
* { idempotencyKey: `order-${myId}` },
|
|
206
|
+
* );
|
|
207
|
+
* const rpc = new ToncenterRpc({ endpoint: "https://toncenter.com/api/v2/jsonRPC" });
|
|
208
|
+
* const { from, to, amountSmallestUnit } = await new OrderPayer(wallet).payOrder(order, { rpc });
|
|
209
|
+
* const finished = await client.waitForOrder(order.order_id);
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
declare class OrderPayer {
|
|
213
|
+
private readonly wallet;
|
|
214
|
+
/** @param wallet - the funded {@link TonWallet} whose own funds will pay invoices */
|
|
215
|
+
constructor(wallet: TonWallet);
|
|
216
|
+
/**
|
|
217
|
+
* Plan the internal message(s) for an order WITHOUT signing or broadcasting.
|
|
218
|
+
* `ton` is pure; `usdt_ton` resolves the payer's jetton wallet via `rpc`.
|
|
219
|
+
*/
|
|
220
|
+
planMessages(payment: PaymentInstruction, opts: PayOrderOptions): Promise<PlannedMessage[]>;
|
|
221
|
+
/** Throw `InsufficientBalanceError` if the wallet can't cover the planned payment + gas. */
|
|
222
|
+
private assertFunded;
|
|
223
|
+
/**
|
|
224
|
+
* Build, sign, and broadcast the payment for an order. Signs exactly ONCE.
|
|
225
|
+
*
|
|
226
|
+
* MONEY: this broadcasts a real on-chain transfer of the wallet's OWN funds.
|
|
227
|
+
* It is not idempotent — calling it twice for the same order pays twice. Guard
|
|
228
|
+
* retries at the order layer (a stable `Idempotency-Key` on `createOrder`, or
|
|
229
|
+
* use `fulfill` which only pays an order still `awaiting_payment`).
|
|
230
|
+
*
|
|
231
|
+
* @param order - a `CreateOrderResult`, or any object carrying a `payment` block (and optional `order_id`)
|
|
232
|
+
* @param opts - the {@link PayOrderOptions} (at minimum `rpc`)
|
|
233
|
+
* @returns the {@link PayOrderResult} — `from`/`to` addresses and the smallest-unit amount sent
|
|
234
|
+
* @throws `WalletError` if the order's `payment` block is missing `pay_to_address`/`memo`
|
|
235
|
+
* @throws `MyStarsValidationError` if the amount is non-positive
|
|
236
|
+
* @throws {@link InsufficientBalanceError} if the wallet can't cover the payment + gas (unless `skipBalanceCheck`)
|
|
237
|
+
*/
|
|
238
|
+
payOrder(order: CreateOrderResult | {
|
|
239
|
+
payment: PaymentInstruction;
|
|
240
|
+
order_id?: string;
|
|
241
|
+
}, opts: PayOrderOptions): Promise<PayOrderResult>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* `fulfill()` — the one-call convenience: create an order, pay it from your
|
|
246
|
+
* wallet, and wait until it's delivered (or failed/reversed/expired).
|
|
247
|
+
*
|
|
248
|
+
* RETRY HAZARD (money): this broadcasts a REAL payment. A naive retry of a
|
|
249
|
+
* `fulfill` that already broadcast would create a SECOND order and pay it AGAIN.
|
|
250
|
+
* Two safeguards make retries safe:
|
|
251
|
+
* 1. A stable `idempotencyKey` is REQUIRED — re-running `fulfill` with the same
|
|
252
|
+
* key returns the SAME order from the server (idempotent replay) instead of
|
|
253
|
+
* minting a duplicate.
|
|
254
|
+
* 2. We only broadcast when the (possibly replayed) order is still
|
|
255
|
+
* `awaiting_payment`. If the replay shows the order already advanced
|
|
256
|
+
* (`paid`/`delivered`/…), we SKIP the payment and just wait — so a retry
|
|
257
|
+
* never double-pays an order whose first payment already landed.
|
|
258
|
+
* If anything throws after the order exists, the error carries `order_id` so you
|
|
259
|
+
* re-attach via `getOrder`/`waitForOrder` instead of re-paying.
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
/** The slice of `MyStarsClient` `fulfill` needs (structurally typed so it's easy to mock). */
|
|
263
|
+
interface FulfillClient {
|
|
264
|
+
createOrder(params: CreateOrderParams, opts?: CreateOrderOptions): Promise<CreateOrderResult>;
|
|
265
|
+
waitForOrder(orderId: string, opts?: WaitForOrderOptions): Promise<Order>;
|
|
266
|
+
}
|
|
267
|
+
/** Options for {@link fulfill} — the {@link PayOrderOptions} (e.g. `rpc`) plus order-create/wait wiring. */
|
|
268
|
+
interface FulfillOptions extends PayOrderOptions {
|
|
269
|
+
/**
|
|
270
|
+
* REQUIRED stable idempotency key — derive it from YOUR OWN order id so a retry
|
|
271
|
+
* of `fulfill` reuses the same key and the server returns the same order instead
|
|
272
|
+
* of creating a duplicate. May also be supplied via `createOptions.idempotencyKey`.
|
|
273
|
+
*/
|
|
274
|
+
idempotencyKey?: string;
|
|
275
|
+
/** Options forwarded to `createOrder` (e.g. a caller-supplied idempotency key). */
|
|
276
|
+
createOptions?: CreateOrderOptions;
|
|
277
|
+
/** Options forwarded to `waitForOrder`. */
|
|
278
|
+
wait?: WaitForOrderOptions;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* An error thrown AFTER the order was created carries the `order_id` so you can
|
|
282
|
+
* re-attach (`getOrder`/`waitForOrder`) instead of re-running `fulfill` (which
|
|
283
|
+
* would re-pay). The error may be any class (a `MyStarsApiError` from the wait, a
|
|
284
|
+
* `WalletError` from the payer, …), so the id rides as an optional property —
|
|
285
|
+
* read it with the type-safe {@link orderIdFromError} accessor.
|
|
286
|
+
*/
|
|
287
|
+
type ErrorWithOrderId = Error & {
|
|
288
|
+
order_id?: string;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Read the `order_id` that `fulfill` attaches to a post-create failure. Returns
|
|
292
|
+
* `undefined` when the error carries none. Use it to recover safely after a
|
|
293
|
+
* `fulfill` throw — re-attach with `client.waitForOrder(id)` / `getOrder(id)`
|
|
294
|
+
* rather than re-running `fulfill` (which would broadcast a second payment).
|
|
295
|
+
*/
|
|
296
|
+
declare function orderIdFromError(err: unknown): string | undefined;
|
|
297
|
+
/**
|
|
298
|
+
* create → pay → wait-until-terminal, in one call.
|
|
299
|
+
*
|
|
300
|
+
* Creates the order with the required stable `idempotencyKey`, broadcasts the
|
|
301
|
+
* payment from `wallet` ONLY if the (possibly idempotent-replayed) order is still
|
|
302
|
+
* `awaiting_payment`, then polls until the order is terminal. See the module note
|
|
303
|
+
* for the money-retry safeguards.
|
|
304
|
+
*
|
|
305
|
+
* @param client - the order-layer client (a `MyStarsClient`, or any {@link FulfillClient})
|
|
306
|
+
* @param wallet - the funded {@link TonWallet} that pays the invoice
|
|
307
|
+
* @param params - the order to create (`CreateOrderParams`)
|
|
308
|
+
* @param opts - {@link FulfillOptions} — MUST include a stable `idempotencyKey` (or `createOptions.idempotencyKey`) plus `rpc`
|
|
309
|
+
* @returns the final {@link Order} (terminal status — `delivered`/`failed`/`reversed`/`expired`)
|
|
310
|
+
* @throws `MyStarsValidationError` if no stable `idempotencyKey` was supplied
|
|
311
|
+
* @throws `RecipientIneligibleError` (422) if the recipient cannot receive the item (no order created)
|
|
312
|
+
* @throws `InsufficientBalanceError` if the wallet can't cover the payment
|
|
313
|
+
* @throws `OrderWaitTimeoutError` if the order doesn't finish before the wait deadline
|
|
314
|
+
* @throws `MyStarsApiError` on other API failures — after the order exists the thrown error carries `order_id` (read via {@link orderIdFromError}); re-attach with `client.waitForOrder(orderId)` instead of re-running `fulfill`
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* const order = await fulfill(
|
|
318
|
+
* client, wallet,
|
|
319
|
+
* { type: "stars", recipient: { username: "durov" }, quantity: 100 },
|
|
320
|
+
* { rpc, idempotencyKey: `order-${myOrderId}` },
|
|
321
|
+
* );
|
|
322
|
+
* if (order.status !== "delivered") console.warn("not delivered:", order.failure_reason);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
declare function fulfill(client: FulfillClient, wallet: TonWallet, params: CreateOrderParams, opts: FulfillOptions): Promise<Order>;
|
|
326
|
+
|
|
327
|
+
export { type CreateTransferArgs, DEFAULT_JETTON_GAS_TON, DEFAULT_USDT_MASTER, type ErrorWithOrderId, type FulfillClient, type FulfillOptions, InsufficientBalanceError, OrderPayer, type PayOrderOptions, type PayOrderResult, type PlannedMessage, type TonRpc, TonWallet, ToncenterRpc, type ToncenterRpcOptions, WalletError, fulfill, orderIdFromError };
|