Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): stx_transferStx method support #6105

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
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
29 changes: 29 additions & 0 deletions src/app/pages/rpc-stx-transfer-stx/rpc-stx-transfer-stx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { isDefined } from '@leather.io/utils';

import { StacksHighFeeWarningContainer } from '@app/features/stacks-high-fee-warning/stacks-high-fee-warning-container';
import { StacksTransactionSigner } from '@app/features/stacks-transaction-request/stacks-transaction-signer';
import { useBreakOnNonCompliantEntity } from '@app/query/common/compliance-checker/compliance-checker.query';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';

import { useRpcStxTransferStx } from './use-rpc-stx-transfer-stx';

export function RpcStxTransferStx() {
const { params, onCancel } = useRpcStxTransferStx();

// Grab account details from store
const currentStacksAccount = useCurrentStacksAccount();
useBreakOnNonCompliantEntity([currentStacksAccount?.address, params.recipient].filter(isDefined));

return (
<StacksHighFeeWarningContainer>
<StacksTransactionSigner
onSignStacksTransaction={onSignStacksTransaction}

Check failure on line 20 in src/app/pages/rpc-stx-transfer-stx/rpc-stx-transfer-stx.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'onSignStacksTransaction'.
onCancel={onCancel}
isMultisig={false}
stacksTransaction={stacksTransaction}

Check failure on line 23 in src/app/pages/rpc-stx-transfer-stx/rpc-stx-transfer-stx.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Cannot find name 'stacksTransaction'.
disableFeeSelection={true}
disableNonceSelection={true}
/>
</StacksHighFeeWarningContainer>
);
}
86 changes: 86 additions & 0 deletions src/app/pages/rpc-stx-transfer-stx/use-rpc-stx-transfer-stx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useMemo } from 'react';

import { makeUnsignedSTXTokenTransfer } from '@stacks/transactions';

import { RpcErrorCode, type StxTransferStxRequestParams } from '@leather.io/rpc';

import { logger } from '@shared/logger';
import { makeRpcErrorResponse, makeRpcSuccessResponse } from '@shared/rpc/rpc-methods';
import { closeWindow } from '@shared/utils';

import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params';
import { initialSearchParams } from '@app/common/initial-search-params';
import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction';

export function useRpcStxTransferStxParams() {
const { origin, tabId } = useDefaultRequestParams();
const requestId = initialSearchParams.get('requestId');
const params = initialSearchParams.get('params');

if (!origin || !params || !requestId) throw new Error('Invalid params');

return useMemo(
() => ({
origin,
tabId: tabId ?? 0,
requestId,
params: JSON.parse(decodeURIComponent(params)),
// params: stxTransferStxRequestParamsSchema.parse(JSON.parse(decodeURIComponent(params))),
}),
[origin, tabId, requestId, params]
);
}

export function useRpcStxTransferStx() {
const { origin, params, requestId, tabId } = useRpcStxTransferStxParams();
const { stacksBroadcastTransaction } = useStacksBroadcastTransaction({ token: '' });

return useMemo(
() => ({
origin,
// TODO: replace with schema infererence
params: params as StxTransferStxRequestParams,
async onSignStacksTransaction(fee: number, nonce: number) {
const tx = await makeUnsignedSTXTokenTransfer({
recipient: params.recipient,
amount: params.amount,
memo: params.memo,
publicKey: params.publicKey,
});

if (!tx) return logger.error('No stacks transaction to sign');

tx.setFee(fee);
tx.setNonce(nonce);

const result = await stacksBroadcastTransaction(tx);
if (!result) throw new Error('Error broadcasting stacks transaction');

chrome.tabs.sendMessage(
tabId,
makeRpcSuccessResponse('stx_transferStx', {
id: requestId,
result: {
txid: result.txid,
transaction: result.transaction.serialize(),
} as any,
})
);
closeWindow();
},
onCancel() {
chrome.tabs.sendMessage(
tabId,
makeRpcErrorResponse('stx_transferStx', {
id: requestId,
error: {
message: 'User denied signing stacks transaction',
code: RpcErrorCode.USER_REJECTION,
},
})
);
},
}),
[origin, params, requestId, stacksBroadcastTransaction, tabId]
);
}
13 changes: 13 additions & 0 deletions src/app/routes/rpc-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RpcSignPsbtSummary } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt-summa
import { RpcStacksMessageSigning } from '@app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message';
import { RpcSignStacksTransaction } from '@app/pages/rpc-sign-stacks-transaction/rpc-sign-stacks-transaction';
import { RpcStxCallContract } from '@app/pages/rpc-stx-call-contract/rpc-stx-call-contract';
import { RpcStxTransferStx } from '@app/pages/rpc-stx-transfer-stx/rpc-stx-transfer-stx';
import { AccountGate } from '@app/routes/account-gate';

