في وقت سابق من هذا العام، تجاوزنا مليار دولار في إجمالي حجم مدفوعات العملات المستقرة. وللتوضيح، قمنا بمعالجة أول معاملة بالعملة المستقرة منذ أقل من 12 شهرًا. بدأ الأمر مع عدد قليل من العملاء الذين طلبوا شيئًا بسيطًا: القدرة على تلقي مدفوعات بالعملة المستقرة من عملائهم حول العالم والحصول على تلك الأموال. تسوية بالدولار الأمريكي- بسرعة وبشكل متوقع، كل ذلك دون تحمل رسوم باهظة. كانت الحلول الحالية للخروج من السوق تعني رسومًا بنسبة 1-3٪، وتسوية تستغرق عدة أيام، وعمليات امتثال طويلة للغاية ومحبطة. رأينا فرصة سانحة وقمنا ببناء نموذج أولي قابل للتطبيق في غضون أسبوعين. وفي غضون شهر واحد، حققنا حجمًا حقيقيًا وهامًا.

كان التحدي هو توسيع نطاقه.

تتم تسوية العملات المستقرة في ثوانٍ معدودة، في أي مكان وفي أي وقت. لم يتم تصميم الأنظمة المصرفية التقليدية لهذا الغرض، فهي تعمل على أساس المعالجة المجمعة، ومواعيد إغلاق أيام العمل، وفترات تسوية تمتد لعدة أيام. يرسل لك عميل في سنغافورة 50,000 دولار أمريكي بعملة USDC في الساعة 2 صباحًا يوم السبت. على السلسلة، يتم تسويتها في ثوانٍ. لن يبدأ التحويل إلى حسابك المصرفي حتى يوم الاثنين، ولن يصل حتى يوم الأربعاء، وقد يظل قيد المراجعة في مكان ما بينهما. إن ربط هذين العالمين يعني التنسيق بين أنظمة لم تصمم أبدًا للتواصل مع بعضها البعض، ولكل منها حالته الخاصة.

تتناول هذه المشاركة اثنين من العناصر الأساسية للبنية التحتية التي أنشأناها لجعل حركة العملات المستقرة تتصرف مثل حركة الأموال المصرفية:

1. تدفق الأموال: محرك تنسيق إعلاني لعمليات سير العمل المالية طويلة الأمد ومتعددة الأنظمة

2. لدينا إطار عمل التنفيذ على السلسلة: نموذج دورة حياة لعمليات موثوقة على السلسلة

كل شيء آخر - منافذ الخروج، منافذ الدخول، وحساباتنا العالمية غير الوديعة بالدولار الأمريكي - يتم بناؤه من خلال تكوين هذه العناصر الأساسية. إذا كنت ستستفيد من شيء واحد من هذا المنشور: العملات المستقرة سهلة؛ العملة المستقرة الخدمات المصرفية ليس كذلك.

تدفق الأموال

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

مشكلة حركة الأموال

تتمثل معظم تحديات التنسيق في البرمجيات في التعامل مع الفشل برشاقة. أما التنسيق المالي فيواجه قيودًا أكثر صعوبة: المال في طريقه بالفعل.

لنفترض أن هناك تدفق إيداع فوري. يتلقى أحد العملاء 10,000 دولار أمريكي من عملة USDC. نقوم بإيداع المبلغ في حسابه على الفور (قبل تسوية ACH الأساسية) حتى يتمكن من استخدام هذا رأس المال فورًا. خلف الكواليس، قمنا بإصدار قرض. عندما تصل ACH بعد أيام، نقوم بتحصيل السداد والرسوم.

هذه أربع عمليات عبر نظامين خارجيين: تصفية العملات المشفرة، صرف القروض، تسوية ACH، تحصيل الرسوم. كل خطوة تعتمد على الخطوة السابقة، ويمكن أن تفشل أي خطوة. لا يمكنك إدارة هذا الأمر باستخدام معالجات متفرقة ومخصصة.

التجريد الأساسي

تدفق الأموال هو نظام تنسيق إعلاني قائم على القواعد. يتكون التجريد الأساسي من ثلاثة أجزاء:

الأحداث هي إشارات تدل على حدوث شيء ما — تسوية ACH، استلام أموال، الحصول على تفويض بطاقة، إلخ. يمكن أن تأتي الأحداث من أنظمة خارجية أو أن تصدر داخليًا.

القواعد تحديد ما يجب أن يحدث استجابة للأحداث. تحدد كل قاعدة قائمة بالأحداث المحفزة وتسلسل الآثار الجانبية التي يجب تنفيذها.

الآثار الجانبية هي الإجراءات التي نتخذها استجابة للحدث: بدء التحويل، إنشاء تعليق، صرف قرض، تحصيل رسوم. يتم تشغيل القاعدة مرة واحدة. عند وصول أول حدث مطابق، يتم تنفيذ الآثار الجانبية بالترتيب، ويتم استهلاك القاعدة. وهذا يضمن التكرار داخل سياق التدفق.

لماذا التصريحية؟

البديل هو التنسيق الإلزامي: المعالجات تستدعي المعالجات، والحالة موزعة عبر الجداول، و"التدفق" موجود فقط في التنسيق الضمني بين أجزاء الكود.

هذا يصلح للتدفقات البسيطة. أما بالنسبة للعمليات المالية التي تستغرق عدة أيام وتشمل عدة أنظمة مع حالات تعليق الامتثال والفشل الجزئي، فإنه يصبح من الصعب الحفاظ عليها. معالجة الأخطاء تتم بشكل مخصص. مسارات الاسترداد تكون ضمنية. بعد ستة أشهر، لا أحد يستطيع أن يجيب بثقة على سؤال "ماذا يحدث إذا فشلت الخطوة 3 بعد نجاح الخطوة 2؟"

القواعد التصريحية تقلب النموذج. يمكنك تعريف آلة الحالة بشكل صريح: هذه أحداث تحفز هذه الإجراءات. يتولى محرك التنسيق التنفيذ والاستمرارية والاستعادة. التدفق هو الوثائق.

الضمانات

يقدم لنا FoF أربعة ثوابت يمكننا الاعتماد عليها:

1. التكرار - يتم تشغيل القاعدة مرة واحدة فقط لكل سياق تدفق، بغض النظر عن تكرار الأحداث أو إعادة المحاولات.

2. التوفيق الحتمي - في ظل نفس الأحداث، يتحول التدفق إلى نفس الحالة

