@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.
Files changed (29) hide show
  1. package/CHANGELOG.md +9 -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,794 @@
1
+ 'use client'
2
+ import { __awaiter } from '../../../_virtual/_tslib.js';
3
+ import { DynamicError } from '@dynamic-labs/utils';
4
+ import { withDynamicWaas } from '@dynamic-labs/waas';
5
+ import { isSameAddress } from '@dynamic-labs/wallet-connector-core';
6
+ import { Logger } from '@dynamic-labs/logger';
7
+ import { WaasAleoWalletConnector } from '../WaasAleoWalletConnector/WaasAleoWalletConnector.js';
8
+ import { AleoWallet } from '../../wallet/AleoWallet/AleoWallet.js';
9
+ import { AleoUiTransaction } from '../../utils/AleoUiTransaction/AleoUiTransaction.js';
10
+ import { ALEO_TOKEN_REGISTRY_PROGRAM } from '../../utils/aleoSendableTokens/aleoSendableTokens.js';
11
+ import { findAleoShieldableToken } from '../../utils/aleoShieldableTokens/aleoShieldableTokens.js';
12
+
13
+ /**
14
+ * Per-program record shape for `<program>/join`. The `join` transition
15
+ * consumes 2 records of the program's "balance" record type and emits
16
+ * 1 merged record of the same type. The credits program calls this
17
+ * record `credits` (`credits.record`); Sealance stablecoins call it
18
+ * `Token` (`Token.record`).
19
+ *
20
+ * To support a new program (e.g. ARC-21 / token_registry.aleo) add an
21
+ * entry here matching the on-chain record name in the program's source.
22
+ */
23
+ const JOIN_PROGRAM_SHAPES = {
24
+ // ARC-21 (token_registry.aleo) — multiple tokens share this program. The
25
+ // join transition's record type is the same across tokens (`Token.record`),
26
+ // so the shape is registered once here. The per-token `token_id` filter
27
+ // lives in `joinRecords` below, since records of different tokens cannot
28
+ // be merged with each other (the on-chain `join` enforces token_id match).
29
+ [ALEO_TOKEN_REGISTRY_PROGRAM]: {
30
+ expectedRecordName: 'Token',
31
+ recordTypeLiteral: 'Token.record',
32
+ },
33
+ 'credits.aleo': {
34
+ expectedRecordName: 'credits',
35
+ recordTypeLiteral: 'credits.record',
36
+ },
37
+ 'test_usad_stablecoin.aleo': {
38
+ expectedRecordName: 'Token',
39
+ recordTypeLiteral: 'Token.record',
40
+ },
41
+ 'test_usdcx_stablecoin.aleo': {
42
+ expectedRecordName: 'Token',
43
+ recordTypeLiteral: 'Token.record',
44
+ },
45
+ 'usad_stablecoin.aleo': {
46
+ expectedRecordName: 'Token',
47
+ recordTypeLiteral: 'Token.record',
48
+ },
49
+ 'usdcx_stablecoin.aleo': {
50
+ expectedRecordName: 'Token',
51
+ recordTypeLiteral: 'Token.record',
52
+ },
53
+ };
54
+ const isJoinSupportedProgram = (programId) => programId in JOIN_PROGRAM_SHAPES;
55
+ /**
56
+ * Builds the `(functionName, inputs, inputTypes)` triple for a Send
57
+ * transition based on the picker-selected token's `programKind`. Lives
58
+ * at module scope so the closure body in `createUiTransaction` stays
59
+ * focused on wiring; the per-kind signatures are documented inline.
60
+ *
61
+ * - `credits` u64 amounts. inputs are `[record, recipient, amount]`,
62
+ * matching `credits.aleo`'s historical signature.
63
+ * - `stablecoin` u128 amounts. inputs are `[recipient, amount, record]`.
64
+ * The iframe appends the Sealance proof literal +
65
+ * `[MerkleProof; 2u32].private` type — caller does NOT
66
+ * pass them. Verified against
67
+ * `dynamic-waas-sdk/packages/aleo/src/client/client.ts`'s
68
+ * `proveTransaction` Sealance auto-injection block.
69
+ * - `arc21` u128 amounts. inputs are `[recipient, amount, record]`.
70
+ * Token_id is encoded inside the record plaintext, so
71
+ * the transition signature does NOT include it.
72
+ * Verified against `token_registry.aleo` source via the
73
+ * Provable explorer `/program` endpoint.
74
+ */
75
+ const buildTransferInputs = (args) => {
76
+ const { mode, to, value, recordPlaintext, token } = args;
77
+ const functionName = mode === 'individual' ? 'transfer_private' : 'transfer_private_to_public';
78
+ if (token.programKind === 'credits') {
79
+ const recipientType = mode === 'individual' ? 'address.private' : 'address.public';
80
+ const amountType = mode === 'individual' ? 'u64.private' : 'u64.public';
81
+ return {
82
+ functionName,
83
+ inputTypes: ['credits.record', recipientType, amountType],
84
+ inputs: [recordPlaintext, to, `${value.toString()}u64`],
85
+ };
86
+ }
87
+ // Stablecoin + ARC-21 share the same outer signature; the iframe's
88
+ // Sealance auto-injection only fires for the four registered
89
+ // stablecoin program ids, so ARC-21 records pass through unchanged.
90
+ const recipientType = mode === 'individual' ? 'address.private' : 'address.public';
91
+ const amountType = mode === 'individual' ? 'u128.private' : 'u128.public';
92
+ return {
93
+ functionName,
94
+ inputTypes: [recipientType, amountType, 'Token.record'],
95
+ inputs: [to, `${value.toString()}u128`, recordPlaintext],
96
+ };
97
+ };
98
+ /**
99
+ * Builds the `(functionName, inputs, inputTypes)` triple for a
100
+ * `<program>/transfer_public_to_private` shield call to self. Mirrors
101
+ * `buildTransferInputs` shape — three program kinds, three signatures:
102
+ *
103
+ * - `credits` u64 amounts. inputs `[recipient, amount]`.
104
+ * - `stablecoin` u128 amounts. inputs `[recipient, amount]`. Sealance
105
+ * proof is NOT appended for `transfer_public_to_private`
106
+ * (verified against the iframe's
107
+ * `SEALANCE_FUNCTIONS_REQUIRING_PROOF` set, which only
108
+ * covers `transfer_private` / `transfer_private_to_public`).
109
+ * - `arc21` u128 amounts. inputs `[token_id, recipient, amount,
110
+ * external_auth_required]`. Token_id from the registry;
111
+ * external_auth from the per-token registry entry
112
+ * (defaults to `false` when unset). Verified against
113
+ * `token_registry.aleo` source via the Provable explorer.
114
+ */
115
+ const buildShieldInputs = (args) => {
116
+ const { to, value, token } = args;
117
+ const functionName = 'transfer_public_to_private';
118
+ if (token.programKind === 'credits') {
119
+ return {
120
+ functionName,
121
+ inputTypes: ['address.private', 'u64.public'],
122
+ inputs: [to, `${value.toString()}u64`],
123
+ };
124
+ }
125
+ if (token.programKind === 'stablecoin') {
126
+ return {
127
+ functionName,
128
+ inputTypes: ['address.private', 'u128.public'],
129
+ inputs: [to, `${value.toString()}u128`],
130
+ };
131
+ }
132
+ // arc21
133
+ if (!token.tokenId) {
134
+ throw new DynamicError(`shieldToken: ARC-21 token ${token.contractAddress} is missing tokenId in registry.`);
135
+ }
136
+ const externalAuth = token.externalAuthRequired ? 'true' : 'false';
137
+ return {
138
+ functionName,
139
+ inputTypes: [
140
+ 'field.public',
141
+ 'address.private',
142
+ 'u128.public',
143
+ 'boolean.public',
144
+ ],
145
+ inputs: [token.tokenId, to, `${value.toString()}u128`, externalAuth],
146
+ };
147
+ };
148
+ const joinShapeForProgram = (programId) => {
149
+ const shape = JOIN_PROGRAM_SHAPES[programId];
150
+ if (!shape) {
151
+ throw new DynamicError(`joinRecords: program ${programId} has no registered join-record shape.`);
152
+ }
153
+ return shape;
154
+ };
155
+ const hasUsableRecordShape = (r) => {
156
+ const rec = r;
157
+ return (typeof (rec === null || rec === void 0 ? void 0 : rec.record_plaintext) === 'string' &&
158
+ rec.record_plaintext.length > 0 &&
159
+ typeof (rec === null || rec === void 0 ? void 0 : rec.program_name) === 'string');
160
+ };
161
+ /**
162
+ * Dynamic WaaS connector for Aleo.
163
+ * Phase 1: supports wallet creation (MPC keygen → aleo1... address) and
164
+ * retrieval of the active WaaS wallet. signMessage and signTransaction
165
+ * throw NotSupported until Sodot adds `EdBls12377.sign(bytes)` and we wire up
166
+ * the Aleo `signRequest` ceremony.
167
+ */
168
+ class DynamicWaasAleoConnector extends withDynamicWaas(WaasAleoWalletConnector) {
169
+ constructor(props) {
170
+ super('Dynamic Waas', props);
171
+ this.ChainWallet = AleoWallet;
172
+ this.name = 'Dynamic Waas';
173
+ this.overrideKey = 'dynamicwaas';
174
+ this.isEmbeddedWallet = true;
175
+ this.logger = new Logger('DynamicWaasAleoConnector');
176
+ this.walletUiUtils = props.walletUiUtils;
177
+ }
178
+ setVerifiedCredentials(verifiedCredentials) {
179
+ this.verifiedCredentials = verifiedCredentials.filter((vc) => vc.walletName === 'dynamicwaas' && vc.chain === 'aleo');
180
+ }
181
+ getWalletClientByAddress(_a) {
182
+ return __awaiter(this, arguments, void 0, function* ({ accountAddress, }) {
183
+ this.setActiveAccountAddress(accountAddress);
184
+ return this.getWaasWalletClient();
185
+ });
186
+ }
187
+ getActiveAccountAddress() {
188
+ return __awaiter(this, void 0, void 0, function* () {
189
+ return this.activeAccountAddress;
190
+ });
191
+ }
192
+ setActiveAccountAddress(accountAddress) {
193
+ this.activeAccountAddress = accountAddress;
194
+ }
195
+ afterWalletSelectHook(walletAddress) {
196
+ this.setActiveAccountAddress(walletAddress);
197
+ }
198
+ requireSignedSessionId() {
199
+ return __awaiter(this, void 0, void 0, function* () {
200
+ var _a;
201
+ const signedSessionId = yield ((_a = this.getSignedSessionId) === null || _a === void 0 ? void 0 : _a.call(this));
202
+ if (!signedSessionId) {
203
+ throw new DynamicError('Signed session ID is required');
204
+ }
205
+ return signedSessionId;
206
+ });
207
+ }
208
+ validateActiveWallet(expectedAddress) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ var _a;
211
+ const walletClient = yield this.getWaasWalletClient();
212
+ const signedSessionId = yield this.requireSignedSessionId();
213
+ const targetWallet = yield walletClient.getWallet({
214
+ accountAddress: expectedAddress,
215
+ authToken: (_a = this.getAuthToken) === null || _a === void 0 ? void 0 : _a.call(this),
216
+ signedSessionId,
217
+ });
218
+ if (!targetWallet) {
219
+ throw new DynamicError('Account not found');
220
+ }
221
+ const isWalletActive = isSameAddress(targetWallet.accountAddress, this.activeAccountAddress || '', this.connectedChain);
222
+ if (!isWalletActive) {
223
+ this.activeAccountAddress = targetWallet.accountAddress;
224
+ }
225
+ });
226
+ }
227
+ ensureActiveAccountFromVerifiedCredentials() {
228
+ var _a, _b;
229
+ if (this.activeAccountAddress)
230
+ return;
231
+ const address = (_b = (_a = this.verifiedCredentials) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.address;
232
+ if (typeof address === 'string') {
233
+ this.setActiveAccountAddress(address);
234
+ }
235
+ }
236
+ getAddress() {
237
+ return __awaiter(this, void 0, void 0, function* () {
238
+ this.ensureActiveAccountFromVerifiedCredentials();
239
+ return this.activeAccountAddress || '';
240
+ });
241
+ }
242
+ connect() {
243
+ return __awaiter(this, void 0, void 0, function* () {
244
+ yield this.getWaasWalletClient();
245
+ });
246
+ }
247
+ getConnectedAccounts() {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ this.ensureActiveAccountFromVerifiedCredentials();
250
+ return this.activeAccountAddress ? [this.activeAccountAddress] : [];
251
+ });
252
+ }
253
+ // Phase 1: signMessage not supported (Sodot EdBls12377 lacks sign(bytes))
254
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
255
+ signMessage(message) {
256
+ return __awaiter(this, void 0, void 0, function* () {
257
+ throw new DynamicError('Aleo signMessage is not supported yet — pending EdBls12377.sign(bytes) from Sodot');
258
+ });
259
+ }
260
+ /* eslint-disable @typescript-eslint/no-unused-vars */
261
+ signMessageWithContext({ message, context, }) {
262
+ /* eslint-enable @typescript-eslint/no-unused-vars */
263
+ throw new DynamicError('Method not implemented.');
264
+ }
265
+ // Phase 1: signTransaction not supported (signAleoRequest ceremony not wired up)
266
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
267
+ signTransaction(_transaction) {
268
+ return __awaiter(this, void 0, void 0, function* () {
269
+ throw new DynamicError('Aleo signTransaction is not supported yet — coming in Phase 2');
270
+ });
271
+ }
272
+ /**
273
+ * Aleo full transaction flow: view key → MPC sign → prove + broadcast via DPS.
274
+ * Delegates to the iframe's DynamicAleoWalletClient.proveTransaction.
275
+ */
276
+ proveTransaction(params) {
277
+ return __awaiter(this, void 0, void 0, function* () {
278
+ var _a, _b;
279
+ this.ensureActiveAccountFromVerifiedCredentials();
280
+ if (!this.activeAccountAddress) {
281
+ throw new DynamicError('Active account address is required');
282
+ }
283
+ const walletClient = yield this.getWaasWalletClient();
284
+ const signedSessionId = yield this.requireSignedSessionId();
285
+ // Pass the currently-selected Aleo network on every call. The iframe
286
+ // uses this to route the SDK build, the Feemaster policy, and proving
287
+ // endpoints. Critical for `credits.aleo` whose program name doesn't
288
+ // vary by network — without `chainId` the iframe defaulted to mainnet
289
+ // for credits flows, which means testnet credits transactions
290
+ // consulted the wrong Feemaster policy and would silently miss
291
+ // sponsorship coverage.
292
+ const chainId = (_a = this.getSelectedNetwork()) === null || _a === void 0 ? void 0 : _a.chainId;
293
+ return walletClient.proveTransaction({
294
+ accountAddress: this.activeAccountAddress,
295
+ authToken: (_b = this.getAuthToken) === null || _b === void 0 ? void 0 : _b.call(this),
296
+ broadcast: params.broadcast,
297
+ chainId,
298
+ functionName: params.functionName,
299
+ inputTypes: params.inputTypes,
300
+ inputs: params.inputs,
301
+ programId: params.programId,
302
+ signedSessionId,
303
+ });
304
+ });
305
+ }
306
+ /**
307
+ * List this wallet's Aleo records across all programs (credits + custom
308
+ * tokens). Delegates into the iframe's DynamicAleoWalletClient.
309
+ * findOwnedRecords, which uses Provable's hosted RecordScanner. View key
310
+ * never leaves the iframe.
311
+ *
312
+ * Each returned record carries `program_name` + `record_name` so callers
313
+ * can group/display by token. Credits records additionally get a parsed
314
+ * `microcredits` field. Other tokens are returned raw — caller parses the
315
+ * plaintext per program schema.
316
+ *
317
+ * First call per wallet registers the view key with Provable (one-time,
318
+ * cached UUID in iframe IndexedDB). Subsequent calls reuse the UUID.
319
+ */
320
+ listOwnedRecords() {
321
+ return __awaiter(this, void 0, void 0, function* () {
322
+ var _a, _b;
323
+ this.ensureActiveAccountFromVerifiedCredentials();
324
+ if (!this.activeAccountAddress) {
325
+ throw new DynamicError('Active account address is required');
326
+ }
327
+ const walletClient = yield this.getWaasWalletClient();
328
+ const signedSessionId = yield this.requireSignedSessionId();
329
+ // Pass the currently-selected Aleo network (`'0'` mainnet, `'1'`
330
+ // testnet) on every call. The iframe routes the scanner instance +
331
+ // walletState cache by this id; toggling networks at runtime just
332
+ // lands on the matching scanner. Mirrors the SVM/Stellar pattern of
333
+ // chainId per call.
334
+ const chainId = (_a = this.getSelectedNetwork()) === null || _a === void 0 ? void 0 : _a.chainId;
335
+ return walletClient.findOwnedRecords({
336
+ accountAddress: this.activeAccountAddress,
337
+ authToken: (_b = this.getAuthToken) === null || _b === void 0 ? void 0 : _b.call(this),
338
+ chainId,
339
+ signedSessionId,
340
+ });
341
+ });
342
+ }
343
+ /**
344
+ * Merge the wallet's owned records down — one final record per Aleo
345
+ * program — by recursively running `<program>/join` two-at-a-time.
346
+ * Mirrors the basic pairwise strategy from Provable's `example-autojoin`:
347
+ * at each round we pair records, run joins concurrently, wait for the
348
+ * outputs to surface from Provable's RecordScanner, and recurse until one
349
+ * record remains for that program. Odd-one-out is carried forward.
350
+ *
351
+ * Cross-program records can't combine on-chain (a `credits.aleo/credits`
352
+ * record and a `test_usad_stablecoin.aleo/Token` record have different
353
+ * record types), so the result is always one record per program.
354
+ *
355
+ * Fees are sponsored by ANF's Feemaster when its policy covers the
356
+ * `(programId, 'join')` pair (today: `credits.aleo`). Programs not in
357
+ * the policy fall through to the user-paid path inside `proveTransaction`.
358
+ *
359
+ * Today: only `credits.aleo` is allowed end-to-end. Stablecoin / ARC-21
360
+ * joins are blocked on snarkVM's `Stack::authorize_request` credits-only
361
+ * check — Provable is tracking the relaxation. Until then non-credits
362
+ * programs are reported as `skipped: 'unsupported'` rather than thrown,
363
+ * so callers can iterate the result and surface what merged vs. what
364
+ * didn't without try/catch noise.
365
+ *
366
+ * @param opts.programIds Programs to merge. Omit (default) to merge
367
+ * every owned program with ≥2 records. Programs with <2 records are
368
+ * reported as `skipped: 'insufficient_records'`.
369
+ * @param opts.filterRecord Optional per-record predicate that narrows
370
+ * the candidate set BEFORE pairing. Used by ARC-21 where one program
371
+ * (`token_registry.aleo`) hosts many tokens — the on-chain `join`
372
+ * refuses to combine records with different `token_id` fields, so we
373
+ * filter by token_id first and never broadcast a doomed merge.
374
+ */
375
+ joinRecords(opts) {
376
+ return __awaiter(this, void 0, void 0, function* () {
377
+ var _a;
378
+ this.ensureActiveAccountFromVerifiedCredentials();
379
+ if (!this.activeAccountAddress) {
380
+ throw new DynamicError('Active account address is required');
381
+ }
382
+ const byProgram = yield this.fetchAndGroupOwnedRecords(opts === null || opts === void 0 ? void 0 : opts.filterRecord);
383
+ // Caller-supplied list takes precedence; default merges every program
384
+ // we observed. We process programs sequentially so Feemaster quota and
385
+ // logs stay readable — concurrency lives inside each program's
386
+ // pairwise round (`Promise.all` over pairs).
387
+ const requested = (opts === null || opts === void 0 ? void 0 : opts.programIds) && opts.programIds.length > 0
388
+ ? opts.programIds
389
+ : [...byProgram.keys()];
390
+ const results = [];
391
+ for (const programId of requested) {
392
+ const records = (_a = byProgram.get(programId)) !== null && _a !== void 0 ? _a : [];
393
+ results.push(yield this.processProgramJoin(programId, records));
394
+ }
395
+ return { results };
396
+ });
397
+ }
398
+ /**
399
+ * Pre-fork: only `credits.aleo` made it past `Stack::authorize_request`.
400
+ * Post-fork (Roy's snarkVM @32ad6c4 + the file:-overrides in
401
+ * dynamic-waas-sdk's pnpm workspace), stablecoin joins authorize too.
402
+ * Programs that *aren't* credits and aren't a known stablecoin still
403
+ * 404 on shape — we'd hit `record name doesn't match` or similar
404
+ * mid-MPC, which is wasteful, so we keep an allowlist.
405
+ */
406
+ processProgramJoin(programId, records) {
407
+ return __awaiter(this, void 0, void 0, function* () {
408
+ if (!isJoinSupportedProgram(programId)) {
409
+ return { programId, rounds: 0, skipped: 'unsupported' };
410
+ }
411
+ if (records.length < 2) {
412
+ return { programId, rounds: 0, skipped: 'insufficient_records' };
413
+ }
414
+ try {
415
+ const { rounds, finalRecordPlaintext } = yield this.joinProgramRecords(programId, records);
416
+ return { finalRecordPlaintext, programId, rounds };
417
+ }
418
+ catch (err) {
419
+ return {
420
+ error: err instanceof Error ? err.message : String(err),
421
+ programId,
422
+ rounds: 0,
423
+ };
424
+ }
425
+ });
426
+ }
427
+ /**
428
+ * Reads the wallet's owned records, applies the optional caller predicate,
429
+ * and groups by program. The `filterRecord` predicate runs first so callers
430
+ * can scope the merge to a sub-set within a program (ARC-21 token_id filter).
431
+ */
432
+ fetchAndGroupOwnedRecords(filterRecord) {
433
+ return __awaiter(this, void 0, void 0, function* () {
434
+ var _a;
435
+ const allRecords = (yield this.listOwnedRecords()).records
436
+ .filter(hasUsableRecordShape)
437
+ .filter((r) => (filterRecord ? filterRecord(r) : true));
438
+ const byProgram = new Map();
439
+ for (const r of allRecords) {
440
+ const list = (_a = byProgram.get(r.program_name)) !== null && _a !== void 0 ? _a : [];
441
+ list.push(r);
442
+ byProgram.set(r.program_name, list);
443
+ }
444
+ return byProgram;
445
+ });
446
+ }
447
+ /**
448
+ * Pairwise-merges `records` (all from `programId`) until one remains.
449
+ * Each round runs every pair's join concurrently, then polls the
450
+ * RecordScanner until the round's output records have surfaced.
451
+ * Caller validates the list shape and ≥2 length.
452
+ *
453
+ * We track every plaintext we've consumed across the entire call in
454
+ * `consumedAcrossCall` and exclude them from each poll result. Provable's
455
+ * RecordScanner indexes a transaction's new outputs and its spent inputs
456
+ * separately — outputs typically surface 5–15 s after broadcast, but the
457
+ * spend index for the consumed inputs can lag much longer (we've seen
458
+ * 30 s+). Without local consumed-tracking, polling either (a) waits for
459
+ * the laggy spend index, slowing every round to its slowest piece, or
460
+ * (b) lets a stale consumed record leak into the next round's polling
461
+ * and mis-count it as a fresh output.
462
+ *
463
+ * Per-program shape (record name + input types) comes from
464
+ * `joinShapeForProgram(programId)`. Today that supports `credits.aleo`
465
+ * and the four Sealance-compliant stablecoin program ids; extending
466
+ * to ARC-21 / token_registry would be another entry there.
467
+ */
468
+ joinProgramRecords(programId, records) {
469
+ return __awaiter(this, void 0, void 0, function* () {
470
+ var _a, _b, _c;
471
+ const { recordTypeLiteral, expectedRecordName } = joinShapeForProgram(programId);
472
+ const inputTypes = [recordTypeLiteral, recordTypeLiteral];
473
+ const consumedAcrossCall = new Set();
474
+ let current = records;
475
+ let rounds = 0;
476
+ while (current.length > 1) {
477
+ rounds += 1;
478
+ const pairs = [];
479
+ for (let i = 0; i + 1 < current.length; i += 2) {
480
+ pairs.push([current[i], current[i + 1]]);
481
+ }
482
+ const carry = current.length % 2 === 1 ? [current[current.length - 1]] : [];
483
+ // Mark these plaintexts consumed BEFORE broadcasting so subsequent
484
+ // polls (this round and any future rounds) ignore them no matter
485
+ // how stale the scanner's spend-index is.
486
+ for (const [a, b] of pairs) {
487
+ consumedAcrossCall.add(a.record_plaintext);
488
+ consumedAcrossCall.add(b.record_plaintext);
489
+ }
490
+ const knownPlaintextsBeforeRound = new Set(current.map((r) => r.record_plaintext));
491
+ (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `[joinRecords:${programId}] round ${rounds}: pairs=${pairs.length}, carry=${carry.length}`);
492
+ yield Promise.all(pairs.map((_d) => __awaiter(this, [_d], void 0, function* ([a, b]) {
493
+ var _e, _f;
494
+ const { txId } = yield this.proveTransaction({
495
+ broadcast: true,
496
+ functionName: 'join',
497
+ inputTypes,
498
+ inputs: [a.record_plaintext, b.record_plaintext],
499
+ programId,
500
+ });
501
+ (_f = (_e = this.logger).debug) === null || _f === void 0 ? void 0 : _f.call(_e, `[joinRecords:${programId}] round ${rounds} pair joined: txId=${txId !== null && txId !== void 0 ? txId : '(none)'}`);
502
+ })));
503
+ const next = yield this.waitForJoinRoundOutputs({
504
+ consumedAcrossCall,
505
+ expectedNewCount: pairs.length,
506
+ expectedProgramName: programId,
507
+ expectedRecordName,
508
+ knownPlaintextsBeforeRound,
509
+ });
510
+ current = [...next, ...carry];
511
+ }
512
+ return {
513
+ finalRecordPlaintext: (_c = current[0]) === null || _c === void 0 ? void 0 : _c.record_plaintext,
514
+ rounds,
515
+ };
516
+ });
517
+ }
518
+ /**
519
+ * Polls `listOwnedRecords` until `expectedNewCount` previously-unseen
520
+ * records of the right program/record-name have surfaced. We do NOT
521
+ * wait for the scanner to mark consumed inputs as spent — that index
522
+ * is independently delayed, and waiting on it stalls rounds whose
523
+ * outputs are already visible. `consumedAcrossCall` is the local-spent
524
+ * set tracked in `joinProgramRecords`; we filter against it explicitly.
525
+ */
526
+ waitForJoinRoundOutputs(args) {
527
+ return __awaiter(this, void 0, void 0, function* () {
528
+ var _a, _b;
529
+ const { consumedAcrossCall, expectedNewCount, expectedProgramName, expectedRecordName, knownPlaintextsBeforeRound, } = args;
530
+ const POLL_INTERVAL_MS = 2000;
531
+ const TIMEOUT_MS = 120000;
532
+ const start = Date.now();
533
+ const isMatch = (r) => {
534
+ const rec = r;
535
+ return ((rec === null || rec === void 0 ? void 0 : rec.program_name) === expectedProgramName &&
536
+ (rec === null || rec === void 0 ? void 0 : rec.record_name) === expectedRecordName &&
537
+ typeof (rec === null || rec === void 0 ? void 0 : rec.record_plaintext) === 'string' &&
538
+ rec.record_plaintext.length > 0);
539
+ };
540
+ while (Date.now() - start < TIMEOUT_MS) {
541
+ const { records } = yield this.listOwnedRecords();
542
+ const fresh = records
543
+ .filter(isMatch)
544
+ .filter((r) => !consumedAcrossCall.has(r.record_plaintext))
545
+ .filter((r) => !knownPlaintextsBeforeRound.has(r.record_plaintext));
546
+ if (fresh.length >= expectedNewCount) {
547
+ return fresh;
548
+ }
549
+ (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `[joinRecords:${expectedProgramName}] polling: fresh=${fresh.length}/${expectedNewCount}`);
550
+ yield new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
551
+ }
552
+ throw new DynamicError(`joinRecords timed out waiting for ${expectedProgramName} joined outputs from RecordScanner. The transactions may have broadcast successfully — try calling joinRecords again after the network settles.`);
553
+ });
554
+ }
555
+ /**
556
+ * Returns the IUITransaction the widget's Send flow drives. Implementing
557
+ * this method is the *only* signal the widget needs to render the "Send"
558
+ * button next to "Deposit" — `isSendBalanceWalletConnector` is duck-typed
559
+ * on the presence of `createUiTransaction`.
560
+ *
561
+ * The widget's send view picks one token from the per-network registry
562
+ * (credits + USAD/USDCx always; mainnet additionally exposes 5 ARC-21
563
+ * hyp_warp tokens). At submit time the picked token's `programKind`
564
+ * drives the transition signature:
565
+ *
566
+ * - `credits` → `credits.aleo/transfer_private(_to_public)`,
567
+ * inputs `[record, recipient, amount]` (u64).
568
+ * - `stablecoin` → `<token>.aleo/transfer_private(_to_public)`,
569
+ * inputs `[recipient, amount, record]` (u128). The
570
+ * iframe appends a Sealance freeze-list exclusion
571
+ * proof + its `[MerkleProof; 2u32].private` type
572
+ * automatically.
573
+ * - `arc21` → `token_registry.aleo/transfer_private(_to_public)`,
574
+ * inputs `[recipient, amount, record]` (u128). The
575
+ * token_id is encoded inside the record plaintext;
576
+ * the transition signature does NOT take it as an
577
+ * explicit argument.
578
+ *
579
+ * Submission path: AleoUiTransaction.submit → onSubmit closure below →
580
+ * this.proveTransaction → iframe (Sodot MPC sign + optional Sealance
581
+ * proof injection) → Feemaster (when policy covers it) → Provable DPS
582
+ * (proves + broadcasts) → returns txId.
583
+ */
584
+ createUiTransaction(from) {
585
+ return __awaiter(this, void 0, void 0, function* () {
586
+ var _a;
587
+ yield this.validateActiveWallet(from);
588
+ // Aleo network ids: 0 = mainnet, 1 = testnet. The registry is keyed
589
+ // by these numeric ids; the connector exposes them via
590
+ // `getSelectedNetwork().chainId` (string-or-number depending on env
591
+ // config). Default to testnet when unset — the widget is already
592
+ // gating on a connected wallet so this only matters during the
593
+ // brief window before the network selection has hydrated.
594
+ const networkChainId = (_a = this.getSelectedNetwork()) === null || _a === void 0 ? void 0 : _a.chainId;
595
+ const networkId = typeof networkChainId === 'number'
596
+ ? networkChainId
597
+ : Number(networkChainId !== null && networkChainId !== void 0 ? networkChainId : 1);
598
+ return new AleoUiTransaction({
599
+ from,
600
+ // Merge CTA: scope the join to the selected token. For credits +
601
+ // stablecoins the program id alone is enough. For ARC-21 the
602
+ // program (`token_registry.aleo`) hosts many tokens, so we
603
+ // additionally pass a token_id-aware predicate — without it the
604
+ // pairwise join would try to combine records of different tokens
605
+ // and the on-chain `join` would reject.
606
+ joinAllRecordsForToken: (token) => __awaiter(this, void 0, void 0, function* () {
607
+ this.activeAccountAddress = from;
608
+ const filterRecord = token.programKind === 'arc21' && token.tokenId
609
+ ? (r) => typeof r.record_plaintext === 'string' &&
610
+ r.record_plaintext.includes(`token_id: ${token.tokenId}`)
611
+ : undefined;
612
+ yield this.joinRecords({
613
+ filterRecord,
614
+ programIds: [token.programId],
615
+ });
616
+ }),
617
+ // Surfaces every owned record (across all programs) to the
618
+ // transaction. AleoUiTransaction filters per selected token via
619
+ // `recordMatchesSendableToken`, including the ARC-21 token_id
620
+ // check on the record plaintext.
621
+ listOwnedRecords: () => __awaiter(this, void 0, void 0, function* () {
622
+ this.activeAccountAddress = from;
623
+ const { records } = yield this.listOwnedRecords();
624
+ return records;
625
+ }),
626
+ networkId,
627
+ onSubmit: (_b) => __awaiter(this, [_b], void 0, function* ({ mode, to, value, recordPlaintext, token, }) {
628
+ // The proveTransaction path keys off `this.activeAccountAddress`,
629
+ // which validateActiveWallet has already aligned with `from`.
630
+ // Setting it explicitly keeps the contract obvious to readers.
631
+ this.activeAccountAddress = from;
632
+ const { functionName, inputs, inputTypes } = buildTransferInputs({
633
+ mode,
634
+ recordPlaintext,
635
+ to,
636
+ token,
637
+ value,
638
+ });
639
+ const result = yield this.proveTransaction({
640
+ broadcast: true,
641
+ functionName,
642
+ inputTypes,
643
+ inputs,
644
+ programId: token.programId,
645
+ });
646
+ if (!result.txId) {
647
+ throw new DynamicError('Aleo transfer broadcast did not return a transaction id.');
648
+ }
649
+ return result.txId;
650
+ }),
651
+ });
652
+ });
653
+ }
654
+ /**
655
+ * True when the given token is registered as shieldable on the
656
+ * currently-selected Aleo network. The widget's Unshielded tab uses this
657
+ * to gate the "Shield Manually" CTA — unregistered tokens (e.g. random
658
+ * third-party Aleo programs surfaced by the redcoast public-balance feed)
659
+ * don't get a CTA they couldn't successfully act on.
660
+ *
661
+ * Native ALEO doesn't have a contract address in redcoast's public-balance
662
+ * feed (it surfaces as `isNative: true` with a sentinel like `'0x0'`),
663
+ * so we recognise it via `isNative` and fall back to the registry's
664
+ * `credits.aleo` entry when the literal address misses.
665
+ */
666
+ canShieldToken(token) {
667
+ return Boolean(this.resolveShieldableToken(token));
668
+ }
669
+ /**
670
+ * True when the Feemaster currently sponsors a shield (
671
+ * `transfer_public_to_private`) of the given token. Wraps the generic
672
+ * `isFeemasterSponsored` and looks up the token's `programId` from the
673
+ * shieldable registry. Used by the widget to decide whether the
674
+ * "Shield Manually" CTA can dispatch silently or needs a user-paid fee
675
+ * confirmation modal first.
676
+ */
677
+ isShieldSponsored(token) {
678
+ return __awaiter(this, void 0, void 0, function* () {
679
+ const resolved = this.resolveShieldableToken(token);
680
+ if (!resolved)
681
+ return false;
682
+ return this.isFeemasterSponsored({
683
+ functionName: 'transfer_public_to_private',
684
+ programId: resolved.programId,
685
+ });
686
+ });
687
+ }
688
+ /**
689
+ * True when ANF's Feemaster policy currently sponsors `(programId,
690
+ * functionName)` on the connector-selected network. Used by the widget
691
+ * to decide whether to show a user-paid confirmation modal before a
692
+ * shield/send/join. Generic — applies to any (program, function) pair,
693
+ * not shield-specific. Never throws — returns `false` on any failure so
694
+ * callers default to "show modal" rather than silently dispatching.
695
+ */
696
+ isFeemasterSponsored(args) {
697
+ return __awaiter(this, void 0, void 0, function* () {
698
+ var _a;
699
+ const walletClient = yield this.getWaasWalletClient();
700
+ if (!walletClient)
701
+ return false;
702
+ const chainId = (_a = this.getSelectedNetwork()) === null || _a === void 0 ? void 0 : _a.chainId;
703
+ try {
704
+ return yield walletClient.isAleoFeemasterCovered({
705
+ chainId,
706
+ functionName: args.functionName,
707
+ programId: args.programId,
708
+ });
709
+ }
710
+ catch (_b) {
711
+ return false;
712
+ }
713
+ });
714
+ }
715
+ resolveShieldableToken(token) {
716
+ var _a;
717
+ const networkChainId = (_a = this.getSelectedNetwork()) === null || _a === void 0 ? void 0 : _a.chainId;
718
+ const networkId = typeof networkChainId === 'number'
719
+ ? networkChainId
720
+ : Number(networkChainId !== null && networkChainId !== void 0 ? networkChainId : 1);
721
+ if (token.address) {
722
+ const direct = findAleoShieldableToken(networkId, token.address);
723
+ if (direct)
724
+ return direct;
725
+ }
726
+ if (token.isNative) {
727
+ return findAleoShieldableToken(networkId, 'credits.aleo');
728
+ }
729
+ return undefined;
730
+ }
731
+ /**
732
+ * Shield (`transfer_public_to_private`) `amount` of the given token from
733
+ * the wallet's public balance into a fresh private record owned by self.
734
+ * Drives the widget's "Shield Manually" CTA on the Unshielded tab.
735
+ *
736
+ * `amount` is in the token's atomic units (microcredits for credits;
737
+ * `10^decimals` for stablecoin / ARC-21). The connector resolves the
738
+ * shield-shape registry by `tokenAddress` (matching the unshielded
739
+ * `TokenBalance.address` redcoast surfaces). Recipient is always self.
740
+ *
741
+ * Throws `DynamicError` if the token has no shield shape registered for
742
+ * the current network — those tokens shouldn't have rendered the CTA in
743
+ * the first place, but the throw is a safety net against a stale UI
744
+ * state slipping a doomed transaction past us.
745
+ */
746
+ shieldToken(args) {
747
+ return __awaiter(this, void 0, void 0, function* () {
748
+ this.ensureActiveAccountFromVerifiedCredentials();
749
+ if (!this.activeAccountAddress) {
750
+ throw new DynamicError('Active account address is required');
751
+ }
752
+ if (args.amount <= BigInt(0)) {
753
+ throw new DynamicError('Shield amount must be > 0.');
754
+ }
755
+ const token = this.resolveShieldableToken({
756
+ address: args.tokenAddress,
757
+ isNative: args.isNative,
758
+ });
759
+ if (!token) {
760
+ throw new DynamicError(`shieldToken: ${args.tokenAddress} is not a registered shieldable Aleo token on the current network.`);
761
+ }
762
+ const { functionName, inputs, inputTypes } = buildShieldInputs({
763
+ to: this.activeAccountAddress,
764
+ token,
765
+ value: args.amount,
766
+ });
767
+ const result = yield this.proveTransaction({
768
+ broadcast: true,
769
+ functionName,
770
+ inputTypes,
771
+ inputs,
772
+ programId: token.programId,
773
+ });
774
+ if (!result.txId) {
775
+ throw new DynamicError('Aleo shield broadcast did not return a transaction id.');
776
+ }
777
+ return result.txId;
778
+ });
779
+ }
780
+ endSession(reason) {
781
+ const _super = Object.create(null, {
782
+ endSession: { get: () => super.endSession }
783
+ });
784
+ return __awaiter(this, void 0, void 0, function* () {
785
+ yield _super.endSession.call(this, reason);
786
+ this.activeAccountAddress = undefined;
787
+ });
788
+ }
789
+ getProvider() {
790
+ return undefined;
791
+ }
792
+ }
793
+
794
+ export { DynamicWaasAleoConnector };