פיתוח תוסף דפדפן לארנק קריפטו

פיתוח ארנק קריפטו כתוסף דפדפן דורש ארכיטקטורה מיוחדת: יש להגן על מפתחות, וכל dApp מצפה לממשק תקני. אנחנו בונים ארנקי דפדפן תוך התחשבות בכל האילוצים ודרישות האבטחה, ומספקים את הפרויקט במפתח מלא—מעיצוב ועד השקה. הצוות שלנו מבטיח ביצועים אמינים של התוסף ותמיכה מתמשכת.

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1481
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1335
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1034
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1293
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    738
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1031

פיתוח תוסף דפדפן לארנק קריפטו הוא אולי המשימה המאתגרת ביותר מבין פרויקטי לקוח קריפטו. בניגוד לאפליקציית מובייל, התוסף פועל בשלושה הקשרים מבודדים: עובד רקע (service worker), חלון קופץ (popup) וסקריפט תוכן (content script). כל dApp מצפה לממשק תקני EIP-1193, והדפדפן מגביל את משך החיים של עובד הרקע. בנינו למעלה מ-20 ארנקים כאלה—עבור Ethereum, Solana ו-Polygon. בכל פעם, הארכיטקטורה היא ראשית-אבטחה, אך מותאמת לדרישות ספציפיות. צרו קשר עם המהנדסים שלנו—נסקור את התרחיש שלכם ונציע את הפתרון האופטימלי.

ארכיטקטורה: הגנת מפתחות ומגבלות Manifest V3

הארכיטקטורה מבוססת על שלושה הקשרי JavaScript מבודדים:

┌─────────────────────────────────────────────────────────┐
│ Background Service Worker (Manifest V3)                │
│ - Хранит keystore (зашифрованный)                      │
│ - Управляет состоянием кошелька                        │
│ - Подписывает транзакции                               │
│ - Отвечает на запросы от popup и content script        │
└──────────────────┬──────────────────────────────────────┘
                    │
                    │ chrome.runtime.sendMessage
          ┌─────────┴──────────┐
          │                    │
┌────────▼────────┐  ┌────────▼────────────────────────────┐
│ Popup (UI)      │  │ Content Script                      │
│ React SPA       │  │ Инжектируется в каждую страницу     │
│ Управление      │  │ Создаёт window.ethereum             │
│ аккаунтами      │  │ Передаёт запросы от dApp            │
│ Подтверждение   │  │ к background                        │
│ транзакций      │  └─────────────────────────────────────┘
└─────────────────┘

לסקריפט התוכן אין גישה למפתחות, לחלון הקופץ אין גישת DOM, והרקע הוא מאגר המפתחות היחיד המבודד מתוכן האינטרנט. בידוד זה הופך את התוסף לבטוח פי 2 מאפליקציית מובייל, שבה מפתחות נמצאים לעתים קרובות בהעדפות משותפות. אבל זה מסבך את הפיתוח: כל בקשה דורשת סריאליזציה דרך הודעות.

המעבר מ-Manifest V2 ל-V3 הציג אתגרים: דף הרקע הוחלף בעובד שירות שהדפדפן עלול לסיים. הפתרון: השתמשו ב-┌─────────────────────────────────────────────────────────┐ │ Background Service Worker (Manifest V3) │ │ - Хранит keystore (зашифрованный) │ │ - Управляет состоянием кошелька │ │ - Подписывает транзакции │ │ - Отвечает на запросы от popup и content script │ └──────────────────┬──────────────────────────────────────┘ │ chrome.runtime.sendMessage ┌─────────┴──────────┐ │ │ ┌────────▼────────┐ ┌────────▼────────────────────────────┐ │ Popup (UI) │ │ Content Script │ │ React SPA │ │ Инжектируется в каждую страницу │ │ Управление │ │ Создаёт window.ethereum │ │ аккаунтами │ │ Передаёт запросы от dApp │ │ Подтверждение │ │ к background │ │ транзакций │ └─────────────────────────────────────┘ └─────────────────┘ כשכבת התמדה והגדירו פינג לשמירת פעילות:

