> 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/alttbyqat-ala-rome/bloom/building.md).

# البناء على Bloom

هذا الدليل يجيب عن سؤال واحد: **كيف يفعل المُنشئ ذلك بنفسه؟** خذ عقدًا حقيقيًا لأصلٍ من نوع Solidity، وانشره على سلسلة Rome كرمزٍ مقيَّد الصلاحيات، وافتحه لمحافظ Solana وكذلك محافظ EVM من دون جسر ومن دون رمز ثانٍ، ووصل قرارك الخاص بالتحقق من الهوية/الامتثال.

الجمهور هو مطوّر يقيّم هذا النهج أو يتبناه. بلوم هو المثال العملي طوال الدليل — وأصلها المباشر **ARCV ("Mineral Vault I")** على Hadrian (`200010`) هي حالة فعلية لكل خطوة أدناه، ووصل النشر الخاص بها (`deployments/200010.tokens/ARCV.json`) يسجل العقود الثلاثة الدقيقة والثماني معاملات التي تنتجها هذه الطريقة.

لا شيء هنا يغيّر عقود الأصل. بلوم تشغّل إطار Arc الخاص بـ Plume **من دون تعديل** (الشجرة المضمّنة في `contracts/`، المطابقة تمامًا بالبايت لـ `plumenetwork/contracts` عند التثبيت في `NOTICE`)؛ العقد الوحيد على جانب Rome هو `ArcTokenFactoryV2`، وهو يضيف دالة واحدة. العمل هو في *كيفية* نشر العقود و *كيفية* تشغيلها — وليس في تغييرها.

> **الطريقة باختصار.** الرمز المقيَّد الصلاحيات هو **ثلاثة** عقود منشورة. ويتم نشره **على أجزاء** — عقد واحد لكل معاملة — لأن استدعاء المصنع في معاملة واحدة يتجاوز الحد الأقصى لحسابات Rome لكل معاملة. ويجب أن تأتي وكلاؤه من **أصول مقفلة بحسب المصدر** أو يفشل التسجيل على السلسلة بشكل مغلق. الامتثال هو **قيمة منطقية واحدة لكل عنوان**، يكتبها من يخولهم مسار KYC الخاص بك. ويمكن الوصول إلى النشر نفسه من محفظة Solana عبر **مرسل اصطناعي** مشتق على السلسلة — من دون جسر، ومن دون رمز مغلف، ومن دون قائمة سماح ثانية.

## المتطلبات المسبقة

كل أدوات العمل المرجعية موجودة في `scripts/` (شغّل `npm install` هناك أولًا). كل أمر يستخرج حقائق السلسلة من السجل بدلًا من تثبيتها داخل الكود، لذا فإن إعادة التوجيه إلى سلسلة Rome أخرى هي تغيير في البيئة، لا في الكود:

```bash
export PRIVATE_KEY=…       # مفتاح النشر/المُصدِر المموَّل على السلسلة الهدف
export CHAIN_ID=200010     # يحدد السلسلة
export REGISTRY_ROOT=…     # نسخة checkout من سجل سلسلة Rome
# تحتاج تدفقات مسار Solana أيضًا إلى:
export SOLANA_KEYPAIR=…    # المسار إلى ملف JSON لمفتاح Solana زوجي ممول (دافع الرسوم)
```

الغاز على سلاسل Rome مسعّر بعملة USDC؛ وتكلف سلسلة النشر الكاملة في حدود 10–15 وحدة أصلية على شبكة devnet. تأتي المفاتيح من البيئة فقط — لا من المستودع أبدًا. ابنِ العقود مرة واحدة قبل النشر، لأن الأصول **يجب** أن تأتي من `rome-contracts/out` (انظر الخطوة 3):

```bash
cd contracts       && forge build --via-ir     # مجموعة Arc المضمّنة
cd rome-contracts  && forge build --via-ir     # ArcTokenFactoryV2 + الوكيل القياسي
```

***

## الخطوة 1 — اعرف ما هو الرمز المقيَّد الصلاحيات *هو* هنا

رمز Bloom المقيَّد الصلاحيات ليس عقدًا واحدًا. إنه **ثلاثة عقود منشورة**، إضافةً إلى أربعة عقود مشتركة ينشرها التشغيل مرة واحدة لكل سلسلة ويعيد كل مُصدر استخدامها.

**العقود الثلاثة الخاصة بكل رمز** — يملك كل أصل بالضبط هذه العقود:

| العقد                        | المصدر                                                      | ما هو                                                                                              |
| ---------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `ArcTokenProxy`              | `contracts/src/proxy/ArcTokenProxy.sol`                     | الرمز نفسه — وكيل UUPS يشير إلى `ArcToken` التنفيذ المشترك. الأرصدة، والحاملون، والأدوار كلها هنا. |
| `WhitelistRestrictions`      | `contracts/src/restrictions/WhitelistRestrictions.sol`      | وحدة قائمة السماح. قيمة منطقية واحدة لكل عنوان؛ تُفرض على كل تحويل أثناء تقييد الأصل.              |
| `YieldBlacklistRestrictions` | `contracts/src/restrictions/YieldBlacklistRestrictions.sol` | وحدة العائد. تستبعد عناوين من توزيعات العائد من دون المساس بحيازتها.                               |

**العقود الأربعة المشتركة، عقد واحد لكل سلسلة** (ينشرها `deploy-infra.ts`):

