@portal-hq/web 3.16.0-alpha.1 → 3.17.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +294 -0
- package/lib/commonjs/index.test.js +231 -0
- package/lib/commonjs/mpc/index.js +27 -1
- package/lib/commonjs/mpc/index.test.js +134 -0
- package/lib/commonjs/shared/types/accountAbstraction.js +2 -0
- package/lib/commonjs/shared/types/index.js +2 -0
- package/lib/esm/index.js +294 -0
- package/lib/esm/index.test.js +233 -2
- package/lib/esm/mpc/index.js +27 -1
- package/lib/esm/mpc/index.test.js +135 -1
- package/lib/esm/shared/types/accountAbstraction.js +1 -0
- package/lib/esm/shared/types/index.js +2 -0
- package/package.json +2 -2
- package/src/__mocks/constants.ts +93 -0
- package/src/__mocks/portal/mpc.ts +11 -0
- package/src/index.test.ts +473 -1
- package/src/index.ts +454 -0
- package/src/mpc/index.test.ts +164 -0
- package/src/mpc/index.ts +36 -1
- package/src/shared/types/accountAbstraction.ts +122 -0
- package/src/shared/types/common.ts +3 -0
- package/src/shared/types/index.ts +3 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -996,6 +996,300 @@ class Portal {
|
|
|
996
996
|
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.buildTransaction(chainId, to, token, amount, traceId);
|
|
997
997
|
});
|
|
998
998
|
}
|
|
999
|
+
/*******************************
|
|
1000
|
+
* Account Abstraction Methods
|
|
1001
|
+
*******************************/
|
|
1002
|
+
buildBatchedUserOp(data) {
|
|
1003
|
+
var _a;
|
|
1004
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1005
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBuildBatchedUserOp(data);
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
broadcastBatchedUserOp(data) {
|
|
1009
|
+
var _a;
|
|
1010
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1011
|
+
return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBroadcastBatchedUserOp(data);
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Build, sign, and broadcast a batch of EIP-155 UserOperations (ERC-4337).
|
|
1016
|
+
*
|
|
1017
|
+
* Returns the broadcast response containing `userOpHash`. Broadcasting does not
|
|
1018
|
+
* guarantee on-chain inclusion — UserOps are processed by a bundler that may
|
|
1019
|
+
* delay or drop them. To wait for confirmation call:
|
|
1020
|
+
* `await portal.waitForConfirmation(result.data.userOpHash, data.chain)`
|
|
1021
|
+
*
|
|
1022
|
+
* @param data.chain - CAIP-2 chain ID; must start with 'eip155:'
|
|
1023
|
+
* @param data.transactions - Ordered list of transfers to batch
|
|
1024
|
+
* @param data.signatureApprovalMemo - Optional memo for the signing approval prompt
|
|
1025
|
+
* @param data.traceId - Optional trace ID for request correlation
|
|
1026
|
+
*/
|
|
1027
|
+
sendBatchUserOp(data) {
|
|
1028
|
+
var _a, _b;
|
|
1029
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1030
|
+
if (!data.chain.startsWith('eip155:')) {
|
|
1031
|
+
throw new Error('[Portal.sendBatchUserOp] UserOperations are only supported on EIP-155 (EVM) chains');
|
|
1032
|
+
}
|
|
1033
|
+
if (!data.transactions || data.transactions.length === 0) {
|
|
1034
|
+
throw new Error('[Portal.sendBatchUserOp] transactions must contain at least one transaction');
|
|
1035
|
+
}
|
|
1036
|
+
// Validate every destination address before starting any async work.
|
|
1037
|
+
for (const tx of data.transactions) {
|
|
1038
|
+
if (!tx.to || !/^0x[0-9a-fA-F]{40}$/i.test(tx.to)) {
|
|
1039
|
+
throw new Error(`[Portal.sendBatchUserOp] Invalid 'to' address "${String(tx.to)}": must be a 42-character EVM address (0x + 40 hex chars)`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const traceId = (_a = data.traceId) !== null && _a !== void 0 ? _a : (0, trace_1.generateTraceId)();
|
|
1043
|
+
// Build each transaction sequentially. The iframe serializes all postMessages
|
|
1044
|
+
// via _portalMessageQueue, so Promise.all would only create the illusion of
|
|
1045
|
+
// parallelism while hiding failures from all-but-the-first transaction.
|
|
1046
|
+
const calls = [];
|
|
1047
|
+
for (let index = 0; index < data.transactions.length; index++) {
|
|
1048
|
+
calls.push(yield this.buildUserOpCall(data.chain, data.transactions[index], traceId, index, 'Portal.sendBatchUserOp'));
|
|
1049
|
+
}
|
|
1050
|
+
// Build the batched UserOperation.
|
|
1051
|
+
let buildResponse;
|
|
1052
|
+
try {
|
|
1053
|
+
buildResponse = yield ((_b = this.mpc) === null || _b === void 0 ? void 0 : _b.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls }, traceId));
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
throw new Error(`[Portal.sendBatchUserOp] Failed to build UserOperation: ${error instanceof Error ? error.message : String(error)}`);
|
|
1057
|
+
}
|
|
1058
|
+
// Validate, sign, and broadcast the built UserOperation, reusing the same
|
|
1059
|
+
// traceId for end-to-end correlation.
|
|
1060
|
+
return this.signAndBroadcastBuiltUserOp(buildResponse, data.chain, data.signatureApprovalMemo, traceId, 'Portal.sendBatchUserOp');
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Build, sign, and broadcast a gas-subsidized batch where the end user
|
|
1065
|
+
* reimburses the platform — in a fee token (e.g. USDC) — for the gas Portal's
|
|
1066
|
+
* paymaster sponsored.
|
|
1067
|
+
*
|
|
1068
|
+
* The flow is two-pass because the reimbursement amount depends on the gas of
|
|
1069
|
+
* the batch it's part of (a chicken-and-egg the helper resolves for you):
|
|
1070
|
+
* 1. build `[...userCalls, feeCallPlaceholder]` to read the estimated gas cost
|
|
1071
|
+
* (`metadata.estimatedGasCostWei`; the placeholder ensures the estimate
|
|
1072
|
+
* reflects the FINAL batch shape)
|
|
1073
|
+
* 2. convert that native gas cost → fee-token amount via your `convertGasToFeeAmount`
|
|
1074
|
+
* 3. build `[...userCalls, feeCall]` with the real amount, sign, broadcast
|
|
1075
|
+
*
|
|
1076
|
+
* Important characteristics:
|
|
1077
|
+
* - You are charging the build-time gas estimate (an upper bound), not the
|
|
1078
|
+
* post-execution actual — actual gas is unknowable before the op runs, and an
|
|
1079
|
+
* atomic batch must fix the reimbursement amount at sign time. Use `bufferBps`
|
|
1080
|
+
* to absorb gas-price drift between the two builds.
|
|
1081
|
+
* - Conversion (native → fee token) is entirely yours; Portal does no FX.
|
|
1082
|
+
* - Throws if the estimated gas cost is 0. Some chains/providers carry no
|
|
1083
|
+
* on-chain fee on the user operation (e.g. Ultra Relay bundler-level
|
|
1084
|
+
* sponsorship on Monad mainnet), so there's nothing to reimburse from; those
|
|
1085
|
+
* chains need a gas price sourced another way.
|
|
1086
|
+
*
|
|
1087
|
+
* @param data.chain - CAIP-2 chain ID; must start with 'eip155:'
|
|
1088
|
+
* @param data.transactions - The end user's actual transfers to execute
|
|
1089
|
+
* @param data.gasReimbursement - Fee token, recipient, and conversion callback
|
|
1090
|
+
* @param data.signatureApprovalMemo - Optional memo for the signing approval prompt
|
|
1091
|
+
* @param data.traceId - Optional trace ID for request correlation
|
|
1092
|
+
*/
|
|
1093
|
+
sendBatchedAssets(data) {
|
|
1094
|
+
var _a, _b, _c, _d;
|
|
1095
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1096
|
+
const ctx = 'Portal.sendBatchedAssets';
|
|
1097
|
+
if (!data.chain.startsWith('eip155:')) {
|
|
1098
|
+
throw new Error(`[${ctx}] UserOperations are only supported on EIP-155 (EVM) chains`);
|
|
1099
|
+
}
|
|
1100
|
+
if (!data.transactions || data.transactions.length === 0) {
|
|
1101
|
+
throw new Error(`[${ctx}] transactions must contain at least one transaction`);
|
|
1102
|
+
}
|
|
1103
|
+
const gr = data.gasReimbursement;
|
|
1104
|
+
if (!gr || typeof gr.convertGasToFeeAmount !== 'function') {
|
|
1105
|
+
throw new Error(`[${ctx}] gasReimbursement.convertGasToFeeAmount (a function) is required`);
|
|
1106
|
+
}
|
|
1107
|
+
if (!gr.feeToken) {
|
|
1108
|
+
throw new Error(`[${ctx}] gasReimbursement.feeToken is required`);
|
|
1109
|
+
}
|
|
1110
|
+
// Validate every destination address before starting any async work.
|
|
1111
|
+
const evmAddress = /^0x[0-9a-fA-F]{40}$/i;
|
|
1112
|
+
for (const tx of data.transactions) {
|
|
1113
|
+
if (!tx.to || !evmAddress.test(tx.to)) {
|
|
1114
|
+
throw new Error(`[${ctx}] Invalid 'to' address "${String(tx.to)}": must be a 42-character EVM address (0x + 40 hex chars)`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (!gr.feeRecipient || !evmAddress.test(gr.feeRecipient)) {
|
|
1118
|
+
throw new Error(`[${ctx}] Invalid gasReimbursement.feeRecipient "${String(gr.feeRecipient)}": must be a 42-character EVM address (0x + 40 hex chars)`);
|
|
1119
|
+
}
|
|
1120
|
+
const traceId = (_a = data.traceId) !== null && _a !== void 0 ? _a : (0, trace_1.generateTraceId)();
|
|
1121
|
+
// 1. Build the user's actual calls once — they don't change between passes.
|
|
1122
|
+
const userCalls = [];
|
|
1123
|
+
for (let index = 0; index < data.transactions.length; index++) {
|
|
1124
|
+
userCalls.push(yield this.buildUserOpCall(data.chain, data.transactions[index], traceId, index, ctx));
|
|
1125
|
+
}
|
|
1126
|
+
// 2. Build a placeholder fee call so the estimation pass reflects the final
|
|
1127
|
+
// batch shape (an N+1-call executeBatch). The amount does not affect the
|
|
1128
|
+
// gas estimate; only the call's presence and shape do.
|
|
1129
|
+
const feeIndex = data.transactions.length;
|
|
1130
|
+
const placeholderAmount = (_b = gr.placeholderAmount) !== null && _b !== void 0 ? _b : '0.01';
|
|
1131
|
+
let placeholderFeeCall;
|
|
1132
|
+
try {
|
|
1133
|
+
placeholderFeeCall = yield this.buildUserOpCall(data.chain, { token: gr.feeToken, value: placeholderAmount, to: gr.feeRecipient }, traceId, feeIndex, ctx);
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
throw new Error(`[${ctx}] Failed to build placeholder fee call: ${error instanceof Error ? error.message : String(error)}`);
|
|
1137
|
+
}
|
|
1138
|
+
// 3. Estimation pass — build the full batch to read the gas it's bounded by.
|
|
1139
|
+
let estimateResponse;
|
|
1140
|
+
try {
|
|
1141
|
+
estimateResponse = yield ((_c = this.mpc) === null || _c === void 0 ? void 0 : _c.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls: [...userCalls, placeholderFeeCall] }, traceId));
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
throw new Error(`[${ctx}] Failed to estimate UserOperation gas: ${error instanceof Error ? error.message : String(error)}`);
|
|
1145
|
+
}
|
|
1146
|
+
let gasCostWei = this.resolveUserOpGasCostWei(estimateResponse, ctx);
|
|
1147
|
+
// Guard against a zero gas cost. Some chains/providers return no on-chain fee
|
|
1148
|
+
// on the user operation (e.g. Ultra Relay bundler-level sponsorship on Monad
|
|
1149
|
+
// mainnet), so the build-time cost is 0 — there's nothing to derive a
|
|
1150
|
+
// reimbursement from. Fail loudly rather than silently charging the user 0.
|
|
1151
|
+
if (gasCostWei === BigInt(0)) {
|
|
1152
|
+
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.`);
|
|
1153
|
+
}
|
|
1154
|
+
// Apply optional buffer (basis points) to absorb gas-price drift before FX.
|
|
1155
|
+
if (gr.bufferBps && gr.bufferBps > 0) {
|
|
1156
|
+
gasCostWei =
|
|
1157
|
+
(gasCostWei * BigInt(10000 + Math.floor(gr.bufferBps))) / BigInt(10000);
|
|
1158
|
+
}
|
|
1159
|
+
// 4. Platform-owned conversion: native gas cost (wei) → fee-token amount.
|
|
1160
|
+
let feeAmount;
|
|
1161
|
+
try {
|
|
1162
|
+
feeAmount = yield gr.convertGasToFeeAmount(gasCostWei);
|
|
1163
|
+
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
throw new Error(`[${ctx}] gasReimbursement.convertGasToFeeAmount threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
1166
|
+
}
|
|
1167
|
+
if (typeof feeAmount !== 'string' || feeAmount.length === 0) {
|
|
1168
|
+
throw new Error(`[${ctx}] convertGasToFeeAmount must return a non-empty amount string, got "${String(feeAmount)}"`);
|
|
1169
|
+
}
|
|
1170
|
+
// 5. Build the real fee call with the converted amount.
|
|
1171
|
+
let feeCall;
|
|
1172
|
+
try {
|
|
1173
|
+
feeCall = yield this.buildUserOpCall(data.chain, { token: gr.feeToken, value: feeAmount, to: gr.feeRecipient }, traceId, feeIndex, ctx);
|
|
1174
|
+
}
|
|
1175
|
+
catch (error) {
|
|
1176
|
+
throw new Error(`[${ctx}] Failed to build fee reimbursement call: ${error instanceof Error ? error.message : String(error)}`);
|
|
1177
|
+
}
|
|
1178
|
+
// 6. Final pass — build the batch we'll actually sign and broadcast.
|
|
1179
|
+
let buildResponse;
|
|
1180
|
+
try {
|
|
1181
|
+
buildResponse = yield ((_d = this.mpc) === null || _d === void 0 ? void 0 : _d.accountAbstractionBuildBatchedUserOp({ chain: data.chain, calls: [...userCalls, feeCall] }, traceId));
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
throw new Error(`[${ctx}] Failed to build UserOperation: ${error instanceof Error ? error.message : String(error)}`);
|
|
1185
|
+
}
|
|
1186
|
+
return this.signAndBroadcastBuiltUserOp(buildResponse, data.chain, data.signatureApprovalMemo, traceId, ctx);
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Build a single ERC-4337 call from a high-level transfer descriptor by routing
|
|
1191
|
+
* it through `buildTransaction` (which resolves token contract + calldata and
|
|
1192
|
+
* normalizes the amount). Native transfers carry `value` (the base-unit amount);
|
|
1193
|
+
* ERC-20 transfers carry `data` and omit `value`.
|
|
1194
|
+
*/
|
|
1195
|
+
buildUserOpCall(chain, tx, traceId, index, ctx) {
|
|
1196
|
+
var _a;
|
|
1197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1198
|
+
let builtTx;
|
|
1199
|
+
try {
|
|
1200
|
+
builtTx = yield this.buildTransaction(chain, tx.to, tx.token, tx.value, traceId);
|
|
1201
|
+
}
|
|
1202
|
+
catch (error) {
|
|
1203
|
+
throw new Error(`[${ctx}] Failed to build call for transaction at index ${index}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1204
|
+
}
|
|
1205
|
+
// Validate the built transaction shape before casting. buildTransaction
|
|
1206
|
+
// returns BuiltTransaction (a union type), so the cast is TypeScript-only and
|
|
1207
|
+
// provides no runtime safety against a changed or unexpected backend response.
|
|
1208
|
+
const rawTx = builtTx;
|
|
1209
|
+
if (typeof ((_a = rawTx === null || rawTx === void 0 ? void 0 : rawTx.transaction) === null || _a === void 0 ? void 0 : _a.to) !== 'string' || !rawTx.transaction.to) {
|
|
1210
|
+
throw new Error(`[${ctx}] buildTransaction returned unexpected shape for transaction at index ${index}`);
|
|
1211
|
+
}
|
|
1212
|
+
const eip155Tx = builtTx;
|
|
1213
|
+
// Normalize the data field — guard against undefined/null/empty string from
|
|
1214
|
+
// the backend. Empty data means no calldata (native transfer). rawAmount is
|
|
1215
|
+
// passed as-is (decimal string e.g. '1000000000000000000'); the AA backend's
|
|
1216
|
+
// build-user-operation endpoint accepts decimal or hex.
|
|
1217
|
+
const txData = eip155Tx.transaction.data || '0x';
|
|
1218
|
+
const isNativeTransfer = txData === '0x';
|
|
1219
|
+
return Object.assign({ to: eip155Tx.transaction.to, data: txData }, (isNativeTransfer ? { value: eip155Tx.metadata.rawAmount } : {}));
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Resolve the native gas cost (in wei) a built UserOperation is bounded by,
|
|
1224
|
+
* from the backend-computed `metadata.estimatedGasCostWei` (`totalGas *
|
|
1225
|
+
* maxFeePerGas`, an upper bound). Throws if the field is absent — the
|
|
1226
|
+
* connect-api build-user-operation gas-cost change must be deployed.
|
|
1227
|
+
*/
|
|
1228
|
+
resolveUserOpGasCostWei(buildResponse, ctx) {
|
|
1229
|
+
var _a;
|
|
1230
|
+
const wei = (_a = buildResponse === null || buildResponse === void 0 ? void 0 : buildResponse.metadata) === null || _a === void 0 ? void 0 : _a.estimatedGasCostWei;
|
|
1231
|
+
if (wei == null || `${wei}`.length === 0) {
|
|
1232
|
+
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`);
|
|
1233
|
+
}
|
|
1234
|
+
return this.toBigIntOrThrow(wei, 'metadata.estimatedGasCostWei', ctx);
|
|
1235
|
+
}
|
|
1236
|
+
toBigIntOrThrow(value, field, ctx) {
|
|
1237
|
+
try {
|
|
1238
|
+
// BigInt() accepts both decimal ('1000000000') and hex ('0x3b9aca00') strings.
|
|
1239
|
+
return BigInt(value);
|
|
1240
|
+
}
|
|
1241
|
+
catch (_a) {
|
|
1242
|
+
throw new Error(`[${ctx}] Could not parse ${field} as an integer: "${String(value)}"`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Validate a built UserOperation, sign its hash with the SECP256K1 raw signer,
|
|
1247
|
+
* and broadcast it. Shared by the batched-UserOp send paths.
|
|
1248
|
+
*/
|
|
1249
|
+
signAndBroadcastBuiltUserOp(buildResponse, chain, signatureApprovalMemo, traceId, ctx) {
|
|
1250
|
+
var _a;
|
|
1251
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1252
|
+
const { userOperation, userOpHash } = buildResponse.data;
|
|
1253
|
+
// Validate userOperation is parseable JSON before signing or broadcasting.
|
|
1254
|
+
// A malformed string from the backend would produce an opaque bundler rejection.
|
|
1255
|
+
try {
|
|
1256
|
+
const parsed = JSON.parse(userOperation);
|
|
1257
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
1258
|
+
throw new Error('parsed value is not a JSON object');
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
catch (e) {
|
|
1262
|
+
throw new Error(`[${ctx}] buildBatchedUserOp returned an invalid userOperation: ${e instanceof Error ? e.message : String(e)}`);
|
|
1263
|
+
}
|
|
1264
|
+
// Validate userOpHash is a valid 32-byte hex string before signing.
|
|
1265
|
+
// Signing an empty or malformed hash wastes the signing operation and produces
|
|
1266
|
+
// a signature that no bundler will accept.
|
|
1267
|
+
if (!userOpHash || !/^(0x)?[0-9a-fA-F]{64}$/.test(userOpHash)) {
|
|
1268
|
+
throw new Error(`[${ctx}] Invalid userOpHash received from buildBatchedUserOp: "${String(userOpHash)}"`);
|
|
1269
|
+
}
|
|
1270
|
+
// Sign the userOpHash — strip the 0x prefix before passing to rawSign.
|
|
1271
|
+
let signature;
|
|
1272
|
+
try {
|
|
1273
|
+
const hashToSign = userOpHash.startsWith('0x')
|
|
1274
|
+
? userOpHash.slice(2)
|
|
1275
|
+
: userOpHash;
|
|
1276
|
+
signature = yield this.rawSign(PortalCurve.SECP256K1, hashToSign, {
|
|
1277
|
+
signatureApprovalMemo,
|
|
1278
|
+
traceId,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
throw new Error(`[${ctx}] Failed to sign userOpHash: ${error instanceof Error ? error.message : String(error)}`);
|
|
1283
|
+
}
|
|
1284
|
+
// Broadcast the signed UserOperation, reusing the same traceId for end-to-end correlation.
|
|
1285
|
+
try {
|
|
1286
|
+
return yield ((_a = this.mpc) === null || _a === void 0 ? void 0 : _a.accountAbstractionBroadcastBatchedUserOp({ chain, userOperation, signature }, traceId));
|
|
1287
|
+
}
|
|
1288
|
+
catch (error) {
|
|
1289
|
+
throw new Error(`[${ctx}] Failed to broadcast UserOperation: ${error instanceof Error ? error.message : String(error)}`);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
999
1293
|
/*******************************
|
|
1000
1294
|
* Swaps Methods
|
|
1001
1295
|
*******************************/
|
|
@@ -747,6 +747,237 @@ describe('Portal', () => {
|
|
|
747
747
|
expect(portal.mpc.getSources).toHaveBeenCalledWith('eip155:1', 'test');
|
|
748
748
|
}));
|
|
749
749
|
});
|
|
750
|
+
describe('buildBatchedUserOp', () => {
|
|
751
|
+
it('should correctly call mpc.accountAbstractionBuildBatchedUserOp', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
752
|
+
yield portal.buildBatchedUserOp(constants_1.mockBuildBatchedUserOpRequest);
|
|
753
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledTimes(1);
|
|
754
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledWith(constants_1.mockBuildBatchedUserOpRequest);
|
|
755
|
+
}));
|
|
756
|
+
});
|
|
757
|
+
describe('broadcastBatchedUserOp', () => {
|
|
758
|
+
it('should correctly call mpc.accountAbstractionBroadcastBatchedUserOp', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
759
|
+
yield portal.broadcastBatchedUserOp(constants_1.mockBroadcastBatchedUserOpRequest);
|
|
760
|
+
expect(portal.mpc.accountAbstractionBroadcastBatchedUserOp).toHaveBeenCalledTimes(1);
|
|
761
|
+
expect(portal.mpc.accountAbstractionBroadcastBatchedUserOp).toHaveBeenCalledWith(constants_1.mockBroadcastBatchedUserOpRequest);
|
|
762
|
+
}));
|
|
763
|
+
});
|
|
764
|
+
describe('sendBatchUserOp', () => {
|
|
765
|
+
it('should build, sign, and broadcast a batched UserOperation', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
766
|
+
const result = yield portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest);
|
|
767
|
+
expect(result).toEqual(expect.objectContaining({
|
|
768
|
+
data: expect.objectContaining({ userOpHash: expect.any(String) }),
|
|
769
|
+
metadata: expect.objectContaining({ chainId: expect.any(String) }),
|
|
770
|
+
}));
|
|
771
|
+
// buildTransaction called once per transaction descriptor
|
|
772
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledTimes(2);
|
|
773
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledWith(constants_1.mockSendBatchUserOpRequest.chain, constants_1.mockSendBatchUserOpRequest.transactions[0].to, constants_1.mockSendBatchUserOpRequest.transactions[0].token, constants_1.mockSendBatchUserOpRequest.transactions[0].value, expect.any(String));
|
|
774
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledWith(constants_1.mockSendBatchUserOpRequest.chain, constants_1.mockSendBatchUserOpRequest.transactions[1].to, constants_1.mockSendBatchUserOpRequest.transactions[1].token, constants_1.mockSendBatchUserOpRequest.transactions[1].value, expect.any(String));
|
|
775
|
+
// buildBatchedUserOp called with ERC-20 call shape (no value field) and the shared traceId
|
|
776
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledTimes(1);
|
|
777
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledWith({
|
|
778
|
+
chain: constants_1.mockSendBatchUserOpRequest.chain,
|
|
779
|
+
calls: [
|
|
780
|
+
{
|
|
781
|
+
to: constants_1.mockBuiltEip155Transaction.transaction.to,
|
|
782
|
+
data: constants_1.mockBuiltEip155Transaction.transaction.data,
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
to: constants_1.mockBuiltEip155Transaction.transaction.to,
|
|
786
|
+
data: constants_1.mockBuiltEip155Transaction.transaction.data,
|
|
787
|
+
},
|
|
788
|
+
],
|
|
789
|
+
}, expect.any(String));
|
|
790
|
+
// rawSign called with SECP256K1 and the userOpHash with 0x prefix stripped
|
|
791
|
+
expect(portal.mpc.rawSign).toHaveBeenCalledTimes(1);
|
|
792
|
+
expect(portal.mpc.rawSign).toHaveBeenCalledWith(_1.PortalCurve.SECP256K1, constants_1.mockBuildBatchedUserOpResponse.data.userOpHash.slice(2), { signatureApprovalMemo: undefined, traceId: expect.any(String) });
|
|
793
|
+
// broadcastBatchedUserOp called with the built userOperation and signature, and the shared traceId
|
|
794
|
+
expect(portal.mpc.accountAbstractionBroadcastBatchedUserOp).toHaveBeenCalledTimes(1);
|
|
795
|
+
expect(portal.mpc.accountAbstractionBroadcastBatchedUserOp).toHaveBeenCalledWith({
|
|
796
|
+
chain: constants_1.mockSendBatchUserOpRequest.chain,
|
|
797
|
+
userOperation: constants_1.mockBuildBatchedUserOpResponse.data.userOperation,
|
|
798
|
+
signature: constants_1.mockSignedHash,
|
|
799
|
+
}, expect.any(String));
|
|
800
|
+
}));
|
|
801
|
+
it('should propagate the same traceId to buildBatchedUserOp and broadcastBatchedUserOp', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
802
|
+
yield portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest);
|
|
803
|
+
const buildTraceId = portal.mpc.accountAbstractionBuildBatchedUserOp.mock.calls[0][1];
|
|
804
|
+
const broadcastTraceId = portal.mpc.accountAbstractionBroadcastBatchedUserOp.mock.calls[0][1];
|
|
805
|
+
expect(typeof buildTraceId).toBe('string');
|
|
806
|
+
expect(buildTraceId).toEqual(broadcastTraceId);
|
|
807
|
+
}));
|
|
808
|
+
it('should include value in the call for native ETH transfers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
809
|
+
;
|
|
810
|
+
portal.mpc.buildTransaction.mockResolvedValueOnce(constants_1.mockBuiltEip155TransactionNative);
|
|
811
|
+
yield portal.sendBatchUserOp({
|
|
812
|
+
chain: 'eip155:1',
|
|
813
|
+
transactions: [{ token: 'ETH', value: '0.1', to: '0x1111111111111111111111111111111111111111' }],
|
|
814
|
+
});
|
|
815
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledWith({
|
|
816
|
+
chain: 'eip155:1',
|
|
817
|
+
calls: [
|
|
818
|
+
{
|
|
819
|
+
to: constants_1.mockBuiltEip155TransactionNative.transaction.to,
|
|
820
|
+
data: '0x',
|
|
821
|
+
value: constants_1.mockBuiltEip155TransactionNative.metadata.rawAmount,
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
}, expect.any(String));
|
|
825
|
+
}));
|
|
826
|
+
it('should not include value in the call for ERC-20 transfers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
827
|
+
yield portal.sendBatchUserOp({
|
|
828
|
+
chain: 'eip155:1',
|
|
829
|
+
transactions: [{ token: 'USDC', value: '1.0', to: '0x1111111111111111111111111111111111111111' }],
|
|
830
|
+
});
|
|
831
|
+
const calls = portal.mpc.accountAbstractionBuildBatchedUserOp.mock.calls[0][0].calls;
|
|
832
|
+
expect(calls[0]).not.toHaveProperty('value');
|
|
833
|
+
}));
|
|
834
|
+
it('should forward signatureApprovalMemo to rawSign', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
835
|
+
yield portal.sendBatchUserOp(Object.assign(Object.assign({}, constants_1.mockSendBatchUserOpRequest), { signatureApprovalMemo: 'approve this batch' }));
|
|
836
|
+
expect(portal.mpc.rawSign).toHaveBeenCalledWith(_1.PortalCurve.SECP256K1, expect.any(String), { signatureApprovalMemo: 'approve this batch', traceId: expect.any(String) });
|
|
837
|
+
}));
|
|
838
|
+
it('should throw if chain is not eip155', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
839
|
+
yield expect(portal.sendBatchUserOp(Object.assign(Object.assign({}, constants_1.mockSendBatchUserOpRequest), { chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' }))).rejects.toThrow('[Portal.sendBatchUserOp] UserOperations are only supported on EIP-155 (EVM) chains');
|
|
840
|
+
}));
|
|
841
|
+
it('should throw if transactions array is empty', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
842
|
+
yield expect(portal.sendBatchUserOp(Object.assign(Object.assign({}, constants_1.mockSendBatchUserOpRequest), { transactions: [] }))).rejects.toThrow('[Portal.sendBatchUserOp] transactions must contain at least one transaction');
|
|
843
|
+
}));
|
|
844
|
+
it('should throw with index context if buildTransaction fails for transaction 0', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
845
|
+
;
|
|
846
|
+
portal.mpc.buildTransaction.mockRejectedValueOnce(new Error('Network error'));
|
|
847
|
+
yield expect(portal.sendBatchUserOp({
|
|
848
|
+
chain: 'eip155:1',
|
|
849
|
+
transactions: [{ token: 'USDC', value: '1.0', to: '0x1111111111111111111111111111111111111111' }],
|
|
850
|
+
})).rejects.toThrow('[Portal.sendBatchUserOp] Failed to build call for transaction at index 0: Network error');
|
|
851
|
+
}));
|
|
852
|
+
it('should throw with index context if buildTransaction fails for transaction 1', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
853
|
+
;
|
|
854
|
+
portal.mpc.buildTransaction
|
|
855
|
+
.mockResolvedValueOnce(constants_1.mockBuiltEip155Transaction)
|
|
856
|
+
.mockRejectedValueOnce(new Error('Network error'));
|
|
857
|
+
yield expect(portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest)).rejects.toThrow('[Portal.sendBatchUserOp] Failed to build call for transaction at index 1: Network error');
|
|
858
|
+
}));
|
|
859
|
+
it('should throw if buildBatchedUserOp fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
860
|
+
;
|
|
861
|
+
portal.mpc.accountAbstractionBuildBatchedUserOp.mockRejectedValueOnce(new Error('UserOp build failed'));
|
|
862
|
+
yield expect(portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest)).rejects.toThrow('[Portal.sendBatchUserOp] Failed to build UserOperation: UserOp build failed');
|
|
863
|
+
}));
|
|
864
|
+
it('should throw if rawSign fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
865
|
+
;
|
|
866
|
+
portal.mpc.rawSign.mockRejectedValueOnce(new Error('Signing failed'));
|
|
867
|
+
yield expect(portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest)).rejects.toThrow('[Portal.sendBatchUserOp] Failed to sign userOpHash: Signing failed');
|
|
868
|
+
}));
|
|
869
|
+
it('should throw if broadcastBatchedUserOp fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
870
|
+
;
|
|
871
|
+
portal.mpc.accountAbstractionBroadcastBatchedUserOp.mockRejectedValueOnce(new Error('Broadcast failed'));
|
|
872
|
+
yield expect(portal.sendBatchUserOp(constants_1.mockSendBatchUserOpRequest)).rejects.toThrow('[Portal.sendBatchUserOp] Failed to broadcast UserOperation: Broadcast failed');
|
|
873
|
+
}));
|
|
874
|
+
});
|
|
875
|
+
describe('sendBatchedAssets', () => {
|
|
876
|
+
const feeRecipient = '0x2222222222222222222222222222222222222222';
|
|
877
|
+
const baseRequest = {
|
|
878
|
+
chain: 'eip155:1',
|
|
879
|
+
transactions: [
|
|
880
|
+
{
|
|
881
|
+
token: 'USDC',
|
|
882
|
+
value: '1.0',
|
|
883
|
+
to: '0x1111111111111111111111111111111111111111',
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
};
|
|
887
|
+
it('should build twice, convert gas to a fee, sign, and broadcast', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
888
|
+
const convertGasToFeeAmount = jest.fn().mockResolvedValue('0.5');
|
|
889
|
+
const result = yield portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient, convertGasToFeeAmount } }));
|
|
890
|
+
expect(result).toEqual(constants_1.mockBroadcastBatchedUserOpResponse);
|
|
891
|
+
// Estimation pass + final pass = 2 builds.
|
|
892
|
+
expect(portal.mpc.accountAbstractionBuildBatchedUserOp).toHaveBeenCalledTimes(2);
|
|
893
|
+
// buildTransaction: 1 user tx + 1 placeholder fee + 1 real fee = 3.
|
|
894
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledTimes(3);
|
|
895
|
+
// Conversion called once with gasCostWei = totalGas(100000) * maxFeePerGas(1e9).
|
|
896
|
+
expect(convertGasToFeeAmount).toHaveBeenCalledTimes(1);
|
|
897
|
+
expect(convertGasToFeeAmount).toHaveBeenCalledWith(BigInt('100000000000000'));
|
|
898
|
+
// Placeholder fee call built with the default '0.01'; real fee call with '0.5'.
|
|
899
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledWith('eip155:1', feeRecipient, 'USDC', '0.01', expect.any(String));
|
|
900
|
+
expect(portal.mpc.buildTransaction).toHaveBeenCalledWith('eip155:1', feeRecipient, 'USDC', '0.5', expect.any(String));
|
|
901
|
+
expect(portal.mpc.rawSign).toHaveBeenCalledTimes(1);
|
|
902
|
+
expect(portal.mpc.accountAbstractionBroadcastBatchedUserOp).toHaveBeenCalledTimes(1);
|
|
903
|
+
}));
|
|
904
|
+
it('should apply bufferBps to the gas cost before conversion', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
905
|
+
const convertGasToFeeAmount = jest.fn().mockResolvedValue('0.6');
|
|
906
|
+
yield portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: {
|
|
907
|
+
feeToken: 'USDC',
|
|
908
|
+
feeRecipient,
|
|
909
|
+
convertGasToFeeAmount,
|
|
910
|
+
bufferBps: 1000, // +10%
|
|
911
|
+
} }));
|
|
912
|
+
// 1e14 * 11000 / 10000 = 1.1e14
|
|
913
|
+
expect(convertGasToFeeAmount).toHaveBeenCalledWith(BigInt('110000000000000'));
|
|
914
|
+
}));
|
|
915
|
+
it('should throw if the build response is missing estimatedGasCostWei', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
916
|
+
const noGasCost = Object.assign(Object.assign({}, constants_1.mockBuildBatchedUserOpResponse), { metadata: { chainId: 'eip155:1' } });
|
|
917
|
+
portal.mpc.accountAbstractionBuildBatchedUserOp.mockResolvedValueOnce(noGasCost);
|
|
918
|
+
const convertGasToFeeAmount = jest.fn();
|
|
919
|
+
yield expect(portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient, convertGasToFeeAmount } }))).rejects.toThrow('[Portal.sendBatchedAssets] build response is missing metadata.estimatedGasCostWei');
|
|
920
|
+
// Conversion should never run if we can't determine the gas cost.
|
|
921
|
+
expect(convertGasToFeeAmount).not.toHaveBeenCalled();
|
|
922
|
+
}));
|
|
923
|
+
it('should throw if the estimated gas cost is 0 (e.g. Ultra Relay zero-fee path)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
924
|
+
const zeroCost = Object.assign(Object.assign({}, constants_1.mockBuildBatchedUserOpResponse), { metadata: {
|
|
925
|
+
chainId: 'eip155:1',
|
|
926
|
+
totalGas: '2000000',
|
|
927
|
+
maxFeePerGas: '0',
|
|
928
|
+
estimatedGasCostWei: '0',
|
|
929
|
+
} });
|
|
930
|
+
portal.mpc.accountAbstractionBuildBatchedUserOp.mockResolvedValueOnce(zeroCost);
|
|
931
|
+
const convertGasToFeeAmount = jest.fn();
|
|
932
|
+
yield expect(portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient, convertGasToFeeAmount } }))).rejects.toThrow('[Portal.sendBatchedAssets] Estimated gas cost is 0');
|
|
933
|
+
expect(convertGasToFeeAmount).not.toHaveBeenCalled();
|
|
934
|
+
}));
|
|
935
|
+
it('should reuse the same traceId across both builds', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
936
|
+
const convertGasToFeeAmount = jest.fn().mockResolvedValue('0.5');
|
|
937
|
+
yield portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient, convertGasToFeeAmount } }));
|
|
938
|
+
const calls = portal.mpc.accountAbstractionBuildBatchedUserOp.mock.calls;
|
|
939
|
+
expect(typeof calls[0][1]).toBe('string');
|
|
940
|
+
expect(calls[0][1]).toEqual(calls[1][1]);
|
|
941
|
+
}));
|
|
942
|
+
it('should throw if chain is not eip155', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
943
|
+
yield expect(portal.sendBatchedAssets({
|
|
944
|
+
chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
945
|
+
transactions: baseRequest.transactions,
|
|
946
|
+
gasReimbursement: {
|
|
947
|
+
feeToken: 'USDC',
|
|
948
|
+
feeRecipient,
|
|
949
|
+
convertGasToFeeAmount: () => '1',
|
|
950
|
+
},
|
|
951
|
+
})).rejects.toThrow('[Portal.sendBatchedAssets] UserOperations are only supported on EIP-155 (EVM) chains');
|
|
952
|
+
}));
|
|
953
|
+
it('should throw if transactions is empty', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
954
|
+
yield expect(portal.sendBatchedAssets({
|
|
955
|
+
chain: 'eip155:1',
|
|
956
|
+
transactions: [],
|
|
957
|
+
gasReimbursement: {
|
|
958
|
+
feeToken: 'USDC',
|
|
959
|
+
feeRecipient,
|
|
960
|
+
convertGasToFeeAmount: () => '1',
|
|
961
|
+
},
|
|
962
|
+
})).rejects.toThrow('[Portal.sendBatchedAssets] transactions must contain at least one transaction');
|
|
963
|
+
}));
|
|
964
|
+
it('should throw if convertGasToFeeAmount is missing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
965
|
+
yield expect(portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient } }))).rejects.toThrow('[Portal.sendBatchedAssets] gasReimbursement.convertGasToFeeAmount (a function) is required');
|
|
966
|
+
}));
|
|
967
|
+
it('should throw if feeRecipient is not a valid EVM address', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
968
|
+
yield expect(portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: {
|
|
969
|
+
feeToken: 'USDC',
|
|
970
|
+
feeRecipient: '0xnope',
|
|
971
|
+
convertGasToFeeAmount: () => '1',
|
|
972
|
+
} }))).rejects.toThrow('[Portal.sendBatchedAssets] Invalid gasReimbursement.feeRecipient');
|
|
973
|
+
}));
|
|
974
|
+
it('should surface an error thrown by the conversion callback', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
975
|
+
const convertGasToFeeAmount = jest
|
|
976
|
+
.fn()
|
|
977
|
+
.mockRejectedValue(new Error('rate unavailable'));
|
|
978
|
+
yield expect(portal.sendBatchedAssets(Object.assign(Object.assign({}, baseRequest), { gasReimbursement: { feeToken: 'USDC', feeRecipient, convertGasToFeeAmount } }))).rejects.toThrow('[Portal.sendBatchedAssets] gasReimbursement.convertGasToFeeAmount threw: rate unavailable');
|
|
979
|
+
}));
|
|
980
|
+
});
|
|
750
981
|
describe('storedClientBackupShare', () => {
|
|
751
982
|
it('should correctly call mpc.storedClientBackupShare', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
752
983
|
yield portal.storedClientBackupShare(true, _1.BackupMethods.password);
|
|
@@ -14,7 +14,7 @@ const errors_1 = require("./errors");
|
|
|
14
14
|
const logger_1 = require("../logger");
|
|
15
15
|
const index_1 = require("../index");
|
|
16
16
|
const trace_1 = require("../shared/trace");
|
|
17
|
-
const WEB_SDK_VERSION = '3.
|
|
17
|
+
const WEB_SDK_VERSION = '3.17.0-alpha.0';
|
|
18
18
|
class Mpc {
|
|
19
19
|
get ready() {
|
|
20
20
|
return this._ready;
|
|
@@ -229,6 +229,7 @@ class Mpc {
|
|
|
229
229
|
param }, ((options === null || options === void 0 ? void 0 : options.signatureApprovalMemo) !== undefined && {
|
|
230
230
|
signatureApprovalMemo: options.signatureApprovalMemo,
|
|
231
231
|
})),
|
|
232
|
+
traceId: options === null || options === void 0 ? void 0 : options.traceId,
|
|
232
233
|
});
|
|
233
234
|
});
|
|
234
235
|
}
|
|
@@ -916,6 +917,31 @@ class Mpc {
|
|
|
916
917
|
});
|
|
917
918
|
});
|
|
918
919
|
}
|
|
920
|
+
/*******************************
|
|
921
|
+
* Account Abstraction Methods
|
|
922
|
+
*******************************/
|
|
923
|
+
accountAbstractionBuildBatchedUserOp(data, traceId) {
|
|
924
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
925
|
+
return this.handleRequestToIframeAndPost({
|
|
926
|
+
methodMessage: 'portal:accountAbstraction:buildBatchedUserOp',
|
|
927
|
+
errorMessage: 'portal:accountAbstraction:buildBatchedUserOpError',
|
|
928
|
+
resultMessage: 'portal:accountAbstraction:buildBatchedUserOpResult',
|
|
929
|
+
data,
|
|
930
|
+
traceId,
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
accountAbstractionBroadcastBatchedUserOp(data, traceId) {
|
|
935
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
936
|
+
return this.handleRequestToIframeAndPost({
|
|
937
|
+
methodMessage: 'portal:accountAbstraction:broadcastBatchedUserOp',
|
|
938
|
+
errorMessage: 'portal:accountAbstraction:broadcastBatchedUserOpError',
|
|
939
|
+
resultMessage: 'portal:accountAbstraction:broadcastBatchedUserOpResult',
|
|
940
|
+
data,
|
|
941
|
+
traceId,
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
}
|
|
919
945
|
/***************************
|
|
920
946
|
* Private Methods
|
|
921
947
|
***************************/
|