3. قابلية التدقيق الكامل - يتم تتبع كل تأثير جانبي مع تتبع أصله إلى الحدث المسبب له

4. قابلية التركيب - التدفقات المعقدة مبنية من قواعد بسيطة تتكون دون أن تصبح متجانسة

تتبع التنفيذ

نحن نتتبع كل تنفيذ للآثار الجانبية عبر سجلات Node — كل منها مرتبط بأصله، مما يشكل شجرة تنفيذ كاملة. عندما يتطلب الامتثال مسار تدقيق، يمكننا تتبع المسار الدقيق عبر النظام.

Root Node (flow.begin)
├── Node: CreateOffRampTransaction
├── Node: SendNotification
└── Node: CreateSettlementRule
    └── (child rule, fires later)
        ├── Node: CollectLoanRepayment
        ├── Node: CollectFees
        └── Node: ResolveTransaction

قابلية التركيب: القواعد المتداخلة

يمكن أن تولد القواعد قواعد فرعية. هكذا تتشكل التدفقات المعقدة والمتعددة الخطوات دون أن تصبح متجانسة. عند إنشاء معاملة خارجية، لا تحاول القاعدة الأولية معالجة كل شيء. إنها تقوم بإعداد المستقبل القواعد — المستمعون الذين ينتظرون الأحداث التي ستحدث لاحقًا:

createRule({ events: ['flow.begin'] })
  .addSideEffect(CreateOffRampTransaction)
  .addSideEffect(SendNotification)
  .addSideEffect(
    CreateRuleSideEffect.create({
      rules: createRule({
        events: ['inbound_ach.completed', 'completed'],
      })
        .addSideEffect(CollectLoanRepayment)
        .addSideEffect(CollectFees)
        .addSideEffect(ResolveTransaction)
        .buildRule(),
    })
  )
  .buildRule();

لا توجد منطقية التسوية كرمز ميت ينتظر أن يتم استدعاؤه. إنها موجودة كقاعدة، تنتظر الحدث الخاص بها. عندما يصل webhook مزود الخدمات المصرفية بعد أيام، يتم تشغيل القاعدة ويستمر التدفق. يتم استهلاك القاعدة الأصلية وتقوم القاعدة الفرعية بنقل السياق إلى الأمام.

وهذا يعني أيضًا أن التدفقات قابلة للتركيب بشكل تعسفي. هل تريد تنفيذ الإيداعات الفورية؟ سهل. أضف قاعدة تصرف القرض وتضع قواعد السداد. كل مسألة معزولة، لكنها جميعًا تشكل تدفقًا متماسكًا.

سياقات تنفيذ الآثار الجانبية

ليست جميع الآثار الجانبية متساوية. بعضها يحتاج إلى أن يكون ذريًا مع معاملة قاعدة البيانات. بعضها يستدعي واجهات برمجة تطبيقات خارجية. بعضها يتم تنفيذه مرة واحدة ثم يُنسى.

MethodExecutionUse Case
addSideEffectSynchronous, same DB transactionAsync, no transaction
addAsyncSideEffectAsync via TemporalExternal API calls, long-running operations
addAsyncNonTransactionalSideEffectAsync, no transactionNotifications, logging, analytics

المكافأة

أكبر فائدة هي كيفية تغيير FoF لطريقة كتابة المهندسين لرموز التنسيق في Slash. بدونه، يحل كل مهندس نفس المشاكل بطرق مختلفة. الجميع يعيد اختراع العجلة، وتكون العجلات جميعها بأشكال مختلفة قليلاً.

FoF يرفع الحد الأدنى. أنت تحدد ماذا يجب أن يحدث، لا كيف للتعامل مع كل حالات الفشل. يتعامل إطار العمل مع التنفيذ والمثابرة والقابلية للمراقبة. يمكن للمهندسين الجدد قراءة تعريف التدفق وفهمه دون الحاجة إلى تتبع طبقات من التعليمات البرمجية الإلزامية. كما يصعب كتابة تعليمات برمجية سيئة للتنسيق عندما تجبرك التجريد على أن تكون واضحًا بشأن الأحداث والآثار الجانبية وانتقالات الحالة.

المنحدرات: FoF في الممارسة العملية

مع FoF كأساس لتكوين التدفقات المالية، أصبح بناء منتجاتنا الأساسية مسألة تحديد القواعد الصحيحة لكل تدفق. لا يهم التجريد ما هي الأنظمة الموجودة على الطرف الآخر، فهو يقوم فقط بالتنسيق.

تعد منافذ الخروج والدخول "تدفقات" تنسقها هذه المحرك، وتقدم نظامًا خارجيًا جديدًا: مزود تشفير (أو مكتب OTC) يتعامل مع تحويلات العملات المستقرة/العملات الورقية. مثل أي نظام خارجي، فإنها تقدم تحديثات الحالة وفقًا لشروطها الخاصة - والتي يمكننا استخدامها لتشغيل أحداث FoF. ومن هناك، يصبح الأمر مجرد تكوين تدفق.

مخارج الطرق السريعة

تتيح منصات Off-ramps للعملاء تلقي مدفوعات العملات المستقرة وتسويتها بالدولار الأمريكي في حساب Slash الخاص بهم. العملية بسيطة ومباشرة:

  1. يتلقى العميل USDC أو USDT على عنوان إيداع نقوم بإنشائه عبر مزود خدمات التشفير الخاص بنا
  2. يكتشف المزود الإيداع، ويقوم بتسييله إلى الدولار الأمريكي، ويبدأ عملية ACH أو تحويل مصرفي.
  3. يستلم مزود الخدمات المصرفية لدينا التحويل الوارد
  4. نقوم بمطابقة التحويل مع المعاملة الأصلية ونقيد المبلغ في الحساب.

بالنسبة للإيداعات الفورية — حيث نقوم بتقييد المبلغ لحساب العميل على الفور وتحصيل السداد عند تسوية ACH — يتضمن التدفق صرف القرض وتحصيل السداد وتحصيل الرسوم. كل مسألة تمثل قاعدة منفصلة، تنتظر الحدث الخاص بها، لتشكل تدفقًا واحدًا متماسكًا. يبدو تعريف FoF كما يلي:

