|
| 1 | +import GatewayClientRepository from '@/api/gateway.js'; |
| 2 | +import type OktoClient from '@/core/index.js'; |
| 3 | +import { BaseError } from '@/errors/base.js'; |
| 4 | +import { getChains } from '@/explorer/chain.js'; |
| 5 | +import type { Address, UserOp } from '@/types/core.js'; |
| 6 | +import { Constants } from '@/utils/index.js'; |
| 7 | +import { generateUUID, nonceToBigInt } from '@/utils/nonce.js'; |
| 8 | +import { |
| 9 | + encodeAbiParameters, |
| 10 | + encodeFunctionData, |
| 11 | + parseAbiParameters, |
| 12 | + stringToBytes, |
| 13 | + toHex, |
| 14 | +} from 'viem'; |
| 15 | +import { INTENT_ABI } from './abi.js'; |
| 16 | +import type { |
| 17 | + AptosRawTransaction, |
| 18 | + AptosRawTransactionIntentParams, |
| 19 | +} from './types.js'; |
| 20 | +import { |
| 21 | + AptosRawTransactionIntentParamsSchema, |
| 22 | + validateSchema, |
| 23 | +} from './userOpInputValidator.js'; |
| 24 | + |
| 25 | +/** |
| 26 | + * Creates a user operation for Aptos Raw Transaction. |
| 27 | + * |
| 28 | + * @param oc - The OktoClient instance used to interact with the blockchain. |
| 29 | + * @param data - The parameters for the Aptos raw transaction. |
| 30 | + * @returns The User Operation (UserOp) for the Aptos raw transaction. |
| 31 | + */ |
| 32 | +export async function aptosRawTransaction( |
| 33 | + oc: OktoClient, |
| 34 | + data: AptosRawTransactionIntentParams, |
| 35 | + feePayerAddress?: Address, |
| 36 | +): Promise<UserOp> { |
| 37 | + if (!oc.isLoggedIn()) { |
| 38 | + throw new BaseError('User not logged in'); |
| 39 | + } |
| 40 | + validateSchema(AptosRawTransactionIntentParamsSchema, data); |
| 41 | + |
| 42 | + if (!feePayerAddress) { |
| 43 | + feePayerAddress = Constants.FEE_PAYER_ADDRESS; |
| 44 | + } |
| 45 | + |
| 46 | + const nonce = generateUUID(); |
| 47 | + |
| 48 | + const jobParametersAbiType = '(string caip2Id, bytes[] transactions)'; |
| 49 | + const gsnDataAbiType = `(bool isRequired, string[] requiredNetworks, ${jobParametersAbiType}[] tokens)`; |
| 50 | + |
| 51 | + const chains = await getChains(oc); |
| 52 | + const currentChain = chains.find( |
| 53 | + (chain) => chain.caipId.toLowerCase() === data.caip2Id.toLowerCase(), |
| 54 | + ); |
| 55 | + |
| 56 | + if (!currentChain) { |
| 57 | + throw new BaseError(`Chain Not Supported`, { |
| 58 | + details: `${data.caip2Id} is not supported for this client`, |
| 59 | + }); |
| 60 | + } |
| 61 | + |
| 62 | + if (!data.caip2Id.toLowerCase().startsWith('aptos:')) { |
| 63 | + throw new BaseError('Invalid chain for Aptos transaction', { |
| 64 | + details: `${data.caip2Id} is not an Aptos chain`, |
| 65 | + }); |
| 66 | + } |
| 67 | + |
| 68 | + const transactionsBytes = data.transactions.map((transaction) => { |
| 69 | + const aptosTransaction: AptosRawTransaction = { |
| 70 | + function: transaction.function, |
| 71 | + typeArguments: transaction.typeArguments || [], |
| 72 | + functionArguments: transaction.functionArguments || [], |
| 73 | + }; |
| 74 | + return toHex(stringToBytes(JSON.stringify(aptosTransaction))); |
| 75 | + }); |
| 76 | + |
| 77 | + const jobparam = encodeAbiParameters( |
| 78 | + parseAbiParameters(jobParametersAbiType), |
| 79 | + [ |
| 80 | + { |
| 81 | + caip2Id: data.caip2Id, |
| 82 | + transactions: transactionsBytes, |
| 83 | + }, |
| 84 | + ], |
| 85 | + ); |
| 86 | + |
| 87 | + const calldata = encodeAbiParameters( |
| 88 | + parseAbiParameters('bytes4, address, uint256, bytes'), |
| 89 | + [ |
| 90 | + Constants.EXECUTE_USEROP_FUNCTION_SELECTOR, |
| 91 | + oc.env.jobManagerAddress, |
| 92 | + Constants.USEROP_VALUE, |
| 93 | + encodeFunctionData({ |
| 94 | + abi: INTENT_ABI, |
| 95 | + functionName: Constants.FUNCTION_NAME, |
| 96 | + args: [ |
| 97 | + toHex(nonceToBigInt(nonce), { size: 32 }), |
| 98 | + oc.clientSWA, |
| 99 | + oc.userSWA, |
| 100 | + feePayerAddress, |
| 101 | + encodeAbiParameters( |
| 102 | + parseAbiParameters('(bool gsnEnabled, bool sponsorshipEnabled)'), |
| 103 | + [ |
| 104 | + { |
| 105 | + gsnEnabled: currentChain.gsnEnabled ?? false, |
| 106 | + sponsorshipEnabled: currentChain.sponsorshipEnabled ?? false, |
| 107 | + }, |
| 108 | + ], |
| 109 | + ), |
| 110 | + encodeAbiParameters(parseAbiParameters(gsnDataAbiType), [ |
| 111 | + { |
| 112 | + isRequired: false, |
| 113 | + requiredNetworks: [], |
| 114 | + tokens: [], |
| 115 | + }, |
| 116 | + ]), |
| 117 | + jobparam, |
| 118 | + Constants.INTENT_TYPE.RAW_TRANSACTION, |
| 119 | + ], |
| 120 | + }), |
| 121 | + ], |
| 122 | + ); |
| 123 | + |
| 124 | + const gasPrice = await GatewayClientRepository.getUserOperationGasPrice(oc); |
| 125 | + |
| 126 | + const userOp: UserOp = { |
| 127 | + sender: oc.userSWA, |
| 128 | + nonce: toHex(nonceToBigInt(nonce), { size: 32 }), |
| 129 | + paymaster: oc.env.paymasterAddress, |
| 130 | + callGasLimit: toHex(Constants.GAS_LIMITS.CALL_GAS_LIMIT), |
| 131 | + verificationGasLimit: toHex(Constants.GAS_LIMITS.VERIFICATION_GAS_LIMIT), |
| 132 | + preVerificationGas: toHex(Constants.GAS_LIMITS.PRE_VERIFICATION_GAS), |
| 133 | + maxFeePerGas: gasPrice.maxFeePerGas, |
| 134 | + maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas, |
| 135 | + paymasterPostOpGasLimit: toHex( |
| 136 | + Constants.GAS_LIMITS.PAYMASTER_POST_OP_GAS_LIMIT, |
| 137 | + ), |
| 138 | + paymasterVerificationGasLimit: toHex( |
| 139 | + Constants.GAS_LIMITS.PAYMASTER_VERIFICATION_GAS_LIMIT, |
| 140 | + ), |
| 141 | + callData: calldata, |
| 142 | + paymasterData: await oc.paymasterData({ |
| 143 | + nonce: nonce, |
| 144 | + validUntil: new Date(Date.now() + 6 * Constants.HOURS_IN_MS), |
| 145 | + }), |
| 146 | + }; |
| 147 | + |
| 148 | + return userOp; |
| 149 | +} |
0 commit comments