@ouronet/ouronet-core 4.3.6

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +3043 -0
  2. package/README.md +1940 -0
  3. package/dist/codex/codec.d.ts +77 -0
  4. package/dist/codex/codec.js +123 -0
  5. package/dist/codex/errors.d.ts +14 -0
  6. package/dist/codex/errors.js +20 -0
  7. package/dist/codex/index.d.ts +27 -0
  8. package/dist/codex/index.js +25 -0
  9. package/dist/codex/seedTypeMigration.d.ts +35 -0
  10. package/dist/codex/seedTypeMigration.js +45 -0
  11. package/dist/codex/types.d.ts +100 -0
  12. package/dist/codex/types.js +19 -0
  13. package/dist/constants/index.d.ts +2 -0
  14. package/dist/constants/index.js +3 -0
  15. package/dist/constants/ouronet.d.ts +33 -0
  16. package/dist/constants/ouronet.js +59 -0
  17. package/dist/constants/tokenIds.d.ts +13 -0
  18. package/dist/constants/tokenIds.js +21 -0
  19. package/dist/index.d.ts +18 -0
  20. package/dist/index.js +1 -0
  21. package/dist/interactions/activateFunctions.d.ts +67 -0
  22. package/dist/interactions/activateFunctions.js +154 -0
  23. package/dist/interactions/addLiquidityFunctions.d.ts +140 -0
  24. package/dist/interactions/addLiquidityFunctions.js +485 -0
  25. package/dist/interactions/coilFunctions.d.ts +73 -0
  26. package/dist/interactions/coilFunctions.js +213 -0
  27. package/dist/interactions/crossChainFunctions.d.ts +211 -0
  28. package/dist/interactions/crossChainFunctions.js +463 -0
  29. package/dist/interactions/dexAcquisitionPoolFunctions.d.ts +18 -0
  30. package/dist/interactions/dexAcquisitionPoolFunctions.js +18 -0
  31. package/dist/interactions/dexCollectablesFunctions.d.ts +28 -0
  32. package/dist/interactions/dexCollectablesFunctions.js +227 -0
  33. package/dist/interactions/dexFunctions.d.ts +23 -0
  34. package/dist/interactions/dexFunctions.js +23 -0
  35. package/dist/interactions/dexOrtoFungibleFunctions.d.ts +33 -0
  36. package/dist/interactions/dexOrtoFungibleFunctions.js +171 -0
  37. package/dist/interactions/dexParseFunctions.d.ts +150 -0
  38. package/dist/interactions/dexParseFunctions.js +316 -0
  39. package/dist/interactions/dexSwapPairAdminFunctions.d.ts +75 -0
  40. package/dist/interactions/dexSwapPairAdminFunctions.js +255 -0
  41. package/dist/interactions/dexSwapPairCalcFunctions.d.ts +55 -0
  42. package/dist/interactions/dexSwapPairCalcFunctions.js +218 -0
  43. package/dist/interactions/dexSwapPairDashboardFunctions.d.ts +47 -0
  44. package/dist/interactions/dexSwapPairDashboardFunctions.js +179 -0
  45. package/dist/interactions/dexSwapPairExecuteFunctions.d.ts +40 -0
  46. package/dist/interactions/dexSwapPairExecuteFunctions.js +301 -0
  47. package/dist/interactions/dexSwapPairSmartSwapFunctions.d.ts +25 -0
  48. package/dist/interactions/dexSwapPairSmartSwapFunctions.js +166 -0
  49. package/dist/interactions/dexTrueFungibleFunctions.d.ts +52 -0
  50. package/dist/interactions/dexTrueFungibleFunctions.js +220 -0
  51. package/dist/interactions/dexTypes.d.ts +465 -0
  52. package/dist/interactions/dexTypes.js +6 -0
  53. package/dist/interactions/errors.d.ts +12 -0
  54. package/dist/interactions/errors.js +13 -0
  55. package/dist/interactions/guardFunctions.d.ts +33 -0
  56. package/dist/interactions/guardFunctions.js +110 -0
  57. package/dist/interactions/index.d.ts +1 -0
  58. package/dist/interactions/index.js +20 -0
  59. package/dist/interactions/infoOneFunctions.d.ts +133 -0
  60. package/dist/interactions/infoOneFunctions.js +566 -0
  61. package/dist/interactions/kadenaFunctions.d.ts +11 -0
  62. package/dist/interactions/kadenaFunctions.js +40 -0
  63. package/dist/interactions/kpayFunctions.d.ts +45 -0
  64. package/dist/interactions/kpayFunctions.js +191 -0
  65. package/dist/interactions/ouroAccountFunctions.d.ts +64 -0
  66. package/dist/interactions/ouroAccountFunctions.js +229 -0
  67. package/dist/interactions/ouroBalanceFunctions.d.ts +39 -0
  68. package/dist/interactions/ouroBalanceFunctions.js +100 -0
  69. package/dist/interactions/ouroCoilFunctions.d.ts +33 -0
  70. package/dist/interactions/ouroCoilFunctions.js +190 -0
  71. package/dist/interactions/ouroFunctions.d.ts +23 -0
  72. package/dist/interactions/ouroFunctions.js +23 -0
  73. package/dist/interactions/ouroMovieBoosterFunctions.d.ts +18 -0
  74. package/dist/interactions/ouroMovieBoosterFunctions.js +203 -0
  75. package/dist/interactions/ouroPriceFunctions.d.ts +38 -0
  76. package/dist/interactions/ouroPriceFunctions.js +96 -0
  77. package/dist/interactions/ouroPrimordialsFunctions.d.ts +10 -0
  78. package/dist/interactions/ouroPrimordialsFunctions.js +480 -0
  79. package/dist/interactions/ouroRotateFunctions.d.ts +22 -0
  80. package/dist/interactions/ouroRotateFunctions.js +114 -0
  81. package/dist/interactions/ouroSubCompressFunctions.d.ts +23 -0
  82. package/dist/interactions/ouroSubCompressFunctions.js +191 -0
  83. package/dist/interactions/ouroTransferFunctions.d.ts +14 -0
  84. package/dist/interactions/ouroTransferFunctions.js +87 -0
  85. package/dist/interactions/ouroTypes.d.ts +113 -0
  86. package/dist/interactions/ouroTypes.js +6 -0
  87. package/dist/interactions/ouroUrStoaFunctions.d.ts +16 -0
  88. package/dist/interactions/ouroUrStoaFunctions.js +121 -0
  89. package/dist/interactions/ouroWrapFunctions.d.ts +22 -0
  90. package/dist/interactions/ouroWrapFunctions.js +234 -0
  91. package/dist/interactions/pensionFunctions.d.ts +12 -0
  92. package/dist/interactions/pensionFunctions.js +127 -0
  93. package/dist/interactions/urStoaFunctions.d.ts +104 -0
  94. package/dist/interactions/urStoaFunctions.js +475 -0
  95. package/dist/interactions/wrapFunctions.d.ts +46 -0
  96. package/dist/interactions/wrapFunctions.js +221 -0
  97. package/dist/pact/cfmBuilders.d.ts +807 -0
  98. package/dist/pact/cfmBuilders.js +812 -0
  99. package/dist/pact/index.d.ts +1 -0
  100. package/dist/pact/index.js +10 -0
  101. package/package.json +64 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * urStoaFunctions.ts — Native UrStoa on-chain interactions