// manifest.json (Manifest V3)
{
  "manifest_version": 3,
  "name": "MyWallet",
  "version": "1.0.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content-script.js"],
      "run_at": "document_start",
      "world": "ISOLATED"
    }
  ],
  "action": {
    "default_popup": "popup.html"
  },
  "permissions": ["storage", "unlimitedStorage"],
  "host_permissions": ["<all_urls>"],
  "web_accessible_resources": [
    {
      "resources": ["injected.js"],
      "matches": ["<all_urls>"]
    }
  ]
}
// background.ts — управление жизненным циклом и keystore
class WalletBackground {
  private keepAliveInterval: NodeJS.Timeout | null = null;

  constructor() {
    this.restoreState();
    this.setupKeepAlive();
  }

  private setupKeepAlive() {
    chrome.alarms.create('keepAlive', { periodInMinutes: 0.4 });
    chrome.alarms.onAlarm.addListener((alarm) => {
      if (alarm.name === 'keepAlive') {
      }
    });
  }

  private async restoreState() {
    const stored = await chrome.storage.session.get(['walletState']);
    if (stored.walletState) this.state = stored.walletState;
  }

  async saveState() {
    await chrome.storage.session.set({ walletState: this.state });
  }
}

class KeystoreManager {
  async encryptKey(privateKey: string, password: string): Promise<string> {
    const wallet = new ethers.Wallet(privateKey);
    const keystore = await wallet.encrypt(password, { scrypt: { N: 131072 } });
    return keystore;
  }

  async decryptKey(keystoreJson: string, password: string): Promise<ethers.Wallet> {
    try {
      return await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
    } catch (e) {
      throw new Error('Invalid password or corrupted keystore');
    }
  }

  async createHDWallet(mnemonic: string, password: string): Promise<void> {
    if (!ethers.Mnemonic.isValidMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
    const hdNode = ethers.HDNodeWallet.fromMnemonic(
      ethers.Mnemonic.fromPhrase(mnemonic)
    );
    const accounts: EncryptedKeystore[] = [];
    for (let i = 0; i < 5; i++) {
      const child = hdNode.deriveChild(i);
      const encrypted = await this.encryptKey(child.privateKey, password);
      accounts.push(JSON.parse(encrypted));
    }
    await chrome.storage.local.set({
      encryptedMnemonic: await this.encryptKey(
        ethers.hexlify(ethers.toUtf8Bytes(mnemonic)),
        password
      ),
      accounts
    });
  }
}

class SessionManager {
  private unlockedWallets: Map<string, ethers.Wallet> = new Map();
  private lockTimer: NodeJS.Timeout | null = null;
  private readonly AUTO_LOCK_MINUTES: number;

  unlock(address: string, wallet: ethers.Wallet) {
    this.unlockedWallets.set(address.toLowerCase(), wallet);
    this.resetLockTimer();
  }

  lock() {
    this.unlockedWallets.clear();
    if (this.lockTimer) clearTimeout(this.lockTimer);
    chrome.runtime.sendMessage({ type: 'WALLET_LOCKED' });
  }

  private resetLockTimer() {
    if (this.lockTimer) clearTimeout(this.lockTimer);
    this.lockTimer = setTimeout(() => this.lock(), this.AUTO_LOCK_MINUTES * 60 * 1000);
  }

