Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/plugins/arbitrum_one/ZenmoneyManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<provider>
<id>arbitrum_one</id>
<company>0</company>
<description>
A plugin that retrieves balances and transactions on the Arbitrum One network using Arbiscan and Etherscan compatible API services.
Input your wallet's address and API key from the API provider.
</description>
<version>1.0</version>
<build>1</build>
<modular>true</modular>
<files>
<js>index.js</js>
<preferences>preferences.xml</preferences>
</files>
</provider>
60 changes: 60 additions & 0 deletions src/plugins/arbitrum_one/__tests__/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, it, expect, jest } from '@jest/globals'
import { ArbitrumOneApi } from '../api'

type FetchMock = jest.MockedFunction<typeof fetch>

describe('ArbitrumOneApi.getBalance', () => {
it('возвращает корректный баланс (объект result)', async () => {
const mockFetch = jest.fn() as FetchMock
global.fetch = mockFetch

mockFetch.mockResolvedValue({
json: async () => ({
status: '1',
result: { balance: '1000000000000000000' }
})
} as any)

const api = new ArbitrumOneApi('TEST_KEY')
const res = await api.getBalance('0x123')

expect(res.balance).toBe('1000000000000000000')
expect(mockFetch).toHaveBeenCalledTimes(1)
})

it('возвращает корректный баланс (строка result)', async () => {
const mockFetch = jest.fn() as FetchMock
global.fetch = mockFetch

mockFetch.mockResolvedValue({
json: async () => ({
status: '1',
result: '2000000000000000000'
})
} as any)

const api = new ArbitrumOneApi('TEST_KEY')
const res = await api.getBalance('0x123')

expect(res.balance).toBe('2000000000000000000')
})
})

describe('ArbitrumOneApi.getBlockNumberByTimestamp', () => {
it('возвращает число блока', async () => {
const mockFetch = jest.fn() as FetchMock
global.fetch = mockFetch

mockFetch.mockResolvedValue({
json: async () => ({
status: '1',
result: '123456'
})
} as any)

const api = new ArbitrumOneApi('TEST_KEY')
const block = await api.getBlockNumberByTimestamp(1700000000)

expect(block).toBe(123456)
})
})
34 changes: 34 additions & 0 deletions src/plugins/arbitrum_one/__tests__/convertBalances.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, it, expect } from '@jest/globals'
import { convertBalances } from '../converter'
import { SUPPORTED_TOKENS } from '../supportedTokens'

describe('convertBalances', () => {
it('конвертирует нативный баланс ETH', () => {
const native = { balance: '1000000000000000000' } // 1 ETH
const tokens: any[] = []

const res = convertBalances(native as any, tokens)

expect(res[0].id).toBe('arbitrum-one-main')
expect(res[0].instrument).toBe('µETH')
expect(res[0].balance).toBe(1000000)
})

it('конвертирует поддерживаемый токен', () => {
const tokenMeta = SUPPORTED_TOKENS[0]
const native = { balance: '0' }
const tokens = [
{
contract: tokenMeta.contract,
balance: String(10 * 10 ** tokenMeta.decimals),
symbol: tokenMeta.symbol
}
]

const res = convertBalances(native as any, tokens as any)

const tokenAcc = res.find((r) => r.instrument === tokenMeta.symbol)
expect(tokenAcc).toBeDefined()
expect(tokenAcc?.balance).toBe(10)
})
})
74 changes: 74 additions & 0 deletions src/plugins/arbitrum_one/__tests__/convertTransactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, it, expect } from '@jest/globals'
import { convertTransactions } from '../converter'
import { SUPPORTED_TOKENS } from '../supportedTokens'

