@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.
@@ -0,0 +1,389 @@
1
+ import {
2
+ delay
3
+ } from "./chunk-FURB5RP7.js";
4
+ import {
5
+ ConfirmationTimeoutError,
6
+ InsufficientFundsError,
7
+ UnknownTokenError,
8
+ WrongFamilyError,
9
+ nativeCost,
10
+ parseUnits,
11
+ rejectForeignToken,
12
+ toInsufficientFundsError
13
+ } from "./chunk-3TQJJ4SQ.js";
14
+
15
+ // src/drivers/stellar/index.ts
16
+ import { Horizon, StrKey as StrKey2 } from "@stellar/stellar-sdk";
17
+
18
+ // src/drivers/stellar/chains.ts
19
+ var STELLAR_DECIMALS = 7;
20
+ var XLM_SYMBOL = "XLM";
21
+ var STELLAR_PASSPHRASE = "Public Global Stellar Network ; September 2015";
22
+ var STELLAR_MAINNET = {
23
+ caip2: "stellar:pubnet",
24
+ passphrase: STELLAR_PASSPHRASE,
25
+ defaultRpc: "https://horizon.stellar.org",
26
+ tokens: {
27
+ // Circle USDC — issuer + code verified live on Horizon /assets before shipping.
28
+ // (Mainnet issuer; NOT the testnet GBBD47IF… one.)
29
+ USDC: {
30
+ issuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
31
+ code: "USDC",
32
+ decimals: STELLAR_DECIMALS,
33
+ symbol: "USDC"
34
+ },
35
+ // Circle EURC — issuer + code verified live on Horizon /assets before shipping.
36
+ EURC: {
37
+ issuer: "GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2",
38
+ code: "EURC",
39
+ decimals: STELLAR_DECIMALS,
40
+ symbol: "EURC"
41
+ }
42
+ }
43
+ };
44
+ function stellarAssetId(code, issuer) {
45
+ return `${code}:${issuer}`;
46
+ }
47
+ function parseStellarAssetId(asset) {
48
+ if (asset === "native") return null;
49
+ const i = asset.indexOf(":");
50
+ if (i <= 0 || i === asset.length - 1) return null;
51
+ return { code: asset.slice(0, i), issuer: asset.slice(i + 1) };
52
+ }
53
+
54
+ // src/drivers/stellar/pay.ts
55
+ import {
56
+ Asset,
57
+ BASE_FEE,
58
+ Memo,
59
+ Operation,
60
+ TransactionBuilder,
61
+ hash
62
+ } from "@stellar/stellar-sdk";
63
+ function memoHashForNonce(nonce) {
64
+ return Buffer.from(hash(Buffer.from(nonce, "utf8")));
65
+ }
66
+ async function payStellar(params) {
67
+ const { server, keypair, accept } = params;
68
+ try {
69
+ const asset = assetForAccept(accept);
70
+ const source = await server.loadAccount(keypair.publicKey());
71
+ const tx = new TransactionBuilder(source, {
72
+ fee: BASE_FEE,
73
+ networkPassphrase: STELLAR_PASSPHRASE
74
+ }).addOperation(
75
+ Operation.payment({
76
+ destination: accept.payTo,
77
+ asset,
78
+ amount: accept.extra.amountFormatted
79
+ })
80
+ ).addMemo(Memo.hash(memoHashForNonce(accept.extra.nonce))).setTimeout(120).build();
81
+ tx.sign(keypair);
82
+ const res = await server.submitTransaction(tx);
83
+ return res.hash;
84
+ } catch (err) {
85
+ throw mapSubmitError(err);
86
+ }
87
+ }
88
+ function assetForAccept(accept) {
89
+ if (accept.asset === "native") return Asset.native();
90
+ const parts = parseStellarAssetId(accept.asset);
91
+ if (!parts) throw new Error(`Stellar: malformed asset id "${accept.asset}".`);
92
+ return new Asset(parts.code, parts.issuer);
93
+ }
94
+ function mapSubmitError(err) {
95
+ const codes = extractResultCodes(err);
96
+ if (codes.some((c) => /underfunded|insufficient|low_reserve|no_trust/i.test(c))) {
97
+ return new InsufficientFundsError(
98
+ "Stellar payment failed: insufficient balance/reserve, or the sender has no trustline for this asset.",
99
+ { cause: err }
100
+ );
101
+ }
102
+ if (isAccountNotFound(err)) {
103
+ return new InsufficientFundsError(
104
+ "Stellar source account not found on-chain \u2014 fund it (\u2265 base reserve) before it can pay.",
105
+ { cause: err }
106
+ );
107
+ }
108
+ return toInsufficientFundsError(err) ?? err;
109
+ }
110
+ function isAccountNotFound(err) {
111
+ const e = err;
112
+ return e?.response?.status === 404 || e?.name === "NotFoundError" || /not found|resource missing/i.test(e?.message ?? "");
113
+ }
114
+ function extractResultCodes(err) {
115
+ const extras = err?.response?.data?.extras?.result_codes;
116
+ if (!extras) return [];
117
+ return [extras.transaction ?? "", ...extras.operations ?? []].filter(Boolean);
118
+ }
119
+
120
+ // src/drivers/stellar/verify.ts
121
+ import { hash as hash2 } from "@stellar/stellar-sdk";
122
+ function memoCandidatesForNonce(nonce) {
123
+ const h = Buffer.from(hash2(Buffer.from(nonce, "utf8")));
124
+ return [h.toString("base64"), h.toString("hex")];
125
+ }
126
+ async function verifyStellar(params) {
127
+ const { reader, accept } = params;
128
+ const nonce = accept.extra.nonce;
129
+ const required = BigInt(accept.amount);
130
+ const wantMemo = memoCandidatesForNonce(nonce);
131
+ const wantAsset = parseStellarAssetId(accept.asset);
132
+ let txs;
133
+ try {
134
+ txs = await reader.transactionsForAccount(accept.payTo, 50);
135
+ } catch {
136
+ return rpcFailed(nonce);
137
+ }
138
+ const tx = txs.find(
139
+ (t) => t.memo_type === "hash" && typeof t.memo === "string" && wantMemo.includes(t.memo)
140
+ );
141
+ if (!tx) return notFound(nonce);
142
+ if (!tx.successful) {
143
+ return {
144
+ ok: false,
145
+ error: "tx_reverted",
146
+ detail: `Stellar tx ${tx.hash} for nonce ${nonce} failed on-chain.`
147
+ };
148
+ }
149
+ const ageSeconds = Math.floor(Date.now() / 1e3) - Math.floor(Date.parse(tx.created_at) / 1e3);
150
+ if (Number.isFinite(ageSeconds) && ageSeconds > accept.maxTimeoutSeconds) {
151
+ return {
152
+ ok: false,
153
+ error: "payment_expired",
154
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
155
+ };
156
+ }
157
+ let payments;
158
+ try {
159
+ payments = await reader.paymentsForTransaction(tx.hash);
160
+ } catch {
161
+ return rpcFailed(nonce);
162
+ }
163
+ const toUs = payments.filter(
164
+ (p) => p.type === "payment" && p.to === accept.payTo && assetMatches(p, wantAsset)
165
+ );
166
+ if (toUs.length === 0) {
167
+ return {
168
+ ok: false,
169
+ error: "transfer_not_found",
170
+ detail: `Stellar tx ${tx.hash} carries our nonce but has no matching ${wantAsset ? wantAsset.code : "XLM"} payment to ${accept.payTo}.`
171
+ };
172
+ }
173
+ const paid = toUs.reduce(
174
+ (sum, p) => sum + parseAmount(p.amount, accept.extra.decimals),
175
+ 0n
176
+ );
177
+ if (paid < required) {
178
+ return {
179
+ ok: false,
180
+ error: "amount_too_low",
181
+ detail: `Paid ${paid}, required ${required}.`
182
+ };
183
+ }
184
+ return {
185
+ ok: true,
186
+ receipt: {
187
+ scheme: "onchain-proof",
188
+ success: true,
189
+ network: accept.network,
190
+ transaction: tx.hash,
191
+ asset: accept.asset,
192
+ amount: accept.amount,
193
+ payer: toUs[0].from,
194
+ payTo: accept.payTo,
195
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
196
+ }
197
+ };
198
+ }
199
+ function assetMatches(p, want) {
200
+ if (want === null) return p.asset_type === "native";
201
+ return p.asset_type !== "native" && p.asset_code === want.code && p.asset_issuer === want.issuer;
202
+ }
203
+ function parseAmount(amount, decimals) {
204
+ try {
205
+ return parseUnits(amount, decimals);
206
+ } catch {
207
+ return 0n;
208
+ }
209
+ }
210
+ function notFound(nonce) {
211
+ return {
212
+ ok: false,
213
+ error: "transfer_not_found",
214
+ detail: `No matching Stellar payment found for nonce ${nonce} (not yet settled, or wrong recipient/amount/asset/memo).`
215
+ };
216
+ }
217
+ function rpcFailed(nonce) {
218
+ return {
219
+ ok: false,
220
+ error: "tx_not_found",
221
+ detail: `Could not read Horizon for nonce ${nonce} (transient RPC failure) \u2014 retry.`
222
+ };
223
+ }
224
+
225
+ // src/drivers/stellar/wallet.ts
226
+ import { Keypair, StrKey } from "@stellar/stellar-sdk";
227
+ function assertStellarWallet(wallet, network) {
228
+ if (typeof wallet !== "object" || wallet === null) {
229
+ throw new WrongFamilyError(
230
+ `chain ${network} is Stellar; wallet must be { secret } (S\u2026 seed) or { keypair }.`
231
+ );
232
+ }
233
+ if ("privateKey" in wallet || "walletClient" in wallet) {
234
+ throw new WrongFamilyError(
235
+ `chain ${network} is Stellar; an EVM wallet can't be used \u2014 pass { secret } (S\u2026 seed) or { keypair }.`
236
+ );
237
+ }
238
+ if ("secretKey" in wallet || "signer" in wallet || "mnemonic" in wallet || "keyPair" in wallet) {
239
+ throw new WrongFamilyError(
240
+ `chain ${network} is Stellar; that looks like a Solana/TON wallet \u2014 pass { secret } (S\u2026 seed) or { keypair }.`
241
+ );
242
+ }
243
+ if (!("secret" in wallet) && !("keypair" in wallet)) {
244
+ throw new WrongFamilyError(
245
+ `chain ${network} is Stellar; wallet must be { secret } (S\u2026 seed) or { keypair }.`
246
+ );
247
+ }
248
+ return wallet;
249
+ }
250
+ function resolveStellarWallet(config) {
251
+ if (config.keypair) return config.keypair;
252
+ if (config.secret) {
253
+ if (!StrKey.isValidEd25519SecretSeed(config.secret)) {
254
+ throw new WrongFamilyError(
255
+ "Stellar wallet { secret } is not a valid S\u2026 secret seed."
256
+ );
257
+ }
258
+ return Keypair.fromSecret(config.secret);
259
+ }
260
+ throw new WrongFamilyError("Stellar wallet needs { secret } (S\u2026 seed) or { keypair }.");
261
+ }
262
+
263
+ // src/drivers/stellar/index.ts
264
+ var stellarDriver = {
265
+ family: "stellar",
266
+ resolve(opts) {
267
+ if (opts.chain !== "stellar") return null;
268
+ const rpcUrl = opts.rpcUrl ?? STELLAR_MAINNET.defaultRpc;
269
+ return makeStellarNetwork(STELLAR_MAINNET, rpcUrl);
270
+ }
271
+ };
272
+ function makeStellarNetwork(preset, rpcUrl) {
273
+ const server = new Horizon.Server(rpcUrl);
274
+ const network = preset.caip2;
275
+ const reader = {
276
+ async transactionsForAccount(account, limit) {
277
+ const page = await server.transactions().forAccount(account).order("desc").limit(limit).includeFailed(true).call();
278
+ return page.records.map((r) => ({
279
+ hash: r.hash,
280
+ successful: r.successful,
281
+ memo: r.memo,
282
+ memo_type: r.memo_type,
283
+ created_at: r.created_at
284
+ }));
285
+ },
286
+ async paymentsForTransaction(txHash) {
287
+ const page = await server.payments().forTransaction(txHash).limit(100).call();
288
+ return page.records.filter((r) => r.type === "payment").map((r) => ({
289
+ type: r.type,
290
+ from: r.from,
291
+ to: r.to,
292
+ asset_type: r.asset_type,
293
+ asset_code: r.asset_code,
294
+ asset_issuer: r.asset_issuer,
295
+ amount: r.amount
296
+ }));
297
+ }
298
+ };
299
+ return {
300
+ family: "stellar",
301
+ network,
302
+ supports: (n) => n === network,
303
+ resolveToken(token) {
304
+ if (token === "native") {
305
+ return { asset: "native", decimals: STELLAR_DECIMALS, symbol: XLM_SYMBOL };
306
+ }
307
+ if (typeof token === "string") {
308
+ const info = preset.tokens[token.toUpperCase()];
309
+ if (!info) {
310
+ const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
311
+ throw new UnknownTokenError(
312
+ `token "${token}" isn't built in for Stellar (known: ${known}). Pass { issuer, code, decimals } for a custom asset, or use 'native'.`
313
+ );
314
+ }
315
+ return {
316
+ asset: stellarAssetId(info.code, info.issuer),
317
+ decimals: info.decimals,
318
+ symbol: info.symbol
319
+ };
320
+ }
321
+ rejectForeignToken(token, "stellar", network);
322
+ const t = token;
323
+ if (!t.issuer || !t.code || typeof t.decimals !== "number") {
324
+ throw new WrongFamilyError(
325
+ `chain ${network} is Stellar; a custom token must be { issuer, code, decimals }.`
326
+ );
327
+ }
328
+ return {
329
+ asset: stellarAssetId(t.code, t.issuer),
330
+ decimals: t.decimals,
331
+ symbol: t.symbol ?? t.code
332
+ };
333
+ },
334
+ describeAsset(asset) {
335
+ if (asset === "native") return { symbol: XLM_SYMBOL, decimals: STELLAR_DECIMALS };
336
+ for (const info of Object.values(preset.tokens)) {
337
+ if (stellarAssetId(info.code, info.issuer) === asset) {
338
+ return { symbol: info.symbol, decimals: info.decimals };
339
+ }
340
+ }
341
+ return null;
342
+ },
343
+ assertValidPayTo(payTo) {
344
+ if (payTo.startsWith("0x")) {
345
+ throw new WrongFamilyError(
346
+ `chain ${network} is Stellar, but payTo "${payTo}" looks like an EVM address.`
347
+ );
348
+ }
349
+ if (!StrKey2.isValidEd25519PublicKey(payTo)) {
350
+ throw new WrongFamilyError(
351
+ `chain ${network} is Stellar, but payTo "${payTo}" is not a valid Stellar account (G\u2026).`
352
+ );
353
+ }
354
+ },
355
+ bindWallet(wallet) {
356
+ return { _native: assertStellarWallet(wallet, network) };
357
+ },
358
+ async send(wallet, accept) {
359
+ const keypair = resolveStellarWallet(wallet._native);
360
+ return payStellar({ server, keypair, accept });
361
+ },
362
+ async confirm(ref) {
363
+ for (let i = 0; i < 10; i += 1) {
364
+ try {
365
+ const tx = await server.transactions().transaction(ref).call();
366
+ return { height: String(tx.ledger_attr ?? tx.ledger ?? 0) };
367
+ } catch {
368
+ await delay(1500);
369
+ }
370
+ }
371
+ throw new ConfirmationTimeoutError(`Stellar tx ${ref} not visible on Horizon in time.`);
372
+ },
373
+ async estimateCost() {
374
+ return nativeCost({
375
+ symbol: XLM_SYMBOL,
376
+ decimals: STELLAR_DECIMALS,
377
+ fee: 100n,
378
+ basis: "heuristic",
379
+ detail: "base fee 100 stroops (1 operation)"
380
+ });
381
+ },
382
+ async verify(_ref, accept) {
383
+ return verifyStellar({ reader, accept });
384
+ }
385
+ };
386
+ }
387
+ export {
388
+ stellarDriver
389
+ };
@@ -0,0 +1,304 @@
1
+ import {
2
+ ConfirmationTimeoutError,
3
+ InsufficientFundsError,
4
+ UnknownTokenError,
5
+ WrongFamilyError,
6
+ nativeCost,
7
+ rejectForeignToken,
8
+ toInsufficientFundsError
9
+ } from "./chunk-3TQJJ4SQ.js";
10
+
11
+ // src/drivers/sui/index.ts
12
+ import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
13
+ import { isValidSuiAddress } from "@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
+ import { Transaction } from "@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 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 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 InsufficientFundsError) throw err;
68
+ if (isSuiAffordability(err)) {
69
+ throw new InsufficientFundsError(
70
+ err instanceof Error ? err.message : "Insufficient SUI/coin balance for the payment.",
71
+ { cause: err }
72
+ );
73
+ }
74
+ throw toInsufficientFundsError(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 {
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 {
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
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
152
+ function assertSuiWallet(wallet, network) {
153
+ if (typeof wallet !== "object" || wallet === null) {
154
+ throw new WrongFamilyError(
155
+ `chain ${network} is Sui; wallet must be { privateKey } (suiprivkey1\u2026 ) or { keypair }.`
156
+ );
157
+ }
158
+ if ("walletClient" in wallet) {
159
+ throw new 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 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 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 Ed25519Keypair.fromSecretKey(config.privateKey);
180
+ } catch (cause) {
181
+ throw new WrongFamilyError(
182
+ "Sui wallet { privateKey } is not a valid suiprivkey1\u2026 secret (or 32-byte key).",
183
+ { cause }
184
+ );
185
+ }
186
+ }
187
+ throw new 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 = 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 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 = (tx.balanceChanges ?? []).map((bc) => ({
216
+ owner: ownerAddress(bc.owner),
217
+ coinType: bc.coinType,
218
+ amount: bc.amount
219
+ }));
220
+ return {
221
+ status: tx.effects?.status?.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 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
+ rejectForeignToken(token, "sui", network);
246
+ const t = token;
247
+ if (!t.coinType || typeof t.decimals !== "number") {
248
+ throw new 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 (!isValidSuiAddress(payTo)) {
267
+ throw new 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(tx.checkpoint ?? "0") };
283
+ } catch (err) {
284
+ throw new ConfirmationTimeoutError(`Sui tx ${ref} did not finalize in time.`, { cause: err });
285
+ }
286
+ },
287
+ async estimateCost() {
288
+ return nativeCost({
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
+ export {
303
+ suiDriver
304
+ };