

Escrow Smart Contract
Müşteri ve hizmet sağlayıcı parayı akıllı sözleşmeye yatırır. İş bittiğinde ve iki taraf da onayladığında ödeme hizmet sağlayıcıya geçer.
Nasıl çalışır
Sözleşmeye beş adım
Basit bir sihirbazla akıllı sözleşme oluşturun.
Basit bir sihirbazla akıllı sözleşme oluşturun.
- Adım 1Sözleşmedeki rolSözleşmedeki rolünüzü seçin. Sözleşmede yalnızca iki rol vardır: müşteri ve hizmet sağlayıcı. Üçüncü taraf hakemlik yoktur.9:41LTE
Sözleşmedeki rol
- Adım 2Para birimi seçimiSözleşmenin para birimini seçin. TON'un yerel para birimi GRAM, oldukça oynak olduğu için hızlı ve küçük işlemler içindir. USDT, büyük ve uzun işlemler için daha istikrarlı bir para birimidir.9:41LTE
Para birimi seçimi
- Adım 3Sözleşme tutarıSeçilen para biriminde sözleşme tutarını girin. Bu tutar, iş tamamlandıktan sonra hizmet sağlayıcıya aktarılacak.9:41LTE
Sözleşme tutarı
Sözleşme tutarı, işin bedelidir. Müşteri tarafından yatırılır.
%10 ile %90 arası. Varsayılan: 30%.
Tutarın 0,4% kadarı, ancak en fazla 15 GRAM.
- Adım 4Cüzdan adresleriCüzdan adreslerini yapıştırın. Para yatırma ve çekme işlemleri daha sonra bu cüzdanlar üzerinden yapılacak.9:41LTE
Cüzdan adresleri
- Adım 5Sözleşme kontrolüDeploy etmeden önce girilen sözleşme bilgilerini kontrol edin.9:41LTE
Sözleşme kontrolü
Sözleşmedeki rol
Oluşturduktan sonra
Sözleşme kartı
Müşteri sizsiniz, para birimi USDT. Gerçek paneldeki kartın aynısından altı görüntü; her adımda yapılabilecek tüm işlemlerle.
Yeni oluşturuldu
Bakiyeler sıfır. İşlem tutarını, doğrudan %30'a kadar ek depozitoyla birlikte tutarı yatırabilir ya da kimse bir şey yatırmamışken sözleşmeyi silebilirsiniz.
Tutar yüklendi, bekleniyor
İşlem tutarı sözleşmede, hizmet sağlayıcının depozitosu henüz yok. Durum “Aktif” olana kadar paranızı çekebilirsiniz. %30'a kadar ek depozitoyu hâlâ ekleyebilirsiniz.
Hizmet sağlayıcı yükledi
Kararlaştırılan iki tutar da yerinde, sözleşme aktif. Tutarlar donduruldu, USDT çekilemez. İşi kabul edebilir, farklı bir pay önerebilir ya da ek depozito (müşterinin ek teminatı) ekleyebilirsiniz.
%20 depozito eklendi
İşlem tutarının üzerinde +200 USDT var. %30 tavanına 100 USDT daha var. Çekim hâlâ kapalı: aktif sözleşme, sözleşme tutarını ve ek depozitoyu tutar.
Hizmet sağlayıcıya %80 önerildi.
Hizmet sağlayıcı kabul ederse 800 USDT alacak. “İşi kabul et” düğmesi kayboldu; yalnızca %100 belirlerseniz yeniden görünür. Teklif değiştirilebilir, ek depozito artırılabilir.
Hizmet sağlayıcı kabul etti
Sözleşme tamamlandı: 800 USDT hizmet sağlayıcıya, 4 USDT hizmetin telif ücreti olarak gitti. Size 400 USDT kaldı, bunu çekebilirsiniz.
Q&A
Sık sorulan sorular
Üçüncü taraf nerede?
Yok. Hizmet sağlayıcı depozitosunu riske atar. İşi sonuna kadar götürmek onun yararınadır.
Hizmet sağlayıcı depozitosu varsayılan olarak işlem tutarının %30'udur. Onun avantajlı olduğu düşünülebilir: o %30'unu, siz ise %100'ünüzü riske atıyorsunuz. Ama önemli olan yüzdeler değil, paradır. Onun %30'u kendi parasıdır ve bunu kaybetmek ona, sizin %100'ünüzü kaybetmeniz kadar acı verebilir. Kimse kendi parasını boş yere feda etmez.
Başka bir risk: müşteri işi aldı ve parayı ödemeyebilir. Bu durumda iki seçenek var. Birincisi, iş neredeyse bitmişken önceden hesaplaşmayı önermek. İkincisi, müşteriden tutarın üzerine %30'a kadar ek depozito yatırmasını istemek. Sözleşme tamamlandıktan sonra müşteri depozitosunu geri alır, bu yüzden işlemi (sözleşmeyi) kapatmak için bir nedeni olur.
Size neden güveneyim?
Güvenmeniz gerekmiyor. Akıllı sözleşme herkese açık. Sözleşmenizin adresi, başlangıç verilerinin ve kodun hash'idir. Kod değiştirilirse adres de farklı olur.
Sözleşmeyi açıp inceleyebilirsiniz (örneğin Tonviewer'da). Tek şey, kodu anlamanız gerekmesi — asıl sorun bu. Ama karşılığında bu %100 kanıttır. Kodu bir yapay zekâ modeline de verebilirsiniz. Aşağıda sözleşme kodu, mümkün olan en basit açıklamayla yer alıyor.
ContractUSDT
Bu, içe aktarmaların listesi: sabitler ve yardımcı fonksiyonlar.
1import "@contracts/common-messages"2import "@contracts/errors"3import "@contracts/contract_USDT/usdt-utils"4import "fees-management"5import "messages"6import "storage"Sözleşmenin kısa tanımı.
8contract ContractUSDT {9 author: "Alexandr_K"10 version: "0.0.4"11 description: "Escrow contract USDT"12 storage: StorageUsdt13 incomingMessages: AllowedMessage14}Sözleşmeyle yapılabilecek işlemlerin listesi.
16type AllowedMessage =17 | TransferNotificationForRecipient18 | ChangePercent19 | AcceptPercent20 | RefundUsdt21 | ErrRefund22 | ErrMode23 | BurnContractYardımcı fonksiyon: sözleşme aktif mi, değil mi. Herkes tutarını yatırınca sözleşme aktif olur ve para artık öylece çekilemez.
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}Para üstü fonksiyonu. Her mesaja işlemin işlenmesi için biraz GRAM eklenir. Fazlasını bu fonksiyon geri gönderir.
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}Jetton cüzdanının adresini hesaplar. Bu adres, sözleşme adresine bağlıdır.
78fun contractUsdtWallet(): address {79 return calcAddressOfJettonWallet(contract.getAddress());80}Bir transfer başarısız olursa para geri gelir ve sözleşme kimde ne kadar olduğuna dair kayıtları düzeltir.
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}Mesaj gövdesi boşsa hiçbir şey yapmayız.
131fun onInternalMessage(in: InMessage) {132 if (in.body.isEmpty()) {133 return;134 }Ardından değişkenlerin tanımlanması.
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 yükleme: transferleri kabul eder ve kimde ne kadar USDT olduğunu kaydeder.
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}Ödeme yüzdesini değiştirir. İş yalnızca yarıya kadar yapıldıysa %50 önerebilirsiniz. Hizmet sağlayıcı kabul ederse sözleşme yerine getirilir. Etmezse pazarlığa devam edin.
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}Hizmet sağlayıcı kabul ederse sözleşme ona payını ve depozitosunu öder, kalanı size gider. %0'da tüm işlem iptal edilir ve herkes parasını geri alır. Bu durumda sözleşmeye %0,4 komisyon ödenmez.
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}En büyük blok: para iadelerini yönetir.
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 != null214 ? msg.addressRefund215 : 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.providerDepositInit235 ) 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}Hata modu. Cüzdan kaybolursa veya para kayıt dışı geldiyse, taraflardan biri kontrolü diğer tarafa devredebilir. “Kayıt dışı” durumu, kasıtlı yapılmadıkça aslında imkânsızdır. Ama yine de dursun. Cüzdanı unutmak ya da kaybetmek ise gayet mümkündür.
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 int8284 ) {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 int8298 ) {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 = 112 ProviderFullRights = 213 ClientFullRightsAfterHalfYear = 16 // клиент получает полные права через полгода14 ProviderFullRightsAfterHalfYear = 26 // исполнитель — через полгода15 BlockErrorMode = 9916}ErrRefund: bildirim olmadan gelen ve kayda geçmeyen jetton'ların acil olarak çekilmesi. Ya da cüzdan kaybolduğunda (daha doğrusu şifresi kaybolduğunda).
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 != null336 ? msg.addressRefund337 : 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: boş bir kabuk ağda kalmasın diye sözleşmeyi yok eder. Onu oluşturan kişi (müşteri veya hizmet sağlayıcı) ya da yönetici yakabilir; yalnızca müşteri ve hizmet sağlayıcının bakiyeleri sıfırken.
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.adminAddress357 ) throw Errors.UnauthorizedSender;358 assert (359 storage.clientAmount == 0 && storage.providerDeposit == 0360 ) 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}Üç sözleşmenin doğrulanmış kaynak kodu. Kod özeti ağdaki kodla aynı. Her bağlantı kaynağı TON Verifier'da açar.
ContractMinterContractUSDTContractTON