@piprail/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +160 -0
- package/ERRORS.md +182 -0
- package/LICENSE +21 -0
- package/README.md +497 -0
- package/STANDARDS.md +123 -0
- package/dist/chunk-3TQJJ4SQ.js +157 -0
- package/dist/chunk-CQREG5LE.cjs +8 -0
- package/dist/chunk-FURB5RP7.js +8 -0
- package/dist/chunk-WQWNPAYQ.cjs +157 -0
- package/dist/index.cjs +1681 -0
- package/dist/index.d.cts +4578 -0
- package/dist/index.d.ts +4578 -0
- package/dist/index.js +1681 -0
- package/dist/near-4P5XNMMB.cjs +346 -0
- package/dist/near-RVXGF7TW.js +346 -0
- package/dist/solana-7PZG3CDO.js +342 -0
- package/dist/solana-F7H4YDW5.cjs +342 -0
- package/dist/stellar-BPPQTLNI.cjs +389 -0
- package/dist/stellar-PAZ352JL.js +389 -0
- package/dist/sui-6N4ZPAGD.js +304 -0
- package/dist/sui-XV4YYSGV.cjs +304 -0
- package/dist/ton-E5RLUPD2.cjs +366 -0
- package/dist/ton-EFZKQAAK.js +366 -0
- package/dist/tron-243DT6PF.js +372 -0
- package/dist/tron-3UDH7KGF.cjs +372 -0
- package/dist/xrpl-6NRFT5CA.cjs +449 -0
- package/dist/xrpl-7GWXDAVZ.js +449 -0
- package/package.json +143 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {
|
|
2
|
+
delay
|
|
3
|
+
} from "./chunk-FURB5RP7.js";
|
|
4
|
+
import {
|
|
5
|
+
ConfirmationTimeoutError,
|
|
6
|
+
UnknownTokenError,
|
|
7
|
+
WrongFamilyError,
|
|
8
|
+
nativeCost,
|
|
9
|
+
rejectForeignToken,
|
|
10
|
+
toInsufficientFundsError
|
|
11
|
+
} from "./chunk-3TQJJ4SQ.js";
|
|
12
|
+
|
|
13
|
+
// src/drivers/ton/index.ts
|
|
14
|
+
import { Address as Address2 } from "@ton/core";
|
|
15
|
+
import { JettonMaster, TonClient } from "@ton/ton";
|
|
16
|
+
|
|
17
|
+
// src/drivers/ton/chains.ts
|
|
18
|
+
var TON_DECIMALS = 9;
|
|
19
|
+
var TON_MAINNET = {
|
|
20
|
+
caip2: "ton:-239",
|
|
21
|
+
defaultRpc: "https://toncenter.com/api/v2/jsonRPC",
|
|
22
|
+
tokens: {
|
|
23
|
+
// USD₮ (Tether) — native, dominant stablecoin on TON. Master + 6 decimals
|
|
24
|
+
// verified on-chain (tonapi + toncenter) before shipping.
|
|
25
|
+
USDT: {
|
|
26
|
+
master: "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
|
|
27
|
+
decimals: 6,
|
|
28
|
+
symbol: "USDT"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/drivers/ton/pay.ts
|
|
34
|
+
import { Address, beginCell, comment as commentCell, internal, toNano } from "@ton/core";
|
|
35
|
+
import { SendMode } from "@ton/ton";
|
|
36
|
+
var OP_JETTON_TRANSFER = 260734629;
|
|
37
|
+
var JETTON_GAS = toNano("0.05");
|
|
38
|
+
var FORWARD_TON = 1n;
|
|
39
|
+
async function payTon(params) {
|
|
40
|
+
const { client, wallet, accept, senderJettonWallet } = params;
|
|
41
|
+
const opened = client.open(wallet.contract);
|
|
42
|
+
const seqno = await opened.getSeqno();
|
|
43
|
+
const cmt = commentCell(accept.extra.nonce);
|
|
44
|
+
const message = accept.asset === "native" ? internal({
|
|
45
|
+
to: Address.parse(accept.payTo),
|
|
46
|
+
value: BigInt(accept.amount),
|
|
47
|
+
bounce: false,
|
|
48
|
+
body: cmt
|
|
49
|
+
}) : internal({
|
|
50
|
+
to: senderJettonWallet,
|
|
51
|
+
value: JETTON_GAS,
|
|
52
|
+
bounce: true,
|
|
53
|
+
body: buildJettonTransfer(accept, wallet.contract.address, cmt)
|
|
54
|
+
});
|
|
55
|
+
await opened.sendTransfer({
|
|
56
|
+
seqno,
|
|
57
|
+
secretKey: wallet.keyPair.secretKey,
|
|
58
|
+
sendMode: SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS,
|
|
59
|
+
messages: [message]
|
|
60
|
+
});
|
|
61
|
+
await waitForSeqno(opened, seqno);
|
|
62
|
+
}
|
|
63
|
+
function buildJettonTransfer(accept, responseTo, forward) {
|
|
64
|
+
return beginCell().storeUint(OP_JETTON_TRANSFER, 32).storeUint(0n, 64).storeCoins(BigInt(accept.amount)).storeAddress(Address.parse(accept.payTo)).storeAddress(responseTo).storeBit(false).storeCoins(FORWARD_TON).storeBit(true).storeRef(forward).endCell();
|
|
65
|
+
}
|
|
66
|
+
async function waitForSeqno(opened, from, { tries = 30, intervalMs = 2e3 } = {}) {
|
|
67
|
+
for (let i = 0; i < tries; i += 1) {
|
|
68
|
+
await delay(intervalMs);
|
|
69
|
+
let current;
|
|
70
|
+
try {
|
|
71
|
+
current = await opened.getSeqno();
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (current > from) return;
|
|
76
|
+
}
|
|
77
|
+
throw new ConfirmationTimeoutError(
|
|
78
|
+
`TON wallet seqno did not advance past ${from} \u2014 the payment may not have been accepted.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/drivers/ton/verify.ts
|
|
83
|
+
var OP_INTERNAL_TRANSFER = 395134233;
|
|
84
|
+
var OP_TEXT_COMMENT = 0;
|
|
85
|
+
async function verifyTon(params) {
|
|
86
|
+
const { client, watch, accept } = params;
|
|
87
|
+
const required = BigInt(accept.amount);
|
|
88
|
+
const nonce = accept.extra.nonce;
|
|
89
|
+
let txs;
|
|
90
|
+
try {
|
|
91
|
+
txs = await client.getTransactions(watch, { limit: 24, archival: true });
|
|
92
|
+
} catch {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: "tx_not_found",
|
|
96
|
+
detail: `Could not read the TON account for nonce ${nonce} (transient RPC failure) \u2014 retry.`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
for (const tx of txs) {
|
|
100
|
+
const incoming = extractIncoming(tx);
|
|
101
|
+
if (!incoming || incoming.comment !== nonce) continue;
|
|
102
|
+
if (!txSucceeded(tx)) {
|
|
103
|
+
return { ok: false, error: "tx_reverted", detail: `TON payment for nonce ${nonce} failed on-chain.` };
|
|
104
|
+
}
|
|
105
|
+
if (incoming.amount < required) {
|
|
106
|
+
return { ok: false, error: "amount_too_low", detail: `Credited ${incoming.amount}, required ${required}.` };
|
|
107
|
+
}
|
|
108
|
+
const ageSeconds = Math.floor(Date.now() / 1e3) - tx.now;
|
|
109
|
+
if (ageSeconds > accept.maxTimeoutSeconds) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: "payment_expired",
|
|
113
|
+
detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
receipt: {
|
|
119
|
+
scheme: "onchain-proof",
|
|
120
|
+
success: true,
|
|
121
|
+
network: accept.network,
|
|
122
|
+
transaction: tx.hash().toString("hex"),
|
|
123
|
+
asset: accept.asset,
|
|
124
|
+
amount: accept.amount,
|
|
125
|
+
payer: incoming.payer,
|
|
126
|
+
payTo: accept.payTo,
|
|
127
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return notFound(nonce);
|
|
132
|
+
}
|
|
133
|
+
function extractIncoming(tx) {
|
|
134
|
+
const inMsg = tx.inMessage;
|
|
135
|
+
if (!inMsg || inMsg.info.type !== "internal" || inMsg.info.bounced) return null;
|
|
136
|
+
const jetton = parseInternalTransfer(inMsg.body);
|
|
137
|
+
if (jetton) {
|
|
138
|
+
return {
|
|
139
|
+
amount: jetton.amount,
|
|
140
|
+
payer: (jetton.from ?? inMsg.info.src).toString(),
|
|
141
|
+
comment: jetton.comment
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
amount: inMsg.info.value.coins,
|
|
146
|
+
payer: inMsg.info.src.toString(),
|
|
147
|
+
comment: readComment(inMsg.body)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function txSucceeded(tx) {
|
|
151
|
+
const d = tx.description;
|
|
152
|
+
if (d.type !== "generic") return false;
|
|
153
|
+
if (d.aborted) return false;
|
|
154
|
+
if (d.computePhase.type === "vm" && !d.computePhase.success) return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
function parseInternalTransfer(body) {
|
|
158
|
+
try {
|
|
159
|
+
const s = body.beginParse();
|
|
160
|
+
if (s.remainingBits < 32) return null;
|
|
161
|
+
if (s.loadUint(32) !== OP_INTERNAL_TRANSFER) return null;
|
|
162
|
+
s.loadUintBig(64);
|
|
163
|
+
const amount = s.loadCoins();
|
|
164
|
+
const from = s.loadMaybeAddress();
|
|
165
|
+
s.loadMaybeAddress();
|
|
166
|
+
s.loadCoins();
|
|
167
|
+
return { amount, from, comment: readForwardComment(s) };
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function readForwardComment(s) {
|
|
173
|
+
try {
|
|
174
|
+
const cs = s.loadBit() ? s.loadRef().beginParse() : s;
|
|
175
|
+
if (cs.remainingBits < 32) return void 0;
|
|
176
|
+
if (cs.loadUint(32) !== OP_TEXT_COMMENT) return void 0;
|
|
177
|
+
return cs.loadStringTail();
|
|
178
|
+
} catch {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function readComment(body) {
|
|
183
|
+
try {
|
|
184
|
+
const s = body.beginParse();
|
|
185
|
+
if (s.remainingBits < 32) return void 0;
|
|
186
|
+
if (s.loadUint(32) !== OP_TEXT_COMMENT) return void 0;
|
|
187
|
+
return s.loadStringTail();
|
|
188
|
+
} catch {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function notFound(nonce) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
error: "transfer_not_found",
|
|
196
|
+
detail: `No matching TON transfer found for nonce ${nonce} (not yet settled, or wrong recipient/amount/token).`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/drivers/ton/wallet.ts
|
|
201
|
+
import { mnemonicToWalletKey } from "@ton/crypto";
|
|
202
|
+
import { WalletContractV4, WalletContractV5R1 } from "@ton/ton";
|
|
203
|
+
function assertTonWallet(wallet, network) {
|
|
204
|
+
if (typeof wallet !== "object" || wallet === null) {
|
|
205
|
+
throw new WrongFamilyError(
|
|
206
|
+
`chain ${network} is TON; wallet must be { mnemonic } (24 words) or { keyPair }.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if ("privateKey" in wallet || "walletClient" in wallet) {
|
|
210
|
+
throw new WrongFamilyError(
|
|
211
|
+
`chain ${network} is TON; an EVM wallet can't be used \u2014 pass { mnemonic } (24 words) or { keyPair }.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (!("mnemonic" in wallet) && !("keyPair" in wallet)) {
|
|
215
|
+
throw new WrongFamilyError(
|
|
216
|
+
`chain ${network} is TON; wallet must be { mnemonic } (24 words) or { keyPair }.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return wallet;
|
|
220
|
+
}
|
|
221
|
+
async function resolveTonWallet(config) {
|
|
222
|
+
const version = config.version ?? "v4";
|
|
223
|
+
let keyPair;
|
|
224
|
+
if (config.mnemonic) {
|
|
225
|
+
const words = Array.isArray(config.mnemonic) ? config.mnemonic : config.mnemonic.trim().split(/\s+/);
|
|
226
|
+
keyPair = await mnemonicToWalletKey(words);
|
|
227
|
+
} else if (config.keyPair) {
|
|
228
|
+
keyPair = config.keyPair;
|
|
229
|
+
} else {
|
|
230
|
+
throw new WrongFamilyError("TON wallet needs { mnemonic } (24 words) or { keyPair }.");
|
|
231
|
+
}
|
|
232
|
+
const contract = version === "v5r1" ? WalletContractV5R1.create({ workchain: 0, publicKey: keyPair.publicKey }) : WalletContractV4.create({ workchain: 0, publicKey: keyPair.publicKey });
|
|
233
|
+
return { keyPair, contract };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/drivers/ton/index.ts
|
|
237
|
+
var tonDriver = {
|
|
238
|
+
family: "ton",
|
|
239
|
+
resolve(opts) {
|
|
240
|
+
if (opts.chain !== "ton") return null;
|
|
241
|
+
const rpcUrl = opts.rpcUrl ?? TON_MAINNET.defaultRpc;
|
|
242
|
+
return makeTonNetwork(TON_MAINNET, rpcUrl);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
function makeTonNetwork(preset, rpcUrl) {
|
|
246
|
+
const client = new TonClient({ endpoint: rpcUrl });
|
|
247
|
+
const network = preset.caip2;
|
|
248
|
+
const jwCache = /* @__PURE__ */ new Map();
|
|
249
|
+
function jettonWalletFor(master, owner) {
|
|
250
|
+
const key = `${master}:${owner}`;
|
|
251
|
+
let p = jwCache.get(key);
|
|
252
|
+
if (!p) {
|
|
253
|
+
p = client.open(JettonMaster.create(Address2.parse(master))).getWalletAddress(Address2.parse(owner));
|
|
254
|
+
jwCache.set(key, p);
|
|
255
|
+
}
|
|
256
|
+
return p;
|
|
257
|
+
}
|
|
258
|
+
function watchAccountFor(accept) {
|
|
259
|
+
return accept.asset === "native" ? Promise.resolve(Address2.parse(accept.payTo)) : jettonWalletFor(accept.asset, accept.payTo);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
family: "ton",
|
|
263
|
+
network,
|
|
264
|
+
supports: (n) => n === network,
|
|
265
|
+
resolveToken(token) {
|
|
266
|
+
if (token === "native") {
|
|
267
|
+
return { asset: "native", decimals: TON_DECIMALS, symbol: "TON" };
|
|
268
|
+
}
|
|
269
|
+
if (typeof token === "string") {
|
|
270
|
+
const info = preset.tokens[token.toUpperCase()];
|
|
271
|
+
if (!info) {
|
|
272
|
+
const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
|
|
273
|
+
throw new UnknownTokenError(
|
|
274
|
+
`token "${token}" isn't built in for TON (known: ${known}). Note: native USDC doesn't exist on TON. Pass { master, decimals } for a custom jetton, or use 'native'.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return { asset: info.master, decimals: info.decimals, symbol: info.symbol };
|
|
278
|
+
}
|
|
279
|
+
rejectForeignToken(token, "ton", network);
|
|
280
|
+
if (!("master" in token)) {
|
|
281
|
+
throw new WrongFamilyError(
|
|
282
|
+
`chain ${network} is TON; a custom token must be { master, decimals }.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
asset: token.master,
|
|
287
|
+
decimals: token.decimals,
|
|
288
|
+
...token.symbol ? { symbol: token.symbol } : {}
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
describeAsset(asset) {
|
|
292
|
+
if (asset === "native") return { symbol: "TON", decimals: TON_DECIMALS };
|
|
293
|
+
for (const info of Object.values(preset.tokens)) {
|
|
294
|
+
if (info.master === asset) return { symbol: info.symbol, decimals: info.decimals };
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
},
|
|
298
|
+
assertValidPayTo(payTo) {
|
|
299
|
+
if (payTo.startsWith("0x")) {
|
|
300
|
+
throw new WrongFamilyError(
|
|
301
|
+
`chain ${network} is TON, but payTo "${payTo}" looks like an EVM address.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
Address2.parse(payTo);
|
|
306
|
+
} catch {
|
|
307
|
+
throw new WrongFamilyError(
|
|
308
|
+
`chain ${network} is TON, but payTo "${payTo}" is not a valid TON address.`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
bindWallet(wallet) {
|
|
313
|
+
return { _native: assertTonWallet(wallet, network) };
|
|
314
|
+
},
|
|
315
|
+
async send(wallet, accept) {
|
|
316
|
+
try {
|
|
317
|
+
const tw = await resolveTonWallet(wallet._native);
|
|
318
|
+
const senderJettonWallet = accept.asset === "native" ? void 0 : await jettonWalletFor(accept.asset, tw.contract.address.toString());
|
|
319
|
+
await payTon({ client, wallet: tw, accept, senderJettonWallet });
|
|
320
|
+
const watch = await watchAccountFor(accept);
|
|
321
|
+
return encodeRef(watch, accept.extra.nonce);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
throw toInsufficientFundsError(err) ?? err;
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
async confirm(ref) {
|
|
327
|
+
const { watch, nonce } = decodeRef(ref);
|
|
328
|
+
const account = Address2.parse(watch);
|
|
329
|
+
for (let i = 0; i < 40; i += 1) {
|
|
330
|
+
await delay(2500);
|
|
331
|
+
let txs;
|
|
332
|
+
try {
|
|
333
|
+
txs = await client.getTransactions(account, { limit: 16, archival: true });
|
|
334
|
+
} catch {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
for (const tx of txs) {
|
|
338
|
+
const inc = extractIncoming(tx);
|
|
339
|
+
if (inc && inc.comment === nonce) return { height: tx.lt.toString() };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
throw new ConfirmationTimeoutError(`TON payment for nonce ${nonce} did not settle in time.`);
|
|
343
|
+
},
|
|
344
|
+
async estimateCost(accept) {
|
|
345
|
+
const fee = accept.asset === "native" ? 10000000n : 50000000n;
|
|
346
|
+
const detail = accept.asset === "native" ? "~0.01 TON network fee" : "~0.05 TON attached for the jetton transfer (leftover refunded)";
|
|
347
|
+
return nativeCost({ symbol: "TON", decimals: TON_DECIMALS, fee, basis: "heuristic", detail });
|
|
348
|
+
},
|
|
349
|
+
async verify(_ref, accept) {
|
|
350
|
+
const watch = await watchAccountFor(accept);
|
|
351
|
+
return verifyTon({ client, watch, accept });
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function encodeRef(watch, nonce) {
|
|
356
|
+
return `ton:${watch.toString()}|${nonce}`;
|
|
357
|
+
}
|
|
358
|
+
function decodeRef(ref) {
|
|
359
|
+
const body = ref.startsWith("ton:") ? ref.slice(4) : ref;
|
|
360
|
+
const i = body.indexOf("|");
|
|
361
|
+
if (i < 0) throw new Error(`malformed TON proof ref: ${ref}`);
|
|
362
|
+
return { watch: body.slice(0, i), nonce: body.slice(i + 1) };
|
|
363
|
+
}
|
|
364
|
+
export {
|
|
365
|
+
tonDriver
|
|
366
|
+
};
|