@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.
- package/CHANGELOG.md +3043 -0
- package/README.md +1940 -0
- package/dist/codex/codec.d.ts +77 -0
- package/dist/codex/codec.js +123 -0
- package/dist/codex/errors.d.ts +14 -0
- package/dist/codex/errors.js +20 -0
- package/dist/codex/index.d.ts +27 -0
- package/dist/codex/index.js +25 -0
- package/dist/codex/seedTypeMigration.d.ts +35 -0
- package/dist/codex/seedTypeMigration.js +45 -0
- package/dist/codex/types.d.ts +100 -0
- package/dist/codex/types.js +19 -0
- package/dist/constants/index.d.ts +2 -0
- package/dist/constants/index.js +3 -0
- package/dist/constants/ouronet.d.ts +33 -0
- package/dist/constants/ouronet.js +59 -0
- package/dist/constants/tokenIds.d.ts +13 -0
- package/dist/constants/tokenIds.js +21 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1 -0
- package/dist/interactions/activateFunctions.d.ts +67 -0
- package/dist/interactions/activateFunctions.js +154 -0
- package/dist/interactions/addLiquidityFunctions.d.ts +140 -0
- package/dist/interactions/addLiquidityFunctions.js +485 -0
- package/dist/interactions/coilFunctions.d.ts +73 -0
- package/dist/interactions/coilFunctions.js +213 -0
- package/dist/interactions/crossChainFunctions.d.ts +211 -0
- package/dist/interactions/crossChainFunctions.js +463 -0
- package/dist/interactions/dexAcquisitionPoolFunctions.d.ts +18 -0
- package/dist/interactions/dexAcquisitionPoolFunctions.js +18 -0
- package/dist/interactions/dexCollectablesFunctions.d.ts +28 -0
- package/dist/interactions/dexCollectablesFunctions.js +227 -0
- package/dist/interactions/dexFunctions.d.ts +23 -0
- package/dist/interactions/dexFunctions.js +23 -0
- package/dist/interactions/dexOrtoFungibleFunctions.d.ts +33 -0
- package/dist/interactions/dexOrtoFungibleFunctions.js +171 -0
- package/dist/interactions/dexParseFunctions.d.ts +150 -0
- package/dist/interactions/dexParseFunctions.js +316 -0
- package/dist/interactions/dexSwapPairAdminFunctions.d.ts +75 -0
- package/dist/interactions/dexSwapPairAdminFunctions.js +255 -0
- package/dist/interactions/dexSwapPairCalcFunctions.d.ts +55 -0
- package/dist/interactions/dexSwapPairCalcFunctions.js +218 -0
- package/dist/interactions/dexSwapPairDashboardFunctions.d.ts +47 -0
- package/dist/interactions/dexSwapPairDashboardFunctions.js +179 -0
- package/dist/interactions/dexSwapPairExecuteFunctions.d.ts +40 -0
- package/dist/interactions/dexSwapPairExecuteFunctions.js +301 -0
- package/dist/interactions/dexSwapPairSmartSwapFunctions.d.ts +25 -0
- package/dist/interactions/dexSwapPairSmartSwapFunctions.js +166 -0
- package/dist/interactions/dexTrueFungibleFunctions.d.ts +52 -0
- package/dist/interactions/dexTrueFungibleFunctions.js +220 -0
- package/dist/interactions/dexTypes.d.ts +465 -0
- package/dist/interactions/dexTypes.js +6 -0
- package/dist/interactions/errors.d.ts +12 -0
- package/dist/interactions/errors.js +13 -0
- package/dist/interactions/guardFunctions.d.ts +33 -0
- package/dist/interactions/guardFunctions.js +110 -0
- package/dist/interactions/index.d.ts +1 -0
- package/dist/interactions/index.js +20 -0
- package/dist/interactions/infoOneFunctions.d.ts +133 -0
- package/dist/interactions/infoOneFunctions.js +566 -0
- package/dist/interactions/kadenaFunctions.d.ts +11 -0
- package/dist/interactions/kadenaFunctions.js +40 -0
- package/dist/interactions/kpayFunctions.d.ts +45 -0
- package/dist/interactions/kpayFunctions.js +191 -0
- package/dist/interactions/ouroAccountFunctions.d.ts +64 -0
- package/dist/interactions/ouroAccountFunctions.js +229 -0
- package/dist/interactions/ouroBalanceFunctions.d.ts +39 -0
- package/dist/interactions/ouroBalanceFunctions.js +100 -0
- package/dist/interactions/ouroCoilFunctions.d.ts +33 -0
- package/dist/interactions/ouroCoilFunctions.js +190 -0
- package/dist/interactions/ouroFunctions.d.ts +23 -0
- package/dist/interactions/ouroFunctions.js +23 -0
- package/dist/interactions/ouroMovieBoosterFunctions.d.ts +18 -0
- package/dist/interactions/ouroMovieBoosterFunctions.js +203 -0
- package/dist/interactions/ouroPriceFunctions.d.ts +38 -0
- package/dist/interactions/ouroPriceFunctions.js +96 -0
- package/dist/interactions/ouroPrimordialsFunctions.d.ts +10 -0
- package/dist/interactions/ouroPrimordialsFunctions.js +480 -0
- package/dist/interactions/ouroRotateFunctions.d.ts +22 -0
- package/dist/interactions/ouroRotateFunctions.js +114 -0
- package/dist/interactions/ouroSubCompressFunctions.d.ts +23 -0
- package/dist/interactions/ouroSubCompressFunctions.js +191 -0
- package/dist/interactions/ouroTransferFunctions.d.ts +14 -0
- package/dist/interactions/ouroTransferFunctions.js +87 -0
- package/dist/interactions/ouroTypes.d.ts +113 -0
- package/dist/interactions/ouroTypes.js +6 -0
- package/dist/interactions/ouroUrStoaFunctions.d.ts +16 -0
- package/dist/interactions/ouroUrStoaFunctions.js +121 -0
- package/dist/interactions/ouroWrapFunctions.d.ts +22 -0
- package/dist/interactions/ouroWrapFunctions.js +234 -0
- package/dist/interactions/pensionFunctions.d.ts +12 -0
- package/dist/interactions/pensionFunctions.js +127 -0
- package/dist/interactions/urStoaFunctions.d.ts +104 -0
- package/dist/interactions/urStoaFunctions.js +475 -0
- package/dist/interactions/wrapFunctions.d.ts +46 -0
- package/dist/interactions/wrapFunctions.js +221 -0
- package/dist/pact/cfmBuilders.d.ts +807 -0
- package/dist/pact/cfmBuilders.js +812 -0
- package/dist/pact/index.d.ts +1 -0
- package/dist/pact/index.js +10 -0
- 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>;
|