SettleDvp는 거래 레코드에 지정된 정산 권한자가 서명합니다. 하나의 트랜잭션에서
자산 레그의 amount_a를 현금 레그 당사자의 수신 주소로, 현금 레그의 amount_b를
자산 레그 당사자의 수신 주소로 이동하고, 양쪽 에스크로에 남은 초과분을 해당 레그의
당사자에게 반환하며, 레코드와 두 에스크로를 닫습니다. 이 전송 중 하나라도 완료할 수
없으면 명령이 실패하고 아무것도 이동하지 않습니다.
이 페이지는 거래 생성과
레그 자금 예치에 이어지는 내용이며, 클라이언트, 서명자,
trade, 그리고 당사자들의 token account를 그대로 사용합니다.
서명 전에 다시 검증하기
정산 권한자는 두 레그를 이동하는 트랜잭션에 서명하므로, 서명하기 전에 각 당사자와 동일한 조회를 수행합니다. 즉, 소유자, 크기, 당사자, 민트, 금액, 만료 시점, 그리고 두 수신 주소를 확인합니다(레그 자금 예치 참조). 프로그램 자체도 정산 시점에 각 민트가 생성 시 기록된 token program에 여전히 속해 있는지, 차단된 확장 기능이 없는지, 거래가 생성되었을 때와 동일한 민트 권한을 가지고 있는지 다시 확인합니다. 따라서 차단된 확장 기능이 추가되었거나, 다른 token program 또는 다른 민트 권한으로 다시 생성된 민트는 정산할 수 없습니다.
수령 계정 생성하기
Settle은 프로그램이 생성하지 않는 4개의 token account에 기록합니다.
| 계정 | 수령 내용 | 소유자 |
|---|---|---|
userADestinationAtaB | 현금 레그(amount_b) | user_a_settlement_destination |
userBDestinationAtaA | 자산 레그(amount_a) | user_b_settlement_destination |
userAAtaA | 자산 레그의 초과분 | user_a |
userBAtaB | 현금 레그의 초과분 | user_b |
초과분 계정은 에스크로에 합의된 금액보다 많은 금액이 있을 때만 사용됩니다. 하지만 해당 민트를 보유한 누구나 에스크로에 최소 단위 몇 개를 보내 초과분을 만들 수 있습니다. 환불 계정이 존재하지 않으면 Settle 전체가 실패하므로, 네 계정 모두를 같은 트랜잭션에서 멱등하게 생성하세요.
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에
속하는지 프로그램에 알려 줍니다. 나머지는 현금 레그의 것입니다. transfer hook이 없는
민트의 경우 0을 전달하고 아무것도 추가하지 않습니다. memo program account는 명령에 필수이며,
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 hook
생성된 IDL에는 고정 계정만 나열되므로, 생성된 빌더는 hook 계정을 알지 못합니다. 해당 토큰을
직접 전송할 때와 동일한 방식으로 각 hook 민트의 추가 계정 메타를 확인(resolve)한 다음, 이를
명령에 추가하세요. 자산 레그의 계정을 먼저, 그다음 현금 레그의 계정을 추가하고,
legAExtrasCount는 첫 번째 그룹의 개수로 설정합니다. TypeScript에서는 확인한 메타를
명령의 accounts 배열에 펼쳐서(spread) 넣고, Rust에서는 빌더의 add_remaining_accounts를
사용합니다. 한도는 hook 프로그램과 검증 계정을 포함하여 레그당 32개 계정입니다. 두 레그 모두
hook이 있는 경우 트랜잭션의 계정 한도가 먼저 적용될 수 있습니다. 프로그램은 전달되는 모든
계정에서 서명자 플래그를 제거하므로, hook은 이 경로를 통해 정산 권한자의 서명을 얻을 수
없습니다.
결과 기록하기
Settle 트랜잭션이 finalized에 도달하면 거래가 정산된 것으로 간주하세요. 정산 후에는
레코드와 두 에스크로가 더 이상 존재하지 않으므로, 나중에 조건이 필요한 시스템은 정산 전에
조회한 내용이나 생성 트랜잭션에서 이를 저장해 두어야 합니다. 닫힌 계정의 rent는 정산
권한자에게 반환되었습니다.
Settle 오류
LegNotFunded는 에스크로에 합의된 금액보다 적은 금액이 있음을 의미합니다. DvpExpired와
SettlementTooEarly는 시간 범위를 놓쳤음을 의미합니다. MintAuthorityChanged와
BlockedMintExtension은 생성 이후 민트가 변경되었음을 의미합니다.
RecipientAtaMismatch는 수신 주소 또는 환불용 token account가 없거나 더 이상 예상한 소유자 및
민트에 속하지 않음을 의미하며, 대개 위의 계정 생성 단계를 건너뛰었기 때문입니다. 어느
경우든 아무것도 이동하지 않았으며, 당사자들은 민트 권한에 따라 거래를 되돌릴 수 있습니다
(거래 되돌리기 참조).
다음 단계
- 거래 되돌리기: 거래가 정산되지 않을 때 자금이 예치된 레그 회수하기
Is this page helpful?