(creatingP2PSwapMessage = false)} />
+{/if}
+
@@ -380,6 +393,7 @@
on:makeMeme={makeMeme}
on:tokenTransfer={tokenTransfer}
on:createPrizeMessage={createPrizeMessage}
+ on:createP2PSwapMessage={createP2PSwapMessage}
on:searchChat={searchChat}
on:createPoll={createPoll} />
{/if}
diff --git a/frontend/app/src/components/home/DisappearingMessages.svelte b/frontend/app/src/components/home/DisappearingMessages.svelte
deleted file mode 100644
index 78a1769d39..0000000000
--- a/frontend/app/src/components/home/DisappearingMessages.svelte
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
diff --git a/frontend/app/src/components/home/DurationPicker.svelte b/frontend/app/src/components/home/DurationPicker.svelte
new file mode 100644
index 0000000000..21ca6063ca
--- /dev/null
+++ b/frontend/app/src/components/home/DurationPicker.svelte
@@ -0,0 +1,89 @@
+
+
+
+
+
diff --git a/frontend/app/src/components/home/Footer.svelte b/frontend/app/src/components/home/Footer.svelte
index 88a5d72104..a108a4d31f 100644
--- a/frontend/app/src/components/home/Footer.svelte
+++ b/frontend/app/src/components/home/Footer.svelte
@@ -123,7 +123,7 @@
{#if editingEvent === undefined && (replyingTo || attachment !== undefined)}
{#if replyingTo}
-
+
{/if}
{#if attachment !== undefined}
@@ -154,6 +154,7 @@
on:searchChat
on:tokenTransfer
on:createPrizeMessage
+ on:createP2PSwapMessage
on:attachGif
on:makeMeme
on:clearAttachment
diff --git a/frontend/app/src/components/home/GroupPermissionsViewer.svelte b/frontend/app/src/components/home/GroupPermissionsViewer.svelte
index 1466a407fb..8f7a77e05f 100644
--- a/frontend/app/src/components/home/GroupPermissionsViewer.svelte
+++ b/frontend/app/src/components/home/GroupPermissionsViewer.svelte
@@ -32,13 +32,10 @@
},
"",
);
- messagePartition = partitionMessagePermissions(
- permissions.messagePermissions,
- "messagePermissions.",
- );
+ messagePartition = partitionMessagePermissions(permissions.messagePermissions, false);
threadPartition = partitionMessagePermissions(
permissions.threadPermissions ?? permissions.messagePermissions,
- "threadPermissions.",
+ true,
);
}
@@ -46,22 +43,28 @@
function partitionMessagePermissions(
mps: MessagePermissions,
- translationExt: string,
+ thread: boolean,
): PermissionsByRole {
+ let permissions: Record = {
+ text: mps.text ?? mps.default,
+ image: mps.image ?? mps.default,
+ video: mps.video ?? mps.default,
+ audio: mps.audio ?? mps.default,
+ file: mps.file ?? mps.default,
+ poll: mps.poll ?? mps.default,
+ crypto: mps.crypto ?? mps.default,
+ giphy: mps.giphy ?? mps.default,
+ memeFighter: mps.memeFighter ?? mps.default,
+ p2pSwap: mps.p2pSwap ?? mps.default,
+ };
+
+ if (!thread) {
+ permissions = { ...permissions, prize: mps.prize ?? mps.default };
+ }
+
return partitionPermissions(
- {
- text: mps.text ?? mps.default,
- image: mps.image ?? mps.default,
- video: mps.video ?? mps.default,
- audio: mps.audio ?? mps.default,
- file: mps.file ?? mps.default,
- poll: mps.poll ?? mps.default,
- crypto: mps.crypto ?? mps.default,
- giphy: mps.giphy ?? mps.default,
- prize: mps.prize ?? mps.default,
- memeFighter: mps.memeFighter ?? mps.default,
- },
- translationExt,
+ permissions,
+ thread ? "threadPermissions." : "messagePermissions.",
);
}
diff --git a/frontend/app/src/components/home/MessageActions.svelte b/frontend/app/src/components/home/MessageActions.svelte
index 185d754542..43573fb497 100644
--- a/frontend/app/src/components/home/MessageActions.svelte
+++ b/frontend/app/src/components/home/MessageActions.svelte
@@ -5,6 +5,7 @@
import { _ } from "svelte-i18n";
import Smiley from "./Smiley.svelte";
import Gift from "svelte-material-icons/GiftOutline.svelte";
+ import SwapIcon from "svelte-material-icons/SwapHorizontal.svelte";
import Bitcoin from "../icons/Bitcoin.svelte";
import MemeFighter from "../icons/MemeFighter.svelte";
import StickerEmoji from "svelte-material-icons/StickerEmoji.svelte";
@@ -51,6 +52,11 @@
drawOpen = false;
}
+ function createP2PSwapMessage() {
+ dispatch("createP2PSwapMessage");
+ drawOpen = false;
+ }
+
function openEmojiPicker() {
messageAction = "emoji";
}
@@ -116,6 +122,9 @@
if (permissions.get("prize")) {
actions.set("prize", cssValues(++index));
}
+ if (permissions.get("p2pSwap")) {
+ actions.set("swap", cssValues(++index));
+ }
return actions;
}
@@ -236,6 +245,16 @@
{/if}
+ {#if supportedActions.has("swap")}
+
+
+
+
+
+ {/if}
{/if}
@@ -264,6 +283,7 @@
.meme,
.poll,
.prize,
+ .swap,
.send-icp {
flex: 0 0 15px;
}
@@ -290,6 +310,7 @@
.meme,
.send-icp,
.prize,
+ .swap,
.poll {
top: -18px;
left: toRem(-44);
@@ -308,6 +329,7 @@
.meme,
.send-icp,
.prize,
+ .swap,
.poll {
left: unset;
right: toRem(-44);
@@ -324,6 +346,7 @@
.gif,
.meme,
.poll,
+ .swap,
.prize {
top: var(--top);
transition-delay: var(--transition-delay);
diff --git a/frontend/app/src/components/home/PrizeContentInitial.svelte b/frontend/app/src/components/home/MessageContentInitial.svelte
similarity index 65%
rename from frontend/app/src/components/home/PrizeContentInitial.svelte
rename to frontend/app/src/components/home/MessageContentInitial.svelte
index defc98d24b..afbd730119 100644
--- a/frontend/app/src/components/home/PrizeContentInitial.svelte
+++ b/frontend/app/src/components/home/MessageContentInitial.svelte
@@ -1,19 +1,20 @@
-{#if me}
-
diff --git a/frontend/app/src/components/home/P2PSwapContentBuilder.svelte b/frontend/app/src/components/home/P2PSwapContentBuilder.svelte
new file mode 100644
index 0000000000..39d9a6edff
--- /dev/null
+++ b/frontend/app/src/components/home/P2PSwapContentBuilder.svelte
@@ -0,0 +1,270 @@
+
+
+{#if confirming}
+
+{/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/app/src/components/home/PrizeContentBuilder.svelte b/frontend/app/src/components/home/PrizeContentBuilder.svelte
index de9e68d91e..35f4b6cb8e 100644
--- a/frontend/app/src/components/home/PrizeContentBuilder.svelte
+++ b/frontend/app/src/components/home/PrizeContentBuilder.svelte
@@ -112,7 +112,6 @@
}
function send() {
- // const fees = BigInt(numberOfWinners) * tokenDetails.transferFee;
const prizes = generatePrizes();
const prizeFees = transferFees * BigInt(numberOfWinners ?? 0);
const content: PrizeContentInitial = {
diff --git a/frontend/app/src/components/home/RepliesTo.svelte b/frontend/app/src/components/home/RepliesTo.svelte
index 05a97011d7..edb2b761d3 100644
--- a/frontend/app/src/components/home/RepliesTo.svelte
+++ b/frontend/app/src/components/home/RepliesTo.svelte
@@ -4,9 +4,9 @@
import {
type RehydratedReplyContext,
OpenChat,
- type ChatIdentifier,
routeForChatIdentifier,
chatIdentifiersEqual,
+ type ChatIdentifier,
} from "openchat-client";
import { rtlStore } from "../../stores/rtl";
import Link from "../Link.svelte";
@@ -71,13 +71,14 @@
- import type {
- EnhancedReplyContext,
- CreatedUser,
- OpenChat,
- ChatIdentifier,
- } from "openchat-client";
+ import type { EnhancedReplyContext, CreatedUser, OpenChat } from "openchat-client";
import { _ } from "svelte-i18n";
import { rtlStore } from "../../stores/rtl";
import { createEventDispatcher, getContext } from "svelte";
@@ -19,7 +14,6 @@
export let replyingTo: EnhancedReplyContext;
export let user: CreatedUser;
export let readonly: boolean;
- export let chatId: ChatIdentifier;
$: me = replyingTo.sender?.userId === user?.userId;
@@ -48,8 +42,9 @@
maxAmount) {
+ } else if (maxAmount !== undefined && amount > maxAmount) {
state = "too_high";
} else {
state = "ok";
@@ -77,9 +79,11 @@
+{#if maxAmount !== undefined}
+{/if}
{#if transferFees !== undefined}
@@ -97,8 +101,8 @@
{#if disappearingMessages}
-
+
{/if}
diff --git a/frontend/app/src/components/home/pinned/PinnedMessage.svelte b/frontend/app/src/components/home/pinned/PinnedMessage.svelte
index 6956460b47..1e9e25b313 100644
--- a/frontend/app/src/components/home/pinned/PinnedMessage.svelte
+++ b/frontend/app/src/components/home/pinned/PinnedMessage.svelte
@@ -90,7 +90,8 @@
pinned
{senderId}
{fill}
- {chatId}
+ failed={false}
+ messageContext={{ chatId }}
edited={msg.edited}
messageIndex={msg.messageIndex}
messageId={msg.messageId}
diff --git a/frontend/app/src/components/home/thread/Thread.svelte b/frontend/app/src/components/home/thread/Thread.svelte
index ce1fd881a2..986bd4f1a9 100644
--- a/frontend/app/src/components/home/thread/Thread.svelte
+++ b/frontend/app/src/components/home/thread/Thread.svelte
@@ -27,6 +27,7 @@
import TimelineDate from "../TimelineDate.svelte";
import { reverseScroll } from "../../../stores/scrollPos";
import { i18nKey } from "../../../i18n/i18n";
+ import P2PSwapContentBuilder from "../P2PSwapContentBuilder.svelte";
const dispatch = createEventDispatcher();
const client = getContext
("client");
@@ -45,6 +46,7 @@
let initialised = false;
let messagesDiv: HTMLDivElement | undefined;
let messagesDivHeight: number;
+ let creatingP2PSwapMessage = false;
$: user = client.user;
$: focusMessageIndex = client.focusThreadMessageIndex;
@@ -209,10 +211,21 @@
) {
goToMessageIndex(ev.detail.index);
}
+
+ function createP2PSwapMessage() {
+ creatingP2PSwapMessage = true;
+ }
+{#if creatingP2PSwapMessage}
+ (creatingP2PSwapMessage = false)} />
+{/if}
+
@@ -340,5 +353,6 @@
on:makeMeme={makeMeme}
on:tokenTransfer={tokenTransfer}
on:createTestMessages={createTestMessages}
+ on:createP2PSwapMessage={createP2PSwapMessage}
on:createPoll={createPoll} />
{/if}
diff --git a/frontend/app/src/i18n/cn.json b/frontend/app/src/i18n/cn.json
index a39a4995c9..3c56d2db93 100644
--- a/frontend/app/src/i18n/cn.json
+++ b/frontend/app/src/i18n/cn.json
@@ -1155,9 +1155,6 @@
"sendTextDisabled": "短信已禁用",
"disappearingMessages": {
"label": "消失的消息",
- "hours": "小时)",
- "minutes": "分钟)",
- "days": "天)",
"summary": "消息将在 {duration} 之后消失",
"timeUpdatedBy": "{changedBy} 将消息消失时间设置为 {duration}",
"disabledBy": "{changedBy} 禁用消失消息",
@@ -1207,5 +1204,40 @@
"messageBlocked": "消息已隐藏",
"removePreviewQuestion": "删除预览?",
"showPreview": "显示预览",
- "excessiveLinksNote": "对于超过 5 个链接的邮件,链接预览会隐藏。"
+ "excessiveLinksNote": "对于超过 5 个链接的邮件,链接预览会隐藏。",
+ "hours": "小时",
+ "minutes": "分钟",
+ "days": "天",
+ "p2pSwap": {
+ "builderTitle": "提出交换报价",
+ "expiryTime": "到期时间",
+ "creatingYourMessage": "正在创建掉期报价,请稍候",
+ "failedToCreateMessage": "无法创建掉期报价",
+ "expired": "已到期",
+ "accepted": "公认",
+ "accept": "接受",
+ "cancel": "取消",
+ "cancelled": "取消",
+ "reserved": "预订的",
+ "summary": "这是将 {fromAmount} {fromToken} 交换为 {toAmount} {toToken} 的报价。",
+ "youReserved": "您已预订此优惠并等待完成。",
+ "youAccepted": "您已接受此要约,待完成。",
+ "reservedBy": "此优惠已由 {user} 预订并等待完成。",
+ "acceptedBy": "此要约已被 {user} 接受并等待完成。",
+ "youCompleted": "您已接受此优惠。",
+ "completed": "此报价已被 {user} 接受。",
+ "confirmSend": "当您创建此优惠时,{amount} {token}(包括 2 笔交易费用)将从您的钱包转移到 OpenChat 托管罐。它将一直保留在那里,直到要约过期、您取消要约或有人接受要约。你想继续吗?",
+ "confirmAccept": "当您接受此优惠时,{amount} {token}(包括 2 笔交易费用)将从您的钱包转移到 OpenChat 托管罐。作为交换,您将收到 {amountOther} {tokenOther}。你想继续吗?",
+ "confirmCancel": "当您取消此优惠时,{amount} {token} 将从 OpenChat 托管罐转回到您的钱包。你想继续吗?",
+ "insufficientBalanceMessage": "您至少需要{amount} {token}(包括2笔交易费用)才能接受此交换。",
+ "insufficientBalance": "余额不足",
+ "tokenBalance": "{token}余额",
+ "already_accepted": "此交换报价已被接受",
+ "swap_cancelled": "此交换优惠已取消",
+ "swap_expired": "此交换优惠已过期",
+ "swap_not_found": "找不到此交换报价",
+ "insufficient_funds": "您的剩余资金不足以接受此掉期优惠",
+ "unknown_accept_error": "接受交换报价时出错",
+ "unknown_cancel_error": "取消掉期优惠时出错"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/de.json b/frontend/app/src/i18n/de.json
index 0bf7d1b1ac..96daf32240 100644
--- a/frontend/app/src/i18n/de.json
+++ b/frontend/app/src/i18n/de.json
@@ -1156,9 +1156,6 @@
"sendTextDisabled": "Textnachrichten sind deaktiviert",
"disappearingMessages": {
"label": "Verschwindende Nachrichten",
- "hours": "Std)",
- "minutes": "Protokoll)",
- "days": "Tage)",
"summary": "Nachrichten verschwinden nach {duration}",
"timeUpdatedBy": "{changedBy} setzt die Zeit für das Verschwinden der Nachricht auf {duration}",
"disabledBy": "{changedBy} hat das Verschwinden von Nachrichten deaktiviert",
@@ -1208,5 +1205,40 @@
"messageBlocked": "Nachricht ausgeblendet",
"removePreviewQuestion": "Vorschau entfernen?",
"showPreview": "Vorschau zeigen",
- "excessiveLinksNote": "Linkvorschauen werden für Nachrichten mit mehr als 5 Links ausgeblendet."
-}
+ "excessiveLinksNote": "Linkvorschauen werden für Nachrichten mit mehr als 5 Links ausgeblendet.",
+ "hours": "Std",
+ "minutes": "Protokoll",
+ "days": "Tage",
+ "p2pSwap": {
+ "builderTitle": "Machen Sie ein Tauschangebot",
+ "expiryTime": "Ablaufdatum",
+ "creatingYourMessage": "Bitte warten Sie, während ein Tauschangebot erstellt wird",
+ "failedToCreateMessage": "Das Tauschangebot konnte nicht erstellt werden",
+ "expired": "Abgelaufen",
+ "accepted": "Akzeptiert",
+ "accept": "Akzeptieren",
+ "cancel": "Stornieren",
+ "cancelled": "Abgesagt",
+ "reserved": "Reserviert",
+ "summary": "Dies ist ein Angebot zum Tausch von {fromAmount} {fromToken} gegen {toAmount} {toToken}.",
+ "youReserved": "Sie haben dieses Angebot reserviert und es wartet auf die Fertigstellung.",
+ "youAccepted": "Sie haben dieses Angebot angenommen und die Fertigstellung steht noch aus.",
+ "reservedBy": "Dieses Angebot wurde von {user} reserviert und muss noch abgeschlossen werden.",
+ "acceptedBy": "Dieses Angebot wurde von {user} angenommen und wartet auf den Abschluss.",
+ "youCompleted": "Sie haben dieses Angebot angenommen.",
+ "completed": "Dieses Angebot wurde von {user} angenommen.",
+ "confirmSend": "Wenn Sie dieses Angebot erstellen, wird {amount} {token} (inklusive 2 Transaktionsgebühren) von Ihrem Wallet in den OpenChat-Escrow-Kanister übertragen. Es bleibt dort, bis entweder das Angebot abläuft, Sie das Angebot stornieren oder jemand das Angebot annimmt. Möchten Sie fortfahren?",
+ "confirmAccept": "Wenn Sie dieses Angebot annehmen, wird {amount} {token} (inklusive 2 Transaktionsgebühren) von Ihrem Wallet in den OpenChat-Treuhandkanister übertragen. Im Gegenzug erhalten Sie {amountOther} {tokenOther}. Möchten Sie fortfahren?",
+ "confirmCancel": "Wenn Sie dieses Angebot stornieren, wird {amount} {token} aus dem OpenChat-Escrow-Kanister zurück auf Ihr Wallet übertragen. Möchten Sie fortfahren?",
+ "insufficientBalanceMessage": "Sie benötigen mindestens {amount} {token} (inklusive 2 Transaktionsgebühren), um diesen Tausch zu akzeptieren.",
+ "insufficientBalance": "Mangelhaftes Gleichgewicht",
+ "tokenBalance": "{token} Guthaben",
+ "already_accepted": "Dieses Tauschangebot wurde bereits angenommen",
+ "swap_cancelled": "Dieses Tauschangebot wurde storniert",
+ "swap_expired": "Dieses Tauschangebot ist abgelaufen",
+ "swap_not_found": "Dieses Tauschangebot konnte nicht gefunden werden",
+ "insufficient_funds": "Ihr Guthaben reicht nicht aus, um dieses Tauschangebot anzunehmen",
+ "unknown_accept_error": "Fehler beim Annehmen des Tauschangebots",
+ "unknown_cancel_error": "Fehler beim Stornieren des Tauschangebots"
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/en.json b/frontend/app/src/i18n/en.json
index 77a19401a8..6719b755fc 100644
--- a/frontend/app/src/i18n/en.json
+++ b/frontend/app/src/i18n/en.json
@@ -1183,9 +1183,6 @@
"hideAuthProviders": "Hide options",
"disappearingMessages": {
"label": "Disappearing messages",
- "hours": "Hour(s)",
- "minutes": "Minute(s)",
- "days": "Day(s)",
"summary": "Messages will disappear after {duration}",
"timeUpdatedBy": "{changedBy} set the disappearing message time to {duration}",
"disabledBy": "{changedBy} disabled disappearing messages",
@@ -1198,5 +1195,40 @@
"messageBlocked": "Message hidden",
"removePreviewQuestion": "Remove preview?",
"showPreview": "Show preview",
- "excessiveLinksNote": "Link previews are hidden for messages with more than 5 links."
-}
+ "excessiveLinksNote": "Link previews are hidden for messages with more than 5 links.",
+ "hours": "Hours",
+ "minutes": "Minutes",
+ "days": "Days",
+ "p2pSwap": {
+ "builderTitle": "Make swap offer",
+ "expiryTime": "Expiry time",
+ "creatingYourMessage": "Please wait while a swap offer is created",
+ "failedToCreateMessage": "Failed to create the swap offer",
+ "expired": "Expired",
+ "accepted": "Accepted",
+ "accept": "Accept",
+ "cancel": "Cancel",
+ "cancelled": "Cancelled",
+ "reserved": "Reserved",
+ "summary": "This is an offer to swap {fromAmount} {fromToken} for {toAmount} {toToken}.",
+ "youReserved": "You have reserved this offer and it is pending completion.",
+ "youAccepted": "You have accepted this offer and it is pending completion.",
+ "reservedBy": "This offer has been reserved by {user} and is pending completion.",
+ "acceptedBy": "This offer has been accepted by {user} and is pending completion.",
+ "youCompleted": "You have accepted this offer.",
+ "completed": "This offer has been accepted by {user}.",
+ "confirmSend": "When you create this offer {amount} {token} (includes 2 transaction fees) will be transferred from your wallet to the OpenChat Escrow canister. It will stay there until either the offer expires, you cancel the offer, or someone accepts the offer. Do you wish to proceed?",
+ "confirmAccept": "When you accept this offer {amount} {token} (includes 2 transaction fees) will be transferred from your wallet to the OpenChat Escrow canister. You will receive {amountOther} {tokenOther} in exchange. Do you wish to proceed?",
+ "confirmCancel": "When you cancel this offer {amount} {token} will be transferred from the OpenChat Escrow canister back to your wallet. Do you wish to proceed?",
+ "insufficientBalanceMessage": "You need at least {amount} {token} (includes 2 transaction fees) to accept this swap.",
+ "insufficientBalance": "Insufficient balance",
+ "tokenBalance": "{token} balance",
+ "already_accepted": "This swap offer has already been accepted",
+ "swap_cancelled": "This swap offer has been cancelled",
+ "swap_expired": "This swap offer has expired",
+ "swap_not_found": "This swap offer could not be found",
+ "insufficient_funds": "You have insufficient funds remaining to accept this swap offer",
+ "unknown_accept_error": "Error accepting swap offer",
+ "unknown_cancel_error": "Error cancelling swap offer"
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/es.json b/frontend/app/src/i18n/es.json
index 25cf8b007e..a248eefa97 100644
--- a/frontend/app/src/i18n/es.json
+++ b/frontend/app/src/i18n/es.json
@@ -1156,9 +1156,6 @@
"sendTextDisabled": "Los mensajes de texto están deshabilitados",
"disappearingMessages": {
"label": "Mensajes que desaparecen",
- "hours": "Horas)",
- "minutes": "Minutos)",
- "days": "Días)",
"summary": "Los mensajes desaparecerán después de {duration}",
"timeUpdatedBy": "{changedBy} establece el tiempo de desaparición del mensaje en {duration}",
"disabledBy": "{changedBy} deshabilitó los mensajes que desaparecen",
@@ -1208,5 +1205,40 @@
"messageBlocked": "Mensaje oculto",
"removePreviewQuestion": "¿Eliminar vista previa?",
"showPreview": "Mostrar vista previa",
- "excessiveLinksNote": "Las vistas previas de enlaces están ocultas para mensajes con más de 5 enlaces."
+ "excessiveLinksNote": "Las vistas previas de enlaces están ocultas para mensajes con más de 5 enlaces.",
+ "hours": "Horas",
+ "minutes": "Minutos",
+ "days": "Días",
+ "p2pSwap": {
+ "builderTitle": "hacer oferta de permuta",
+ "expiryTime": "Tiempo de expiración",
+ "creatingYourMessage": "Espere mientras se crea una oferta de intercambio.",
+ "failedToCreateMessage": "No se pudo crear la oferta de intercambio",
+ "expired": "Venció",
+ "accepted": "Aceptado",
+ "accept": "Aceptar",
+ "cancel": "Cancelar",
+ "cancelled": "Cancelado",
+ "reserved": "Reservado",
+ "summary": "Esta es una oferta para intercambiar {fromAmount} {fromToken} por {toAmount} {toToken}.",
+ "youReserved": "Has reservado esta oferta y está pendiente de completarse.",
+ "youAccepted": "Has aceptado esta oferta y está pendiente de completarse.",
+ "reservedBy": "Esta oferta ha sido reservada por {user} y está pendiente de completarse.",
+ "acceptedBy": "Esta oferta ha sido aceptada por {user} y está pendiente de finalización.",
+ "youCompleted": "Has aceptado esta oferta.",
+ "completed": "Esta oferta ha sido aceptada por {user}.",
+ "confirmSend": "Cuando cree esta oferta, {amount} {token} (incluye 2 tarifas de transacción) se transferirá de su billetera al contenedor de depósito en garantía de OpenChat. Permanecerá allí hasta que la oferta caduque, usted la cancele o alguien la acepte. ¿Desea continuar?",
+ "confirmAccept": "Cuando acepte esta oferta, {amount} {token} (incluye 2 tarifas de transacción) se transferirá de su billetera al contenedor de depósito en garantía de OpenChat. Recibirás {amountOther} {tokenOther} a cambio. ¿Desea continuar?",
+ "confirmCancel": "Cuando cancele esta oferta, {amount} {token} se transferirá del contenedor de OpenChat Escrow a su billetera. ¿Desea continuar?",
+ "insufficientBalanceMessage": "Necesita al menos {amount} {token} (incluye 2 tarifas de transacción) para aceptar este intercambio.",
+ "insufficientBalance": "Saldo insuficiente",
+ "tokenBalance": "saldo {token}",
+ "already_accepted": "Esta oferta de intercambio ya ha sido aceptada",
+ "swap_cancelled": "Esta oferta de intercambio ha sido cancelada.",
+ "swap_expired": "Esta oferta de intercambio ha caducado",
+ "swap_not_found": "Esta oferta de intercambio no se pudo encontrar",
+ "insufficient_funds": "No le quedan fondos suficientes para aceptar esta oferta de intercambio",
+ "unknown_accept_error": "Error al aceptar la oferta de intercambio",
+ "unknown_cancel_error": "Error al cancelar la oferta de intercambio"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/fr.json b/frontend/app/src/i18n/fr.json
index 529c50ef6d..2bd5ce1b85 100644
--- a/frontend/app/src/i18n/fr.json
+++ b/frontend/app/src/i18n/fr.json
@@ -1154,9 +1154,6 @@
"sendTextDisabled": "Les messages texte sont désactivés",
"disappearingMessages": {
"label": "Messages qui disparaissent",
- "hours": "Heures)",
- "minutes": "Minutes)",
- "days": "Jours)",
"summary": "Les messages disparaîtront après {duration}",
"timeUpdatedBy": "{changedBy} définit l'heure de disparition du message sur {duration}",
"disabledBy": "{changedBy} a désactivé les messages qui disparaissent",
@@ -1206,5 +1203,40 @@
"messageBlocked": "Message masqué",
"removePreviewQuestion": "Supprimer l'aperçu ?",
"showPreview": "Afficher l'aperçu",
- "excessiveLinksNote": "Les aperçus des liens sont masqués pour les messages contenant plus de 5 liens."
+ "excessiveLinksNote": "Les aperçus des liens sont masqués pour les messages contenant plus de 5 liens.",
+ "hours": "Heures",
+ "minutes": "Minutes",
+ "days": "Jours",
+ "p2pSwap": {
+ "builderTitle": "Faire une offre d'échange",
+ "expiryTime": "Date d'expiration",
+ "creatingYourMessage": "Veuillez patienter pendant la création d'une offre d'échange",
+ "failedToCreateMessage": "Échec de la création de l'offre d'échange",
+ "expired": "Expiré",
+ "accepted": "Accepté",
+ "accept": "Accepter",
+ "cancel": "Annuler",
+ "cancelled": "Annulé",
+ "reserved": "Réservé",
+ "summary": "Il s'agit d'une offre d'échange de {fromAmount} {fromToken} contre {toAmount} {toToken}.",
+ "youReserved": "Vous avez réservé cette offre et elle est en cours de finalisation.",
+ "youAccepted": "Vous avez accepté cette offre et elle est en attente de finalisation.",
+ "reservedBy": "Cette offre a été réservée par {user} et est en attente de finalisation.",
+ "acceptedBy": "Cette offre a été acceptée par {user} et est en attente de finalisation.",
+ "youCompleted": "Vous avez accepté cette offre.",
+ "completed": "Cette offre a été acceptée par {user}.",
+ "confirmSend": "Lorsque vous créez cette offre, {amount} {token} (comprend 2 frais de transaction) sera transféré de votre portefeuille vers le canister OpenChat Escrow. Il y restera jusqu'à ce que l'offre expire, que vous annuliez l'offre ou que quelqu'un accepte l'offre. Voulez-vous continuer?",
+ "confirmAccept": "Lorsque vous acceptez cette offre, {amount} {token} (comprend 2 frais de transaction) sera transféré de votre portefeuille vers le canister OpenChat Escrow. Vous recevrez {amountOther} {tokenOther} en échange. Voulez-vous continuer?",
+ "confirmCancel": "Lorsque vous annulez cette offre, {amount} {token} sera transféré du conteneur OpenChat Escrow vers votre portefeuille. Voulez-vous continuer?",
+ "insufficientBalanceMessage": "Vous avez besoin d'au moins {amount} {token} (comprend 2 frais de transaction) pour accepter cet échange.",
+ "insufficientBalance": "Solde insuffisant",
+ "tokenBalance": "solde {token}",
+ "already_accepted": "Cette offre d'échange a déjà été acceptée",
+ "swap_cancelled": "Cette offre d'échange a été annulée",
+ "swap_expired": "Cette offre d'échange est expirée",
+ "swap_not_found": "Cette offre d'échange est introuvable",
+ "insufficient_funds": "Il ne vous reste pas suffisamment de fonds pour accepter cette offre d'échange",
+ "unknown_accept_error": "Erreur lors de l'acceptation de l'offre d'échange",
+ "unknown_cancel_error": "Erreur lors de l'annulation de l'offre d'échange"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/hi.json b/frontend/app/src/i18n/hi.json
index f054282945..621c1e8ad9 100644
--- a/frontend/app/src/i18n/hi.json
+++ b/frontend/app/src/i18n/hi.json
@@ -1183,9 +1183,6 @@
"hideAuthProviders": "विकल्प छिपाएँ",
"disappearingMessages": {
"label": "गायब हो रहे संदेश",
- "hours": "घंटे)",
- "minutes": "मिनट)",
- "days": "दिन",
"summary": "{duration} के बाद संदेश गायब हो जाएंगे",
"timeUpdatedBy": "{changedBy} ने गायब होने वाले संदेश का समय {duration} पर सेट किया",
"disabledBy": "{changedBy} ने गायब होने वाले संदेशों को अक्षम कर दिया",
@@ -1198,5 +1195,40 @@
"messageBlocked": "संदेश छुपाया गया",
"removePreviewQuestion": "पूर्वावलोकन हटाएँ?",
"showPreview": "पूर्वावलोकन दिखाएं",
- "excessiveLinksNote": "5 से अधिक लिंक वाले संदेशों के लिए लिंक पूर्वावलोकन छिपे हुए हैं।"
+ "excessiveLinksNote": "5 से अधिक लिंक वाले संदेशों के लिए लिंक पूर्वावलोकन छिपे हुए हैं।",
+ "hours": "घंटे",
+ "minutes": "मिनट",
+ "days": "दिन",
+ "p2pSwap": {
+ "builderTitle": "अदला-बदली की पेशकश करें",
+ "expiryTime": "समाप्ति समय",
+ "creatingYourMessage": "कृपया स्वैप ऑफर बनने तक प्रतीक्षा करें",
+ "failedToCreateMessage": "स्वैप ऑफ़र बनाने में विफल",
+ "expired": "खत्म हो चुका",
+ "accepted": "स्वीकृत",
+ "accept": "स्वीकार करना",
+ "cancel": "रद्द करना",
+ "cancelled": "रद्द",
+ "reserved": "सुरक्षित",
+ "summary": "यह {fromAmount} {fromToken} को {toAmount} {toToken} से बदलने का ऑफर है।",
+ "youReserved": "आपने यह प्रस्ताव आरक्षित कर लिया है और यह पूरा होने तक लंबित है।",
+ "youAccepted": "आपने यह प्रस्ताव स्वीकार कर लिया है और यह पूरा होना बाकी है।",
+ "reservedBy": "यह ऑफ़र {user} द्वारा आरक्षित किया गया है और पूरा होने के लिए लंबित है।",
+ "acceptedBy": "यह प्रस्ताव {user} द्वारा स्वीकार कर लिया गया है और पूरा होने के लिए लंबित है।",
+ "youCompleted": "आपने यह प्रस्ताव स्वीकार कर लिया है.",
+ "completed": "यह प्रस्ताव {user} द्वारा स्वीकार कर लिया गया है.",
+ "confirmSend": "जब आप यह ऑफर बनाते हैं तो {amount} {token} (2 लेनदेन शुल्क शामिल है) आपके वॉलेट से ओपनचैट एस्क्रो कनस्तर में स्थानांतरित कर दिया जाएगा। यह तब तक वहीं रहेगा जब तक ऑफ़र समाप्त नहीं हो जाता, आप ऑफ़र रद्द नहीं कर देते, या कोई ऑफ़र स्वीकार नहीं कर लेता। क्या आप आगे बढ़ना चाहते हैं?",
+ "confirmAccept": "जब आप इस ऑफर को स्वीकार करते हैं तो {amount} {token} (2 लेनदेन शुल्क शामिल है) आपके वॉलेट से ओपनचैट एस्क्रो कनस्तर में स्थानांतरित कर दिया जाएगा। बदले में आपको {amountOther} {tokenOther} प्राप्त होगा। क्या आप आगे बढ़ना चाहते हैं?",
+ "confirmCancel": "जब आप इस ऑफर को रद्द करते हैं तो {amount} {token} को ओपनचैट एस्क्रो कनस्तर से वापस आपके वॉलेट में स्थानांतरित कर दिया जाएगा। क्या आप आगे बढ़ना चाहते हैं?",
+ "insufficientBalanceMessage": "इस स्वैप को स्वीकार करने के लिए आपको कम से कम {amount} {token} (2 लेनदेन शुल्क शामिल) की आवश्यकता है।",
+ "insufficientBalance": "अपर्याप्त शेषराशि",
+ "tokenBalance": "{token} संतुलन",
+ "already_accepted": "यह स्वैप ऑफर पहले ही स्वीकार कर लिया गया है",
+ "swap_cancelled": "यह स्वैप ऑफर रद्द कर दिया गया है",
+ "swap_expired": "यह स्वैप ऑफर समाप्त हो गया है",
+ "swap_not_found": "यह स्वैप ऑफर नहीं मिल सका",
+ "insufficient_funds": "इस स्वैप प्रस्ताव को स्वीकार करने के लिए आपके पास अपर्याप्त धनराशि शेष है",
+ "unknown_accept_error": "स्वैप प्रस्ताव स्वीकार करने में त्रुटि",
+ "unknown_cancel_error": "स्वैप ऑफ़र रद्द करने में त्रुटि"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/it.json b/frontend/app/src/i18n/it.json
index a9843fb0aa..76c6790fbc 100644
--- a/frontend/app/src/i18n/it.json
+++ b/frontend/app/src/i18n/it.json
@@ -1154,9 +1154,6 @@
"sendTextDisabled": "I messaggi di testo sono disabilitati",
"disappearingMessages": {
"label": "Messaggi che scompaiono",
- "hours": "Ore)",
- "minutes": "Minuti)",
- "days": "Giorno(i)",
"summary": "I messaggi scompariranno dopo le ore {duration}",
"timeUpdatedBy": "{changedBy} imposta l'ora del messaggio a scomparsa su {duration}",
"disabledBy": "{changedBy} ha disabilitato i messaggi a scomparsa",
@@ -1206,5 +1203,40 @@
"messageBlocked": "Messaggio nascosto",
"removePreviewQuestion": "Rimuovere l'anteprima?",
"showPreview": "Anteprima dello spettacolo",
- "excessiveLinksNote": "Le anteprime dei collegamenti sono nascoste per i messaggi con più di 5 collegamenti."
+ "excessiveLinksNote": "Le anteprime dei collegamenti sono nascoste per i messaggi con più di 5 collegamenti.",
+ "hours": "Ore",
+ "minutes": "Minuti",
+ "days": "Giorni",
+ "p2pSwap": {
+ "builderTitle": "Fai un'offerta di scambio",
+ "expiryTime": "Tempo di scadenza",
+ "creatingYourMessage": "Attendi mentre viene creata un'offerta di scambio",
+ "failedToCreateMessage": "Impossibile creare l'offerta di scambio",
+ "expired": "Scaduto",
+ "accepted": "Accettato",
+ "accept": "Accettare",
+ "cancel": "Annulla",
+ "cancelled": "Annullato",
+ "reserved": "Riservato",
+ "summary": "Questa è un'offerta per scambiare {fromAmount} {fromToken} con {toAmount} {toToken}.",
+ "youReserved": "Hai prenotato questa offerta ed è in attesa di completamento.",
+ "youAccepted": "Hai accettato questa offerta ed è in attesa di completamento.",
+ "reservedBy": "Questa offerta è stata prenotata da {user} ed è in attesa di completamento.",
+ "acceptedBy": "Questa offerta è stata accettata da {user} ed è in attesa di completamento.",
+ "youCompleted": "Hai accettato questa offerta.",
+ "completed": "Questa offerta è stata accettata da {user}.",
+ "confirmSend": "Quando crei questa offerta, {amount} {token} (include 2 commissioni di transazione) verrà trasferito dal tuo portafoglio al contenitore di deposito a garanzia di OpenChat. Rimarrà lì fino alla scadenza dell'offerta, fino all'annullamento dell'offerta o fino all'accettazione dell'offerta da parte di qualcuno. Desideri procedere?",
+ "confirmAccept": "Quando accetti questa offerta, {amount} {token} (include 2 commissioni di transazione) verrà trasferito dal tuo portafoglio al contenitore di deposito a garanzia di OpenChat. Riceverai {amountOther} {tokenOther} in cambio. Desideri procedere?",
+ "confirmCancel": "Quando annulli questa offerta, {amount} {token} verrà trasferito dal contenitore di deposito in garanzia di OpenChat al tuo portafoglio. Desideri procedere?",
+ "insufficientBalanceMessage": "Sono necessari almeno {amount} {token} (include 2 commissioni di transazione) per accettare questo scambio.",
+ "insufficientBalance": "Equilibrio insufficiente",
+ "tokenBalance": "saldo {token}",
+ "already_accepted": "Questa offerta di scambio è già stata accettata",
+ "swap_cancelled": "Questa offerta di scambio è stata annullata",
+ "swap_expired": "Questa offerta di scambio è scaduta",
+ "swap_not_found": "Impossibile trovare questa offerta di scambio",
+ "insufficient_funds": "Non hai fondi rimanenti sufficienti per accettare questa offerta di scambio",
+ "unknown_accept_error": "Errore nell'accettazione dell'offerta di scambio",
+ "unknown_cancel_error": "Errore durante l'annullamento dell'offerta di scambio"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/iw.json b/frontend/app/src/i18n/iw.json
index 78119f62c4..bd951d1e5f 100644
--- a/frontend/app/src/i18n/iw.json
+++ b/frontend/app/src/i18n/iw.json
@@ -1152,9 +1152,6 @@
"sendTextDisabled": "הודעות טקסט מושבתות",
"disappearingMessages": {
"label": "הודעות נעלמות",
- "hours": "שעה (ות)",
- "minutes": "דקות)",
- "days": "ימים",
"summary": "ההודעות ייעלמו לאחר {duration}",
"timeUpdatedBy": "{changedBy} הגדר את זמן ההודעה הנעלמת ל-{duration}",
"disabledBy": "{changedBy} השבית הודעות נעלמות",
@@ -1204,5 +1201,40 @@
"messageBlocked": "הודעה מוסתרת",
"removePreviewQuestion": "להסיר את התצוגה המקדימה?",
"showPreview": "הצג תצוגה מקדימה",
- "excessiveLinksNote": "תצוגות מקדימות של קישורים מוסתרות עבור הודעות עם יותר מ-5 קישורים."
+ "excessiveLinksNote": "תצוגות מקדימות של קישורים מוסתרות עבור הודעות עם יותר מ-5 קישורים.",
+ "hours": "שעה (ות",
+ "minutes": "דקות",
+ "days": "ימים",
+ "p2pSwap": {
+ "builderTitle": "תציעו הצעת החלפה",
+ "expiryTime": "זמן תפוגה",
+ "creatingYourMessage": "אנא המתן בזמן יצירת הצעת החלפה",
+ "failedToCreateMessage": "יצירת הצעת ההחלפה נכשלה",
+ "expired": "לא בתוקף",
+ "accepted": "מְקוּבָּל",
+ "accept": "לְקַבֵּל",
+ "cancel": "לְבַטֵל",
+ "cancelled": "מבוטל",
+ "reserved": "שמורות",
+ "summary": "זוהי הצעה להחליף את {fromAmount} {fromToken} ב-{toAmount} {toToken}.",
+ "youReserved": "שמרת את ההצעה הזו והיא ממתינה להשלמתה.",
+ "youAccepted": "קיבלת הצעה זו והיא ממתינה להשלמה.",
+ "reservedBy": "הצעה זו נשמרה על ידי {user} והיא ממתינה להשלמתה.",
+ "acceptedBy": "הצעה זו התקבלה על ידי {user} והיא ממתינה להשלמתה.",
+ "youCompleted": "קיבלת את ההצעה הזו.",
+ "completed": "הצעה זו התקבלה על ידי {user}.",
+ "confirmSend": "כאשר אתה יוצר את ההצעה הזו {amount} {token} (כולל 2 עמלות עסקה) יועבר מהארנק שלך אל קופסת ה-OpenChat Escrow. זה יישאר שם עד שההצעה יפוג, תבטל את ההצעה או שמישהו יקבל את ההצעה. האם אתה מעוניין להמשיך?",
+ "confirmAccept": "כאשר תקבל הצעה זו {amount} {token} (כולל 2 עמלות עסקה) יועברו מהארנק שלך אל קופסת ה-OpenChat Escrow. תקבל {amountOther} {tokenOther} בתמורה. האם אתה מעוניין להמשיך?",
+ "confirmCancel": "כאשר תבטל הצעה זו {amount} {token} יועבר ממיכל ה-OpenChat Escrow בחזרה לארנק שלך. האם אתה מעוניין להמשיך?",
+ "insufficientBalanceMessage": "אתה צריך לפחות {amount} {token} (כולל 2 עמלות עסקה) כדי לקבל את ההחלפה הזו.",
+ "insufficientBalance": "איזון לא מספיק",
+ "tokenBalance": "יתרת {token}",
+ "already_accepted": "הצעת ההחלפה הזו כבר התקבלה",
+ "swap_cancelled": "הצעת ההחלפה הזו בוטלה",
+ "swap_expired": "פג תוקף הצעת ההחלפה הזו",
+ "swap_not_found": "לא ניתן למצוא את הצעת ההחלפה הזו",
+ "insufficient_funds": "אין לך מספיק כסף כדי לקבל את הצעת ההחלפה הזו",
+ "unknown_accept_error": "שגיאה בקבלת הצעת ההחלפה",
+ "unknown_cancel_error": "שגיאה בביטול הצעת ההחלפה"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/jp.json b/frontend/app/src/i18n/jp.json
index ab9a3eaf33..3972abed20 100644
--- a/frontend/app/src/i18n/jp.json
+++ b/frontend/app/src/i18n/jp.json
@@ -1155,9 +1155,6 @@
"sendTextDisabled": "テキストメッセージが無効になっています",
"disappearingMessages": {
"label": "消えるメッセージ",
- "hours": "時間)",
- "minutes": "分)",
- "days": "日々)",
"summary": "メッセージは {duration} 後に消えます",
"timeUpdatedBy": "{changedBy} はメッセージが消える時間を {duration} に設定します",
"disabledBy": "{changedBy} は消えるメッセージを無効にしました",
@@ -1207,5 +1204,40 @@
"messageBlocked": "メッセージが隠されています",
"removePreviewQuestion": "プレビューを削除しますか?",
"showPreview": "ショープレビュー",
- "excessiveLinksNote": "リンクが 5 つを超えるメッセージのリンク プレビューは非表示になります。"
+ "excessiveLinksNote": "リンクが 5 つを超えるメッセージのリンク プレビューは非表示になります。",
+ "hours": "時間",
+ "minutes": "分",
+ "days": "日々",
+ "p2pSwap": {
+ "builderTitle": "交換オファーをする",
+ "expiryTime": "有効期限",
+ "creatingYourMessage": "スワップオファーが作成されるまでお待ちください",
+ "failedToCreateMessage": "スワップオファーの作成に失敗しました",
+ "expired": "期限切れ",
+ "accepted": "承認されました",
+ "accept": "受け入れる",
+ "cancel": "キャンセル",
+ "cancelled": "キャンセル",
+ "reserved": "予約済み",
+ "summary": "これは、{fromAmount} {fromToken} と {toAmount} {toToken} を交換するというオファーです。",
+ "youReserved": "このオファーは予約済みで、完了待ちです。",
+ "youAccepted": "あなたはこのオファーを受け入れましたが、完了は保留中です。",
+ "reservedBy": "このオファーは {user} によって予約されており、完了待ちです。",
+ "acceptedBy": "このオファーは {user} によって受け入れられ、完了待ちです。",
+ "youCompleted": "あなたはこの申し出を受け入れました。",
+ "completed": "このオファーは {user} によって受け入れられました。",
+ "confirmSend": "このオファーを作成すると、{amount} {token} (2 つの取引手数料を含む) がウォレットから OpenChat エスクロー キャニスターに転送されます。オファーの有効期限が切れるか、ユーザーがオファーをキャンセルするか、誰かがオファーを受け入れるまで、そこに残ります。続行しますか?",
+ "confirmAccept": "このオファーを受け入れると、{amount} {token} (2 つの取引手数料を含む) がウォレットから OpenChat エスクロー キャニスターに転送されます。代わりに {amountOther} {tokenOther} を受け取ります。続行しますか?",
+ "confirmCancel": "このオファーをキャンセルすると、{amount} {token} が OpenChat エスクロー キャニスターからウォレットに戻されます。続行しますか?",
+ "insufficientBalanceMessage": "このスワップを受け入れるには、少なくとも {amount} {token} (2 つの取引手数料を含む) が必要です。",
+ "insufficientBalance": "残高不足",
+ "tokenBalance": "{token} 残高",
+ "already_accepted": "このスワップオファーはすでに受け入れられています",
+ "swap_cancelled": "このスワップオファーはキャンセルされました",
+ "swap_expired": "このスワップオファーは期限切れです",
+ "swap_not_found": "このスワップ オファーが見つかりませんでした",
+ "insufficient_funds": "このスワップオファーを受け入れるには資金が不足しています",
+ "unknown_accept_error": "スワップオファーの受け入れエラー",
+ "unknown_cancel_error": "スワップオファーのキャンセル中にエラーが発生しました"
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/ru.json b/frontend/app/src/i18n/ru.json
index baa60332e0..ce9b96a913 100644
--- a/frontend/app/src/i18n/ru.json
+++ b/frontend/app/src/i18n/ru.json
@@ -1154,9 +1154,6 @@
"sendTextDisabled": "Текстовые сообщения отключены",
"disappearingMessages": {
"label": "Исчезающие сообщения",
- "hours": "Часы)",
- "minutes": "Минута(ы)",
- "days": "День(а)",
"summary": "Сообщения исчезнут после {duration}",
"timeUpdatedBy": "{changedBy} устанавливает время исчезновения сообщения на {duration}",
"disabledBy": "{changedBy} отключил исчезающие сообщения",
@@ -1206,5 +1203,40 @@
"messageBlocked": "Сообщение скрыто",
"removePreviewQuestion": "Удалить предварительный просмотр?",
"showPreview": "Показать предварительный просмотр",
- "excessiveLinksNote": "Предварительный просмотр ссылок скрыт для сообщений, содержащих более 5 ссылок."
+ "excessiveLinksNote": "Предварительный просмотр ссылок скрыт для сообщений, содержащих более 5 ссылок.",
+ "hours": "Часы",
+ "minutes": "Минуты",
+ "days": "Дни",
+ "p2pSwap": {
+ "builderTitle": "Сделать предложение обмена",
+ "expiryTime": "Срок годности",
+ "creatingYourMessage": "Пожалуйста, подождите, пока будет создано предложение обмена.",
+ "failedToCreateMessage": "Не удалось создать предложение обмена.",
+ "expired": "Истекший",
+ "accepted": "Принял",
+ "accept": "Принимать",
+ "cancel": "Отмена",
+ "cancelled": "Отменено",
+ "reserved": "Сдержанный",
+ "summary": "Это предложение обменять {fromAmount} {fromToken} на {toAmount} {toToken}.",
+ "youReserved": "Вы зарезервировали это предложение, и оно ожидает завершения.",
+ "youAccepted": "Вы приняли это предложение, и оно ожидает завершения.",
+ "reservedBy": "Это предложение зарезервировано пользователем {user} и ожидает завершения.",
+ "acceptedBy": "Это предложение было принято {user} и ожидает завершения.",
+ "youCompleted": "Вы приняли это предложение.",
+ "completed": "Это предложение было принято {user}.",
+ "confirmSend": "Когда вы создадите это предложение, {amount} {token} (включает комиссию за 2 транзакции) будет переведен из вашего кошелька в контейнер условного депонирования OpenChat. Он будет оставаться там до тех пор, пока не истечет срок действия предложения, вы не отмените его или кто-то не примет предложение. Вы хотите продолжить?",
+ "confirmAccept": "Когда вы примете это предложение, {amount} {token} (включает 2 комиссии за транзакцию) будет переведен из вашего кошелька в контейнер условного депонирования OpenChat. Взамен вы получите {amountOther} {tokenOther}. Вы хотите продолжить?",
+ "confirmCancel": "Когда вы отмените это предложение, {amount} {token} будет переведен из контейнера OpenChat Escrow обратно в ваш кошелек. Вы хотите продолжить?",
+ "insufficientBalanceMessage": "Чтобы принять этот своп, вам необходимо как минимум {amount} {token} (включая комиссию за 2 транзакции).",
+ "insufficientBalance": "Недостаточный баланс",
+ "tokenBalance": "{token} баланс",
+ "already_accepted": "Это предложение обмена уже принято",
+ "swap_cancelled": "Это предложение обмена было отменено",
+ "swap_expired": "Срок действия этого предложения обмена истек",
+ "swap_not_found": "Данное предложение обмена не найдено.",
+ "insufficient_funds": "У вас недостаточно средств, чтобы принять это предложение обмена",
+ "unknown_accept_error": "Ошибка при принятии предложения обмена.",
+ "unknown_cancel_error": "Ошибка при отмене предложения обмена."
+ }
}
\ No newline at end of file
diff --git a/frontend/app/src/i18n/vi.json b/frontend/app/src/i18n/vi.json
index 9c50115a21..242773c4ee 100644
--- a/frontend/app/src/i18n/vi.json
+++ b/frontend/app/src/i18n/vi.json
@@ -1155,9 +1155,6 @@
"sendTextDisabled": "Tin nhắn văn bản bị vô hiệu hóa",
"disappearingMessages": {
"label": "Tin nhắn biến mất",
- "hours": "Giờ)",
- "minutes": "Phút)",
- "days": "(Các) ngày",
"summary": "Tin nhắn sẽ biến mất sau {duration}",
"timeUpdatedBy": "{changedBy} đặt thời gian tin nhắn biến mất thành {duration}",
"disabledBy": "{changedBy} đã vô hiệu hóa các tin nhắn biến mất",
@@ -1207,5 +1204,40 @@
"messageBlocked": "Tin nhắn bị ẩn",
"removePreviewQuestion": "Xóa bản xem trước?",
"showPreview": "Hiển thị bản xem trước",
- "excessiveLinksNote": "Bản xem trước liên kết bị ẩn đối với các thư có hơn 5 liên kết."
+ "excessiveLinksNote": "Bản xem trước liên kết bị ẩn đối với các thư có hơn 5 liên kết.",
+ "hours": "Giờ",
+ "minutes": "Phút",
+ "days": "Ngày",
+ "p2pSwap": {
+ "builderTitle": "Đưa ra đề nghị trao đổi",
+ "expiryTime": "Thời gian hết hạn",
+ "creatingYourMessage": "Vui lòng đợi trong khi đề nghị hoán đổi được tạo",
+ "failedToCreateMessage": "Không tạo được ưu đãi hoán đổi",
+ "expired": "Hết hạn",
+ "accepted": "Đã được chấp nhận",
+ "accept": "Chấp nhận",
+ "cancel": "Hủy bỏ",
+ "cancelled": "Đã hủy",
+ "reserved": "Kín đáo",
+ "summary": "Đây là đề nghị đổi {fromAmount} {fromToken} lấy {toAmount} {toToken}.",
+ "youReserved": "Bạn đã đặt trước ưu đãi này và nó đang chờ hoàn thành.",
+ "youAccepted": "Bạn đã chấp nhận ưu đãi này và nó đang chờ hoàn thành.",
+ "reservedBy": "Ưu đãi này đã được {user} đặt trước và đang chờ hoàn thành.",
+ "acceptedBy": "Ưu đãi này đã được {user} chấp nhận và đang chờ hoàn thành.",
+ "youCompleted": "Bạn đã chấp nhận lời đề nghị này.",
+ "completed": "Ưu đãi này đã được {user} chấp nhận.",
+ "confirmSend": "Khi bạn tạo ưu đãi này, {amount} {token} (bao gồm 2 phí giao dịch) sẽ được chuyển từ ví của bạn sang hộp Ký quỹ OpenChat. Nó sẽ ở đó cho đến khi ưu đãi hết hạn, bạn hủy ưu đãi hoặc ai đó chấp nhận ưu đãi. Bạn có muốn tiếp tục?",
+ "confirmAccept": "Khi bạn chấp nhận ưu đãi này, {amount} {token} (bao gồm 2 phí giao dịch) sẽ được chuyển từ ví của bạn sang hộp Ký quỹ OpenChat. Đổi lại bạn sẽ nhận được {amountOther} {tokenOther}. Bạn có muốn tiếp tục?",
+ "confirmCancel": "Khi bạn hủy ưu đãi này, {amount} {token} sẽ được chuyển từ hộp Ký quỹ OpenChat trở lại ví của bạn. Bạn có muốn tiếp tục?",
+ "insufficientBalanceMessage": "Bạn cần ít nhất {amount} {token} (bao gồm 2 phí giao dịch) để chấp nhận giao dịch hoán đổi này.",
+ "insufficientBalance": "Thiếu cân bằng",
+ "tokenBalance": "số dư {token}",
+ "already_accepted": "Đề nghị hoán đổi này đã được chấp nhận",
+ "swap_cancelled": "Ưu đãi hoán đổi này đã bị hủy",
+ "swap_expired": "Ưu đãi hoán đổi này đã hết hạn",
+ "swap_not_found": "Không thể tìm thấy ưu đãi trao đổi này",
+ "insufficient_funds": "Bạn không còn đủ tiền để chấp nhận đề nghị hoán đổi này",
+ "unknown_accept_error": "Lỗi chấp nhận đề nghị trao đổi",
+ "unknown_cancel_error": "Lỗi hủy ưu đãi trao đổi"
+ }
}
\ No newline at end of file
diff --git a/frontend/openchat-agent/src/services/common/chatMappers.ts b/frontend/openchat-agent/src/services/common/chatMappers.ts
index 4a86ca4d64..aa5fb604ac 100644
--- a/frontend/openchat-agent/src/services/common/chatMappers.ts
+++ b/frontend/openchat-agent/src/services/common/chatMappers.ts
@@ -63,6 +63,12 @@ import type {
ApiChat,
ApiPrizeCotentInitial,
ApiMessagePermissions,
+ ApiP2PSwapContentInitial,
+ ApiTokenInfo,
+ ApiP2PSwapContent,
+ ApiP2PSwapStatus,
+ ApiCancelP2PSwapResponse as ApiUserCancelP2PSwapResponse,
+ ApiAcceptP2PSwapResponse as ApiUserAcceptP2PSwapResponse,
} from "../user/candid/idl";
import type {
Message,
@@ -152,6 +158,11 @@ import type {
MessagePermissions,
ExpiredEventsRange,
ExpiredMessagesRange,
+ P2PSwapContentInitial,
+ P2PSwapContent,
+ P2PSwapStatus,
+ TokenInfo,
+ CancelP2PSwapResponse,
} from "openchat-shared";
import {
ProposalDecisionStatus,
@@ -168,7 +179,7 @@ import {
SNS1_SYMBOL,
isAccountIdentifierValid,
} from "openchat-shared";
-import type { WithdrawCryptoArgs } from "../user/candid/types";
+import type { SwapStatusError, WithdrawCryptoArgs } from "../user/candid/types";
import type {
ApiGroupCanisterGroupChatSummary,
ApiAddReactionResponse as ApiAddGroupReactionResponse,
@@ -197,6 +208,8 @@ import type {
ApiResetInviteCodeResponse,
ApiRegisterProposalVoteResponse as ApiGroupRegisterProposalVoteResponse,
ApiClaimPrizeResponse as ApiClaimGroupPrizeResponse,
+ ApiAcceptP2PSwapResponse as ApiGroupAcceptP2PSwapResponse,
+ ApiCancelP2PSwapResponse as ApiGroupCancelP2PSwapResponse,
} from "../group/candid/idl";
import type {
ApiGateCheckFailedReason,
@@ -231,9 +244,12 @@ import type {
ApiEnableInviteCodeResponse as ApiCommunityEnableInviteCodeResponse,
ApiRegisterProposalVoteResponse as ApiCommunityRegisterProposalVoteResponse,
ApiClaimPrizeResponse as ApiClaimChannelPrizeResponse,
+ ApiAcceptP2PSwapResponse as ApiCommunityAcceptP2PSwapResponse,
+ ApiCancelP2PSwapResponse as ApiCommunityCancelP2PSwapResponse,
} from "../community/candid/idl";
import { ReplicaNotUpToDateError } from "../error";
import { messageMatch } from "../user/mappers";
+import type { AcceptP2PSwapResponse } from "openchat-shared";
const E8S_AS_BIGINT = BigInt(100_000_000);
@@ -337,6 +353,9 @@ export function messageContent(candid: ApiMessageContent, sender: string): Messa
if ("ReportedMessage" in candid) {
return reportedMessage(candid.ReportedMessage);
}
+ if ("P2PSwap" in candid) {
+ return p2pSwapContent(candid.P2PSwap);
+ }
throw new UnsupportedValueError("Unexpected ApiMessageContent type received", candid);
}
@@ -416,6 +435,75 @@ function prizeContent(candid: ApiPrizeContent): PrizeContent {
};
}
+function p2pSwapContent(candid: ApiP2PSwapContent): P2PSwapContent {
+ return {
+ kind: "p2p_swap_content",
+ token0: tokenInfo(candid.token0),
+ token1: tokenInfo(candid.token1),
+ token0Amount: candid.token0_amount,
+ token1Amount: candid.token1_amount,
+ caption: optional(candid.caption, identity),
+ expiresAt: candid.expires_at,
+ status: p2pTradeStatus(candid.status),
+ swapId: candid.swap_id,
+ token0TxnIn: candid.token0_txn_in,
+ };
+}
+
+function tokenInfo(candid: ApiTokenInfo): TokenInfo {
+ return {
+ fee: candid.fee,
+ decimals: candid.decimals,
+ symbol: token(candid.token),
+ ledger: candid.ledger.toString(),
+ };
+}
+
+function p2pTradeStatus(candid: ApiP2PSwapStatus): P2PSwapStatus {
+ if ("Open" in candid) {
+ return { kind: "p2p_swap_open" };
+ }
+ if ("Reserved" in candid) {
+ return {
+ kind: "p2p_swap_reserved",
+ reservedBy: candid.Reserved.reserved_by.toString(),
+ };
+ }
+ if ("Accepted" in candid) {
+ return {
+ kind: "p2p_swap_accepted",
+ acceptedBy: candid.Accepted.accepted_by.toString(),
+ token1TxnIn: candid.Accepted.token1_txn_in,
+ };
+ }
+ if ("Cancelled" in candid) {
+ return {
+ kind: "p2p_swap_cancelled",
+ token0TxnOut: optional(candid.Cancelled.token0_txn_out, identity),
+ };
+
+ }
+ if ("Expired" in candid) {
+ return {
+ kind: "p2p_swap_expired",
+ token0TxnOut: optional(candid.Expired.token0_txn_out, identity),
+ };
+
+ }
+ if ("Completed" in candid) {
+ const { accepted_by, token1_txn_in, token0_txn_out, token1_txn_out } = candid.Completed;
+ return {
+ kind: "p2p_swap_completed",
+ acceptedBy: accepted_by.toString(),
+ token1TxnIn: token1_txn_in,
+ token0TxnOut: token0_txn_out,
+ token1TxnOut: token1_txn_out,
+ };
+ }
+
+ throw new UnsupportedValueError("Unexpected ApiP2PSwapStatus type received", candid);
+}
+
export function apiUser(domain: User): ApiUser {
return {
user_id: Principal.fromText(domain.userId),
@@ -956,6 +1044,7 @@ function apiMessagePermissions(permissions: MessagePermissions): ApiMessagePermi
giphy: apiOptional(apiPermissionRole, permissions.giphy),
prize: apiOptional(apiPermissionRole, permissions.prize),
p2p_swap: apiOptional(apiPermissionRole, permissions.p2pSwap),
+ p2p_trade: [],
custom:
permissions.memeFighter !== undefined
? [{ subtype: "meme_fighter", role: apiPermissionRole(permissions.memeFighter) }]
@@ -1103,6 +1192,9 @@ export function apiMessageContent(domain: MessageContent): ApiMessageContentInit
case "prize_content_initial":
return { Prize: apiPrizeContentInitial(domain) };
+ case "p2p_swap_content_initial":
+ return { P2PSwap: apiP2PSwapContentInitial(domain) };
+
case "meme_fighter_content":
// eslint-disable-next-line no-case-declarations
const encoder = new TextEncoder();
@@ -1135,6 +1227,7 @@ export function apiMessageContent(domain: MessageContent): ApiMessageContentInit
case "message_reminder_content":
case "message_reminder_created_content":
case "reported_message_content":
+ case "p2p_swap_content":
throw new Error(`Incorrectly attempting to send {domain.kind} content to the server`);
}
}
@@ -1344,6 +1437,14 @@ export function accessGate(candid: ApiAccessGate): AccessGate {
fee: candid.Payment.fee,
};
}
+ if ("TokenBalance" in candid) {
+ return {
+ kind: "token_balance_gate",
+ ledgerCanister: candid.TokenBalance.ledger_canister_id.toString(),
+ minBalance: candid.TokenBalance.min_balance,
+ }
+ }
+
throw new UnsupportedValueError("Unexpected ApiGroupGate type received", candid);
}
@@ -1367,6 +1468,26 @@ export function apiPrizeContentInitial(domain: PrizeContentInitial): ApiPrizeCot
};
}
+export function apiP2PSwapContentInitial(domain: P2PSwapContentInitial): ApiP2PSwapContentInitial {
+ return {
+ token0: apiTokenInfo(domain.token0),
+ token1: apiTokenInfo(domain.token1),
+ token0_amount: domain.token0Amount,
+ token1_amount: domain.token1Amount,
+ caption: apiOptional(identity, domain.caption),
+ expires_in: domain.expiresIn,
+ };
+}
+
+function apiTokenInfo(domain: TokenInfo): ApiTokenInfo {
+ return {
+ fee: domain.fee,
+ decimals: domain.decimals,
+ token: apiToken(domain.symbol),
+ ledger: Principal.fromText(domain.ledger),
+ };
+}
+
export function apiPendingCryptoContent(domain: CryptocurrencyContent): ApiCryptoContent {
return {
recipient: Principal.fromText(domain.transfer.recipient),
@@ -1659,6 +1780,9 @@ export function gateCheckFailedReason(candid: ApiGateCheckFailedReason): GateChe
console.warn("PaymentFailed: ", candid);
return "payment_failed";
}
+ if ("InsufficientBalance" in candid) {
+ return "insufficient_balance";
+ }
throw new UnsupportedValueError("Unexpected ApiGateCheckFailedReason type received", candid);
}
@@ -2307,3 +2431,87 @@ export function claimPrizeResponse(
return CommonResponses.failure();
}
}
+
+export function statusError(candid: SwapStatusError): AcceptP2PSwapResponse & CancelP2PSwapResponse {
+ if ("Reserved" in candid) {
+ return {
+ kind: "already_reserved",
+ reservedBy: candid.Reserved.reserved_by.toString(),
+ }
+ }
+ if ("Accepted" in candid) {
+ return {
+ kind: "already_accepted",
+ acceptedBy: candid.Accepted.accepted_by.toString(),
+ token1TxnIn: candid.Accepted.token1_txn_in,
+ }
+ }
+ if ("Completed" in candid) {
+ const { accepted_by, token1_txn_in, token0_txn_out, token1_txn_out } = candid.Completed;
+ return {
+ kind: "already_completed",
+ acceptedBy: accepted_by.toString(),
+ token1TxnIn: token1_txn_in,
+ token0TxnOut: token0_txn_out,
+ token1TxnOut: token1_txn_out,
+ }
+ }
+ if ("Cancelled" in candid) {
+ return {
+ kind: "swap_cancelled",
+ token0TxnOut: optional(candid.Cancelled.token0_txn_out, identity),
+ }
+ }
+ if ("Expired" in candid) {
+ return {
+ kind: "swap_expired",
+ token0TxnOut: optional(candid.Expired.token0_txn_out, identity),
+ }
+ }
+
+ throw new UnsupportedValueError("Unexpected SwapStatusError type received", candid);
+}
+
+export function acceptP2PSwapResponse(
+ candid: ApiCommunityAcceptP2PSwapResponse | ApiGroupAcceptP2PSwapResponse | ApiUserAcceptP2PSwapResponse)
+: AcceptP2PSwapResponse {
+ if ("Success" in candid) {
+ return { kind: "success", token1TxnIn: candid.Success.token1_txn_in };
+ }
+ if ("StatusError" in candid) {
+ return statusError(candid.StatusError);
+ }
+ if ("ChatNotFound" in candid) return { kind: "chat_not_found" };
+ if ("UserNotInGroup" in candid) return { kind: "user_not_in_group" };
+ if ("UserNotInCommunity" in candid) return { kind: "user_not_in_community" };
+ if ("UserNotInChannel" in candid) return { kind: "user_not_in_channel" };
+ if ("ChannelNotFound" in candid) return { kind: "channel_not_found" };
+ if ("SwapNotFound" in candid) return { kind: "swap_not_found" };
+ if ("ChatFrozen" in candid) return { kind: "chat_frozen" };
+ if ("UserSuspended" in candid) return { kind: "user_suspended" };
+ if ("InternalError" in candid) return { kind: "internal_error", text: candid.InternalError };
+ if ("InsufficientFunds" in candid) return { kind: "insufficient_funds" };
+
+ throw new UnsupportedValueError("Unexpected ApiAcceptP2PSwapResponse type received", candid);
+}
+
+export function cancelP2PSwapResponse(
+ candid: ApiCommunityCancelP2PSwapResponse | ApiGroupCancelP2PSwapResponse | ApiUserCancelP2PSwapResponse)
+: CancelP2PSwapResponse {
+ if ("Success" in candid) {
+ return { kind: "success" };
+ }
+ if ("StatusError" in candid) {
+ return statusError(candid.StatusError);
+ }
+ if ("ChatNotFound" in candid) return { kind: "chat_not_found" };
+ if ("UserNotInGroup" in candid) return { kind: "user_not_in_group" };
+ if ("UserNotInCommunity" in candid) return { kind: "user_not_in_community" };
+ if ("UserNotInChannel" in candid) return { kind: "user_not_in_channel" };
+ if ("ChannelNotFound" in candid) return { kind: "channel_not_found" };
+ if ("ChatFrozen" in candid) return { kind: "chat_frozen" };
+ if ("SwapNotFound" in candid) return { kind: "swap_not_found" };
+ if ("UserSuspended" in candid) return { kind: "user_suspended" };
+
+ throw new UnsupportedValueError("Unexpected ApiCancelP2PSwapResponse type received", candid);
+}
\ No newline at end of file
diff --git a/frontend/openchat-agent/src/services/community/candid/idl.d.ts b/frontend/openchat-agent/src/services/community/candid/idl.d.ts
index a9d92d6cd0..3c79ae0afa 100644
--- a/frontend/openchat-agent/src/services/community/candid/idl.d.ts
+++ b/frontend/openchat-agent/src/services/community/candid/idl.d.ts
@@ -74,6 +74,8 @@ import {
FollowThreadResponse,
UnfollowThreadResponse,
ClaimPrizeResponse,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "./types";
export {
_SERVICE as CommunityService,
@@ -149,6 +151,8 @@ export {
FollowThreadResponse as ApiFollowThreadResponse,
UnfollowThreadResponse as ApiUnfollowThreadResponse,
ClaimPrizeResponse as ApiClaimPrizeResponse,
+ AcceptP2PSwapResponse as ApiAcceptP2PSwapResponse,
+ CancelP2PSwapResponse as ApiCancelP2PSwapResponse,
};
export const idlFactory: IDL.InterfaceFactory;
diff --git a/frontend/openchat-agent/src/services/community/candid/idl.js b/frontend/openchat-agent/src/services/community/candid/idl.js
index 41cc1f4ea9..6d7db88039 100644
--- a/frontend/openchat-agent/src/services/community/candid/idl.js
+++ b/frontend/openchat-agent/src/services/community/candid/idl.js
@@ -1,7 +1,51 @@
export const idlFactory = ({ IDL }) => {
const ChannelId = IDL.Nat;
+ const MessageId = IDL.Nat;
+ const MessageIndex = IDL.Nat32;
+ const AcceptP2PSwapArgs = IDL.Record({
+ 'channel_id' : ChannelId,
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : IDL.Opt(MessageIndex),
+ });
+ const AcceptSwapSuccess = IDL.Record({ 'token1_txn_in' : IDL.Nat64 });
const CanisterId = IDL.Principal;
const UserId = CanisterId;
+ const SwapStatusErrorReserved = IDL.Record({ 'reserved_by' : UserId });
+ const SwapStatusErrorAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusErrorCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorExpired = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusError = IDL.Variant({
+ 'Reserved' : SwapStatusErrorReserved,
+ 'Accepted' : SwapStatusErrorAccepted,
+ 'Cancelled' : SwapStatusErrorCancelled,
+ 'Completed' : SwapStatusErrorCompleted,
+ 'Expired' : SwapStatusErrorExpired,
+ });
+ const AcceptP2PSwapResponse = IDL.Variant({
+ 'UserNotInChannel' : IDL.Null,
+ 'ChannelNotFound' : IDL.Null,
+ 'ChatFrozen' : IDL.Null,
+ 'Success' : AcceptSwapSuccess,
+ 'UserNotInCommunity' : IDL.Null,
+ 'UserSuspended' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ 'InternalError' : IDL.Text,
+ 'InsufficientFunds' : IDL.Null,
+ });
const AddMembersToChannelArgs = IDL.Record({
'channel_id' : ChannelId,
'user_ids' : IDL.Vec(UserId),
@@ -25,6 +69,7 @@ export const idlFactory = ({ IDL }) => {
const GateCheckFailedReason = IDL.Variant({
'NotDiamondMember' : IDL.Null,
'PaymentFailed' : TransferFromError,
+ 'InsufficientBalance' : IDL.Nat,
'NoSnsNeuronsFound' : IDL.Null,
'NoSnsNeuronsWithRequiredDissolveDelayFound' : IDL.Null,
'NoSnsNeuronsWithRequiredStakeFound' : IDL.Null,
@@ -61,9 +106,8 @@ export const idlFactory = ({ IDL }) => {
'UserNotInCommunity' : IDL.Null,
'UserSuspended' : IDL.Null,
'CommunityFrozen' : IDL.Null,
+ 'InternalError' : IDL.Text,
});
- const MessageId = IDL.Nat;
- const MessageIndex = IDL.Nat32;
const AddReactionArgs = IDL.Record({
'channel_id' : ChannelId,
'username' : IDL.Text,
@@ -97,6 +141,20 @@ export const idlFactory = ({ IDL }) => {
'CannotBlockSelf' : IDL.Null,
'CannotBlockUser' : IDL.Null,
});
+ const CancelP2PSwapArgs = IDL.Record({
+ 'channel_id' : ChannelId,
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : IDL.Opt(MessageIndex),
+ });
+ const CancelP2PSwapResponse = IDL.Variant({
+ 'UserNotInChannel' : IDL.Null,
+ 'ChannelNotFound' : IDL.Null,
+ 'ChatFrozen' : IDL.Null,
+ 'Success' : IDL.Null,
+ 'UserNotInCommunity' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ });
const GroupRole = IDL.Variant({
'Participant' : IDL.Null,
'Admin' : IDL.Null,
@@ -197,6 +255,7 @@ export const idlFactory = ({ IDL }) => {
'crypto' : IDL.Opt(PermissionRole),
'giphy' : IDL.Opt(PermissionRole),
'default' : PermissionRole,
+ 'p2p_trade' : IDL.Opt(PermissionRole),
'image' : IDL.Opt(PermissionRole),
'prize' : IDL.Opt(PermissionRole),
'p2p_swap' : IDL.Opt(PermissionRole),
@@ -225,6 +284,10 @@ export const idlFactory = ({ IDL }) => {
'min_dissolve_delay' : IDL.Opt(Milliseconds),
'governance_canister_id' : CanisterId,
});
+ const TokenBalanceGate = IDL.Record({
+ 'min_balance' : IDL.Nat,
+ 'ledger_canister_id' : CanisterId,
+ });
const PaymentGate = IDL.Record({
'fee' : IDL.Nat,
'ledger_canister_id' : CanisterId,
@@ -233,6 +296,7 @@ export const idlFactory = ({ IDL }) => {
const AccessGate = IDL.Variant({
'VerifiedCredential' : VerifiedCredentialGate,
'SnsNeuron' : SnsNeuronGate,
+ 'TokenBalance' : TokenBalanceGate,
'DiamondMember' : IDL.Null,
'Payment' : PaymentGate,
});
@@ -315,13 +379,28 @@ export const idlFactory = ({ IDL }) => {
'config' : PollConfig,
});
const TextContent = IDL.Record({ 'text' : IDL.Text });
- const ImageContent = IDL.Record({
- 'height' : IDL.Nat32,
- 'mime_type' : IDL.Text,
- 'blob_reference' : IDL.Opt(BlobReference),
- 'thumbnail_data' : IDL.Text,
- 'caption' : IDL.Opt(IDL.Text),
- 'width' : IDL.Nat32,
+ const P2PSwapReserved = IDL.Record({ 'reserved_by' : UserId });
+ const P2PSwapAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const P2PSwapCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapExpired = P2PSwapCancelled;
+ const P2PSwapStatus = IDL.Variant({
+ 'Reserved' : P2PSwapReserved,
+ 'Open' : IDL.Null,
+ 'Accepted' : P2PSwapAccepted,
+ 'Cancelled' : P2PSwapCancelled,
+ 'Completed' : P2PSwapCompleted,
+ 'Expired' : P2PSwapExpired,
});
const Cryptocurrency = IDL.Variant({
'InternetComputer' : IDL.Null,
@@ -331,6 +410,31 @@ export const idlFactory = ({ IDL }) => {
'CKBTC' : IDL.Null,
'Other' : IDL.Text,
});
+ const TokenInfo = IDL.Record({
+ 'fee' : IDL.Nat,
+ 'decimals' : IDL.Nat8,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+ });
+ const P2PSwapContent = IDL.Record({
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : IDL.Nat64,
+ 'swap_id' : IDL.Nat32,
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_at' : TimestampMillis,
+ });
+ const ImageContent = IDL.Record({
+ 'height' : IDL.Nat32,
+ 'mime_type' : IDL.Text,
+ 'blob_reference' : IDL.Opt(BlobReference),
+ 'thumbnail_data' : IDL.Text,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'width' : IDL.Nat32,
+ });
const PrizeContent = IDL.Record({
'token' : Cryptocurrency,
'end_date' : TimestampMillis,
@@ -550,6 +654,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContent,
'Image' : ImageContent,
'Prize' : PrizeContent,
'Custom' : CustomMessageContent,
@@ -756,6 +861,7 @@ export const idlFactory = ({ IDL }) => {
'CommunityFrozen' : IDL.Null,
'NameTooLong' : FieldTooLongResult,
'NameTaken' : IDL.Null,
+ 'InternalError' : IDL.Text,
});
const CreateUserGroupArgs = IDL.Record({
'user_ids' : IDL.Vec(UserId),
@@ -829,7 +935,6 @@ export const idlFactory = ({ IDL }) => {
'Success' : IDL.Record({ 'content' : MessageContent }),
'UserNotInCommunity' : IDL.Null,
'MessageHardDeleted' : IDL.Null,
- 'MessageNotDeleted' : IDL.Null,
});
const EmptyArgs = IDL.Record({});
const DisableInviteCodeResponse = IDL.Variant({
@@ -838,6 +943,14 @@ export const idlFactory = ({ IDL }) => {
'UserSuspended' : IDL.Null,
'CommunityFrozen' : IDL.Null,
});
+ const P2PSwapContentInitial = IDL.Record({
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_in' : Milliseconds,
+ });
const PrizeContentInitial = IDL.Record({
'end_date' : TimestampMillis,
'caption' : IDL.Opt(IDL.Text),
@@ -850,6 +963,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContentInitial,
'Image' : ImageContent,
'Prize' : PrizeContentInitial,
'Custom' : CustomMessageContent,
@@ -1062,6 +1176,7 @@ export const idlFactory = ({ IDL }) => {
});
const ChannelMatch = IDL.Record({
'id' : ChannelId,
+ 'subtype' : IDL.Opt(GroupSubtype),
'gate' : IDL.Opt(AccessGate),
'name' : IDL.Text,
'description' : IDL.Text,
@@ -1772,6 +1887,11 @@ export const idlFactory = ({ IDL }) => {
'NameTaken' : IDL.Null,
});
return IDL.Service({
+ 'accept_p2p_swap' : IDL.Func(
+ [AcceptP2PSwapArgs],
+ [AcceptP2PSwapResponse],
+ [],
+ ),
'add_members_to_channel' : IDL.Func(
[AddMembersToChannelArgs],
[AddMembersToChannelResponse],
@@ -1779,6 +1899,11 @@ export const idlFactory = ({ IDL }) => {
),
'add_reaction' : IDL.Func([AddReactionArgs], [AddReactionResponse], []),
'block_user' : IDL.Func([BlockUserArgs], [BlockUserResponse], []),
+ 'cancel_p2p_swap' : IDL.Func(
+ [CancelP2PSwapArgs],
+ [CancelP2PSwapResponse],
+ [],
+ ),
'change_channel_role' : IDL.Func(
[ChangeChannelRoleArgs],
[ChangeChannelRoleResponse],
diff --git a/frontend/openchat-agent/src/services/community/candid/types.d.ts b/frontend/openchat-agent/src/services/community/candid/types.d.ts
index 6209c08af9..30096fcb5e 100644
--- a/frontend/openchat-agent/src/services/community/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/community/candid/types.d.ts
@@ -1,8 +1,25 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptP2PSwapArgs {
+ 'channel_id' : ChannelId,
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : [] | [MessageIndex],
+}
+export type AcceptP2PSwapResponse = { 'UserNotInChannel' : null } |
+ { 'ChannelNotFound' : null } |
+ { 'ChatFrozen' : null } |
+ { 'Success' : AcceptSwapSuccess } |
+ { 'UserNotInCommunity' : null } |
+ { 'UserSuspended' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null } |
+ { 'InternalError' : string } |
+ { 'InsufficientFunds' : null };
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -44,7 +61,8 @@ export type AddMembersToChannelResponse = {
{ 'Success' : null } |
{ 'UserNotInCommunity' : null } |
{ 'UserSuspended' : null } |
- { 'CommunityFrozen' : null };
+ { 'CommunityFrozen' : null } |
+ { 'InternalError' : string };
export interface AddReactionArgs {
'channel_id' : ChannelId,
'username' : string,
@@ -131,6 +149,18 @@ export interface BuildVersion {
'minor' : number,
'patch' : number,
}
+export interface CancelP2PSwapArgs {
+ 'channel_id' : ChannelId,
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : [] | [MessageIndex],
+}
+export type CancelP2PSwapResponse = { 'UserNotInChannel' : null } |
+ { 'ChannelNotFound' : null } |
+ { 'ChatFrozen' : null } |
+ { 'Success' : null } |
+ { 'UserNotInCommunity' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null };
export type CanisterId = Principal;
export type CanisterUpgradeStatus = { 'NotRequired' : null } |
{ 'InProgress' : null };
@@ -168,6 +198,7 @@ export type ChangeRoleResponse = { 'Invalid' : null } |
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -484,7 +515,8 @@ export type CreateChannelResponse = { 'MaxChannelsCreated' : number } |
{ 'RulesTooShort' : FieldTooShortResult } |
{ 'CommunityFrozen' : null } |
{ 'NameTooLong' : FieldTooLongResult } |
- { 'NameTaken' : null };
+ { 'NameTaken' : null } |
+ { 'InternalError' : string };
export interface CreateUserGroupArgs {
'user_ids' : Array,
'name' : string,
@@ -575,8 +607,7 @@ export type DeletedMessageResponse = { 'UserNotInChannel' : null } |
{ 'NotAuthorized' : null } |
{ 'Success' : { 'content' : MessageContent } } |
{ 'UserNotInCommunity' : null } |
- { 'MessageHardDeleted' : null } |
- { 'MessageNotDeleted' : null };
+ { 'MessageHardDeleted' : null };
export interface DiamondMembershipDetails {
'pay_in_chat' : boolean,
'subscription' : DiamondMembershipSubscription,
@@ -792,6 +823,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -939,6 +971,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -1154,6 +1187,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -1169,6 +1203,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -1208,6 +1243,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1374,6 +1410,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1620,6 +1694,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1807,6 +1889,24 @@ export type SummaryUpdatesResponse = {
} |
{ 'SuccessNoUpdates' : null } |
{ 'PrivateCommunity' : null };
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1867,6 +1967,16 @@ export type ToggleMuteNotificationsResponse = { 'UserNotInChannel' : null } |
{ 'UserNotInCommunity' : null } |
{ 'UserSuspended' : null } |
{ 'CommunityFrozen' : null };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
@@ -2080,12 +2190,14 @@ export interface VideoContent {
export type VoteOperation = { 'RegisterVote' : null } |
{ 'DeleteVote' : null };
export interface _SERVICE {
+ 'accept_p2p_swap' : ActorMethod<[AcceptP2PSwapArgs], AcceptP2PSwapResponse>,
'add_members_to_channel' : ActorMethod<
[AddMembersToChannelArgs],
AddMembersToChannelResponse
>,
'add_reaction' : ActorMethod<[AddReactionArgs], AddReactionResponse>,
'block_user' : ActorMethod<[BlockUserArgs], BlockUserResponse>,
+ 'cancel_p2p_swap' : ActorMethod<[CancelP2PSwapArgs], CancelP2PSwapResponse>,
'change_channel_role' : ActorMethod<
[ChangeChannelRoleArgs],
ChangeChannelRoleResponse
diff --git a/frontend/openchat-agent/src/services/community/community.client.ts b/frontend/openchat-agent/src/services/community/community.client.ts
index f8645f42b6..342fec614c 100644
--- a/frontend/openchat-agent/src/services/community/community.client.ts
+++ b/frontend/openchat-agent/src/services/community/community.client.ts
@@ -63,6 +63,8 @@ import {
enableInviteCodeResponse,
registerProposalVoteResponse,
claimPrizeResponse,
+ acceptP2PSwapResponse,
+ cancelP2PSwapResponse,
} from "../common/chatMappers";
import type {
AccessGate,
@@ -128,6 +130,7 @@ import type {
OptionUpdate,
ClaimPrizeResponse,
OptionalChatPermissions,
+ AcceptP2PSwapResponse,
} from "openchat-shared";
import {
textToCode,
@@ -162,6 +165,7 @@ import {
} from "../../utils/caching";
import { mergeCommunityDetails, mergeGroupChatDetails } from "../../utils/chat";
import { muteNotificationsResponse } from "../notifications/mappers";
+import type { CancelP2PSwapResponse } from "openchat-shared";
export class CommunityClient extends CandidService {
private service: CommunityService;
@@ -1267,4 +1271,26 @@ export class CommunityClient extends CandidService {
reportMessageResponse,
);
}
+
+ acceptP2PSwap(channelId: string, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.service.accept_p2p_swap({
+ channel_id: BigInt(channelId),
+ thread_root_message_index: apiOptional(identity, threadRootMessageIndex),
+ message_id: messageId
+ }),
+ acceptP2PSwapResponse,
+ );
+ }
+
+ cancelP2PSwap(channelId: string, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.service.cancel_p2p_swap({
+ channel_id: BigInt(channelId),
+ thread_root_message_index: apiOptional(identity, threadRootMessageIndex),
+ message_id: messageId
+ }),
+ cancelP2PSwapResponse,
+ );
+ }
}
diff --git a/frontend/openchat-agent/src/services/community/mappers.ts b/frontend/openchat-agent/src/services/community/mappers.ts
index 84b4188348..088cbd42a5 100644
--- a/frontend/openchat-agent/src/services/community/mappers.ts
+++ b/frontend/openchat-agent/src/services/community/mappers.ts
@@ -133,6 +133,9 @@ export function addMembersToChannelResponse(
if ("CommunityFrozen" in candid) {
return CommonResponses.communityFrozen();
}
+ if ("InternalError" in candid) {
+ return CommonResponses.internalError();
+ }
throw new UnsupportedValueError(
"Unexpected ApiAddMembersToChannelResponse type received",
candid,
@@ -180,6 +183,10 @@ function failedGateCheckReason(candid: ApiGateCheckFailedReason): GateCheckFaile
console.warn("PaymentFailed: ", candid);
return "payment_failed";
}
+ if ("InsufficientBalance" in candid) {
+ return "insufficient_balance";
+ }
+
throw new UnsupportedValueError("Unexpected ApiGateCheckFailedReason type received", candid);
}
@@ -738,4 +745,4 @@ export function followThreadResponse(
export function reportMessageResponse(candid: ReportMessageResponse): boolean {
return "Success" in candid || "AlreadyReported" in candid;
-}
+}
\ No newline at end of file
diff --git a/frontend/openchat-agent/src/services/group/candid/idl.d.ts b/frontend/openchat-agent/src/services/group/candid/idl.d.ts
index caedac404d..72b0edc351 100644
--- a/frontend/openchat-agent/src/services/group/candid/idl.d.ts
+++ b/frontend/openchat-agent/src/services/group/candid/idl.d.ts
@@ -67,7 +67,9 @@ import {
ConvertIntoCommunityResponse,
FollowThreadResponse,
UnfollowThreadResponse,
- OptionalMessagePermissions
+ OptionalMessagePermissions,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse
} from "./types";
export {
_SERVICE as GroupService,
@@ -138,6 +140,8 @@ export {
FollowThreadResponse as ApiFollowThreadResponse,
UnfollowThreadResponse as ApiUnfollowThreadResponse,
OptionalMessagePermissions as ApiOptionalMessagePermissions,
+ AcceptP2PSwapResponse as ApiAcceptP2PSwapResponse,
+ CancelP2PSwapResponse as ApiCancelP2PSwapResponse
};
export const idlFactory: IDL.InterfaceFactory;
diff --git a/frontend/openchat-agent/src/services/group/candid/idl.js b/frontend/openchat-agent/src/services/group/candid/idl.js
index 907b4ffb04..76b7c9c0d2 100644
--- a/frontend/openchat-agent/src/services/group/candid/idl.js
+++ b/frontend/openchat-agent/src/services/group/candid/idl.js
@@ -1,6 +1,47 @@
export const idlFactory = ({ IDL }) => {
const MessageId = IDL.Nat;
const MessageIndex = IDL.Nat32;
+ const AcceptP2PSwapArgs = IDL.Record({
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : IDL.Opt(MessageIndex),
+ });
+ const AcceptSwapSuccess = IDL.Record({ 'token1_txn_in' : IDL.Nat64 });
+ const CanisterId = IDL.Principal;
+ const UserId = CanisterId;
+ const SwapStatusErrorReserved = IDL.Record({ 'reserved_by' : UserId });
+ const SwapStatusErrorAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusErrorCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorExpired = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusError = IDL.Variant({
+ 'Reserved' : SwapStatusErrorReserved,
+ 'Accepted' : SwapStatusErrorAccepted,
+ 'Cancelled' : SwapStatusErrorCancelled,
+ 'Completed' : SwapStatusErrorCompleted,
+ 'Expired' : SwapStatusErrorExpired,
+ });
+ const AcceptP2PSwapResponse = IDL.Variant({
+ 'UserNotInGroup' : IDL.Null,
+ 'ChatFrozen' : IDL.Null,
+ 'Success' : AcceptSwapSuccess,
+ 'UserSuspended' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ 'InternalError' : IDL.Text,
+ 'InsufficientFunds' : IDL.Null,
+ });
const AddReactionArgs = IDL.Record({
'username' : IDL.Text,
'display_name' : IDL.Opt(IDL.Text),
@@ -19,8 +60,6 @@ export const idlFactory = ({ IDL }) => {
'UserSuspended' : IDL.Null,
'InvalidReaction' : IDL.Null,
});
- const CanisterId = IDL.Principal;
- const UserId = CanisterId;
const BlockUserArgs = IDL.Record({
'user_id' : UserId,
'correlation_id' : IDL.Nat64,
@@ -37,6 +76,17 @@ export const idlFactory = ({ IDL }) => {
'CannotBlockSelf' : IDL.Null,
'CannotBlockUser' : IDL.Null,
});
+ const CancelP2PSwapArgs = IDL.Record({
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : IDL.Opt(MessageIndex),
+ });
+ const CancelP2PSwapResponse = IDL.Variant({
+ 'UserNotInGroup' : IDL.Null,
+ 'ChatFrozen' : IDL.Null,
+ 'Success' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ });
const GroupRole = IDL.Variant({
'Participant' : IDL.Null,
'Admin' : IDL.Null,
@@ -271,6 +321,46 @@ export const idlFactory = ({ IDL }) => {
'config' : PollConfig,
});
const TextContent = IDL.Record({ 'text' : IDL.Text });
+ const P2PSwapReserved = IDL.Record({ 'reserved_by' : UserId });
+ const P2PSwapAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const P2PSwapCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapExpired = P2PSwapCancelled;
+ const P2PSwapStatus = IDL.Variant({
+ 'Reserved' : P2PSwapReserved,
+ 'Open' : IDL.Null,
+ 'Accepted' : P2PSwapAccepted,
+ 'Cancelled' : P2PSwapCancelled,
+ 'Completed' : P2PSwapCompleted,
+ 'Expired' : P2PSwapExpired,
+ });
+ const TokenInfo = IDL.Record({
+ 'fee' : IDL.Nat,
+ 'decimals' : IDL.Nat8,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+ });
+ const P2PSwapContent = IDL.Record({
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : IDL.Nat64,
+ 'swap_id' : IDL.Nat32,
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_at' : TimestampMillis,
+ });
const ImageContent = IDL.Record({
'height' : IDL.Nat32,
'mime_type' : IDL.Text,
@@ -426,6 +516,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContent,
'Image' : ImageContent,
'Prize' : PrizeContent,
'Custom' : CustomMessageContent,
@@ -444,7 +535,6 @@ export const idlFactory = ({ IDL }) => {
'NotAuthorized' : IDL.Null,
'Success' : IDL.Record({ 'content' : MessageContent }),
'MessageHardDeleted' : IDL.Null,
- 'MessageNotDeleted' : IDL.Null,
});
const DisableInviteCodeArgs = IDL.Record({ 'correlation_id' : IDL.Nat64 });
const DisableInviteCodeResponse = IDL.Variant({
@@ -453,6 +543,15 @@ export const idlFactory = ({ IDL }) => {
'Success' : IDL.Null,
'UserSuspended' : IDL.Null,
});
+ const Milliseconds = IDL.Nat64;
+ const P2PSwapContentInitial = IDL.Record({
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_in' : Milliseconds,
+ });
const PrizeContentInitial = IDL.Record({
'end_date' : TimestampMillis,
'caption' : IDL.Opt(IDL.Text),
@@ -465,6 +564,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContentInitial,
'Image' : ImageContent,
'Prize' : PrizeContentInitial,
'Custom' : CustomMessageContent,
@@ -597,6 +697,7 @@ export const idlFactory = ({ IDL }) => {
'crypto' : IDL.Opt(PermissionRole),
'giphy' : IDL.Opt(PermissionRole),
'default' : PermissionRole,
+ 'p2p_trade' : IDL.Opt(PermissionRole),
'image' : IDL.Opt(PermissionRole),
'prize' : IDL.Opt(PermissionRole),
'p2p_swap' : IDL.Opt(PermissionRole),
@@ -653,12 +754,15 @@ export const idlFactory = ({ IDL }) => {
'credential' : IDL.Text,
'issuer' : IDL.Text,
});
- const Milliseconds = IDL.Nat64;
const SnsNeuronGate = IDL.Record({
'min_stake_e8s' : IDL.Opt(IDL.Nat64),
'min_dissolve_delay' : IDL.Opt(Milliseconds),
'governance_canister_id' : CanisterId,
});
+ const TokenBalanceGate = IDL.Record({
+ 'min_balance' : IDL.Nat,
+ 'ledger_canister_id' : CanisterId,
+ });
const PaymentGate = IDL.Record({
'fee' : IDL.Nat,
'ledger_canister_id' : CanisterId,
@@ -667,6 +771,7 @@ export const idlFactory = ({ IDL }) => {
const AccessGate = IDL.Variant({
'VerifiedCredential' : VerifiedCredentialGate,
'SnsNeuron' : SnsNeuronGate,
+ 'TokenBalance' : TokenBalanceGate,
'DiamondMember' : IDL.Null,
'Payment' : PaymentGate,
});
@@ -1390,8 +1495,18 @@ export const idlFactory = ({ IDL }) => {
'InternalError' : IDL.Null,
});
return IDL.Service({
+ 'accept_p2p_swap' : IDL.Func(
+ [AcceptP2PSwapArgs],
+ [AcceptP2PSwapResponse],
+ [],
+ ),
'add_reaction' : IDL.Func([AddReactionArgs], [AddReactionResponse], []),
'block_user' : IDL.Func([BlockUserArgs], [BlockUserResponse], []),
+ 'cancel_p2p_swap' : IDL.Func(
+ [CancelP2PSwapArgs],
+ [CancelP2PSwapResponse],
+ [],
+ ),
'change_role' : IDL.Func([ChangeRoleArgs], [ChangeRoleResponse], []),
'claim_prize' : IDL.Func([ClaimPrizeArgs], [ClaimPrizeResponse], []),
'convert_into_community' : IDL.Func(
diff --git a/frontend/openchat-agent/src/services/group/candid/types.d.ts b/frontend/openchat-agent/src/services/group/candid/types.d.ts
index 5c98c29072..674bca70ab 100644
--- a/frontend/openchat-agent/src/services/group/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/group/candid/types.d.ts
@@ -1,8 +1,22 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptP2PSwapArgs {
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : [] | [MessageIndex],
+}
+export type AcceptP2PSwapResponse = { 'UserNotInGroup' : null } |
+ { 'ChatFrozen' : null } |
+ { 'Success' : AcceptSwapSuccess } |
+ { 'UserSuspended' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null } |
+ { 'InternalError' : string } |
+ { 'InsufficientFunds' : null };
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -98,6 +112,15 @@ export interface BuildVersion {
'minor' : number,
'patch' : number,
}
+export interface CancelP2PSwapArgs {
+ 'message_id' : MessageId,
+ 'thread_root_message_index' : [] | [MessageIndex],
+}
+export type CancelP2PSwapResponse = { 'UserNotInGroup' : null } |
+ { 'ChatFrozen' : null } |
+ { 'Success' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null };
export type CanisterId = Principal;
export type CanisterUpgradeStatus = { 'NotRequired' : null } |
{ 'InProgress' : null };
@@ -122,6 +145,7 @@ export type ChangeRoleResponse = { 'Invalid' : null } |
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -461,8 +485,7 @@ export type DeletedMessageResponse = { 'MessageNotFound' : null } |
{ 'CallerNotInGroup' : null } |
{ 'NotAuthorized' : null } |
{ 'Success' : { 'content' : MessageContent } } |
- { 'MessageHardDeleted' : null } |
- { 'MessageNotDeleted' : null };
+ { 'MessageHardDeleted' : null };
export interface DiamondMembershipDetails {
'pay_in_chat' : boolean,
'subscription' : DiamondMembershipSubscription,
@@ -657,6 +680,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -804,6 +828,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -1000,6 +1025,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -1015,6 +1041,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -1054,6 +1081,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1219,6 +1247,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1445,6 +1511,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface ResetInviteCodeArgs { 'correlation_id' : bigint }
export type ResetInviteCodeResponse = { 'ChatFrozen' : null } |
{ 'NotAuthorized' : null } |
@@ -1572,6 +1646,24 @@ export interface SummaryUpdatesArgs { 'updates_since' : TimestampMillis }
export type SummaryUpdatesResponse = { 'CallerNotInGroup' : null } |
{ 'Success' : { 'updates' : GroupCanisterGroupChatSummaryUpdates } } |
{ 'SuccessNoUpdates' : null };
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1624,6 +1716,16 @@ export type TimestampUpdate = { 'NoChange' : null } |
export interface ToggleMuteNotificationsArgs { 'mute' : boolean }
export type ToggleMuteNotificationsResponse = { 'CallerNotInGroup' : null } |
{ 'Success' : null };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
@@ -1793,8 +1895,10 @@ export interface VideoContent {
export type VoteOperation = { 'RegisterVote' : null } |
{ 'DeleteVote' : null };
export interface _SERVICE {
+ 'accept_p2p_swap' : ActorMethod<[AcceptP2PSwapArgs], AcceptP2PSwapResponse>,
'add_reaction' : ActorMethod<[AddReactionArgs], AddReactionResponse>,
'block_user' : ActorMethod<[BlockUserArgs], BlockUserResponse>,
+ 'cancel_p2p_swap' : ActorMethod<[CancelP2PSwapArgs], CancelP2PSwapResponse>,
'change_role' : ActorMethod<[ChangeRoleArgs], ChangeRoleResponse>,
'claim_prize' : ActorMethod<[ClaimPrizeArgs], ClaimPrizeResponse>,
'convert_into_community' : ActorMethod<
diff --git a/frontend/openchat-agent/src/services/group/group.client.ts b/frontend/openchat-agent/src/services/group/group.client.ts
index 5d25c2ab4d..c01235f547 100644
--- a/frontend/openchat-agent/src/services/group/group.client.ts
+++ b/frontend/openchat-agent/src/services/group/group.client.ts
@@ -47,6 +47,7 @@ import type {
FollowThreadResponse,
OptionalChatPermissions,
ToggleMuteNotificationResponse,
+ AcceptP2PSwapResponse,
} from "openchat-shared";
import {
DestinationInvalidError,
@@ -113,6 +114,8 @@ import {
groupDetailsUpdatesResponse,
registerProposalVoteResponse,
claimPrizeResponse,
+ acceptP2PSwapResponse,
+ cancelP2PSwapResponse,
} from "../common/chatMappers";
import { DataClient } from "../data/data.client";
import { mergeGroupChatDetails } from "../../utils/chat";
@@ -122,6 +125,7 @@ import { generateUint64 } from "../../utils/rng";
import type { AgentConfig } from "../../config";
import { setCachedMessageFromSendResponse } from "../../utils/caching";
import { muteNotificationsResponse } from "../notifications/mappers";
+import type { CancelP2PSwapResponse } from "openchat-shared";
export class GroupClient extends CandidService {
private groupService: GroupService;
@@ -910,4 +914,24 @@ export class GroupClient extends CandidService {
reportMessageResponse,
);
}
+
+ acceptP2PSwap(threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.groupService.accept_p2p_swap({
+ thread_root_message_index: apiOptional(identity, threadRootMessageIndex),
+ message_id: messageId
+ }),
+ acceptP2PSwapResponse,
+ );
+ }
+
+ cancelP2PSwap(threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.groupService.cancel_p2p_swap({
+ thread_root_message_index: apiOptional(identity, threadRootMessageIndex),
+ message_id: messageId
+ }),
+ cancelP2PSwapResponse,
+ );
+ }
}
diff --git a/frontend/openchat-agent/src/services/group/mappers.ts b/frontend/openchat-agent/src/services/group/mappers.ts
index fc9e95d962..5bb65a7abb 100644
--- a/frontend/openchat-agent/src/services/group/mappers.ts
+++ b/frontend/openchat-agent/src/services/group/mappers.ts
@@ -244,8 +244,8 @@ function apiOptionalMessagePermissions(
crypto: apiOptionUpdate(apiPermissionRole, permissions.crypto),
giphy: apiOptionUpdate(apiPermissionRole, permissions.giphy),
prize: apiOptionUpdate(apiPermissionRole, permissions.prize),
+ p2p_swap: apiOptionUpdate(apiPermissionRole, permissions.p2pSwap),
p2p_trade: apiOptionUpdate(apiPermissionRole, undefined),
- p2p_swap: apiOptionUpdate(apiPermissionRole, undefined),
custom_updated,
custom_deleted,
};
@@ -684,4 +684,4 @@ export function followThreadResponse(
export function reportMessageResponse(candid: ReportMessageResponse): boolean {
return "Success" in candid || "AlreadyReported" in candid;
-}
+}
\ No newline at end of file
diff --git a/frontend/openchat-agent/src/services/groupIndex/candid/idl.js b/frontend/openchat-agent/src/services/groupIndex/candid/idl.js
index ec40cfd4a8..32fe9c1f48 100644
--- a/frontend/openchat-agent/src/services/groupIndex/candid/idl.js
+++ b/frontend/openchat-agent/src/services/groupIndex/candid/idl.js
@@ -66,6 +66,10 @@ export const idlFactory = ({ IDL }) => {
'min_dissolve_delay' : IDL.Opt(Milliseconds),
'governance_canister_id' : CanisterId,
});
+ const TokenBalanceGate = IDL.Record({
+ 'min_balance' : IDL.Nat,
+ 'ledger_canister_id' : CanisterId,
+ });
const PaymentGate = IDL.Record({
'fee' : IDL.Nat,
'ledger_canister_id' : CanisterId,
@@ -74,6 +78,7 @@ export const idlFactory = ({ IDL }) => {
const AccessGate = IDL.Variant({
'VerifiedCredential' : VerifiedCredentialGate,
'SnsNeuron' : SnsNeuronGate,
+ 'TokenBalance' : TokenBalanceGate,
'DiamondMember' : IDL.Null,
'Payment' : PaymentGate,
});
@@ -106,8 +111,16 @@ export const idlFactory = ({ IDL }) => {
'page_index' : IDL.Nat32,
'search_term' : IDL.Opt(IDL.Text),
});
+ const GovernanceProposalsSubtype = IDL.Record({
+ 'is_nns' : IDL.Bool,
+ 'governance_canister_id' : CanisterId,
+ });
+ const GroupSubtype = IDL.Variant({
+ 'GovernanceProposals' : GovernanceProposalsSubtype,
+ });
const GroupMatch = IDL.Record({
'id' : ChatId,
+ 'subtype' : IDL.Opt(GroupSubtype),
'gate' : IDL.Opt(AccessGate),
'name' : IDL.Text,
'description' : IDL.Text,
@@ -182,13 +195,6 @@ export const idlFactory = ({ IDL }) => {
'count' : IDL.Nat8,
'exclusions' : IDL.Vec(ChatId),
});
- const GovernanceProposalsSubtype = IDL.Record({
- 'is_nns' : IDL.Bool,
- 'governance_canister_id' : CanisterId,
- });
- const GroupSubtype = IDL.Variant({
- 'GovernanceProposals' : GovernanceProposalsSubtype,
- });
const BuildVersion = IDL.Record({
'major' : IDL.Nat32,
'minor' : IDL.Nat32,
@@ -257,13 +263,28 @@ export const idlFactory = ({ IDL }) => {
'config' : PollConfig,
});
const TextContent = IDL.Record({ 'text' : IDL.Text });
- const ImageContent = IDL.Record({
- 'height' : IDL.Nat32,
- 'mime_type' : IDL.Text,
- 'blob_reference' : IDL.Opt(BlobReference),
- 'thumbnail_data' : IDL.Text,
- 'caption' : IDL.Opt(IDL.Text),
- 'width' : IDL.Nat32,
+ const P2PSwapReserved = IDL.Record({ 'reserved_by' : UserId });
+ const P2PSwapAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const P2PSwapCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapExpired = P2PSwapCancelled;
+ const P2PSwapStatus = IDL.Variant({
+ 'Reserved' : P2PSwapReserved,
+ 'Open' : IDL.Null,
+ 'Accepted' : P2PSwapAccepted,
+ 'Cancelled' : P2PSwapCancelled,
+ 'Completed' : P2PSwapCompleted,
+ 'Expired' : P2PSwapExpired,
});
const Cryptocurrency = IDL.Variant({
'InternetComputer' : IDL.Null,
@@ -273,6 +294,31 @@ export const idlFactory = ({ IDL }) => {
'CKBTC' : IDL.Null,
'Other' : IDL.Text,
});
+ const TokenInfo = IDL.Record({
+ 'fee' : IDL.Nat,
+ 'decimals' : IDL.Nat8,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+ });
+ const P2PSwapContent = IDL.Record({
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : IDL.Nat64,
+ 'swap_id' : IDL.Nat32,
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_at' : TimestampMillis,
+ });
+ const ImageContent = IDL.Record({
+ 'height' : IDL.Nat32,
+ 'mime_type' : IDL.Text,
+ 'blob_reference' : IDL.Opt(BlobReference),
+ 'thumbnail_data' : IDL.Text,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'width' : IDL.Nat32,
+ });
const PrizeContent = IDL.Record({
'token' : Cryptocurrency,
'end_date' : TimestampMillis,
@@ -492,6 +538,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContent,
'Image' : ImageContent,
'Prize' : PrizeContent,
'Custom' : CustomMessageContent,
diff --git a/frontend/openchat-agent/src/services/groupIndex/candid/types.d.ts b/frontend/openchat-agent/src/services/groupIndex/candid/types.d.ts
index 92f1bf826d..34773cf5c4 100644
--- a/frontend/openchat-agent/src/services/groupIndex/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/groupIndex/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -102,6 +104,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -621,6 +624,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -768,6 +772,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -962,6 +967,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -977,6 +983,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -1016,6 +1023,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1170,6 +1178,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1318,6 +1364,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1394,6 +1448,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1429,6 +1501,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/localUserIndex/candid/idl.js b/frontend/openchat-agent/src/services/localUserIndex/candid/idl.js
index f7725f92fb..3a4a364182 100644
--- a/frontend/openchat-agent/src/services/localUserIndex/candid/idl.js
+++ b/frontend/openchat-agent/src/services/localUserIndex/candid/idl.js
@@ -128,13 +128,28 @@ export const idlFactory = ({ IDL }) => {
'config' : PollConfig,
});
const TextContent = IDL.Record({ 'text' : IDL.Text });
- const ImageContent = IDL.Record({
- 'height' : IDL.Nat32,
- 'mime_type' : IDL.Text,
- 'blob_reference' : IDL.Opt(BlobReference),
- 'thumbnail_data' : IDL.Text,
- 'caption' : IDL.Opt(IDL.Text),
- 'width' : IDL.Nat32,
+ const P2PSwapReserved = IDL.Record({ 'reserved_by' : UserId });
+ const P2PSwapAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const P2PSwapCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapExpired = P2PSwapCancelled;
+ const P2PSwapStatus = IDL.Variant({
+ 'Reserved' : P2PSwapReserved,
+ 'Open' : IDL.Null,
+ 'Accepted' : P2PSwapAccepted,
+ 'Cancelled' : P2PSwapCancelled,
+ 'Completed' : P2PSwapCompleted,
+ 'Expired' : P2PSwapExpired,
});
const Cryptocurrency = IDL.Variant({
'InternetComputer' : IDL.Null,
@@ -144,6 +159,31 @@ export const idlFactory = ({ IDL }) => {
'CKBTC' : IDL.Null,
'Other' : IDL.Text,
});
+ const TokenInfo = IDL.Record({
+ 'fee' : IDL.Nat,
+ 'decimals' : IDL.Nat8,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+ });
+ const P2PSwapContent = IDL.Record({
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : IDL.Nat64,
+ 'swap_id' : IDL.Nat32,
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_at' : TimestampMillis,
+ });
+ const ImageContent = IDL.Record({
+ 'height' : IDL.Nat32,
+ 'mime_type' : IDL.Text,
+ 'blob_reference' : IDL.Opt(BlobReference),
+ 'thumbnail_data' : IDL.Text,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'width' : IDL.Nat32,
+ });
const PrizeContent = IDL.Record({
'token' : Cryptocurrency,
'end_date' : TimestampMillis,
@@ -363,6 +403,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContent,
'Image' : ImageContent,
'Prize' : PrizeContent,
'Custom' : CustomMessageContent,
@@ -428,6 +469,7 @@ export const idlFactory = ({ IDL }) => {
'crypto' : IDL.Opt(PermissionRole),
'giphy' : IDL.Opt(PermissionRole),
'default' : PermissionRole,
+ 'p2p_trade' : IDL.Opt(PermissionRole),
'image' : IDL.Opt(PermissionRole),
'prize' : IDL.Opt(PermissionRole),
'p2p_swap' : IDL.Opt(PermissionRole),
@@ -490,6 +532,10 @@ export const idlFactory = ({ IDL }) => {
'min_dissolve_delay' : IDL.Opt(Milliseconds),
'governance_canister_id' : CanisterId,
});
+ const TokenBalanceGate = IDL.Record({
+ 'min_balance' : IDL.Nat,
+ 'ledger_canister_id' : CanisterId,
+ });
const PaymentGate = IDL.Record({
'fee' : IDL.Nat,
'ledger_canister_id' : CanisterId,
@@ -498,6 +544,7 @@ export const idlFactory = ({ IDL }) => {
const AccessGate = IDL.Variant({
'VerifiedCredential' : VerifiedCredentialGate,
'SnsNeuron' : SnsNeuronGate,
+ 'TokenBalance' : TokenBalanceGate,
'DiamondMember' : IDL.Null,
'Payment' : PaymentGate,
});
@@ -978,6 +1025,7 @@ export const idlFactory = ({ IDL }) => {
const GateCheckFailedReason = IDL.Variant({
'NotDiamondMember' : IDL.Null,
'PaymentFailed' : TransferFromError,
+ 'InsufficientBalance' : IDL.Nat,
'NoSnsNeuronsFound' : IDL.Null,
'NoSnsNeuronsWithRequiredDissolveDelayFound' : IDL.Null,
'NoSnsNeuronsWithRequiredStakeFound' : IDL.Null,
diff --git a/frontend/openchat-agent/src/services/localUserIndex/candid/types.d.ts b/frontend/openchat-agent/src/services/localUserIndex/candid/types.d.ts
index 29faf63c42..7e2871f292 100644
--- a/frontend/openchat-agent/src/services/localUserIndex/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/localUserIndex/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -82,6 +84,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -541,6 +544,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -694,6 +698,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -980,6 +985,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -995,6 +1001,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -1034,6 +1041,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1188,6 +1196,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1349,6 +1395,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1415,6 +1469,24 @@ export type SummaryUpdatesResponse = {
{ 'SuccessGroup' : GroupCanisterGroupChatSummary } |
{ 'SuccessNoUpdates' : null } |
{ 'InternalError' : string };
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1450,6 +1522,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/notifications/candid/types.d.ts b/frontend/openchat-agent/src/services/notifications/candid/types.d.ts
index 9ba83fa7ae..e8bd88d364 100644
--- a/frontend/openchat-agent/src/services/notifications/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/notifications/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -82,6 +84,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -511,6 +514,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -658,6 +662,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -849,6 +854,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -864,6 +870,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -903,6 +910,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1057,6 +1065,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1199,6 +1245,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1253,6 +1307,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1288,6 +1360,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/online/candid/types.d.ts b/frontend/openchat-agent/src/services/online/candid/types.d.ts
index 48e0afc384..28363b7956 100644
--- a/frontend/openchat-agent/src/services/online/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/online/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -82,6 +84,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -511,6 +514,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -658,6 +662,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -859,6 +864,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -874,6 +880,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -913,6 +920,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1067,6 +1075,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1202,6 +1248,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1253,6 +1307,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1288,6 +1360,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/openchatAgent.ts b/frontend/openchat-agent/src/services/openchatAgent.ts
index bca61e35ee..9d8cf1f886 100644
--- a/frontend/openchat-agent/src/services/openchatAgent.ts
+++ b/frontend/openchat-agent/src/services/openchatAgent.ts
@@ -189,6 +189,8 @@ import type {
CommunityCanisterCommunitySummaryUpdates,
TranslationCorrections,
TranslationCorrection,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "openchat-shared";
import {
UnsupportedValueError,
@@ -402,11 +404,12 @@ export class OpenChatAgent extends EventTarget {
if (chatId.kind === "channel") {
if (
event.event.content.kind === "crypto_content" ||
- event.event.content.kind === "prize_content_initial"
+ event.event.content.kind === "prize_content_initial" ||
+ event.event.content.kind === "p2p_swap_content_initial"
) {
return this.userClient.sendMessageWithTransferToChannel(
chatId,
- event.event.content.transfer.recipient,
+ event.event.content.kind !== "p2p_swap_content_initial" ? event.event.content.transfer.recipient : undefined,
user,
event,
threadRootMessageIndex,
@@ -430,11 +433,12 @@ export class OpenChatAgent extends EventTarget {
if (chatId.kind === "group_chat") {
if (
event.event.content.kind === "crypto_content" ||
- event.event.content.kind === "prize_content_initial"
+ event.event.content.kind === "prize_content_initial" ||
+ event.event.content.kind === "p2p_swap_content_initial"
) {
return this.userClient.sendMessageWithTransferToGroup(
chatId,
- event.event.content.transfer.recipient,
+ event.event.content.kind !== "p2p_swap_content_initial" ? event.event.content.transfer.recipient : undefined,
user,
event,
threadRootMessageIndex,
@@ -3108,4 +3112,38 @@ export class OpenChatAgent extends EventTarget {
reportedMessages(userId: string | undefined): Promise {
return this._userIndexClient.reportedMessages(userId);
}
+
+ acceptP2PSwap(chatId: ChatIdentifier, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ if (chatId.kind === "channel") {
+ return this.communityClient(chatId.communityId).acceptP2PSwap(
+ chatId.channelId,
+ threadRootMessageIndex,
+ messageId,
+ );
+ } else if (chatId.kind === "group_chat") {
+ return this.getGroupClient(chatId.groupId).acceptP2PSwap(
+ threadRootMessageIndex,
+ messageId,
+ );
+ } else {
+ return this.userClient.acceptP2PSwap(chatId.userId, messageId);
+ }
+ }
+
+ cancelP2PSwap(chatId: ChatIdentifier, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ if (chatId.kind === "channel") {
+ return this.communityClient(chatId.communityId).cancelP2PSwap(
+ chatId.channelId,
+ threadRootMessageIndex,
+ messageId,
+ );
+ } else if (chatId.kind === "group_chat") {
+ return this.getGroupClient(chatId.groupId).cancelP2PSwap(
+ threadRootMessageIndex,
+ messageId,
+ );
+ } else {
+ return this.userClient.cancelP2PSwap(chatId.userId, messageId);
+ }
+ }
}
diff --git a/frontend/openchat-agent/src/services/proposalsBot/candid/types.d.ts b/frontend/openchat-agent/src/services/proposalsBot/candid/types.d.ts
index 9941471be2..69e734f42f 100644
--- a/frontend/openchat-agent/src/services/proposalsBot/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/proposalsBot/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -82,6 +84,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -511,6 +514,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -658,6 +662,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -849,6 +854,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -864,6 +870,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -903,6 +910,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1057,6 +1065,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1192,6 +1238,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1256,6 +1310,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1291,6 +1363,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/registry/candid/types.d.ts b/frontend/openchat-agent/src/services/registry/candid/types.d.ts
index 0674a8403e..af199cdac6 100644
--- a/frontend/openchat-agent/src/services/registry/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/registry/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -88,6 +90,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -517,6 +520,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -664,6 +668,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -855,6 +860,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -870,6 +876,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -910,6 +917,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1073,6 +1081,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1213,6 +1259,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1264,6 +1318,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1299,6 +1371,10 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
export interface TokenDetails {
'fee' : bigint,
'decimals' : number,
@@ -1313,6 +1389,12 @@ export interface TokenDetails {
'symbol' : string,
'transaction_url_format' : string,
}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/storageBucket/candid/types.d.ts b/frontend/openchat-agent/src/services/storageBucket/candid/types.d.ts
index ba0f2a3b39..a2f624d210 100644
--- a/frontend/openchat-agent/src/services/storageBucket/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/storageBucket/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -82,6 +84,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -541,6 +544,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -688,6 +692,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -879,6 +884,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -894,6 +900,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -933,6 +940,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1087,6 +1095,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1222,6 +1268,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1273,6 +1327,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1308,6 +1380,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/storageIndex/candid/types.d.ts b/frontend/openchat-agent/src/services/storageIndex/candid/types.d.ts
index 400b13cda6..59a1ab6662 100644
--- a/frontend/openchat-agent/src/services/storageIndex/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/storageIndex/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -112,6 +114,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -541,6 +544,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -688,6 +692,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -879,6 +884,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -894,6 +900,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -933,6 +940,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1087,6 +1095,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1232,6 +1278,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1285,6 +1339,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1320,6 +1392,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/services/user/anonUser.client.ts b/frontend/openchat-agent/src/services/user/anonUser.client.ts
index f4195c6a0b..e5f9a3957d 100644
--- a/frontend/openchat-agent/src/services/user/anonUser.client.ts
+++ b/frontend/openchat-agent/src/services/user/anonUser.client.ts
@@ -1,3 +1,5 @@
+import type { CancelP2PSwapResponse } from "openchat-shared";
+import type { AcceptP2PSwapResponse } from "openchat-shared";
import type {
InitialStateResponse,
UpdatesResponse,
@@ -185,7 +187,7 @@ export class AnonUserClient {
sendMessageWithTransferToGroup(
_groupId: GroupChatIdentifier,
- _recipientId: string,
+ _recipientId: string | undefined,
_sender: CreatedUser,
_event: EventWrapper,
_threadRootMessageIndex: number | undefined,
@@ -204,7 +206,7 @@ export class AnonUserClient {
sendMessageWithTransferToChannel(
_id: ChannelIdentifier,
- _recipientId: string,
+ _recipientId: string | undefined,
_sender: CreatedUser,
_event: EventWrapper,
_threadRootMessageIndex: number | undefined,
@@ -405,4 +407,12 @@ export class AnonUserClient {
deleteDirectChat(_userId: string, _blockUser: boolean): Promise {
throw new AnonymousOperationError();
}
+
+ acceptP2PSwap(_userId: string, _messageId: bigint): Promise {
+ throw new AnonymousOperationError();
+ }
+
+ cancelP2PSwap(_userId: string, _messageId: bigint): Promise {
+ throw new AnonymousOperationError();
+ }
}
diff --git a/frontend/openchat-agent/src/services/user/candid/idl.d.ts b/frontend/openchat-agent/src/services/user/candid/idl.d.ts
index 517ddc74d5..5f02fb4fd3 100644
--- a/frontend/openchat-agent/src/services/user/candid/idl.d.ts
+++ b/frontend/openchat-agent/src/services/user/candid/idl.d.ts
@@ -29,6 +29,8 @@ import {
PrizeContent,
PrizeWinnerContent,
PrizeContentInitial,
+ P2PSwapContentInitial,
+ TokenInfo,
MessageReminderCreated,
MessageReminder,
Proposal,
@@ -144,6 +146,10 @@ import {
SwapTokensResponse,
TokenSwapStatusResponse,
ApproveTransferResponse,
+ P2PSwapContent,
+ P2PSwapStatus,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "./types";
export {
_SERVICE as UserService,
@@ -215,6 +221,8 @@ export {
PrizeContent as ApiPrizeContent,
PrizeWinnerContent as ApiPrizeWinnerContent,
PrizeContentInitial as ApiPrizeCotentInitial,
+ P2PSwapContentInitial as ApiP2PSwapContentInitial,
+ TokenInfo as ApiTokenInfo,
GiphyContent as ApiGiphyContent,
GiphyImageVariant as ApiGiphyImageVariant,
ChatMetrics as ApiChatMetrics,
@@ -290,6 +298,10 @@ export {
SwapTokensResponse as ApiSwapTokensResponse,
TokenSwapStatusResponse as ApiTokenSwapStatusResponse,
ApproveTransferResponse as ApiApproveTransferResponse,
+ P2PSwapContent as ApiP2PSwapContent,
+ P2PSwapStatus as ApiP2PSwapStatus,
+ AcceptP2PSwapResponse as ApiAcceptP2PSwapResponse,
+ CancelP2PSwapResponse as ApiCancelP2PSwapResponse,
};
export const idlFactory: IDL.InterfaceFactory;
diff --git a/frontend/openchat-agent/src/services/user/candid/idl.js b/frontend/openchat-agent/src/services/user/candid/idl.js
index 3b84d0c9ef..d5e1a247e8 100644
--- a/frontend/openchat-agent/src/services/user/candid/idl.js
+++ b/frontend/openchat-agent/src/services/user/candid/idl.js
@@ -1,14 +1,52 @@
export const idlFactory = ({ IDL }) => {
- const Milliseconds = IDL.Nat64;
const CanisterId = IDL.Principal;
+ const UserId = CanisterId;
+ const MessageId = IDL.Nat;
+ const AcceptP2PSwapArgs = IDL.Record({
+ 'user_id' : UserId,
+ 'message_id' : MessageId,
+ });
+ const AcceptSwapSuccess = IDL.Record({ 'token1_txn_in' : IDL.Nat64 });
+ const SwapStatusErrorReserved = IDL.Record({ 'reserved_by' : UserId });
+ const SwapStatusErrorAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusErrorCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const SwapStatusErrorExpired = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const SwapStatusError = IDL.Variant({
+ 'Reserved' : SwapStatusErrorReserved,
+ 'Accepted' : SwapStatusErrorAccepted,
+ 'Cancelled' : SwapStatusErrorCancelled,
+ 'Completed' : SwapStatusErrorCompleted,
+ 'Expired' : SwapStatusErrorExpired,
+ });
+ const AcceptP2PSwapResponse = IDL.Variant({
+ 'ChatNotFound' : IDL.Null,
+ 'Success' : AcceptSwapSuccess,
+ 'UserSuspended' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ 'InternalError' : IDL.Text,
+ 'InsufficientFunds' : IDL.Null,
+ });
+ const Milliseconds = IDL.Nat64;
const ChatId = CanisterId;
const AddHotGroupExclusionsArgs = IDL.Record({
'duration' : IDL.Opt(Milliseconds),
'groups' : IDL.Vec(ChatId),
});
const AddHotGroupExclusionsResponse = IDL.Variant({ 'Success' : IDL.Null });
- const UserId = CanisterId;
- const MessageId = IDL.Nat;
const MessageIndex = IDL.Nat32;
const AddReactionArgs = IDL.Record({
'user_id' : UserId,
@@ -89,6 +127,16 @@ export const idlFactory = ({ IDL }) => {
});
const CancelMessageReminderArgs = IDL.Record({ 'reminder_id' : IDL.Nat64 });
const CancelMessageReminderResponse = IDL.Variant({ 'Success' : IDL.Null });
+ const CancelP2PSwapArgs = IDL.Record({
+ 'user_id' : UserId,
+ 'message_id' : MessageId,
+ });
+ const CancelP2PSwapResponse = IDL.Variant({
+ 'ChatNotFound' : IDL.Null,
+ 'Success' : IDL.Null,
+ 'StatusError' : SwapStatusError,
+ 'SwapNotFound' : IDL.Null,
+ });
const ContactsArgs = IDL.Record({});
const Contact = IDL.Record({
'nickname' : IDL.Opt(IDL.Text),
@@ -121,6 +169,10 @@ export const idlFactory = ({ IDL }) => {
'min_dissolve_delay' : IDL.Opt(Milliseconds),
'governance_canister_id' : CanisterId,
});
+ const TokenBalanceGate = IDL.Record({
+ 'min_balance' : IDL.Nat,
+ 'ledger_canister_id' : CanisterId,
+ });
const PaymentGate = IDL.Record({
'fee' : IDL.Nat,
'ledger_canister_id' : CanisterId,
@@ -129,6 +181,7 @@ export const idlFactory = ({ IDL }) => {
const AccessGate = IDL.Variant({
'VerifiedCredential' : VerifiedCredentialGate,
'SnsNeuron' : SnsNeuronGate,
+ 'TokenBalance' : TokenBalanceGate,
'DiamondMember' : IDL.Null,
'Payment' : PaymentGate,
});
@@ -201,6 +254,7 @@ export const idlFactory = ({ IDL }) => {
'crypto' : IDL.Opt(PermissionRole),
'giphy' : IDL.Opt(PermissionRole),
'default' : PermissionRole,
+ 'p2p_trade' : IDL.Opt(PermissionRole),
'image' : IDL.Opt(PermissionRole),
'prize' : IDL.Opt(PermissionRole),
'p2p_swap' : IDL.Opt(PermissionRole),
@@ -342,13 +396,28 @@ export const idlFactory = ({ IDL }) => {
'config' : PollConfig,
});
const TextContent = IDL.Record({ 'text' : IDL.Text });
- const ImageContent = IDL.Record({
- 'height' : IDL.Nat32,
- 'mime_type' : IDL.Text,
- 'blob_reference' : IDL.Opt(BlobReference),
- 'thumbnail_data' : IDL.Text,
- 'caption' : IDL.Opt(IDL.Text),
- 'width' : IDL.Nat32,
+ const P2PSwapReserved = IDL.Record({ 'reserved_by' : UserId });
+ const P2PSwapAccepted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapCancelled = IDL.Record({
+ 'token0_txn_out' : IDL.Opt(IDL.Nat64),
+ });
+ const P2PSwapCompleted = IDL.Record({
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : IDL.Nat64,
+ 'token0_txn_out' : IDL.Nat64,
+ 'token1_txn_in' : IDL.Nat64,
+ });
+ const P2PSwapExpired = P2PSwapCancelled;
+ const P2PSwapStatus = IDL.Variant({
+ 'Reserved' : P2PSwapReserved,
+ 'Open' : IDL.Null,
+ 'Accepted' : P2PSwapAccepted,
+ 'Cancelled' : P2PSwapCancelled,
+ 'Completed' : P2PSwapCompleted,
+ 'Expired' : P2PSwapExpired,
});
const Cryptocurrency = IDL.Variant({
'InternetComputer' : IDL.Null,
@@ -358,6 +427,31 @@ export const idlFactory = ({ IDL }) => {
'CKBTC' : IDL.Null,
'Other' : IDL.Text,
});
+ const TokenInfo = IDL.Record({
+ 'fee' : IDL.Nat,
+ 'decimals' : IDL.Nat8,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+ });
+ const P2PSwapContent = IDL.Record({
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : IDL.Nat64,
+ 'swap_id' : IDL.Nat32,
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_at' : TimestampMillis,
+ });
+ const ImageContent = IDL.Record({
+ 'height' : IDL.Nat32,
+ 'mime_type' : IDL.Text,
+ 'blob_reference' : IDL.Opt(BlobReference),
+ 'thumbnail_data' : IDL.Text,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'width' : IDL.Nat32,
+ });
const PrizeContent = IDL.Record({
'token' : Cryptocurrency,
'end_date' : TimestampMillis,
@@ -577,6 +671,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContent,
'Image' : ImageContent,
'Prize' : PrizeContent,
'Custom' : CustomMessageContent,
@@ -595,7 +690,14 @@ export const idlFactory = ({ IDL }) => {
'NotAuthorized' : IDL.Null,
'Success' : IDL.Record({ 'content' : MessageContent }),
'MessageHardDeleted' : IDL.Null,
- 'MessageNotDeleted' : IDL.Null,
+ });
+ const P2PSwapContentInitial = IDL.Record({
+ 'token0_amount' : IDL.Nat,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : IDL.Opt(IDL.Text),
+ 'token1_amount' : IDL.Nat,
+ 'expires_in' : Milliseconds,
});
const PrizeContentInitial = IDL.Record({
'end_date' : TimestampMillis,
@@ -609,6 +711,7 @@ export const idlFactory = ({ IDL }) => {
'File' : FileContent,
'Poll' : PollContent,
'Text' : TextContent,
+ 'P2PSwap' : P2PSwapContentInitial,
'Image' : ImageContent,
'Prize' : PrizeContentInitial,
'Custom' : CustomMessageContent,
@@ -845,7 +948,7 @@ export const idlFactory = ({ IDL }) => {
const InitUserPrincipalMigrationResponse = IDL.Variant({
'Success' : IDL.Null,
});
- const InitialStateArgs = IDL.Record({ 'disable_cache' : IDL.Opt(IDL.Bool) });
+ const EmptyArgs = IDL.Record({});
const UserCanisterChannelSummary = IDL.Record({
'channel_id' : ChannelId,
'read_by_me_up_to' : IDL.Opt(MessageIndex),
@@ -1013,6 +1116,7 @@ export const idlFactory = ({ IDL }) => {
'avatar_id' : IDL.Opt(IDL.Nat),
'direct_chats' : DirectChatsInitial,
'timestamp' : TimestampMillis,
+ 'local_user_index_canister_id' : CanisterId,
'suspended' : IDL.Bool,
}),
});
@@ -1161,7 +1265,6 @@ export const idlFactory = ({ IDL }) => {
'UserSuspended' : IDL.Null,
'NameTaken' : IDL.Null,
});
- const EmptyArgs = IDL.Record({});
const SavedCryptoAccountsResponse = IDL.Variant({
'Success' : IDL.Vec(NamedAccount),
});
@@ -1206,6 +1309,7 @@ export const idlFactory = ({ IDL }) => {
});
const SendMessageResponse = IDL.Variant({
'TextTooLong' : IDL.Nat32,
+ 'P2PSwapSetUpFailed' : IDL.Text,
'TransferSuccessV2' : IDL.Record({
'timestamp' : TimestampMillis,
'chat_id' : ChatId,
@@ -1215,6 +1319,7 @@ export const idlFactory = ({ IDL }) => {
'message_index' : MessageIndex,
}),
'TransferCannotBeZero' : IDL.Null,
+ 'DuplicateMessageId' : IDL.Null,
'Success' : IDL.Record({
'timestamp' : TimestampMillis,
'chat_id' : ChatId,
@@ -1250,7 +1355,9 @@ export const idlFactory = ({ IDL }) => {
'thread_root_message_index' : IDL.Opt(MessageIndex),
});
const SendMessageWithTransferToChannelResponse = IDL.Variant({
+ 'Retrying' : IDL.Tuple(IDL.Text, CompletedCryptoTransaction),
'TextTooLong' : IDL.Nat32,
+ 'P2PSwapSetUpFailed' : IDL.Text,
'UserNotInChannel' : CompletedCryptoTransaction,
'ChannelNotFound' : CompletedCryptoTransaction,
'TransferCannotBeZero' : IDL.Null,
@@ -1269,7 +1376,6 @@ export const idlFactory = ({ IDL }) => {
'InvalidRequest' : IDL.Text,
'TransferCannotBeToSelf' : IDL.Null,
'TransferFailed' : IDL.Text,
- 'InternalError' : IDL.Tuple(IDL.Text, CompletedCryptoTransaction),
'RulesNotAccepted' : IDL.Null,
'CryptocurrencyNotSupported' : Cryptocurrency,
});
@@ -1287,7 +1393,9 @@ export const idlFactory = ({ IDL }) => {
'thread_root_message_index' : IDL.Opt(MessageIndex),
});
const SendMessageWithTransferToGroupResponse = IDL.Variant({
+ 'Retrying' : IDL.Tuple(IDL.Text, CompletedCryptoTransaction),
'TextTooLong' : IDL.Nat32,
+ 'P2PSwapSetUpFailed' : IDL.Text,
'CallerNotInGroup' : IDL.Opt(CompletedCryptoTransaction),
'ChatFrozen' : IDL.Null,
'TransferCannotBeZero' : IDL.Null,
@@ -1303,7 +1411,6 @@ export const idlFactory = ({ IDL }) => {
'InvalidRequest' : IDL.Text,
'TransferCannotBeToSelf' : IDL.Null,
'TransferFailed' : IDL.Text,
- 'InternalError' : IDL.Tuple(IDL.Text, CompletedCryptoTransaction),
'RulesNotAccepted' : IDL.Null,
'CryptocurrencyNotSupported' : Cryptocurrency,
});
@@ -1399,12 +1506,6 @@ export const idlFactory = ({ IDL }) => {
'TransferFailed' : IDL.Text,
'InternalError' : IDL.Text,
});
- const TokenInfo = IDL.Record({
- 'fee' : IDL.Nat,
- 'decimals' : IDL.Nat8,
- 'token' : Cryptocurrency,
- 'ledger' : CanisterId,
- });
const SwapTokensArgs = IDL.Record({
'input_amount' : IDL.Nat,
'min_output_amount' : IDL.Nat,
@@ -1592,6 +1693,11 @@ export const idlFactory = ({ IDL }) => {
'Success' : CompletedCryptoTransaction,
});
return IDL.Service({
+ 'accept_p2p_swap' : IDL.Func(
+ [AcceptP2PSwapArgs],
+ [AcceptP2PSwapResponse],
+ [],
+ ),
'add_hot_group_exclusions' : IDL.Func(
[AddHotGroupExclusionsArgs],
[AddHotGroupExclusionsResponse],
@@ -1615,6 +1721,11 @@ export const idlFactory = ({ IDL }) => {
[CancelMessageReminderResponse],
[],
),
+ 'cancel_p2p_swap' : IDL.Func(
+ [CancelP2PSwapArgs],
+ [CancelP2PSwapResponse],
+ [],
+ ),
'contacts' : IDL.Func([ContactsArgs], [ContactsResponse], ['query']),
'create_community' : IDL.Func(
[CreateCommunityArgs],
@@ -1665,11 +1776,7 @@ export const idlFactory = ({ IDL }) => {
[InitUserPrincipalMigrationResponse],
[],
),
- 'initial_state' : IDL.Func(
- [InitialStateArgs],
- [InitialStateResponse],
- ['query'],
- ),
+ 'initial_state' : IDL.Func([EmptyArgs], [InitialStateResponse], ['query']),
'leave_community' : IDL.Func(
[LeaveCommunityArgs],
[LeaveCommunityResponse],
diff --git a/frontend/openchat-agent/src/services/user/candid/types.d.ts b/frontend/openchat-agent/src/services/user/candid/types.d.ts
index bb5bd1e72c..d0dd262242 100644
--- a/frontend/openchat-agent/src/services/user/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/user/candid/types.d.ts
@@ -1,8 +1,21 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptP2PSwapArgs {
+ 'user_id' : UserId,
+ 'message_id' : MessageId,
+}
+export type AcceptP2PSwapResponse = { 'ChatNotFound' : null } |
+ { 'Success' : AcceptSwapSuccess } |
+ { 'UserSuspended' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null } |
+ { 'InternalError' : string } |
+ { 'InsufficientFunds' : null };
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -120,6 +133,14 @@ export interface CachedGroupChatSummaries {
}
export interface CancelMessageReminderArgs { 'reminder_id' : bigint }
export type CancelMessageReminderResponse = { 'Success' : null };
+export interface CancelP2PSwapArgs {
+ 'user_id' : UserId,
+ 'message_id' : MessageId,
+}
+export type CancelP2PSwapResponse = { 'ChatNotFound' : null } |
+ { 'Success' : null } |
+ { 'StatusError' : SwapStatusError } |
+ { 'SwapNotFound' : null };
export type CanisterId = Principal;
export type CanisterUpgradeStatus = { 'NotRequired' : null } |
{ 'InProgress' : null };
@@ -131,6 +152,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -541,8 +563,7 @@ export type DeletedMessageResponse = { 'MessageNotFound' : null } |
{ 'ChatNotFound' : null } |
{ 'NotAuthorized' : null } |
{ 'Success' : { 'content' : MessageContent } } |
- { 'MessageHardDeleted' : null } |
- { 'MessageNotDeleted' : null };
+ { 'MessageHardDeleted' : null };
export interface DiamondMembershipDetails {
'pay_in_chat' : boolean,
'subscription' : DiamondMembershipSubscription,
@@ -741,6 +762,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -899,6 +921,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -1061,7 +1084,6 @@ export interface IndexedNotification {
}
export interface InitUserPrincipalMigrationArgs { 'new_principal' : Principal }
export type InitUserPrincipalMigrationResponse = { 'Success' : null };
-export interface InitialStateArgs { 'disable_cache' : [] | [boolean] }
export type InitialStateResponse = {
'Success' : {
'communities' : CommunitiesInitial,
@@ -1071,6 +1093,7 @@ export type InitialStateResponse = {
'avatar_id' : [] | [bigint],
'direct_chats' : DirectChatsInitial,
'timestamp' : TimestampMillis,
+ 'local_user_index_canister_id' : CanisterId,
'suspended' : boolean,
}
};
@@ -1139,6 +1162,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -1154,6 +1178,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -1193,6 +1218,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1368,6 +1394,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1573,6 +1637,14 @@ export interface ReportedMessage {
'count' : number,
'reports' : Array,
}
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1610,6 +1682,7 @@ export interface SelectedGroupUpdates {
'blocked_users_added' : Array,
}
export type SendMessageResponse = { 'TextTooLong' : number } |
+ { 'P2PSwapSetUpFailed' : string } |
{
'TransferSuccessV2' : {
'timestamp' : TimestampMillis,
@@ -1621,6 +1694,7 @@ export type SendMessageResponse = { 'TextTooLong' : number } |
}
} |
{ 'TransferCannotBeZero' : null } |
+ { 'DuplicateMessageId' : null } |
{
'Success' : {
'timestamp' : TimestampMillis,
@@ -1664,8 +1738,10 @@ export interface SendMessageWithTransferToChannelArgs {
'thread_root_message_index' : [] | [MessageIndex],
}
export type SendMessageWithTransferToChannelResponse = {
- 'TextTooLong' : number
+ 'Retrying' : [string, CompletedCryptoTransaction]
} |
+ { 'TextTooLong' : number } |
+ { 'P2PSwapSetUpFailed' : string } |
{ 'UserNotInChannel' : CompletedCryptoTransaction } |
{ 'ChannelNotFound' : CompletedCryptoTransaction } |
{ 'TransferCannotBeZero' : null } |
@@ -1686,7 +1762,6 @@ export type SendMessageWithTransferToChannelResponse = {
{ 'InvalidRequest' : string } |
{ 'TransferCannotBeToSelf' : null } |
{ 'TransferFailed' : string } |
- { 'InternalError' : [string, CompletedCryptoTransaction] } |
{ 'RulesNotAccepted' : null } |
{ 'CryptocurrencyNotSupported' : Cryptocurrency };
export interface SendMessageWithTransferToGroupArgs {
@@ -1703,8 +1778,10 @@ export interface SendMessageWithTransferToGroupArgs {
'thread_root_message_index' : [] | [MessageIndex],
}
export type SendMessageWithTransferToGroupResponse = {
- 'TextTooLong' : number
+ 'Retrying' : [string, CompletedCryptoTransaction]
} |
+ { 'TextTooLong' : number } |
+ { 'P2PSwapSetUpFailed' : string } |
{ 'CallerNotInGroup' : [] | [CompletedCryptoTransaction] } |
{ 'ChatFrozen' : null } |
{ 'TransferCannotBeZero' : null } |
@@ -1722,7 +1799,6 @@ export type SendMessageWithTransferToGroupResponse = {
{ 'InvalidRequest' : string } |
{ 'TransferCannotBeToSelf' : null } |
{ 'TransferFailed' : string } |
- { 'InternalError' : [string, CompletedCryptoTransaction] } |
{ 'RulesNotAccepted' : null } |
{ 'CryptocurrencyNotSupported' : Cryptocurrency };
export interface SetAvatarArgs { 'avatar' : [] | [Document] }
@@ -1802,6 +1878,24 @@ export interface SubscriptionInfo {
'keys' : SubscriptionKeys,
}
export interface SubscriptionKeys { 'auth' : string, 'p256dh' : string }
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface SwapTokensArgs {
'input_amount' : bigint,
'min_output_amount' : bigint,
@@ -1877,6 +1971,10 @@ export type TipMessageResponse = { 'Retrying' : string } |
{ 'TransferFailed' : string } |
{ 'InternalError' : [string, CompletedCryptoTransaction] } |
{ 'CannotTipSelf' : null };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
export interface TokenInfo {
'fee' : bigint,
'decimals' : number,
@@ -2085,6 +2183,7 @@ export type WithdrawCryptoResponse = { 'CurrencyNotSupported' : null } |
{ 'TransactionFailed' : FailedCryptoTransaction } |
{ 'Success' : CompletedCryptoTransaction };
export interface _SERVICE {
+ 'accept_p2p_swap' : ActorMethod<[AcceptP2PSwapArgs], AcceptP2PSwapResponse>,
'add_hot_group_exclusions' : ActorMethod<
[AddHotGroupExclusionsArgs],
AddHotGroupExclusionsResponse
@@ -2104,6 +2203,7 @@ export interface _SERVICE {
[CancelMessageReminderArgs],
CancelMessageReminderResponse
>,
+ 'cancel_p2p_swap' : ActorMethod<[CancelP2PSwapArgs], CancelP2PSwapResponse>,
'contacts' : ActorMethod<[ContactsArgs], ContactsResponse>,
'create_community' : ActorMethod<
[CreateCommunityArgs],
@@ -2133,7 +2233,7 @@ export interface _SERVICE {
[InitUserPrincipalMigrationArgs],
InitUserPrincipalMigrationResponse
>,
- 'initial_state' : ActorMethod<[InitialStateArgs], InitialStateResponse>,
+ 'initial_state' : ActorMethod<[EmptyArgs], InitialStateResponse>,
'leave_community' : ActorMethod<[LeaveCommunityArgs], LeaveCommunityResponse>,
'leave_group' : ActorMethod<[LeaveGroupArgs], LeaveGroupResponse>,
'manage_favourite_chats' : ActorMethod<
diff --git a/frontend/openchat-agent/src/services/user/mappers.ts b/frontend/openchat-agent/src/services/user/mappers.ts
index 2b9ddfab2b..c8394ed7f1 100644
--- a/frontend/openchat-agent/src/services/user/mappers.ts
+++ b/frontend/openchat-agent/src/services/user/mappers.ts
@@ -345,7 +345,7 @@ export function archiveChatResponse(candid: ApiArchiveUnarchiveChatsResponse): A
export function sendMessageWithTransferToChannelResponse(
candid: ApiSendMessageWithTransferToChannelResponse,
sender: string,
- recipient: string,
+ recipient: string | undefined,
): SendMessageResponse {
if ("Success" in candid) {
return {
@@ -353,8 +353,8 @@ export function sendMessageWithTransferToChannelResponse(
timestamp: candid.Success.timestamp,
messageIndex: candid.Success.message_index,
eventIndex: candid.Success.event_index,
- transfer: completedCryptoTransfer(candid.Success.transfer, sender, recipient),
expiresAt: optional(candid.Success.expires_at, Number),
+ transfer: completedCryptoTransfer(candid.Success.transfer, sender, recipient ?? ""),
};
} else {
console.warn("SendMessageWithTransferToChannel failed with", candid);
@@ -365,7 +365,7 @@ export function sendMessageWithTransferToChannelResponse(
export function sendMessageWithTransferToGroupResponse(
candid: ApiSendMessageWithTransferToGroupResponse,
sender: string,
- recipient: string,
+ recipient: string | undefined,
): SendMessageResponse {
if ("Success" in candid) {
return {
@@ -373,8 +373,8 @@ export function sendMessageWithTransferToGroupResponse(
timestamp: candid.Success.timestamp,
messageIndex: candid.Success.message_index,
eventIndex: candid.Success.event_index,
- transfer: completedCryptoTransfer(candid.Success.transfer, sender, recipient),
expiresAt: optional(candid.Success.expires_at, Number),
+ transfer: completedCryptoTransfer(candid.Success.transfer, sender, recipient ?? ""),
};
} else {
console.warn("SendMessageWithTransferToGroup failed with", candid);
@@ -442,9 +442,18 @@ export function sendMessageResponse(
if ("ChatFrozen" in candid) {
return { kind: "chat_frozen" };
}
+ if ("ChatFrozen" in candid) {
+ return { kind: "chat_frozen" };
+ }
+ if ("P2PSwapSetUpFailed" in candid) {
+ return { kind: "p2p_swap_setup_failed", text: candid.P2PSwapSetUpFailed };
+ }
if ("InternalError" in candid) {
return { kind: "internal_error" };
}
+ if ("DuplicateMessageId" in candid) {
+ return { kind: "duplicate_message_id"};
+ }
throw new UnsupportedValueError("Unexpected ApiSendMessageResponse type received", candid);
}
@@ -1222,4 +1231,4 @@ export function approveTransferResponse(
return { kind: "approve_error", error: JSON.stringify(candid.ApproveError) };
}
throw new UnsupportedValueError("Unexpected ApiApproveTransferResponse type received", candid);
-}
+}
\ No newline at end of file
diff --git a/frontend/openchat-agent/src/services/user/user.client.ts b/frontend/openchat-agent/src/services/user/user.client.ts
index 4502cf1cf6..418293a4da 100644
--- a/frontend/openchat-agent/src/services/user/user.client.ts
+++ b/frontend/openchat-agent/src/services/user/user.client.ts
@@ -73,6 +73,8 @@ import type {
ApproveTransferResponse,
MessageContext,
PendingCryptocurrencyTransfer,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "openchat-shared";
import { CandidService } from "../candidService";
import {
@@ -138,6 +140,8 @@ import {
deleteGroupResponse,
apiChatIdentifier,
apiToken,
+ acceptP2PSwapResponse,
+ cancelP2PSwapResponse,
} from "../common/chatMappers";
import { DataClient } from "../data/data.client";
import { muteNotificationsResponse } from "../notifications/mappers";
@@ -588,7 +592,7 @@ export class UserClient extends CandidService {
sendMessageWithTransferToGroup(
groupId: GroupChatIdentifier,
- recipientId: string,
+ recipientId: string | undefined,
sender: CreatedUser,
event: EventWrapper,
threadRootMessageIndex: number | undefined,
@@ -614,7 +618,7 @@ export class UserClient extends CandidService {
private sendMessageWithTransferToGroupToBackend(
groupId: GroupChatIdentifier,
- recipientId: string,
+ recipientId: string | undefined,
sender: CreatedUser,
event: EventWrapper,
threadRootMessageIndex: number | undefined,
@@ -664,7 +668,7 @@ export class UserClient extends CandidService {
sendMessageWithTransferToChannel(
id: ChannelIdentifier,
- recipientId: string,
+ recipientId: string | undefined,
sender: CreatedUser,
event: EventWrapper,
threadRootMessageIndex: number | undefined,
@@ -692,7 +696,7 @@ export class UserClient extends CandidService {
private sendMessageWithTransferToChannelToBackend(
id: ChannelIdentifier,
- recipientId: string,
+ recipientId: string | undefined,
sender: CreatedUser,
event: EventWrapper,
threadRootMessageIndex: number | undefined,
@@ -1254,4 +1258,24 @@ export class UserClient extends CandidService {
(resp) => "Success" in resp,
);
}
+
+ acceptP2PSwap(userId: string, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.userService.accept_p2p_swap({
+ user_id: Principal.fromText(userId),
+ message_id: messageId,
+ }),
+ acceptP2PSwapResponse,
+ );
+ }
+
+ cancelP2PSwap(userId: string, messageId: bigint): Promise {
+ return this.handleResponse(
+ this.userService.cancel_p2p_swap({
+ user_id: Principal.fromText(userId),
+ message_id: messageId,
+ }),
+ cancelP2PSwapResponse,
+ );
+ }
}
diff --git a/frontend/openchat-agent/src/services/userIndex/candid/types.d.ts b/frontend/openchat-agent/src/services/userIndex/candid/types.d.ts
index b86240891a..b0c1fe928e 100644
--- a/frontend/openchat-agent/src/services/userIndex/candid/types.d.ts
+++ b/frontend/openchat-agent/src/services/userIndex/candid/types.d.ts
@@ -1,8 +1,10 @@
import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
+export interface AcceptSwapSuccess { 'token1_txn_in' : bigint }
export type AccessGate = { 'VerifiedCredential' : VerifiedCredentialGate } |
{ 'SnsNeuron' : SnsNeuronGate } |
+ { 'TokenBalance' : TokenBalanceGate } |
{ 'DiamondMember' : null } |
{ 'Payment' : PaymentGate };
export type AccessGateUpdate = { 'NoChange' : null } |
@@ -99,6 +101,7 @@ export interface CanisterWasm {
export type ChannelId = bigint;
export interface ChannelMatch {
'id' : ChannelId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -571,6 +574,7 @@ export type FrozenGroupUpdate = { 'NoChange' : null } |
{ 'SetToSome' : FrozenGroupInfo };
export type GateCheckFailedReason = { 'NotDiamondMember' : null } |
{ 'PaymentFailed' : TransferFromError } |
+ { 'InsufficientBalance' : bigint } |
{ 'NoSnsNeuronsFound' : null } |
{ 'NoSnsNeuronsWithRequiredDissolveDelayFound' : null } |
{ 'NoSnsNeuronsWithRequiredStakeFound' : null };
@@ -718,6 +722,7 @@ export interface GroupInviteCodeChanged {
}
export interface GroupMatch {
'id' : ChatId,
+ 'subtype' : [] | [GroupSubtype],
'gate' : [] | [AccessGate],
'name' : string,
'description' : string,
@@ -911,6 +916,7 @@ export type MessageContent = { 'ReportedMessage' : ReportedMessage } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContent } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContent } |
{ 'Custom' : CustomMessageContent } |
@@ -926,6 +932,7 @@ export type MessageContentInitial = { 'Giphy' : GiphyContent } |
{ 'File' : FileContent } |
{ 'Poll' : PollContent } |
{ 'Text' : TextContent } |
+ { 'P2PSwap' : P2PSwapContentInitial } |
{ 'Image' : ImageContent } |
{ 'Prize' : PrizeContentInitial } |
{ 'Custom' : CustomMessageContent } |
@@ -965,6 +972,7 @@ export interface MessagePermissions {
'crypto' : [] | [PermissionRole],
'giphy' : [] | [PermissionRole],
'default' : PermissionRole,
+ 'p2p_trade' : [] | [PermissionRole],
'image' : [] | [PermissionRole],
'prize' : [] | [PermissionRole],
'p2p_swap' : [] | [PermissionRole],
@@ -1119,6 +1127,44 @@ export interface OptionalMessagePermissions {
export type OptionalMessagePermissionsUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : OptionalMessagePermissions };
+export interface P2PSwapAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface P2PSwapCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface P2PSwapContent {
+ 'status' : P2PSwapStatus,
+ 'token0_txn_in' : bigint,
+ 'swap_id' : number,
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_at' : TimestampMillis,
+}
+export interface P2PSwapContentInitial {
+ 'token0_amount' : bigint,
+ 'token0' : TokenInfo,
+ 'token1' : TokenInfo,
+ 'caption' : [] | [string],
+ 'token1_amount' : bigint,
+ 'expires_in' : Milliseconds,
+}
+export type P2PSwapExpired = P2PSwapCancelled;
+export interface P2PSwapReserved { 'reserved_by' : UserId }
+export type P2PSwapStatus = { 'Reserved' : P2PSwapReserved } |
+ { 'Open' : null } |
+ { 'Accepted' : P2PSwapAccepted } |
+ { 'Cancelled' : P2PSwapCancelled } |
+ { 'Completed' : P2PSwapCompleted } |
+ { 'Expired' : P2PSwapExpired };
export interface Participant {
'role' : GroupRole,
'user_id' : UserId,
@@ -1323,6 +1369,14 @@ export interface ReportedMessage {
}
export interface ReportedMessagesArgs { 'user_id' : [] | [UserId] }
export type ReportedMessagesResponse = { 'Success' : { 'json' : string } };
+export type ReserveP2PSwapResult = { 'Success' : ReserveP2PSwapSuccess } |
+ { 'SwapNotFound' : null } |
+ { 'Failure' : P2PSwapStatus };
+export interface ReserveP2PSwapSuccess {
+ 'created' : TimestampMillis,
+ 'content' : P2PSwapContent,
+ 'created_by' : UserId,
+}
export interface RoleChanged {
'user_ids' : Array,
'changed_by' : UserId,
@@ -1422,6 +1476,24 @@ export interface SuspensionDetails {
'suspended_by' : UserId,
'reason' : string,
}
+export type SwapStatusError = { 'Reserved' : SwapStatusErrorReserved } |
+ { 'Accepted' : SwapStatusErrorAccepted } |
+ { 'Cancelled' : SwapStatusErrorCancelled } |
+ { 'Completed' : SwapStatusErrorCompleted } |
+ { 'Expired' : SwapStatusErrorExpired };
+export interface SwapStatusErrorAccepted {
+ 'accepted_by' : UserId,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorCancelled { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorCompleted {
+ 'accepted_by' : UserId,
+ 'token1_txn_out' : bigint,
+ 'token0_txn_out' : bigint,
+ 'token1_txn_in' : bigint,
+}
+export interface SwapStatusErrorExpired { 'token0_txn_out' : [] | [bigint] }
+export interface SwapStatusErrorReserved { 'reserved_by' : UserId }
export interface Tally {
'no' : bigint,
'yes' : bigint,
@@ -1457,6 +1529,16 @@ export type TimestampNanos = bigint;
export type TimestampUpdate = { 'NoChange' : null } |
{ 'SetToNone' : null } |
{ 'SetToSome' : TimestampMillis };
+export interface TokenBalanceGate {
+ 'min_balance' : bigint,
+ 'ledger_canister_id' : CanisterId,
+}
+export interface TokenInfo {
+ 'fee' : bigint,
+ 'decimals' : number,
+ 'token' : Cryptocurrency,
+ 'ledger' : CanisterId,
+}
export interface Tokens { 'e8s' : bigint }
export type TotalPollVotes = { 'Anonymous' : Array<[number, number]> } |
{ 'Visible' : Array<[number, Array]> } |
diff --git a/frontend/openchat-agent/src/utils/caching.ts b/frontend/openchat-agent/src/utils/caching.ts
index 5c760d5b17..36a8e871ae 100644
--- a/frontend/openchat-agent/src/utils/caching.ts
+++ b/frontend/openchat-agent/src/utils/caching.ts
@@ -41,6 +41,9 @@ import {
MAX_MESSAGES,
} from "openchat-shared";
import type { Principal } from "@dfinity/principal";
+import type { CryptocurrencyContent } from "openchat-shared";
+import type { PrizeContent } from "openchat-shared";
+import type { P2PSwapContent } from "openchat-shared";
const CACHE_VERSION = 95;
const MAX_INDEX = 9999999999;
@@ -697,7 +700,7 @@ export function setCachedMessageFromSendResponse(
setCachedMessageIfNotExists(db, chatId, event, threadRootMessageIndex);
- return [resp, message];
+ return [resp, event.event];
};
}
@@ -738,10 +741,48 @@ function messageToEvent(
message: Message,
resp: SendMessageSuccess | TransferSuccess,
): EventWrapper {
+ let content = message.content;
+
+ if (resp.kind === "transfer_success") {
+ switch (message.content.kind) {
+ case "crypto_content":
+ content = { ...message.content, transfer: resp.transfer } as CryptocurrencyContent;
+ break;
+ case "prize_content_initial":
+ content = {
+ kind: "prize_content",
+ prizesRemaining: message.content.prizes.length,
+ prizesPending: 0,
+ winners: [],
+ token: message.content.transfer.token,
+ endDate: message.content.endDate,
+ caption: message.content.caption,
+ diamondOnly: message.content.diamondOnly,
+ } as PrizeContent;
+ break;
+ case "p2p_swap_content_initial":
+ content = {
+ kind: "p2p_swap_content",
+ token0: message.content.token0,
+ token0Amount: message.content.token0Amount,
+ token1: message.content.token1,
+ token1Amount: message.content.token1Amount,
+ caption: message.content.caption,
+ expiresAt: BigInt(Date.now()) + message.content.expiresIn,
+ status: { kind: "p2p_swap_open" },
+ token0TxnIn: resp.transfer.blockIndex,
+ // Note: we don't have this in the response but actually we don't use it on the FE
+ swapId: 0,
+ } as P2PSwapContent;
+ break;
+ }
+ }
+
return {
event: {
...message,
messageIndex: resp.messageIndex,
+ content,
},
index: resp.eventIndex,
timestamp: resp.timestamp,
diff --git a/frontend/openchat-client/src/openchat.ts b/frontend/openchat-client/src/openchat.ts
index 18c57043b2..450f303c89 100644
--- a/frontend/openchat-client/src/openchat.ts
+++ b/frontend/openchat-client/src/openchat.ts
@@ -364,6 +364,8 @@ import type {
TranslationCorrection,
Success,
Failure,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "openchat-shared";
import {
AuthProvider,
@@ -395,6 +397,8 @@ import {
isMessageNotification,
userIdsFromTransactions,
contentTypeToPermission,
+ mapAcceptP2PSwapResponseToStatus,
+ mapCancelP2PSwapResponseToStatus,
anonymousUser,
ANON_USER_ID,
isPaymentGate,
@@ -452,6 +456,7 @@ import {
extractEnabledLinks,
stripLinkDisabledMarker,
} from "./utils/linkPreviews";
+import type { SendMessageResponse } from "openchat-shared";
import { applyTranslationCorrection } from "./stores/i18n";
import { getUserCountryCode } from "./utils/location";
@@ -3186,83 +3191,105 @@ export class OpenChat extends OpenChatAgentWorker {
},
};
- const canRetry = this.canRetryMessage(retryEvent.event.content);
-
- const messageFilterFailed = doesMessageFailFilter(event.event, get(messageFiltersStore));
-
// add the *new* event to unconfirmed
unconfirmed.add(messageContext, retryEvent);
// TODO - what about mentions?
+ this.sendMessageCommon(
+ chat,
+ messageContext,
+ retryEvent,
+ [],
+ rulesAccepted,
+ communityRulesAccepted);
+ }
+
+ private async sendMessageCommon(
+ chat: ChatSummary,
+ messageContext: MessageContext,
+ eventWrapper: EventWrapper,
+ mentioned: User[] = [],
+ rulesAccepted: number | undefined = undefined,
+ communityRulesAccepted: number | undefined = undefined): Promise {
+
+ const {chatId, threadRootMessageIndex} = messageContext;
+
+ const canRetry = this.canRetryMessage(eventWrapper.event.content);
+
+ const messageFilterFailed = doesMessageFailFilter(eventWrapper.event, get(messageFiltersStore));
+
this.sendRequest({
kind: "sendMessage",
chatType: chat.kind,
messageContext,
user: this._liveState.user,
- mentioned: [],
- event: retryEvent,
+ mentioned,
+ event: eventWrapper,
rulesAccepted,
communityRulesAccepted,
messageFilterFailed,
})
- .then(([resp, msg]) => {
- if (resp.kind === "success" || resp.kind === "transfer_success") {
- this.onSendMessageSuccess(chatId, resp, msg, threadRootMessageIndex);
- if (msg.kind === "message" && msg.content.kind === "crypto_content") {
- this.refreshAccountBalance(msg.content.transfer.ledger);
- }
- if (threadRootMessageIndex !== undefined) {
- trackEvent("sent_threaded_message");
+ .then(([resp, msg]) => {
+ if (resp.kind === "success" || resp.kind === "transfer_success") {
+ this.onSendMessageSuccess(chatId, resp, msg, threadRootMessageIndex);
+ if (msg.kind === "message" && msg.content.kind === "crypto_content") {
+ this.refreshAccountBalance(msg.content.transfer.ledger);
+ }
+ if (threadRootMessageIndex !== undefined) {
+ trackEvent("sent_threaded_message");
+ } else {
+ if (chat.kind === "direct_chat") {
+ trackEvent("sent_direct_message");
} else {
- if (chat.kind === "direct_chat") {
- trackEvent("sent_direct_message");
+ if (chat.public) {
+ trackEvent("sent_public_group_message");
} else {
- if (chat.public) {
- trackEvent("sent_public_group_message");
- } else {
- trackEvent("sent_private_group_message");
- }
+ trackEvent("sent_private_group_message");
}
}
- if (msg.repliesTo !== undefined) {
- // double counting here which I think is OK since we are limited to string events
- trackEvent("replied_to_message");
- }
- } else {
- if (resp.kind == "rules_not_accepted") {
- this.markChatRulesAcceptedLocally(false);
- }
-
- if (resp.kind == "community_rules_not_accepted") {
- this.markCommunityRulesAcceptedLocally(false);
- }
+ }
+ if (msg.repliesTo !== undefined) {
+ // double counting here which I think is OK since we are limited to string events
+ trackEvent("replied_to_message");
+ }
+ } else {
+ if (resp.kind == "rules_not_accepted") {
+ this.markChatRulesAcceptedLocally(false);
+ }
- this.onSendMessageFailure(
- chatId,
- msg.messageId,
- threadRootMessageIndex,
- event,
- canRetry,
- );
+ if (resp.kind == "community_rules_not_accepted") {
+ this.markCommunityRulesAcceptedLocally(false);
}
- })
- .catch((err) => {
+
this.onSendMessageFailure(
chatId,
- event.event.messageId,
+ msg.messageId,
threadRootMessageIndex,
- event,
+ eventWrapper,
canRetry,
- err,
+ resp,
);
- });
+ }
+ })
+ .catch((err) => {
+ this.onSendMessageFailure(
+ chatId,
+ eventWrapper.event.messageId,
+ threadRootMessageIndex,
+ eventWrapper,
+ canRetry,
+ undefined,
+ err,
+ );
+ });
}
private canRetryMessage(content: MessageContent): boolean {
return (
content.kind !== "poll_content" &&
content.kind !== "crypto_content" &&
- content.kind !== "prize_content_initial"
+ content.kind !== "prize_content_initial" &&
+ content.kind !== "p2p_swap_content_initial"
);
}
@@ -3368,72 +3395,13 @@ export class OpenChat extends OpenChatAgentWorker {
expiresAt: threadRootMessageIndex ? undefined : this.eventExpiry(chat, timestamp),
};
- const canRetry = this.canRetryMessage(msg.content);
-
- const messageFilterFailed = doesMessageFailFilter(msg, get(messageFiltersStore));
-
- this.sendRequest({
- kind: "sendMessage",
- chatType: chat.kind,
+ this.sendMessageCommon(
+ chat,
messageContext,
- user: this._liveState.user,
- mentioned,
event,
+ mentioned,
rulesAccepted,
- communityRulesAccepted,
- messageFilterFailed,
- })
- .then(([resp, msg]) => {
- if (resp.kind === "success" || resp.kind === "transfer_success") {
- this.onSendMessageSuccess(chatId, resp, msg, threadRootMessageIndex);
- if (msg.kind === "message" && msg.content.kind === "crypto_content") {
- this.refreshAccountBalance(msg.content.transfer.ledger);
- }
- if (threadRootMessageIndex !== undefined) {
- trackEvent("sent_threaded_message");
- } else {
- if (chat.kind === "direct_chat") {
- trackEvent("sent_direct_message");
- } else {
- if (chat.public) {
- trackEvent("sent_public_group_message");
- } else {
- trackEvent("sent_private_group_message");
- }
- }
- }
- if (msg.repliesTo !== undefined) {
- // double counting here which I think is OK since we are limited to string events
- trackEvent("replied_to_message");
- }
- } else {
- if (resp.kind == "rules_not_accepted") {
- this.markChatRulesAcceptedLocally(false);
- }
-
- if (resp.kind == "community_rules_not_accepted") {
- this.markCommunityRulesAcceptedLocally(false);
- }
-
- this.onSendMessageFailure(
- chatId,
- msg.messageId,
- threadRootMessageIndex,
- event,
- canRetry,
- );
- }
- })
- .catch((err) => {
- this.onSendMessageFailure(
- chatId,
- event.event.messageId,
- threadRootMessageIndex,
- event,
- canRetry,
- err,
- );
- });
+ communityRulesAccepted);
this.postSendMessage(chat, event, threadRootMessageIndex);
}
@@ -3474,6 +3442,7 @@ export class OpenChat extends OpenChatAgentWorker {
threadRootMessageIndex: number | undefined,
event: EventWrapper,
canRetry: boolean,
+ response?: SendMessageResponse,
err?: unknown,
) {
this.removeMessage(chatId, messageId, this._liveState.user.userId, threadRootMessageIndex);
@@ -3484,6 +3453,8 @@ export class OpenChat extends OpenChatAgentWorker {
if (err !== undefined) {
this._logger.error("Exception sending message", err);
+ } else if (response !== undefined) {
+ this._logger.error("Error sending message", JSON.stringify(response));
}
this.dispatchEvent(new SendMessageFailed(!canRetry));
@@ -5098,6 +5069,39 @@ export class OpenChat extends OpenChatAgentWorker {
});
}
+ acceptP2PSwap(chatId: ChatIdentifier, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ localMessageUpdates.setP2PSwapStatus(messageId, {
+ kind: "p2p_swap_reserved",
+ reservedBy: this._liveState.user.userId,
+ });
+ return this.sendRequest({ kind: "acceptP2PSwap", chatId, threadRootMessageIndex, messageId })
+ .then((resp) => {
+ localMessageUpdates.setP2PSwapStatus(messageId, mapAcceptP2PSwapResponseToStatus(resp, this._liveState.user.userId));
+ return resp;
+ })
+ .catch((err) => {
+ localMessageUpdates.setP2PSwapStatus(messageId, { kind: "p2p_swap_open" });
+ this._logger.error("Accepting p2p swap failed", err);
+ return { kind: "internal_error", text: err.toString() };
+ });
+ }
+
+ cancelP2PSwap(chatId: ChatIdentifier, threadRootMessageIndex: number | undefined, messageId: bigint): Promise {
+ localMessageUpdates.setP2PSwapStatus(messageId, {
+ kind: "p2p_swap_cancelled",
+ });
+ return this.sendRequest({ kind: "cancelP2PSwap", chatId, threadRootMessageIndex, messageId })
+ .then((resp) => {
+ localMessageUpdates.setP2PSwapStatus(messageId, mapCancelP2PSwapResponseToStatus(resp));
+ return resp;
+ })
+ .catch((err) => {
+ localMessageUpdates.setP2PSwapStatus(messageId, { kind: "p2p_swap_open" });
+ this._logger.error("Cancelling p2p swap failed", err);
+ return { kind: "internal_error", text: err.toString() };
+ });
+ }
+
private overwriteUserInStore(
userId: string,
updater: (user: UserSummary) => UserSummary | undefined,
diff --git a/frontend/openchat-client/src/stores/localMessageUpdates.ts b/frontend/openchat-client/src/stores/localMessageUpdates.ts
index 6042654b2d..33f60b8952 100644
--- a/frontend/openchat-client/src/stores/localMessageUpdates.ts
+++ b/frontend/openchat-client/src/stores/localMessageUpdates.ts
@@ -5,6 +5,7 @@ import {
type LocalReaction,
type MessageContent,
type ThreadSummary,
+ type P2PSwapStatus,
} from "openchat-shared";
import { LocalUpdatesStore } from "./localUpdatesStore";
@@ -86,6 +87,9 @@ export class LocalMessageUpdatesStore extends LocalUpdatesStore ({ prizeClaimed: userId }));
}
+ setP2PSwapStatus(messageId: bigint, status: P2PSwapStatus): void {
+ this.applyUpdate(messageId, (_) => ({ p2pSwapStatus: status }));
+ }
markPollVote(messageId: bigint, vote: LocalPollVote): void {
this.applyUpdate(messageId, (updates) => ({
pollVotes: [...(updates?.pollVotes ?? []), vote],
diff --git a/frontend/openchat-client/src/utils/chat.ts b/frontend/openchat-client/src/utils/chat.ts
index 0678583eee..440509d405 100644
--- a/frontend/openchat-client/src/utils/chat.ts
+++ b/frontend/openchat-client/src/utils/chat.ts
@@ -36,7 +36,6 @@ import type {
TimelineItem,
TipsReceived,
ThreadSummary,
- PrizeContent,
MessagePermission,
ChatPermissions,
OptionalChatPermissions,
@@ -1091,6 +1090,10 @@ export function canSendGroupMessage(
? chat.permissions.threadPermissions ?? chat.permissions.messagePermissions
: chat.permissions.messagePermissions;
+ if (permission === "prize" && mode === "thread") {
+ return false;
+ }
+
return (
!chat.frozen &&
isPermitted(
@@ -1252,26 +1255,6 @@ export function mergeSendMessageResponse(
msg: Message,
resp: SendMessageSuccess | TransferSuccess,
): EventWrapper {
- let content = msg.content;
- if (resp.kind === "transfer_success") {
- switch (msg.content.kind) {
- case "crypto_content":
- content = { ...msg.content, transfer: resp.transfer } as CryptocurrencyContent;
- break;
- case "prize_content_initial":
- content = {
- kind: "prize_content",
- prizesRemaining: msg.content.prizes.length,
- prizesPending: 0,
- winners: [],
- token: msg.content.transfer.token,
- endDate: msg.content.endDate,
- caption: msg.content.caption,
- diamondOnly: msg.content.diamondOnly,
- } as PrizeContent;
- break;
- }
- }
return {
index: resp.eventIndex,
timestamp: resp.timestamp,
@@ -1279,7 +1262,6 @@ export function mergeSendMessageResponse(
event: {
...msg,
messageIndex: resp.messageIndex,
- content,
},
};
}
@@ -1470,6 +1452,7 @@ function mergeLocalUpdates(
if (localUpdates?.prizeClaimed !== undefined) {
if (message.content.kind === "prize_content") {
if (!message.content.winners.includes(localUpdates.prizeClaimed)) {
+ message.content = { ...message.content };
message.content.winners.push(localUpdates.prizeClaimed);
message.content.prizesRemaining -= 1;
message.content.prizesPending += 1;
@@ -1477,6 +1460,13 @@ function mergeLocalUpdates(
}
}
+ if (localUpdates?.p2pSwapStatus !== undefined && message.content.kind === "p2p_swap_content") {
+ message.content = {
+ ...message.content,
+ status: localUpdates.p2pSwapStatus,
+ };
+ }
+
if (localUpdates?.reactions !== undefined) {
let reactions = [...message.reactions];
for (const localReaction of localUpdates.reactions) {
@@ -1901,6 +1891,7 @@ function diffMessagePermissions(
diff.giphy = updateFromOptions(original.giphy, updated.giphy);
diff.prize = updateFromOptions(original.prize, updated.prize);
diff.memeFighter = updateFromOptions(original.memeFighter, updated.memeFighter);
+ diff.p2pSwap = updateFromOptions(original.p2pSwap, updated.p2pSwap);
return diff;
}
diff --git a/frontend/openchat-shared/src/domain/access.ts b/frontend/openchat-shared/src/domain/access.ts
index 08ebe96180..88a541a365 100644
--- a/frontend/openchat-shared/src/domain/access.ts
+++ b/frontend/openchat-shared/src/domain/access.ts
@@ -6,7 +6,8 @@ export type AccessGate =
| PaymentGate
| DiamondGate
| NftGate
- | CredentialGate;
+ | CredentialGate
+ | TokenBalanceGate;
export type NoGate = { kind: "no_gate" };
@@ -33,6 +34,12 @@ export type PaymentGate = {
fee: bigint;
};
+export type TokenBalanceGate = {
+ kind: "token_balance_gate";
+ ledgerCanister: string;
+ minBalance: bigint;
+};
+
export function isNeuronGate(gate: AccessGate): gate is NeuronGate {
return gate.kind === "neuron_gate";
}
diff --git a/frontend/openchat-shared/src/domain/chat/chat.ts b/frontend/openchat-shared/src/domain/chat/chat.ts
index f220e39b18..55be172450 100644
--- a/frontend/openchat-shared/src/domain/chat/chat.ts
+++ b/frontend/openchat-shared/src/domain/chat/chat.ts
@@ -56,6 +56,8 @@ export type MessageContent =
| ProposalContent
| PrizeContent
| PrizeContentInitial
+ | P2PSwapContent
+ | P2PSwapContentInitial
| PrizeWinnerContent
| MessageReminderCreatedContent
| MessageReminderContent
@@ -72,6 +74,23 @@ export interface PrizeContentInitial {
prizes: bigint[];
}
+export interface P2PSwapContentInitial {
+ kind: "p2p_swap_content_initial";
+ token0: TokenInfo;
+ token1: TokenInfo;
+ token0Amount: bigint;
+ token1Amount: bigint;
+ caption?: string;
+ expiresIn: bigint;
+}
+
+export interface TokenInfo {
+ fee: bigint,
+ decimals: number,
+ symbol: string,
+ ledger: string,
+}
+
export type CaptionedContent =
| AttachmentContent
| CryptocurrencyContent
@@ -101,6 +120,7 @@ export function isCaptionedContent(content: MessageContent): content is Captione
case "crypto_content":
case "giphy_content":
case "prize_content":
+ case "p2p_swap_content":
return true;
default:
return false;
@@ -273,6 +293,56 @@ export interface PrizeContent {
caption?: string;
}
+export interface P2PSwapContent {
+ kind: "p2p_swap_content";
+ token0: TokenInfo;
+ token1: TokenInfo;
+ token0Amount: bigint;
+ token1Amount: bigint;
+ caption?: string;
+ expiresAt: bigint;
+ status: P2PSwapStatus;
+ swapId: number;
+ token0TxnIn: TransactionId;
+}
+
+export type TransactionId = bigint;
+
+export type P2PSwapStatus = P2PSwapOpen | P2PSwapReserved | P2PSwapAccepted | P2PSwapCancelled | P2PSwapExpired | P2PSwapCompleted;
+
+export interface P2PSwapOpen {
+ kind: "p2p_swap_open";
+}
+
+export interface P2PSwapReserved {
+ kind: "p2p_swap_reserved";
+ reservedBy: string;
+}
+
+export interface P2PSwapAccepted {
+ kind: "p2p_swap_accepted";
+ acceptedBy: string;
+ token1TxnIn: TransactionId,
+}
+
+export interface P2PSwapCancelled {
+ kind: "p2p_swap_cancelled";
+ token0TxnOut?: TransactionId,
+}
+
+export interface P2PSwapExpired {
+ kind: "p2p_swap_expired";
+ token0TxnOut?: TransactionId,
+}
+
+export interface P2PSwapCompleted {
+ kind: "p2p_swap_completed";
+ acceptedBy: string;
+ token1TxnIn: TransactionId,
+ token0TxnOut: TransactionId,
+ token1TxnOut: TransactionId,
+}
+
export interface ProposalContent {
kind: "proposal_content";
governanceCanisterId: string;
@@ -580,6 +650,7 @@ export type LocalMessageUpdates = {
undeletedContent?: MessageContent;
revealedContent?: MessageContent;
prizeClaimed?: string;
+ p2pSwapStatus?: P2PSwapStatus;
reactions?: LocalReaction[];
pollVotes?: LocalPollVote[];
threadSummary?: Partial;
@@ -890,7 +961,7 @@ export type DirectChatsInitial = {
pinned: DirectChatIdentifier[];
};
-export type ChatIdentifier = ChannelIdentifier | DirectChatIdentifier | GroupChatIdentifier;
+export type ChatIdentifier = MultiUserChatIdentifier | DirectChatIdentifier;
export type MultiUserChatIdentifier = ChannelIdentifier | GroupChatIdentifier;
export type ExpiredEventsRange = { kind: "expired_events_range"; start: number; end: number };
@@ -1495,7 +1566,9 @@ export type SendMessageResponse =
| ChatFrozen
| RulesNotAccepted
| Offline
- | CommunityRulesNotAccepted;
+ | CommunityRulesNotAccepted
+ | P2PSwapSetUpFailed
+ | DuplicateMessageId;
export type SendMessageSuccess = {
kind: "success";
@@ -1573,7 +1646,8 @@ export type GateCheckFailedReason =
| "no_sns_neuron_found"
| "dissolve_delay_not_met"
| "min_stake_not_met"
- | "payment_failed";
+ | "payment_failed"
+ | "insufficient_balance";
export type ChatFrozenEvent = {
kind: "chat_frozen";
@@ -1589,6 +1663,15 @@ export type CommunityRulesNotAccepted = {
kind: "community_rules_not_accepted";
};
+export type P2PSwapSetUpFailed = {
+ kind: "p2p_swap_setup_failed";
+ text: string;
+}
+
+export type DuplicateMessageId = {
+ kind: "duplicate_message_id";
+}
+
export type GateUpdatedEvent = {
kind: "gate_updated";
updatedBy: string;
@@ -1965,3 +2048,56 @@ export type GroupAndCommunitySummaryUpdatesResponse =
kind: "not_found";
}
| { kind: "error"; error: string };
+
+export type AcceptP2PSwapResponse =
+ | { kind: "success", token1TxnIn : TransactionId }
+ | { kind: "already_reserved", reservedBy: string }
+ | {
+ kind: "already_accepted",
+ acceptedBy: string,
+ token1TxnIn: TransactionId
+ }
+ | { kind: "already_completed",
+ acceptedBy: string,
+ token1TxnIn: TransactionId,
+ token0TxnOut: TransactionId,
+ token1TxnOut: TransactionId,
+ }
+ | { kind: "swap_cancelled", token0TxnOut?: TransactionId }
+ | { kind: "swap_expired", token0TxnOut?: TransactionId }
+ | { kind: "swap_not_found" }
+ | { kind: "channel_not_found" }
+ | { kind: "chat_not_found" }
+ | { kind: "user_suspended" }
+ | { kind: "user_not_in_group" }
+ | { kind: "user_not_in_community" }
+ | { kind: "user_not_in_channel" }
+ | { kind: "chat_frozen" }
+ | { kind: "insufficient_funds" }
+ | { kind: "internal_error", text: string };
+
+export type CancelP2PSwapResponse =
+ | { kind: "success" }
+ | { kind: "already_reserved", reservedBy: string }
+ | {
+ kind: "already_accepted",
+ acceptedBy: string,
+ token1TxnIn: TransactionId
+ }
+ | { kind: "already_completed",
+ acceptedBy: string,
+ token1TxnIn: TransactionId,
+ token0TxnOut: TransactionId,
+ token1TxnOut: TransactionId,
+ }
+ | { kind: "swap_cancelled", token0TxnOut?: TransactionId }
+ | { kind: "swap_expired", token0TxnOut?: TransactionId }
+ | { kind: "swap_not_found" }
+ | { kind: "chat_not_found" }
+ | { kind: "channel_not_found" }
+ | { kind: "user_suspended" }
+ | { kind: "user_not_in_group" }
+ | { kind: "user_not_in_community" }
+ | { kind: "user_not_in_channel" }
+ | { kind: "chat_frozen" }
+ | { kind: "internal_error", text: string };
\ No newline at end of file
diff --git a/frontend/openchat-shared/src/domain/community/index.ts b/frontend/openchat-shared/src/domain/community/index.ts
index 44c5115ceb..ea616ec55c 100644
--- a/frontend/openchat-shared/src/domain/community/index.ts
+++ b/frontend/openchat-shared/src/domain/community/index.ts
@@ -28,6 +28,7 @@ import type {
ChatNotFound,
CommunityFrozen,
Failure,
+ InternalError,
NotAuthorised,
Offline,
Success,
@@ -125,6 +126,7 @@ export type AddMembersToChannelResponse =
| UserNotInCommunity
| UserSuspended
| CommunityFrozen
+ | InternalError
| Offline;
export type BlockCommunityUserResponse = Success | Failure | Offline;
diff --git a/frontend/openchat-shared/src/domain/permission.ts b/frontend/openchat-shared/src/domain/permission.ts
index df043b3c55..9eacc2d3c0 100644
--- a/frontend/openchat-shared/src/domain/permission.ts
+++ b/frontend/openchat-shared/src/domain/permission.ts
@@ -12,18 +12,7 @@ export type CommunityPermissionRole = CommunityRolesType[number];
export type MemberRole = "admin" | "moderator" | "member" | "owner" | "none";
-export const messagePermissionsList = [
- "text",
- "image",
- "video",
- "audio",
- "file",
- "poll",
- "crypto",
- "giphy",
- "prize",
- "memeFighter",
-] as const;
+export const messagePermissionsList = ["text", "image", "video", "audio", "file", "poll", "crypto", "giphy", "prize", "memeFighter", "p2pSwap"] as const;
type MessagePermissionsType = typeof messagePermissionsList;
export type MessagePermission = MessagePermissionsType[number];
@@ -61,8 +50,8 @@ export type MessagePermissions = {
crypto?: ChatPermissionRole;
giphy?: ChatPermissionRole;
prize?: ChatPermissionRole;
- p2pSwap?: ChatPermissionRole;
memeFighter?: ChatPermissionRole;
+ p2pSwap?: ChatPermissionRole;
};
export type OptionalChatPermissions = {
@@ -90,6 +79,7 @@ export type OptionalMessagePermissions = {
giphy: OptionUpdate;
prize: OptionUpdate;
memeFighter: OptionUpdate;
+ p2pSwap: OptionUpdate;
};
export type CommunityPermissions = {
@@ -122,6 +112,7 @@ export function defaultOptionalMessagePermissions(): OptionalMessagePermissions
giphy: undefined,
prize: undefined,
memeFighter: undefined,
+ p2pSwap: undefined,
};
}
diff --git a/frontend/openchat-shared/src/domain/worker.ts b/frontend/openchat-shared/src/domain/worker.ts
index 787d497ac9..32382f4dc1 100644
--- a/frontend/openchat-shared/src/domain/worker.ts
+++ b/frontend/openchat-shared/src/domain/worker.ts
@@ -64,6 +64,8 @@ import type {
MessageContext,
PendingCryptocurrencyTransfer,
TipMessageResponse,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "./chat";
import type { BlobReference, StorageStatus } from "./data/data";
import type { UpdateMarketMakerConfigArgs, UpdateMarketMakerConfigResponse } from "./marketMaker";
@@ -315,7 +317,9 @@ export type WorkerRequest =
| ApproveTranslationCorrection
| RejectTranslationCorrection
| GetTranslationCorrections
- | GetExchangeRates;
+ | GetExchangeRates
+ | AcceptP2PSwap
+ | CancelP2PSwap;
type GetTranslationCorrections = {
kind: "getTranslationCorrections";
@@ -1207,6 +1211,8 @@ export type WorkerResponseInner =
| TokenSwapStatusResponse
| DiamondMembershipFees[]
| TranslationCorrections
+ | AcceptP2PSwapResponse
+ | CancelP2PSwapResponse
| Record;
export type WorkerResponse = Response;
@@ -1475,6 +1481,20 @@ type GetReportedMessages = {
userId: string | undefined;
};
+type AcceptP2PSwap = {
+ chatId: ChatIdentifier;
+ threadRootMessageIndex: number | undefined;
+ messageId: bigint;
+ kind: "acceptP2PSwap";
+};
+
+type CancelP2PSwap = {
+ chatId: ChatIdentifier;
+ threadRootMessageIndex: number | undefined;
+ messageId: bigint;
+ kind: "cancelP2PSwap";
+};
+
// prettier-ignore
export type WorkerResult = T extends PinMessage
? PinMessageResponse
@@ -1792,4 +1812,8 @@ export type WorkerResult = T extends PinMessage
? TranslationCorrections
: T extends GetTranslationCorrections
? TranslationCorrections
+ : T extends AcceptP2PSwap
+ ? AcceptP2PSwapResponse
+ : T extends CancelP2PSwap
+ ? CancelP2PSwapResponse
: never;
diff --git a/frontend/openchat-shared/src/utils/chat.ts b/frontend/openchat-shared/src/utils/chat.ts
index fec5965a7d..3ea91c439d 100644
--- a/frontend/openchat-shared/src/utils/chat.ts
+++ b/frontend/openchat-shared/src/utils/chat.ts
@@ -14,6 +14,9 @@ import type {
AccountTransaction,
AttachmentContent,
MessagePermission,
+ P2PSwapStatus,
+ AcceptP2PSwapResponse,
+ CancelP2PSwapResponse,
} from "../domain";
import { extractUserIdsFromMentions, UnsupportedValueError } from "../domain";
import type { MessageFormatter } from "./i18n";
@@ -143,6 +146,8 @@ export function getContentAsFormattedText(
text = content.proposal.title;
} else if (content.kind === "giphy_content") {
text = captionedContent(formatter("giphyMessage"), content.caption);
+ } else if (content.kind === "p2p_swap_content" || content.kind === "p2p_swap_content_initial") {
+ text = captionedContent("p2p swap", content.caption);
} else if (content.kind === "prize_content" || content.kind === "prize_content_initial") {
text = captionedContent(formatter("prizeMessage"), content.caption);
} else if (content.kind === "prize_winner_content") {
@@ -319,3 +324,75 @@ export function contentTypeToPermission(contentType: AttachmentContent["kind"]):
throw new UnsupportedValueError("Unknown attachment content type", contentType);
}
}
+
+export function mapAcceptP2PSwapResponseToStatus(response: AcceptP2PSwapResponse, userId: string): P2PSwapStatus {
+ switch (response.kind) {
+ case "success": return {
+ kind: "p2p_swap_accepted",
+ acceptedBy: userId,
+ token1TxnIn: response.token1TxnIn
+ };
+ case "already_reserved": return {
+ kind: "p2p_swap_reserved",
+ reservedBy: response.reservedBy
+ };
+ case "already_accepted": return {
+ kind: "p2p_swap_accepted",
+ acceptedBy: response.acceptedBy,
+ token1TxnIn: response.token1TxnIn
+ };
+ case "already_completed": return {
+ kind: "p2p_swap_completed",
+ acceptedBy: response.acceptedBy,
+ token1TxnIn: response.token1TxnIn,
+ token0TxnOut: response.token0TxnOut,
+ token1TxnOut: response.token1TxnOut,
+ };
+ case "swap_cancelled": return {
+ kind: "p2p_swap_cancelled",
+ token0TxnOut: response.token0TxnOut,
+ };
+ case "swap_expired": return {
+ kind: "p2p_swap_expired",
+ token0TxnOut: response.token0TxnOut,
+ };
+ default: return {
+ kind: "p2p_swap_open"
+ };
+ }
+}
+
+export function mapCancelP2PSwapResponseToStatus(response: CancelP2PSwapResponse): P2PSwapStatus {
+ switch (response.kind) {
+ case "success": return {
+ kind: "p2p_swap_cancelled"
+ };
+ case "already_reserved": return {
+ kind: "p2p_swap_reserved",
+ reservedBy: response.reservedBy
+ };
+ case "already_accepted": return {
+ kind: "p2p_swap_accepted",
+ acceptedBy: response.acceptedBy,
+ token1TxnIn: response.token1TxnIn
+ };
+ case "already_completed": return {
+ kind: "p2p_swap_completed",
+ acceptedBy: response.acceptedBy,
+ token1TxnIn: response.token1TxnIn,
+ token0TxnOut: response.token0TxnOut,
+ token1TxnOut: response.token1TxnOut,
+ };
+ case "swap_cancelled": return {
+ kind: "p2p_swap_cancelled",
+ token0TxnOut: response.token0TxnOut,
+ };
+ case "swap_expired": return {
+ kind: "p2p_swap_expired",
+ token0TxnOut: response.token0TxnOut,
+ };
+ default: return {
+ kind: "p2p_swap_open"
+ };
+ }
+}
diff --git a/frontend/openchat-worker/src/worker.ts b/frontend/openchat-worker/src/worker.ts
index bb2079ed12..0b3287cb87 100644
--- a/frontend/openchat-worker/src/worker.ts
+++ b/frontend/openchat-worker/src/worker.ts
@@ -1473,6 +1473,30 @@ self.addEventListener("message", (msg: MessageEvent) =>
executeThenReply(payload, correlationId, agent.getTranslationCorrections());
break;
+ case "acceptP2PSwap":
+ executeThenReply(
+ payload,
+ correlationId,
+ agent.acceptP2PSwap(
+ payload.chatId,
+ payload.threadRootMessageIndex,
+ payload.messageId,
+ ),
+ );
+ break;
+
+ case "cancelP2PSwap":
+ executeThenReply(
+ payload,
+ correlationId,
+ agent.cancelP2PSwap(
+ payload.chatId,
+ payload.threadRootMessageIndex,
+ payload.messageId,
+ ),
+ );
+ break;
+
default:
logger?.debug("WORKER: unknown message kind received: ", kind);
}