Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion infrastructure/bicep/container-apps/apps/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ environmentOptions=("loc" "dev" "prd")
# "jdmd": JuiceDollar Mainnet dApp
# "jdmm": JuiceDollar Mainnet Monitoring
# "rup": realUnit Ponder
appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "jdtp" "jdta" "jdtd" "jdtm" "jdmp" "jdma" "jdmd" "jdmm" "rup")
# "nbt": Nanobot
appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "jdtp" "jdta" "jdtd" "jdtm" "jdmp" "jdma" "jdmd" "jdmm" "rup" "nbt")

# --- FUNCTIONS --- #
selectOption() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"fileShareQuota": {
"value": 50
},
"containerImage": {
"value": "dfxswiss/nanobot:latest"
},
"containerVolumeMounts": {
"value": [
{
"volumeName": "volume",
"mountPath": "/root/.nanobot"
}
]
},
"containerCPU": {
"value": "1"
},
"containerMemory": {
"value": "2Gi"
},
"containerMinReplicas": {
"value": 1
},
"containerMaxReplicas": {
"value": 1
},
"containerIngressTargetPort": {
"value": 18790
},
"containerIngressAdditionalPorts": {
"value": []
},
"containerProbes": {
"value": []
},
"containerEnv": {
"value": [
{
"name": "ANTHROPIC_API_KEY",
"value": "[ANTHROPIC_API_KEY]"
},
{
"name": "TELEGRAM_BOT_TOKEN",
"value": "[TELEGRAM_BOT_TOKEN]"
},
{
"name": "TELEGRAM_USER_ID",
"value": "[TELEGRAM_USER_ID]"
},
{
"name": "GH_TOKEN",
"value": "[GH_TOKEN]"
}
]
},
"containerCommand": {
"value": []
},
"containerArgs": {
"value": []
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ set -e
environmentOptions=("loc" "dev" "prd")

# "rup": RealUnit Ponder
appNameOptions=("rup")
# "nbt": Nanobot
appNameOptions=("rup" "nbt")

# --- FUNCTIONS --- #
selectOption() {
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -90,42 +90,12 @@ describe('RealUnitBlockchainService', () => {
});

describe('getBrokerbotSellPrice', () => {
it('should query BrokerBot contract and apply default 0.5% slippage', async () => {
it('should query BrokerBot contract and return exact amount', async () => {
// BrokerBot returns 1000 ZCHF (in Wei) for 10 shares
mockReadContract.mockResolvedValue(BigInt('1000000000000000000000'));

const result = await service.getBrokerbotSellPrice(MOCK_BROKERBOT_ADDRESS, 10);

// 1000 ZCHF * (1 - 0.005) = 995 ZCHF
expect(result.zchfAmountWei).toBe(BigInt('995000000000000000000'));
});

it('should calculate correctly for 1 share', async () => {
// BrokerBot returns 100 ZCHF for 1 share
mockReadContract.mockResolvedValue(BigInt('100000000000000000000'));

const result = await service.getBrokerbotSellPrice(MOCK_BROKERBOT_ADDRESS, 1);

// 100 * 0.995 = 99.5 ZCHF
expect(result.zchfAmountWei).toBe(BigInt('99500000000000000000'));
});

it('should accept custom slippage in basis points', async () => {
// BrokerBot returns 1000 ZCHF for 10 shares
mockReadContract.mockResolvedValue(BigInt('1000000000000000000000'));

const result = await service.getBrokerbotSellPrice(MOCK_BROKERBOT_ADDRESS, 10, 100); // 1% slippage

// 1000 * (1 - 0.01) = 990 ZCHF
expect(result.zchfAmountWei).toBe(BigInt('990000000000000000000'));
});

it('should handle zero slippage', async () => {
mockReadContract.mockResolvedValue(BigInt('1000000000000000000000'));

const result = await service.getBrokerbotSellPrice(MOCK_BROKERBOT_ADDRESS, 10, 0);

// Full amount with no slippage
expect(result.zchfAmountWei).toBe(BigInt('1000000000000000000000'));
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,7 @@ export class RealUnitBlockchainService {
};
}

async getBrokerbotSellPrice(
brokerbotAddress: string,
shares: number,
slippageBps = 50,
): Promise<{ zchfAmountWei: bigint }> {
async getBrokerbotSellPrice(brokerbotAddress: string, shares: number): Promise<{ zchfAmountWei: bigint }> {
const blockchain = [Environment.DEV, Environment.LOC].includes(GetConfig().environment)
? Blockchain.SEPOLIA
: Blockchain.ETHEREUM;
Expand All @@ -150,21 +146,17 @@ export class RealUnitBlockchainService {
});

// Call getSellPrice on the BrokerBot contract
const sellPriceWei = (await publicClient.readContract({
const zchfAmountWei = (await publicClient.readContract({
address: brokerbotAddress as `0x${string}`,
abi: BROKERBOT_ABI,
functionName: 'getSellPrice',
args: [BigInt(shares)],
} as any)) as bigint;

if (sellPriceWei === 0n) {
if (zchfAmountWei === 0n) {
throw new Error('BrokerBot returned zero sell price');
}

// Apply slippage buffer (reduce expected amount to account for price movement)
const slippageFactor = BigInt(10000 - slippageBps);
const zchfAmountWei = (sellPriceWei * slippageFactor) / BigInt(10000);

return { zchfAmountWei };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export class ScryptWebSocketConnection {

// --- PUBLIC METHODS --- //

async send(request: ScryptRequest): Promise<ScryptMessage> {
return this.request(request);
async send(type: ScryptMessageType, data: any[]): Promise<void> {
return this.notify({ type, data });
}

async fetch<T>(streamName: ScryptMessageType, filters?: Record<string, unknown>): Promise<T[]> {
Expand All @@ -98,7 +98,8 @@ export class ScryptWebSocketConnection {
}

async requestAndWaitForUpdate<T>(
request: ScryptRequest,
type: ScryptMessageType,
data: any[],
streamName: ScryptMessageType,
matcher: (data: T[]) => T | null,
timeoutMs: number,
Expand All @@ -118,7 +119,7 @@ export class ScryptWebSocketConnection {
}
});

this.request(request, timeoutMs).catch((error) => {
this.request({ type, data }, timeoutMs).catch((error) => {
clearTimeout(timeoutId);
unsubscribe();
reject(error);
Expand Down Expand Up @@ -247,6 +248,15 @@ export class ScryptWebSocketConnection {

// --- REQUEST/RESPONSE --- //

private async notify(message: ScryptRequest): Promise<void> {
const ws = await this.ensureConnected();

const reqId = ++this.reqIdCounter;
const request: ScryptRequest = { ...message, reqid: reqId };

ws.send(JSON.stringify(request));
}

private async request(message: ScryptRequest, timeoutMs = 30000): Promise<ScryptMessage> {
const ws = await this.ensureConnected();

Expand Down
96 changes: 36 additions & 60 deletions src/integration/exchange/services/scrypt.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,21 @@ export class ScryptService extends PricingProvider {
): Promise<ScryptWithdrawResponse> {
const clReqId = randomUUID();

const withdrawRequest = {
type: ScryptMessageType.NEW_WITHDRAW_REQUEST,
data: [
{
Quantity: amount.toString(),
Currency: currency,
MarketAccount: 'default',
RoutingInfo: {
WalletAddress: address,
Memo: memo ?? '',
DestinationTag: '',
},
ClReqID: clReqId,
},
],
const withdrawData = {
Quantity: amount.toString(),
Currency: currency,
MarketAccount: 'default',
RoutingInfo: {
WalletAddress: address,
Memo: memo ?? '',
DestinationTag: '',
},
ClReqID: clReqId,
};

const transaction = await this.connection.requestAndWaitForUpdate<ScryptBalanceTransaction>(
withdrawRequest,
ScryptMessageType.NEW_WITHDRAW_REQUEST,
[withdrawData],
ScryptMessageType.BALANCE_TRANSACTION,
(transactions) =>
transactions.find((t) => t.ClReqID === clReqId && t.TransactionType === ScryptTransactionType.WITHDRAWAL) ??
Expand Down Expand Up @@ -163,21 +159,15 @@ export class ScryptService extends PricingProvider {
timeStamp: Date;
txHashes?: string[];
}): Promise<void> {
const request = {
type: ScryptMessageType.NEW_DEPOSIT_REQUEST,
reqid: Date.now(),
data: [
{
Currency: params.currency,
ClReqID: params.reqId,
Quantity: params.amount.toString(),
TransactTime: params.timeStamp.toISOString(),
TxHashes: (params.txHashes?.length ? params.txHashes : [params.reqId]).map((hash) => ({ TxHash: hash })),
},
],
const depositData = {
Currency: params.currency,
ClReqID: params.reqId,
Quantity: params.amount.toString(),
TransactTime: params.timeStamp.toISOString(),
TxHashes: (params.txHashes?.length ? params.txHashes : [params.reqId]).map((hash) => ({ TxHash: hash })),
};

await this.connection.send(request);
await this.connection.send(ScryptMessageType.NEW_DEPOSIT_REQUEST, [depositData]);
}

// --- TRANSACTIONS --- //
Expand Down Expand Up @@ -406,13 +396,9 @@ export class ScryptService extends PricingProvider {
orderData.Price = price.toString();
}

const orderRequest = {
type: ScryptMessageType.NEW_ORDER_SINGLE,
data: [orderData],
};

const report = await this.connection.requestAndWaitForUpdate<ScryptExecutionReport>(
orderRequest,
ScryptMessageType.NEW_ORDER_SINGLE,
[orderData],
ScryptMessageType.EXECUTION_REPORT,
(reports) => reports.find((r) => r.ClOrdID === clOrdId) ?? null,
60000,
Expand All @@ -430,24 +416,19 @@ export class ScryptService extends PricingProvider {

private async cancelOrder(clOrdId: string, from: string, to: string): Promise<boolean> {
const { symbol } = await this.getTradePair(from, to);
const origClOrdId = clOrdId;
const newClOrdId = randomUUID();

const cancelRequest = {
type: ScryptMessageType.ORDER_CANCEL_REQUEST,
data: [
{
OrigClOrdID: origClOrdId,
ClOrdID: newClOrdId,
Symbol: symbol,
},
],
const cancelData = {
OrigClOrdID: clOrdId,
ClOrdID: newClOrdId,
Symbol: symbol,
};

const report = await this.connection.requestAndWaitForUpdate<ScryptExecutionReport>(
cancelRequest,
ScryptMessageType.ORDER_CANCEL_REQUEST,
[cancelData],
ScryptMessageType.EXECUTION_REPORT,
(reports) => reports.find((r) => r.OrigClOrdID === origClOrdId || r.ClOrdID === newClOrdId) ?? null,
(reports) => reports.find((r) => r.OrigClOrdID === clOrdId || r.ClOrdID === newClOrdId) ?? null,
60000,
);

Expand All @@ -462,24 +443,19 @@ export class ScryptService extends PricingProvider {
newPrice: number,
): Promise<string> {
const { symbol } = await this.getTradePair(from, to);
const origClOrdId = clOrdId;
const newClOrdId = randomUUID();

const replaceRequest = {
type: ScryptMessageType.ORDER_CANCEL_REPLACE_REQUEST,
data: [
{
OrigClOrdID: origClOrdId,
ClOrdID: newClOrdId,
Symbol: symbol,
OrderQty: newQuantity.toString(),
Price: newPrice.toString(),
},
],
const editData = {
OrigClOrdID: clOrdId,
ClOrdID: newClOrdId,
Symbol: symbol,
OrderQty: newQuantity.toString(),
Price: newPrice.toString(),
};

const report = await this.connection.requestAndWaitForUpdate<ScryptExecutionReport>(
replaceRequest,
ScryptMessageType.ORDER_CANCEL_REPLACE_REQUEST,
[editData],
ScryptMessageType.EXECUTION_REPORT,
(reports) => reports.find((r) => r.ClOrdID === newClOrdId) ?? null,
60000,
Expand Down
Loading
Loading