| العقد                | الدور                                                                        |
| -------------------- | ---------------------------------------------------------------------------- |
| `RestrictionsRouter` | سجل أنواع الوحدات — توصيل السلسلة، يُضبط مرة واحدة بواسطة التشغيل.           |
| `ArcTokenFactoryV2`  | التسجيل + تجزئة الكود القياسية للوكيل (الخطوتان 2–3).                        |
| `ArcToken` (التنفيذ) | منطق الأصل المشترك القابل لإعادة الاستخدام الذي يشير إليه كل رمز عبر الوكيل. |
| `ArcTokenPurchase`   | واجهة متجر واحدة، مشتركة بين كل رمز وكل مُصدر (الخطوة 4).                    |
| wUSDC                | جانب النقد — عملة العائد وعملة البيع والشراء.                                |

تقييد التحويل وتقييد العائد هما **وحدتان منفصلتان عمدًا**: يمكن منع حامل من الدخل مع الاحتفاظ بالأصل، وهذا هو بالضبط ما تنفذه العقوبات وأوامر المحكمة في الواقع.

حقيقة "ثلاثة عقود" هذه أساسية للتحقق. `verify-deployments.ts` و `register-asset.ts` يتوقعان كلاهما أن يُحلَّ رمز إلى ثلاثة عقود منشورة — وقد أدى التحقق من رمز يدويًا مرة واحدة إلى تفويت سبعة منها عبر سلسلة كاملة (كل وحدة عائد وثلاث وحدات قائمة سماح).

تمر دورة الحياة بثلاث مراحل وبابٍ أحادي الاتجاه:

```
مسودة     الوكيل مباشر، ولا توجد وحدة مرتبطة — لا شيء يقيّد التحويل
  ↓
غير مقيّد   الوحدة مرتبطة، transfersAllowed = true   (WhitelistRestrictions.initialize تضبط هذا)
  ↓  صكّ المعروض، وبناء قائمة السماح               [قابل للعكس]
مقيَّد     setTransfersAllowed(false)                [باب أحادي الاتجاه — يصبح المعروض نهائيًا]
```

يكون التقييد أحادي الاتجاه بالنسبة إلى المعروض لأن الصك والحرق يمران عبر `address(0)` كطرفٍ مقابل، و `address(0)` لا يمكن أبدًا إدراجه في قائمة السماح — لذا فبمجرد تفعيل التقييد، لا يمكن أبدًا صك أي معروض جديد. هذا هو الضمان الذي يقدمه الأصل المقيَّد للصلاحيات لحامليه.

***

## الخطوة 2 — انشره على أجزاء، لا كمعاملة واحدة أبدًا

تقدم Arc عملية `createToken` ذات خطوة واحدة، تنشر الوكيل وكلا الوحدتين وتوصلهما باستدعاء واحد. **على Rome لا يمكنك استخدامه.** تحتاج تلك المعاملة إلى **77 قفل حسابات Solana**، وحد Rome لكل معاملة هو **62 قفلًا**. لا يمكن أن تتسع أبدًا — لا بالضبط، ولا مع أي تمرير مُحسِّن.

لذا فإن النشر هو **على أجزاء**على أجزاء: كل عقد في معاملة مستقلة، ويحصل الرمز على حالة مماثلة لحالة المصنع عبر `ArcTokenFactoryV2.registerToken` بدلًا من العملية الأحادية الضخمة. `createToken`هذا هو معالج الإنشاء، وهو ما تفعله وحدة تحكم المُصدر خطوةً بخطوة:

![معالج الإنشاء](/files/973d563b9425495422fe7dc1dfdda58e5c41644d)

التنفيذ المرجعي هو `arc/plan/create.ts` (`createSequence()`)، والذي تعرضه الواجهة على شكل ست بطاقات (`bloom/lib/createCards.ts`) عبر **ثماني معاملات**، بالترتيب الدقيق الذي تفرضه العقود:

| # | المعاملة              | استدعاء العقد                                                                 |
| - | --------------------- | ----------------------------------------------------------------------------- |
| 1 | نشر الأصل             | `new ArcTokenProxy(impl, initData)` حيث `initData = ArcToken.initialize(...)` |
| 2 | نشر وحدة قائمة السماح | `new WhitelistRestrictions()`                                                 |
| 3 | تعيينك كمدير لها      | `whitelist.initialize(issuer)`                                                |
| 4 | فرضها على التحويلات   | `token.setRestrictionModule(TRANSFER, whitelist)`                             |
| 5 | نشر وحدة العائد       | `new YieldBlacklistRestrictions()`                                            |
| 6 | تعيينك كمدير لها      | `yieldBlacklist.initialize(issuer)`                                           |
| 7 | فرضها على العائد      | `token.setRestrictionModule(YIELD, yieldBlacklist)`                           |
| 8 | التسجيل مع المصنع     | `factoryV2.registerToken(token, impl)`                                        |

شكل السكربت هو أمر واحد:

```bash
# مرة واحدة لكل سلسلة (تشغيل، لا لكل رمز) — ينشر العقود الأربعة المشتركة
npx tsx deploy-infra.ts

# لكل رمز — المعالج، يُعاد تشغيله كسكربت
NAME="Mineral Vault I" SYMBOL=ARCV SUPPLY=1000000 DECIMALS=6 npx tsx create-token.ts
```