3
+ *
4
+ * Balance: (try 0.0 (coin.UR_UR|Balance "<account>"))
5
+ * Guard: (try false (coin.UR_UR|Guard "<account>"))
6
+ * Transfer: coin.C_UR|Transfer / Transmit / TransferAnew / TransmitAnew
7
+ */
8
+ import { Pact } from "@stoachain/kadena-stoic-legacy/client";
9
+ import { getFailoverClient } from "@stoachain/stoa-core/network";
10
+ import { calculateAutoGasLimit } from "@stoachain/stoa-core/gas";
11
+ import { pactRead } from "@stoachain/stoa-core/reads";
12
+ import { KADENA_CHAIN_ID, KADENA_NAMESPACE, KADENA_NETWORK, STOA_AUTONOMIC_OURONETGASSTATION, } from "../constants/index.js";
13
+ import { universalSignTransaction, fromKeypair } from "@stoachain/stoa-core/signing";
14
+ import { safeCreationTime, formatDecimalForPact } from "@stoachain/stoa-core/pact";
15
+ import { createSimulationError, logDetailedError } from "@stoachain/stoa-core/errors";
16
+ import { getLogger } from "@stoachain/stoa-core/observability";
17
+ import { ed25519 } from "@noble/curves/ed25519";
18
+ import { describeKeyset } from "./guardFunctions.js";
19
+ // ── Signature verification ──────────────────────────────────────────────────
20
+ function hexToU8(hex) {
21
+ const bytes = new Uint8Array(hex.length / 2);
22
+ for (let i = 0; i < hex.length; i += 2)
23
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
24
+ return bytes;
25
+ }
26
+ /**
27
+ * Verify an Ed25519 signature against the transaction hash.
28
+ * Works for both standard Ed25519 and BIP32-Ed25519 signatures
29
+ * (both produce valid Ed25519 verify-able sigs).
30
+ */
31
+ function verifyEd25519Sig(hashBase64Url, sigHex, pubKeyHex) {
32
+ try {
33
+ // Decode base64url hash → raw bytes
34
+ const b64 = hashBase64Url.replace(/-/g, "+").replace(/_/g, "/");
35
+ const bin = atob(b64);
36
+ const hashBytes = new Uint8Array(bin.length);
37
+ for (let i = 0; i < bin.length; i++)
38
+ hashBytes[i] = bin.charCodeAt(i);
39
+ return ed25519.verify(hexToU8(sigHex), hashBytes, hexToU8(pubKeyHex));
40
+ }
41
+ catch (error) {
42
+ getLogger().error("Error in verifyEd25519Sig:", error);
43
+ return false;
44
+ }
45
+ }
46
+ // ── Balance ─────────────────────────────────────────────────────────────────
47
+ export async function getUrStoaBalance(account) {
48
+ try {
49
+ const pactCode = `(try 0.0 (coin.UR_UR|Balance "${account}"))`;
50
+ const response = await pactRead(pactCode, { tier: "T5" });
51
+ if (response?.result?.status === "success") {
52
+ const data = response.result.data;
53
+ if (typeof data === "number")
54
+ return Number.isFinite(data) ? data : null;
55
+ if (data?.decimal !== undefined) {
56
+ const n = parseFloat(data.decimal);
57
+ return Number.isFinite(n) ? n : null;
58
+ }
59
+ const n = parseFloat(String(data));
60
+ return Number.isFinite(n) ? n : null;
61
+ }
62
+ return null;
63
+ }
64
+ catch (error) {
65
+ getLogger().error("Error fetching UrStoa balance:", error);
66
+ return null;
67
+ }
68
+ }
69
+ // ── Describe keyset ref ─────────────────────────────────────────────────────
70
+ async function describeKeysetOrNull(ksRef) {
71
+ try {
72
+ const r = await describeKeyset(ksRef);
73
+ if (!Array.isArray(r.keys))
74
+ return null;
75
+ return { keys: r.keys, pred: r.pred ?? "keys-all" };
76
+ }
77
+ catch (err) {
78
+ getLogger().error("Error in describeKeysetOrNull:", err);
79
+ return null;
80
+ }
81
+ }
82
+ // ── Guard ───────────────────────────────────────────────────────────────────
83
+ export async function getUrStoaGuard(account) {
84
+ const empty = { exists: false, isKeyset: false, keys: [], pred: "" };
85
+ try {
86
+ const pactCode = `(try false (coin.UR_UR|Guard "${account}"))`;
87
+ const response = await pactRead(pactCode, { tier: "T7" });
88
+ if (response?.result?.status === "success") {
89
+ const data = response.result.data;
90
+ if (data === false || data === "false")
91
+ return empty;
92
+ if (data && typeof data === "object") {
93
+ // Case 1: Direct keyset — keys + pred directly on the guard object
94
+ if (Array.isArray(data.keys) && data.keys.length > 0) {
95
+ return {
96
+ exists: true,
97
+ guard: data,
98
+ isKeyset: true,
99
+ keys: data.keys,
100
+ pred: data.pred ?? "keys-all",
101
+ };
102
+ }
103
+ // Case 2: Keyset reference — unpack via (describe-keyset <ref>)
104
+ if (data.keysetref && typeof data.keysetref === "string") {
105
+ const resolved = await describeKeysetOrNull(data.keysetref);
106
+ if (resolved) {
107
+ return {
108
+ exists: true,
109
+ guard: data,
110
+ isKeyset: true,
111
+ keys: resolved.keys,
112
+ pred: resolved.pred,
113
+ };
114
+ }
115
+ }
116
+ // Also handle keysetref nested in a ks-name field
117
+ const ksName = data["ks-name"] ?? data.keysetref ?? data["keysetref-name"];
118
+ if (ksName && typeof ksName === "string" && !data.keys?.length) {
119
+ const resolved = await describeKeysetOrNull(ksName);
120
+ if (resolved) {
121
+ return {
122
+ exists: true,
123
+ guard: data,
124
+ isKeyset: true,
125
+ keys: resolved.keys,
126
+ pred: resolved.pred,
127
+ };
128
+ }
129
+ }
130
+ // Case 3: Any other guard type — exists but not a usable keyset
131
+ return {
132
+ exists: true,
133
+ guard: data,
134
+ isKeyset: false,
135
+ keys: [],
136
+ pred: "",
137
+ };
138
+ }
139
+ return empty;
140
+ }
141
+ return null;
142
+ }
143
+ catch (error) {
144
+ getLogger().error("Error fetching UrStoa guard:", error);
145
+ return null;
146
+ }
147
+ }
148
+ export async function executeNativeUrStoaTransfer(params) {
149
+ const { senderAddress, receiverAddress, amount, paymentKeyAddress, paymentKeypair, senderGuardKeys, isTransferFamily, receiverExists, receiverKeyset, } = params;
150
+ // v3.2.1: replaced `parseFloat(amount).toFixed(3)` with the validated
151
+ // formatter to close audit finding F-BUG-003. The old `.toFixed(3)` form
152
+ // silently rounded fourth-decimal precision (e.g. user typed "1.9999",
153
+ // transaction sent "2.000" — chain-side accepted the inflated value while
154
+ // the user's account was debited the wrong amount). The new formatter
155
+ // preserves the user's full input precision (truncates at 24 decimals
156
+ // by default rather than rounding at 3) and throws synchronously on
157
+ // malformed input.
158
+ const amountStr = formatDecimalForPact(amount);
159
+ const isAnew = !receiverExists;
160
+ // Select pact code
161
+ let pactCode;
162
+ if (receiverExists && isTransferFamily) {
163
+ pactCode = `(coin.C_UR|Transfer "${senderAddress}" "${receiverAddress}" ${amountStr})`;
164
+ }
165
+ else if (receiverExists && !isTransferFamily) {
166
+ pactCode = `(coin.C_UR|Transmit "${senderAddress}" "${receiverAddress}" ${amountStr})`;
167
+ }
168
+ else if (!receiverExists && isTransferFamily) {
169
+ pactCode = `(coin.C_UR|TransferAnew "${senderAddress}" "${receiverAddress}" (read-keyset "ks") ${amountStr})`;
170
+ }
171
+ else {
172
+ pactCode = `(coin.C_UR|TransmitAnew "${senderAddress}" "${receiverAddress}" (read-keyset "ks") ${amountStr})`;
173
+ }
174
+ const buildTransaction = (gasLimitOverride) => {
175
+ let builder = Pact.builder
176
+ .execution(pactCode)
177
+ .setMeta({
178
+ senderAccount: STOA_AUTONOMIC_OURONETGASSTATION,
179
+ creationTime: safeCreationTime(),
180
+ chainId: KADENA_CHAIN_ID,
181
+ gasLimit: gasLimitOverride ?? 2000000,
182
+ })
183
+ .setNetworkId(KADENA_NETWORK);
184
+ // Add keyset data for Anew variants
185
+ if (isAnew && receiverKeyset) {
186
+ builder = builder.addData("ks", { keys: receiverKeyset.keys, pred: receiverKeyset.pred });
187
+ }
188
+ // Payment key signer — GAS_PAYER ALWAYS uses ("", 0, 0.0) as args
189
+ if (isTransferFamily) {
190
+ builder = builder.addSigner(paymentKeypair.publicKey, (w) => [
191
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, "", { int: 0 }, { decimal: "0.0" }),
192
+ w(`coin.UR|TRANSFER`, senderAddress, receiverAddress, { decimal: amountStr }),
193
+ ]);
194
+ }
195
+ else {
196
+ builder = builder.addSigner(paymentKeypair.publicKey, (w) => [
197
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, "", { int: 0 }, { decimal: "0.0" }),
198
+ ]);
199
+ }
200
+ // Pure guard signers — only add 1 key (minimum needed for keys-any/keys-all)
201
+ // For Transmit: payment key + 1 guard key = 2 signers total (matching on-chain convention)
202
+ const addedPubs = new Set([paymentKeypair.publicKey]);
203
+ for (const k of senderGuardKeys) {
204
+ if (!addedPubs.has(k.publicKey)) {
205
+ builder = builder.addSigner(k.publicKey);
206
+ addedPubs.add(k.publicKey);
207
+ break; // Only add 1 pure guard key signer
208
+ }
209
+ }
210
+ return builder.createTransaction();
211
+ };
212
+ const { dirtyRead, submit } = getFailoverClient(KADENA_CHAIN_ID);
213
+ // 1. Simulate
214
+ const simTx = buildTransaction();
215
+ const simulation = await dirtyRead(simTx);
216
+ if (simulation.result.status === "failure") {
217
+ const error = createSimulationError("Native UrStoa Transfer", simulation.result, `Sender: ${senderAddress} | Receiver: ${receiverAddress} | Amount: ${amountStr}`);
218
+ logDetailedError(error);
219
+ throw error;
220
+ }
221
+ // 2. Adaptive gas
222
+ const gasLimit = simulation.gas ? calculateAutoGasLimit(simulation.gas) : 2000000;
223
+ // 3. Collect signers — payment key + 1 guard key only (matching on-chain convention)
224
+ const allSigners = [paymentKeypair];
225
+ const seenPubs = new Set([paymentKeypair.publicKey]);
226
+ for (const k of senderGuardKeys) {
227
+ if (!seenPubs.has(k.publicKey)) {
228
+ allSigners.push(k);
229
+ seenPubs.add(k.publicKey);
230
+ break; // Only 1 guard key needed
231
+ }
232
+ }
233
+ // 4. Sign, verify, and filter invalid signatures
234
+ // Some guard keys may produce invalid signatures (e.g. signing algorithm
235
+ // mismatch between seed types). We verify each signature after signing
236
+ // and rebuild without the bad keys if the guard predicate still holds.
237
+ let finalTx = buildTransaction(gasLimit);
238
+ let signed = await universalSignTransaction(finalTx, allSigners.map((s) => fromKeypair(s)));
239
+ // Verify all signatures
240
+ const cmd = JSON.parse(signed.cmd);
241
+ const signerPubs = cmd.signers.map((s) => s.pubKey);
242
+ const invalidIdxs = [];
243
+ for (let i = 0; i < signed.sigs.length; i++) {
244
+ const sig = signed.sigs[i];
245
+ if (sig?.sig) {
246
+ const valid = verifyEd25519Sig(signed.hash, sig.sig, signerPubs[i]);
247
+ if (!valid) {
248
+ getLogger().warn(`[UrStoa] Invalid signature at position ${i} for key ${signerPubs[i]} — will rebuild without it.`);
249
+ invalidIdxs.push(i);
250
+ }
251
+ }
252
+ }
253
+ // If any guard key signatures are invalid, rebuild without those keys
254
+ if (invalidIdxs.length > 0) {
255
+ const invalidPubs = new Set(invalidIdxs.map((i) => signerPubs[i]));
256
+ // Never remove the payment key (position 0)
257
+ if (invalidPubs.has(paymentKeypair.publicKey)) {
258
+ throw new Error(`Payment key signature is invalid (key: ${paymentKeypair.publicKey.slice(0, 16)}…). Cannot proceed.`);
259
+ }
260
+ // Filter out guard keys with invalid signatures
261
+ const validGuardKeys = senderGuardKeys.filter((k) => !invalidPubs.has(k.publicKey));
262
+ if (validGuardKeys.length === 0) {
263
+ throw new Error("All guard key signatures are invalid. Check that the keys in Codex match the sender's guard " +
264
+ "(possible signing algorithm mismatch between seed types).");
265
+ }
266
+ // v3.3.0 (closes part of F-LOGGER-SEAM-001): routed through
267
+ // getLogger().warn — signature pruning is an unusual operational
268
+ // event (guard keys in Codex didn't match the sender's on-chain
269
+ // guard, suggesting algorithm mismatch or stale key state) that a
270
+ // HUB operator running structured logs would want to capture in
271
+ // their incident pipeline. Promoted from info → warn-level for
272
+ // that reason; the seam's `info` channel is reserved for
273
+ // recoverable status events (node-recovery, error-suggestions).
274
+ getLogger().warn(`[UrStoa] Rebuilding transaction with ${validGuardKeys.length} valid guard key(s) ` +
275
+ `(removed ${invalidIdxs.length} invalid).`);
276
+ // Rebuild with only valid guard keys
277
+ params._filteredGuardKeys = validGuardKeys;
278
+ const rebuildTransaction = (gasLimitOverride) => {
279
+ let builder = Pact.builder
280
+ .execution(pactCode)
281
+ .setMeta({
282
+ senderAccount: STOA_AUTONOMIC_OURONETGASSTATION,
283
+ creationTime: safeCreationTime(),
284
+ chainId: KADENA_CHAIN_ID,
285
+ gasLimit: gasLimitOverride ?? 2000000,
286
+ })
287
+ .setNetworkId(KADENA_NETWORK);
288
+ if (isAnew && receiverKeyset) {
289
+ builder = builder.addData("ks", { keys: receiverKeyset.keys, pred: receiverKeyset.pred });
290
+ }
291
+ if (isTransferFamily) {
292
+ builder = builder.addSigner(paymentKeypair.publicKey, (w) => [
293
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, paymentKeyAddress, { int: 0 }, { decimal: "0.0" }),
294
+ w(`coin.UR|TRANSFER`, senderAddress, receiverAddress, { decimal: amountStr }),
295
+ ]);
296
+ }
297
+ else {
298
+ builder = builder.addSigner(paymentKeypair.publicKey, (w) => [
299
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, paymentKeyAddress, { int: 0 }, { decimal: "0.0" }),
300
+ ]);
301
+ }
302
+ const addedPubs = new Set([paymentKeypair.publicKey]);
303
+ for (const k of validGuardKeys) {
304
+ if (!addedPubs.has(k.publicKey)) {
305
+ builder = builder.addSigner(k.publicKey);
306
+ addedPubs.add(k.publicKey);
307
+ }
308
+ }
309
+ return builder.createTransaction();
310
+ };
311
+ finalTx = rebuildTransaction(gasLimit);
312
+ const validSigners = [paymentKeypair];
313
+ const seen2 = new Set([paymentKeypair.publicKey]);
314
+ for (const k of validGuardKeys) {
315
+ if (!seen2.has(k.publicKey)) {
316
+ validSigners.push(k);
317
+ seen2.add(k.publicKey);
318
+ }
319
+ }
320
+ signed = await universalSignTransaction(finalTx, validSigners.map((s) => fromKeypair(s)));
321
+ // Verify rebuilt transaction signatures
322
+ const cmd2 = JSON.parse(signed.cmd);
323
+ const pubs2 = cmd2.signers.map((s) => s.pubKey);
324
+ for (let i = 0; i < signed.sigs.length; i++) {
325
+ const sig = signed.sigs[i];
326
+ if (sig?.sig && !verifyEd25519Sig(signed.hash, sig.sig, pubs2[i])) {
327
+ throw new Error(`Signature still invalid after rebuild at position ${i} (key: ${pubs2[i].slice(0, 16)}…). ` +
328
+ `Possible key derivation mismatch in Codex.`);
329
+ }
330
+ }
331
+ }
332
+ // 5. Submit
333
+ return await submit(signed);
334
+ }
335
+ export async function executeStakeUrStoa(params) {
336
+ const { paymentKeyAddress, amount, gasStationKey } = params;
337
+ // v3.2.1: validate `amount` ONCE outside the closure so a malformed input
338
+ // throws before any chain interaction, AND the formatted-decimal string
339
+ // can be reused both inside the Pact-code interpolation (was raw
340
+ // `${amount}` — closes F-SEC-001 Pact-code injection vector) and for the
341
+ // capability arg (was `String(numAmount)` which had the float-precision
342
+ // gap). The validated string is the single source of truth for both
343
+ // sites, so the cap-arg and the executed code are guaranteed to agree.
344
+ const validatedAmount = formatDecimalForPact(amount);
345
+ const buildTransaction = (gasLimitOverride) => Pact.builder
346
+ .execution(`(coin.C_URV|Stake "${paymentKeyAddress}" ${validatedAmount})`)
347
+ .setMeta({
348
+ senderAccount: STOA_AUTONOMIC_OURONETGASSTATION,
349
+ creationTime: safeCreationTime(),
350
+ chainId: KADENA_CHAIN_ID,
351
+ gasLimit: gasLimitOverride ?? 2000000,
352
+ })
353
+ .setNetworkId(KADENA_NETWORK)
354
+ .addSigner(gasStationKey.publicKey, (w) => [
355
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, "", { int: 0 }, { decimal: "0.0" }),
356
+ w("coin.URV|STAKE", paymentKeyAddress, { decimal: validatedAmount }),
357
+ ])
358
+ .createTransaction();
359
+ const { dirtyRead, submit } = getFailoverClient(KADENA_CHAIN_ID);
360
+ const simTx = buildTransaction();
361
+ const simulation = await dirtyRead(simTx);
362
+ if (simulation.result.status === "failure") {
363
+ const error = createSimulationError("Stake UrStoa", simulation.result, `Account: ${paymentKeyAddress} | Amount: ${validatedAmount}`);
364
+ logDetailedError(error);
365
+ throw error;
366
+ }
367
+ const gasLimit = simulation.gas ? calculateAutoGasLimit(simulation.gas) : 2000000;
368
+ const finalTx = buildTransaction(gasLimit);
369
+ const signed = await universalSignTransaction(finalTx, [fromKeypair(gasStationKey)]);
370
+ return await submit(signed);
371
+ }
372
+ export async function executeUnstakeUrStoa(params) {
373
+ const { paymentKeyAddress, amount, gasStationKey } = params;
374
+ // v3.2.1: same single-source-of-truth pattern as `executeStakeUrStoa`.
375
+ // Validates `amount` once before building, reuses for both pact-code
376
+ // interpolation (closes F-SEC-001) and capability arg (closes the
377
+ // float-precision gap in `String(numAmount)`).
378
+ const validatedAmount = formatDecimalForPact(amount);
379
+ const buildTransaction = (gasLimitOverride) => Pact.builder
380
+ .execution(`(coin.C_URV|Unstake "${paymentKeyAddress}" ${validatedAmount})`)
381
+ .setMeta({
382
+ senderAccount: STOA_AUTONOMIC_OURONETGASSTATION,
383
+ creationTime: safeCreationTime(),
384
+ chainId: KADENA_CHAIN_ID,
385
+ gasLimit: gasLimitOverride ?? 2000000,
386
+ })
387
+ .setNetworkId(KADENA_NETWORK)
388
+ .addSigner(gasStationKey.publicKey, (w) => [
389
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, "", { int: 0 }, { decimal: "0.0" }),
390
+ w("coin.URV|UNSTAKE", paymentKeyAddress, { decimal: validatedAmount }),
391
+ ])
392
+ .createTransaction();
393
+ const { dirtyRead, submit } = getFailoverClient(KADENA_CHAIN_ID);
394
+ const simTx = buildTransaction();
395
+ const simulation = await dirtyRead(simTx);
396
+ if (simulation.result.status === "failure") {
397
+ const error = createSimulationError("Unstake UrStoa", simulation.result, `Account: ${paymentKeyAddress} | Amount: ${validatedAmount}`);
398
+ logDetailedError(error);
399
+ throw error;
400
+ }
401
+ const gasLimit = simulation.gas ? calculateAutoGasLimit(simulation.gas) : 2000000;
402
+ const finalTx = buildTransaction(gasLimit);
403
+ const signed = await universalSignTransaction(finalTx, [fromKeypair(gasStationKey)]);
404
+ return await submit(signed);
405
+ }
406
+ // ─── Check if account exists in coin table ───────────────────────────────────
407
+ /**
408
+ * Polls for urStoa account existence in the coin table using:
409
+ * (if (= (typeof (try false (coin.UR_Balance account))) "bool") false true)
410
+ *
411
+ * Returns: true = exists, null = doesn't exist OR RPC error (both collapse
412
+ * to a non-existence/uncertainty signal — callers should treat null as
413
+ * "do not proceed assuming the account exists").
414
+ *
415
+ * @see {@link checkCoinAccountExists} in `ouroFunctions.ts` — both functions
416
+ * share the nullable-boolean return shape (`Promise<boolean | null>`) but
417
+ * differ in account scope: this one probes urStoa account existence via
418
+ * `coin.UR_Balance`; the sibling probes a generic coin account via
419
+ * `coin.get-balance`. Renaming to disambiguate the two identifiers is
420
+ * deferred to a separate breaking change to keep the consumer migration
421
+ * surface tight.
422
+ */
423
+ export async function checkCoinAccountExists(account) {
424
+ const pactCode = `(if (= (typeof (try false (coin.UR_Balance "${account}"))) "bool") false true)`;
425
+ try {
426
+ const r = await pactRead(pactCode, { tier: "T5" });
427
+ if (r?.result?.status === "success")
428
+ return r.result.data === true ? true : null;
429
+ return null;
430
+ }
431
+ catch (error) {
432
+ getLogger().error("Error in checkCoinAccountExists (urStoa):", error);
433
+ return null;
434
+ }
435
+ }
436
+ export async function executeCollectUrStoa(params) {
437
+ const { paymentKeyAddress, gasStationKey, accountExists = true } = params;
438
+ // Extract pubkey from k: address for keyset
439
+ const pubkey = paymentKeyAddress.startsWith("k:") ? paymentKeyAddress.slice(2) : paymentKeyAddress;
440
+ const buildTransaction = (gasLimitOverride) => {
441
+ const code = accountExists
442
+ ? `(coin.C_URV|Collect "${paymentKeyAddress}")`
443
+ : `(coin.C_CreateAccount "${paymentKeyAddress}" (read-keyset "ks"))\n(coin.C_URV|Collect "${paymentKeyAddress}")`;
444
+ let builder = Pact.builder
445
+ .execution(code)
446
+ .setMeta({
447
+ senderAccount: STOA_AUTONOMIC_OURONETGASSTATION,
448
+ creationTime: safeCreationTime(),
449
+ chainId: KADENA_CHAIN_ID,
450
+ gasLimit: gasLimitOverride ?? 2000000,
451
+ })
452
+ .setNetworkId(KADENA_NETWORK)
453
+ .addSigner(gasStationKey.publicKey, (w) => [
454
+ w(`${KADENA_NAMESPACE}.DALOS.GAS_PAYER`, "", { int: 0 }, { decimal: "0.0" }),
455
+ w("coin.URV|COLLECT", paymentKeyAddress),
456
+ ]);
457
+ // Add keyset data for account creation
458
+ if (!accountExists) {
459
+ builder = builder.addData("ks", { keys: [pubkey], pred: "keys-all" });
460
+ }
461
+ return builder.createTransaction();
462
+ };
463
+ const { dirtyRead, submit } = getFailoverClient(KADENA_CHAIN_ID);
464
+ const simTx = buildTransaction();
465
+ const simulation = await dirtyRead(simTx);
466
+ if (simulation.result.status === "failure") {
467
+ const error = createSimulationError("Collect UrStoa", simulation.result, `Account: ${paymentKeyAddress}`);
468
+ logDetailedError(error);
469
+ throw error;
470
+ }
471
+ const gasLimit = simulation.gas ? calculateAutoGasLimit(simulation.gas) : 2000000;
472
+ const finalTx = buildTransaction(gasLimit);
473
+ const signed = await universalSignTransaction(finalTx, [fromKeypair(gasStationKey)]);
474
+ return await submit(signed);
475
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * wrapFunctions.ts
3
+ * On-chain interactions for C_WrapStoa (ouronet-ns.TS01-C2.LQD)
4
+ */
5
+ import type { IKadenaKeypair } from "@stoachain/stoa-core/signing";
6
+ /**
7
+ * (ouronet-ns.INFO-ONE.LIQUID|INFO_WrapStoa <patron:string> <wrapper:string> <amount:decimal>)
8
+ * Returns cost info: ignis, kadena, pre-text, post-text, etc.
9
+ */
10
+ export declare function getWrapStoaInfo(patron: string, wrapper: string, amount: string): Promise<any | null>;
11
+ /**
12
+ * (ouronet-ns.DALOS.UR_AccountKadena <wrapper>) → k: address (payment key)
13
+ */
14
+ export declare function getWrapperPaymentKey(wrapper: string): Promise<string | null>;
15
+ /**
16
+ * (coin.get-balance <paymentKeyAddress>) → decimal balance
17
+ */
18
+ export declare function getPaymentKeyBalance(paymentKeyAddress: string): Promise<number | null>;
19
+ export interface WrapStoaParams {
20
+ patronAddress: string;
21
+ wrapperAddress: string;
22
+ amount: string;
23
+ numAmount: number;
24
+ paymentKeyAddress: string;
25
+ gasStationKey: IKadenaKeypair;
26
+ paymentSignerKey: IKadenaKeypair;
27
+ patronGuardKeys: IKadenaKeypair[];
28
+ wrapperGuardKeys: IKadenaKeypair[];
29
+ }
30
+ export declare function executeWrapStoa(params: WrapStoaParams): Promise<any>;
31
+ /**
32
+ * (ouronet-ns.INFO-ONE.LIQUID|INFO_WrapUrStoa <patron:string> <wrapper:string> <amount:decimal>)
33
+ * Returns cost info: ignis, kadena, pre-text, post-text, etc.
34
+ */
35
+ export declare function getWrapUrStoaInfo(patron: string, wrapper: string, amount: string): Promise<any | null>;
36
+ export interface WrapUrStoaParams {
37
+ patronAddress: string;
38
+ wrapperAddress: string;
39
+ amount: string;
40
+ numAmount: number;
41
+ paymentKeyAddress: string;
42
+ gasStationKey: IKadenaKeypair;
43
+ patronGuardKeys: IKadenaKeypair[];
44
+ wrapperGuardKeys: IKadenaKeypair[];
45
+ }
46
+ export declare function executeWrapUrStoa(params: WrapUrStoaParams): Promise<any>;