const offRampInstantDepositFlow = createRule({
  name: 'instant_deposit',
  events: ['provider.deposit_received'],
})
  .addSideEffect(
    DisburseLoan.create((ctx) => ({
      accountId: ctx.accountId,
      amount: ctx.providerDeposit.amount,
    }))
  )
  .addSideEffect(
	  // The settlement logic doesn't run immediately
	  // it waits as a rule until the ACH actually settles
	  // which might be days later
    CreateRule.create((ctx) => ({
      traceId: ctx.deposit.ach_trace_number,

      rule: createRule({
        events: ['inbound_ach.settled'],
      })
        .addSideEffect(RecollectLoan.create({ loanId: ctx.loanId }))
        .addSideEffect(
          CollectFees.create({ depositId: ctx.providerDeposit.id })
        ),
    }))
  );

المنحدرات

المنحدرات هي العكس: يرسل العملاء الدولار الأمريكي من حساب Slash الخاص بهم ويحصلون على عملات مستقرة في محفظة خارجية. التدفق:

  1. يبدأ العميل عملية تحويل إلى عنوان محفظة الوجهة
  2. نقوم بإنشاء حسابات على حسابهم بالمبلغ بالإضافة إلى الرسوم
  3. نرسل ACH أو حوالة مصرفية إلى تعليمات الإيداع لدى مزود خدمات التشفير لدينا
  4. يتلقى المزود الأموال ويقوم بتسليم العملات المستقرة إلى الوجهة المقصودة
const providerAccountId = '...';
const userAccountId = '...';

const onRampFlow = createRule({
  name: 'on_ramp',
  events: ['flow.begin'],
})
  .addSideEffect(
    InitiateTransfer.create((ctx) => ({
		  type: 'wire',
		  source: userAccountId,
		  destination: providerAccountId;
		  // ...
    }))
  )
  .addSideEffect(
    CreateRule.create((ctx) => ({
      traceId: ctx.transaction.id,
      rule: createRule({
        events: ['outbound_wire.completed'],
      })
        .addSideEffect(CaptureFees.create({ transactionId: ctx.transaction.id }))
    }))
  )
  .addSideEffect(
    CreateRule.create((ctx) => ({
      traceId: ctx.transaction.id,
      rule: createRule({
        events: ['provider.delivery_failed'],
      })
        .addSideEffect(CancelPendingFees.create({ transactionId: ctx.transaction.id }))
        .addSideEffect(InitiateTransfer.create({ 
			     type: 'book',
			     from: operatingAccountId,
			     destination: userAccountId,
			     description: 'Refund',
			     // ...
         })),
    }))
  );

اللافت للنظر هو قلة البنية التحتية الجديدة التي تطلبها ذلك. فقد تم نقل إطار عمل FoF ومنطق التسوية الذي أنشأناه للخروج مباشرة. أما الدخول فهو قواعد مختلفة تستجيب لأحداث مختلفة، ولكنها تعتمد على نفس الآلية الأساسية.

دورة الحياة على السلسلة

حل FoF مشكلة التنسيق على الجانب النقدي — القنوات المصرفية والمزودون والامتثال. ولكن عندما بدأنا في بناء Global USD، واجهنا مشكلة جديدة: السلسلة نفسها. إن إجراء معاملة على السلسلة، والتأكد من أنها تمت بالفعل، والتعامل مع حالات الفشل وإعادة تنظيم السلسلة، واستخلاص حالة دقيقة من النتائج — هذه مشكلة تنسيق مختلفة. كنا بحاجة إلى نفس الضمانات التي حصلنا عليها مع FoF، ولكن للتنفيذ على السلسلة.

النمط: النية → التنفيذ → التوفيق

نحن نستخدم نمطًا ثابتًا في جميع عمليات blockchain:

1. النية: أعلن ما نحاول القيام به

2. تنفيذ: إرسال المعاملة ومتابعتها حتى يتم إدراجها في الكتلة

3 التوفيق: معالجة الكتل المؤكدة، تحديث الحالة الداخلية، تشغيل التدفقات النهائية

إذا كنت من عالم التمويل التقليدي، فإن القياس بسيط:

  • النية ≈ أمر الدفع
  • تنفيذ ≈ معلق
  • التسوية ≈ المنشور

لكل مرحلة مسؤوليات وأساليب فشل متميزة. يشرح باقي هذا القسم كيفية بناء كل طبقة.

قبل أن نتمكن من تنفيذ أي شيء، نحتاج إلى تحديد ماذا نحن نقوم بتنفيذها. معاملة البلوكشين هي في الأساس تعليمات (وظيفة يتم استدعاؤها بمعلمات)، لكن السلسلة لا تفهم التعليمات التي يمكن قراءتها بواسطة البشر. يتم ترميز كل شيء إلى بيانات المكالمات - كتلة من البايتات السداسية العشرية تحدد الوظيفة المطلوب استدعاؤها والمعلمات المطلوب تمريرها.

على سبيل المثال، تحويل USDC بسيط — "إرسال 500 USDC إلى العنوان X" — يصبح:

0xa9059cbb0000000000000000000000007e2f5e1fd4d79ed41118fc6f59b53b575c51f182000000000000000000000000000000000000000000000000000000001dcd6500

بيانات المكالمات الأولية مثل هذه غير واضحة ولا توفر أي معلومات عن لماذا تم تحويل الأموال. وعندما تقوم ببناء أنظمة تحتاج إلى تتبع سياق الأعمال - ليس فقط الأصول التي تم تحويلها، ولكن أيضًا أن هذه كانت رسوم تحصيل للفاتورة رقم 1234 - فأنت بحاجة إلى الحفاظ على هذا السياق.

نحل هذه المشكلة باستخدام سجل لتعريفات المكالمات المكتوبة:

```tsx
export const erc20Transfer = blockchainCallRegistry.define(
  defineBlockchainCall({
    key: 'erc20.transfer',
    abi: 'function transfer(address to, uint256 amount)',
    request: {
      input: tbox.obj({
        contract: tbox.hex(),
        recipient: tbox.hex(),
        amount: tbox.bigIntStr(),
      }),
      categories: [
        'outbound_wire_transfer',
        'outbound_ach_credit',
        'outbound_transfer_fee',
      ],
      tags: [],
      context: tbox.obj({
        relatedAuthorizationId: tbox.opt(tbox.id('authorizationRequest')),
      }),
    },
    encode: ({ input, encodeFunctionData }) => ({
      to: input.contract,
      data: encodeFunctionData([input.recipient, BigInt(input.amount)]),
      value: bigIntStr(0n),
    }),
  })
);

```