describe('convertTransactions', () => {
it('конвертирует исходящую ETH транзакцию', () => {
const nativeTxs = [
{
hash: '0x1',
from: '0xaaa',
to: '0xbbb',
value: '1000000000000000000', // 1 ETH
gasUsed: '21000',
gasPrice: '1000000000',
timeStamp: '1700000000'
}
]

const res = convertTransactions(nativeTxs as any, [], '0xaaa')

expect(res).toHaveLength(1)
const tx = res[0]
expect(tx.movements[0].sum).toBe(-1000000)
expect(tx.movements[0].fee).toBeGreaterThan(0)
expect(tx.merchant.fullTitle).toBe('0xbbb')
})

it('конвертирует входящую ETH транзакцию', () => {
const nativeTxs = [
{
hash: '0x2',
from: '0xccc',
to: '0xddd',
value: '2000000000000000000', // 2 ETH
gasUsed: '21000',
gasPrice: '1000000000',
timeStamp: '1700000001'
}
]

const res = convertTransactions(nativeTxs as any, [], '0xddd')

expect(res).toHaveLength(1)
const tx = res[0]
expect(tx.movements[0].sum).toBe(2000000)
expect(tx.movements[0].fee).toBe(0)
expect(tx.merchant.fullTitle).toBe('0xccc')
})

it('конвертирует ERC20 транзакцию', () => {
const tokenMeta = SUPPORTED_TOKENS[0]

const nativeTxs: any[] = []
const tokenTxs = [
{
hash: '0x3',
from: '0xaaa',
to: '0xbbb',
contract: tokenMeta.contract,
value: String(5 * 10 ** tokenMeta.decimals),
timeStamp: '1700000002'
}
]

const res = convertTransactions(nativeTxs as any, tokenTxs as any, '0xaaa')

expect(res).toHaveLength(1)
const tx = res[0]
expect(tx.movements[0].sum).toBe(-5)
expect(tx.movements[0].account.id).toBe(
`arbitrum-one-${tokenMeta.symbol.toLowerCase()}`
)
})
})
27 changes: 27 additions & 0 deletions src/plugins/arbitrum_one/__tests__/merchants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, it, expect } from '@jest/globals'
import { normalizeMerchant, KNOWN_CONTRACTS } from '../merchants'

describe('normalizeMerchant', () => {
it('возвращает Unknown для пустого адреса', () => {
expect(normalizeMerchant('', '0x0')).toBe('Unknown')
})

it('распознаёт Self', () => {
expect(normalizeMerchant('0xAbC', '0xabc')).toBe('Self')
})

it('распознаёт известный контракт', () => {
const addr = Object.keys(KNOWN_CONTRACTS)[0]
expect(normalizeMerchant(addr, '0x0')).toBe(KNOWN_CONTRACTS[addr])
})

it('форматирует неизвестный контракт', () => {
const addr = '0x1111111111111111111111111111111111111111'
const res = normalizeMerchant(addr, '0x0')
expect(res.startsWith('Contract ')).toBe(true)
})

it('возвращает исходный addr для не‑контрактов', () => {
expect(normalizeMerchant('Some Name', '0x0')).toBe('Some Name')
})
})
46 changes: 46 additions & 0 deletions src/plugins/arbitrum_one/__tests__/queue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect, jest } from '@jest/globals'
import { ArbitrumOneApi } from '../api'

type FetchMock = jest.MockedFunction<typeof fetch>

describe('ArbitrumOneApi очередь', () => {
it('выполняет задачи последовательно через публичные методы', async () => {
const mockFetch = jest.fn() as FetchMock
global.fetch = mockFetch

// First API call
mockFetch.mockResolvedValueOnce({
json: async () => ({
status: '1',
result: { balance: '100' }
})
} as any)

// Second API call
mockFetch.mockResolvedValueOnce({
json: async () => ({
status: '1',
result: { balance: '200' }
})
} as any)

const api = new ArbitrumOneApi('TEST')

// Start two requests in a row — they should execute sequentially
const p1 = api.getBalance('0x111')
const p2 = api.getBalance('0x222')

const r1 = await p1
const r2 = await p2

expect(r1.balance).toBe('100')
expect(r2.balance).toBe('200')

// Check that fetch was called twice
expect(mockFetch).toHaveBeenCalledTimes(2)

// Check order of calls
expect(mockFetch.mock.calls[0][0]).toContain('0x111')
expect(mockFetch.mock.calls[1][0]).toContain('0x222')
})
})
Loading