@dynamic-labs/aleo 4.79.2 → 4.81.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.cjs +1 -1
  3. package/package.js +1 -1
  4. package/package.json +10 -6
  5. package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.cjs +798 -0
  6. package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.d.ts +409 -0
  7. package/src/connectors/DynamicWaasAleoConnector/DynamicWaasAleoConnector.js +794 -0
  8. package/src/connectors/DynamicWaasAleoConnector/index.cjs +13 -0
  9. package/src/connectors/DynamicWaasAleoConnector/index.d.ts +3 -0
  10. package/src/connectors/DynamicWaasAleoConnector/index.js +9 -0
  11. package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.cjs +216 -0
  12. package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.d.ts +116 -0
  13. package/src/connectors/WaasAleoWalletConnector/WaasAleoWalletConnector.js +211 -0
  14. package/src/connectors/WaasAleoWalletConnector/index.d.ts +1 -0
  15. package/src/index.cjs +15 -0
  16. package/src/index.d.ts +5 -0
  17. package/src/index.js +7 -0
  18. package/src/utils/AleoUiTransaction/AleoUiTransaction.cjs +354 -0
  19. package/src/utils/AleoUiTransaction/AleoUiTransaction.d.ts +130 -0
  20. package/src/utils/AleoUiTransaction/AleoUiTransaction.js +350 -0
  21. package/src/utils/AleoUiTransaction/index.d.ts +2 -0
  22. package/src/utils/aleoSendableTokens/aleoSendableTokens.cjs +185 -0
  23. package/src/utils/aleoSendableTokens/aleoSendableTokens.d.ts +78 -0
  24. package/src/utils/aleoSendableTokens/aleoSendableTokens.js +175 -0
  25. package/src/utils/aleoSendableTokens/index.d.ts +2 -0
  26. package/src/utils/aleoShieldableTokens/aleoShieldableTokens.cjs +119 -0
  27. package/src/utils/aleoShieldableTokens/aleoShieldableTokens.d.ts +45 -0
  28. package/src/utils/aleoShieldableTokens/aleoShieldableTokens.js +114 -0
  29. 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,2 @@
1
+ export { AleoUiTransaction } from './AleoUiTransaction';
2
+ export type { AleoSubmitParams, AleoTransferMode } from './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;