هذه نسخة تجريبية.

Mainnetجيتونات تجريبية
TON Blockchain

Escrow Smart Contract

يودع العميل ومقدم الخدمة الأموال في عقد ذكي. يحصل مقدم الخدمة على أجره عند إنجاز العمل وموافقة الطرفين.

كيف يعمل

خمس خطوات إلى العقد

أنشئ عقدًا ذكيًا بمساعدة معالج بسيط.

  1. الخطوة 1دورك في العقداختر دورك في العقد. في العقد دوران فقط: العميل ومقدم الخدمة. لا يوجد تحكيم من طرف ثالث.
    9:41LTE

    دورك في العقد

    دورك في العقد

    العميل هو من يطلب الخدمة أو السلعة. يدفع 100% مقدمًا، إضافةً إلى وديعة قابلة للاسترداد تصل إلى 30% إن رغب.

    مقدم الخدمة هو من يقدّم الخدمة أو السلعة. يودع وديعة قابلة للاسترداد (افتراضيًا 30%) من مبلغ العقد.

  2. الخطوة 2اختيار العملةاختر عملة العقد. GRAM — العملة الأصلية لشبكة TON — مناسبة للصفقات السريعة والصغيرة، لأنها متقلبة إلى حد كبير. USDT عملة أكثر استقرارًا للصفقات الكبيرة والطويلة.
    9:41LTE

    اختيار العملة

    عملة العقد

    GRAM — إنشاء أسرع. يمكنك إنشاء العقد وإيداع المبلغ فورًا في معاملة واحدة.

    USDT — إنشاء على مرحلتين. أولًا يُنشأ العقد (يجب أن تحتوي محفظتك على العملة الأصلية GRAM أو أن تدعم المعاملات دون غاز؛ ويُفضَّل GRAM)، ثم يُودَع USDT.USDT أقل تقلبًا من GRAM.

  3. الخطوة 3مبلغ العقدأدخل مبلغ العقد بالعملة المختارة. سيُحوَّل هذا المبلغ إلى مقدم الخدمة بعد إنجاز العمل.
    9:41LTE

    مبلغ العقد

    GRAM

    مبلغ العقد هو ثمن العمل. يودعه العميل.

    30%

    من 10% إلى 90%. القيمة الافتراضية: 30%.

    0,4%

    0,4% من المبلغ، على ألا تتجاوز 15 GRAM.

  4. الخطوة 4عناوين المحافظالصق عناوين المحافظ. لاحقًا سيجري إيداع الأموال وسحبها عبر هذه المحافظ.
    9:41LTE

    عناوين المحافظ

  5. الخطوة 5مراجعة العقدراجع بيانات العقد المُدخلة قبل النشر.
    9:41LTE

    مراجعة العقد

    شروط العقد
    العملة
    GRAM
    مبلغ العقد
    1 000 GRAM
    وديعة مقدم الخدمة
    300 GRAM · 30%
    عمولة الخدمة
    4 GRAM · 0,4%

بعد الإنشاء

بطاقة العقد

