accounts
Version:
Tempo Accounts SDK
219 lines (183 loc) • 6.41 kB
text/typescript
import { tempoLocalnet } from 'viem/chains'
import { WebCryptoP256 } from 'viem/tempo'
import { describe, expect, test } from 'vp/test'
import { accounts, privateKeys } from '../../test/config.js'
import * as Account from './Account.js'
import * as Store from './Store.js'
describe('hydrate', () => {
test('default: returns json-rpc account when sign is false', () => {
const result = Account.hydrate({ address: accounts[0].address })
expect(result).toMatchInlineSnapshot(`
{
"address": "${accounts[0].address}",
"type": "json-rpc",
}
`)
})
test('behavior: hydrates secp256k1 account', () => {
const result = Account.hydrate(
{
address: accounts[0].address,
keyType: 'secp256k1',
privateKey: privateKeys[0],
},
{ signable: true },
)
expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
expect(result.type).toMatchInlineSnapshot(`"local"`)
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
})
test('behavior: hydrates p256 account', () => {
const result = Account.hydrate(
{
address: accounts[0].address,
keyType: 'p256',
privateKey: privateKeys[0],
},
{ signable: true },
)
expect(result.type).toMatchInlineSnapshot(`"local"`)
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
})
test('behavior: hydrates webCrypto account', async () => {
const result = Account.hydrate(
{
address: accounts[0].address,
keyType: 'webCrypto',
keyPair: await WebCryptoP256.createKeyPair(),
},
{ signable: true },
)
expect(result.type).toMatchInlineSnapshot(`"local"`)
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
})
test('behavior: hydrates webAuthn_headless account', () => {
const result = Account.hydrate(
{
address: accounts[0].address,
keyType: 'webAuthn_headless',
privateKey: privateKeys[0],
rpId: 'example.com',
origin: 'https://example.com',
},
{ signable: true },
)
expect(result.type).toMatchInlineSnapshot(`"local"`)
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
})
test('error: throws when sign is true but no sign data', () => {
expect(() =>
Account.hydrate({ address: accounts[0].address }, { signable: true }),
).toThrowErrorMatchingInlineSnapshot(
`[Provider.UnauthorizedError: Account "${accounts[0].address}" cannot sign.]`,
)
})
})
describe('find', () => {
function setup(
storeAccounts: readonly Account.Store[] = [],
accessKeys: readonly Store.AccessKey[] = [],
) {
const store = Store.create({ chainId: tempoLocalnet.id })
store.setState({ accounts: storeAccounts, accessKeys, activeAccount: 0 })
return store
}
test('default: resolves active account', () => {
const store = setup([
{
address: accounts[0].address,
keyType: 'secp256k1',
privateKey: privateKeys[0],
},
])
const result = Account.find({ store })
expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
expect(result.type).toMatchInlineSnapshot(`"json-rpc"`)
})
test('behavior: resolves by address', () => {
const store = setup([
{
address: accounts[0].address,
keyType: 'secp256k1',
privateKey: privateKeys[0],
},
{
address: accounts[1].address,
keyType: 'secp256k1',
privateKey: privateKeys[1],
},
])
const result = Account.find({ address: accounts[1].address, store })
expect(result.address).toMatchInlineSnapshot(`"${accounts[1].address}"`)
})
test('behavior: resolves signable account', () => {
const store = setup([
{
address: accounts[0].address,
keyType: 'secp256k1',
privateKey: privateKeys[0],
},
])
const result = Account.find({ signable: true, store })
expect(result.type).toMatchInlineSnapshot(`"local"`)
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
})
test('behavior: prefers access key over root account', async () => {
const keyPair = await WebCryptoP256.createKeyPair()
const store = setup(
[{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
[
{
address: '0x0000000000000000000000000000000000000099',
access: accounts[0].address,
chainId: tempoLocalnet.id,
keyType: 'webCrypto',
keyPair,
},
],
)
const result = Account.find({ chainId: tempoLocalnet.id, store, signable: true })
expect(result.source).toMatchInlineSnapshot(`"accessKey"`)
})
test('behavior: accessKey false skips access key', async () => {
const keyPair = await WebCryptoP256.createKeyPair()
const store = setup(
[{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
[
{
address: '0x0000000000000000000000000000000000000099',
access: accounts[0].address,
chainId: tempoLocalnet.id,
keyType: 'webCrypto',
keyPair,
},
],
)
const result = Account.find({ accessKey: false, signable: true, store })
expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
expect(result.source).not.toBe('accessKey')
})
test('behavior: falls back to root when no access key exists', () => {
const store = setup(
[{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
[],
)
const result = Account.find({ signable: true, store })
expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
expect(result.type).toMatchInlineSnapshot(`"local"`)
})
test('error: throws when address not found', () => {
const store = setup([])
expect(() =>
Account.find({ address: accounts[0].address, store }),
).toThrowErrorMatchingInlineSnapshot(
`[Provider.UnauthorizedError: Account "${accounts[0].address}" not found.]`,
)
})
test('error: throws when no active account', () => {
const store = setup([])
expect(() => Account.find({ store })).toThrowErrorMatchingInlineSnapshot(
`[Provider.DisconnectedError: No active account.]`,
)
})
})