Пополнение ног сделки

Пополнение — это обычный TransferChecked на эскроу ноги, поэтому любой кошелёк или система хранения, способные отправить перевод токена, могут пополнить ногу. Клиент DvP нужен только для чтения записи сделки перед пополнением. Программа проверяет балансы эскроу только при расчёте; при поступлении перевода ничего не проверяется, поэтому каждая сторона читает запись сделки до перевода.

Эта страница продолжает раздел создание сделки; клиент и подписанты переходят из него.

Проверьте условия

Создание записи не требует разрешений, а адреса эскроу выводятся только из записи и mint, но никогда не из условий. Поэтому перевод на эскроу может поступить по условиям, с которыми вы не соглашались, включая адреса расчёта, которые перенаправляют средства вашего контрагента на третий адрес. Перед пополнением прочитайте аккаунт SwapDvp и сверьте его с согласованной сделкой.

verifySwapDvp в TypeScript и verify::decode_swap_dvp_account в Rust подтверждают, что аккаунт принадлежит программе 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 agreed
const expectedExpiry = 1_798_761_600n; // as agreed, unix seconds
if (
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 и возвращает любой излишек стороне ноги, поэтому излишек не пропадёт, но при расчёте добавится второй перевод, который может завершиться ошибкой на некоторых трансфер-хуках (см. примечания ниже).

Используйте TransferChecked, а не непроверяемый Transfer. Минты Token-2022 с расширениями, зависящими от mint account, например Pausable, отклоняют перевод, который его не передаёт, а минт с трансфер-хуком требует, чтобы аккаунты хука были разрешены и добавлены так же, как для любого другого перевода этого токена.

Участник ноги с активом, подписывая как 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]);

Участник денежной ноги делает то же самое для эскроу B с userBAtaB, mintB, userBSigner, t.amountB и { programAddress: tokenProgramB }.

Перевод пополнения, замеченный на уровне confirmed, всё ещё может быть заменён. Программа читает баланс эскроу в сети при расчёте, поэтому Settle, отправленный при непоступившем переводе пополнения, завершится ошибкой LegNotFunded, и ничего не переместится; цена — неудачный Settle и повторная попытка. Считайте ногу пополненной, когда её перевод достиг уровня finalized.

Примечания

  • Ноги с Wrapped SOL. Программа читает баланс нативного эскроу после его синхронизации, поэтому lamports, отправленные на эскроу wrapped-SOL системным переводом, учитываются. Никогда не отправляйте SOL на эскроу для любого другого минта: программа не может вернуть его, и он достанется тому, кто закроет аккаунт.
  • Минты с ограниченным доступом. Для минта с замороженным Default Account State эскроу создаётся замороженным, и перевод завершается ошибкой, пока эмитент не разморозит его или не допустит. В обзоре описано взаимодействие с Token ACL.
  • Трансфер-хуки. Пополняйте эскроу с хуками строго на согласованную сумму. Если излишек вынуждает сделать возврат при расчёте, возврат повторно использует аккаунты хука ноги с другим получателем и суммой, что некоторые хуки отклоняют. Выход — Reclaim, повторное пополнение точной суммой и новый расчёт.
  • Пополнение после истечения срока. Ничто не мешает переводу после expiry_timestamp. Он поступает, расчёт больше невозможен, и участник ноги возвращает средства через Reclaim, пока сделка открыта, или через Recover после её закрытия. См. отмену сделки.
  • Кто пополняет первым. Программа не задаёт очерёдность двух ног. Порядок определяют сами стороны; любая сторона может вернуть свою ногу в любой момент до расчёта, если только полномочия минта (freeze, pause или трансфер-хук) не блокируют эскроу.

Дальнейшие шаги

Is this page helpful?

Содержание

Редактировать страницу