أنت العميل، والعملة USDT. ست لقطات للبطاقة نفسها كما في لوحة التحكم الحقيقية، مع كل الإجراءات المتاحة في كل خطوة.

  1. بانتظار الإيداعات
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    0 / 1 000
    مقدم الخدمة
    0 / 300

    *بالإضافة إلى وديعة قابلة للاسترداد حتى 30% (حد أقصى)

    USDT

    أُنشئ للتو

    الأرصدة صفر. يمكنك إيداع مبلغ الصفقة، أو المبلغ مع زيادة تصل إلى 30% مباشرة، أو حذف العقد ما دام لم يودع أحد شيئًا.

  2. بانتظار الإيداعات
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    1 000 / 1 000
    مقدم الخدمة
    0 / 300

    *بالإضافة إلى وديعة قابلة للاسترداد حتى 30% (حد أقصى)

    USDT

    تم الإيداع وبانتظار الطرف الآخر

    مبلغ الصفقة موجود في العقد، ووديعة مقدم الخدمة لم تصل بعد. ما دامت الحالة ليست «نشط»، يمكنك سحب أموالك. لا يزال بإمكانك إضافة الزيادة حتى 30%.

  3. نشط
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    1 000 / 1 000
    مقدم الخدمة
    300 / 300
    USDT

    يجب أن يوافق مقدم الخدمة. سيُقرَّب المبلغ إلى نسبة مئوية صحيحة من مبلغ الصفقة.

    *بالإضافة إلى وديعة قابلة للاسترداد حتى 30% (حد أقصى)

    USDT

    أودع مقدم الخدمة

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

  4. نشط
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    1 200 / 1 000 + 200 · 20%
    مقدم الخدمة
    300 / 300
    USDT

    يجب أن يوافق مقدم الخدمة. سيُقرَّب المبلغ إلى نسبة مئوية صحيحة من مبلغ الصفقة.

    *بالإضافة إلى وديعة قابلة للاسترداد حتى 30% (حد أقصى)

    USDT

    أُضيفت وديعة 20%

    يوجد +200 USDT فوق مبلغ الصفقة. يتبقى 100 USDT حتى سقف 30%. السحب لا يزال مغلقًا: العقد النشط يحتفظ بمبلغ العقد إضافةً إلى الزيادة.

  5. تم اقتراح دفعة 800 USDT
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    1 200 / 1 000 + 200 · 20%
    مقدم الخدمة
    300 / 300
    USDT

    المقترح حاليًا 800 USDT · 80%. يمكن استبداله.

    *بالإضافة إلى وديعة قابلة للاسترداد حتى 30% (حد أقصى)

    USDT

    اقتُرح 80% لمقدم الخدمة.

    سيحصل مقدم الخدمة على 800 USDT إذا وافق. اختفى زر «قبول العمل»، ولن يظهر مجددًا إلا إذا حددت 100%. يمكن استبدال الاقتراح وزيادة الوديعة الإضافية.

  6. مكتمل 800 USDT
    1 000 USDT
    عقد بتاريخ 21.09.2026 - EQDe…0001
    العميل (أنت)
    400
    مقدم الخدمة
    1 096

    وافق مقدم الخدمة

    اكتمل العقد: ذهب 800 USDT إلى مقدم الخدمة، و4 USDT إتاوة للخدمة. تبقّى لك 400 USDT يمكنك سحبها.

Q&A

الأسئلة الشائعة

أين الطرف الثالث؟

لا يوجد. مقدم الخدمة يخاطر بوديعته، لذا من مصلحته إنجاز العمل حتى النهاية.

افتراضيًا، وديعة مقدم الخدمة 30% من مبلغ الصفقة. قد يبدو أن لديه الأفضلية: فهو يخاطر بـ30% وأنت بـ100%. لكن المهم ليس النسب، بل المال. فهذه الـ30% هي أمواله الخاصة، وقد يكون فقدانها مؤلمًا له بقدر ما يؤلمك فقدان الـ100% الخاصة بك. لا أحد يضحّي بماله بلا سبب.

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

لماذا عليّ أن أثق بكم؟

لا داعي للثقة. العقد الذكي علني. عنوان عقدك هو تجزئة (hash) للبيانات الأولية والكود. إذا استُبدل الكود، يصبح العنوان مختلفًا.

يمكن فتحه والاطلاع عليه (مثلًا في Tonviewer). الأمر الوحيد أنك تحتاج إلى فهم الكود — وهذه هي المشكلة الأساسية. لكنه في المقابل دليل بنسبة 100%. يمكنك أيضًا إعطاء الكود لنموذج ذكاء اصطناعي. في الأسفل كود العقد مع أبسط شرح ممكن.

ContractUSDT

هذه قائمة الاستيرادات: الثوابت والدوال المساعدة.

contract_usdt.tolk
1import "@contracts/common-messages"
2import "@contracts/errors"
3import "@contracts/contract_USDT/usdt-utils"
4import "fees-management"
5import "messages"
6import "storage"

وصف موجز للعقد.

