Skip to content

Commit 2c85819

Browse files
authored
Generalise Escrow so it can be used by 3rd parties (#8356)
1 parent 8b340c0 commit 2c85819

35 files changed

+483
-81
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/canisters/community/impl/src/timer_job_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ impl Job for NotifyEscrowCanisterOfDepositJob {
365365
escrow_canister_id,
366366
&escrow_canister::notify_deposit::Args {
367367
swap_id: self.swap_id,
368-
user_id: Some(self.user_id),
368+
deposited_by: Some(self.user_id.into()),
369369
},
370370
)
371371
.await

backend/canisters/community/impl/src/updates/c2c_notify_p2p_swap_status_change.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ fn c2c_notify_p2p_swap_status_change(args: Args) {
1313
}
1414

1515
fn c2c_notify_p2p_swap_status_change_impl(args: Args, state: &mut RuntimeState) {
16-
let P2PSwapLocation::Message(m) = args.location;
16+
let P2PSwapLocation::Message(m) = args.location else {
17+
return;
18+
};
19+
1720
let mut result = None;
1821

1922
if let Chat::Channel(_, channel_id) = m.chat {
@@ -75,7 +78,7 @@ fn c2c_notify_p2p_swap_status_change_impl(args: Args, state: &mut RuntimeState)
7578
.chat
7679
.events
7780
.complete_p2p_swap(
78-
c.accepted_by,
81+
c.accepted_by.into(),
7982
m.thread_root_message_index,
8083
m.message_id,
8184
c.token0_transfer_out.block_index,

backend/canisters/escrow/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
- Expose `liquid_cycles_balance` in metrics ([#8350](https://github.com/open-chat-labs/open-chat/pull/8350))
1212
- Add delay before retrying c2c call under certain error conditions ([#8355](https://github.com/open-chat-labs/open-chat/pull/8355))
13+
- Generalise Escrow so it can be used by 3rd parties ([#8356](https://github.com/open-chat-labs/open-chat/pull/8356))
1314

1415
## [[2.0.1694](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1694-escrow)] - 2025-04-09
1516

backend/canisters/escrow/api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition.workspace = true
77

88
[dependencies]
99
candid = { workspace = true }
10+
candid_gen = { path = "../../../libraries/candid_gen" }
1011
icrc-ledger-types = { workspace = true }
1112
oc_error_codes = { path = "../../../libraries/error_codes" }
1213
serde = { workspace = true }

backend/canisters/escrow/api/can.did

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
type CanisterId = principal;
2+
type ChannelId = nat32;
3+
type ChatId = CanisterId;
4+
type CommunityId = CanisterId;
5+
type MessageId = nat64;
6+
type MessageIndex = nat32;
7+
type TimestampMillis = nat64;
8+
9+
type OCError = record { nat16; opt text; };
10+
11+
type Chat = variant {
12+
Direct : ChatId;
13+
Group : ChatId;
14+
Channel : record { CommunityId; ChannelId };
15+
};
16+
17+
type TokenInfo = record {
18+
symbol : text;
19+
ledger : CanisterId;
20+
decimals : nat8;
21+
fee : nat;
22+
};
23+
24+
type P2PSwapLocation = variant {
25+
Message : Message;
26+
External;
27+
};
28+
29+
type Message = record {
30+
chat : Chat;
31+
thread_root_message_index : opt MessageIndex;
32+
message_id : MessageId;
33+
};
34+
35+
type CreateSwapArgs = record {
36+
// Specifies whether the swap is associated with an OpenChat message or is External.
37+
location : P2PSwapLocation;
38+
39+
// The token to be deposited by the offerer of the swap.
40+
token0 : TokenInfo;
41+
42+
// The amount of token0 to be deposited. When making the deposit it is necessary to add the fee to this amount to cover the transaction fee for the swap or refund.
43+
token0_amount : nat;
44+
45+
// The principal of the party offering the swap. If this is not specified then the caller's principal is used.
46+
token0_principal : opt principal;
47+
48+
// The token to be deposited by the accepter of the swap.
49+
token1 : TokenInfo;
50+
51+
// The amount of token1 to be deposited. When making the deposit it is necessary to add the fee to this amount to cover the transaction fee for the swap or refund.
52+
token1_amount : nat;
53+
54+
// The principal of the party who can accept the swap. If this is not specified then anyone can accept the swap.
55+
token1_principal : opt principal;
56+
57+
// The swap will expire after this timestamp.
58+
expires_at : TimestampMillis;
59+
60+
// The principals of parties other than the offerer who can cancel the swap.
61+
additional_admins : vec principal;
62+
63+
// If specified, this canister will be notified when the status of the swap has changed.
64+
canister_to_notify : opt CanisterId;
65+
};
66+
67+
type CreateSwapResponse = variant {
68+
Success : record {
69+
id : nat32;
70+
// These addresses use the standard text encoding for ICRC-1 accounts:
71+
// https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/TextualEncoding.md#textual-encoding-of-icrc-1-accounts
72+
token0_deposit_address : text;
73+
token1_deposit_address : opt text;
74+
};
75+
InvalidSwap : text;
76+
Error : OCError;
77+
};
78+
79+
type CancelSwapArgs = record {
80+
swap_id : nat32;
81+
};
82+
83+
type CancelSwapResponse = variant {
84+
Success;
85+
SwapAlreadyAccepted;
86+
SwapExpired;
87+
SwapNotFound;
88+
NotAuthorized;
89+
Error : OCError;
90+
};
91+
92+
type NotifyDepositArgs = record {
93+
swap_id : nat32;
94+
95+
// The principal of the party whose tokens have been deposited
96+
deposited_by : opt principal;
97+
};
98+
99+
type NotifyDepositResponse = variant {
100+
Success : record {
101+
complete : bool;
102+
};
103+
BalanceTooLow : record {
104+
balance : nat;
105+
balance_required : nat;
106+
};
107+
SwapAlreadyAccepted;
108+
SwapCancelled;
109+
SwapExpired;
110+
SwapNotFound;
111+
NotAuthorized;
112+
InternalError : text;
113+
Error : OCError;
114+
};
115+
116+
type LookupSwapArgs = record {
117+
swap_id : nat32;
118+
119+
// The principal of the accepting party or the caller if not specified
120+
accepting_principal : opt principal;
121+
};
122+
123+
type LookupSwapResponse = variant {
124+
Success: record {
125+
id : nat32;
126+
location : P2PSwapLocation;
127+
created_at : TimestampMillis;
128+
offered_by : principal;
129+
restricted_to : opt principal;
130+
token0 : TokenInfo;
131+
amount0 : nat;
132+
// This address uses the standard text encoding for ICRC-1 accounts:
133+
// https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/TextualEncoding.md#textual-encoding-of-icrc-1-accounts
134+
token0_deposit_address : text;
135+
token1 : TokenInfo;
136+
amount1 : nat;
137+
// This address uses the standard text encoding for ICRC-1 accounts:
138+
// https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/TextualEncoding.md#textual-encoding-of-icrc-1-accounts
139+
token1_deposit_address : text;
140+
expires_at : TimestampMillis;
141+
additional_admins : vec principal;
142+
canister_to_notify : opt CanisterId;
143+
};
144+
SwapNotFound;
145+
PrincipalNotFound;
146+
Error : OCError;
147+
};
148+
149+
service : {
150+
lookup_swap : (LookupSwapArgs) -> (LookupSwapResponse) query;
151+
create_swap : (CreateSwapArgs) -> (CreateSwapResponse);
152+
cancel_swap : (CancelSwapArgs) -> (CancelSwapResponse);
153+
notify_deposit : (NotifyDepositArgs) -> (NotifyDepositResponse);
154+
};

backend/canisters/escrow/api/src/lib.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ use icrc_ledger_types::icrc1::account::Subaccount;
33
use serde::Serialize;
44
use sha256::sha256;
55
use types::icrc1::CompletedCryptoTransaction;
6-
use types::{P2PSwapLocation, TimestampMillis, UserId};
6+
use types::{P2PSwapLocation, TimestampMillis};
77

88
mod lifecycle;
9+
mod queries;
910
mod updates;
1011

1112
pub use lifecycle::*;
13+
pub use queries::*;
1214
pub use updates::*;
1315

1416
#[derive(Serialize, Deserialize, Debug)]
@@ -33,13 +35,13 @@ pub struct SwapStatusExpired {
3335

3436
#[derive(Serialize, Deserialize, Debug)]
3537
pub struct SwapStatusAccepted {
36-
pub accepted_by: UserId,
38+
pub accepted_by: Principal,
3739
pub accepted_at: TimestampMillis,
3840
}
3941

4042
#[derive(Serialize, Deserialize, Debug)]
4143
pub struct SwapStatusCompleted {
42-
pub accepted_by: UserId,
44+
pub accepted_by: Principal,
4345
pub accepted_at: TimestampMillis,
4446
pub token0_transfer_out: CompletedCryptoTransaction,
4547
pub token1_transfer_out: CompletedCryptoTransaction,
@@ -49,14 +51,15 @@ pub struct SwapStatusCompleted {
4951
#[derive(Serialize, Deserialize, Debug)]
5052
pub struct SwapStatusChange {
5153
pub swap_id: u32,
52-
pub created_by: UserId,
54+
#[serde(alias = "created_by")]
55+
pub offered_by: Principal,
5356
pub location: P2PSwapLocation,
5457
pub status: SwapStatus,
5558
}
5659

57-
pub fn deposit_subaccount(user_id: UserId, swap_id: u32) -> Subaccount {
60+
pub fn deposit_subaccount(principal: Principal, swap_id: u32) -> Subaccount {
5861
let mut bytes = Vec::new();
59-
bytes.extend_from_slice(Principal::from(user_id).as_slice());
62+
bytes.extend_from_slice(principal.as_slice());
6063
bytes.extend_from_slice(&swap_id.to_be_bytes());
6164
sha256(&bytes)
6265
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
use candid_gen::generate_candid_method;
2+
13
fn main() {
4+
generate_candid_method!(escrow, lookup_swap, query);
5+
6+
generate_candid_method!(escrow, create_swap, update);
7+
generate_candid_method!(escrow, cancel_swap, update);
8+
generate_candid_method!(escrow, notify_deposit, update);
9+
210
candid::export_service!();
311
std::print!("{}", __export_service());
412
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use candid::{CandidType, Principal};
2+
use oc_error_codes::OCError;
3+
use serde::{Deserialize, Serialize};
4+
use types::{CanisterId, P2PSwapLocation, TimestampMillis, TokenInfo};
5+
6+
#[derive(CandidType, Serialize, Deserialize, Debug)]
7+
pub struct Args {
8+
pub swap_id: u32,
9+
pub accepting_principal: Option<Principal>,
10+
}
11+
12+
#[expect(clippy::large_enum_variant)]
13+
#[derive(CandidType, Serialize, Deserialize, Debug)]
14+
pub enum Response {
15+
Success(Swap),
16+
SwapNotFound,
17+
PrincipalNotFound,
18+
Error(OCError),
19+
}
20+
21+
#[derive(CandidType, Serialize, Deserialize, Debug)]
22+
pub struct Swap {
23+
pub id: u32,
24+
pub location: P2PSwapLocation,
25+
pub created_at: TimestampMillis,
26+
pub offered_by: Principal,
27+
pub restricted_to: Option<Principal>,
28+
pub token0: TokenInfo,
29+
pub amount0: u128,
30+
pub token0_deposit_address: String,
31+
pub token1: TokenInfo,
32+
pub amount1: u128,
33+
pub token1_deposit_address: String,
34+
pub expires_at: TimestampMillis,
35+
pub additional_admins: Vec<Principal>,
36+
pub canister_to_notify: Option<CanisterId>,
37+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod lookup_swap;

0 commit comments

Comments
 (0)