المُصدر (`PRIVATE_KEY`) يوقّع كل معاملة وينتهي به الأمر وهو يحمل أدوار الرمز (`ArcToken.initialize` تمنح إلى `msg.sender` — انظر الخطوة 7). التسجيل (المعاملة 8) هو ما يفتح أمام الواجهة الأمامية `enableToken` وأي ترقية تمر عبر المصنع.

> **لا "تحسّن" هذا بإعادته إلى تصميم أحادي.** تجميع هذه الاستدعاءات في معاملة واحدة لتوفير عمليات الذهاب والإياب يعيد بالضبط تجاوز حد الـ 62 قفلًا الذي وُجد المسار على أجزاء لتجنبه. والحد نفسه هو السبب في أن توزيع العائد يسير حاملًا واحدًا لكل معاملة (الخطوة 8). إذا فشل استدعاء مع `Too many accounts: N > 62`، فهذا يعني أن معاملة واحدة تلمس عددًا كبيرًا جدًا من الحسابات — قسّمها، لا تحاول ضبطها.

***

## الخطوة 3 — انشر الوكلاء فقط من الأصول المقفلة بحسب المصدر

`registerToken` هو حد الأمان الذي يجعل عمليات النشر على أجزاء آمنة. لن يقبل إلا رمزًا يكون **تجزئة وقت تشغيله مطابقة لتجزئة `ArcTokenProxy`** القياسية المضمنة في المصنع وقت النشر:

```solidity
// rome-contracts/src/ArcTokenFactoryV2.sol
bytes32 private immutable CANONICAL_PROXY_CODEHASH =
    keccak256(type(ArcTokenProxy).runtimeCode);

function registerToken(address token, address implementation) external {
    if (token.codehash != CANONICAL_PROXY_CODEHASH) revert ProxyCodehashUnknown();
    // …يجب أن يكون التنفيذ معتمدًا من المصنع، ويجب أن يحمل msg.sender
    //    token's ADMIN_ROLE، ويجب ألا يكون الرمز مسجَّلًا بالفعل.
}
```

ولأن تلك التجزئة هي **ثابتة غير قابلة للتغيير**، فإن أي وكيل مبني من أصل آخر يفشل بشكل مغلق مع `ProxyCodehashUnknown`. ويترتب على ذلك قاعدتان، وكلاهما يُطبَّق عبر CI والقواعد الصارمة في `CLAUDE.md`:

1. **يجب نشر وكلاء الرموز من `rome-contracts/out`** — لا أبدًا من `contracts/out`، ولا من بناء مختلف. هذا هو التجميع الذي تثبته تجزئة الكود القياسية للمصنع.
2. **`bytecode_hash = "none"` و `cbor_metadata = false`** في `rome-contracts/foundry.toml` هي عناصر أساسية. مع تفعيل البيانات الوصفية، فإن `ArcTokenProxy` وقت التشغيل *المضمَّن* في المصنع (`type().runtimeCode`) يحمل ذيل CBOR/IPFS مختلفًا عن الأصل المستقل `out/` الذي ينشر منه المعالج — لذا فإن `registerToken` الرفض على السلسلة يحدث بينما **كل اختبارات Foundry المصدريّة تمر** (يضمِّن Foundry النسختين معًا، لذلك لا يستطيع رؤية عدم التطابق). `rome-contracts/test/ArtifactProvenance.t.sol` يثبت هذه الخاصية؛ وكان التشغيل الممول هو ما كشفها أصلًا.

> **مجموعة اختبارات خضراء داخل المصدر لا تثبت المصدرية.** قبل شحن صورة، وبعد أي تغيير في إعدادات المترجم، أثبت الخاصية مقابل *المُنشَر* المصنع (للقراءة فقط، من دون مفاتيح):
>
> ```bash
> cd scripts && CHAIN_ID=<id> npx tsx verify-artifact-provenance.ts
> ```
>
> إنه يَحسب تجزئة `ArcTokenProxy` الأصل المحلي لديك ويؤكد أن هذه التجزئة تظهر حرفيًا داخل بايت كود المصنع المنشور.

إذا كان لا بد من تغيير إعدادات المترجم، **فأدرِ المصنع** — انشر واحدًا جديدًا يثبت فيه الثابت التجزئة الجديدة، وأعِد التوصيل — بدلًا من ترقيع الفحص يدويًا:

```bash
cd scripts && CHAIN_ID=<id> npx tsx rotate-factory.ts
```

الاستدارة تحتفظ بالعنوان القديم في الإيصال؛ ويجب إعادة تسجيل الرموز المسجَّلة ضد المصنع القديم لدى المصنع الجديد.

***

## الخطوة 4 — افتح بيعًا عبر الواجهة الأمامية المشتركة

البيع فعل منفصل عن الإنشاء، وهو يعمل على الواجهة الأمامية المشتركة الوحيدة. `ArcTokenPurchase` ثلاثة أشياء يجب أن تكون صحيحة قبل فتح البيع، و `ArcTokenPurchase.enableToken` يتحقق من كل واحدة على السلسلة:

