@portal-hq/web 3.16.0 → 3.17.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.
@@ -3764,4 +3764,138 @@ describe('Mpc', () => {
3764
3764
  });
3765
3765
  });
3766
3766
  });
3767
+ describe('accountAbstractionBuildBatchedUserOp', () => {
3768
+ const args = constants_1.mockBuildBatchedUserOpRequest;
3769
+ const res = constants_1.mockBuildBatchedUserOpResponse;
3770
+ it('should successfully build a user operation', (done) => {
3771
+ var _a;
3772
+ jest
3773
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
3774
+ .mockImplementation((message, origin) => {
3775
+ const { type, data } = message;
3776
+ expect(type).toEqual('portal:accountAbstraction:buildBatchedUserOp');
3777
+ expect(data).toMatchObject(args);
3778
+ expect(typeof message.traceId).toBe('string');
3779
+ expect(origin).toEqual(mockHostOrigin);
3780
+ window.dispatchEvent(new MessageEvent('message', {
3781
+ origin: mockHostOrigin,
3782
+ data: {
3783
+ type: 'portal:accountAbstraction:buildBatchedUserOpResult',
3784
+ data: res,
3785
+ },
3786
+ }));
3787
+ });
3788
+ mpc
3789
+ .accountAbstractionBuildBatchedUserOp(args)
3790
+ .then((data) => {
3791
+ expect(data).toEqual(res);
3792
+ done();
3793
+ })
3794
+ .catch((_) => {
3795
+ expect(0).toEqual(1);
3796
+ done();
3797
+ });
3798
+ });
3799
+ it('should error out if the iframe sends an error message', (done) => {
3800
+ var _a;
3801
+ jest
3802
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
3803
+ .mockImplementationOnce((message, origin) => {
3804
+ const { type, data } = message;
3805
+ expect(type).toEqual('portal:accountAbstraction:buildBatchedUserOp');
3806
+ expect(data).toMatchObject(args);
3807
+ expect(typeof message.traceId).toBe('string');
3808
+ expect(origin).toEqual(mockHostOrigin);
3809
+ window.dispatchEvent(new MessageEvent('message', {
3810
+ origin: mockHostOrigin,
3811
+ data: {
3812
+ type: 'portal:accountAbstraction:buildBatchedUserOpError',
3813
+ data: {
3814
+ code: 1,
3815
+ message: 'test',
3816
+ },
3817
+ },
3818
+ }));
3819
+ });
3820
+ mpc
3821
+ .accountAbstractionBuildBatchedUserOp(args)
3822
+ .then(() => {
3823
+ expect(0).toEqual(1);
3824
+ done();
3825
+ })
3826
+ .catch((e) => {
3827
+ expect(e).toBeInstanceOf(errors_1.PortalMpcError);
3828
+ expect(e.message).toEqual('test');
3829
+ expect(e.code).toEqual(1);
3830
+ done();
3831
+ });
3832
+ });
3833
+ });
3834
+ describe('accountAbstractionBroadcastBatchedUserOp', () => {
3835
+ const args = constants_1.mockBroadcastBatchedUserOpRequest;
3836
+ const res = constants_1.mockBroadcastBatchedUserOpResponse;
3837
+ it('should successfully broadcast a user operation', (done) => {
3838
+ var _a;
3839
+ jest
3840
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
3841
+ .mockImplementation((message, origin) => {
3842
+ const { type, data } = message;
3843
+ expect(type).toEqual('portal:accountAbstraction:broadcastBatchedUserOp');
3844
+ expect(data).toMatchObject(args);
3845
+ expect(typeof message.traceId).toBe('string');
3846
+ expect(origin).toEqual(mockHostOrigin);
3847
+ window.dispatchEvent(new MessageEvent('message', {
3848
+ origin: mockHostOrigin,
3849
+ data: {
3850
+ type: 'portal:accountAbstraction:broadcastBatchedUserOpResult',
3851
+ data: res,
3852
+ },
3853
+ }));
3854
+ });
3855
+ mpc
3856
+ .accountAbstractionBroadcastBatchedUserOp(args)
3857
+ .then((data) => {
3858
+ expect(data).toEqual(res);
3859
+ done();
3860
+ })
3861
+ .catch((_) => {
3862
+ expect(0).toEqual(1);
3863
+ done();
3864
+ });
3865
+ });
3866
+ it('should error out if the iframe sends an error message', (done) => {
3867
+ var _a;
3868
+ jest
3869
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
3870
+ .mockImplementationOnce((message, origin) => {
3871
+ const { type, data } = message;
3872
+ expect(type).toEqual('portal:accountAbstraction:broadcastBatchedUserOp');
3873
+ expect(data).toMatchObject(args);
3874
+ expect(typeof message.traceId).toBe('string');
3875
+ expect(origin).toEqual(mockHostOrigin);
3876
+ window.dispatchEvent(new MessageEvent('message', {
3877
+ origin: mockHostOrigin,
3878
+ data: {
3879
+ type: 'portal:accountAbstraction:broadcastBatchedUserOpError',
3880
+ data: {
3881
+ code: 1,
3882
+ message: 'test',
3883
+ },
3884
+ },
3885
+ }));
3886
+ });
3887
+ mpc
3888
+ .accountAbstractionBroadcastBatchedUserOp(args)
3889
+ .then(() => {
3890
+ expect(0).toEqual(1);
3891
+ done();
3892
+ })
3893
+ .catch((e) => {
3894
+ expect(e).toBeInstanceOf(errors_1.PortalMpcError);
3895
+ expect(e.message).toEqual('test');
3896
+ expect(e.code).toEqual(1);
3897
+ done();
3898
+ });
3899
+ });
3900
+ });
3767
3901
  });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -32,3 +32,5 @@ __exportStar(require("./lifi"), exports);
