@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,304 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunkWQWNPAYQcjs = require('./chunk-WQWNPAYQ.cjs');
|
|
10
|
+
|
|
11
|
+
// src/drivers/sui/index.ts
|
|
12
|
+
var _jsonRpc = require('@mysten/sui/jsonRpc');
|
|
13
|
+
var _utils = require('@mysten/sui/utils');
|
|
14
|
+
|
|
15
|
+
// src/drivers/sui/chains.ts
|
|
16
|
+
var SUI_DECIMALS = 9;
|
|
17
|
+
var SUI_SYMBOL = "SUI";
|
|
18
|
+
var SUI_NATIVE_COINTYPE = "0x2::sui::SUI";
|
|
19
|
+
var SUI_MAINNET = {
|
|
20
|
+
caip2: "sui:mainnet",
|
|
21
|
+
defaultRpc: "https://fullnode.mainnet.sui.io:443",
|
|
22
|
+
tokens: {
|
|
23
|
+
// Circle USDC — coin type + 6 decimals verified live on mainnet
|
|
24
|
+
// (suix_getCoinMetadata) before shipping. No native USDT on Sui.
|
|
25
|
+
USDC: {
|
|
26
|
+
coinType: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
|
|
27
|
+
decimals: 6,
|
|
28
|
+
symbol: "USDC"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/drivers/sui/pay.ts
|
|
34
|
+
var _transactions = require('@mysten/sui/transactions');
|
|
35
|
+
async function paySui(params) {
|
|
36
|
+
const { client, keypair, accept } = params;
|
|
37
|
+
const sender = keypair.getPublicKey().toSuiAddress();
|
|
38
|
+
const amount = BigInt(accept.amount);
|
|
39
|
+
try {
|
|
40
|
+
const tx = new (0, _transactions.Transaction)();
|
|
41
|
+
tx.setSender(sender);
|
|
42
|
+
if (accept.asset === "native") {
|
|
43
|
+
const [coin] = tx.splitCoins(tx.gas, [amount]);
|
|
44
|
+
tx.transferObjects([coin], accept.payTo);
|
|
45
|
+
} else {
|
|
46
|
+
const coins = await client.getCoins({ owner: sender, coinType: accept.asset });
|
|
47
|
+
if (!coins.data.length) {
|
|
48
|
+
throw new (0, _chunkWQWNPAYQcjs.InsufficientFundsError)(
|
|
49
|
+
`Sui wallet holds no ${accept.asset} coin objects to pay from.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const ids = coins.data.map((c) => c.coinObjectId);
|
|
53
|
+
const primary = ids[0];
|
|
54
|
+
if (ids.length > 1) {
|
|
55
|
+
tx.mergeCoins(tx.object(primary), ids.slice(1).map((id) => tx.object(id)));
|
|
56
|
+
}
|
|
57
|
+
const [coin] = tx.splitCoins(tx.object(primary), [amount]);
|
|
58
|
+
tx.transferObjects([coin], accept.payTo);
|
|
59
|
+
}
|
|
60
|
+
const res = await client.signAndExecuteTransaction({
|
|
61
|
+
signer: keypair,
|
|
62
|
+
transaction: tx,
|
|
63
|
+
options: { showEffects: true }
|
|
64
|
+
});
|
|
65
|
+
return res.digest;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err instanceof _chunkWQWNPAYQcjs.InsufficientFundsError) throw err;
|
|
68
|
+
if (isSuiAffordability(err)) {
|
|
69
|
+
throw new (0, _chunkWQWNPAYQcjs.InsufficientFundsError)(
|
|
70
|
+
err instanceof Error ? err.message : "Insufficient SUI/coin balance for the payment.",
|
|
71
|
+
{ cause: err }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
throw _nullishCoalesce(_chunkWQWNPAYQcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isSuiAffordability(err) {
|
|
78
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
79
|
+
return /no valid gas|gas.*(balance|coin)|insufficient|balance is too low|GasBalanceTooLow/i.test(m);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/drivers/sui/verify.ts
|
|
83
|
+
async function verifySui(params) {
|
|
84
|
+
const { reader, digest, accept } = params;
|
|
85
|
+
const required = BigInt(accept.amount);
|
|
86
|
+
const wantCoinType = accept.asset === "native" ? SUI_NATIVE_COINTYPE : accept.asset;
|
|
87
|
+
let tx;
|
|
88
|
+
try {
|
|
89
|
+
tx = await reader.getTransaction(digest);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return txNotFound(digest);
|
|
92
|
+
}
|
|
93
|
+
if (!tx) return txNotFound(digest);
|
|
94
|
+
if (tx.status !== "success") {
|
|
95
|
+
return { ok: false, error: "tx_reverted", detail: `Sui tx ${digest} did not succeed (status=${tx.status}).` };
|
|
96
|
+
}
|
|
97
|
+
if (typeof tx.timestampMs === "number") {
|
|
98
|
+
const ageSeconds = Math.floor(Date.now() / 1e3) - Math.floor(tx.timestampMs / 1e3);
|
|
99
|
+
if (ageSeconds > accept.maxTimeoutSeconds) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
error: "payment_expired",
|
|
103
|
+
detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
let paid = 0n;
|
|
108
|
+
let payer = "";
|
|
109
|
+
for (const bc of tx.balanceChanges) {
|
|
110
|
+
if (bc.coinType !== wantCoinType) continue;
|
|
111
|
+
let v;
|
|
112
|
+
try {
|
|
113
|
+
v = BigInt(bc.amount);
|
|
114
|
+
} catch (e2) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (bc.owner === accept.payTo && v > 0n) paid += v;
|
|
118
|
+
else if (v < 0n && !payer) payer = bc.owner;
|
|
119
|
+
}
|
|
120
|
+
if (paid < required) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
error: "transfer_not_found",
|
|
124
|
+
detail: `No Sui transfer of >= ${required} (${wantCoinType}) to ${accept.payTo} in ${digest}.`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
receipt: {
|
|
130
|
+
scheme: "onchain-proof",
|
|
131
|
+
success: true,
|
|
132
|
+
network: accept.network,
|
|
133
|
+
transaction: digest,
|
|
134
|
+
asset: accept.asset,
|
|
135
|
+
amount: accept.amount,
|
|
136
|
+
payer,
|
|
137
|
+
payTo: accept.payTo,
|
|
138
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function txNotFound(digest) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
error: "tx_not_found",
|
|
146
|
+
detail: `Sui tx ${digest} not found or not yet propagated \u2014 retry.`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/drivers/sui/wallet.ts
|
|
151
|
+
var _ed25519 = require('@mysten/sui/keypairs/ed25519');
|
|
152
|
+
function assertSuiWallet(wallet, network) {
|
|
153
|
+
if (typeof wallet !== "object" || wallet === null) {
|
|
154
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
155
|
+
`chain ${network} is Sui; wallet must be { privateKey } (suiprivkey1\u2026 ) or { keypair }.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if ("walletClient" in wallet) {
|
|
159
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
160
|
+
`chain ${network} is Sui; a viem { walletClient } can't be used \u2014 pass { privateKey } (suiprivkey1\u2026) or { keypair }.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if ("secretKey" in wallet || "signer" in wallet || "mnemonic" in wallet || "keyPair" in wallet || "secret" in wallet || "seed" in wallet || "accountId" in wallet) {
|
|
164
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
165
|
+
`chain ${network} is Sui; that looks like another family's wallet \u2014 pass { privateKey } (suiprivkey1\u2026) or { keypair }.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!("privateKey" in wallet) && !("keypair" in wallet)) {
|
|
169
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
170
|
+
`chain ${network} is Sui; wallet must be { privateKey } (suiprivkey1\u2026) or { keypair }.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return wallet;
|
|
174
|
+
}
|
|
175
|
+
function resolveSuiKeypair(config) {
|
|
176
|
+
if (config.keypair) return config.keypair;
|
|
177
|
+
if (config.privateKey != null) {
|
|
178
|
+
try {
|
|
179
|
+
return _ed25519.Ed25519Keypair.fromSecretKey(config.privateKey);
|
|
180
|
+
} catch (cause) {
|
|
181
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
182
|
+
"Sui wallet { privateKey } is not a valid suiprivkey1\u2026 secret (or 32-byte key).",
|
|
183
|
+
{ cause }
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)("Sui wallet needs { privateKey } (suiprivkey1\u2026) or { keypair }.");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/drivers/sui/index.ts
|
|
191
|
+
var suiDriver = {
|
|
192
|
+
family: "sui",
|
|
193
|
+
resolve(opts) {
|
|
194
|
+
if (opts.chain !== "sui") return null;
|
|
195
|
+
const rpcUrl = _nullishCoalesce(opts.rpcUrl, () => ( SUI_MAINNET.defaultRpc));
|
|
196
|
+
return makeSuiNetwork(SUI_MAINNET, rpcUrl);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
function ownerAddress(owner) {
|
|
200
|
+
if (owner && typeof owner === "object" && "AddressOwner" in owner) {
|
|
201
|
+
return owner.AddressOwner;
|
|
202
|
+
}
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
function makeSuiNetwork(preset, rpcUrl) {
|
|
206
|
+
const client = new (0, _jsonRpc.SuiJsonRpcClient)({ url: rpcUrl, network: "mainnet" });
|
|
207
|
+
const network = preset.caip2;
|
|
208
|
+
const reader = {
|
|
209
|
+
async getTransaction(digest) {
|
|
210
|
+
const tx = await client.getTransactionBlock({
|
|
211
|
+
digest,
|
|
212
|
+
options: { showBalanceChanges: true, showEffects: true }
|
|
213
|
+
});
|
|
214
|
+
if (!tx) return null;
|
|
215
|
+
const balanceChanges = (_nullishCoalesce(tx.balanceChanges, () => ( []))).map((bc) => ({
|
|
216
|
+
owner: ownerAddress(bc.owner),
|
|
217
|
+
coinType: bc.coinType,
|
|
218
|
+
amount: bc.amount
|
|
219
|
+
}));
|
|
220
|
+
return {
|
|
221
|
+
status: _nullishCoalesce(_optionalChain([tx, 'access', _ => _.effects, 'optionalAccess', _2 => _2.status, 'optionalAccess', _3 => _3.status]), () => ( "unknown")),
|
|
222
|
+
timestampMs: tx.timestampMs != null ? Number(tx.timestampMs) : void 0,
|
|
223
|
+
balanceChanges
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
family: "sui",
|
|
229
|
+
network,
|
|
230
|
+
supports: (n) => n === network,
|
|
231
|
+
resolveToken(token) {
|
|
232
|
+
if (token === "native") {
|
|
233
|
+
return { asset: "native", decimals: SUI_DECIMALS, symbol: SUI_SYMBOL };
|
|
234
|
+
}
|
|
235
|
+
if (typeof token === "string") {
|
|
236
|
+
const info = preset.tokens[token.toUpperCase()];
|
|
237
|
+
if (!info) {
|
|
238
|
+
const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
|
|
239
|
+
throw new (0, _chunkWQWNPAYQcjs.UnknownTokenError)(
|
|
240
|
+
`token "${token}" isn't built in for Sui (known: ${known}). Note: no native USDT on Sui. Pass { coinType, decimals } for a custom coin, or use 'native'.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return { asset: info.coinType, decimals: info.decimals, symbol: info.symbol };
|
|
244
|
+
}
|
|
245
|
+
_chunkWQWNPAYQcjs.rejectForeignToken.call(void 0, token, "sui", network);
|
|
246
|
+
const t = token;
|
|
247
|
+
if (!t.coinType || typeof t.decimals !== "number") {
|
|
248
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
249
|
+
`chain ${network} is Sui; a custom token must be { coinType, decimals }.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
asset: t.coinType,
|
|
254
|
+
decimals: t.decimals,
|
|
255
|
+
...t.symbol ? { symbol: t.symbol } : {}
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
describeAsset(asset) {
|
|
259
|
+
if (asset === "native") return { symbol: SUI_SYMBOL, decimals: SUI_DECIMALS };
|
|
260
|
+
for (const info of Object.values(preset.tokens)) {
|
|
261
|
+
if (info.coinType === asset) return { symbol: info.symbol, decimals: info.decimals };
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
},
|
|
265
|
+
assertValidPayTo(payTo) {
|
|
266
|
+
if (!_utils.isValidSuiAddress.call(void 0, payTo)) {
|
|
267
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
268
|
+
`chain ${network} is Sui, but payTo "${payTo}" is not a valid Sui address (0x + 32 bytes).`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
bindWallet(wallet) {
|
|
273
|
+
return { _native: assertSuiWallet(wallet, network) };
|
|
274
|
+
},
|
|
275
|
+
async send(wallet, accept) {
|
|
276
|
+
const keypair = resolveSuiKeypair(wallet._native);
|
|
277
|
+
return paySui({ client, keypair, accept });
|
|
278
|
+
},
|
|
279
|
+
async confirm(ref) {
|
|
280
|
+
try {
|
|
281
|
+
const tx = await client.waitForTransaction({ digest: ref, options: { showEffects: true } });
|
|
282
|
+
return { height: String(_nullishCoalesce(tx.checkpoint, () => ( "0"))) };
|
|
283
|
+
} catch (err) {
|
|
284
|
+
throw new (0, _chunkWQWNPAYQcjs.ConfirmationTimeoutError)(`Sui tx ${ref} did not finalize in time.`, { cause: err });
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async estimateCost() {
|
|
288
|
+
return _chunkWQWNPAYQcjs.nativeCost.call(void 0, {
|
|
289
|
+
symbol: SUI_SYMBOL,
|
|
290
|
+
decimals: SUI_DECIMALS,
|
|
291
|
+
fee: 3000000n,
|
|
292
|
+
// ~0.003 SUI
|
|
293
|
+
basis: "heuristic",
|
|
294
|
+
detail: "\u22480.003 SUI (computation + storage; storage is largely rebated on success)"
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
async verify(ref, accept) {
|
|
298
|
+
return verifySui({ reader, digest: ref, accept });
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
exports.suiDriver = suiDriver;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
|
+
|
|
3
|
+
var _chunkCQREG5LEcjs = require('./chunk-CQREG5LE.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
var _chunkWQWNPAYQcjs = require('./chunk-WQWNPAYQ.cjs');
|
|
12
|
+
|
|
13
|
+
// src/drivers/ton/index.ts
|
|
14
|
+
var _core = require('@ton/core');
|
|
15
|
+
var _ton = require('@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
|
+
|
|
35
|
+
|
|
36
|
+
var OP_JETTON_TRANSFER = 260734629;
|
|
37
|
+
var JETTON_GAS = _core.toNano.call(void 0, "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 = _core.comment.call(void 0, accept.extra.nonce);
|
|
44
|
+
const message = accept.asset === "native" ? _core.internal.call(void 0, {
|
|
45
|
+
to: _core.Address.parse(accept.payTo),
|
|
46
|
+
value: BigInt(accept.amount),
|
|
47
|
+
bounce: false,
|
|
48
|
+
body: cmt
|
|
49
|
+
}) : _core.internal.call(void 0, {
|
|
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: _ton.SendMode.PAY_GAS_SEPARATELY | _ton.SendMode.IGNORE_ERRORS,
|
|
59
|
+
messages: [message]
|
|
60
|
+
});
|
|
61
|
+
await waitForSeqno(opened, seqno);
|
|
62
|
+
}
|
|
63
|
+
function buildJettonTransfer(accept, responseTo, forward) {
|
|
64
|
+
return _core.beginCell.call(void 0, ).storeUint(OP_JETTON_TRANSFER, 32).storeUint(0n, 64).storeCoins(BigInt(accept.amount)).storeAddress(_core.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 _chunkCQREG5LEcjs.delay.call(void 0, intervalMs);
|
|
69
|
+
let current;
|
|
70
|
+
try {
|
|
71
|
+
current = await opened.getSeqno();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (current > from) return;
|
|
76
|
+
}
|
|
77
|
+
throw new (0, _chunkWQWNPAYQcjs.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 (e2) {
|
|
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: (_nullishCoalesce(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 (e3) {
|
|
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 (e4) {
|
|
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 (e5) {
|
|
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
|
+
var _crypto = require('@ton/crypto');
|
|
202
|
+
|
|
203
|
+
function assertTonWallet(wallet, network) {
|
|
204
|
+
if (typeof wallet !== "object" || wallet === null) {
|
|
205
|
+
throw new (0, _chunkWQWNPAYQcjs.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 (0, _chunkWQWNPAYQcjs.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 (0, _chunkWQWNPAYQcjs.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 = _nullishCoalesce(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 _crypto.mnemonicToWalletKey.call(void 0, words);
|
|
227
|
+
} else if (config.keyPair) {
|
|
228
|
+
keyPair = config.keyPair;
|
|
229
|
+
} else {
|
|
230
|
+
throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)("TON wallet needs { mnemonic } (24 words) or { keyPair }.");
|
|
231
|
+
}
|
|
232
|
+
const contract = version === "v5r1" ? _ton.WalletContractV5R1.create({ workchain: 0, publicKey: keyPair.publicKey }) : _ton.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 = _nullishCoalesce(opts.rpcUrl, () => ( TON_MAINNET.defaultRpc));
|
|
242
|
+
return makeTonNetwork(TON_MAINNET, rpcUrl);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
function makeTonNetwork(preset, rpcUrl) {
|
|
246
|
+
const client = new (0, _ton.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(_ton.JettonMaster.create(_core.Address.parse(master))).getWalletAddress(_core.Address.parse(owner));
|
|
254
|
+
jwCache.set(key, p);
|
|
255
|
+
}
|
|
256
|
+
return p;
|
|
257
|
+
}
|
|
258
|
+
function watchAccountFor(accept) {
|
|
259
|
+
return accept.asset === "native" ? Promise.resolve(_core.Address.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 (0, _chunkWQWNPAYQcjs.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
|
+
_chunkWQWNPAYQcjs.rejectForeignToken.call(void 0, token, "ton", network);
|
|
280
|
+
if (!("master" in token)) {
|
|
281
|
+
throw new (0, _chunkWQWNPAYQcjs.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 (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
|
|
301
|
+
`chain ${network} is TON, but payTo "${payTo}" looks like an EVM address.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
_core.Address.parse(payTo);
|
|
306
|
+
} catch (e6) {
|
|
307
|
+
throw new (0, _chunkWQWNPAYQcjs.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 _nullishCoalesce(_chunkWQWNPAYQcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
async confirm(ref) {
|
|
327
|
+
const { watch, nonce } = decodeRef(ref);
|
|
328
|
+
const account = _core.Address.parse(watch);
|
|
329
|
+
for (let i = 0; i < 40; i += 1) {
|
|
330
|
+
await _chunkCQREG5LEcjs.delay.call(void 0, 2500);
|
|
331
|
+
let txs;
|
|
332
|
+
try {
|
|
333
|
+
txs = await client.getTransactions(account, { limit: 16, archival: true });
|
|
334
|
+
} catch (e7) {
|
|
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 (0, _chunkWQWNPAYQcjs.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 _chunkWQWNPAYQcjs.nativeCost.call(void 0, { 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
|
+
|
|
365
|
+
|
|
366
|
+
exports.tonDriver = tonDriver;
|