```solidity
// contracts/src/ArcTokenPurchase.sol
function enableToken(address _tokenContract, uint256 _numberOfTokens, uint256 _tokenPrice)
    external onlyTokenAdmin(_tokenContract)                       // (أ) أنت تحمل ADMIN_ROLE الخاص بالرمز
{
    // (ب) يجب أن يكون الرمز معروفًا لدى المصنع — هذا ما اشترته registerToken (الخطوة 2):
    if (ArcTokenFactory(ps.tokenFactory).getTokenImplementation(_tokenContract) == address(0))
        revert TokenNotCreatedByFactory();
    // (ج) يجب أن تحتفظ الواجهة الأمامية مسبقًا بمخزون البيع:
    if (ArcToken(_tokenContract).balanceOf(address(this)) < _numberOfTokens)
        revert ContractMissingRequiredTokens();
    // …يجب أن يكون السعر والعدد موجبين.
}
```

هناك متطلب رابع ضمني يفرضه الأصل المقيَّد: لأن **كل تحويل يمر عبر بوابة قائمة السماح**، فإن نقل المخزون إلى الواجهة الأمامية ينجح فقط إذا كان عنوان الواجهة الأمامية نفسه ضمن قائمة السماح على رمزك. لذلك فإن التسلسل الكامل لفتح البيع هو:

1. **ضع الواجهة الأمامية في قائمة السماح** على وحدة قائمة السماح الخاصة برمزك (`batchAddToWhitelist([storefront])` — انظر الخطوة 5). من دون هذا، يفشل تحويل المخزون في (2) مع `TransferRestricted()` بمجرد تفعيل التقييد.
2. **نقل المخزون** إلى عنوان الواجهة الأمامية (من إيصال البنية التحتية في `arcTokenPurchase`).
3. **`enableToken(token, amount, price)`** — يدفع المشترون الآن wUSDC ويتلقون RWA.

يستحق تقسيم السحب أن يُعرف على واجهة أمامية *مشتركة* ، وهو غير متماثل (`arc/roles.ts` هو المرجعية):

| الإجراء                                                    | السلطة                                         | يحمله                   |
| ---------------------------------------------------------- | ---------------------------------------------- | ----------------------- |
| `enableToken` / `disableToken` / `withdrawUnsoldArcTokens` | الرمز الخاص بـ `ADMIN_ROLE` (`onlyTokenAdmin`) | أنت، المُصدر            |
| `withdrawPurchaseTokens` (عائدات wUSDC)                    | الخاص بالواجهة الأمامية `DEFAULT_ADMIN_ROLE`   | من نشر الواجهة الأمامية |

على سلسلة مستضافة ذاتيًا أنت الطرفان معًا. وعلى واجهة أمامية مشتركة فإن حوض العائدات هو مبيعات كل مُصدر، لذا فإن السحب عليه يكون على مستوى المنصة — صمّم التسوية لديك حول ذلك بدلًا من افتراض تحكم أحادي.

***

## الخطوة 5 — وصل KYC / الامتثال الخاص بك

هذا هو القسم الذي يقرأه العميل قبل أن يوافق، لذا فهو كود لا نثر. **يُفرض الامتثال بواسطة عقد الرمز نفسه** — لا بواسطة فحص خارج السلسلة، ولا مرشح للمُرتِّب، ولا سياسة للمنصة. تنتقل القواعد مع الرمز إلى أي مسار تنفيذ، على كلا مساري المحافظ. ما تسجله السلسلة هو **النتيجة** لقرار KYC الخاص بك، على شكل قيمة منطقية واحدة:

```solidity
// contracts/src/restrictions/WhitelistRestrictions.sol — الحالة على السلسلة، كاملة
struct WhitelistStorage {
    mapping(address => bool) isWhitelisted;   // ← قيمة منطقية واحدة لكل عنوان. لا توجد بيانات شخصية، أبدًا.
    bool transfersAllowed;                    // false = مقيَّد؛ لا ينقل إلا العناوين المدرجة في قائمة السماح
    EnumerableSet.AddressSet whitelistedAddresses;
}
```

### الواجهة التي يكتب إليها مسارك

قرار القبول/الرفض من مزود KYC الخاص بك يصبح بالضبط أحد هذه الاستدعاءات:

```solidity
interface IWhitelistRestrictions {
    function addToWhitelist(address account) external;                  // onlyRole(MANAGER_ROLE)
    function batchAddToWhitelist(address[] calldata accounts) external; // onlyRole(MANAGER_ROLE)
    function removeFromWhitelist(address account) external;             // onlyRole(MANAGER_ROLE)
    function isWhitelisted(address account) external view returns (bool);
    function setTransfersAllowed(bool allowed) external;                // onlyRole(ADMIN_ROLE)
}
```

### من يمكنه الكتابة

> **السلطة التي يحتاجها أي قبول هي `MANAGER_ROLE` على&#x20;*****الوحدة*****&#x20;— لا `ADMIN_ROLE` على الرمز.** `addToWhitelist` / `batchAddToWhitelist` / `removeFromWhitelist` كلها `onlyRole(MANAGER_ROLE)`، وهذا الدور يعيش على `WhitelistRestrictions` النسخة. المصادقة `ADMIN_ROLE` الكتابة على الرمز المميز ثم الكتابة إلى الوحدة عيب حقيقي دفع هذا المستودع ثمنه بالفعل. لا تقترح `WHITELIST_ADMIN_ROLE` كإصلاح لرفض — فهي تُمنح عند initialize و **لا يتم التحقق منها في أي مكان في مجموعة الاختبارات** (`arc/roles.ts` يؤكد أنها هي الرتبة الوحيدة التي لا تضبط أي شيء).

