diff --git a/src/plugins/arbitrum_one/ZenmoneyManifest.xml b/src/plugins/arbitrum_one/ZenmoneyManifest.xml
new file mode 100755
index 000000000..b6d5d5a24
--- /dev/null
+++ b/src/plugins/arbitrum_one/ZenmoneyManifest.xml
@@ -0,0 +1,16 @@
+
+
+ arbitrum_one
+ 0
+
+ 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.
+
+ 1.0
+ 1
+ true
+
+ index.js
+ preferences.xml
+
+
diff --git a/src/plugins/arbitrum_one/__tests__/api.test.ts b/src/plugins/arbitrum_one/__tests__/api.test.ts
new file mode 100644
index 000000000..26c525d33
--- /dev/null
+++ b/src/plugins/arbitrum_one/__tests__/api.test.ts
@@ -0,0 +1,60 @@
+import { describe, it, expect, jest } from '@jest/globals'
+import { ArbitrumOneApi } from '../api'
+
+type FetchMock = jest.MockedFunction
+
+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)
+ })
+})
diff --git a/src/plugins/arbitrum_one/__tests__/convertBalances.test.ts b/src/plugins/arbitrum_one/__tests__/convertBalances.test.ts
new file mode 100644
index 000000000..c2f5fd045
--- /dev/null
+++ b/src/plugins/arbitrum_one/__tests__/convertBalances.test.ts
@@ -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)
+ })
+})
diff --git a/src/plugins/arbitrum_one/__tests__/convertTransactions.test.ts b/src/plugins/arbitrum_one/__tests__/convertTransactions.test.ts
new file mode 100644
index 000000000..7d72f9e48
--- /dev/null
+++ b/src/plugins/arbitrum_one/__tests__/convertTransactions.test.ts
@@ -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()}`
+ )
+ })
+})
diff --git a/src/plugins/arbitrum_one/__tests__/merchants.test.ts b/src/plugins/arbitrum_one/__tests__/merchants.test.ts
new file mode 100644
index 000000000..1870bd5d5
--- /dev/null
+++ b/src/plugins/arbitrum_one/__tests__/merchants.test.ts
@@ -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')
+ })
+})
diff --git a/src/plugins/arbitrum_one/__tests__/queue.test.ts b/src/plugins/arbitrum_one/__tests__/queue.test.ts
new file mode 100644
index 000000000..5bc0af8fb
--- /dev/null
+++ b/src/plugins/arbitrum_one/__tests__/queue.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, jest } from '@jest/globals'
+import { ArbitrumOneApi } from '../api'
+
+type FetchMock = jest.MockedFunction
+
+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')
+ })
+})
diff --git a/src/plugins/arbitrum_one/api.ts b/src/plugins/arbitrum_one/api.ts
new file mode 100644
index 000000000..34fb14c63
--- /dev/null
+++ b/src/plugins/arbitrum_one/api.ts
@@ -0,0 +1,189 @@
+import {
+ ArbiscanResponse,
+ BalanceResponse,
+ TokenBalance,
+ Transaction,
+ TokenTransfer
+} from './types'
+
+import { SUPPORTED_TOKENS } from './supportedTokens'
+
+const API_URL = 'https://api.etherscan.io/v2/api'
+const CHAIN_ID = 42161
+const PAGE_SIZE = 100
+
+async function delay (ms: number): Promise {
+ return await new Promise((resolve) => setTimeout(resolve, ms))
+}
+
+export class ArbitrumOneApi {
+ private readonly apiKey: string
+
+ private readonly queue: Array<() => Promise> = []
+ private isProcessing = false
+ private readonly MIN_DELAY = 350
+
+ constructor (apiKey: string) {
+ this.apiKey = apiKey
+ }
+
+ private async enqueue(task: () => Promise): Promise {
+ return await new Promise((resolve, reject) => {
+ this.queue.push(async (): Promise => {
+ try {
+ const result = await task()
+ resolve(result)
+ } catch (err) {
+ reject(err)
+ }
+ })
+
+ void this.processQueue()
+ })
+ }
+
+ private async processQueue (): Promise {
+ if (this.isProcessing) return
+ this.isProcessing = true
+
+ while (this.queue.length > 0) {
+ const job = this.queue.shift()
+ if (job == null) continue
+
+ await job()
+ await delay(this.MIN_DELAY)
+ }
+
+ this.isProcessing = false
+ }
+
+ private async call(params: Record): Promise {
+ return await this.enqueue(async () => {
+ const url = new URL(API_URL)
+
+ url.searchParams.set('chainid', String(CHAIN_ID))
+ url.searchParams.set('apikey', this.apiKey)
+
+ for (const [key, value] of Object.entries(params)) {
+ url.searchParams.set(key, String(value))
+ }
+
+ const response = await fetch(url.toString())
+ const json = (await response.json()) as ArbiscanResponse
+
+ if (json.status !== '1') {
+ throw new Error(json.message ?? 'Etherscan API error')
+ }
+
+ return json.result
+ })
+ }
+
+ // Normalization for all cases
+ async getBalance (address: string): Promise {
+ const res = await this.call({
+ module: 'account',
+ action: 'balance',
+ address,
+ tag: 'latest'
+ })
+
+ // Etherscan v2 sometimes returns a string instead of an object
+ if (typeof res === 'string') {
+ return { balance: res }
+ }
+
+ if (typeof res === 'object' && res.balance != null) {
+ return { balance: res.balance }
+ }
+
+ return { balance: '0' }
+ }
+
+ // Get startblocknumber
+ async getBlockNumberByTimestamp (ts: number): Promise {
+ const result = await this.call({
+ module: 'block',
+ action: 'getblocknobytime',
+ timestamp: ts,
+ closest: 'before'
+ })
+
+ return Number(result)
+ }
+
+ async getTokenBalances (address: string): Promise {
+ const result = await Promise.all(
+ SUPPORTED_TOKENS.map(async (token) => {
+ const balance = await this.call({
+ module: 'account',
+ action: 'tokenbalance',
+ contractaddress: token.contract,
+ address,
+ tag: 'latest'
+ })
+
+ return {
+ contract: token.contract,
+ balance: String(balance),
+ symbol: token.symbol
+ }
+ })
+ )
+
+ return result
+ }
+
+ async getTransactions (address: string, fromDate: Date): Promise {
+ return await this.fetchTxPages({
+ action: 'txlist',
+ address,
+ fromDate
+ })
+ }
+
+ async getTokenTransfers (address: string, fromDate: Date): Promise {
+ return await this.fetchTxPages({
+ action: 'tokentx',
+ address,
+ fromDate
+ })
+ }
+
+ private async fetchTxPages(opts: {
+ action: string
+ address: string
+ fromDate: Date
+ }): Promise {
+ let page = 1
+ const all: T[] = []
+
+ const startTimestamp = Math.floor(opts.fromDate.getTime() / 1000)
+ const startBlock = await this.getBlockNumberByTimestamp(startTimestamp)
+
+ while (true) {
+ const result = await this.call({
+ module: 'account',
+ action: opts.action,
+ address: opts.address,
+ page,
+ offset: PAGE_SIZE,
+ sort: 'asc',
+ startblock: startBlock,
+ endblock: 9999999999
+ })
+
+ const normalized = result.map((tx) => ({
+ ...tx,
+ contract: tx.contractAddress
+ }))
+
+ all.push(...normalized)
+
+ if (result.length < PAGE_SIZE) break
+ page++
+ }
+
+ return all
+ }
+}
diff --git a/src/plugins/arbitrum_one/converter.ts b/src/plugins/arbitrum_one/converter.ts
new file mode 100644
index 000000000..606599b3b
--- /dev/null
+++ b/src/plugins/arbitrum_one/converter.ts
@@ -0,0 +1,168 @@
+import { BalanceResponse, TokenBalance, Transaction, TokenTransfer } from './types'
+
+// -----------
+// Tokens
+// -----------
+import { SUPPORTED_TOKENS } from './supportedTokens'
+
+// ------
+// Merchants
+// ------
+import { normalizeMerchant } from './merchants'
+
+// Helper to coerce various numeric types (string, number, bigint) to Number safely
+function toNumeric (value: any): number {
+ if (value == null) return 0
+ if (typeof value === 'number') return value
+ if (typeof value === 'string') {
+ const v = value.trim()
+ if (v === '') return 0
+ const n = Number(v)
+ return isFinite(n) ? n : 0
+ }
+ if (typeof value === 'bigint') {
+ try {
+ return Number(value.toString())
+ } catch (err) {
+ return 0
+ }
+ }
+ if (typeof value === 'object' && typeof value.toString === 'function') {
+ const n = Number(value.toString())
+ return isFinite(n) ? n : 0
+ }
+ return 0
+}
+
+// -------------------------
+// ACCOUNTS
+// -------------------------
+
+export function convertBalances (native: BalanceResponse, tokenBalances: TokenBalance[]): any[] {
+ const result = []
+
+ // ETH → µETH
+ const ethRaw = toNumeric(native.balance)
+ // Convert wei to micro-ETH (µETH): 1 ETH = 1e18 wei = 1e6 µETH => divide wei by 1e12
+ const ethAmount = isFinite(ethRaw) ? ethRaw / 1e12 : 0
+
+ result.push({
+ id: 'arbitrum-one-main',
+ type: 'checking',
+ title: 'Arbitrum One (ETH)',
+ instrument: 'µETH',
+ balance: ethAmount, // ZenMoney interprets this as µETH
+ syncIds: ['arbitrum-one-main']
+ })
+
+ // Tokens
+ for (const tb of tokenBalances) {
+ const token = SUPPORTED_TOKENS.find(
+ (t) => t.contract.toLowerCase() === tb.contract.toLowerCase()
+ )
+
+ if (token == null) continue
+
+ const raw = toNumeric(tb.balance)
+ const amount = isFinite(raw) ? raw / 10 ** token.decimals : 0
+
+ result.push({
+ id: `arbitrum-one-${token.symbol.toLowerCase()}`,
+ type: 'checking',
+ title: `Arbitrum One (${token.symbol})`,
+ instrument: token.symbol,
+ balance: amount,
+ syncIds: [`arbitrum-one-${token.symbol.toLowerCase()}`]
+ })
+ }
+
+ return result
+}
+
+// -------------------------
+// TRANSACTIONS
+// -------------------------
+
+export function convertTransactions (
+ nativeTxs: Transaction[],
+ tokenTxs: TokenTransfer[],
+ address: string
+): any[] {
+ const result = []
+ const addr = address.toLowerCase()
+
+ // ETH transactions
+ for (const tx of nativeTxs) {
+ const value = toNumeric(tx.value) / 1e12
+ const fee = (toNumeric(tx.gasUsed) * toNumeric(tx.gasPrice)) / 1e12
+
+ // skip completely empty transactions (neither value nor fee)
+ if ((value === 0 || !isFinite(value)) && (fee === 0 || !isFinite(fee))) {
+ continue
+ }
+
+ const from = tx.from.toLowerCase()
+ const isOutgoing = from === addr
+ const sign = isOutgoing ? -1 : 1
+
+ result.push({
+ hold: null,
+ date: new Date(Number(tx.timeStamp) * 1000),
+ movements: [
+ {
+ id: tx.hash,
+ account: { id: 'arbitrum-one-main' },
+ invoice: null,
+ sum: sign * value,
+ fee: isOutgoing ? fee : 0
+ }
+ ],
+ merchant: {
+ fullTitle: normalizeMerchant(isOutgoing ? tx.to : tx.from, address),
+ mcc: null,
+ location: null
+ },
+ comment: null
+ })
+ }
+
+ // ERC20 transactions
+ for (const tx of tokenTxs) {
+ const token = SUPPORTED_TOKENS.find(
+ (t) => t.contract.toLowerCase() === tx.contract.toLowerCase()
+ )
+
+ if (token == null) continue
+
+ const value = toNumeric(tx.value) / (10 ** token.decimals)
+ if (value === 0) continue
+
+ const from = tx.from.toLowerCase()
+ // const to = tx.to.toLowerCase()
+
+ const isOutgoing = from === addr
+ const sign = isOutgoing ? -1 : 1
+
+ result.push({
+ hold: null,
+ date: new Date(Number(tx.timeStamp) * 1000),
+ movements: [
+ {
+ id: `${tx.hash}-${token.symbol}`,
+ account: { id: `arbitrum-one-${token.symbol.toLowerCase()}` },
+ invoice: null,
+ sum: sign * value,
+ fee: 0
+ }
+ ],
+ merchant: {
+ fullTitle: isOutgoing ? tx.to : tx.from,
+ mcc: null,
+ location: null
+ },
+ comment: null
+ })
+ }
+
+ return result
+}
diff --git a/src/plugins/arbitrum_one/index.ts b/src/plugins/arbitrum_one/index.ts
new file mode 100644
index 000000000..ba6ec3359
--- /dev/null
+++ b/src/plugins/arbitrum_one/index.ts
@@ -0,0 +1 @@
+export { scrape } from './plugin'
diff --git a/src/plugins/arbitrum_one/merchants.ts b/src/plugins/arbitrum_one/merchants.ts
new file mode 100644
index 000000000..7929680f9
--- /dev/null
+++ b/src/plugins/arbitrum_one/merchants.ts
@@ -0,0 +1,36 @@
+export const KNOWN_CONTRACTS: Record = {
+ // DEX
+ '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45': 'Uniswap V3 Router',
+ '0x1b02da8cb0d097eb8d57a175b88c7d8b47997506': 'SushiSwap Router',
+ '0x2df1adb252afb77fe762586fa6c116857f163df7': 'PancakeSwap Router',
+
+ // Bridges
+ '0x0000000000000000000000000000000000000064': 'Arbitrum Bridge',
+ '0x3c2269811836af69497e5f486a85d7316753cf62': 'LayerZero Endpoint',
+ '0x8731d54e9d02c286767d56ac03e8037c07e01e98': 'Stargate Router',
+
+ // Lending / Perps
+ '0x5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2': 'Aave Lending Pool',
+ '0x32df62dc3aed2cd6224193052ce665dc18165841': 'Radiant Capital',
+ '0x489ee077994b6658eafa855c308275ead8097c4a': 'GMX Exchange',
+
+ // System
+ '0x0000000000000000000000000000000000000000': 'System'
+}
+
+export function normalizeMerchant (addr: string, user: string): string {
+ if (addr == null || addr === '') return 'Unknown'
+
+ const a = addr.toLowerCase()
+ const u = user.toLowerCase()
+
+ if (a === u) return 'Self'
+
+ if (KNOWN_CONTRACTS[a] != null) return KNOWN_CONTRACTS[a]
+
+ if (a.startsWith('0x') && a.length === 42) {
+ return `Contract ${addr.slice(0, 6)}…${addr.slice(-4)}`
+ }
+
+ return addr
+}
diff --git a/src/plugins/arbitrum_one/plugin.ts b/src/plugins/arbitrum_one/plugin.ts
new file mode 100644
index 000000000..f570413d6
--- /dev/null
+++ b/src/plugins/arbitrum_one/plugin.ts
@@ -0,0 +1,60 @@
+import type { ScrapeFunc } from '../../types/zenmoney'
+import { ArbitrumOneApi } from './api'
+import { convertBalances, convertTransactions } from './converter'
+import type { Preferences } from './types'
+
+export const scrape: ScrapeFunc = async ({ preferences, fromDate }) => {
+ const apiKey = preferences.apiKey
+
+ // Split the addresses string by comma
+ const raw = preferences.account
+
+ // raw can be a string or string[]
+ const addresses = Array.isArray(raw)
+ ? raw
+ : raw.split(',').map(a => a.trim())
+
+ const normalized = addresses
+ .map((a: string) => a.toLowerCase())
+ .filter(a => a.length > 0)
+
+ const api = new ArbitrumOneApi(apiKey)
+
+ const allAccounts: any[] = []
+ const allTransactions: any[] = []
+
+ for (const address of normalized) {
+ // Balances
+ const [nativeBalance, tokenBalances] = await Promise.all([
+ api.getBalance(address),
+ api.getTokenBalances(address)
+ ])
+
+ const accounts = convertBalances(nativeBalance, tokenBalances)
+ allAccounts.push(...accounts)
+
+ // Transactions
+ const [nativeTxs, tokenTxs] = await Promise.all([
+ api.getTransactions(address, fromDate),
+ api.getTokenTransfers(address, fromDate)
+ ])
+
+ const fromTs = fromDate.getTime()
+
+ const filteredNative = nativeTxs.filter(
+ tx => Number(tx.timeStamp) * 1000 >= fromTs
+ )
+
+ const filteredToken = tokenTxs.filter(
+ tx => Number(tx.timeStamp) * 1000 >= fromTs
+ )
+
+ const transactions = convertTransactions(filteredNative, filteredToken, address)
+ allTransactions.push(...transactions)
+ }
+
+ return {
+ accounts: allAccounts,
+ transactions: allTransactions
+ }
+}
diff --git a/src/plugins/arbitrum_one/preferences.xml b/src/plugins/arbitrum_one/preferences.xml
new file mode 100755
index 000000000..84a09f262
--- /dev/null
+++ b/src/plugins/arbitrum_one/preferences.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
diff --git a/src/plugins/arbitrum_one/supportedTokens.ts b/src/plugins/arbitrum_one/supportedTokens.ts
new file mode 100644
index 000000000..889a66f74
--- /dev/null
+++ b/src/plugins/arbitrum_one/supportedTokens.ts
@@ -0,0 +1,12 @@
+export const SUPPORTED_TOKENS = [
+ {
+ symbol: 'USDT',
+ decimals: 6,
+ contract: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9'
+ },
+ {
+ symbol: 'USDC',
+ decimals: 6,
+ contract: '0xaf88d065e77c8cc2239327c5edb3a432268e5831'
+ }
+]
diff --git a/src/plugins/arbitrum_one/types.ts b/src/plugins/arbitrum_one/types.ts
new file mode 100644
index 000000000..7fdfab50d
--- /dev/null
+++ b/src/plugins/arbitrum_one/types.ts
@@ -0,0 +1,42 @@
+export interface ArbiscanResponse {
+ status: string
+ message: string
+ result: T
+}
+
+export interface BalanceResponse {
+ balance: string
+}
+
+export interface TokenBalance {
+ contract: string
+ balance: string
+ symbol: string
+}
+
+export interface Transaction {
+ hash: string
+ timeStamp: string
+ from: string
+ to: string
+ value: string
+ gasUsed: string
+ gasPrice: string
+ contractAddress?: string
+ contract?: string
+}
+
+export interface TokenTransfer {
+ hash: string
+ timeStamp: string
+ from: string
+ to: string
+ value: string
+ contractAddress: string
+ contract: string
+}
+
+export interface Preferences {
+ apiKey: string
+ account: string | string[]
+}