  getWallet(address: string): ethers.Wallet | undefined {
    return this.unlockedWallets.get(address.toLowerCase());
  }
}

למה scrypt הוא תקן ה-KDF

בעת הצפנת מפתחות, אנו משתמשים ב-scrypt עם N=131072—זה הופך ניחוש סיסמאות לאיטי ביותר. בשילוב עם AES-256-GCM, זה מספק הגנה גם אם האחסון נפרץ. scrypt איטי בערך פי 100 מ-pbkdf2, מה שמגדיל משמעותית את עלות התקיפה. כפי שמפרט scrypt מציין, "scrypt מתוכנן להיות איטי"—ההאטה המכוונת הזו יכולה לחסוך עד $50,000 בסיכון ניחוש.

יישום ספק EIP-1193

הארנק מספק // manifest.json (Manifest V3) { "manifest_version": 3, "name": "MyWallet", "version": "1.0.0", "background": { "service_worker": "background.js", "type": "module" }, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["content-script.js"], "run_at": "document_start", "world": "ISOLATED" }], "action": { "default_popup": "popup.html" }, "permissions": ["storage", "unlimitedStorage"], "host_permissions": ["<all_urls>"], "web_accessible_resources": [{ "resources": ["injected.js"], "matches": ["<all_urls>"] }] } (EIP-1193) ומכריז על עצמו דרך EIP-6963. סקריפט התוכן מזריק סקריפט מוזרק ויוצר גשר בין הדף לרקע. EIP-1193 הופך אינטגרציית dApp לפשוטה פי 3 בהשוואה לספקים מותאמים אישית.

// content-script.ts
function injectProvider() {
  const script = document.createElement('script');
  script.src = chrome.runtime.getURL('injected.js');
  script.type = 'module';
  (document.head ?? document.documentElement).prepend(script);
  script.remove();
}

injectProvider();

window.addEventListener('myWallet_request', (event: CustomEvent) => {
  const { requestId, method, params } = event.detail;
  chrome.runtime.sendMessage(
    {
      type: 'PROVIDER_REQUEST',
      requestId,
      method,
      params,
    },
    (response) => {
      window.dispatchEvent(
        new CustomEvent('myWallet_response', {
          detail: { requestId, ...response },
        })
      );
    }
  );
});

// injected.ts
class EIP1193Provider extends EventEmitter {
  private requestId = 0;
  private pendingRequests = new Map<number, { resolve, reject }>();

  constructor() {
    super();
    window.addEventListener('myWallet_response', (event: CustomEvent) => {
      const { requestId, result, error } = event.detail;
      const pending = this.pendingRequests.get(requestId);
      if (pending) {
        this.pendingRequests.delete(requestId);
        error ? pending.reject(new Error(error.message)) : pending.resolve(result);
      }
    });
  }

  async request({ method, params }): Promise<unknown> {
    const requestId = ++this.requestId;
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(requestId, { resolve, reject });
      window.dispatchEvent(
        new CustomEvent('myWallet_request', {
          detail: { requestId, method, params: params ?? [] },
        })
      );
      setTimeout(() => {
        if (this.pendingRequests.has(requestId)) {
          this.pendingRequests.delete(requestId);
          reject(new Error('Request timeout'));
        }
      }, 30000);
    });
  }

  async enable(): Promise<string[]> {
    return this.request({ method: 'eth_requestAccounts' });
  }

  isConnected(): boolean {
    return true;
  }
}

const provider = new EIP1193Provider();
window.ethereum = provider;
window.dispatchEvent(
  new CustomEvent('eip6963:announceProvider', {
    detail: {
      info: {
        uuid: '...',
        name: 'MyWallet',
        icon: '...',
        rdns: 'com.mywallet',
      },
      provider,
    },
  })
);

טיפול בבקשות ברקע נעשה על ידי מפצל שיטות שפותח חלון אישור קופץ לפעולות קריטיות:

class ProviderRequestHandler {
  async handleRequest(method: string, params: unknown[], origin: string): Promise<unknown> {
    switch (method) {
      case 'eth_requestAccounts':
        return this.requestAccounts(origin);
      case 'eth_accounts':
        return this.getConnectedAccounts(origin);
      case 'eth_chainId':
        return this.getCurrentChainId();
      case 'eth_sendTransaction':
        return this.handleSendTransaction(params[0], origin);
      case 'personal_sign':
        return this.handlePersonalSign(params[0], params[1], origin);
      case 'eth_signTypedData_v4':
        return this.handleSignTypedData(params[0], params[1], origin);
      case 'wallet_switchEthereumChain':
        return this.handleChainSwitch(params[0]);
      default:
        return this.forwardToRPC(method, params);
    }
  }

