@lukasrosario/account
Version:
Base Account SDK
468 lines (422 loc) • 10.7 kB
text/typescript
import { OwnerAccount } from ':core/type/index.js';
import { getClient } from ':store/chain-clients/utils.js';
import { Signature } from 'ox';
import { numberToHex, toHex } from 'viem';
import { createSmartAccount } from './createSmartAccount.js';
import { createSubAccountSigner } from './createSubAccountSigner.js';
vi.mock('viem/actions', () => ({
getCode: vi.fn().mockResolvedValue(undefined),
waitForCallsStatus: vi.fn().mockResolvedValue({
status: 'success',
receipts: [
{
logs: [],
status: 1,
blockHash: '0x',
blockNumber: 1,
gasUsed: 130161,
transactionHash: '0x',
},
],
}),
}));
vi.mock(':store/store.js', () => ({
store: {
getState: vi.fn().mockReturnValue({
subAccount: {
address: '0x',
factory: '0x',
factoryData: '0x',
},
}),
subAccounts: {
get: vi.fn().mockReturnValue({
address: '0x',
factory: '0x',
factoryData: '0x',
}),
},
subAccountsConfig: {
get: vi.fn().mockReturnValue({
toOwnerAccount: vi.fn().mockResolvedValue({
account: {
address: '0x',
type: 'local',
sign: vi.fn().mockResolvedValue,
},
}),
}),
},
},
config: {
get: vi.fn().mockReturnValue({}),
},
}));
vi.mock(':store/chain-clients/utils.js', () => ({
getBundlerClient: vi.fn().mockReturnValue({}),
getClient: vi.fn().mockReturnValue({
request: vi.fn(),
chain: {
id: 84532,
},
}),
}));
vi.mock('./createSmartAccount.js', () => ({
createSmartAccount: vi.fn().mockResolvedValue({
sign: vi.fn(),
signMessage: vi.fn(),
signTypedData: vi.fn(),
}),
}));
vi.mock('./getAccountIndex.js', () => ({
getAccountIndex: vi.fn().mockResolvedValue(1),
}));
describe('createSubAccountSigner', () => {
let owner = {
address: '0x',
publicKey: '0x',
type: 'webAuthn',
sign: vi.fn().mockResolvedValue({
webauthn: {
authenticatorData: '0x',
clientDataJSON: JSON.stringify({
type: 'webauthn',
}),
},
signature: Signature.toHex({
r: BigInt('49782753348462494199823712700004552394425719014458918871452329774910450607807'),
s: BigInt('33726695977844476214676913201140481102225469284307016937915595756355928419768'),
yParity: 1,
}),
}),
} as unknown as OwnerAccount;
it('should create a signer', async () => {
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
expect(signer.request).toBeDefined();
});
it('handle send calls', async () => {
// Mock store subaccount sign method
const request = vi.fn((args) => {
if (args.method === 'wallet_prepareCalls') {
return {
signatureRequest: {
hash: '0x',
},
type: '0x',
userOp: '0x',
chainId: numberToHex(84532),
};
}
if (args.method === 'wallet_sendPreparedCalls') {
return ['0x'];
}
return undefined;
});
(getClient as any).mockReturnValue({
request,
chain: {
id: 84532,
},
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
await signer.request({
method: 'wallet_sendCalls',
params: [
{
chainId: numberToHex(84532),
calls: [
{
to: '0x',
data: '0x',
},
],
from: '0x',
version: '1.0',
},
],
});
expect(request).toHaveBeenCalledWith({
method: 'wallet_prepareCalls',
params: [
{
calls: [{ to: '0x', data: '0x' }],
capabilities: {},
chainId: numberToHex(84532),
from: '0x',
version: '1.0',
},
],
});
expect(request).toHaveBeenCalledWith({
method: 'wallet_sendPreparedCalls',
params: [
{
version: '1.0',
type: '0x',
data: '0x',
chainId: numberToHex(84532),
signature: { type: 'webauthn', data: expect.any(Object) },
},
],
});
});
it('handle send calls with funding capability if parentAddress is provided', async () => {
// Mock store subaccount sign method
const request = vi.fn((args) => {
if (args.method === 'wallet_prepareCalls') {
return {
signatureRequest: {
hash: '0x',
},
type: '0x',
userOp: '0x',
chainId: numberToHex(84532),
};
}
if (args.method === 'wallet_sendPreparedCalls') {
return ['0x'];
}
return undefined;
});
(getClient as any).mockReturnValue({
request,
chain: {
id: 84532,
},
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
parentAddress: '0x',
});
await signer.request({
method: 'wallet_sendCalls',
params: [
{
chainId: numberToHex(84532),
calls: [
{
to: '0x',
data: '0x',
},
],
from: '0x',
version: '1.0',
},
],
});
expect(request).toHaveBeenCalledWith({
method: 'wallet_prepareCalls',
params: [
{
calls: [{ to: '0x', data: '0x' }],
capabilities: {
funding: [
{
type: 'spendPermission',
data: {
autoApply: true,
sources: ['0x'],
preference: 'PREFER_DIRECT_BALANCE',
},
},
],
},
chainId: numberToHex(84532),
from: '0x',
version: '1.0',
},
],
});
});
it('handle send transaction', async () => {
const mock = vi.fn();
(createSmartAccount as any).mockResolvedValue({
sign: mock,
});
const request = vi.fn((args) => {
if (args.method === 'wallet_prepareCalls') {
return {
signatureRequest: {
hash: '0x',
},
type: '0x',
userOp: '0x',
chainId: numberToHex(84532),
};
}
if (args.method === 'wallet_sendPreparedCalls') {
return ['0x'];
}
if (args.method === 'wallet_getCallsStatus') {
return {
status: 'CONFIRMED',
receipts: [
{
logs: [],
status: 1,
blockHash: '0x',
blockNumber: 1,
gasUsed: 130161,
transactionHash: '0x',
},
],
};
}
return undefined;
});
(getClient as any).mockReturnValue({
request,
chain: {
id: 84532,
},
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
const result = await signer.request({
method: 'eth_sendTransaction',
params: [{ to: '0x', data: '0x' }],
});
expect(result).toBe('0x');
});
it('handle sign message', async () => {
const mock = vi.fn();
(createSmartAccount as any).mockResolvedValue({
signMessage: mock,
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
const message = 'hello world';
const hexMessage = toHex(message);
await signer.request({
method: 'personal_sign',
params: [hexMessage, '0x'],
});
expect(mock).toHaveBeenCalledWith({ message});
});
it('handle sign typed data', async () => {
const mock = vi.fn();
(createSmartAccount as any).mockResolvedValue({
signTypedData: mock,
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
await signer.request({
method: 'eth_signTypedData_v4',
params: ['0x123', { hash: '0x' }],
});
expect(mock).toHaveBeenCalledWith({ hash: '0x' });
});
it('personal_sign calls signMessage with decoded message', async () => {
const mock = vi.fn();
(createSmartAccount as any).mockResolvedValue({
signMessage: mock,
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
});
const message = 'hello';
const hexMessage = toHex(message);
await signer.request({
method: 'personal_sign',
params: [hexMessage, '0x'], // hex encoded "hello"
});
expect(mock).toHaveBeenCalledWith({ message });
});
it('handles attribution', async () => {
const request = vi.fn((args) => {
if (args.method === 'wallet_prepareCalls') {
return {
signatureRequest: {
hash: '0x',
},
type: '0x',
userOp: '0x',
chainId: numberToHex(84532),
};
}
if (args.method === 'wallet_sendPreparedCalls') {
return ['0x'];
}
if (args.method === 'wallet_getCallsStatus') {
return {
status: 'CONFIRMED',
receipts: [
{
logs: [],
status: 1,
blockHash: '0x',
blockNumber: 1,
gasUsed: 130161,
transactionHash: '0x',
},
],
};
}
return undefined;
});
(getClient as any).mockReturnValue({
request,
getChainId: vi.fn().mockResolvedValue(84532),
chain: {
id: 84532,
},
});
const signer = await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
attribution: {
suffix: '0x7890',
},
});
// Typecheck for appOrigin option
await createSubAccountSigner({
address: '0x',
client: getClient(84532)!,
owner,
attribution: {
appOrigin: 'https://app.com',
},
});
await signer.request({
method: 'wallet_sendCalls',
params: [
{
calls: [{ to: '0x', data: '0x123456' }],
chainId: numberToHex(84532),
from: '0x',
version: '1.0',
},
],
});
expect(request).toHaveBeenCalledWith({
method: 'wallet_prepareCalls',
params: [
expect.objectContaining({
capabilities: expect.objectContaining({ attribution: { suffix: '0x7890' } }),
}),
],
});
});
});