> For the complete documentation index, see [llms.txt](https://docs.rome.builders/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rome.builders/ar/adlh-almtw-ryn/dual-lane-app.md).

# بناء تطبيق ثنائي المسار

شرح خطوة بخطوة لتطبيق ثنائي المسار — عقد Solidity واحد يستخدمه كلٌّ من مستخدم MetaMask ومستخدم محفظة Solana، مع توضيح ما يحدث بالضبط في كل خطوة.

تطبيق **تطبيق ثنائي المسار** هو عقد Solidity واحد يستخدمه كلٌّ من **ميتاماسك (EVM)** مستخدم و **محفظة Solana** يستخدمه مباشرة — نفس العقد، نفس الحالة، وكلٌ منهما باستخدام المحفظة التي لديه بالفعل. تشرح هذه الصفحة *بالضبط* ما الذي يحدث في كل خطوة، على كل جانب.

المثال عبارة عن **خزنة**: أنت `deposit` USDC ثم لاحقًا `withdraw` إياها. «Stake / unstake»، «supply / redeem»، «tip / claim» لها البنية نفسها.

## ما تكتبه — خزنة ERC-20 قياسية

في Rome، يظهر USDC الخاص بمستخدم سولانا على جانب EVM كـ **رمز ERC-20** — غلاف SPL لذلك الـ mint (مثلًا `wUSDC`). لذا عقدك خزنة رموز عادية: تسحب الرموز باستخدام `transferFrom` وتعيدها باستخدام `transfer`. لا شيء خاص بـ Rome:

```solidity
interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract Vault {
    IERC20 public immutable token;                 // غلاف wUSDC
    mapping(address => uint256) public balanceOf;
    constructor(IERC20 _token) { token = _token; }

    function deposit(uint256 amount) external {
        require(token.transferFrom(msg.sender, address(this), amount));
        balanceOf[msg.sender] += amount;
    }
    function withdraw(uint256 amount) external {
        balanceOf[msg.sender] -= amount;
        require(token.transfer(msg.sender, amount));
    }
}
```

> **لماذا ERC-20، وليس `قابل للدفع` / `msg.value`?** الرصيد القابل للإنفاق لمستخدم سولانا هو **حساب الرمز SPL الخاص بمحفظته**، ويظهر واحدًا لواحد عبر هذا الغلاف ERC-20 — *وليس* رصيد EVM الأصلي. لذا يمكن لمستخدم سولانا دائمًا تمويل `transferFrom`، لكن `deposit() قابلة للدفع` ستحتاج إلى قيمة أصلية لا يملكونها عند السكون. ابنِ حول الرمز وسيتصرف المساران بالطريقة نفسها.

انشره باستخدام Foundry أو Hardhat، مع توجيه المُنشئ إلى عنوان الغلاف لرمزك (من [السجل](https://github.com/rome-protocol/rome-registry)). في Rome، رمز الغاز هو USDC، لذا تحتاج إلى رصيد بسيط من USDC كغاز للنشر.

## المساران

| المسار     | المحفظة                                  | كيف يستدعيه التطبيق                                                             |
| ---------- | ---------------------------------------- | ------------------------------------------------------------------------------- |
| **EVM**    | ميتاماسك (مفتاح EVM)                     | `submitRomeTx` — كتابة Rome القياسية                                            |
| **Solana** | محفظة سولانا مثل Solflare (مفتاح سولانا) | `submitRomeTxV1` — معاملة Solana v1 واحدة لكل استدعاء، دون الحاجة إلى مفتاح EVM |

مسار EVM عادي. بقية هذه الصفحة هي **مسار Solana** — النصف المثير للاهتمام.

## الفكرة الأساسية: الكيان الاصطناعي مجرد وسيط تمرير

هوية EVM لمستخدم سولانا هي **العنوان الاصطناعي** — `keccak256(solana_pubkey)[12:]`. إنه `msg.sender` في العقد، لكنه **لا يحتفظ بشيء عند السكون.** أموال المستخدم موجودة في **محفظة Solana** (كـ SPL USDC)، وعلى جانب EVM فإن نفس الرصيد هو ما `wUSDC.balanceOf(synthetic)` يقرأه. تتدفق القيمة *من خلال* عبر الكيان الاصطناعي:

* **إلى الداخل** (الإيداع): حساب رمز المحفظة → حساب رمز الكيان الاصطناعي → العقد (عبر `transferFrom`).
* **إلى الخارج** (السحب): العقد → حساب رمز الكيان الاصطناعي → حساب رمز المحفظة.

يعود صافي الكيان الاصطناعي إلى الصفر بعد كل ذهاب وإياب.

## مرة واحدة: التفعيل (تهيئة الكيان الاصطناعي)

حساب الكيان الاصطناعي الجديد على السلسلة لا يوجد حتى تنشئه. في أول مرة يتصرف فيها مستخدم سولانا، يكون كيانه الاصطناعي **مهيأ** باستخدام `create_pda` استدعاء — وبعد ذلك يمكن توقيع الاستدعاءات التي تنقل القيمة (ERC-20 `transferFrom`، والتمرير) بواسطةه.

`submitRomeTxV1` لا يقوم بذلك نيابةً عنك، لذا قم بالتهيئة صراحةً، مرة واحدة، قبل أول استدعاء ينقل القيمة. مكان جيد لذلك هو خطوة «Activate»، أو مباشرة قبل أول approve:

```javascript
import { submitRomeTxV1, isSyntheticProvisioned, buildCreatePdaCall, syntheticAddress } from "@rome-protocol/sdk";

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, chainId, programId, signer }; // signer: a Keypair in scripts, a LaneSigner in a wallet app
if (!(await isSyntheticProvisioned(connection, programId, synthetic))) {
  await submitRomeTxV1(deps, buildCreatePdaCall(synthetic)); // one create_pda
}
```

## لمرة واحدة: امنح الغلاف

غلاف wUSDC ينقل رموز المستخدم باعتباره **مفوّض SPL** من حساب الرموز الخاص بهم، لذا يتعين على كل مستخدم منحَه هذا الحق مرة واحدة لكل غلاف، قبل أول إيداع. ينطبق هذا على مستخدم ميتاماسك وعلى عنوان الكيان الاصطناعي لمستخدم سولانا على حد سواء. بدون هذه المنحة، فإن كل `transferFrom` خروج من المحفظة يفشل ويُلغى الإيداع بخطأ SPL `0x4` (عدم تطابق المالك).

تحقّق من خلال دالة العرض الخاصة بالغلاف `isEnabled(user)`. إذا أعادت `false`، فأرسل `approve_spl(wrapper, 2^64-1, mint)` مباشرة إلى الـ Helper precompile عند `0xff00000000000000000000000000000000000009`؛ الـ mint هو `mint_id()` على الغلاف. على مسار EVM، هذا `submitRomeTx` استدعاء، وعلى مسار سولانا `submitRomeTxV1` استدعاء:

```javascript
import { submitRomeTx, submitRomeTxV1, syntheticAddress } from "@rome-protocol/sdk";
import { createPublicClient, encodeFunctionData, http, parseAbi } from "viem";

const HELPER = "0xff00000000000000000000000000000000000009";
const wrapperAbi = parseAbi(["function isEnabled(address user) view returns (bool)", "function mint_id() view returns (bytes32)"]);
const grantAbi = parseAbi(["function approve_spl(address spender, uint64 amount, bytes32 mint)"]);

const publicClient = createPublicClient({ transport: http(rpcUrl) }); // عميل viem على RPC الخاص بالسلسلة

// who: the MetaMask address, or syntheticAddress(wallet.publicKey) for a Solana user
let grant = null; // يبقى null عندما تكون isEnabled صحيحة
if (!(await publicClient.readContract({ address: wUSDC, abi: wrapperAbi, functionName: "isEnabled", args: [who] }))) {
  const mint = await publicClient.readContract({ address: wUSDC, abi: wrapperAbi, functionName: "mint_id" });
  grant = { to: HELPER, data: encodeFunctionData({ abi: grantAbi, functionName: "approve_spl", args: [wUSDC, 2n ** 64n - 1n, mint] }) };
}
// EVM lane:    if (grant) await submitRomeTx(provider, { from: who, ...grant });
// Solana lane: if (grant) await submitRomeTxV1(deps, grant);
```

## ما يحتاجه كل جانب

|                           | يحتاج                                                    | لماذا                                                                                                      |
| ------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **مستخدم سولانا**         | **SOL** (قليلًا)                                         | يدفع رسوم معاملة سولانا في كل معاملة على أي مسار                                                           |
|                           | **USDC كرمز SPL** في محفظتهم                             | القيمة التي يودعونها (وتظهر على جانب EVM كـ `wUSDC`)                                                       |
| **مستخدم EVM (ميتاماسك)** | **USDC** كرصيد الغاز الخاص بهم على Rome                  | الغاز، والقيمة التي يودعونها بعد أن تُغلَّف؛ ويزيدونه عبر **جسر USDC** إلى Rome (لا يوجد صنبور)            |
|                           | أ **تغليف** لذلك الغاز إلى **wUSDC** (`withdraw_to_ata`) | الخزنة تأخذ wUSDC، وهو الغلاف ERC-20، وليس الغاز الأصلي. رصيد USDC الغاز في ميتاماسك ليس wUSDC حتى يُغلَّف |
|                           | المنح لمرة واحدة **إذن الغلاف** (`approve_spl`)          | يتيح لغلاف wUSDC تحريك رموزهم؛ انظر [لمرة واحدة: امنح الغلاف](#one-time-grant-the-wrapper)                 |
| **أنت (المطوّر)**         | محفظة بها **USDC** غاز على Rome                          | لنشر العقد                                                                                                 |

## القيمة الداخلة — الإيداع (خطوة بخطوة)

لدى مستخدم سولانا SOL + USDC في محفظته على سولانا. كل خطوة هي معاملة Solana v1، **موقعة من تلك المحفظة**، ويرسلها التطبيق إلى Solana RPC. تُبنى خطوة التمويل في التطبيق وتوقّعها المحفظة بمطالبة واحدة؛ ويُركِّب الوكيل كل استدعاء للعقد، وتوقّعه المحفظة بعد رسالة تفويض مقروءة:

1. **خطوة التمويل** — `buildFundLeg(...)` → `submitSolanaInstructionsV1(...)`. ينشئ حساب رمز USDC الخاص بالكيان الاصطناعي (إن لزم) ويشغّل **`ActivateAta`**، ناقلًا `الكمية` من USDC من حساب الرموز الخاص بالمحفظة **إلى**. الآن `wUSDC.balanceOf(synthetic)` يعرض ذلك الرصيد.
2. **امنح الغلاف** (عندما `isEnabled` تكون false) — `submitRomeTxV1(deps, { to: HELPER, data: approve_spl(wUSDC, 2^64-1, mint) })`. انظر [لمرة واحدة: امنح الغلاف](#one-time-grant-the-wrapper). *(هيّئ الكيان الاصطناعي قبل هذا إذا كان هذا أول استدعاء للمسار للمستخدم.)*
3. **الموافقة** — `submitRomeTxV1(deps, { to: wUSDC, data: approve(vault, amount) })`. يتيح للخزنة سحب الرموز.
4. **إيداع** — `submitRomeTxV1(deps, { to: vault, data: deposit(amount) })`. تُشغّل الخزنة `transferFrom(synthetic, vault, amount)` — ينتقل USDC من الكيان الاصطناعي إلى الخزنة، ويُقيَّد باسم عنوان الكيان الاصطناعي.

**الأثر الصافي:** انتقل USDC **محفظة سولانا → (الكيان الاصطناعي) → الخزنة.**

```javascript
import { syntheticAddress, buildFundLeg, submitSolanaInstructionsV1, submitRomeTxV1 } from "@rome-protocol/sdk";
import { encodeFunctionData, erc20Abi } from "viem";

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, chainId, programId, signer }; // signer: a LaneSigner (payer, signAuthHash, signV1Wire) or a Keypair

// 1) خطوة التمويل — USDC المحفظة → حساب رمز الكيان الاصطناعي (توقّع المحفظة)
await submitSolanaInstructionsV1(
  buildFundLeg({ programId, chainId, mint: usdcMint, amount: depositAmount, wallet: wallet.publicKey, synthetic }),
  { connection, signer },
);

// 2) امنح الغلاف، مرة واحدة (الكيان الاصطناعي مهيأ بالفعل)؛ `grant` هو الاستدعاء المُنشأ تحت "مرة واحدة: امنح الغلاف"، أو null عندما تكون isEnabled صحيحة
if (grant) await submitRomeTxV1(deps, grant);

// 3) وافق على الخزنة
await submitRomeTxV1(deps, { to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });

// 4) الإيداع — الخزنة تسحب عبر transferFrom
await submitRomeTxV1(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

## القيمة الخارجة — السحب (خطوة بخطوة)

الآن يسحب المستخدم. أيضًا موقعة من المحفظة، مع مطالبتين لكل استدعاء:

1. **سحب** — `submitRomeTxV1(deps, { to: vault, data: withdraw(amount) })`. `vault.withdraw` تُشغّل `transfer(synthetic, amount)` — ينتقل USDC من الخزنة إلى **الخاص بالكيان الاصطناعي** حساب الرموز.
2. **خطوة التمرير** — `buildSweepLeg(...)` تمنحك `HelperProgram.transfer_spl` الاستدعاء + الحسابات؛ شغّلها (أنشئ حساب الرموز الخاص بالمحفظة إذا لزم باستخدام `submitSolanaInstructionsV1`، ثم أرسل استدعاء Helper عبر `submitRomeTxV1`؛ يجد الوكيل حسابات الرموز) لنقل USDC من الكيان الاصطناعي **إلى محفظة سولانا الخاصة بالمستخدم**. يعود صافي الكيان الاصطناعي إلى الصفر.

**الأثر الصافي:** انتقل USDC **الخزنة → (الكيان الاصطناعي) → محفظة سولانا الخاصة بالمستخدم.** لا شيء يبقى عالقًا.

```javascript
import { buildSweepLeg } from "@rome-protocol/sdk";

// 1) السحب — تعيد الخزنة USDC إلى حساب رموز الكيان الاصطناعي
await submitRomeTxV1(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "withdraw", args: [amount] }) });

// 2) خطوة التمرير — حساب رمز الكيان الاصطناعي → حساب رمز محفظة المستخدم نفسه
const sweep = buildSweepLeg({ programId, mint: usdcMint, amount, wallet: wallet.publicKey, synthetic });
await submitSolanaInstructionsV1([sweep.ensureWalletAtaIx], { connection, signer });
await submitRomeTxV1(deps, { to: sweep.helperTo, data: sweep.calldata });
```

## مقاسة على Hadrian

كل خطوة أدناه على مسار سولانا نُفِّذت كمعاملة version-1 واحدة على Hadrian:

| الخطوة                     | وحدات الحوسبة | بايتات | الحسابات |
| -------------------------- | ------------- | ------ | -------- |
| التهيئة (`create_pda`)     | 39,008        | 703    | 9        |
| منح الغلاف (`approve_spl`) | 50,687        | 899    | 13       |
| الموافقة                   | 79,335        | 769    | 10       |
| إيداع الخزنة               | 242,880       | 1,070  | 20       |
| سحب الخزنة                 | 280,903       | 1,037  | 19       |
| التمرير (`transfer_spl`)   | 46,810        | 899    | 13       |

## المطبات

هذه هي الأمور التي تُخطئ فيها معاملة مبنية يدويًا على مسار سولانا. يتولى SDK وموَرِّب الوكيل معظمها:

* **التهيئة.** يجب إنشاء حساب كيان اصطناعي جديد (`create_pda`) قبل أي استدعاء ينقل القيمة، وإلا فلن يستطيع توقيع التحويل. `submitRomeTxV1` لا يقوم بذلك نيابةً عنك: تحقّق من `isSyntheticProvisioned` وأرسل `buildCreatePdaCall(synthetic)` مرة واحدة.
* **امنح الغلاف مرة واحدة.** الغلاف ينقل الرموز بصفته المفوض SPL للمستخدم. تحقّق من `isEnabled` وأرسل `approve_spl` إلى Helper precompile قبل أول إيداع، وإلا سيتراجع الإيداع مع خطأ SPL `0x4`.
* **أنفق الغلاف، لا `msg.value`.** رصيد مستخدم سولانا هو حساب الرموز SPL الخاص به، ويظهر كغلاف ERC-20 — حرّكه باستخدام `transfer` / `transferFrom`، وليس القيمة الأصلية أبدًا.
* **مُركَّبة لك.** تتم معالجة ComputeBudget، ومحفظة الخزانة، واكتشاف الحسابات بواسطة موَرِّب الوكيل، لذا لا يظهر أي منها في كودك. قبل أن توقّع المحفظة، يتحقق SDK من المعاملة المركبة ويرفض أي شيء سوى الاستدعاء الذي فوّضه المستخدم.
* **إلى أين تُرسل.** توقّع المحفظة معاملة v1 و **يرسلها التطبيق إلى Solana RPC** — وليس إلى الوكيل، الذي يركبها (الاستدعاء المرحَّل هو الاستثناء الوحيد، أدناه؛ انظر [الاستدعاءات المُرحَّلة والاستدعاءات الطويلة](/ar/adlh-almtw-ryn/call-evm-from-solana.md#relayed-calls-and-long-calls)). على السلسلة، يستنبط البرنامج `msg.sender` من موقّع سولانا.
* **مطالبتان لكل استدعاء.** توقّع المحفظة رسالة تفويض مقروءة (`تفويض Rome` وهاش معاملة EVM)، ثم المعاملة، وكلاهما بالمفتاح نفسه.
* **تحقّق من أن محفظتك توقّع v1.** `signV1Wire` يجب أن تعيد التوقيع بطول 64 بايت على رسالة v1؛ عبر Wallet Standard يكون ذلك `solana:signTransaction` على السلك الخام و `signedTransaction.slice(-64)`. ليست كل المحافظ تفك ترميز معاملات v1 بعد، لذا اختبر محفظتك قبل البناء عليها. على devnet حاليًا، يوقّع Solflare هذه المعاملات؛ أما Phantom فلا يفعل ذلك بعد.
* **الاستدعاءات الكبيرة تُرحَّل.** يُرحَّل الاستدعاء الذي لا يتسع في معاملة واحدة بواسطة الوكيل بالتوقيع التفويضي نفسه، وتكون النتيجة `route: "relay"`. يُدفع الاستدعاء المرحَّل بعملة الغاز الأصلية للسلسلة، لذا زِد رصيد الكيان الاصطناعي بـ `fundSyntheticGas(deps, amountWei)`. انظر [الاستدعاءات المُرحَّلة والاستدعاءات الطويلة](/ar/adlh-almtw-ryn/call-evm-from-solana.md#relayed-calls-and-long-calls).
* **الغاز هو USDC.** لا يوجد صنبور — اجسر USDC إلى الداخل (انظر [الحصول على تمويل](/ar/almward/faucets.md)).

السابق `submitRomeTxSolanaLane` ما يزال موجودًا في SDK: فهو يبني معاملة سولانا قديمة في العميل (مطالبة محفظة واحدة، مع معالجة ComputeBudget ومحفظة الخزانة بواسطة SDK).

## التطبيق نفسه من ميتاماسك

يستدعي مستخدم EVM العقد نفسه بـ `submitRomeTx` — أدوات EVM قياسية، والغاز بـ USDC. هناك فرق واحد عن مستخدم سولانا: مستخدم ميتاماسك يملك **USDC غاز أصلية**، لا wUSDC. تأخذ الخزنة غلاف wUSDC، لذا يغلّف المستخدم جزءًا من غازه أولًا، ثم يمنح الغلاف مرة واحدة، ثم ينفذ خطوات ERC-20 كالمعتاد. لا توجد خطوات تمويل أو تمرير، لأن الرموز المغلفة موجودة بالفعل على عنوان EVM الخاص به.

1. **غلّف الغاز إلى wUSDC** — أرسل `withdraw_to_ata(wei)` إلى Withdraw precompile عند `0x4200000000000000000000000000000000000016`. يحرق تلك الكمية من الغاز الأصلي ويُضيف الكمية المكافئة إلى حساب رموز المستخدم في رمز الغاز، والذي `wUSDC.balanceOf(user)` ثم يظهر. `wei` يكون بوحدة الغاز ذات 18 منزلة عشرية ويجب أن يكون مضاعفًا لـ 10^12 (وحدة wUSDC واحدة ذات 6 منازل عشرية)، وإلا سيتراجع الاستدعاء. تجاوز هذه الخطوة إذا كان المستخدم يملك wUSDC بالفعل.
2. **امنح الغلاف** (عندما `isEnabled` تكون false) — نفس `approve_spl` الاستدعاء المذكور أعلاه، مُرسلًا مع `submitRomeTx`. انظر [لمرة واحدة: امنح الغلاف](#one-time-grant-the-wrapper).
3. **الموافقة** — `approve(vault, amount)` على wUSDC.
4. **إيداع** — `deposit(amount)` على الخزنة. السحب هو `withdraw(amount)`.

```javascript
import { submitRomeTx } from "@rome-protocol/sdk";
import { encodeFunctionData, erc20Abi, parseAbi } from "viem";

const WITHDRAW = "0x4200000000000000000000000000000000000016";
const withdrawAbi = parseAbi(["function withdraw_to_ata(uint256 wei_)"]);

// 1) غلّف بعض USDC الغاز إلى wUSDC (weiAmount بوحدات الغاز ذات 18 منزلة عشرية)
await submitRomeTx(provider, { from: who, to: WITHDRAW, data: encodeFunctionData({ abi: withdrawAbi, functionName: "withdraw_to_ata", args: [weiAmount] }) });

// 2) امنح الغلاف، مرة واحدة: `grant` هو الاستدعاء المُنشأ تحت "مرة واحدة: امنح الغلاف"، أو null
if (grant) await submitRomeTx(provider, { from: who, ...grant });

// 3) وافق و 4) أودِع، كما هو الحال مع أي ERC-20
await submitRomeTx(provider, { from: who, to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });
await submitRomeTx(provider, { from: who, to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

يشترك المستخدمان في نفس `balanceOf` الحالة في الخزنة.

## ما التالي

* [استدعِ EVM من Solana](/ar/adlh-almtw-ryn/call-evm-from-solana.md) — آليات مسار سولانا بالتفصيل
* [استدعِ Solana من EVM](/ar/adlh-almtw-ryn/call-solana-from-evm.md) — الاتجاه الآخر (CPI)
* [الحصول على تمويل](/ar/almward/faucets.md) — USDC كرمز الغاز؛ اجسره إلى الداخل


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.rome.builders/ar/adlh-almtw-ryn/dual-lane-app.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