`WhitelistRestrictions.initialize(issuer)` (المعاملة 3 من المعالج) تمنح المُصدِر `DEFAULT_ADMIN_ROLE`, `ADMIN_ROLE`, `MANAGER_ROLE`, `WHITELIST_ADMIN_ROLE`، و `UPGRADER_ROLE` على تلك الوحدة. لذا فإن المُصدِر يملك `MANAGER_ROLE` مباشرةً من الصندوق ويمكنه أيضًا تفويضها: لأن المُصدِر يملك `DEFAULT_ADMIN_ROLE`،  `grantRole(MANAGER_ROLE, serviceAccount)` يسلّم الكتابة إلى موقّع خلفي.

### تحوّل القرار إلى كتابة على السلسلة

وجّه webhook الخاص بمورّدك إلى موقّع يملك `MANAGER_ROLE`، وعند الموافقة، اجمع العناوين دفعةً واحدة:

```typescript
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

// يجب أن يملك `signer` الرتبة MANAGER_ROLE على وحدة قائمة السماح لهذا الرمز المميز.
const wallet = createWalletClient({ account, transport: http(chain.rpcUrl) });

// قال المورّد "موافق" لهذه العناوين:
await wallet.writeContract({
  address: whitelistModule,                 // مُستخرَج مِن الرمز المميز عبر getRestrictionModule
  abi: whitelistAbi,
  functionName: 'batchAddToWhitelist',
  args: [approvedAddresses],
});
```

استخرج `whitelistModule` من الرمز المميز نفسه (`ArcToken.getRestrictionModule(TRANSFER_RESTRICTION_TYPE)`)، لا أبدًا من إسقاط مُخزَّن — فإرسال كتابة إلى الوحدة الخطأ هو كيف انتهى "الموافقة على أي أصل" ذات مرة إلى الكتابة في قائمة السماح الخاصة برمز مميز واحد.

### النسخة الموقّعة بالمحفظة (عرض ذاتي)

لمنشور تجريبي بلا صلاحيات حيث يُدخل الزوار أنفسهم، امنح `MANAGER_ROLE` إلى `TestApprover` (`rome-contracts/src/TestApprover.sol`) الذي `الوحدة` هو **ثابتة غير قابلة للتغيير**. ثم `approve(address)` يمكن لأي شخص استدعاؤها من محفظته الخاصة، وهو يدفع الغاز من محفظته هو — فهي تستدعي `addToWhitelist` فقط إذا لم يكن العنوان مُدرَجًا بالفعل:

```solidity
function approve(address account) external {
    IWhitelistRestrictions target = IWhitelistRestrictions(module);   // ثابت — وحدة واحدة، إلى الأبد
    if (!target.isWhitelisted(account)) target.addToWhitelist(account);
    emit Approved(account, msg.sender);
}
```

هذه هي "بوابة تسجيل الدخول" — فهي تعرض للزائر الآلية الحقيقية (موافقة تتحول إلى إدخال في قائمة سماح على السلسلة يفرضه الرمز المميز بعد ذلك) من دون أن تحتفظ بأي شيء. **لا تمنح `MANAGER_ROLE` إلى `TestApprover` على رمز مميز إنتاجي** — فهذا يجعل قائمة السماح الخاصة بذلك الرمز المميز بلا صلاحيات.

### عندما يبدأ الضبط في العمل، ومع تبديل المورّدين

لا يقيّد الضبط إلا عندما يكون الأصل **مُضبَطًا** (`setTransfersAllowed(false)`, `ADMIN_ROLE` على الوحدة). بينما هو غير مُضبَط، تكون التحويلات مفتوحة — ابنِ قائمة السماح أولًا، ثم فعِّل الضبط. وبمجرد تفعيله، فإن أي تحويل يكون فيه أحد الطرفين خارج القائمة سيُرفض بالخطأ المطبوع `TransferRestricted()` (`0xe827105e`).

> **لا يتطلب تبديل مزوّدي KYC لاحقًا أي تغيير على السلسلة.** وحدة قائمة السماح، وواجهتها، والقيمة المنطقية التي تخزّنها لا تعتمد على المورّد. لتبديل المورّدين، وجّه مصدر قرار مختلفًا إلى نفس `MANAGER_ROLE` الموقّع — لا إعادة نشر، لا ترحيل، ولا تغيير في الحالة. لم تعرف السلسلة قط أي مورّد أصدر النداء، بل فقط النتيجة.

***

## الخطوة 6 — افتح الأصل نفسه لمحافظ سولانا

يمكن الوصول إلى العقود الثلاثة نفسها من محفظة سولانا **من دون جسر ولا رمز مميز ثانٍ**المستخدم الأصلي على سولانا لا يملك مفتاح EVM في أي مكان؛ وبدلًا من ذلك تكون هوية EVM الخاصة به *اصطناعية*، مشتقة على السلسلة من موقّع سولانا الفعلي في المعاملة:

```
synthetic EVM address = keccak256(solana_pubkey)[12..32]
```

يستنتج برنامج Rome EVM هذا من الموقّع نفسه (`do_tx_unsigned::derive_sender`), لذلك لا يمكن انتحالها ولا يوجد لها مفتاح خاص secp256k1 — **توقيع سولانا هو الشيء الوحيد الذي يمكنه قيادة هذا العنوان**. الاشتقاق المرجعي هو `arc/materialise/solana/identity.ts` (`syntheticAddress`)، متوافق بايت-بايت مع القاعدة على السلسلة:

