إنشاء صفقة

تسجّل CreateDvp شروط الصفقة وتنشئ حسابَي الضمان اللذين سيموّلهما الطرفان. ولا تنقل أي رموز. يمكن لأي جهة إرسالها، لذا قد يأتي السجل من أحد الطرفين المقابلين، أو من مشغّل جهة التسوية، أو من طرف ثالث، ويتحقق كل مشارك آخر من الشروط المخزّنة قبل العمل بها (راجع تمويل الجانبين).

تستخدم المقتطفات العميل المُولَّد مباشرةً مع عناوين نائبة، لتظهر الحسابات والموقّعون المعنيون. وهي تستهدف devnet، حيث يُنشر البرنامج على العنوان نفسه المستخدم في mainnet-beta.

التثبيت

لا توجد حزمة منشورة لعملاء DvP حتى 2 أكتوبر 2026. كلاهما مُولَّد من IDL الخاص بالبرنامج الموجود في المستودع، ويُستخدم من المصدر مباشرةً.

git clone https://github.com/solana-foundation/dvp.git
cd dvp && git checkout df9919ed02c25a93620e7f6820107c9050ce3b92
pnpm install && pnpm generate-clients && pnpm build
# The client is clients/typescript/src; copy or link it into your project as ./dvp
pnpm add @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token-2022 @solana-program/memo

كُتبت هذه الصفحات بالاستناد إلى الـ commit df9919e، مع @solana/kit 8 في TypeScript وحزم solana-* الإصدار 2.x في Rust.

تنتقل المقتطفات في كل صفحة خطوة إلى ما يليها: تستخدم كل صفحة العميل والموقّعين والعناوين المعرّفة في الصفحات السابقة. إن payerSigner وuserASigner وuserBSigner وauthoritySigner هي TransactionSigner الخاصة بدافع rent والطرفين وجهة التسوية، على سبيل المثال من createKeyPairSignerFromBytes. أما مقتطفات Rust فتبني التعليمات وتترك التوقيع والإرسال للمستدعي.

اشتقاق العناوين

سجل الصفقة هو Program Derived Address مشتق من جهة التسوية والطرفين والـ mint‌ين وقيمة nonce. وكل حساب ضمان هو associated token account الخاص بالسجل لأحد الـ mint. لا يحتاج أي من الاشتقاقين إلى الوصول إلى الشبكة.

استخدم قيمة nonce عشوائية بحجم 64 بت لكل صفقة. وبما أن الإنشاء لا يتطلب إذنًا، فإن nonce يمكن التنبؤ به يتيح لطرف ثالث شغل العنوان الذي كانت الصفقة ستستخدمه. مرّر قيمة nonce والمبالغ بصيغة bigint في TypeScript؛ فالعميل المُولَّد يرفض number.

استبدل كل عنصر نائب ..._HERE بعنوان حقيقي قبل التشغيل. تتحقق address() من صيغة base58 وتُطلق خطأً عند وجود عنصر نائب، بينما يفشل تجميع pubkey!.

import { address, createClient } from "@solana/kit";
import { solanaRpc } from "@solana/kit-plugin-rpc";
import { signer } from "@solana/kit-plugin-signer";
import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";
import { findSwapDvpEscrowAta, findSwapDvpPda } from "./dvp";
const client = createClient()
.use(signer(payerSigner))
.use(
solanaRpc({
rpcUrl: "https://api.devnet.solana.com",
rpcSubscriptionsUrl: "wss://api.devnet.solana.com",
}),
);
const settlementAuthority = address("SETTLEMENT_AUTHORITY_ADDRESS_HERE");
const userA = address("ASSET_LEG_PARTY_ADDRESS_HERE");
const userB = address("CASH_LEG_PARTY_ADDRESS_HERE");
const mintA = address("ASSET_MINT_ADDRESS_HERE");
const mintB = address("CASH_MINT_ADDRESS_HERE");
const tokenProgramA = TOKEN_2022_PROGRAM_ADDRESS;
const tokenProgramB = TOKEN_2022_PROGRAM_ADDRESS;
// A fresh random nonce per trade, as a bigint.
const nonce = new DataView(
crypto.getRandomValues(new Uint8Array(8)).buffer,
).getBigUint64(0);
const [swapDvp] = await findSwapDvpPda({
settlementAuthority,
userA,
userB,
mintA,
mintB,
nonce,
});
const [escrowA] = await findSwapDvpEscrowAta({
swapDvp,
mint: mintA,
tokenProgram: tokenProgramA,
});
const [escrowB] = await findSwapDvpEscrowAta({
swapDvp,
mint: mintB,
tokenProgram: tokenProgramB,
});

تسجيل الشروط

