UNPKG

@lukasrosario/account

Version:

Base Account SDK

468 lines (422 loc) 10.7 kB
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' } }), }), ], }); }); });