```typescript
export function syntheticAddress(pubkey: SolanaPubkeyInput): `0x${string}` {
  return `0x${keccak256(pubkeyBytes(pubkey)).slice(-40)}`;
}
```

**ما الذي يفعله البناء لدعم المسار** — لاحظ أن *لا شيء منه تغيير في عقد*:

1. **أضف مستخدم سولانا إلى قائمة السماح عبر عنوانه الاصطناعي.** بالنسبة إلى طبقة الامتثال، العنوان الاصطناعي هو عنوان عادي: الصق المفتاح العام لسولانا الخاص بالمستخدم، واستخرج العنوان الاصطناعي، و `batchAddToWhitelist([synthetic])`تغطي قائمة سماح واحدة كلا عالمي المحافظ.
2. **وفّر PDA الخاص بالتوقيع مرة واحدة، قبل أول معاملة له.** يجب أن يوجد أولًا PDA لسلطة خارجية يصرّح بتوقيع العنوان الاصطناعي، وإنشاؤه هو استدعاء لمسار EVM (`create_pda`) — لا يستطيع مستخدم سولانا وحده فعل ذلك، لذا يقوم المُصدِر بتوفيره. التوفير هو `external_auth`، وليس كسولًا.
3. **أرسل عبر مكتبة المسار، لا يدويًا.** `submitDoTxUnsigned` (`arc/materialise/solana/submit.ts`) يبني `DoTxUnsigned` تعليمة — *غير موقّعة* حمولة EIP-1559 مخوّلة بواسطة توقيع سولانا — ويتولى أربع مسائل يخطئ فيها إنشاء معاملة يدويًا:
   * **اكتشاف الحسابات** عبر `rome_emulateCallAccounts`، والذي يقرر قابلية كتابة كل حساب. يجب أن تُمرَّر قيمة النداء `القيمة` **يجب** إلى الاكتشاف، وإلا فإن تحويل القيمة يجعل PDA الخاص بميزان المستلم للقراءة فقط ويفشل على السلسلة.
   * **ميزانية الحوسبة** —  `DoTxUnsigned` يتطلب **إطار heap بحجم 250 KB** و **حدًا 1.35 M CU**  (يستنفد Rome’s EVM الإعدادات الافتراضية 32 KB / 200 K)؛ مع ترك هامش يقارب 50 K تحت سقف سولانا البالغ 1.4 M.
   * **PDA الخاص بمحفظة الرسوم (الكنز)**، يُضاف قابلًا للكتابة — والاكتشاف يتجاهله.
   * **الخطة البديلة v0 + جدول البحث.** عندما تتجاوز حسابات النداء غلاف المعاملة القديمة البالغ 1232 بايت، يعيد العميل الإرسال كمعاملة v0 عبر جدول بحث عناوين جديد. (قياس اليوم يبيّن أن شراءً من متجر يناسب الغلاف القديم عند نحو 1,061 من 1232 بايت؛ وقد يتجاوزه حاملٌ جديد لم يُنشأ له بعد حساب الرمز المميز المرتبط.)

المستخدم هو **دافع الرسوم** ويدفع lamports ‏(SOL) من محفظة سولانا الخاصة به؛ أما النسخة الاصطناعية التي يتحكم بها فتحمل الأصول ولا تحمل SOL أبدًا. بعد كل إرسال في هذا المسار، استعلم عن nonce الخاص بـ EVM قبل الإرسال التالي — فالمفهرس يتأخر قليلًا عن تأكيد سولانا.

> **المسار لا يدعم إلا النمط الذرّي.** أ `DoTxUnsigned` هي معاملة EVM واحدة تُنفَّذ داخل معاملة سولانا واحدة بواسطة VM الذرّي الخاص بـ Rome. الـ VM التكراري (تنفيذ EVM واحد على عدة معاملات سولانا) هو **غير قابل للوصول عبر هذا المسار بعد**. معاملة لن تتسع ذرّيًا لا يمكن أن تسلك هذا المسار أصلًا — الإصلاح هو استدعاء أصغر، لا بديل. لاحظ أن بديل v0+ALT هو غلاف *غلافًا*أكبر، وليس تنفيذًا مختلفًا؛ فهو لا يزال معاملة ذرية واحدة.

**لماذا لا يمكن تسرب الأصل الحقيقي المرمّز خارج المحيط.** الأصل مقيم في حالة EVM — أرصدته مخزنة داخل حسابات برنامج Rome EVM. لا يوجد **سكّ SPL** منه ولا شيء أصلي على سولانا يمكن تحريكه؛ فبدائيات نقل الأصول في هذا المسار تعمل على حسابات رموز SPL، وهو أمر لا يملكه رصيد مخزَّن في EVM. كل حركة يستطيع توقيع سولانا التعبير عنها هي استدعاء إلى عقد الرمز المميز، الذي يشغّل بوابة قائمة السماح — وتؤكد الحزمة المموّلة أن تحويلًا إلى عنوان غير موجود في القائمة يُرفض بالطريقة نفسها سواء وُقِّع بمفتاح EVM أو بمفتاح سولانا. وساق النقد (wUSDC) هي الاستثناء المقصود: فهي مدعومة بـ SPL و *تقوم* بالتحويل إلى حساب الرمز المميز الخاص بالمستخدم نفسه.