يعمل المهندسون بمصطلحات المجال — العقد والمستلم والمبلغ — بدلاً من سلاسل سداسية عشرية. يقوم السجل بالتحقق من صحة المدخلات ومعالجة الترميز والحفاظ على البيانات الوصفية التي سنحتاجها لاحقًا: الفئة والعلامات وسياق العمل.

يصبح إنشاء مكالمة أمرًا بسيطًا:

const call = erc20Transfer.create({
  walletId: 'wallet_12345',
  chain: 'base',
  request: {
    input: {
      contract: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
      recipient: BASE_USDC_FEE_ACCOUNT,
      amount: bigIntStr(25_000_000n), // 25 USD
    },
    category: 'outbound_transfer_fee',
    tags: [],
    description: 'USDC Fee Transfer - SLASH FINANCE',
    context: {}
  },
})

كل مكالمة تصبح سجلاً في BlockchainCall:

interface BlockchainCall {
  id: string;
  type: string;                      // 'erc20.transfer'
  chain: Chain;
  walletId: string;
  to: Hex;                           // Target contract
  value: BigIntStr;                  // ETH value (usually 0)
  data: Hex;                         // Encoded calldata
  request: BlockchainCallRequest;    // Original typed request with metadata

  // Intent association
  intentId: string;
  indexInBatch: number // Position in the batched transaction
}

نحن نتعامل مع BlockchainCall كوحدة عمل أساسية. قد تتضمن المعاملة عدة مكالمات، ولكن كل مكالمة تمثل عملية واحدة قابلة للمساءلة. يحافظ حقل الطلب على الإدخال الكامل المكتوب بالإضافة إلى البايتات المشفرة - بما في ذلك أي بيانات وصفية وسياق تعسفي. هذه البيانات الوصفية هي ما يسمح لنا بالإجابة على سؤال "لماذا تم تحويل مبلغ 500 دولار؟" عندما نقوم بمطابقة حالة السلسلة مع العمليات التجارية.

التنفيذ: توجيه المعاملات إلى نهايتها

يبدو إرسال معاملة أمرًا بسيطًا. لكن في الواقع، هناك فرق شاسع بين "إرسال هذا" و"وصوله".

عندما ترسل معاملة، فإنها لا تذهب مباشرة إلى كتلة. إنها تدخل إلى ميمبول- منطقة انتظار حيث تظل المعاملات حتى يقوم منتج الكتلة باستلامها. أثناء الانتظار، يمكن أن يتم إسقاط المعاملات (امتلاء mempool)، أو تجاوزها (دفع شخص ما رسومًا أعلى)، أو تجميدها (انخفاض سعر الغاز عن الظروف الحالية للشبكة).

الغاز هي الطريقة التي تحسب بها الشبكات القائمة على إيثريوم التكلفة. كل عملية تكلف غازًا، وتدفع ثمن الغاز باستخدام الرمز الأصلي للشبكة. عندما ترسل معاملة، تحدد الحد الأقصى لسعر الغاز الذي ترغب في دفعه. إذا ارتفع ازدحام الشبكة بعد إرسال المعاملة، فقد لا يكون سعر الغاز الذي دفعته تنافسيًا بعد الآن، وستبقى معاملتك في ذاكرة التخزين المؤقتة (mempool) في انتظار، وربما إلى الأبد.

حتى بعد أن يتم إدراج المعاملة في كتلة، فإنها لا تعتبر نهائية بالفعل. يمكن أن تتعرض سلاسل الكتل إلى عمليات إعادة التنظيم (reorgs) - الحالات التي تتخلص فيها الشبكة من الكتل الحديثة وتستبدلها بسلسلة كتل مختلفة. قد تختفي معاملة كنت تعتقد أنها مؤكدة. هذا أمر نادر الحدوث في الشبكات الناضجة، ولكن "نادر" لا يعني "مستحيل" عندما تتعامل بأموال حقيقية.

يحدث كل من هذه الأعطال في طبقة مختلفة: تقدير الغاز، التوقيع، الإرسال، التأكيد. ويتطلب التعافي من كل منها إجراءً تصحيحياً مختلفاً - فالمعاملة المتوقفة تحتاج إلى إعادة الإرسال باستخدام غاز أعلى، والتوقيع غير الصالح يحتاج إلى إعادة التوقيع، وإعادة التنظيم تحتاج إلى إعادة محاولة التدفق بأكمله.

نتعامل مع هذا الأمر من خلال نمذجة دورة حياة التنفيذ على شكل هرم مكون من 4 كيانات. في القمة يوجد النتيجة التجارية التي نسعى لتحقيقها. وتحتها، توجد طبقات أكثر تحديدًا تتعامل مع الإعداد والتوقيع والتقديم. كل طبقة تمتلك مجال فشلها الخاص بها ويمكنها إعادة المحاولة بشكل مستقل قبل تصعيد الأمر إلى الطبقة الأعلى:

