Skip to content

Commit

Permalink
feat: use durable nonce for escrow txs (#1459)
Browse files Browse the repository at this point in the history
Co-authored-by: Kirill Bubochkin <ookami.kb@gmail.com>
  • Loading branch information
justinenerio and ookami-kb authored Jun 16, 2024
1 parent 11f4613 commit fb09937
Show file tree
Hide file tree
Showing 30 changed files with 1,280 additions and 854 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:dfunc/dfunc.dart';
import 'package:espressocash_api/espressocash_api.dart';
import 'package:injectable/injectable.dart';
import 'package:solana/encoder.dart';
import 'package:solana/solana.dart';

import '../../utils/transactions.dart';
import '../priority_fees/services/add_priority_fees.dart';
import '../tokens/token.dart';
import 'escrow_account.dart';
import 'instructions.dart';

@injectable
class CreateCanceledEscrow {
const CreateCanceledEscrow(
this._client,
this._addPriorityFees,
this._ecClient,
);

final SolanaClient _client;
final AddPriorityFees _addPriorityFees;
final EspressoCashClient _ecClient;

Future<SignedTx> call({
required Ed25519HDPublicKey escrowAccount,
required Ed25519HDPublicKey senderAccount,
required Commitment commitment,
}) async {
final mint = Token.usdc.publicKey;

final nonceData = await _ecClient.getFreeNonce();
final platformAccount = Ed25519HDPublicKey.fromBase58(nonceData.authority);

await validateEscrow(
address: escrowAccount,
mint: mint,
client: _client,
commitment: commitment,
);

final ataSender = await findAssociatedTokenAddress(
owner: senderAccount,
mint: mint,
);

final ataEscrow = await findAssociatedTokenAddress(
owner: escrowAccount,
mint: mint,
);

final escrowIx = await EscrowInstruction.cancelEscrow(
escrowAccount: escrowAccount,
depositorAccount: platformAccount,
senderAccount: senderAccount,
senderTokenAccount: ataSender,
vaultTokenAccount: ataEscrow,
);

final message = Message(
instructions: [
SystemInstruction.advanceNonceAccount(
nonce: Ed25519HDPublicKey.fromBase58(nonceData.nonceAccount),
nonceAuthority: platformAccount,
),
escrowIx,
],
);

final compiled = message.compile(
recentBlockhash: nonceData.nonce,
feePayer: platformAccount,
);

final priorityFees = await _ecClient.getDurableFees();

return await SignedTx(
compiledMessage: compiled,
signatures: [
platformAccount.emptySignature(),
senderAccount.emptySignature(),
],
).let(
(tx) => _addPriorityFees(
tx: tx,
commitment: commitment,
maxPriorityFee: priorityFees.cancelLink,
platform: platformAccount,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:dfunc/dfunc.dart';
import 'package:espressocash_api/espressocash_api.dart';
import 'package:injectable/injectable.dart';
import 'package:solana/encoder.dart';
import 'package:solana/solana.dart';

import '../../utils/transactions.dart';
import '../priority_fees/services/add_priority_fees.dart';
import '../tokens/token.dart';
import 'escrow_account.dart';
import 'instructions.dart';

@injectable
class CreateIncomingEscrow {
const CreateIncomingEscrow(
this._client,
this._addPriorityFees,
this._ecClient,
);

final SolanaClient _client;
final AddPriorityFees _addPriorityFees;
final EspressoCashClient _ecClient;

Future<SignedTx> call({
required Ed25519HDKeyPair escrowAccount,
required Ed25519HDPublicKey receiverAccount,
required Commitment commitment,
}) async {
final mint = Token.usdc.publicKey;

await validateEscrow(
address: escrowAccount.publicKey,
mint: mint,
client: _client,
commitment: commitment,
);

final nonceData = await _ecClient.getFreeNonce();
final platformAccount = Ed25519HDPublicKey.fromBase58(nonceData.authority);

final instructions = <Instruction>[];

final ataEscrow = await findAssociatedTokenAddress(
owner: escrowAccount.publicKey,
mint: mint,
);

final ataReceiver = await findAssociatedTokenAddress(
owner: receiverAccount,
mint: mint,
);

final shouldCreateAta = !await _client.hasAssociatedTokenAccount(
owner: receiverAccount,
mint: mint,
commitment: commitment,
);

if (shouldCreateAta) {
final iCreateATA = AssociatedTokenAccountInstruction.createAccount(
funder: platformAccount,
address: ataReceiver,
owner: receiverAccount,
mint: mint,
);

instructions.add(iCreateATA);
}

final escrowIx = await EscrowInstruction.completeEscrow(
escrowAccount: escrowAccount.publicKey,
receiverTokenAccount: ataReceiver,
depositorAccount: platformAccount,
vaultTokenAccount: ataEscrow,
);

instructions.add(escrowIx);

final int fee;
if (shouldCreateAta) {
final transactionFees = await _ecClient.getFees();
fee = transactionFees.escrowPaymentAtaFee;
final ataPlatform = await findAssociatedTokenAddress(
owner: platformAccount,
mint: mint,
);
final iTransferFee = TokenInstruction.transfer(
amount: fee,
source: ataReceiver,
destination: ataPlatform,
owner: receiverAccount,
);
instructions.add(iTransferFee);
} else {
fee = 0;
}

final message = Message(
instructions: [
SystemInstruction.advanceNonceAccount(
nonce: Ed25519HDPublicKey.fromBase58(nonceData.nonceAccount),
nonceAuthority: platformAccount,
),
...instructions,
],
);

final compiled = message.compile(
recentBlockhash: nonceData.nonce,
feePayer: platformAccount,
);
final data = compiled.toByteArray().toList();

final priorityFees = await _ecClient.getDurableFees();

return await SignedTx(
compiledMessage: compiled,
signatures: [
platformAccount.emptySignature(),
await escrowAccount.sign(data),
if (shouldCreateAta) receiverAccount.emptySignature(),
],
).let(
(tx) => _addPriorityFees(
tx: tx,
commitment: commitment,
maxPriorityFee: priorityFees.outgoingLink,
platform: platformAccount,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'package:dfunc/dfunc.dart';
import 'package:espressocash_api/espressocash_api.dart';
import 'package:injectable/injectable.dart';
import 'package:solana/encoder.dart';
import 'package:solana/solana.dart';

import '../../utils/transactions.dart';
import '../priority_fees/services/add_priority_fees.dart';
import '../tokens/token.dart';
import 'instructions.dart';

typedef OutgoingEscrowData = ({SignedTx tx, Ed25519HDKeyPair escrow});

@injectable
class CreateOutgoingEscrow {
const CreateOutgoingEscrow(
this._client,
this._addPriorityFees,
this._ecClient,
);

final SolanaClient _client;
final AddPriorityFees _addPriorityFees;
final EspressoCashClient _ecClient;

Future<OutgoingEscrowData> call({
required Ed25519HDPublicKey senderAccount,
required int amount,
required Commitment commitment,
}) async {
final mint = Token.usdc.publicKey;

final nonceData = await _ecClient.getFreeNonce();
final platformAccount = Ed25519HDPublicKey.fromBase58(nonceData.authority);

final escrowAccount = await Ed25519HDKeyPair.random();

final senderATAData = await _client.getAssociatedTokenAccount(
owner: senderAccount,
mint: mint,
commitment: commitment,
);

if (senderATAData == null) {
throw Exception('No token account.');
}

final ataSender = Ed25519HDPublicKey.fromBase58(senderATAData.pubkey);

final instructions = <Instruction>[];

final ataEscrow = await findAssociatedTokenAddress(
owner: escrowAccount.publicKey,
mint: mint,
);
final iCreateATA = AssociatedTokenAccountInstruction.createAccount(
funder: platformAccount,
address: ataEscrow,
owner: escrowAccount.publicKey,
mint: mint,
);

instructions.add(iCreateATA);

final transactionFees = await _ecClient.getFees();

final ataPlatform = await findAssociatedTokenAddress(
owner: platformAccount,
mint: mint,
);
final iTransferFee = TokenInstruction.transfer(
amount: transactionFees.escrowPayment,
source: ataSender,
destination: ataPlatform,
owner: senderAccount,
);

instructions.add(iTransferFee);

final escrowIx = await EscrowInstruction.initEscrow(
amount: amount,
escrowAccount: escrowAccount.publicKey,
senderAccount: senderAccount,
depositorAccount: platformAccount,
senderTokenAccount: ataSender,
vaultTokenAccount: ataEscrow,
);

instructions.add(escrowIx);

final message = Message(
instructions: [
SystemInstruction.advanceNonceAccount(
nonce: Ed25519HDPublicKey.fromBase58(nonceData.nonceAccount),
nonceAuthority: platformAccount,
),
...instructions,
],
);

final compiled = message.compile(
recentBlockhash: nonceData.nonce,
feePayer: platformAccount,
);
final data = compiled.toByteArray().toList();

final priorityFees = await _ecClient.getDurableFees();

return SignedTx(
compiledMessage: compiled,
signatures: [
platformAccount.emptySignature(),
await escrowAccount.sign(data),
senderAccount.emptySignature(),
],
)
.let(
(tx) => _addPriorityFees(
tx: tx,
commitment: commitment,
maxPriorityFee: priorityFees.outgoingLink,
platform: platformAccount,
),
)
.then((tx) => (tx: tx, escrow: escrowAccount));
}
}
Loading

0 comments on commit fb09937

Please sign in to comment.