32
32
  __exportStar(require("./noah"), exports);
33
33
  __exportStar(require("./hypernative"), exports);
34
34
  __exportStar(require("./blockaid"), exports);
35
+ // Account Abstraction types
36
+ __exportStar(require("./accountAbstraction"), exports);
package/lib/esm/index.js CHANGED
@@ -967,6 +967,300 @@ class Portal {
967
967
  return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.buildTransaction(chainId, to, token, amount, traceId);
968
968
  });
969
969
  }
970
+ /*******************************
971
+ * Account Abstraction Methods
972
+ *******************************/
973
+ buildBatchedUserOp(data) {
974
+ var _a;
975
+ return __awaiter(this, void 0, void 0, function* () {
976
+ return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBuildBatchedUserOp(data);
977
+ });
978
+ }
979
+ broadcastBatchedUserOp(data) {
980
+ var _a;
981
+ return __awaiter(this, void 0, void 0, function* () {
982
+ return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBroadcastBatchedUserOp(data);
983
+ });
984
+ }
985
+ /**
986
+ * Build, sign, and broadcast a batch of EIP-155 UserOperations (ERC-4337).
987
+ *
988
+ * Returns the broadcast response containing `userOpHash`. Broadcasting does not
989
+ * guarantee on-chain inclusion — UserOps are processed by a bundler that may
990
+ * delay or drop them. To wait for confirmation call:
991
+ * `await portal.waitForConfirmation(result.data.userOpHash, data.chain)`
992
+ *
993
+ * @param data.chain - CAIP-2 chain ID; must start with 'eip155:'
994
+ * @param data.transactions - Ordered list of transfers to batch
995
+ * @param data.signatureApprovalMemo - Optional memo for the signing approval prompt
996
+ * @param data.traceId - Optional trace ID for request correlation
997
+ */
998
+ sendBatchUserOp(data) {
999
+ var _a, _b;
1000
+ return __awaiter(this, void 0, void 0, function* () {
1001
+ if (!data.chain.startsWith('eip155:')) {
1002
+ throw new Error('[Portal.sendBatchUserOp] UserOperations are only supported on EIP-155 (EVM) chains');
1003
+ }
1004
+ if (!data.transactions || data.transactions.length === 0) {
1005
+ throw new Error('[Portal.sendBatchUserOp] transactions must contain at least one transaction');
1006
+ }
1007
+ // Validate every destination address before starting any async work.
1008
+ for (const tx of data.transactions) {
1009
+ if (!tx.to || !/^0x[0-9a-fA-F]{40}$/i.test(tx.to)) {
1010
+ throw new Error(`[Portal.sendBatchUserOp] Invalid 'to' address "${String(tx.to)}": must be a 42-character EVM address (0x + 40 hex chars)`);
1011
+ }
1012
+ }
1013
+ const traceId = (_a = data.traceId) !== null && _a !== void 0 ? _a : generateTraceId();
1014
+ // Build each transaction sequentially. The iframe serializes all postMessages
1015
+ // via _portalMessageQueue, so Promise.all would only create the illusion of
1016
+ // parallelism while hiding failures from all-but-the-first transaction.
1017
+ const calls = [];
1018
+ for (let index = 0; index < data.transactions.length; index++) {
1019
+ calls.push(yield this.buildUserOpCall(data.chain, data.transactions[index], traceId, index, 'Portal.sendBatchUserOp'));
1020
+ }
1021
+ // Build the batched UserOperation.
1022
+ let buildResponse;
1023
+ try {
1024
+ buildResponse = yield ((_b = this.mpc) === null || _b === void 0 ? void 0 : _b.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls }, traceId));
1025
+ }
1026
+ catch (error) {
1027
+ throw new Error(`[Portal.sendBatchUserOp] Failed to build UserOperation: ${error instanceof Error ? error.message : String(error)}`);
1028
+ }
1029
+ // Validate, sign, and broadcast the built UserOperation, reusing the same
1030
+ // traceId for end-to-end correlation.
1031
+ return this.signAndBroadcastBuiltUserOp(buildResponse, data.chain, data.signatureApprovalMemo, traceId, 'Portal.sendBatchUserOp');
1032
+ });
1033
+ }
1034
+ /**
1035
+ * Build, sign, and broadcast a gas-subsidized batch where the end user
1036
+ * reimburses the platform — in a fee token (e.g. USDC) — for the gas Portal's
1037
+ * paymaster sponsored.
1038
+ *
1039
+ * The flow is two-pass because the reimbursement amount depends on the gas of
1040
+ * the batch it's part of (a chicken-and-egg the helper resolves for you):
1041
+ * 1. build `[...userCalls, feeCallPlaceholder]` to read the estimated gas cost
1042
+ * (`metadata.estimatedGasCostWei`; the placeholder ensures the estimate
1043
+ * reflects the FINAL batch shape)
1044
+ * 2. convert that native gas cost → fee-token amount via your `convertGasToFeeAmount`
1045
+ * 3. build `[...userCalls, feeCall]` with the real amount, sign, broadcast
1046
+ *
1047
+ * Important characteristics:
1048
+ * - You are charging the build-time gas estimate (an upper bound), not the
1049
+ * post-execution actual — actual gas is unknowable before the op runs, and an
1050
+ * atomic batch must fix the reimbursement amount at sign time. Use `bufferBps`
1051
+ * to absorb gas-price drift between the two builds.
1052
+ * - Conversion (native → fee token) is entirely yours; Portal does no FX.
1053
+ * - Throws if the estimated gas cost is 0. Some chains/providers carry no
1054
+ * on-chain fee on the user operation (e.g. Ultra Relay bundler-level
1055
+ * sponsorship on Monad mainnet), so there's nothing to reimburse from; those
1056
+ * chains need a gas price sourced another way.
1057
+ *
1058
+ * @param data.chain - CAIP-2 chain ID; must start with 'eip155:'
1059
+ * @param data.transactions - The end user's actual transfers to execute
1060
+ * @param data.gasReimbursement - Fee token, recipient, and conversion callback
1061
+ * @param data.signatureApprovalMemo - Optional memo for the signing approval prompt
1062
+ * @param data.traceId - Optional trace ID for request correlation
1063
+ */
1064
+ sendBatchedAssets(data) {
1065
+ var _a, _b, _c, _d;
1066
+ return __awaiter(this, void 0, void 0, function* () {
1067
+ const ctx = 'Portal.sendBatchedAssets';
1068
+ if (!data.chain.startsWith('eip155:')) {
1069
+ throw new Error(`[${ctx}] UserOperations are only supported on EIP-155 (EVM) chains`);
1070
+ }
1071
+ if (!data.transactions || data.transactions.length === 0) {
1072
+ throw new Error(`[${ctx}] transactions must contain at least one transaction`);
1073
+ }
1074
+ const gr = data.gasReimbursement;
1075
+ if (!gr || typeof gr.convertGasToFeeAmount !== 'function') {
1076
+ throw new Error(`[${ctx}] gasReimbursement.convertGasToFeeAmount (a function) is required`);
1077
+ }
1078
+ if (!gr.feeToken) {
1079
+ throw new Error(`[${ctx}] gasReimbursement.feeToken is required`);
1080
+ }
1081
+ // Validate every destination address before starting any async work.
1082
+ const evmAddress = /^0x[0-9a-fA-F]{40}$/i;
1083
+ for (const tx of data.transactions) {
1084
+ if (!tx.to || !evmAddress.test(tx.to)) {
1085
+ throw new Error(`[${ctx}] Invalid 'to' address "${String(tx.to)}": must be a 42-character EVM address (0x + 40 hex chars)`);
1086
+ }
1087
+ }
1088
+ if (!gr.feeRecipient || !evmAddress.test(gr.feeRecipient)) {
1089
+ throw new Error(`[${ctx}] Invalid gasReimbursement.feeRecipient "${String(gr.feeRecipient)}": must be a 42-character EVM address (0x + 40 hex chars)`);
1090
+ }
1091
+ const traceId = (_a = data.traceId) !== null && _a !== void 0 ? _a : generateTraceId();
1092
+ // 1. Build the user's actual calls once — they don't change between passes.
1093
+ const userCalls = [];
1094
+ for (let index = 0; index < data.transactions.length; index++) {
1095
+ userCalls.push(yield this.buildUserOpCall(data.chain, data.transactions[index], traceId, index, ctx));
1096
+ }
1097
+ // 2. Build a placeholder fee call so the estimation pass reflects the final
1098
+ // batch shape (an N+1-call executeBatch). The amount does not affect the
1099
+ // gas estimate; only the call's presence and shape do.
1100
+ const feeIndex = data.transactions.length;
1101
+ const placeholderAmount = (_b = gr.placeholderAmount) !== null && _b !== void 0 ? _b : '0.01';
1102
+ let placeholderFeeCall;
1103
+ try {
1104
+ placeholderFeeCall = yield this.buildUserOpCall(data.chain, { token: gr.feeToken, value: placeholderAmount, to: gr.feeRecipient }, traceId, feeIndex, ctx);
1105
+ }
1106
+ catch (error) {
1107
+ throw new Error(`[${ctx}] Failed to build placeholder fee call: ${error instanceof Error ? error.message : String(error)}`);
1108
+ }
1109
+ // 3. Estimation pass — build the full batch to read the gas it's bounded by.
1110
+ let estimateResponse;
1111
+ try {
1112
+ estimateResponse = yield ((_c = this.mpc) === null || _c === void 0 ? void 0 : _c.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls: [...userCalls, placeholderFeeCall] }, traceId));
1113
+ }
1114
+ catch (error) {
1115
+ throw new Error(`[${ctx}] Failed to estimate UserOperation gas: ${error instanceof Error ? error.message : String(error)}`);
1116
+ }
1117
+ let gasCostWei = this.resolveUserOpGasCostWei(estimateResponse, ctx);
1118
+ // Guard against a zero gas cost. Some chains/providers return no on-chain fee
1119
+ // on the user operation (e.g. Ultra Relay bundler-level sponsorship on Monad
1120
+ // mainnet), so the build-time cost is 0 — there's nothing to derive a
1121
+ // reimbursement from. Fail loudly rather than silently charging the user 0.
1122
+ if (gasCostWei === BigInt(0)) {
1123
+ throw new Error(`[${ctx}] Estimated gas cost is 0 — this chain/provider carries no on-chain fee on the user operation (e.g. Ultra Relay bundler-level sponsorship). Cannot derive a reimbursement amount; supply the gas price another way for this chain.`);
1124
+ }
1125
+ // Apply optional buffer (basis points) to absorb gas-price drift before FX.
1126
+ if (gr.bufferBps && gr.bufferBps > 0) {
1127
+ gasCostWei =
1128
+ (gasCostWei * BigInt(10000 + Math.floor(gr.bufferBps))) / BigInt(10000);
1129
+ }
1130
+ // 4. Platform-owned conversion: native gas cost (wei) → fee-token amount.
1131
+ let feeAmount;
1132
+ try {
1133
+ feeAmount = yield gr.convertGasToFeeAmount(gasCostWei);
1134
+ }
1135
+ catch (error) {
1136
+ throw new Error(`[${ctx}] gasReimbursement.convertGasToFeeAmount threw: ${error instanceof Error ? error.message : String(error)}`);
1137
+ }
1138
+ if (typeof feeAmount !== 'string' || feeAmount.length === 0) {
1139
+ throw new Error(`[${ctx}] convertGasToFeeAmount must return a non-empty amount string, got "${String(feeAmount)}"`);
1140
+ }
1141
+ // 5. Build the real fee call with the converted amount.
1142
+ let feeCall;
1143
+ try {
1144
+ feeCall = yield this.buildUserOpCall(data.chain, { token: gr.feeToken, value: feeAmount, to: gr.feeRecipient }, traceId, feeIndex, ctx);
1145
+ }
1146
+ catch (error) {
1147
+ throw new Error(`[${ctx}] Failed to build fee reimbursement call: ${error instanceof Error ? error.message : String(error)}`);
1148
+ }
1149
+ // 6. Final pass — build the batch we'll actually sign and broadcast.
1150
+ let buildResponse;
1151
+ try {
1152
+ buildResponse = yield ((_d = this.mpc) === null || _d === void 0 ? void 0 : _d.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls: [...userCalls, feeCall] }, traceId));
1153
+ }
1154
+ catch (error) {
1155
+ throw new Error(`[${ctx}] Failed to build UserOperation: ${error instanceof Error ? error.message : String(error)}`);
1156
+ }
1157
+ return this.signAndBroadcastBuiltUserOp(buildResponse, data.chain, data.signatureApprovalMemo, traceId, ctx);
1158
+ });
1159
+ }
1160
+ /**
1161
+ * Build a single ERC-4337 call from a high-level transfer descriptor by routing
1162
+ * it through `buildTransaction` (which resolves token contract + calldata and
1163
+ * normalizes the amount). Native transfers carry `value` (the base-unit amount);
1164
+ * ERC-20 transfers carry `data` and omit `value`.
1165
+ */
1166
+ buildUserOpCall(chain, tx, traceId, index, ctx) {
1167
+ var _a;
1168
+ return __awaiter(this, void 0, void 0, function* () {
1169
+ let builtTx;
1170
+ try {
1171
+ builtTx = yield this.buildTransaction(chain, tx.to, tx.token, tx.value, traceId);
1172
+ }
1173
+ catch (error) {
1174
+ throw new Error(`[${ctx}] Failed to build call for transaction at index ${index}: ${error instanceof Error ? error.message : String(error)}`);
1175
+ }
1176
+ // Validate the built transaction shape before casting. buildTransaction
1177
+ // returns BuiltTransaction (a union type), so the cast is TypeScript-only and
1178
+ // provides no runtime safety against a changed or unexpected backend response.
1179
+ const rawTx = builtTx;
1180
+ if (typeof ((_a = rawTx === null || rawTx === void 0 ? void 0 : rawTx.transaction) === null || _a === void 0 ? void 0 : _a.to) !== 'string' || !rawTx.transaction.to) {
1181
+ throw new Error(`[${ctx}] buildTransaction returned unexpected shape for transaction at index ${index}`);
1182
+ }
1183
+ const eip155Tx = builtTx;
1184
+ // Normalize the data field — guard against undefined/null/empty string from
1185
+ // the backend. Empty data means no calldata (native transfer). rawAmount is
1186
+ // passed as-is (decimal string e.g. '1000000000000000000'); the AA backend's
1187
+ // build-user-operation endpoint accepts decimal or hex.
1188
+ const txData = eip155Tx.transaction.data || '0x';
1189
+ const isNativeTransfer = txData === '0x';
1190
+ return Object.assign({ to: eip155Tx.transaction.to, data: txData }, (isNativeTransfer ? { value: eip155Tx.metadata.rawAmount } : {}));
1191
+ });
1192
+ }
1193
+ /**
1194
+ * Resolve the native gas cost (in wei) a built UserOperation is bounded by,
1195
+ * from the backend-computed `metadata.estimatedGasCostWei` (`totalGas *
1196
+ * maxFeePerGas`, an upper bound). Throws if the field is absent — the
1197
+ * connect-api build-user-operation gas-cost change must be deployed.
1198
+ */
1199
+ resolveUserOpGasCostWei(buildResponse, ctx) {
1200
+ var _a;
1201
+ const wei = (_a = buildResponse === null || buildResponse === void 0 ? void 0 : buildResponse.metadata) === null || _a === void 0 ? void 0 : _a.estimatedGasCostWei;
1202
+ if (wei == null || `${wei}`.length === 0) {
1203
+ throw new Error(`[${ctx}] build response is missing metadata.estimatedGasCostWei; the connect-api build-user-operation gas-cost change must be deployed for the target environment`);
1204
+ }
1205
+ return this.toBigIntOrThrow(wei, 'metadata.estimatedGasCostWei', ctx);
1206
+ }
1207
+ toBigIntOrThrow(value, field, ctx) {
1208
+ try {
1209
+ // BigInt() accepts both decimal ('1000000000') and hex ('0x3b9aca00') strings.
1210
+ return BigInt(value);
1211
+ }
1212
+ catch (_a) {
1213
+ throw new Error(`[${ctx}] Could not parse ${field} as an integer: "${String(value)}"`);
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Validate a built UserOperation, sign its hash with the SECP256K1 raw signer,
1218
+ * and broadcast it. Shared by the batched-UserOp send paths.
1219
+ */
1220
+ signAndBroadcastBuiltUserOp(buildResponse, chain, signatureApprovalMemo, traceId, ctx) {
1221
+ var _a;
1222
+ return __awaiter(this, void 0, void 0, function* () {
1223
+ const { userOperation, userOpHash } = buildResponse.data;
1224
+ // Validate userOperation is parseable JSON before signing or broadcasting.
1225
+ // A malformed string from the backend would produce an opaque bundler rejection.
1226
+ try {
1227
+ const parsed = JSON.parse(userOperation);
1228
+ if (!parsed || typeof parsed !== 'object') {
1229
+ throw new Error('parsed value is not a JSON object');
1230
+ }
1231
+ }
1232
+ catch (e) {
1233
+ throw new Error(`[${ctx}] buildBatchedUserOp returned an invalid userOperation: ${e instanceof Error ? e.message : String(e)}`);
1234
+ }
1235
+ // Validate userOpHash is a valid 32-byte hex string before signing.
1236
+ // Signing an empty or malformed hash wastes the signing operation and produces
1237
+ // a signature that no bundler will accept.
1238
+ if (!userOpHash || !/^(0x)?[0-9a-fA-F]{64}$/.test(userOpHash)) {
1239
+ throw new Error(`[${ctx}] Invalid userOpHash received from buildBatchedUserOp: "${String(userOpHash)}"`);
1240
+ }
1241
+ // Sign the userOpHash — strip the 0x prefix before passing to rawSign.
1242
+ let signature;
1243
+ try {
1244
+ const hashToSign = userOpHash.startsWith('0x')
1245
+ ? userOpHash.slice(2)
1246
+ : userOpHash;
1247
+ signature = yield this.rawSign(PortalCurve.SECP256K1, hashToSign, {
1248
+ signatureApprovalMemo,
1249
+ traceId,
1250
+ });
1251
+ }
1252
+ catch (error) {
1253
+ throw new Error(`[${ctx}] Failed to sign userOpHash: ${error instanceof Error ? error.message : String(error)}`);
1254
+ }
1255
+ // Broadcast the signed UserOperation, reusing the same traceId for end-to-end correlation.
1256
+ try {
1257
+ return yield ((_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBroadcastBatchedUserOp({ chain, userOperation, signature }, traceId));
1258
+ }
1259
+ catch (error) {
1260
+ throw new Error(`[${ctx}] Failed to broadcast UserOperation: ${error instanceof Error ? error.message : String(error)}`);
1261
+ }
1262
+ });
1263
+ }
970
1264
  /*******************************
971
1265
  * Swaps Methods
972
1266
  *******************************/