자금 예치는 레그의 에스크로로 보내는 일반 TransferChecked이므로, 토큰 전송을 보낼 수 있는 모든 지갑이나
수탁 시스템에서 레그에 자금을 예치할 수 있습니다. 예치 전에 거래 레코드를 읽을 때만
DvP 클라이언트가 필요합니다. 프로그램은 정산 시점에만 에스크로 잔액을 확인하며,
전송이 도착할 때는 아무것도 검증하지 않으므로
각 당사자는 전송하기 전에 거래 레코드를 읽어야 합니다.
이 페이지는 거래 생성에서 이어지며, 클라이언트와 서명자는 그대로 사용합니다.
조건 확인하기
레코드 생성은 허가가 필요 없으며, 에스크로 주소는 조건과 무관하게 레코드와 민트만으로 도출됩니다.
따라서 에스크로로 들어오는 전송이 합의하지 않은 조건에 대해 도착할 수 있으며,
여기에는 상대방의 정산 대금을 제3의 주소로 돌리는 정산 목적지도 포함됩니다.
자금을 예치하기 전에 SwapDvp 계정을 읽어 합의한 거래와 대조하세요.
TypeScript의 verifySwapDvp와 Rust의 verify::decode_swap_dvp_account는
해당 계정이 DvP 프로그램 소유인지, 크기가 정확히 458바이트인지,
그리고 계정 자체의 필드에서 도출되는 주소에 위치하는지 확인합니다(fetchSwapDvpChecked는
처음 두 가지만 확인합니다). 일반 fetchSwapDvp 및 decodeSwapDvp 리더는
이 중 어느 것도 확인하지 않습니다. 당사자, 금액, 시간, 목적지에 대한 검사는
호출자가 합의된 거래와 대조하여 직접 수행해야 합니다.
import { unwrapOption } from "@solana/kit";import { verifySwapDvp } from "./dvp";const trade = await verifySwapDvp(client.rpc, swapDvp);const t = trade.data;const expectedAmountA = 1_000_000_000n;const expectedAmountB = 998_500_000n;const expectedEarliestSettlement: bigint | null = null; // as agreedconst expectedExpiry = 1_798_761_600n; // as agreed, unix secondsif (t.userA !== userA ||t.userB !== userB ||t.mintA !== mintA ||t.mintB !== mintB ||t.settlementAuthority !== settlementAuthority ||t.tokenProgramA !== tokenProgramA ||t.tokenProgramB !== tokenProgramB ||t.amountA !== expectedAmountA ||t.amountB !== expectedAmountB ||unwrapOption(t.earliestSettlementTimestamp) !== expectedEarliestSettlement ||t.expiryTimestamp !== expectedExpiry ||t.userASettlementDestination !== userA ||t.userBSettlementDestination !== userB) {throw new Error("Stored terms do not match the agreed trade; do not fund");}if (t.expiryTimestamp <= BigInt(Math.floor(Date.now() / 1000)) + 60n) {throw new Error("Trade expires too soon to fund safely");}
레코드를 상대방이나 정산 권한자가 생성한 경우, 모든 필드를 장부 외부에서 합의한 조건과 비교하세요. 직접 생성했다면, 이 읽기를 통해 전송한 그대로 반영되었는지 확인할 수 있습니다.
에스크로로 전송하기
합의된 금액 그대로 레그에 자금을 예치하세요. 프로그램은 정산 금액을
amount_x로 제한하고 초과분을 레그의 당사자에게 반환하므로 초과분이 유실되지는 않지만,
정산 시 두 번째 전송이 추가되며 일부 트랜스퍼 훅에서는 실패할 수 있습니다
(아래 참고 사항 참조).
검증 없는 Transfer가 아닌 TransferChecked를 사용하세요. Pausable처럼
mint account에 의존하는 확장 기능이 있는 Token-2022 민트는 이를 제공하지 않는 전송을 거부하며,
트랜스퍼 훅이 있는 민트는 해당 토큰의 다른 전송과 마찬가지로 훅의 계정을
확인하여 추가해야 합니다.
자산 레그의 당사자가 userASigner로 서명하여 에스크로 A에 자금을 예치합니다.
import {findAssociatedTokenPda,getTransferCheckedInstruction,} from "@solana-program/token-2022";const [userAAtaA] = await findAssociatedTokenPda({mint: mintA,owner: userA,tokenProgram: tokenProgramA,});const [userBAtaB] = await findAssociatedTokenPda({mint: mintB,owner: userB,tokenProgram: tokenProgramB,});const fundAssetLeg = getTransferCheckedInstruction({source: userAAtaA,mint: mintA,destination: escrowA,authority: userASigner,amount: t.amountA,decimals: 6,}, { programAddress: tokenProgramA });await client.sendTransaction([fundAssetLeg]);
현금 레그의 당사자는 userBAtaB, mintB, userBSigner, t.amountB,
{ programAddress: tokenProgramB }를 사용하여 에스크로 B에 대해 동일하게 수행합니다.
confirmed 상태로 확인된 예치 전송도
이후에 대체될 수 있습니다. 프로그램은 정산 시점에 온체인에서 에스크로 잔액을 읽으므로,
도착하지 않은 예치 전송을 전제로 보낸 Settle은 LegNotFunded로 실패하며
아무것도 이동하지 않습니다. 이 경우 비용은 실패한 Settle과 재시도뿐입니다.
전송이 finalized 상태가 되었을 때 레그에 자금이 예치된 것으로 간주하세요.
참고 사항
- Wrapped SOL 레그. 프로그램은 네이티브 에스크로의 잔액을 동기화한 후 읽으므로, 시스템 전송으로 wrapped SOL 에스크로에 보낸 lamport도 잔액에 포함됩니다. 다른 민트의 에스크로에는 SOL을 절대 보내지 마세요. 프로그램이 이를 반환할 수 없으며, 해당 계정을 닫는 사람에게 넘어갑니다.
- 제한된 민트. Default Account State가 frozen인 민트에서는 에스크로가 동결된 상태로 생성되며, 발행자가 동결을 해제하거나 허용할 때까지 전송이 실패합니다. 개요에서 Token ACL과의 상호작용을 설명합니다.
- 트랜스퍼 훅. 훅이 있는 에스크로에는 합의된 금액 그대로 자금을 예치하세요. 초과분 때문에 정산 시 환불이 필요하면, 환불은 레그의 훅 계정을 다른 목적지와 금액으로 재사용하는데 일부 훅은 이를 거부합니다. 복구 방법은 Reclaim을 실행하고, 정확한 금액으로 다시 예치한 뒤 다시 정산하는 것입니다.
- 만료 이후 예치.
expiry_timestamp이후의 전송을 막는 장치는 없습니다. 전송은 도착하지만 더 이상 정산할 수 없으며, 레그의 당사자는 거래가 열려 있는 동안에는 Reclaim으로, 거래가 종료된 후에는 Recover로 회수합니다. 거래 해제를 참조하세요. - 누가 먼저 예치하는가. 프로그램은 두 레그의 순서를 정하지 않습니다. 순서는 당사자들이 정할 문제이며, 민트 권한(동결, 일시 중지 또는 트랜스퍼 훅)이 에스크로를 막지 않는 한 어느 쪽이든 정산 전에는 언제든 자신의 레그를 회수할 수 있습니다.
다음 단계
Is this page helpful?