***

## الخطوة 7 — اعرف من يملك السلطة بعد النشر

`ArcToken.initialize` يعمل من منشئ الوكيل (tx 1) ويمنح الأدوار إلى `msg.sender` — سائق المعالج، أي المُصدِر. وهو يمنح **خمسة** أدوارًا صراحةً:

```solidity
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(MANAGER_ROLE, msg.sender);
_grantRole(YIELD_MANAGER_ROLE, msg.sender);
_grantRole(YIELD_DISTRIBUTOR_ROLE, msg.sender);
```

تبعاتان لا يجب أن يغفل عنهما البنّاء:

* **`MINTER_ROLE`, `BURNER_ROLE`، و `UPGRADER_ROLE` لا تُمنح عند initialize.** يُسكّ العرض الأولي داخل `initialize` (عملية داخلية `_mint`، لا حاجة لأي دور)، لكن *لاحقًا* `سكّ` أو `حرق` يتطلب من المُصدِر أن يمنح نفسه الدور أولًا. ويمكنه ذلك، لأنه يملك `DEFAULT_ADMIN_ROLE` (مدير كل دور — لا شيء يستدعي `_setRoleAdmin`، لذا `DEFAULT_ADMIN_ROLE` يديرها جميعًا). لكن ذلك خطوة إضافية مقصودة، لا قوةً محيطة.
* **`UPGRADER_ROLE` لا يُمنح أبدًا للمصنع.** الترقيات عبر المصنع (`ArcTokenFactoryV2.upgradeToken`) تتطلب أن **يحمل المصنع نفسه** على الرمز المميز — منحه لنفسك لا يفعل شيئًا. تلك المنحة باب باتجاه واحد لا يستطيع المصنع التراجع عنه، لذا فهي اختيارية: إذا أردت ترقيات عبر المصنع، `UPGRADER_ROLE` grantRole(UPGRADER\_ROLE, factory) `grantRole(UPGRADER_ROLE, factory)` صراحةً؛ وإلا فإن المُصدِر يرقّي الرمز مباشرةً.

نموذج السلطة الكامل المكوَّن من 21 سلطة (11 اسم دور عبر ستة عقود — فالاسم نفسه سلطة مختلفة على كل عقد) مُقَنَّن في `arc/roles.ts`في `rolesRequiredFor(contract, functionName)` بدلًا من قراءة العقد الخاص بالدور نفسه، لأن أربع وظائف للمتجر وكلا وظيفتي المصنع محجوبة بواسطة سلطة تعيش على *عقد* .

***

## الخطوة 8 — وزّع العائد

يدفع الرمز المميز العائد بعملة wUSDC (مثبتة عند `initialize`، قابلة للتغيير عبر `setYieldToken` تحت `YIELD_MANAGER_ROLE`). يمر توزيع العائد على مجموعة الحائزين:

```solidity
function distributeYieldWithLimit(uint256 totalAmount, uint256 startIndex, uint256 maxHolders)
    external onlyRole(YIELD_DISTRIBUTOR_ROLE) nonReentrant
    returns (uint256 nextIndex, uint256 totalHolders, uint256 amountDistributed);
```

الآليات المهمة لتنفيذه بشكل صحيح:

* **الكامل `totalAmount` يُسحب من المستدعي فقط عند `startIndex == 0` النافذة** (`safeTransferFrom(msg.sender, this, totalAmount)`). وافق على هذا الإجمالي للرمز المميز قبل النافذة الأولى؛ النوافذ اللاحقة تُدفع من الرصيد المحتفظ به بالفعل.
* **`nextIndex` يعود إلى `0` عندما يكتمل المرور.** كرّر `distributeYieldWithLimit(total, nextIndex, …)` حتى `nextIndex` يعود `0`.
* **تظل حصص الحائزين المقيدة بالعائد في عقد الرمز المميز** — تُتخطّى، لا يُعاد توزيعها.

> **على Rome الحد العملي هو `maxHolders = 1` في كل معاملة** — وهو نفس حد 62 حسابًا من الخطوة 2 — لذا يُجرى المرور على العائد حائزًا واحدًا في كل معاملة (مقاسًا بنحو 318 K غاز لكل حائز). كل حائز في قائمة السماح، سواء EVM أو أصلي على سولانا، يحصل على نصيبه النسبي؛ والسكربت المرجعي هو `measure-yield-batch.ts` وتؤكد خطوة العائد في الاختبار المدعوم ذلك من البداية إلى النهاية.

```bash
# صيغة مرجعية في USAGE.md: مرّ على حائز واحد في كل معاملة حتى يعود nextIndex إلى 0
distributeYieldWithLimit(total, offset, 1)
```

***

## الخطوة 9 — تحقّق مما نشرته

لا شيء يرسل المصادر تلقائيًا إلى أي مكان في Rome، لذا فإن التحقق خطوة تنفذها أنت. بعد أي نشر — للبنية التحتية أو لرمز مميز جديد — شغّل أداة التحقق عديمة المفاتيح، للقراءة فقط، والقابلة للتكرار (العقود التي تم التحقق منها مسبقًا تُتخطى، لذا فإن إعادة التشغيل بعد إصدار رمز مميز تتحقق فقط من الجديد):

```bash
cd scripts && CHAIN_ID=<id> npx tsx verify-deployments.ts
```