import { SuspenseLoadingSpinner } from './app-routes';
Expand Down Expand Up @@ -96,5 +97,17 @@ export const rpcRequestRoutes = (
{ledgerStacksTxSigningRoutes}
<Route path={RouteUrls.EditNonce} element={<EditNonceSheet />} />
</Route>

<Route
path={RouteUrls.RpcStxTransferStx}
element={
<AccountGate>
<RpcStxTransferStx />
</AccountGate>
}
>
{ledgerStacksTxSigningRoutes}
<Route path={RouteUrls.EditNonce} element={<EditNonceSheet />} />
</Route>
</>
);
12 changes: 10 additions & 2 deletions src/background/messaging/rpc-message-handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RpcErrorCode } from '@leather.io/rpc';
import { RpcErrorCode, stxTransferStxMethodName } from '@leather.io/rpc';

import { WalletRequests, makeRpcErrorResponse } from '@shared/rpc/rpc-methods';

Expand All @@ -18,11 +18,14 @@
} from './rpc-methods/sign-stacks-message';
import { rpcStxCallContract } from './rpc-methods/stx-call-contract';
import { rpcStxGetAddresses } from './rpc-methods/stx-get-addresses';
import { rpcStxTransferStx } from './rpc-methods/stx-transfer-stx';
import { rpcSupportedMethods } from './rpc-methods/supported-methods';

export async function rpcMessageHandler(message: WalletRequests, port: chrome.runtime.Port) {
listenForOriginTabClose({ tabId: port.sender?.tab?.id });

console.log(message);

Check failure on line 27 in src/background/messaging/rpc-message-handler.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Unexpected console statement

switch (message.method) {
case 'open': {
await rpcOpen(message, port);
Expand Down Expand Up @@ -82,10 +85,15 @@
break;
}

case stxTransferStxMethodName: {
await rpcStxTransferStx(message, port);
break;
}

default:
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('' as any, {
makeRpcErrorResponse(message.method, {
id: message.id,
error: {
code: RpcErrorCode.METHOD_NOT_FOUND,
Expand Down
34 changes: 34 additions & 0 deletions src/background/messaging/rpc-methods/stx-transfer-stx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { RpcErrorCode, type StxTransferStxRequest } from '@leather.io/rpc';

import { RouteUrls } from '@shared/route-urls';
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';

import {
type RequestParams,
listenForPopupClose,
makeSearchParamsWithDefaults,
triggerRequestWindowOpen,
} from '../messaging-utils';

export async function rpcStxTransferStx(message: StxTransferStxRequest, port: chrome.runtime.Port) {
const requestParams: RequestParams = [
['params', encodeURIComponent(JSON.stringify(message.params))],
['requestId', message.id],
];

const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams);

const { id } = await triggerRequestWindowOpen(RouteUrls.RpcStxTransferStx, urlParams);

listenForPopupClose({
tabId,
id,
response: makeRpcErrorResponse('stx_transferStx', {
id: message.id,
error: {
code: RpcErrorCode.USER_REJECTION,
message: 'User rejected the Stacks transaction signing request',
},
}),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ export function createBitcoinTransactionMonitor(addresses: MonitoredAddress[]):
await handleTransactionMessage(message);
} else if (message['conversions']) {
_btcPriceUsd = readMempooWsBtcPriceUsd(message['conversions']);
} else {
logger.debug('Unrecognized Message Type: ', event.data);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/shared/route-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,5 @@ export enum RouteUrls {
// Request routes stacks
RpcSignStacksTransaction = '/sign-stacks-transaction',
RpcStxCallContract = '/stx-call-contract',
RpcStxTransferStx = '/stx-transfer-stx',
}
5 changes: 5 additions & 0 deletions src/shared/rpc/rpc-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ export function makeRpcErrorResponse<T extends WalletMethodNames>(
) {
return { jsonrpc: '2.0', ...response } as LeatherRpcMethodMap[T]['response'];
}

// makeRpcSuccessResponse('stx_transferStx', {
// id: '123',
// result: {},
// });
Loading