BlockchainIntent (what we're trying to achieve)
    └── PreparedCall (unsigned transaction, gas estimates locked)
            └── PreparedCallExecution (signing attempt)
                    └── PreparedCallExecutionNode (submission attempt)

BlockchainIntent يمثل النتيجة التجارية: "تحويل 500 دولار أمريكي إلى هذا العنوان كدفعة للفاتورة رقم 1234". إنه المنسق الأعلى مستوى الذي يتتبع دورة الحياة الكاملة ويمكنه إعادة المحاولة إذا لزم الأمر.

PreparedCall هي معاملة ثابتة وغير موقعة مع تقديرات غاز محددة. إذا انتهت صلاحية تقديرات الغاز (تغيرت ظروف الشبكة)، نقوم بإنشاء PreparedCall جديدة.

تنفيذ المكالمة المعدة مسبقًا يمثل محاولة التوقيع. بالنسبة للعمليات من جانب الخادم، نقوم بالتوقيع تلقائيًا. بالنسبة للعمليات التي يواجهها المستخدم (مثل Global USD)، يوافق المستخدم عبر OTP. في كلتا الحالتين، بمجرد التوقيع، نكون جاهزين للإرسال.

عقدة تنفيذ المكالمة المعدة مسبقًا هي محاولة إرسال واحدة. نرسل المعاملة إلى الشبكة ونطلب إدراجها. إذا فشلت لأسباب قابلة لإعادة المحاولة (انتهاء مهلة الشبكة، حذفها من mempool)، نقوم بإنشاء عقدة جديدة ونحاول مرة أخرى.

تتعامل كل طبقة مع مجال الفشل الخاص بها:

FailureLayerResolution
Network timeoutExecutionNodeRetry with new node
Retry with new nodeRe-org after confirmationRetry with new node
Invalid signaturePreparedCallExecutionRequire new signature
Gas underestimateExecutionNode → IntentEscalate after N retries
Expired gas estimatesPreparedCall → IntentEscalate, create new PreparedCall
Re-org after confirmationBlockchainIntentSpawn child intent

الفكرة الأساسية هي أن حالات الفشل تتصاعد إلى الطبقة الأصلية عندما تستنفد الطبقة خياراتها العلاجية. لنأخذ على سبيل المثال التقدير المنخفض المستمر للغاز. ترتفع حدة ازدحام الشبكة، ولم تعد معلمات الغاز المقفلة في PreparedCall تنافسية. تعيد عقدة التنفيذ المحاولة عدة مرات، وربما يزول الازدحام. بعد N فشل، لا يمكنها فعل المزيد. يتصاعد الفشل إلى التنفيذ، الذي يصل إلى حالة نهائية ويتصاعد إلى النية. تولد النية نية فرعية بمضاعفات غاز أعلى، وتبني PreparedCall جديد، وتبدأ الدورة من جديد.

تتعامل كل طبقة مع مجال الفشل الخاص بها، ولكن التصعيد يكون صريحًا. تحتفظ النية الأصلية بالسجل الكامل؛ بينما تحصل النية الفرعية على محاولة جديدة مع معلمات معدلة. لا نفقد أبدًا السياق المتعلق بـ لماذا نحن نحاول مرة أخرى.

المصالحة: من سلسلة الأحداث إلى حالة المنتج

تم تضمين المعاملة في كتلة. ماذا الآن؟

لا تخبرنا سلسلة الكتل مباشرةً أن تحويلاً بقيمة 1000 دولار أمريكي قد تم. بل تخبرنا أن معاملة قد تم تنفيذها وإصدار بعض سجلات الأحداث. نحتاج إلى تحليل تلك السجلات، وفهم معناها، وتحديث حالتنا الداخلية وفقًا لذلك.

سجلات الأحداث هي الطريقة التي تستخدمها العقود الذكية لتوصيل ما حدث أثناء التنفيذ. عندما تستدعي نقل وظيفة على عقد USDC، يصدر العقد نقل حدث بثلاث بيانات: من أرسله، ومن استلمه، وكم قيمته. يتم تسجيل هذا الحدث في إيصال المعاملة كإدخال في السجل.

لكن السجلات يتم ترميزها كحقول موضوع وبيانات تحتوي على قيم مرمزة سداسي عشري. يتطلب تحليلها معرفة توقيع الحدث وفك ترميز المعلمات. يبدو سجل النقل الخام كما يلي:

// A 5 USDC transfer on Base
{
  "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  "topics": [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x0000000000000000000000007e2f5e1fd4d79ed41118fc6f59b53b575c51f182",
    "0x000000000000000000000000a6dbc393e2b1c30cff2fbc3930c3e4ddfc9d1373"
  ],
  "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40",
  "transactionIndex": "0x34",
  "logIndex": "0x15d",
}

كيف يمكننا معرفة أن هذا نقل؟ كل سجل المواضيع[0] هو التجزئة keccak256 لتوقيع الحدث - في هذه الحالة، 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef هو تجزئة التحويل (العنوان المفهرس من، العنوان المفهرس إلى، قيمة uint256)- حدث النقل القياسي ERC-20. المعلمات المحددة بعلامة مفهرس يتم تخزينها في مصفوفة الموضوعات حسب ترتيب الإعلان، بعد التجزئة المميزة. بالنسبة لحدث النقل هذا، من موجود في المواضيع[1] و إلى في المواضيع[2]. المعلمات غير المفهرسة مثل القيمة مشفرة بواسطة ABI في بيانات.

استخراج تفاصيل النقل من هذا السجل:

  • من: المواضيع[1] (32 بايت، مملوءة بالصفر) → 0x7e2f5e1fd4d79ed41118fc6f59b53b575c51f182
  • إلى: المواضيع[2] (32 بايت، مملوءة بالصفر) → 0xa6dbc393e2b1c30cff2fbc3930c3e4ddfc9d1373
  • القيمة: بيانات تم فك تشفيره كـ uint256 → 0x4c4b40 = 5,000,000 (5 USDC، لأن USDC له 6 أرقام عشرية).

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

يقوم المعالج باستيعاب بيانات السجل الأولية، ويقوم بتحليلها إلى حقول مكتوبة (بدلاً من سلاسل سداسية عشرية)، وينتج كيانات المجال.

const transferLogProcessor = createLogProcessor({
  abi: 'event Transfer(address indexed from, address indexed to, uint256 value)',
  requires: ({ log, chain }) => ({
    token: TokensDependency.args({ chain, contracts: [log.address] }),
  }),
  process: ({ log, tx, parsed, deps }) => {
    return {
      erc20Transfers: [
        {
          from: parsed.from,
          to: parsed.to,
          amount: parsed.value,
          contract: log.address,
          decimals: deps.token.decimals,
		      txHash: tx.hash,
        },
      ],
    };
  },
});

الشمولية مقابل التأكيد

نحن نتعامل مع مرحلتين متميزتين من دورة الحياة:

  1. الشمول - تظهر المعاملة لأول مرة في كتلة. الحالة مؤقتة - لا يزال من الممكن إعادة تنظيم الكتلة.
  2. التأكيد - يصل الكتلة إلى عمق كافٍ (كتل لاحقة كافية لإزالة احتمال إعادة التنظيم). الحالة نهائية.

هذا التمييز مهم. قد نقوم بتحديث واجهة المستخدم لإظهار حالة معلقة عند الإدراج، ولكننا لن نطلق تدفقات FoF النهائية حتى يتم التأكيد. تكلفة التصرف بناءً على الحالة المؤقتة غير محدودة.

تتعامل معالجات السجلات مع الأحداث الفردية، ولكننا غالبًا ما نحتاج إلى التنسيق بينها أو إضافة حالة على مستوى المعاملة. تقوم معالجات المعاملات بإنجاز ذلك: فهي تتلقى الناتج المدمج من جميع معالجات السجلات ويمكنها تحويله أو إضافته أو تشغيل تأثيرات إضافية في المراحل اللاحقة. وهنا أيضًا يظهر دورة الحياة ذات المرحلتين. معالجة المعاملة يعمل عند التضمين - ننتج حالة مؤقتة. تأكيد العملية يتم تشغيله بمجرد أن يصبح الكتلة نهائيًا - وهذا هو المكان الذي نكمل فيه عادةً دورات حياة العمليات المالية.

