SettleDvp는 거래 레코드에 지정된 정산 권한자가 서명합니다. 하나의
트랜잭션에서 자산 레그의 amount_a를 현금 레그 당사자의 목적지로,
현금 레그의 amount_b를 자산 레그 당사자의 목적지로 이동하고,
두 에스크로 중 어느 쪽이든 남은 잉여분은 해당 레그의 당사자에게 반환하며,
레코드와 두 에스크로를 닫습니다. 이 전송 중 하나라도 완료되지 못하면
인스트럭션은 실패하고 아무것도 이동하지 않습니다.
이 페이지는 거래 생성과
레그 자금 예치에서 이어집니다. 클라이언트, 서명자,
trade, 그리고 당사자들의 token account는 그대로 이어서 사용합니다.
서명 전 재검증
정산 권한자는 양쪽 레그를 이동시키는 트랜잭션에 서명하므로, 서명하기 전에 각 당사자와 동일한 읽기 검증을 수행합니다. 즉 소유자, 크기, 당사자, 민트, 금액, 만료 시점, 그리고 두 목적지를 확인합니다(레그 자금 예치 참조). 프로그램 자체도 정산 시점에 각 민트가 생성 시 기록된 token program에 여전히 속해 있는지, 차단된 확장 기능이 없는지, 거래 생성 시와 동일한 민트 권한을 가지고 있는지 다시 확인합니다. 따라서 차단된 확장 기능이 있거나, 다른 token program을 사용하거나, 민트 권한이 다른 상태로 다시 생성된 민트는 정산될 수 없습니다.
수신 계정 생성
Settle은 프로그램이 생성하지 않는 token account 4개에 기록합니다:
| 계정 | 수신 대상 | 소유자 |
|---|---|---|
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,}),);
Settle
legAExtrasCount는 고정 목록 뒤에 추가된 계정 중 몇 개가 자산 레그 전송의
transfer hook에 속하는지 프로그램에 알려 주며, 나머지는 현금 레그의 것입니다.
transfer hook이 없는 민트의 경우 0을 전달하고 아무것도 추가하지 마세요.
memo program account는 인스트럭션에서 필수이며, 메모가 필요한 목적지에만
사용됩니다.
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를 사용합니다.
상한은 레그당 계정 32개이며, hook 프로그램과 검증 계정을 포함합니다. 양쪽
레그에 hook이 있으면 트랜잭션 계정 한도가 먼저 걸릴 수 있습니다. 프로그램은
전달되는 모든 계정에서 서명자 플래그를 제거하므로, hook은 이 경로로 정산
권한자의 서명을 얻을 수 없습니다.
결과 기록
Settle 트랜잭션이 finalized에 도달하면 거래가 정산된 것으로 간주하세요.
정산 후에는 레코드와 두 에스크로가 더 이상 존재하지 않으므로, 나중에 조건이
필요한 시스템은 정산 전 읽기 결과나 생성 트랜잭션에서 조건을 저장해 두어야
합니다. 닫힌 계정의 rent는 정산 권한자에게 돌아갔습니다.
Settle 오류
LegNotFunded는 에스크로에 합의된 금액보다 적은 자금이 있다는 뜻이고,
DvpExpired와 SettlementTooEarly는 시간 범위를 놓쳤다는 뜻이며,
MintAuthorityChanged와 BlockedMintExtension은 생성 이후 민트가 변경되었다는
뜻입니다. RecipientAtaMismatch는 목적지 또는 환불 token account가 없거나
더 이상 예상한 소유자 및 민트에 속하지 않는다는 뜻으로, 대개 위의 계정 생성
단계를 건너뛰었기 때문입니다. 어느 경우에도 아무것도 이동하지 않았으며,
민트 권한에 따라 당사자들이 거래를 취소할 수 있습니다(거래 취소 참조).
다음 단계
- 거래 취소: 거래가 정산되지 않을 때 자금이 예치된 레그 회수하기
Is this page helpful?