يوقّع جهةُ التسوية المذكورة في سجل الصفقة على SettleDvp. في
معاملة واحدة تنقل التعليمة amount_a من جانب الأصل إلى وجهة
طرف جانب النقد، وamount_b من جانب النقد إلى وجهة طرف جانب الأصل،
وتعيد أي فائض في أيٍّ من حسابَي الضمان إلى طرف ذلك الجانب، وتغلق السجل
وحسابَي الضمان معًا. وإذا تعذّر إتمام أيٍّ من هذه التحويلات، تفشل التعليمة
ولا يتحرك أي شيء.
تتابع هذه الصفحة ما ورد في إنشاء صفقة
وتمويل الجانبين؛ إذ يستمر استخدام العميل والموقّعين
وtrade وtoken accounts الخاصة بالطرفين.
إعادة التحقق قبل التوقيع
توقّع جهة التسوية على المعاملة التي تنقل الجانبين، لذا تجري قبل التوقيع القراءة نفسها التي يجريها كل طرف: المالك، والحجم، والطرفان، والـ mints، والمبالغ، وتاريخ الانتهاء، والوجهتان (راجع تمويل الجانبين). كما يعيد البرنامج نفسه عند التسوية التحقق من أن كل mint ما زال تابعًا لـ token program المسجَّل عند الإنشاء، ولا يحمل أي امتداد محظور، وله سلطة mint نفسها التي كانت وقت إنشاء الصفقة؛ وبذلك لا يمكن تسوية صفقة يتضمن mint أُعيد إنشاؤه بامتداد محظور، أو بـ token program مختلف، أو بسلطة mint مختلفة.
إنشاء حسابات المستلمين
تكتب Settle في أربعة token accounts لا ينشئها البرنامج:
| الحساب | يستلم | المالك |
|---|---|---|
userADestinationAtaB | جانب النقد (amount_b) | user_a_settlement_destination |
userBDestinationAtaA | جانب الأصل (amount_a) | user_b_settlement_destination |
userAAtaA | أي فائض في جانب الأصل | user_a |
userBAtaB | أي فائض في جانب النقد | user_b |
لا تُستخدم حسابات الفائض إلا عندما يحتوي حساب الضمان على أكثر من المبلغ المتفق عليه، لكن بإمكان أي شخص يملك الـ mint أن يُحدث فائضًا بإرسال بضع وحدات أساسية إلى حساب الضمان. وإذا لم يكن حساب الاسترداد موجودًا، تفشل عملية Settle بأكملها، لذا أنشئ الحسابات الأربعة جميعها بصورة آمنة عند التكرار (idempotent) في المعاملة نفسها.
import {findAssociatedTokenPda,getCreateAssociatedTokenIdempotentInstruction,} from "@solana-program/token-2022";const destinationA = t.userASettlementDestination; // defaults to userAconst destinationB = t.userBSettlementDestination; // defaults to userBconst [userADestinationAtaB] = await findAssociatedTokenPda({mint: mintB,owner: destinationA,tokenProgram: tokenProgramB,});const [userBDestinationAtaA] = await findAssociatedTokenPda({mint: mintA,owner: destinationB,tokenProgram: tokenProgramA,});// userAAtaA and userBAtaB are from fund the legs.const createRecipientAccounts = [{ ata: userADestinationAtaB, owner: destinationA, mint: mintB, tokenProgram: tokenProgramB },{ ata: userBDestinationAtaA, owner: destinationB, mint: mintA, tokenProgram: tokenProgramA },{ ata: userAAtaA, owner: userA, mint: mintA, tokenProgram: tokenProgramA },{ ata: userBAtaB, owner: userB, mint: mintB, tokenProgram: tokenProgramB },].map(({ ata, owner, mint, tokenProgram }) =>getCreateAssociatedTokenIdempotentInstruction({payer: authoritySigner,ata,owner,mint,tokenProgram,}),);
التسوية
يخبر legAExtrasCount البرنامجَ بعدد الحسابات المضافة بعد القائمة الثابتة
والتي تعود إلى transfer hook الخاص بتحويل جانب الأصل؛ أما الباقي فيعود إلى
جانب النقد. وبالنسبة إلى الـ mints التي لا تملك transfer hooks، مرّر القيمة صفرًا
ولا تُضِف شيئًا. إن program account الخاص ببرنامج memo مطلوب في التعليمة،
ولا يُستخدم إلا مع الوجهات التي تشترط memo.
import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";import { getSettleDvpInstruction } from "./dvp";const settleInstruction = getSettleDvpInstruction({settlementAuthority: authoritySigner,swapDvp,mintA,mintB,dvpAtaA: escrowA,dvpAtaB: escrowB,userADestinationAtaB,userBDestinationAtaA,userAAtaA,userBAtaB,tokenProgramA,tokenProgramB,memoProgram: MEMO_PROGRAM_ADDRESS,legAExtrasCount: 0,});const { context: settleContext } = await client.sendTransaction([...createRecipientAccounts,settleInstruction,]);const settleSignature = settleContext.signature;console.log("Submitted:", settleSignature);// sendTransaction returns at the client's commitment (confirmed by default).// Wait for finalized before recording the trade as settled.let finalized = false;for (let attempt = 0; attempt < 30 && !finalized; attempt++) {const {value: [status],} = await client.rpc.getSignatureStatuses([settleSignature]).send();if (status?.err) {throw new Error("Settle transaction failed");}finalized = status?.confirmationStatus === "finalized";if (!finalized) {await new Promise((resolve) => setTimeout(resolve, 2_000));}}if (!finalized) {// A closed record means it settled; an open one is safe to settle again.throw new Error("Settle not finalized after 60s; read the trade record");}console.log("Settled:", settleSignature);
Transfer hooks
لا يسرد IDL المُولَّد سوى الحسابات الثابتة، ولذلك لا تعرف أدوات البناء المُولَّدة
شيئًا عن حسابات الـ hook. حلّ الـ extra account metas الخاصة بكل mint يملك hook
بالطريقة نفسها التي تتبعها عند تحويل مباشر لذلك الرمز، ثم أضِفها إلى
التعليمة: حسابات جانب الأصل أولًا، ثم حسابات جانب النقد، مع ضبط
legAExtrasCount على عدد حسابات المجموعة الأولى. في TypeScript، وزّع
الـ metas المحلولة على مصفوفة accounts الخاصة بالتعليمة؛ وفي Rust، استخدم
add_remaining_accounts على أداة البناء. الحد الأقصى هو 32 حسابًا لكل جانب،
بما في ذلك برنامج الـ hook وحساب التحقق؛ وقد يكون حد الحسابات في المعاملة هو
الأسبق في التقييد عندما يحمل الجانبان كلاهما hooks. ويزيل البرنامج علامة التوقيع
من كل حساب يُمرَّر، وبذلك لا يستطيع الـ hook الحصول على توقيع جهة التسوية
عبر هذا المسار.
تسجيل النتيجة
اعتبر الصفقة مُسوّاة عندما تبلغ معاملة Settle حالة finalized. فالسجل
وحسابا الضمان لا يبقيان موجودين بعد التسوية، لذا فإن النظام الذي يحتاج إلى
الشروط لاحقًا يخزّنها من قراءته السابقة للتسوية أو من معاملة الإنشاء. أما
rent الحسابات المغلقة فقد آل إلى جهة التسوية.
أخطاء Settle
يعني LegNotFunded أن حساب ضمان يحتوي على أقل من المبلغ المتفق عليه؛ ويعني DvpExpired
وSettlementTooEarly أن النافذة الزمنية فاتت؛ ويعني MintAuthorityChanged
وBlockedMintExtension أن mint قد تغيّر بعد الإنشاء؛
ويعني RecipientAtaMismatch أن token account الخاص بالوجهة أو بالاسترداد مفقود أو
لم يعد يخص المالك والـ mint المتوقعين، وغالبًا لأن خطوة إنشاء الحسابات
المذكورة أعلاه قد تم تخطيها. وفي كل الحالات لا يتحرك أي شيء، ويمكن للطرفين
التراجع عن الصفقة، وفقًا لسلطات الـ mint (راجع
التراجع عن صفقة).
الخطوات التالية
- التراجع عن صفقة: استرداد الجانبين الممولين عندما لا تتم تسوية الصفقة
Is this page helpful?