const processor = createBlockchainTransactionProcessor()
  .requires(({ transaction }) => ({
    // Transaction-level dependencies
  }))
  .processTransaction(async ({ logResults }) => {
    const transfers = logResults.erc20Transfers ?? [];

    // ...

    return {
 	  // ...
      erc20Transfers:
		transfers.map((t) => ({ type: 'insert', entity: t })),
    };
  })
  .processConfirmation(async ({ logResults }) => {
    // Runs when block reaches finality

    const flowOfFundsRulesToTrigger = await Promise.all([
      findFlowOfFundsRules({
        for: logResults.erc20Transfers.map((t) => t.fromAddress),
        events: ['on_chain_transfer.source.confirmed'],
      }),
      findFlowOfFundsRules({
        for: logResults.erc20Transfers.map((t) => t.toAddress),
        events: ['on_chain_transfer.destination.confirmed'],
      }),
    ]);

    // ...

    return {
      // ...
      flowOfFundsRulesToTrigger: flowOfFundsRulesToTrigger.flat(),
      erc20Transfers: logResults.erc20Transfers.map((t) => ({
        type: 'update',
        entity: t,
        changes: {
          status: 'confirmed',
        },
      })),
    };
  })
  .build();

ربط السجلات بالمكالمات

عندما ينتج معالج السجلات لدينا سجل نقل، فإنه يحتاج إلى إعادة الارتباط بـ BlockchainCall الأصلي. يخبرنا السجل بما يلي ماذا حدث — انتقلت الأصول من أ إلى ب. يخبرنا BlockchainCall لماذا—كان هذا تحصيل رسوم، أو دفعة لمورد، أو استرداد أموال. بالنسبة للمعاملات البسيطة التي تتطلب مكالمة واحدة، فإن الأمر بسيط. أما بالنسبة للمعاملات المجمعة —حيث نجمع عدة عمليات في معاملة واحدة على السلسلة لتوفير الغاز— فإن الأمر يصبح أكثر صعوبة. يقدم لنا الإيصال قائمة ثابتة بجميع السجلات الصادرة أثناء التنفيذ، دون الإشارة إلى المكالمة التي أنتجت كل سجل. نحل هذه المشكلة من خلال تتبع إطار المكالمة، والذي نغطيته في القسم المتقدم أدناه.

متقدم: إسناد السجلات المجمعة إلى المكالمات الفردية

يغطي هذا القسم تحديًا تقنيًا محددًا يتعلق بالمعاملات المجمعة. إذا كنت لا تعمل مع ERC-4337 أو التنفيذ المجمع، فلا تتردد في الانتقال إلى Global USD. ذكرنا سابقًا أن ربط السجلات بـ BlockchainCall الأصلي أمر سهل بالنسبة للمعاملات البسيطة. أما بالنسبة للمعاملات المجمعة، فالأمر ليس كذلك.

المشكلة

عندما نجمع عدة عمليات في معاملة واحدة — على سبيل المثال، دفعة بقيمة 500 دولار بالإضافة إلى رسوم بقيمة 1 دولار — يتم تنفيذ كلتا العمليتين بشكل متزامن. يوفر لنا إيصال المعاملة قائمة ثابتة بكل السجلات التي تم إصدارها أثناء التنفيذ:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "type": "0x2",
        "status": "0x1",
        "logs": [
            {
                "address": "0x0000000071727de22e5e9d8baf0edac6f37da032",
                "topics": [ /* ... */ ],
                "data": "0x",
                "logIndex": "0x3db",
            },
            {
                "address": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                "topics": ["0xddf252ad...", /* ... */ ],
                "data": "0x000000000000000000000000000000000000000000000000000000001dcd6500",
                "logIndex": "0x3dc",
            },
            {
                "address": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                "topics": ["0xddf252ad...", /* ... */ ],
                "data": "0x000000000000000000000000000000000000000000000000000000000010c8e0",
                "logIndex": "0x3dd",
            },
            {
                "address": "0x0000000071727de22e5e9d8baf0edac6f37da032",
                "topics": ["0x49628fd1...", /* ... */],
                "data": "0x000000000000000000000000000000000000000000000001000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000009206e5fb300000000000000000000000000000000000000000000000000000000000036f83",
                "logIndex": "0x3de",
            }
        ],
        "logsBloom": "0x...",
        "transactionHash": "0x95aaaa96f3ccefbfcedb1fe5e41d817bc663fcac305ebfc3861f2a189f664cda",
        "transactionIndex": "0x18e",
        "blockHash": "0xfdc6c014e387c5b6f95edc17a25e81f694a5ad46fc75a247aeb0ad3f5dcd8004",
        "blockNumber": "0x25b1de6",
        "gasUsed": "0x28c6d",
        "effectiveGasPrice": "0x1b3ed0",
        "blobGasUsed": "0x24078",
        "from": "0x8dc6004dcbfdceeff98f16e7d58b0fde5fab1412",
        "to": "0x0000000071727de22e5e9d8baf0edac6f37da032",
    }
}

نحصل على مصفوفة مسطحة لكل سجل تم إصداره أثناء التنفيذ. بالنظر إلى هذا الإيصال، يمكننا تحديد حدثين نقل في مؤشرات السجل 1 و 2 (وكلاهما يشتركان في 0xddf252ad... توقيع الحدث الذي ناقشناه سابقًا).

ولكن أيهما كان الدفع وأيهما كان الرسوم؟ الإيصال لا يخبرنا بذلك — فالسجلات تُنسب إلى المعاملة عالية المستوى، وليس إلى المكالمات الفردية داخل الدفعة. قد تفكر في: مجرد مطابقة السجلات مع المكالمات بالترتيب. ولكن هذا لا يعمل إلا إذا أصدرت كل مكالمة سجلاً واحداً بالضبط. التحويل البسيط يصدر سجلاً واحداً؛ أما التبادل فقد يصدر خمسة سجلات. بدون معرفة الحدود، لا يمكنك تعيينها بشكل موثوق.

تتبع إطار المكالمة