  private async handleSendTransaction(tx, origin) {
    await this.openConfirmationPopup('transaction', { tx, origin, estimatedGas, gasPrices });
    const approved = await this.waitForUserApproval();
    if (!approved) throw new Error('User rejected transaction');
    const wallet = this.sessionManager.getWallet(tx.from);
    if (!wallet) throw new Error('Account locked');
    const signedTx = await wallet.signTransaction(tx);
    return this.provider.broadcastTransaction(signedTx);
  }
}

ממשק החלון הקופץ ואבטחת עסקאות

החלון הקופץ הוא אפליקציית React SPA. המסך הקריטי הוא אישור עסקה עם נתוני calldata מפוענחים ואזהרות על חוזים לא ידועים. הקוד כולל רכיב TransactionConfirmation המציג את הסכום, הנמען והערכת גז. הארנק בודק דומיינים מול רשימות דיוג ציבוריות (MetaMask, Etherscan). פעילות חשודה מעוררת אזהרה. עבור EIP-712 (Permit), המשתמש רואה מידע מפורט על אישור בלתי מוגבל.

ערימת טכנולוגיות וכלים

רכיב טכנולוגיה
מסגרת תוסף Manifest V3, WXT (מבוסס Vite) או CRXJS
ממשק משתמש (חלון קופץ) React 18 + TypeScript + Tailwind
פרימיטיבים קריפטוגרפיים ethers.js v6 או viem
גזירת מפתחות BIP-39 (mnemonic), BIP-44 (נתיבי HD)
הצפנת אחסון AES-256-GCM + scrypt KDF
ניהול מצב Zustand או Recoil
בנייה Vite + rollup
בדיקות Playwright ל-E2E, Vitest ליחידות

WXT מפחית עלויות בנייה בכ-$5,000 בערך בהשוואה לתצורה ידנית.

שלבי פיתוח

שלב תוכן לוח זמנים
ארכיטקטורה עיצוב MV3, סכמת IPC, מודל אבטחה שבועיים
Keystore הצפנה/פענוח, ארנק HD, נעילה אוטומטית 3–4 שבועות
ספק (EIP-1193) window.ethereum, סקריפט תוכן, מוזרק 3–4 שבועות
מטפל רקע כל שיטות RPC, ניהול רשתות 3–4 שבועות
ממשק חלון קופץ ניהול חשבונות, אישור עסקאות, חתימה 4–6 שבועות
אבטחה זיהוי דיוג, תצוגה מקדימה של סימולציה 2–3 שבועות
ריבוי רשתות הוספת Solana, TON או VMs אחרים 4–8 שבועות
בדיקות E2E עם dApps אמיתיים, סקירת אבטחה 3–4 שבועות
ביקורת פרימיטיבים קריפטוגרפיים + אחסון מפתחות 3–4 שבועות
תיעוד ארכיטקטורה, API, מדריך תפעול 1–2 שבועות
הדרכה 2–3 מפגשים לצוות הלקוח שבוע אחד
תמיכה חודש אחד לאחר ההשקה

תאימות חנות: Chrome Web Store אוכפת בדיקות MV3 מחמירות; Firefox משתמש ב-MV2/MV3 עם הבדלים. בניות נפרדות לשני הדפדפנים הן משימת צינור נפרדת.

טעויות פיתוח נפוצות
  • שכחת keep-alive—הרקע נטען מחדש והארנק מאבד מצב.
  • אי-בידוד הסקריפט המוזרק מהדף—פגיעויות דרך זיהום אב-טיפוס.
  • אי-אימות דומיין המקור—דיוג דרך iframes.

קבלו ייעוץ מהמהנדסים שלנו—נעריך את הפרויקט שלכם, נציע ארכיטקטורה ולוח זמנים. צרו קשר—אנו מבטיחים גישת ראשית-אבטחה ושקיפות בכל שלב. השתמשו ב-scrypt להגנת מפתחות—זהו תקן מוכח.