تأخذ CreateDvp المبلغين بالوحدات الأساسية، وتاريخ انتهاء الصلاحية، وقيمة nonce، وأربعة حقول اختيارية: مرجعًا غير شفاف بحجم يصل إلى 64 بايت، ووجهة تسوية لكل جانب، وأبكر وقت للتسوية (راجع الملاحظات). وجهة التسوية هي المكان الذي تصل إليه عائدات ذلك الطرف عند التسوية، مثل محفظة إيداع لدى أمين حفظ؛ وتكون افتراضيًا عنوان الطرف نفسه وتُثبَّت بمجرد وجود السجل. أما المبالغ المستردة والمستعادة فتذهب دائمًا إلى token account الخاص بالطرف نفسه لـ mint ذلك الجانب، وليس إلى وجهة تسوية، أيًّا كان من موّل حساب الضمان.

يموّل الدافع الـ rent الخاص بالسجل وعلامة nonce (tombstone) وحسابَي الضمان. وعند الإغلاق، يذهب هذا الـ rent إلى من يوقّع تعليمة الإغلاق، لا إلى الدافع. لا تُغلق علامة tombstone أبدًا، لذا تترك كل صفقة رصيدًا صغيرًا دائمًا. عند تقدير ميزانية SOL الخاصة بالدافع، اشتق مبالغ الـ rent باستخدام طريقة RPC getMinimumBalanceForRentExemption بدلًا من تثبيتها في الشيفرة.

import { getCreateDvpInstruction } from "./dvp";
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
const createInstruction = getCreateDvpInstruction({
payer: payerSigner,
swapDvp,
nonceTombstone: (
await getProgramDerivedAddress({
programAddress: DVP_SWAP_PROGRAM_PROGRAM_ADDRESS,
seeds: ["nonce", getAddressEncoder().encode(swapDvp)],
})
)[0],
settlementAuthority,
userA,
userB,
mintA,
mintB,
dvpAtaA: escrowA,
dvpAtaB: escrowB,
tokenProgramA,
tokenProgramB,
amountA: 1_000_000_000n, // 1,000 units of a 6-decimal asset
amountB: 998_500_000n, // 998.50 units of a 6-decimal cash token
expiryTimestamp: nowSeconds + 60n * 60n * 24n, // one day
nonce,
refString: "ORDER-2026-10-02-0001",
userASettlementDestination: null, // defaults to userA
userBSettlementDestination: null, // defaults to userB
earliestSettlementTimestamp: null,
});
const { context } = await client.sendTransaction([createInstruction]);
console.log("Trade record:", swapDvp, "signature:", context.signature);

يحتاج مقتطف TypeScript إلى getProgramDerivedAddress وgetAddressEncoder من @solana/kit، وDVP_SWAP_PROGRAM_PROGRAM_ADDRESS من العميل، ضمن النطاق. ويترك مُنشئ Rust كلًّا من System Program وAssociated Token Program على قيمهما الافتراضية.

ملاحظات

  • انتهاء الصلاحية. وحدها Settle تتحقق من انتهاء الصلاحية. تعمل Reclaim وCancel وReject بعده، ويمكن لطرف الجانب استرداد أي إيداع يصل بعد انتهاء الصلاحية. يمكن أن يكون انتهاء الصلاحية بعد سنة واحدة على الأكثر من الإنشاء.
  • الوقت هو وقت الشبكة. يقرأ البرنامج الساعة على السلسلة (onchain)، وقد تختلف عن توقيت الساعة الفعلي، أحيانًا بعشرات الثواني، وقد تقفز إلى الأمام. اترك هامشًا بين انتهاء الصلاحية ووقت التسوية المقصود، وتجنّب التمويل أو التسوية قرب أيٍّ من الحدّين، ولا تعتمد على انتهاء الصلاحية لفرض موعد نهائي بالثانية. يقترح README الخاص بالبرنامج تخصيص هامش انحراف يبلغ نحو دقيقة.
  • أبكر وقت للتسوية. اختياري. إذا حُدِّد، تُرفض Settle قبله، ويجب ألا يتجاوز وقت انتهاء الصلاحية.
  • فحوصات وقت الإنشاء. يفشل الإنشاء بالخطأ PartyNotSignerCapable إذا لم يكن أحد الطرفين حسابًا مملوكًا لـ System Program (وهذا يستبعد multisig الخاص بـ SPL Token)، أو بأحد الأخطاء SettlementDestinationIsSwapDvp أو SelfDvp أو SameMint أو ZeroAmount أو SettlementAuthorityIsParty أو ExpiryNotInFuture. القائمة الكاملة في جدول الأخطاء.

الخطوات التالية

Is this page helpful?

جدول المحتويات

تعديل الصفحة