8contract ContractUSDT {
9 author: "Alexandr_K"
10 version: "0.0.4"
11 description: "Escrow contract USDT"
12 storage: StorageUsdt
13 incomingMessages: AllowedMessage
14}

قائمة الإجراءات المتاحة على العقد.

16type AllowedMessage =
17 | TransferNotificationForRecipient
18 | ChangePercent
19 | AcceptPercent
20 | RefundUsdt
21 | ErrRefund
22 | ErrMode
23 | BurnContract

دالة مساعدة: هل العقد نشط أم لا. عندما يودع الجميع مبالغهم يصبح العقد نشطًا، ولم يعد السحب ممكنًا بهذه البساطة.

28fun StorageUsdt.isActive(self, extra: ExtraFieldsUsdt): bool {
29 if (extra.completed) {
30 return false;
31 }
32 return self.clientAmount >= extra.clientAmountInit &&
33 self.providerDeposit >= extra.providerDepositInit;
34}

دالة للباقي. تُرفق مع كل رسالة كمية صغيرة من GRAM لمعالجة المعاملة. وتعيد الدالة ما يزيد.

67fun sendExcessesTon(to: address, amount: coins) {
68 if (amount > MIN_MESSAGE_VALUE_BOUND) {
69 val excessesMsg = createMessage({
70 bounce: BounceMode.NoBounce,
71 dest: to,
72 value: amount,
73 });
74 excessesMsg.send(SEND_MODE_IGNORE_ERRORS);
75 }
76}

تحسب عنوان محفظة الجيتون. ويعتمد على عنوان العقد.

78fun contractUsdtWallet(): address {
79 return calcAddressOfJettonWallet(contract.getAddress());
80}

إذا فشل التحويل، تعود الأموال ويصحح العقد سجلاته: من يملك كم.

105fun onBouncedMessage(in: InMessageBounced) {
106 in.bouncedBody.skipBouncedPrefix();
107 val msg = lazy BounceOpToHandle.fromSlice(in.bouncedBody);
108 var storage = lazy StorageUsdt.load();
109 match (msg) {
110 TransferBounce => {
111 if (msg.queryId == QueryRole.Client as uint64) {
112 storage.clientAmount += msg.jettonAmount;
113 } else if (msg.queryId == QueryRole.Provider as uint64) {
114 storage.providerDeposit += msg.jettonAmount;
115 } else if (msg.queryId == QueryRole.Admin as uint64) {
116 var extra: ExtraFieldsUsdt = lazy storage.extra.load();
117 extra.royaltyInit += msg.jettonAmount;
118 storage.extra = extra.toCell();
119 } else {
120 // ErrRefund (QueryRole.None) и неизвестные id: учёт не уменьшали
121 return;
122 }
123 }
124 }
125 storage.save();
126}

إذا كان نص الرسالة فارغًا، لا نفعل شيئًا.

