@dynamic-labs/aleo 4.79.2 → 4.80.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 +9 -0
- package/package.cjs +1 -1
- package/package.js +1 -1
- package/package.json +10 -6
- package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.cjs +798 -0
- package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.d.ts +409 -0
- package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.js +794 -0
- package/src/connectors/DynamicWaasAleoConnector/index.cjs +13 -0
- package/src/connectors/DynamicWaasAleoConnector/index.d.ts +3 -0
- package/src/connectors/DynamicWaasAleoConnector/index.js +9 -0
- package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.cjs +216 -0
- package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.d.ts +116 -0
- package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.js +211 -0
- package/src/connectors/WaasAleoWalletConnector/index.d.ts +1 -0
- package/src/index.cjs +15 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +7 -0
- package/src/utils/AleoUiTransaction/AleoUiTransaction.cjs +354 -0
- package/src/utils/AleoUiTransaction/AleoUiTransaction.d.ts +130 -0
- package/src/utils/AleoUiTransaction/AleoUiTransaction.js +350 -0
- package/src/utils/AleoUiTransaction/index.d.ts +2 -0
- package/src/utils/aleoSendableTokens/aleoSendableTokens.cjs +185 -0
- package/src/utils/aleoSendableTokens/aleoSendableTokens.d.ts +78 -0
- package/src/utils/aleoSendableTokens/aleoSendableTokens.js +175 -0
- package/src/utils/aleoSendableTokens/index.d.ts +2 -0
- package/src/utils/aleoShieldableTokens/aleoShieldableTokens.cjs +119 -0
- package/src/utils/aleoShieldableTokens/aleoShieldableTokens.d.ts +45 -0
- package/src/utils/aleoShieldableTokens/aleoShieldableTokens.js +114 -0
- package/src/utils/aleoShieldableTokens/index.d.ts +2 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { __awaiter } from '../../../_virtual/_tslib.js';
|
|
3
|
+
import { formatNumberText } from '@dynamic-labs/utils';
|
|
4
|
+
import { recordMatchesSendableToken, extractRecordAtomicAmount, getAleoSendableTokensForNetwork, ALEO_CREDITS_PROGRAM, ALEO_CREDITS_DECIMALS } from '../aleoSendableTokens/aleoSendableTokens.js';
|
|
5
|
+
|
|
6
|
+
// `10n ** decimals` via string construction. Equivalent to `BigInt(10) **
|
|
7
|
+
// BigInt(decimals)` but avoids the TS2791 error in environments whose
|
|
8
|
+
// `target` predates es2016 (which is where rollup compiles this package).
|
|
9
|
+
const pow10 = (decimals) => BigInt(`1${'0'.repeat(decimals)}`);
|
|
10
|
+
// Aleo addresses are 63-character strings: `aleo1` prefix + 58 lowercase
|
|
11
|
+
// alphanumeric characters. We deliberately don't enforce the bech32m
|
|
12
|
+
// alphabet — Aleo's encoding admits a wider character set than standard
|
|
13
|
+
// bech32m (e.g. `o` appears in real testnet addresses). Format-level
|
|
14
|
+
// validation only — the on-chain transition rejects bad checksums with a
|
|
15
|
+
// clear error, so we don't pull the Provable WASM into the widget bundle
|
|
16
|
+
// for client-side checksum verification.
|
|
17
|
+
const ALEO_ADDRESS_REGEX = /^aleo1[a-z0-9]{58}$/;
|
|
18
|
+
/**
|
|
19
|
+
* Aleo `IUITransaction` for the widget's Send flow.
|
|
20
|
+
*
|
|
21
|
+
* Both supported modes spend FROM a private record (so the wallet's
|
|
22
|
+
* available balance is always the shielded record sum); they differ
|
|
23
|
+
* only in what the recipient receives:
|
|
24
|
+
*
|
|
25
|
+
* - **Individual** = `<program>/transfer_private` — recipient also gets
|
|
26
|
+
* a private record. The most common Aleo flow.
|
|
27
|
+
* - **Exchange** = `<program>/transfer_private_to_public` —
|
|
28
|
+
* recipient's *public* balance is incremented. Useful for off-ramps,
|
|
29
|
+
* exchange deposits, etc.
|
|
30
|
+
*
|
|
31
|
+
* The submit path branches on the **selected token's program kind**:
|
|
32
|
+
*
|
|
33
|
+
* - `credits` → `credits.aleo` transitions, 3 inputs `[record, recipient, amount]`.
|
|
34
|
+
* - `stablecoin` → `<stablecoin>.aleo` transitions, 3 inputs
|
|
35
|
+
* `[recipient, amount, record]`. The iframe appends a
|
|
36
|
+
* Sealance freeze-list exclusion proof (and its type)
|
|
37
|
+
* automatically — see `proveTransaction` in the iframe's
|
|
38
|
+
* Aleo client.
|
|
39
|
+
* - `arc21` → `token_registry.aleo` transitions, 3 inputs
|
|
40
|
+
* `[recipient, amount, record]`. The token_id is
|
|
41
|
+
* encoded inside the record's plaintext, so the
|
|
42
|
+
* transition signature does NOT include it as a
|
|
43
|
+
* separate argument — only the record carries it.
|
|
44
|
+
*
|
|
45
|
+
* In all kinds, the form auto-picks the smallest single record (filtered
|
|
46
|
+
* to the selected token) that covers the amount. If no single record
|
|
47
|
+
* fits, the form surfaces a merge CTA backed by `joinAllRecordsForToken`.
|
|
48
|
+
*/
|
|
49
|
+
class AleoUiTransaction {
|
|
50
|
+
constructor({ from, networkId, listOwnedRecords, joinAllRecordsForToken, onSubmit, }) {
|
|
51
|
+
var _a;
|
|
52
|
+
this.chain = 'ALEO';
|
|
53
|
+
this.data = undefined;
|
|
54
|
+
this.fee = { gas: undefined };
|
|
55
|
+
this.transactionModes = [
|
|
56
|
+
{
|
|
57
|
+
icon: 'individual',
|
|
58
|
+
id: 'individual',
|
|
59
|
+
label: 'Individual',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
icon: 'exchange',
|
|
63
|
+
id: 'exchange',
|
|
64
|
+
label: 'Exchange',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
// No default — the user must explicitly pick Individual or Exchange
|
|
68
|
+
// before submitting. Both modes spend from the same shielded balance,
|
|
69
|
+
// so the rest of the form stays interactive without a selection; we
|
|
70
|
+
// just gate the Preview/Submit button. Pre-selecting felt
|
|
71
|
+
// presumptuous given Individual vs. Exchange has real semantic
|
|
72
|
+
// differences (recipient gets a private record vs. public balance).
|
|
73
|
+
this.selectedTransactionMode = undefined;
|
|
74
|
+
this.from = from;
|
|
75
|
+
this.networkId = networkId;
|
|
76
|
+
this.listOwnedRecords = listOwnedRecords;
|
|
77
|
+
this.joinAllRecordsForToken = joinAllRecordsForToken;
|
|
78
|
+
this.onSubmitFn = onSubmit;
|
|
79
|
+
this.selectedToken = (_a = this.tokensForNetwork()[0]) !== null && _a !== void 0 ? _a : this.creditsFallback();
|
|
80
|
+
}
|
|
81
|
+
setTransactionMode(modeId) {
|
|
82
|
+
if (modeId !== 'exchange' && modeId !== 'individual')
|
|
83
|
+
return;
|
|
84
|
+
this.selectedTransactionMode = modeId;
|
|
85
|
+
}
|
|
86
|
+
setSelectedToken(contractAddress) {
|
|
87
|
+
const match = this.tokensForNetwork().find((t) => t.contractAddress === contractAddress);
|
|
88
|
+
if (match)
|
|
89
|
+
this.selectedToken = match;
|
|
90
|
+
}
|
|
91
|
+
setPendingAmount(amountInput) {
|
|
92
|
+
try {
|
|
93
|
+
this.pendingValue = this.parse(amountInput);
|
|
94
|
+
}
|
|
95
|
+
catch (_a) {
|
|
96
|
+
this.pendingValue = undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Triggered by the form's "Merge records first" button. After this
|
|
101
|
+
* resolves the form re-evaluates `getRecordMergeAction`, so we drop the
|
|
102
|
+
* cached records first to force a fresh `listOwnedRecords` on the
|
|
103
|
+
* next call (the on-chain merge produced new records).
|
|
104
|
+
*/
|
|
105
|
+
mergeRecordsForSelectedToken() {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
yield this.joinAllRecordsForToken(this.selectedToken);
|
|
108
|
+
this.cachedRecords = undefined;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
getRecordMergeAction() {
|
|
112
|
+
// Both modes consume one private record, so the CTA fires for either
|
|
113
|
+
// selection when the user's largest single record (of the selected
|
|
114
|
+
// token) can't cover the amount. (Exchange = transfer_private_to_public
|
|
115
|
+
// also burns a record; the only difference is that the recipient gets
|
|
116
|
+
// public balance instead of a private record.)
|
|
117
|
+
if (this.pendingValue === undefined || this.pendingValue <= BigInt(0)) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
// We can only know "fits / doesn't fit" if we've loaded records at
|
|
121
|
+
// least once. The form refreshes the action on amount blur — by then
|
|
122
|
+
// the iframe has typically been pinged at least once (e.g. when
|
|
123
|
+
// `getBalance` ran), but we also have to handle the cold case where
|
|
124
|
+
// records haven't been fetched yet. Returning `undefined` here is
|
|
125
|
+
// safe: the form will re-check on blur after the user types again,
|
|
126
|
+
// and `listOwnedRecords` will be invoked from the submit path
|
|
127
|
+
// anyway. We choose to never block submit on an unknown — the worst
|
|
128
|
+
// case is the user clicks Preview, sees the underlying error, and
|
|
129
|
+
// clicks merge from there.
|
|
130
|
+
if (!this.cachedRecords)
|
|
131
|
+
return undefined;
|
|
132
|
+
const matching = this.cachedRecords.filter((r) => recordMatchesSendableToken(r, this.selectedToken));
|
|
133
|
+
const largest = matching.reduce((acc, r) => {
|
|
134
|
+
const v = extractRecordAtomicAmount(r, this.selectedToken);
|
|
135
|
+
return v > acc ? v : acc;
|
|
136
|
+
}, BigInt(0));
|
|
137
|
+
if (largest >= this.pendingValue)
|
|
138
|
+
return undefined;
|
|
139
|
+
// No single record fits. The CTA mirrors the language we use in the
|
|
140
|
+
// demo + the merge button we already shipped. Action delegates back
|
|
141
|
+
// to the connector via the closure injected at construction time.
|
|
142
|
+
const { symbol } = this.selectedToken;
|
|
143
|
+
return {
|
|
144
|
+
actionLabel: 'Merge records first',
|
|
145
|
+
message: `No single private record covers this amount. Largest is ${this.format(largest)} ${symbol}; you're sending ${this.format(this.pendingValue)} ${symbol}.`,
|
|
146
|
+
onAction: () => this.mergeRecordsForSelectedToken(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parses a human-readable amount (e.g. `"0.5"`) into atomic units using
|
|
151
|
+
* the *selected token's* decimals (credits = 6, stablecoin = 6, each
|
|
152
|
+
* ARC-21 token has its own — wETH = 18, etc.). BigInt math via integer
|
|
153
|
+
* multiplication keeps precision lossless up to `decimals` places.
|
|
154
|
+
*/
|
|
155
|
+
parse(input) {
|
|
156
|
+
const { decimals } = this.selectedToken;
|
|
157
|
+
const trimmed = input.trim();
|
|
158
|
+
const re = new RegExp(`^(\\d+)(?:\\.(\\d{1,${decimals}}))?$`);
|
|
159
|
+
const match = re.exec(trimmed);
|
|
160
|
+
if (!match) {
|
|
161
|
+
throw new Error(`Invalid amount — expected up to ${decimals} decimal places.`);
|
|
162
|
+
}
|
|
163
|
+
const [, whole, fracRaw] = match;
|
|
164
|
+
const frac = (fracRaw !== null && fracRaw !== void 0 ? fracRaw : '').padEnd(decimals, '0');
|
|
165
|
+
return BigInt(whole) * pow10(decimals) + BigInt(frac);
|
|
166
|
+
}
|
|
167
|
+
// Formats an atomic-units bigint into a display string using the
|
|
168
|
+
// selected token's decimals. BigInt math throughout — Number() coercion
|
|
169
|
+
// on the whole atomic value would lose precision for high-decimal tokens
|
|
170
|
+
// (e.g. wETH at 18 decimals: 9 wETH ≈ 9·10¹⁸ atomic units, well past
|
|
171
|
+
// Number.MAX_SAFE_INTEGER).
|
|
172
|
+
format(value, { precision } = {}) {
|
|
173
|
+
const { decimals } = this.selectedToken;
|
|
174
|
+
if (decimals === 0) {
|
|
175
|
+
return formatNumberText(value.toString(), { precision });
|
|
176
|
+
}
|
|
177
|
+
const negative = value < BigInt(0);
|
|
178
|
+
const abs = negative ? -value : value;
|
|
179
|
+
const divisor = pow10(decimals);
|
|
180
|
+
const wholePart = abs / divisor;
|
|
181
|
+
const fracPart = abs % divisor;
|
|
182
|
+
// Linear-time trailing-zero trim. Avoids `/0+$/` — anchored, bounded
|
|
183
|
+
// input, but the SAST tooling flags any `+`-quantified regex as
|
|
184
|
+
// ReDoS-prone, so we walk back manually.
|
|
185
|
+
const fracPadded = fracPart.toString().padStart(decimals, '0');
|
|
186
|
+
let fracEnd = fracPadded.length;
|
|
187
|
+
while (fracEnd > 0 && fracPadded[fracEnd - 1] === '0')
|
|
188
|
+
fracEnd -= 1;
|
|
189
|
+
const fracString = fracPadded.slice(0, fracEnd);
|
|
190
|
+
const sign = negative ? '-' : '';
|
|
191
|
+
const decimalString = fracString.length > 0
|
|
192
|
+
? `${sign}${wholePart.toString()}.${fracString}`
|
|
193
|
+
: `${sign}${wholePart.toString()}`;
|
|
194
|
+
return formatNumberText(decimalString, { precision });
|
|
195
|
+
}
|
|
196
|
+
validateAddressFormat(address) {
|
|
197
|
+
return ALEO_ADDRESS_REGEX.test(address);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Returns the user's *available* balance for the selected token in
|
|
201
|
+
* atomic units — the sum of unspent matching records. Both Send modes
|
|
202
|
+
* spend FROM records, so the displayed available balance is the same
|
|
203
|
+
* regardless of the toggle. Caches the record list so the merge CTA
|
|
204
|
+
* (and the auto-pick at submit time) can share it.
|
|
205
|
+
*/
|
|
206
|
+
getBalance() {
|
|
207
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
+
const records = yield this.refreshRecordsCache();
|
|
209
|
+
return records
|
|
210
|
+
.filter((r) => recordMatchesSendableToken(r, this.selectedToken))
|
|
211
|
+
.reduce((acc, r) => acc + extractRecordAtomicAmount(r, this.selectedToken), BigInt(0));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Feemaster sponsors `credits.aleo/transfer_public` and `transfer_private`
|
|
216
|
+
* per ANF's policy, so the user pays zero on credits sends. Stablecoin
|
|
217
|
+
* + ARC-21 sends today fall through to user-paid (Feemaster policy
|
|
218
|
+
* doesn't yet cover them on testnet); the iframe still completes the
|
|
219
|
+
* transfer, just deducts the credits-fee directly. The optimistic `0n`
|
|
220
|
+
* here matches what we tell the user via `isGasSponsored()` below.
|
|
221
|
+
*/
|
|
222
|
+
fetchFee() {
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
this.fee.gas = BigInt(0);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
isGasSponsored() {
|
|
228
|
+
return this.selectedToken.programKind === 'credits';
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Tells the SendBalanceView to populate the picker / "Available"
|
|
232
|
+
* display from the wallet's *shielded* balance per token — not from
|
|
233
|
+
* the redcoast public-balance fetch the form would otherwise use.
|
|
234
|
+
* Both Send modes (Individual = `transfer_private`, Exchange =
|
|
235
|
+
* `transfer_private_to_public`) consume a private record, so what
|
|
236
|
+
* the user can actually send is the records sum.
|
|
237
|
+
*
|
|
238
|
+
* Returns one entry per token in the network registry (credits +
|
|
239
|
+
* stablecoins always; mainnet additionally exposes the 5 ARC-21
|
|
240
|
+
* hyp_warp tokens). Tokens with zero matching records still appear so
|
|
241
|
+
* the user can see the full menu — picking a zero-balance token
|
|
242
|
+
* surfaces the merge / fund flow, not silently hides the option.
|
|
243
|
+
*/
|
|
244
|
+
getSendableTokenBalances() {
|
|
245
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
246
|
+
const records = yield this.refreshRecordsCache();
|
|
247
|
+
const tokens = this.tokensForNetwork();
|
|
248
|
+
return tokens.map((token) => {
|
|
249
|
+
var _a;
|
|
250
|
+
const sumAtomic = records
|
|
251
|
+
.filter((r) => recordMatchesSendableToken(r, token))
|
|
252
|
+
.reduce((acc, r) => acc + extractRecordAtomicAmount(r, token), BigInt(0));
|
|
253
|
+
const divisor = Number(pow10(token.decimals));
|
|
254
|
+
const rawBalance = Number(sumAtomic);
|
|
255
|
+
return {
|
|
256
|
+
address: token.contractAddress,
|
|
257
|
+
balance: rawBalance / divisor,
|
|
258
|
+
decimals: token.decimals,
|
|
259
|
+
// Every Aleo Send token uses the same record-spend mechanics and
|
|
260
|
+
// the same `transaction.parse` / `getBalance` / `submit` pipeline
|
|
261
|
+
// internally (decimals + program both come from `selectedToken`).
|
|
262
|
+
// The form's "non-native" branch was designed for EVM-style ERC-20
|
|
263
|
+
// tokens that need a separate `parseNonNativeToken` + a separate
|
|
264
|
+
// contract balance source — neither of which applies here. Marking
|
|
265
|
+
// every entry as native sends the form through `transaction.parse`
|
|
266
|
+
// + `transaction.getBalance`, which is exactly what we want for
|
|
267
|
+
// credits AND stablecoins AND ARC-21 alike. Not a hack — it's an
|
|
268
|
+
// accurate description of the form's contract for this chain.
|
|
269
|
+
isNative: true,
|
|
270
|
+
logoURI: (_a = token.logoURI) !== null && _a !== void 0 ? _a : '',
|
|
271
|
+
name: token.name,
|
|
272
|
+
rawBalance,
|
|
273
|
+
symbol: token.symbol,
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
submit() {
|
|
279
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
280
|
+
if (!this.selectedTransactionMode) {
|
|
281
|
+
throw new Error('Pick Individual or Exchange before submitting an Aleo transfer.');
|
|
282
|
+
}
|
|
283
|
+
if (!this.to) {
|
|
284
|
+
throw new Error('Recipient address is required');
|
|
285
|
+
}
|
|
286
|
+
if (this.value === undefined) {
|
|
287
|
+
throw new Error('Amount is required');
|
|
288
|
+
}
|
|
289
|
+
// Both modes consume one private record. Pick the smallest record of
|
|
290
|
+
// the SELECTED token that covers `value` for minimal change-record
|
|
291
|
+
// fragmentation. If none fits we throw — the form should have already
|
|
292
|
+
// shown the merge CTA, but we keep the safety net so a stale form
|
|
293
|
+
// state can't slip a doomed transaction past us.
|
|
294
|
+
const targetValue = this.value;
|
|
295
|
+
const records = yield this.refreshRecordsCache();
|
|
296
|
+
const fitting = records
|
|
297
|
+
.filter((r) => recordMatchesSendableToken(r, this.selectedToken) &&
|
|
298
|
+
typeof r.record_plaintext === 'string')
|
|
299
|
+
.map((r) => ({
|
|
300
|
+
atomic: extractRecordAtomicAmount(r, this.selectedToken),
|
|
301
|
+
plaintext: r.record_plaintext,
|
|
302
|
+
}))
|
|
303
|
+
.filter((r) => r.atomic >= targetValue)
|
|
304
|
+
.sort((a, b) => {
|
|
305
|
+
if (a.atomic < b.atomic)
|
|
306
|
+
return -1;
|
|
307
|
+
if (a.atomic > b.atomic)
|
|
308
|
+
return 1;
|
|
309
|
+
return 0;
|
|
310
|
+
});
|
|
311
|
+
if (fitting.length === 0) {
|
|
312
|
+
throw new Error('No single private record covers this amount. Merge records first.');
|
|
313
|
+
}
|
|
314
|
+
return this.onSubmitFn({
|
|
315
|
+
mode: this.selectedTransactionMode,
|
|
316
|
+
recordPlaintext: fitting[0].plaintext,
|
|
317
|
+
to: this.to,
|
|
318
|
+
token: this.selectedToken,
|
|
319
|
+
value: this.value,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
refreshRecordsCache() {
|
|
324
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
325
|
+
const records = yield this.listOwnedRecords();
|
|
326
|
+
this.cachedRecords = records;
|
|
327
|
+
return records;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
tokensForNetwork() {
|
|
331
|
+
return getAleoSendableTokensForNetwork(this.networkId);
|
|
332
|
+
}
|
|
333
|
+
// Last-resort default when the network registry is empty (shouldn't
|
|
334
|
+
// happen in practice — both supported networks include credits).
|
|
335
|
+
creditsFallback() {
|
|
336
|
+
return {
|
|
337
|
+
contractAddress: ALEO_CREDITS_PROGRAM,
|
|
338
|
+
decimals: ALEO_CREDITS_DECIMALS,
|
|
339
|
+
logoURI: '',
|
|
340
|
+
name: 'Aleo Credits',
|
|
341
|
+
programId: ALEO_CREDITS_PROGRAM,
|
|
342
|
+
programKind: 'credits',
|
|
343
|
+
recordName: 'credits',
|
|
344
|
+
recordTypeLiteral: 'credits.record',
|
|
345
|
+
symbol: 'ALEO',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export { AleoUiTransaction };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-network registry of tokens spendable from the wallet's *shielded*
|
|
8
|
+
* (private record) state via the widget Send view.
|
|
9
|
+
*
|
|
10
|
+
* Three program kinds, three submit paths:
|
|
11
|
+
*
|
|
12
|
+
* - `credits` `credits.aleo/transfer_private`
|
|
13
|
+
* inputs: [record, recipient, amount]
|
|
14
|
+
* inputTypes: ['credits.record','address.private','u64.private']
|
|
15
|
+
*
|
|
16
|
+
* `credits.aleo/transfer_private_to_public`
|
|
17
|
+
* inputs: [record, recipient, amount]
|
|
18
|
+
* inputTypes: ['credits.record','address.public','u64.public']
|
|
19
|
+
*
|
|
20
|
+
* - `stablecoin` `<stablecoin>/transfer_private`
|
|
21
|
+
* inputs: [recipient, amount, record]
|
|
22
|
+
* inputTypes: ['address.private','u128.private','Token.record']
|
|
23
|
+
* (Sealance proof + its `[MerkleProof; 2u32].private` type
|
|
24
|
+
* are appended by the iframe — see
|
|
25
|
+
* `dynamic-waas-sdk/packages/aleo/src/client/client.ts`.)
|
|
26
|
+
*
|
|
27
|
+
* - `arc21` `token_registry.aleo/transfer_private`
|
|
28
|
+
* inputs: [recipient, amount, record]
|
|
29
|
+
* inputTypes: ['address.private','u128.private','Token.record']
|
|
30
|
+
* (token_id is encoded inside the record's plaintext, not
|
|
31
|
+
* passed as an explicit input.)
|
|
32
|
+
*
|
|
33
|
+
* The 5 ARC-21 entries are mainnet-only (Hyperlane warp routes are not
|
|
34
|
+
* deployed on testnet). Stablecoins and credits exist on both networks.
|
|
35
|
+
*/
|
|
36
|
+
const ALEO_MAINNET_NETWORK_ID = 0;
|
|
37
|
+
const ALEO_TESTNET_NETWORK_ID = 1;
|
|
38
|
+
const ALEO_CREDITS_DECIMALS = 6;
|
|
39
|
+
const ALEO_CREDITS_PROGRAM = 'credits.aleo';
|
|
40
|
+
const ALEO_TOKEN_REGISTRY_PROGRAM = 'token_registry.aleo';
|
|
41
|
+
const ALEO_CREDITS_TOKEN = {
|
|
42
|
+
contractAddress: ALEO_CREDITS_PROGRAM,
|
|
43
|
+
decimals: ALEO_CREDITS_DECIMALS,
|
|
44
|
+
logoURI: '',
|
|
45
|
+
name: 'Aleo Credits',
|
|
46
|
+
programId: ALEO_CREDITS_PROGRAM,
|
|
47
|
+
programKind: 'credits',
|
|
48
|
+
recordName: 'credits',
|
|
49
|
+
recordTypeLiteral: 'credits.record',
|
|
50
|
+
symbol: 'ALEO',
|
|
51
|
+
};
|
|
52
|
+
const ALEO_STABLECOIN_DECIMALS = 6;
|
|
53
|
+
const buildStablecoin = (programId, symbol, name) => ({
|
|
54
|
+
contractAddress: programId,
|
|
55
|
+
decimals: ALEO_STABLECOIN_DECIMALS,
|
|
56
|
+
name,
|
|
57
|
+
programId,
|
|
58
|
+
programKind: 'stablecoin',
|
|
59
|
+
recordName: 'Token',
|
|
60
|
+
recordTypeLiteral: 'Token.record',
|
|
61
|
+
symbol,
|
|
62
|
+
});
|
|
63
|
+
const buildArc21 = (args) => ({
|
|
64
|
+
contractAddress: args.warpProgram,
|
|
65
|
+
decimals: args.decimals,
|
|
66
|
+
externalAuthRequired: false,
|
|
67
|
+
name: args.name,
|
|
68
|
+
programId: ALEO_TOKEN_REGISTRY_PROGRAM,
|
|
69
|
+
programKind: 'arc21',
|
|
70
|
+
recordName: 'Token',
|
|
71
|
+
recordTypeLiteral: 'Token.record',
|
|
72
|
+
symbol: args.symbol,
|
|
73
|
+
tokenId: args.tokenId,
|
|
74
|
+
});
|
|
75
|
+
const ALEO_SENDABLE_TOKENS_BY_NETWORK = {
|
|
76
|
+
[ALEO_MAINNET_NETWORK_ID]: [
|
|
77
|
+
ALEO_CREDITS_TOKEN,
|
|
78
|
+
buildStablecoin('usad_stablecoin.aleo', 'USAD', 'USAD'),
|
|
79
|
+
buildStablecoin('usdcx_stablecoin.aleo', 'USDCx', 'USDCx'),
|
|
80
|
+
buildArc21({
|
|
81
|
+
decimals: 9,
|
|
82
|
+
name: 'Wrapped SOL',
|
|
83
|
+
symbol: 'wSOL',
|
|
84
|
+
tokenId: '2045969100091121326225168054634646230244820821909676777152465722877810201564field',
|
|
85
|
+
warpProgram: 'hyp_warp_token_sol.aleo',
|
|
86
|
+
}),
|
|
87
|
+
buildArc21({
|
|
88
|
+
decimals: 18,
|
|
89
|
+
name: 'Wrapped ETH',
|
|
90
|
+
symbol: 'wETH',
|
|
91
|
+
tokenId: '8189585964265444162798552221009403350643900573290534096996249214099143169251field',
|
|
92
|
+
warpProgram: 'hyp_warp_token_eth.aleo',
|
|
93
|
+
}),
|
|
94
|
+
buildArc21({
|
|
95
|
+
decimals: 8,
|
|
96
|
+
name: 'Wrapped BTC',
|
|
97
|
+
symbol: 'wBTC',
|
|
98
|
+
tokenId: '3491859903473482085250871387962132231204352466326026409883548131580582527809field',
|
|
99
|
+
warpProgram: 'hyp_warp_token_wbtc.aleo',
|
|
100
|
+
}),
|
|
101
|
+
buildArc21({
|
|
102
|
+
decimals: 6,
|
|
103
|
+
name: 'Tether USD',
|
|
104
|
+
symbol: 'USDT',
|
|
105
|
+
tokenId: '7881654794448182580124231856035865816599796419758925654292946277940935117913field',
|
|
106
|
+
warpProgram: 'hyp_warp_token_usdt.aleo',
|
|
107
|
+
}),
|
|
108
|
+
buildArc21({
|
|
109
|
+
decimals: 6,
|
|
110
|
+
name: 'USD Coin',
|
|
111
|
+
symbol: 'USDC',
|
|
112
|
+
tokenId: '4697275201844475848710842677807162058146139844643350200269139278887318953049field',
|
|
113
|
+
warpProgram: 'hyp_warp_token_usdc.aleo',
|
|
114
|
+
}),
|
|
115
|
+
],
|
|
116
|
+
[ALEO_TESTNET_NETWORK_ID]: [
|
|
117
|
+
ALEO_CREDITS_TOKEN,
|
|
118
|
+
buildStablecoin('test_usad_stablecoin.aleo', 'USAD', 'USAD'),
|
|
119
|
+
buildStablecoin('test_usdcx_stablecoin.aleo', 'USDCx', 'USDCx'),
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
const getAleoSendableTokensForNetwork = (networkId) => { var _a; return (_a = ALEO_SENDABLE_TOKENS_BY_NETWORK[networkId]) !== null && _a !== void 0 ? _a : []; };
|
|
123
|
+
/**
|
|
124
|
+
* True when the owned record matches the given sendable token: same program
|
|
125
|
+
* + record name, and (for ARC-21) same token_id encoded in the plaintext.
|
|
126
|
+
*
|
|
127
|
+
* For ARC-21, multiple tokens share `token_registry.aleo/Token`, so the
|
|
128
|
+
* plaintext-level token_id check is what disambiguates wSOL records from
|
|
129
|
+
* USDT records, etc. We match by substring rather than parsing the full
|
|
130
|
+
* record DSL — the field literal `<digits>field` is unambiguous and the
|
|
131
|
+
* record DSL is stable across snarkVM versions.
|
|
132
|
+
*/
|
|
133
|
+
const recordMatchesSendableToken = (record, token) => {
|
|
134
|
+
if (record.program_name !== token.programId)
|
|
135
|
+
return false;
|
|
136
|
+
if (record.record_name !== token.recordName)
|
|
137
|
+
return false;
|
|
138
|
+
if (typeof record.record_plaintext !== 'string')
|
|
139
|
+
return false;
|
|
140
|
+
if (token.programKind !== 'arc21')
|
|
141
|
+
return true;
|
|
142
|
+
if (!token.tokenId)
|
|
143
|
+
return false;
|
|
144
|
+
return record.record_plaintext.includes(`token_id: ${token.tokenId}`);
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Atomic-units amount from the record plaintext.
|
|
148
|
+
*
|
|
149
|
+
* - credits → pre-parsed `microcredits` (decimal string).
|
|
150
|
+
* - stablecoin / arc21 → parsed from `amount: <digits>u128` in plaintext.
|
|
151
|
+
*
|
|
152
|
+
* Returns `0n` when nothing parses — the caller's UI rules
|
|
153
|
+
* (sum / merge CTA / picker entry) all degrade gracefully on a zero amount.
|
|
154
|
+
*/
|
|
155
|
+
const extractRecordAtomicAmount = (record, token) => {
|
|
156
|
+
if (token.programKind === 'credits') {
|
|
157
|
+
if (typeof record.microcredits !== 'string')
|
|
158
|
+
return BigInt(0);
|
|
159
|
+
try {
|
|
160
|
+
return BigInt(record.microcredits);
|
|
161
|
+
}
|
|
162
|
+
catch (_a) {
|
|
163
|
+
return BigInt(0);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (typeof record.record_plaintext !== 'string')
|
|
167
|
+
return BigInt(0);
|
|
168
|
+
const match = /amount:\s*(\d+)u128/.exec(record.record_plaintext);
|
|
169
|
+
if (!match)
|
|
170
|
+
return BigInt(0);
|
|
171
|
+
try {
|
|
172
|
+
return BigInt(match[1]);
|
|
173
|
+
}
|
|
174
|
+
catch (_b) {
|
|
175
|
+
return BigInt(0);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
exports.ALEO_CREDITS_DECIMALS = ALEO_CREDITS_DECIMALS;
|
|
180
|
+
exports.ALEO_CREDITS_PROGRAM = ALEO_CREDITS_PROGRAM;
|
|
181
|
+
exports.ALEO_SENDABLE_TOKENS_BY_NETWORK = ALEO_SENDABLE_TOKENS_BY_NETWORK;
|
|
182
|
+
exports.ALEO_TOKEN_REGISTRY_PROGRAM = ALEO_TOKEN_REGISTRY_PROGRAM;
|
|
183
|
+
exports.extractRecordAtomicAmount = extractRecordAtomicAmount;
|
|
184
|
+
exports.getAleoSendableTokensForNetwork = getAleoSendableTokensForNetwork;
|
|
185
|
+
exports.recordMatchesSendableToken = recordMatchesSendableToken;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-network registry of tokens spendable from the wallet's *shielded*
|
|
3
|
+
* (private record) state via the widget Send view.
|
|
4
|
+
*
|
|
5
|
+
* Three program kinds, three submit paths:
|
|
6
|
+
*
|
|
7
|
+
* - `credits` `credits.aleo/transfer_private`
|
|
8
|
+
* inputs: [record, recipient, amount]
|
|
9
|
+
* inputTypes: ['credits.record','address.private','u64.private']
|
|
10
|
+
*
|
|
11
|
+
* `credits.aleo/transfer_private_to_public`
|
|
12
|
+
* inputs: [record, recipient, amount]
|
|
13
|
+
* inputTypes: ['credits.record','address.public','u64.public']
|
|
14
|
+
*
|
|
15
|
+
* - `stablecoin` `<stablecoin>/transfer_private`
|
|
16
|
+
* inputs: [recipient, amount, record]
|
|
17
|
+
* inputTypes: ['address.private','u128.private','Token.record']
|
|
18
|
+
* (Sealance proof + its `[MerkleProof; 2u32].private` type
|
|
19
|
+
* are appended by the iframe — see
|
|
20
|
+
* `dynamic-waas-sdk/packages/aleo/src/client/client.ts`.)
|
|
21
|
+
*
|
|
22
|
+
* - `arc21` `token_registry.aleo/transfer_private`
|
|
23
|
+
* inputs: [recipient, amount, record]
|
|
24
|
+
* inputTypes: ['address.private','u128.private','Token.record']
|
|
25
|
+
* (token_id is encoded inside the record's plaintext, not
|
|
26
|
+
* passed as an explicit input.)
|
|
27
|
+
*
|
|
28
|
+
* The 5 ARC-21 entries are mainnet-only (Hyperlane warp routes are not
|
|
29
|
+
* deployed on testnet). Stablecoins and credits exist on both networks.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ALEO_CREDITS_DECIMALS = 6;
|
|
32
|
+
export declare const ALEO_CREDITS_PROGRAM = "credits.aleo";
|
|
33
|
+
export declare const ALEO_TOKEN_REGISTRY_PROGRAM = "token_registry.aleo";
|
|
34
|
+
export type AleoProgramKind = 'arc21' | 'credits' | 'stablecoin';
|
|
35
|
+
export type AleoSendableToken = {
|
|
36
|
+
contractAddress: string;
|
|
37
|
+
symbol: string;
|
|
38
|
+
name: string;
|
|
39
|
+
decimals: number;
|
|
40
|
+
programId: string;
|
|
41
|
+
tokenId?: string;
|
|
42
|
+
externalAuthRequired?: boolean;
|
|
43
|
+
recordTypeLiteral: string;
|
|
44
|
+
recordName: string;
|
|
45
|
+
programKind: AleoProgramKind;
|
|
46
|
+
logoURI?: string;
|
|
47
|
+
};
|
|
48
|
+
export declare const ALEO_SENDABLE_TOKENS_BY_NETWORK: Record<number, AleoSendableToken[]>;
|
|
49
|
+
export declare const getAleoSendableTokensForNetwork: (networkId: number) => AleoSendableToken[];
|
|
50
|
+
/** Owned record as returned by `DynamicAleoWalletClient.findOwnedRecords`. */
|
|
51
|
+
export type AleoOwnedRecord = {
|
|
52
|
+
program_name?: string;
|
|
53
|
+
record_name?: string;
|
|
54
|
+
record_plaintext?: string;
|
|
55
|
+
/** Set only for `credits.aleo/credits` records — pre-parsed by the iframe. */
|
|
56
|
+
microcredits?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* True when the owned record matches the given sendable token: same program
|
|
60
|
+
* + record name, and (for ARC-21) same token_id encoded in the plaintext.
|
|
61
|
+
*
|
|
62
|
+
* For ARC-21, multiple tokens share `token_registry.aleo/Token`, so the
|
|
63
|
+
* plaintext-level token_id check is what disambiguates wSOL records from
|
|
64
|
+
* USDT records, etc. We match by substring rather than parsing the full
|
|
65
|
+
* record DSL — the field literal `<digits>field` is unambiguous and the
|
|
66
|
+
* record DSL is stable across snarkVM versions.
|
|
67
|
+
*/
|
|
68
|
+
export declare const recordMatchesSendableToken: (record: AleoOwnedRecord, token: AleoSendableToken) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Atomic-units amount from the record plaintext.
|
|
71
|
+
*
|
|
72
|
+
* - credits → pre-parsed `microcredits` (decimal string).
|
|
73
|
+
* - stablecoin / arc21 → parsed from `amount: <digits>u128` in plaintext.
|
|
74
|
+
*
|
|
75
|
+
* Returns `0n` when nothing parses — the caller's UI rules
|
|
76
|
+
* (sum / merge CTA / picker entry) all degrade gracefully on a zero amount.
|
|
77
|
+
*/
|
|
78
|
+
export declare const extractRecordAtomicAmount: (record: AleoOwnedRecord, token: AleoSendableToken) => bigint;
|