وقد تبين أن الحل هو debug_traceTransaction- طريقة RPC لعقدة أرشيف Geth يستخدمها معظم الأشخاص لتصحيح الأخطاء في المعاملات الفاشلة. لكنها تقوم بشيء آخر: تعيد تشغيل المعاملة وتعيد شجرة إطار الاستدعاء الكاملة، مع إرفاق السجلات بالعمق الصحيح في التسلسل الهرمي للاستدعاء.

{
  "jsonrpc": "2.0",
  "method": "debug_traceTransaction",
  "params": [
    "0x...",
    {
        "tracer": "callTracer",
        "tracerConfig": {
            "withLog": true
        }
    }
  ],
  "id": 1
}

والنتيجة هي بنية متداخلة بشكل متكرر لإطارات المكالمات (مبسطة لتسهيل القراءة)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "from": "0x8dc6004dcbfdceeff98f16e7d58b0fde5fab1412",
    "to": "0x0000000071727de22e5e9d8baf0edac6f37da032",
    "input": "0x765e827f...",
    "calls": [
      { /* ... */ },
      { /* ... */ },
      {
        "from": "0x0000000071727de22e5e9d8baf0edac6f37da032",
        "to": "0x0000000071727de22e5e9d8baf0edac6f37da032",
        "input": "0x0042dc53...",
        "calls": [
          {
			// ...
            "calls": [
              {
                "from": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                "to": "0xe1452ff880739d26f341c2510222e808def38d10",
                "input": "0xa9059cbb...",
                "logs": [
                  {
                    "address": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                    "topics": [ /* ... */ ],
                    "data": "0x...",
                    "position": "0x0",
                    "index": "0x1"
                  }
                ],
                "value": "0x0",
                "type": "DELEGATECALL"
              }
            ]
          },
          {
			// ...
            "calls": [
              {
                "from": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                "to": "0xe1452ff880739d26f341c2510222e808def38d10",
                "input": "0xa9059cbb...",
                "logs": [
                  {
                    "address": "0xcc7b940e22e1eef83c0170608aed6d5fd386cdad",
                    "topics": [ /* ... */ ],
                    "data": "0x...",
                    "position": "0x0",
                    "index": "0x2"
                  }
                ],
                "value": "0x0",
                "type": "DELEGATECALL"
              }
            ]
          }
        ]
      }
    ],
    "logs": [],
    "value": "0x0",
    "type": "CALL"
  }
}

نقوم بتسوية هذه البنية التكرارية في مخطط يحافظ على العلاقات الشجرية:

export type EvmCallType =
  | ('CALL' | 'DELEGATECALL' | 'STATICCALL' | 'CREATE' | 'CREATE2')
  | (string & {});

/**
 * Represents a single frame in an EVM transaction's call stack trace.
 *
 * Each call frame captures the execution of one contract call, including:
 * - Its position in the call tree (parent, depth, index)
 * - Call details (type, from, to, value, input, output)
 * - Gas metrics (gas available, gas used)
 *
 * Call frames form a tree structure via the parentId relationship, where:
 * - Root frame (depth 0): The initial transaction call
 * - Child frames: Calls made by the parent contract (CALL, DELEGATECALL, etc.)
 */
export interface IEvmCallFrameSchema {
  id: Id<'evmCallFrame'>;
  transactionHash: string;
  parentId: Id<'evmCallFrame'> | undefined;
  depth: number;
  index: number;
  type: EvmCallType;
  from: `0x${string}`;
  to: `0x${string}` | undefined;
  value: string;
  input: `0x${string}`;
  output: `0x${string}` | undefined;
  gas: string | undefined;
  gasUsed: string | undefined;
  logs: {
    address: `0x${string}`;
    topics: `0x${string}`[];
    data: `0x${string}`;
    position: number;
    index: number;
  }[];
}
const root = debugTraceTransactionResp.result;

const recursivelyBuildSchemas = (
  frame: typeof root,
  index: number,
  parent?: InsertSchema<IEvmCallFrameSchema>
): InsertSchema<IEvmCallFrameSchema>[] => {
  const schema: InsertSchema<IEvmCallFrameSchema> = {
    id: generateId('evmCallFrame'),
    transactionHash,
    parentId: parent?.id,
    depth: (parent?.depth ?? -1) + 1,
    index,
    type: frame.type,
    from: frame.from,
    to: frame.to,
    value: (frame.value ? BigInt(frame.value) : BigInt(0)).toString(),
    input: frame.input,
    output: frame.output,
    gas: frame.gas ? BigInt(frame.gas).toString() : undefined,
    gasUsed: frame.gasUsed ? BigInt(frame.gasUsed).toString() : undefined,
    logs: (frame.logs ?? []).map((log) => ({
      ...log,
      position: Number(log.position),
      index: Number(log.index),
    })),
  };

  return [
    schema,
    ...(frame.calls ?? []).flatMap((call, index) =>
      recursivelyBuildSchemas(call, index, schema)
    ),
  ];
};

const schemas = recursivelyBuildSchemas(root, 0);

const trace: InsertSchema<IEvmTransactionTraceSchema> = {
  transactionHash,
  chain,
  rootFrameId: schemas[0].id,
};

await persistTransactionTrace({
  trace,
  callFrames: schemas,
});

لنفترض أن هناك عملية UserOp مجمعة تتضمن تحويلين USDC: دفعة بقيمة 500 دولار ورسوم بقيمة 1.10 دولار، ممثلة في تتبع التنفيذ أعلاه. يقدم لنا التتبع ما يلي:

EntryPoint.handleOps()
...
└── SmartWallet.executeBatch()
    ├── Call 0: USDC.transfer(500.00 USDC to 0x...)
   └── Transfer(from, to, 500000000)
    ├── Call 1: USDC.transfer(1.10 USDC to Slash)
   └── Transfer(from, to, 1100000)
    └── ...

يمكن الآن تمثيل المعاملة بأكملها على شكل شجرة. وهذا يعيد صياغة المشكلة بأكملها: بدلاً من استنتاج البنية من مصفوفة سجلات مسطحة، نقوم بإعادة بناء شجرة التنفيذ - حيث تكون هرمية الاستدعاءات واضحة وتكون السجلات مرفقة بالإطارات التي أصدرتها.

من هناك، يصبح الإسناد أمراً بسيطاً. ابحث عن العقدة المطابقة لـ executeBatch() استدعاء، تكرار عبر عناصره الفرعية في الفهارس 0..N-1، ويجمع السجلات بشكل متكرر من كل شجرة فرعية. كل فهرس فرعي 0..N-1 يربط مباشرة بـ BlockchainCall المقابل له indexInBatch. نحن نعرف الآن بالضبط أي مكالمة أنتجت أي سجلات.

