Skip to content

Commit

Permalink
[XRP] Add support for Escrow transactions (#3572)
Browse files Browse the repository at this point in the history
* add XRP escrow proto defs

* XRP escrow transaction encoding

* update XRP signer

* XRP escrow transaction tests

* add XRP mainnet escrow transaction tests

* [KMP]: Bump to 4.0.10

---------

Co-authored-by: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com>
  • Loading branch information
rikublock and satoshiotomakan authored Dec 8, 2023
1 parent 296f9f3 commit 230e7ab
Show file tree
Hide file tree
Showing 6 changed files with 491 additions and 4 deletions.
2 changes: 1 addition & 1 deletion samples/kmp/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.trustwallet:wallet-core-kotlin:4.0.9")
implementation("com.trustwallet:wallet-core-kotlin:4.0.10")
}
}
val commonTest by getting {
Expand Down
72 changes: 72 additions & 0 deletions src/XRP/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
signPayment(input, output, transaction);
break;

case Proto::SigningInput::kOpEscrowCreate:
transaction.createEscrowCreate(
input.op_escrow_create().amount(),
input.op_escrow_create().destination(),
input.op_escrow_create().destination_tag(),
input.op_escrow_create().cancel_after(),
input.op_escrow_create().finish_after(),
input.op_escrow_create().condition());
break;

case Proto::SigningInput::kOpEscrowCancel:
transaction.createEscrowCancel(
input.op_escrow_cancel().owner(),
input.op_escrow_cancel().offer_sequence());
break;

case Proto::SigningInput::kOpEscrowFinish:
transaction.createEscrowFinish(
input.op_escrow_finish().owner(),
input.op_escrow_finish().offer_sequence(),
input.op_escrow_finish().condition(),
input.op_escrow_finish().fulfillment());
break;

case Proto::SigningInput::kOpNftokenBurn:
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
break;
Expand Down Expand Up @@ -94,6 +118,30 @@ TW::Data Signer::preImage() const {
signPayment(input, output, transaction);
break;

case Proto::SigningInput::kOpEscrowCreate:
transaction.createEscrowCreate(
input.op_escrow_create().amount(),
input.op_escrow_create().destination(),
input.op_escrow_create().destination_tag(),
input.op_escrow_create().cancel_after(),
input.op_escrow_create().finish_after(),
input.op_escrow_create().condition());
break;

case Proto::SigningInput::kOpEscrowCancel:
transaction.createEscrowCancel(
input.op_escrow_cancel().owner(),
input.op_escrow_cancel().offer_sequence());
break;

case Proto::SigningInput::kOpEscrowFinish:
transaction.createEscrowFinish(
input.op_escrow_finish().owner(),
input.op_escrow_finish().offer_sequence(),
input.op_escrow_finish().condition(),
input.op_escrow_finish().fulfillment());
break;

case Proto::SigningInput::kOpNftokenBurn:
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
break;
Expand Down Expand Up @@ -149,6 +197,30 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub
signPayment(input, output, transaction);
break;

case Proto::SigningInput::kOpEscrowCreate:
transaction.createEscrowCreate(
input.op_escrow_create().amount(),
input.op_escrow_create().destination(),
input.op_escrow_create().destination_tag(),
input.op_escrow_create().cancel_after(),
input.op_escrow_create().finish_after(),
input.op_escrow_create().condition());
break;

case Proto::SigningInput::kOpEscrowCancel:
transaction.createEscrowCancel(
input.op_escrow_cancel().owner(),
input.op_escrow_cancel().offer_sequence());
break;

case Proto::SigningInput::kOpEscrowFinish:
transaction.createEscrowFinish(
input.op_escrow_finish().owner(),
input.op_escrow_finish().offer_sequence(),
input.op_escrow_finish().condition(),
input.op_escrow_finish().fulfillment());
break;

case Proto::SigningInput::kOpNftokenBurn:
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
break;
Expand Down
53 changes: 50 additions & 3 deletions src/XRP/Transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Data Transaction::serialize() const {

auto data = Data();

/// field must be sorted by field type then by field name
/// fields must be sorted by field type code then by field code (key)
// https://xrpl.org/serialization.html#canonical-field-order

/// "type"
encodeType(FieldType::int16, 2, data);
encode16BE(uint16_t(transaction_type), data);
Expand All @@ -36,17 +38,37 @@ Data Transaction::serialize() const {
encode32BE(sequence, data);

/// "destinationTag"
if ((transaction_type == TransactionType::payment) && encode_tag) {
if (((transaction_type == TransactionType::payment) ||
(transaction_type == TransactionType::EscrowCreate)) && encode_tag) {
encodeType(FieldType::int32, 14, data);
encode32BE(static_cast<uint32_t>(destination_tag), data);
}

/// "OfferSequence"
if ((transaction_type == TransactionType::EscrowCancel) ||
(transaction_type == TransactionType::EscrowFinish)) {
encodeType(FieldType::int32, 25, data);
encode32BE(offer_sequence, data);
}

/// "lastLedgerSequence"
if (last_ledger_sequence > 0) {
encodeType(FieldType::int32, 27, data);
encode32BE(last_ledger_sequence, data);
}

/// "CancelAfter"
if ((transaction_type == TransactionType::EscrowCreate) && cancel_after > 0) {
encodeType(FieldType::int32, 36, data);
encode32BE(static_cast<uint32_t>(cancel_after), data);
}

/// "FinishAfter"
if ((transaction_type == TransactionType::EscrowCreate) && finish_after > 0) {
encodeType(FieldType::int32, 37, data);
encode32BE(static_cast<uint32_t>(finish_after), data);
}

/// "NFTokenId"
if ((transaction_type == TransactionType::NFTokenCreateOffer) ||
(transaction_type == TransactionType::NFTokenBurn)) {
Expand All @@ -71,6 +93,9 @@ Data Transaction::serialize() const {
} else if (transaction_type == TransactionType::TrustSet) {
encodeType(FieldType::amount, 3, data);
append(data, serializeCurrencyAmount(limit_amount));
} else if (transaction_type == TransactionType::EscrowCreate) {
encodeType(FieldType::amount, 1, data);
append(data, serializeAmount(amount));
}

/// "fee"
Expand All @@ -82,23 +107,45 @@ Data Transaction::serialize() const {
encodeType(FieldType::vl, 3, data);
encodeBytes(pub_key, data);
}

/// "txnSignature"
if (!signature.empty()) {
encodeType(FieldType::vl, 4, data);
encodeBytes(signature, data);
}

/// "Fulfillment"
if ((transaction_type == TransactionType::EscrowFinish) && !fulfillment.empty()) {
encodeType(FieldType::vl, 16, data);
encodeBytes(fulfillment, data);
}

/// "Condition"
if (((transaction_type == TransactionType::EscrowCreate) ||
(transaction_type == TransactionType::EscrowFinish)) && !condition.empty()) {
encodeType(FieldType::vl, 17, data);
encodeBytes(condition, data);
}

/// "account"
encodeType(FieldType::account, 1, data);
encodeBytes(serializeAddress(account), data);

/// "destination"
if ((transaction_type == TransactionType::payment) ||
(transaction_type == TransactionType::NFTokenCreateOffer)) {
(transaction_type == TransactionType::NFTokenCreateOffer) ||
(transaction_type == TransactionType::EscrowCreate)) {
encodeType(FieldType::account, 3, data);
encodeBytes(destination, data);
}

/// "Owner"
if ((transaction_type == TransactionType::EscrowCancel) ||
(transaction_type == TransactionType::EscrowFinish)) {
encodeType(FieldType::account, 2, data);
encodeBytes(owner, data);
}

/// "NFTokenOffers"
if (transaction_type == TransactionType::NFTokenCancelOffer) {
// only support one offer
Expand Down
42 changes: 42 additions & 0 deletions src/XRP/Transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ enum class FieldType: int {
enum class TransactionType {
no_type = -1,
payment = 0,
EscrowCreate = 1,
EscrowFinish = 2,
EscrowCancel = 4,
TrustSet = 20,
NFTokenBurn = 26,
NFTokenCreateOffer = 27,
Expand Down Expand Up @@ -64,6 +67,12 @@ class Transaction {
int64_t destination_tag;
Data pub_key;
Data signature;
int64_t cancel_after;
int64_t finish_after;
Data owner;
int32_t offer_sequence;
Data condition;
Data fulfillment;
Data nftoken_id;
Data sell_offer;
Data token_offers;
Expand All @@ -78,6 +87,9 @@ class Transaction {
, account(p_account)
, encode_tag(false)
, destination_tag(0)
, cancel_after(0)
, finish_after(0)
, offer_sequence(0)
, nftoken_id(0)
, sell_offer(0)
, token_offers(0)
Expand Down Expand Up @@ -108,6 +120,36 @@ class Transaction {
setCurrencyAmount(currency_amount, currency, value, issuer);
}

void createEscrowCreate(int64_t amount, const std::string& destination, int64_t destination_tag,
int64_t cancel_after, int64_t finish_after, const std::string& condition) {
transaction_type = TransactionType::EscrowCreate;
if (cancel_after == 0 && finish_after == 0) {
throw std::invalid_argument("Either CancelAfter or FinishAfter must be specified");
} else if (finish_after == 0 && condition.length() == 0) {
throw std::invalid_argument("Either Condition or FinishAfter must be specified");
}
this->amount = amount;
setDestination(destination, destination_tag);
this->cancel_after = cancel_after;
this->finish_after = finish_after;
this->condition = parse_hex(condition);
}

void createEscrowCancel(const std::string& owner, int32_t offer_sequence) {
transaction_type = TransactionType::EscrowCancel;
setAccount(owner, this->owner);
this->offer_sequence = offer_sequence;
}

void createEscrowFinish(const std::string& owner, int32_t offer_sequence,
const std::string& condition, const std::string& fulfillment) {
transaction_type = TransactionType::EscrowFinish;
setAccount(owner, this->owner);
this->offer_sequence = offer_sequence;
this->condition = parse_hex(condition);
this->fulfillment = parse_hex(fulfillment);
}

void createNFTokenBurn(const std::string& p_nftoken_id) {
transaction_type = TransactionType::NFTokenBurn;
nftoken_id = parse_hex(p_nftoken_id);
Expand Down
52 changes: 52 additions & 0 deletions src/proto/Ripple.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,52 @@ message OperationPayment {
int64 destination_tag = 4;
}

// https://xrpl.org/escrowcreate.html
message OperationEscrowCreate {
// Escrow amount
int64 amount = 1;

// Beneficiary account
string destination = 2;

// Destination Tag
int64 destination_tag = 3;

// Escrow expire time
int64 cancel_after = 4;

// Escrow release time
int64 finish_after = 5;

// Crypto condition
// https://datatracker.ietf.org/doc/html/draft-thomas-crypto-conditions-02#section-8.1
string condition = 6;
}

// https://xrpl.org/escrowcancel.html
message OperationEscrowCancel {
// Funding account
string owner = 1;

// Escrow transaction sequence
int32 offer_sequence = 2;
}

// https://xrpl.org/escrowfinish.html
message OperationEscrowFinish {
// Funding account
string owner = 1;

// Escrow transaction sequence
int32 offer_sequence = 2;

// Crypto condition
string condition = 3;

// Fulfillment matching condition
string fulfillment = 4;
}

// https://xrpl.org/nftokenburn.html
message OperationNFTokenBurn {
// Hash256 NFTokenId
Expand Down Expand Up @@ -99,6 +145,12 @@ message SigningInput {
OperationNFTokenAcceptOffer op_nftoken_accept_offer = 11;

OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12;

OperationEscrowCreate op_escrow_create = 16;

OperationEscrowCancel op_escrow_cancel = 17;

OperationEscrowFinish op_escrow_finish = 18;
}

// Only used by tss chain-integration.
Expand Down
Loading

0 comments on commit 230e7ab

Please sign in to comment.