أنه يتوقع **ثلاثة** العقود لكل رمز مميز (الوكيل + الوحدتان) إضافة إلى البنية التحتية، ويبلغك بما تعذر التحقق منه بدلًا من فشل النشر. القيام بهذا يدويًا مرة واحدة فوّت سبعة عقود.

تسجيل الأصل في سجل Rome يعامل التحقق كـ **بوابة**، لا كزخرفة (`register-asset.ts`): يكسب الرمز المميز `apps/arc/<chain>.json` سجلًا — يحمل `standard: arc-permissioned-erc20` و `transferRestriction` ويسمي بوابة قائمة السماح الخاصة به — فقط عندما تكون عقوده الثلاثة كلها موثقة على Sourcify. يقرأ ذلك السجل الحقائق من السلسلة (فالرمز المنشور عبر المعالج لا يملك إيصالًا committed) ويكتب فقط نسخة سجل محلية؛ وتمرير طلب الدمج مهمة شخص.

```bash
CHAIN_ID=200010 npx tsx register-asset.ts <tokenAddress>        # تحقق + حضّر سجل السجل
CHAIN_ID=200010 DRY=1 npx tsx register-asset.ts <tokenAddress>  # معاينة للقراءة فقط
```

لإثبات شامل من البداية إلى النهاية لكل الحلقة — إنشاء، إدراج في القائمة البيضاء، تفعيل البوابة، البيع على كلا المسارين، توزيع العائد — شغّل الاختبار المدعوم ضد أي سلسلة:

```bash
PRIVATE_KEY=… CHAIN_ID=… SOLANA_KEYPAIR=… npx tsx smoke.ts
```

***

## الخطوة 10 — اعرف ما يبقى خارج السلسلة

نموذج الثقة صريح عمدًا، وعلى البنّاء أن يعرضه بالطريقة نفسها لمستخدميه:

* **علامة قائمة السماح هي قرارك خارج السلسلة.** تحدث KYC/AML في عمليتك — مورّدوك، قواعدك. لا تسجل السلسلة ولا تفرض إلا *النتيجة*, `isWhitelisted(addr)`.
* **حالة السلسلة الوحيدة هي قيمة منطقية واحدة لكل عنوان.** لا بيانات تعريف شخصية، لا مستندات، لا ادعاءات هوية، لا إثباتات على السلسلة، ولا برهانًا تشفيريًا على مَن يملك العنوان. هذا هو نموذج Arc كما يُستخدم على Plume؛ فهو يستبدل التحويلات الأثقل لمعيار هوية على السلسلة (مثل ERC-3643) بآليات أخف وثقة يحتفظ بها المُصدِر.
* **الاسترداد سلطة للمُصدِر، لا مخرج طوارئ.** المحفظة المفقودة — EVM أو سولانا — يُستردّ أمرها بإدراج عنوان بديل في القائمة البيضاء، وإذا لزم الأمر باستخدام السكّ/الحرق/الترقية ضمن عمليةك القانونية الخاصة (Arc لا يملك بدائية تحويل قسري مدمجة). لذا فإن حفظ المُصدِر لمفتاحه جزء من موقف الامتثال.
* **لا مُرحّل، ولا سطح حفظ.** يحتفظ المستخدمون برسوم الغاز الخاصة بهم على كلا المسارين. خدمة دافع الرسوم ستعني الاحتفاظ بالمفاتيح — وهو سطح لا ينبغي لمنتج RWA إضافته.

***

## مرجع: الأدوات

كل سكربت يعمل من `scripts/` بعد `npm install`، مع `PRIVATE_KEY`, `CHAIN_ID`، و `REGISTRY_ROOT` مُصدَّران.

| السكربت                         | ما الذي يفعله                                                                     |
| ------------------------------- | --------------------------------------------------------------------------------- |
| `deploy-infra.ts`               | مرة واحدة لكل سلسلة: العقود المشتركة الأربعة، موصولة بـ wUSDC.                    |
| `create-token.ts`               | المعالج الجزئي — ثماني معاملات، رمز مميز واحد.                                    |
| `rotate-factory.ts`             | انشر مصنعًا جديدًا بعد تغيير في المترجم/الأصل؛ أعد التوصيل.                       |
| `verify-artifact-provenance.ts` | أثبت أن أصل الوكيل الخاص بك مقبول من المصنع المنشور (للقراءة فقط).                |
| `verify-deployments.ts`         | تحقّق من العقود الثلاثة لكل رمز مميز + البنية التحتية على Sourcify (للقراءة فقط). |
| `register-asset.ts`             | تحقق من البوابة، ثم حضّر سجل الامتثال في السجل.                                   |
| `measure-yield-batch.ts`        | أعد قياس سقف العائد لكل معاملة على السلسلة.                                       |
| `smoke.ts`                      | الحزمة المدعومة من البداية إلى النهاية عبر كلا المسارين.                          |
| `sweep-synthetic-native.ts`     | استردّ الغاز الأصلي من حساب اصطناعي (نظافة الاختبار).                             |

تقع خلفية أعمق إلى جانب هذا الدليل: `docs/ARCHITECTURE.md` (نموذج التطبيق متعدد الطبقات)، `docs/LANES.md` (المساران والحفظ)، `docs/COMPLIANCE.md` (نموذج الثقة)، و `docs/USAGE.md` (دليل تشغيل المشغّل).


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rome.builders/ar/alttbyqat-ala-rome/bloom/building.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
