@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,372 @@
|
|
|
1
|
+
import {
|
|
2
|
+
delay
|
|
3
|
+
} from "./chunk-FURB5RP7.js";
|
|
4
|
+
import {
|
|
5
|
+
ConfirmationTimeoutError,
|
|
6
|
+
InsufficientFundsError,
|
|
7
|
+
UnknownTokenError,
|
|
8
|
+
WrongFamilyError,
|
|
9
|
+
nativeCost,
|
|
10
|
+
rejectForeignToken,
|
|
11
|
+
toInsufficientFundsError
|
|
12
|
+
} from "./chunk-3TQJJ4SQ.js";
|
|
13
|
+
|
|
14
|
+
// src/drivers/tron/index.ts
|
|
15
|
+
import { TronWeb } from "tronweb";
|
|
16
|
+
|
|
17
|
+
// src/drivers/tron/chains.ts
|
|
18
|
+
var TRX_DECIMALS = 6;
|
|
19
|
+
var TRON_MAINNET = {
|
|
20
|
+
caip2: "tron:mainnet",
|
|
21
|
+
defaultRpc: "https://api.trongrid.io",
|
|
22
|
+
tokens: {
|
|
23
|
+
// USD₮ (Tether) — the dominant stablecoin on Tron. Contract + 6 decimals +
|
|
24
|
+
// symbol verified live on-chain (TronGrid triggerconstantcontract) before shipping.
|
|
25
|
+
USDT: {
|
|
26
|
+
address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
|
|
27
|
+
decimals: 6,
|
|
28
|
+
symbol: "USDT"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/drivers/tron/pay.ts
|
|
34
|
+
var TRANSFER_FN = "transfer(address,uint256)";
|
|
35
|
+
var FEE_LIMIT = 1e8;
|
|
36
|
+
async function payTron(params) {
|
|
37
|
+
const { client, from, privateKey, tokenAddress, accept, bind } = params;
|
|
38
|
+
try {
|
|
39
|
+
const built = await client.transactionBuilder.triggerSmartContract(
|
|
40
|
+
tokenAddress,
|
|
41
|
+
TRANSFER_FN,
|
|
42
|
+
{ feeLimit: FEE_LIMIT },
|
|
43
|
+
[
|
|
44
|
+
{ type: "address", value: accept.payTo },
|
|
45
|
+
{ type: "uint256", value: accept.amount }
|
|
46
|
+
],
|
|
47
|
+
from
|
|
48
|
+
);
|
|
49
|
+
if (!built.result?.result) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Tron transfer build failed${built.result?.message ? `: ${decodeMaybeHex(built.result.message)}` : ""}.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
let unsigned = built.transaction;
|
|
55
|
+
if (bind === "memo") {
|
|
56
|
+
unsigned = await client.transactionBuilder.addUpdateData(unsigned, accept.extra.nonce, "utf8");
|
|
57
|
+
}
|
|
58
|
+
const signed = await client.trx.sign(unsigned, privateKey);
|
|
59
|
+
const broadcast = await client.trx.sendRawTransaction(signed);
|
|
60
|
+
if (broadcast.result === true || broadcast.txid || broadcast.transaction?.txID) {
|
|
61
|
+
return broadcast.txid ?? broadcast.transaction?.txID ?? signed.txID;
|
|
62
|
+
}
|
|
63
|
+
if (isTronAffordability(broadcast.code, broadcast.message)) {
|
|
64
|
+
throw new InsufficientFundsError(
|
|
65
|
+
`Tron payment rejected (${broadcast.code ?? "broadcast"}): not enough TRX for energy/bandwidth, or insufficient token balance.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Tron broadcast failed: ${broadcast.code ?? ""} ${decodeMaybeHex(broadcast.message)}`.trim()
|
|
70
|
+
);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof InsufficientFundsError) throw err;
|
|
73
|
+
throw toInsufficientFundsError(err) ?? err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function decodeMaybeHex(message) {
|
|
77
|
+
if (!message) return "";
|
|
78
|
+
if (/^[0-9a-fA-F]+$/.test(message) && message.length % 2 === 0) {
|
|
79
|
+
try {
|
|
80
|
+
const decoded = Buffer.from(message, "hex").toString("utf8");
|
|
81
|
+
if (/^[\x20-\x7e\s]+$/.test(decoded)) return decoded;
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return message;
|
|
86
|
+
}
|
|
87
|
+
function isTronAffordability(code, message) {
|
|
88
|
+
const decoded = decodeMaybeHex(message);
|
|
89
|
+
if (code && /BANDWIDTH_ERROR|CONTRACT_VALIDATE_ERROR/.test(code)) {
|
|
90
|
+
return /balance is not sufficient|insufficient|not enough|exceeds/i.test(decoded);
|
|
91
|
+
}
|
|
92
|
+
return /balance is not sufficient|insufficient (funds|balance|energy)|not enough/i.test(decoded);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/drivers/tron/verify.ts
|
|
96
|
+
var TRANSFER_TOPIC = "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
97
|
+
async function verifyTron(params) {
|
|
98
|
+
const { reader, accept, txid, tokenHex20, payToHex20, toBase58 } = params;
|
|
99
|
+
const required = BigInt(accept.amount);
|
|
100
|
+
let info;
|
|
101
|
+
try {
|
|
102
|
+
info = await reader.getTransactionInfo(txid);
|
|
103
|
+
} catch {
|
|
104
|
+
return txNotFound(txid);
|
|
105
|
+
}
|
|
106
|
+
if (!info || !info.id) return txNotFound(txid);
|
|
107
|
+
if (info.receipt?.result !== "SUCCESS") {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "tx_reverted",
|
|
111
|
+
detail: `Tron tx ${txid} did not succeed (receipt.result=${info.receipt?.result ?? "none"}).`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const ageSeconds = Math.floor(Date.now() / 1e3) - Math.floor(info.blockTimeStamp / 1e3);
|
|
115
|
+
if (Number.isFinite(ageSeconds) && ageSeconds > accept.maxTimeoutSeconds) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
error: "payment_expired",
|
|
119
|
+
detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const { total, from } = sumTransfersTo(info.log ?? [], tokenHex20, payToHex20);
|
|
123
|
+
if (total < required) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
error: "transfer_not_found",
|
|
127
|
+
detail: `No TRC-20 Transfer of >= ${required} (token ${accept.asset}) to ${accept.payTo} in ${txid}.`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
receipt: {
|
|
133
|
+
scheme: "onchain-proof",
|
|
134
|
+
success: true,
|
|
135
|
+
network: accept.network,
|
|
136
|
+
transaction: txid,
|
|
137
|
+
asset: accept.asset,
|
|
138
|
+
amount: accept.amount,
|
|
139
|
+
payer: from ? toBase58(from) : "",
|
|
140
|
+
payTo: accept.payTo,
|
|
141
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function sumTransfersTo(logs, tokenHex20, payToHex20) {
|
|
146
|
+
let total = 0n;
|
|
147
|
+
let from = null;
|
|
148
|
+
for (const log of logs) {
|
|
149
|
+
if ((log.address ?? "").toLowerCase() !== tokenHex20) continue;
|
|
150
|
+
const topics = log.topics ?? [];
|
|
151
|
+
if (topics.length < 3) continue;
|
|
152
|
+
if ((topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
|
|
153
|
+
if (last20(topics[2]) !== payToHex20) continue;
|
|
154
|
+
try {
|
|
155
|
+
total += BigInt(`0x${log.data}`);
|
|
156
|
+
from = last20(topics[1]);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { total, from };
|
|
161
|
+
}
|
|
162
|
+
function last20(topic) {
|
|
163
|
+
return (topic ?? "").slice(-40).toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
function txNotFound(txid) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
error: "tx_not_found",
|
|
169
|
+
detail: `Tron tx ${txid} not found or not yet confirmed (solidified) \u2014 retry.`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/drivers/tron/wallet.ts
|
|
174
|
+
function assertTronWallet(wallet, network) {
|
|
175
|
+
if (typeof wallet !== "object" || wallet === null) {
|
|
176
|
+
throw new WrongFamilyError(
|
|
177
|
+
`chain ${network} is Tron; wallet must be { privateKey } (32-byte hex).`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if ("walletClient" in wallet) {
|
|
181
|
+
throw new WrongFamilyError(
|
|
182
|
+
`chain ${network} is Tron; a viem { walletClient } can't be used \u2014 pass { privateKey } (32-byte hex).`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if ("secretKey" in wallet || "signer" in wallet || "mnemonic" in wallet || "keyPair" in wallet || "secret" in wallet || "keypair" in wallet || "seed" in wallet) {
|
|
186
|
+
throw new WrongFamilyError(
|
|
187
|
+
`chain ${network} is Tron; that looks like a Solana/TON/Stellar/XRPL wallet \u2014 pass { privateKey } (32-byte hex).`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (!("privateKey" in wallet)) {
|
|
191
|
+
throw new WrongFamilyError(
|
|
192
|
+
`chain ${network} is Tron; wallet must be { privateKey } (32-byte hex).`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return wallet;
|
|
196
|
+
}
|
|
197
|
+
function resolveTronPrivateKey(config) {
|
|
198
|
+
if (!config.privateKey) {
|
|
199
|
+
throw new WrongFamilyError("Tron wallet needs { privateKey } (32-byte hex).");
|
|
200
|
+
}
|
|
201
|
+
const hex = config.privateKey.replace(/^0x/i, "").toLowerCase();
|
|
202
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) {
|
|
203
|
+
throw new WrongFamilyError(
|
|
204
|
+
"Tron wallet { privateKey } must be a 32-byte hex string (64 hex chars)."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return hex;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/drivers/tron/index.ts
|
|
211
|
+
var tronDriver = {
|
|
212
|
+
family: "tron",
|
|
213
|
+
resolve(opts) {
|
|
214
|
+
if (opts.chain !== "tron") return null;
|
|
215
|
+
const rpcUrl = opts.rpcUrl ?? TRON_MAINNET.defaultRpc;
|
|
216
|
+
return makeTronNetwork(TRON_MAINNET, rpcUrl);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
function makeTronNetwork(preset, rpcUrl) {
|
|
220
|
+
const tronWeb = new TronWeb({ fullHost: rpcUrl });
|
|
221
|
+
const network = preset.caip2;
|
|
222
|
+
function hex20(base58) {
|
|
223
|
+
return tronWeb.address.toHex(base58).replace(/^41/i, "").toLowerCase();
|
|
224
|
+
}
|
|
225
|
+
const reader = {
|
|
226
|
+
async getTransactionInfo(txid) {
|
|
227
|
+
const info = await tronWeb.trx.getTransactionInfo(txid);
|
|
228
|
+
if (!info || !info.id) return null;
|
|
229
|
+
return info;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
family: "tron",
|
|
234
|
+
network,
|
|
235
|
+
supports: (n) => n === network,
|
|
236
|
+
resolveToken(token) {
|
|
237
|
+
if (token === "native") {
|
|
238
|
+
throw new UnknownTokenError(
|
|
239
|
+
`Tron payments are TRC-20 only \u2014 native TRX isn't a built-in asset. Use 'USDT' or a custom { address, decimals }.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (typeof token === "string") {
|
|
243
|
+
const info = preset.tokens[token.toUpperCase()];
|
|
244
|
+
if (!info) {
|
|
245
|
+
const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
|
|
246
|
+
throw new UnknownTokenError(
|
|
247
|
+
`token "${token}" isn't built in for Tron (known: ${known}). Note: native USDC doesn't exist on Tron. Pass { address, decimals } for a custom TRC-20.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return { asset: info.address, decimals: info.decimals, symbol: info.symbol };
|
|
251
|
+
}
|
|
252
|
+
rejectForeignToken(token, "tron", network);
|
|
253
|
+
if (!("address" in token)) {
|
|
254
|
+
throw new WrongFamilyError(
|
|
255
|
+
`chain ${network} is Tron; a custom token must be { address, decimals } (Base58 T\u2026 contract).`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const t = token;
|
|
259
|
+
if (t.address.startsWith("0x") || !tronWeb.isAddress(t.address)) {
|
|
260
|
+
throw new WrongFamilyError(
|
|
261
|
+
`chain ${network} is Tron, but token address "${t.address}" is not a valid Tron contract (T\u2026).`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
asset: t.address,
|
|
266
|
+
decimals: t.decimals,
|
|
267
|
+
...t.symbol ? { symbol: t.symbol } : {}
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
describeAsset(asset) {
|
|
271
|
+
if (asset === "native") return null;
|
|
272
|
+
for (const info of Object.values(preset.tokens)) {
|
|
273
|
+
if (info.address === asset) return { symbol: info.symbol, decimals: info.decimals };
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
},
|
|
277
|
+
assertValidPayTo(payTo) {
|
|
278
|
+
if (payTo.startsWith("0x")) {
|
|
279
|
+
throw new WrongFamilyError(
|
|
280
|
+
`chain ${network} is Tron, but payTo "${payTo}" looks like an EVM address.`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (!tronWeb.isAddress(payTo)) {
|
|
284
|
+
throw new WrongFamilyError(
|
|
285
|
+
`chain ${network} is Tron, but payTo "${payTo}" is not a valid Tron address (T\u2026).`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
bindWallet(wallet) {
|
|
290
|
+
return { _native: assertTronWallet(wallet, network) };
|
|
291
|
+
},
|
|
292
|
+
async send(wallet, accept) {
|
|
293
|
+
const privateKey = resolveTronPrivateKey(wallet._native);
|
|
294
|
+
const from = tronWeb.address.fromPrivateKey(privateKey);
|
|
295
|
+
if (!from) {
|
|
296
|
+
throw new WrongFamilyError("Tron wallet { privateKey } could not derive an address.");
|
|
297
|
+
}
|
|
298
|
+
return payTron({
|
|
299
|
+
client: tronWeb,
|
|
300
|
+
from,
|
|
301
|
+
privateKey,
|
|
302
|
+
tokenAddress: accept.asset,
|
|
303
|
+
accept
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
async confirm(ref) {
|
|
307
|
+
const txid = stripTronPrefix(ref);
|
|
308
|
+
for (let i = 0; i < 30; i += 1) {
|
|
309
|
+
try {
|
|
310
|
+
const info = await reader.getTransactionInfo(txid);
|
|
311
|
+
if (info) return { height: String(info.blockNumber ?? 0) };
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
await delay(2500);
|
|
315
|
+
}
|
|
316
|
+
throw new ConfirmationTimeoutError(`Tron tx ${txid} did not solidify in time.`);
|
|
317
|
+
},
|
|
318
|
+
async estimateCost(accept, opts) {
|
|
319
|
+
const ENERGY_PRICE = 420n;
|
|
320
|
+
const BANDWIDTH_SUN = 345000n;
|
|
321
|
+
if (opts?.from) {
|
|
322
|
+
try {
|
|
323
|
+
const r = await tronWeb.transactionBuilder.triggerConstantContract(
|
|
324
|
+
accept.asset,
|
|
325
|
+
"transfer(address,uint256)",
|
|
326
|
+
{},
|
|
327
|
+
[
|
|
328
|
+
{ type: "address", value: accept.payTo },
|
|
329
|
+
{ type: "uint256", value: accept.amount }
|
|
330
|
+
],
|
|
331
|
+
opts.from
|
|
332
|
+
);
|
|
333
|
+
const energy = BigInt(r.energy_used ?? 0);
|
|
334
|
+
if (energy > 0n) {
|
|
335
|
+
return nativeCost({
|
|
336
|
+
symbol: "TRX",
|
|
337
|
+
decimals: TRX_DECIMALS,
|
|
338
|
+
fee: energy * ENERGY_PRICE + BANDWIDTH_SUN,
|
|
339
|
+
basis: "estimated",
|
|
340
|
+
detail: `energy ${energy} @ ${ENERGY_PRICE} sun + bandwidth`
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return nativeCost({
|
|
347
|
+
symbol: "TRX",
|
|
348
|
+
decimals: TRX_DECIMALS,
|
|
349
|
+
fee: 30000n * ENERGY_PRICE + BANDWIDTH_SUN,
|
|
350
|
+
basis: "heuristic",
|
|
351
|
+
detail: "~30k energy + bandwidth (sender not staked; pass { from } for a precise estimate)"
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
async verify(ref, accept) {
|
|
355
|
+
const txid = stripTronPrefix(ref);
|
|
356
|
+
return verifyTron({
|
|
357
|
+
reader,
|
|
358
|
+
accept,
|
|
359
|
+
txid,
|
|
360
|
+
tokenHex20: hex20(accept.asset),
|
|
361
|
+
payToHex20: hex20(accept.payTo),
|
|
362
|
+
toBase58: (h) => tronWeb.address.fromHex(`41${h}`)
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function stripTronPrefix(ref) {
|
|
368
|
+
return ref.startsWith("tron:") ? ref.slice(5) : ref;
|
|
369
|
+
}
|
|
370
|
+
export {
|
|
371
|
+
tronDriver
|
|
372
|
+
};
|