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