@base-org/account
Version:
Base Account SDK
229 lines • 10.8 kB
JavaScript
import { isViemError, standardErrors, viemHttpErrorToProviderError } from '../../../core/error/errors.js';
import { ensureHexString } from '../../../core/type/util.js';
import { assertArrayPresence, assertPresence } from '../../../util/assertPresence.js';
import { convertCredentialToJSON } from '../../../util/encoding.js';
import { get } from '../../../util/get.js';
import { hexToString, isHex, numberToHex, } from 'viem';
import { createWalletSendCallsRequest, injectRequestCapabilities, waitForCallsTransactionHash, } from '../utils.js';
import { createSmartAccount } from './createSmartAccount.js';
export async function createSubAccountSigner({ address, client, factory, factoryData, owner, ownerIndex, parentAddress, attribution, }) {
const subAccount = {
address,
factory,
factoryData,
};
const chainId = client.chain?.id;
if (!chainId) {
throw standardErrors.rpc.internal('chainId not found');
}
const account = await createSmartAccount({
owner,
ownerIndex: ownerIndex ?? 1,
address,
client,
factoryData,
});
const request = async (args) => {
try {
switch (args.method) {
case 'wallet_addSubAccount':
return subAccount;
case 'eth_accounts':
return [subAccount.address];
case 'eth_coinbase':
return subAccount.address;
case 'net_version':
return chainId.toString();
case 'eth_chainId':
return numberToHex(chainId);
case 'eth_sendTransaction': {
assertArrayPresence(args.params);
const rawParams = args.params[0];
assertPresence(rawParams.to, standardErrors.rpc.invalidParams('to is required'));
const params = {
to: rawParams.to,
data: ensureHexString(rawParams.data ?? '0x', true),
value: ensureHexString(rawParams.value ?? '0x', true),
from: rawParams.from ?? subAccount.address,
};
// Transform into wallet_sendCalls request
const sendCallsRequest = createWalletSendCallsRequest({
calls: [params],
chainId,
from: params.from,
});
const response = (await request(sendCallsRequest));
return waitForCallsTransactionHash({
client,
id: response,
});
}
case 'wallet_sendCalls': {
assertArrayPresence(args.params);
// Get the client for the chain
const chainId = get(args.params[0], 'chainId');
if (!chainId) {
throw standardErrors.rpc.invalidParams('chainId is required');
}
if (!isHex(chainId)) {
throw standardErrors.rpc.invalidParams('chainId must be a hex encoded integer');
}
if (!args.params[0]) {
throw standardErrors.rpc.invalidParams('params are required');
}
if (!('calls' in args.params[0])) {
throw standardErrors.rpc.invalidParams('calls are required');
}
let prepareCallsRequest = {
method: 'wallet_prepareCalls',
params: [
{
version: '1.0',
calls: args.params[0].calls,
chainId: chainId,
from: subAccount.address,
capabilities: 'capabilities' in args.params[0]
? args.params[0].capabilities
: {},
},
],
};
if (parentAddress) {
prepareCallsRequest = injectRequestCapabilities(prepareCallsRequest, {
funding: [
{
type: 'spendPermission',
data: {
autoApply: true,
sources: [parentAddress],
preference: 'PREFER_DIRECT_BALANCE',
},
},
],
});
}
let prepareCallsResponse = (await request(prepareCallsRequest));
const signResponse = await owner.sign?.({
// Hash returned from wallet_prepareCalls is double hex encoded
hash: hexToString(prepareCallsResponse.signatureRequest.hash),
});
let signatureData;
if (!signResponse) {
throw standardErrors.rpc.internal('signature not found');
}
if (isHex(signResponse)) {
signatureData = {
type: 'secp256k1',
data: {
address: owner.address,
signature: signResponse,
},
};
}
else {
signatureData = {
type: 'webauthn',
data: {
signature: JSON.stringify(convertCredentialToJSON({
id: owner.id ?? '1',
...signResponse,
})),
publicKey: owner.publicKey,
},
};
}
const sendPreparedCallsResponse = (await request({
method: 'wallet_sendPreparedCalls',
params: [
{
version: '1.0',
type: prepareCallsResponse.type,
data: prepareCallsResponse.userOp,
chainId: prepareCallsResponse.chainId,
signature: signatureData,
},
],
}));
return sendPreparedCallsResponse[0];
}
case 'wallet_sendPreparedCalls': {
assertArrayPresence(args.params);
// Get the client for the chain
const chainId = get(args.params[0], 'chainId');
if (!chainId) {
throw standardErrors.rpc.invalidParams('chainId is required');
}
if (!isHex(chainId)) {
throw standardErrors.rpc.invalidParams('chainId must be a hex encoded integer');
}
const sendPreparedCallsResponse = await client.request({
method: 'wallet_sendPreparedCalls',
params: args.params,
});
return sendPreparedCallsResponse;
}
case 'wallet_prepareCalls': {
assertArrayPresence(args.params);
// Get the client for the chain
const chainId = get(args.params[0], 'chainId');
if (!chainId) {
throw standardErrors.rpc.invalidParams('chainId is required');
}
if (!isHex(chainId)) {
throw standardErrors.rpc.invalidParams('chainId must be a hex encoded integer');
}
if (!args.params[0]) {
throw standardErrors.rpc.invalidParams('params are required');
}
if (!get(args.params[0], 'calls')) {
throw standardErrors.rpc.invalidParams('calls are required');
}
const prepareCallsParams = args.params[0];
if (attribution &&
prepareCallsParams.capabilities &&
!('attribution' in prepareCallsParams.capabilities)) {
prepareCallsParams.capabilities.attribution = attribution;
}
const prepareCallsResponse = await client.request({
method: 'wallet_prepareCalls',
params: [{ ...args.params[0], chainId: chainId }],
});
return prepareCallsResponse;
}
case 'personal_sign': {
assertArrayPresence(args.params);
// Param is expected to be a hex encoded string
if (!isHex(args.params[0])) {
throw standardErrors.rpc.invalidParams('message must be a hex encoded string');
}
// signMessage expects the unencoded message
const message = hexToString(args.params[0]);
return account.signMessage({ message });
}
case 'eth_signTypedData_v4': {
assertArrayPresence(args.params);
const typedData = typeof args.params[1] === 'string' ? JSON.parse(args.params[1]) : args.params[1];
return account.signTypedData(typedData);
}
case 'eth_signTypedData_v1':
case 'eth_signTypedData_v3':
case 'wallet_addEthereumChain':
case 'wallet_switchEthereumChain':
default:
throw standardErrors.rpc.methodNotSupported();
}
}
catch (error) {
// Convert error to RPC error if possible
if (isViemError(error)) {
const newError = viemHttpErrorToProviderError(error);
if (newError) {
throw newError;
}
}
throw error;
}
};
return { request };
}
//# sourceMappingURL=createSubAccountSigner.js.map