Skip to content

Commit 230e7ab

Browse files
[XRP] Add support for Escrow transactions (#3572)
* 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>
1 parent 296f9f3 commit 230e7ab

File tree

6 files changed

+491
-4
lines changed

6 files changed

+491
-4
lines changed

samples/kmp/shared/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ kotlin {
3535
sourceSets {
3636
val commonMain by getting {
3737
dependencies {
38-
implementation("com.trustwallet:wallet-core-kotlin:4.0.9")
38+
implementation("com.trustwallet:wallet-core-kotlin:4.0.10")
3939
}
4040
}
4141
val commonTest by getting {

src/XRP/Signer.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
2626
signPayment(input, output, transaction);
2727
break;
2828

29+
case Proto::SigningInput::kOpEscrowCreate:
30+
transaction.createEscrowCreate(
31+
input.op_escrow_create().amount(),
32+
input.op_escrow_create().destination(),
33+
input.op_escrow_create().destination_tag(),
34+
input.op_escrow_create().cancel_after(),
35+
input.op_escrow_create().finish_after(),
36+
input.op_escrow_create().condition());
37+
break;
38+
39+
case Proto::SigningInput::kOpEscrowCancel:
40+
transaction.createEscrowCancel(
41+
input.op_escrow_cancel().owner(),
42+
input.op_escrow_cancel().offer_sequence());
43+
break;
44+
45+
case Proto::SigningInput::kOpEscrowFinish:
46+
transaction.createEscrowFinish(
47+
input.op_escrow_finish().owner(),
48+
input.op_escrow_finish().offer_sequence(),
49+
input.op_escrow_finish().condition(),
50+
input.op_escrow_finish().fulfillment());
51+
break;
52+
2953
case Proto::SigningInput::kOpNftokenBurn:
3054
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
3155
break;
@@ -94,6 +118,30 @@ TW::Data Signer::preImage() const {
94118
signPayment(input, output, transaction);
95119
break;
96120

121+
case Proto::SigningInput::kOpEscrowCreate:
122+
transaction.createEscrowCreate(
123+
input.op_escrow_create().amount(),
124+
input.op_escrow_create().destination(),
125+
input.op_escrow_create().destination_tag(),
126+
input.op_escrow_create().cancel_after(),
127+
input.op_escrow_create().finish_after(),
128+
input.op_escrow_create().condition());
129+
break;
130+
131+
case Proto::SigningInput::kOpEscrowCancel:
132+
transaction.createEscrowCancel(
133+
input.op_escrow_cancel().owner(),
134+
input.op_escrow_cancel().offer_sequence());
135+
break;
136+
137+
case Proto::SigningInput::kOpEscrowFinish:
138+
transaction.createEscrowFinish(
139+
input.op_escrow_finish().owner(),
140+
input.op_escrow_finish().offer_sequence(),
141+
input.op_escrow_finish().condition(),
142+
input.op_escrow_finish().fulfillment());
143+
break;
144+
97145
case Proto::SigningInput::kOpNftokenBurn:
98146
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
99147
break;
@@ -149,6 +197,30 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub
149197
signPayment(input, output, transaction);
150198
break;
151199

200+
case Proto::SigningInput::kOpEscrowCreate:
201+
transaction.createEscrowCreate(
202+
input.op_escrow_create().amount(),
203+
input.op_escrow_create().destination(),
204+
input.op_escrow_create().destination_tag(),
205+
input.op_escrow_create().cancel_after(),
206+
input.op_escrow_create().finish_after(),
207+
input.op_escrow_create().condition());
208+
break;
209+
210+
case Proto::SigningInput::kOpEscrowCancel:
211+
transaction.createEscrowCancel(
212+
input.op_escrow_cancel().owner(),
213+
input.op_escrow_cancel().offer_sequence());
214+
break;
215+
216+
case Proto::SigningInput::kOpEscrowFinish:
217+
transaction.createEscrowFinish(
218+
input.op_escrow_finish().owner(),
219+
input.op_escrow_finish().offer_sequence(),
220+
input.op_escrow_finish().condition(),
221+
input.op_escrow_finish().fulfillment());
222+
break;
223+
152224
case Proto::SigningInput::kOpNftokenBurn:
153225
transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id());
154226
break;

src/XRP/Transaction.cpp

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Data Transaction::serialize() const {
2222

2323
auto data = Data();
2424

25-
/// field must be sorted by field type then by field name
25+
/// fields must be sorted by field type code then by field code (key)
26+
// https://xrpl.org/serialization.html#canonical-field-order
27+
2628
/// "type"
2729
encodeType(FieldType::int16, 2, data);
2830
encode16BE(uint16_t(transaction_type), data);
@@ -36,17 +38,37 @@ Data Transaction::serialize() const {
3638
encode32BE(sequence, data);
3739

3840
/// "destinationTag"
39-
if ((transaction_type == TransactionType::payment) && encode_tag) {
41+
if (((transaction_type == TransactionType::payment) ||
42+
(transaction_type == TransactionType::EscrowCreate)) && encode_tag) {
4043
encodeType(FieldType::int32, 14, data);
4144
encode32BE(static_cast<uint32_t>(destination_tag), data);
4245
}
4346

47+
/// "OfferSequence"
48+
if ((transaction_type == TransactionType::EscrowCancel) ||
49+
(transaction_type == TransactionType::EscrowFinish)) {
50+
encodeType(FieldType::int32, 25, data);
51+
encode32BE(offer_sequence, data);
52+
}
53+
4454
/// "lastLedgerSequence"
4555
if (last_ledger_sequence > 0) {
4656
encodeType(FieldType::int32, 27, data);
4757
encode32BE(last_ledger_sequence, data);
4858
}
4959

60+
/// "CancelAfter"
61+
if ((transaction_type == TransactionType::EscrowCreate) && cancel_after > 0) {
62+
encodeType(FieldType::int32, 36, data);
63+
encode32BE(static_cast<uint32_t>(cancel_after), data);
64+
}
65+
66+
/// "FinishAfter"
67+
if ((transaction_type == TransactionType::EscrowCreate) && finish_after > 0) {
68+
encodeType(FieldType::int32, 37, data);
69+
encode32BE(static_cast<uint32_t>(finish_after), data);
70+
}
71+
5072
/// "NFTokenId"
5173
if ((transaction_type == TransactionType::NFTokenCreateOffer) ||
5274
(transaction_type == TransactionType::NFTokenBurn)) {
@@ -71,6 +93,9 @@ Data Transaction::serialize() const {
7193
} else if (transaction_type == TransactionType::TrustSet) {
7294
encodeType(FieldType::amount, 3, data);
7395
append(data, serializeCurrencyAmount(limit_amount));
96+
} else if (transaction_type == TransactionType::EscrowCreate) {
97+
encodeType(FieldType::amount, 1, data);
98+
append(data, serializeAmount(amount));
7499
}
75100

76101
/// "fee"
@@ -82,23 +107,45 @@ Data Transaction::serialize() const {
82107
encodeType(FieldType::vl, 3, data);
83108
encodeBytes(pub_key, data);
84109
}
110+
85111
/// "txnSignature"
86112
if (!signature.empty()) {
87113
encodeType(FieldType::vl, 4, data);
88114
encodeBytes(signature, data);
89115
}
90116

117+
/// "Fulfillment"
118+
if ((transaction_type == TransactionType::EscrowFinish) && !fulfillment.empty()) {
119+
encodeType(FieldType::vl, 16, data);
120+
encodeBytes(fulfillment, data);
121+
}
122+
123+
/// "Condition"
124+
if (((transaction_type == TransactionType::EscrowCreate) ||
125+
(transaction_type == TransactionType::EscrowFinish)) && !condition.empty()) {
126+
encodeType(FieldType::vl, 17, data);
127+
encodeBytes(condition, data);
128+
}
129+
91130
/// "account"
92131
encodeType(FieldType::account, 1, data);
93132
encodeBytes(serializeAddress(account), data);
94133

95134
/// "destination"
96135
if ((transaction_type == TransactionType::payment) ||
97-
(transaction_type == TransactionType::NFTokenCreateOffer)) {
136+
(transaction_type == TransactionType::NFTokenCreateOffer) ||
137+
(transaction_type == TransactionType::EscrowCreate)) {
98138
encodeType(FieldType::account, 3, data);
99139
encodeBytes(destination, data);
100140
}
101141

142+
/// "Owner"
143+
if ((transaction_type == TransactionType::EscrowCancel) ||
144+
(transaction_type == TransactionType::EscrowFinish)) {
145+
encodeType(FieldType::account, 2, data);
146+
encodeBytes(owner, data);
147+
}
148+
102149
/// "NFTokenOffers"
103150
if (transaction_type == TransactionType::NFTokenCancelOffer) {
104151
// only support one offer

src/XRP/Transaction.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ enum class FieldType: int {
2929
enum class TransactionType {
3030
no_type = -1,
3131
payment = 0,
32+
EscrowCreate = 1,
33+
EscrowFinish = 2,
34+
EscrowCancel = 4,
3235
TrustSet = 20,
3336
NFTokenBurn = 26,
3437
NFTokenCreateOffer = 27,
@@ -64,6 +67,12 @@ class Transaction {
6467
int64_t destination_tag;
6568
Data pub_key;
6669
Data signature;
70+
int64_t cancel_after;
71+
int64_t finish_after;
72+
Data owner;
73+
int32_t offer_sequence;
74+
Data condition;
75+
Data fulfillment;
6776
Data nftoken_id;
6877
Data sell_offer;
6978
Data token_offers;
@@ -78,6 +87,9 @@ class Transaction {
7887
, account(p_account)
7988
, encode_tag(false)
8089
, destination_tag(0)
90+
, cancel_after(0)
91+
, finish_after(0)
92+
, offer_sequence(0)
8193
, nftoken_id(0)
8294
, sell_offer(0)
8395
, token_offers(0)
@@ -108,6 +120,36 @@ class Transaction {
108120
setCurrencyAmount(currency_amount, currency, value, issuer);
109121
}
110122

123+
void createEscrowCreate(int64_t amount, const std::string& destination, int64_t destination_tag,
124+
int64_t cancel_after, int64_t finish_after, const std::string& condition) {
125+
transaction_type = TransactionType::EscrowCreate;
126+
if (cancel_after == 0 && finish_after == 0) {
127+
throw std::invalid_argument("Either CancelAfter or FinishAfter must be specified");
128+
} else if (finish_after == 0 && condition.length() == 0) {
129+
throw std::invalid_argument("Either Condition or FinishAfter must be specified");
130+
}
131+
this->amount = amount;
132+
setDestination(destination, destination_tag);
133+
this->cancel_after = cancel_after;
134+
this->finish_after = finish_after;
135+
this->condition = parse_hex(condition);
136+
}
137+
138+
void createEscrowCancel(const std::string& owner, int32_t offer_sequence) {
139+
transaction_type = TransactionType::EscrowCancel;
140+
setAccount(owner, this->owner);
141+
this->offer_sequence = offer_sequence;
142+
}
143+
144+
void createEscrowFinish(const std::string& owner, int32_t offer_sequence,
145+
const std::string& condition, const std::string& fulfillment) {
146+
transaction_type = TransactionType::EscrowFinish;
147+
setAccount(owner, this->owner);
148+
this->offer_sequence = offer_sequence;
149+
this->condition = parse_hex(condition);
150+
this->fulfillment = parse_hex(fulfillment);
151+
}
152+
111153
void createNFTokenBurn(const std::string& p_nftoken_id) {
112154
transaction_type = TransactionType::NFTokenBurn;
113155
nftoken_id = parse_hex(p_nftoken_id);

src/proto/Ripple.proto

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,52 @@ message OperationPayment {
4040
int64 destination_tag = 4;
4141
}
4242

43+
// https://xrpl.org/escrowcreate.html
44+
message OperationEscrowCreate {
45+
// Escrow amount
46+
int64 amount = 1;
47+
48+
// Beneficiary account
49+
string destination = 2;
50+
51+
// Destination Tag
52+
int64 destination_tag = 3;
53+
54+
// Escrow expire time
55+
int64 cancel_after = 4;
56+
57+
// Escrow release time
58+
int64 finish_after = 5;
59+
60+
// Crypto condition
61+
// https://datatracker.ietf.org/doc/html/draft-thomas-crypto-conditions-02#section-8.1
62+
string condition = 6;
63+
}
64+
65+
// https://xrpl.org/escrowcancel.html
66+
message OperationEscrowCancel {
67+
// Funding account
68+
string owner = 1;
69+
70+
// Escrow transaction sequence
71+
int32 offer_sequence = 2;
72+
}
73+
74+
// https://xrpl.org/escrowfinish.html
75+
message OperationEscrowFinish {
76+
// Funding account
77+
string owner = 1;
78+
79+
// Escrow transaction sequence
80+
int32 offer_sequence = 2;
81+
82+
// Crypto condition
83+
string condition = 3;
84+
85+
// Fulfillment matching condition
86+
string fulfillment = 4;
87+
}
88+
4389
// https://xrpl.org/nftokenburn.html
4490
message OperationNFTokenBurn {
4591
// Hash256 NFTokenId
@@ -99,6 +145,12 @@ message SigningInput {
99145
OperationNFTokenAcceptOffer op_nftoken_accept_offer = 11;
100146

101147
OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12;
148+
149+
OperationEscrowCreate op_escrow_create = 16;
150+
151+
OperationEscrowCancel op_escrow_cancel = 17;
152+
153+
OperationEscrowFinish op_escrow_finish = 18;
102154
}
103155

104156
// Only used by tss chain-integration.

0 commit comments

Comments
 (0)