نظرًا لأن كل معاملة تقريبًا تحتاج إلى هذا الإسناد، فقد قمنا بدمجه مباشرة في معالج السجلات لدينا. وهو يعيد بناء شجرة المكالمات الكاملة، ويطابق السجلات مع الإطارات الأصلية، ويحل جميع مكالمات BlockchainCalls في الدفعة. ثم يتلقى كل معالج سجلات سياق المكالمة والإطار المحدد للسجل الذي يتعامل معه:

const transferLogProcessor = createLogProcessor({
  abi: 'event Transfer(address indexed from, address indexed to, uint256 value)',
  requires: ({ call, callFrame }) => ({
    // ...
  }),
  process: ({ call, callFrame }) => {

    return {
     // ...
    };
  },
});

سلسلة الإسناد الكاملة:

```
Transfer log
 EvmCallFrame (index 0 under executeBatch)
 BlockchainCall (indexInBatch: 0)
 type: 'erc20.transfer'
 category: 'outbound_ach_credit'
 context: { recipientName: 'Acme Corp' }
```

الدولار الأمريكي العالمي: النقطة الأساسية

حلّت عمليات السحب والإيداع مشكلة لعملائنا الحاليين، وهم الشركات التي تمتلك حسابات مصرفية في الولايات المتحدة وترغب في التحويل بين العملات التقليدية والعملات المشفرة. لكننا ظللنا نسمع من قطاع مختلف: الشركات الدولية التي تحتاج إلى الوصول إلى قنوات التحويل بالدولار الأمريكي ولكن لا يمكنها الحصول عليها بسهولة.

إذا كنت مقاول برمجيات في الأرجنتين، أو تاجر تجارة إلكترونية في نيجيريا، أو شركة SaaS في جنوب شرق آسيا، فإن فتح حساب مصرفي في الولايات المتحدة غالبًا ما يتطلب تأسيس كيان أمريكي — محامون، ووكلاء مسجلون، وشهور من النفقات العامة. العديد من الشركات الشرعية محرومة فعليًا من الاقتصاد الدولاري، ليس بسبب أي شيء فعلته، ولكن بسبب المكان الذي تأسست فيه.

العملات المستقرة تغير هذا الوضع. رصيد USDC هو رصيد بالدولار. Global USD هي محاولتنا لبناء بنية تحتية مصرفية على أساس هذه الفرضية.

غير احتجازية حسب التصميم

لقد أنشأنا Global USD كنظام غير أمين. وقد استند هذا القرار إلى عاملين: تعقيد اللوائح التنظيمية والثقة.

يستلزم الاحتفاظ بأموال العملاء متطلبات ترخيص تختلف باختلاف الولاية القضائية. تعمل البنية غير الاحتجازية على تبسيط وضع الترخيص لدينا في العديد من هذه الأسواق. على صعيد الثقة، يتحكم العملاء في مفاتيحهم الخاصة — وبحكم تصميمها، لا يمكن لـ Slash بدء التحويلات دون الحصول على إذن تشفير من الموقعين على الحساب.

البدائية الأساسية هي محفظة ذكية: عقد ذكي يعمل كمحفظة ولكن مع تحكم قابل للبرمجة في الوصول.

كل حساب Global USD هو محفظة ذكية تخضع لنظام التوقيع المتعدد. كل عضو مخول في الشركة يمتلك مفتاحًا. تتطلب التحويلات موافقتهم قبل تنفيذها. Slash يمكنه تحضير معاملة، لكننا لا نستطيع تنفيذ دون الحصول على إذن من الموقّع.

التوقيع دون حضانة

وهذا يطرح سؤالاً يتعلق بتجربة المستخدم: إذا كان المستخدمون يتحكمون في المفاتيح، ألا يحتاجون إلى إدارة عبارات البذور وتوقيع المعاملات يدويًا؟

نحن نستخدم بنية محفظة مدمجة من Privy و Alchemy. عندما ينشئ المستخدم حسابًا، يتم إنشاء مفتاح خاص داخل ذاكرة معزولة عن الأجهزة (بيئة تنفيذ موثوقة أو TEE). المفتاح موجود، ولكنه مصمم بحيث لا يمكن الوصول إليه مباشرة من قبل Slash أو أي شخص آخر. عندما يبدأ المستخدم عملية تحويل، فإنه يوافق عليها عبر OTP، مما يخول TEE التوقيع نيابة عنه. ثم يتم إرسال المعاملة الموقعة إلى الشبكة.

من وجهة نظر المستخدم، يبدو الأمر وكأنه الموافقة على تحويل مصرفي. من وجهة نظر الحفظ، نحن لا نلمس المفاتيح الخاصة أبدًا.

ما الذي يفتحه هذا

يمكن الآن لأي شركة في لاغوس الاحتفاظ بالدولار الأمريكي وتلقي المدفوعات من العملاء الأمريكيين ودفع المبالغ للموردين الدوليين، كل ذلك دون الحاجة إلى حساب مصرفي في الولايات المتحدة ودون مخاطر الحراسة، مع نفس إجراءات التدقيق والامتثال التي نطبقها على أي عميل من عملاء Slash.

هذا هو ما يمكن أن تكون عليه العملات المستقرة في الواقع: ليست مجرد وسيلة للدفع، بل بنية تحتية أساسية لنظام مالي أكثر سهولة.

ماذا بعد؟

البرمجيات الأولية التي قمنا بتطويرها لا تقتصر على تحويل الأموال بين العملات التقليدية والعملات المشفرة. إنها الأساس لكل ما نقوم بتطويره في Slash. نحن نعمل على توسيع عروض حساباتنا العالمية، مما يتيح لمزيد من الشركات الوصول إلى قنوات التحويل بالدولار الأمريكي بغض النظر عن مكان تأسيسها. ونحن نعمل على تطوير بطاقتنا العالمية: بطاقة مدعومة بعملة مستقرة وتوفر نسبة عالية من الاسترداد النقدي، تتيح للعملاء إنفاق أرصدتهم في أي مكان. وكلاهما يعتمد بشكل كبير على نفس أطر التنسيق والتنفيذ التي وصفناها هنا. إذا وصلت إلى هذه المرحلة وأنت مهندس ترغب في حل مشاكل البنية التحتية الصعبة لعملاء حقيقيين في شركة سريعة النمو، فنحن نبحث عن موظفين.

Read more from us