131fun onInternalMessage(in: InMessage) {
132 if (in.body.isEmpty()) {
133 return;
134 }

بعد ذلك يأتي تعريف المتغيرات.

137val msg = lazy AllowedMessage.fromSlice(in.body);
138var storage: StorageUsdt = lazy StorageUsdt.load();
139val tonBalanceBeforeMsg = contract.getOriginalBalance() - in.valueCoins;
140val storageFee = MIN_TONS_FOR_STORAGE_THIS_CONTRACT -
141min(tonBalanceBeforeMsg, MIN_TONS_FOR_STORAGE_THIS_CONTRACT);
142var msgValue = in.valueCoins - storageFee - CONTRACT_GAS_CONSUMPTION;

إيداع USDT: يستقبل التحويلات ويسجّل كم يملك كل طرف من USDT.

145TransferNotificationForRecipient => {
146 assert (in.senderAddress == contractUsdtWallet()) throw Errors.InvalidWallet;
147 assert (msg.transferInitiator != null) throw Errors.InvalidData;
148 if (msg.transferInitiator == storage.clientAddress) {
149 storage.clientAmount += msg.jettonAmount;
150 storage.save();
151 } else if (msg.transferInitiator == storage.providerAddress) {
152 storage.providerDeposit += msg.jettonAmount;
153 storage.save();
154 }
155 sendExcessesTon(msg.transferInitiator, msgValue);
156}

يغيّر نسبة الدفع. إذا أُنجز العمل إلى النصف فقط، يمكن اقتراح 50%. وافق مقدم الخدمة — نُفّذ العقد. لم يوافق — واصلا التفاوض.

162ChangePercent => {
163 assert (msgValue > 0) throw Errors.NotEnoughGas;
164 assert (in.senderAddress == storage.clientAddress) throw Errors.UnauthorizedSender;
165 assert (msg.percent >= 0 && msg.percent <= 100) throw Errors.WrongPercent;
166 var extra: ExtraFieldsUsdt = lazy storage.extra.load();
167 assert (extra.completed == false) throw Errors.ContractIsCompleted;
168 assert (storage.isActive(extra)) throw Errors.ContractIsNotActive;
169 extra.percent = msg.percent;
170 if (msg.percent == 100) {
171 extra.completed = true;
172 storage.providerDeposit += extra.clientAmountInit - extra.royaltyInit;
173 storage.clientAmount -= extra.clientAmountInit;
174 }
175 storage.extra = extra.toCell();
176 storage.save();
177 sendExcessesTon(in.senderAddress, msgValue);
178}

إذا وافق مقدم الخدمة، يدفع له العقد حصته مع وديعته، والباقي لك. عند 0% تُلغى الصفقة كلها ويستعيد الجميع أموالهم. وفي هذه الحالة لا تُدفع للعقد عمولة 0.4%.

183AcceptPercent => {
184 assert (msgValue > 0) throw Errors.NotEnoughGas;
185 assert (in.senderAddress == storage.providerAddress) throw Errors.UnauthorizedSender;
186 var extra: ExtraFieldsUsdt = lazy storage.extra.load();
187 assert (extra.completed == false) throw Errors.ContractIsCompleted;
188 assert (extra.percent != 100) throw Errors.WrongPercent;
189 assert (msg.percent == extra.percent) throw Errors.WrongPercent;
190 extra.completed = true;
191 val percentValue = extra.clientAmountInit / 100 * msg.percent;
192 if (msg.percent == 0) {
193 extra.royaltyInit = 0;
194 }
195 storage.providerDeposit += percentValue - extra.royaltyInit;
196 storage.clientAmount -= percentValue;
197 storage.extra = extra.toCell();
198 storage.save();
199 sendExcessesTon(in.senderAddress, msgValue);
200}

أكبر كتلة — تدير استرداد الأموال.

208RefundUsdt => {
209 msgValue -= (MIN_MESSAGE_VALUE_BOUND + MIN_FORWARD_TON_AMOUNT);
210 assert (msgValue > 0) throw Errors.NotEnoughGas;
211 assert (msg.jettonAmount > 0) throw Errors.InvalidData;
212 var extra: ExtraFieldsUsdt = lazy storage.extra.load();
213 val transferRecipient: address = msg.addressRefund != null
214 ? msg.addressRefund
215 : in.senderAddress;
216 val isActive = storage.isActive(extra);
217 var queryId: uint64;
218 var jettonAmount: coins;
219 if (in.senderAddress == storage.clientAddress) {
220 queryId = QueryRole.Client;
221 if (isActive) {
222 val deposit: uint72 = (extra.clientAmountInit / 100) * 30;
223 val locked = extra.clientAmountInit + deposit;
224 assert (storage.clientAmount > locked) throw Errors.NotEnoughJettons;
225 jettonAmount = min(storage.clientAmount - locked, msg.jettonAmount);
226 } else {
227 jettonAmount = min(storage.clientAmount, msg.jettonAmount);
228 }
229 storage.clientAmount -= jettonAmount;
230 } else if (in.senderAddress == storage.providerAddress) {
231 queryId = QueryRole.Provider;
232 if (isActive) {
233 assert (
234 storage.providerDeposit > extra.providerDepositInit
235 ) throw Errors.NotEnoughJettons;
236 jettonAmount = min(
237 storage.providerDeposit - extra.providerDepositInit,
238 msg.jettonAmount,
239 );
240 } else {
241 jettonAmount = min(storage.providerDeposit, msg.jettonAmount);
242 }
243 storage.providerDeposit -= jettonAmount;
244 } else if (in.senderAddress == extra.adminAddress) {
245 queryId = QueryRole.Admin;
246 val maxRefundUsdt: uint72 = extra.completed ? extra.royaltyInit : 0;
247 jettonAmount = min(maxRefundUsdt, msg.jettonAmount);
248 extra.royaltyInit -= jettonAmount;
249 storage.extra = extra.toCell();
250 } else {
251 throw Errors.UnauthorizedSender;
252 }
253 assert (jettonAmount > 0) throw Errors.NotEnoughJettons;
254 storage.save();
255 sendAskToTransferUsdt(
256 queryId,
257 jettonAmount,
258 transferRecipient,
259 msgValue + MIN_MESSAGE_VALUE_BOUND + MIN_FORWARD_TON_AMOUNT,
260 );
261}

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

271ErrMode => {
272 assert (msgValue > 0) throw Errors.NotEnoughGas;
273 var extra: ExtraFieldsUsdt = lazy storage.extra.load();
274 var noticeTo: address? = null;
275 var noticeAboutClient: bool = false;
276 if (in.senderAddress == storage.clientAddress) {
277 if (msg.value == ErrModeVal.ProviderFullRights as int8) {
278 extra.errMode = ErrModeVal.ProviderFullRights;
279 } else if (msg.value == ErrModeVal.BlockErrorMode as int8) {
280 extra.errMode = ErrModeVal.BlockErrorMode;
281 } else if (
282 msg.value == ErrModeVal.ClientFullRightsAfterHalfYear as int8 &&
283 extra.errMode != ErrModeVal.BlockErrorMode as int8
284 ) {
285 extra.errMode = ErrModeVal.ClientFullRightsAfterHalfYear;
286 extra.errModeAt = blockchain.now() as uint64;
287 noticeTo = storage.providerAddress;
288 noticeAboutClient = true;
289 }
290 } else if (in.senderAddress == storage.providerAddress) {
291 if (msg.value == ErrModeVal.ClientFullRights as int8) {
292 extra.errMode = ErrModeVal.ClientFullRights;
293 } else if (msg.value == ErrModeVal.BlockErrorMode as int8) {
294 extra.errMode = ErrModeVal.BlockErrorMode;
295 } else if (
296 msg.value == ErrModeVal.ProviderFullRightsAfterHalfYear as int8 &&
297 extra.errMode != ErrModeVal.BlockErrorMode as int8
298 ) {
299 extra.errMode = ErrModeVal.ProviderFullRightsAfterHalfYear;
300 extra.errModeAt = blockchain.now() as uint64;
301 noticeTo = storage.clientAddress;
302 noticeAboutClient = false;
303 }
304 } else {
305 throw Errors.UnauthorizedSender;
306 }
307 if (noticeTo != null) {
308 assert (msgValue > HALF_YEAR_NOTICE_VALUE) throw Errors.NotEnoughGas;
309 msgValue -= HALF_YEAR_NOTICE_VALUE;
310 }
311 storage.extra = extra.toCell();
312 storage.save();
313 if (noticeTo != null) {
314 sendHalfYearControlNotice(noticeTo, noticeAboutClient);
315 }
316 sendExcessesTon(in.senderAddress, msgValue);
317}
10enum ErrModeVal {
11 ClientFullRights = 1
12 ProviderFullRights = 2
13 ClientFullRightsAfterHalfYear = 16 // клиент получает полные права через полгода
14 ProviderFullRightsAfterHalfYear = 26 // исполнитель — через полгода
15 BlockErrorMode = 99
16}

ErrRefund — سحب طارئ للجيتونات التي وصلت دون إشعار ولم تُسجَّل. أو عندما تُفقد المحفظة (بالأدق: تُفقد كلمة مرورها).

321ErrRefund => {
322 msgValue -= (MIN_MESSAGE_VALUE_BOUND + MIN_FORWARD_TON_AMOUNT);
323 assert (msgValue > 0) throw Errors.NotEnoughGas;
324 assert (msg.jettonAmount > 0) throw Errors.InvalidData;
325 val extra: ExtraFieldsUsdt = lazy storage.extra.load();
326 if (
327 !extra.allowsErrRefund(
328 in.senderAddress,
329 storage.clientAddress,
330 storage.providerAddress,
331 )
332 ) {
333 throw Errors.UnauthorizedSender;
334 }
335 val transferRecipient: address = msg.addressRefund != null
336 ? msg.addressRefund
337 : in.senderAddress;
338 sendAskToTransferUsdt(
339 QueryRole.None,
340 msg.jettonAmount as coins,
341 transferRecipient,
342 msgValue + MIN_MESSAGE_VALUE_BOUND + MIN_FORWARD_TON_AMOUNT,
343 );
344}
36fun ExtraFieldsUsdt.allowsErrRefund(
37 self,
38 sender: address,
39 client: address,
40 provider: address,
41): bool {
42 if (sender == client) {
43 if (self.errMode == ErrModeVal.ClientFullRights as int8) {
44 return true;
45 }
46 return self.errMode == ErrModeVal.ClientFullRightsAfterHalfYear as int8 &&
47 (blockchain.now() as uint64) >= self.errModeAt + HALF_YEAR;
48 }
49 if (sender == provider) {
50 if (self.errMode == ErrModeVal.ProviderFullRights as int8) {
51 return true;
52 }
53 return self.errMode == ErrModeVal.ProviderFullRightsAfterHalfYear as int8 &&
54 (blockchain.now() as uint64) >= self.errModeAt + HALF_YEAR;
55 }
56 return false;
57}

Burn — تدمير العقد كي لا تبقى قشرة فارغة عالقة في الشبكة. يمكن أن يحرقه من أنشأه (العميل أو مقدم الخدمة) أو المسؤول — وفقط عندما يكون رصيدا العميل ومقدم الخدمة صفرًا.

351BurnContract => {
352 assert (msgValue > 0) throw Errors.NotEnoughGas;
353 val extra: ExtraFieldsUsdt = lazy storage.extra.load();
354 val deployer = extra.deployerAddress(storage.clientAddress, storage.providerAddress);
355 assert (
356 in.senderAddress == deployer || in.senderAddress == extra.adminAddress
357 ) throw Errors.UnauthorizedSender;
358 assert (
359 storage.clientAmount == 0 && storage.providerDeposit == 0
360 ) throw Errors.BalancesNotEmpty;
361 if (extra.completed && extra.royaltyInit > 0) {
362 val jettonTon = MIN_MESSAGE_VALUE_BOUND + MIN_FORWARD_TON_AMOUNT;
363 msgValue -= jettonTon;
364 assert (msgValue > 0) throw Errors.NotEnoughGas;
365 sendAskToTransferUsdt(
366 QueryRole.Admin,
367 extra.royaltyInit as coins,
368 extra.adminAddress,
369 msgValue + jettonTon,
370 );
371 }
372 val burn = createMessage({
373 bounce: BounceMode.NoBounce,
374 dest: in.senderAddress,
375 value: 0,
376 });
377 burn.send(SEND_MODE_CARRY_ALL_BALANCE | SEND_MODE_DESTROY);
378}
59fun ExtraFieldsUsdt.deployerAddress(self, client: address, provider: address): address {
60 if (self.mode == QueryRole.Client as int8) {
61 return client;
62 }
63 assert (self.mode == QueryRole.Provider as int8) throw Errors.UnauthorizedSender;
64 return provider;
65}

الكود المصدري الموثّق للعقود الثلاثة. تجزئة الكود تطابق ما هو على الشبكة. كل رابط يفتح المصدر على TON Verifier.

ContractMinterContractUSDTContractTON