diff --git a/packages/core/src/api/stacks/StacksGetAddress.ts b/packages/core/src/api/stacks/StacksGetAddress.ts new file mode 100644 index 000000000..8fb230cfd --- /dev/null +++ b/packages/core/src/api/stacks/StacksGetAddress.ts @@ -0,0 +1,83 @@ +import { StacksGetAddress as HardwareNexaGetAddress } from '@onekeyfe/hd-transport'; +import { UI_REQUEST } from '../../constants/ui-request'; +import { serializedPath, validatePath } from '../helpers/pathUtils'; +import { BaseMethod } from '../BaseMethod'; +import { validateParams } from '../helpers/paramsValidator'; +import { StacksGetAddressParams } from '../../types'; + +export default class StacksGetAddress extends BaseMethod { + hasBundle = false; + + init() { + this.checkDeviceId = true; + this.notAllowDeviceMode = [...this.notAllowDeviceMode, UI_REQUEST.INITIALIZE]; + + this.hasBundle = !!this.payload?.bundle; + const payload = this.hasBundle ? this.payload : { bundle: [this.payload] }; + + // check payload + validateParams(payload, [{ name: 'bundle', type: 'array' }]); + + // init params + this.params = []; + payload.bundle.forEach((batch: StacksGetAddressParams) => { + const addressN = validatePath(batch.path, 3); + + validateParams(batch, [ + { name: 'path', required: true }, + { name: 'showOnOneKey', type: 'boolean' }, + { name: 'prefix', type: 'string' }, + { name: 'scheme', type: 'string' }, + ]); + + const showOnOneKey = batch.showOnOneKey ?? true; + + this.params.push({ + address_n: addressN, + show_display: showOnOneKey, + prefix: batch.prefix, + // @ts-expect-error + scheme: batch.scheme, + }); + }); + } + + getVersionRange() { + return { + model_mini: { + min: '3.2.0', + }, + model_touch: { + min: '4.4.0', + }, + }; + } + + async run() { + const responses: { + path: string; + pub: string; + address: string; + }[] = []; + + for (let i = 0; i < this.params.length; i++) { + const param = this.params[i]; + + const res = await this.device.commands.typedCall('StacksGetAddress', 'StacksAddress', { + ...param, + }); + + const { address } = res.message; + + const result = { + path: serializedPath(param.address_n), + pub: res.message.public_key, + address, + }; + responses.push(result); + this.postPreviousAddressMessage(result); + } + + return Promise.resolve(this.hasBundle ? responses : responses[0]); + } +} diff --git a/packages/core/src/api/stacks/StacksSignTransaction.ts b/packages/core/src/api/stacks/StacksSignTransaction.ts new file mode 100644 index 000000000..79611c715 --- /dev/null +++ b/packages/core/src/api/stacks/StacksSignTransaction.ts @@ -0,0 +1,105 @@ +import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared'; +import { TypedCall } from '@onekeyfe/hd-transport'; +import { TypedResponseMessage } from '../../device/DeviceCommands'; +import { BaseMethod } from '../BaseMethod'; +import { validateParams } from '../helpers/paramsValidator'; +import { validatePath } from '../helpers/pathUtils'; +import { StacksSignTransactionParams, StacksSignature } from '../../types/api/stacksSignTransaction'; + +export default class StacksSignTransaction extends BaseMethod { + hasBundle = false; + + init() { + const payload = this.payload as StacksSignTransactionParams; + + payload.inputs.forEach(input => { + validateParams(input, [ + { name: 'path', type: 'string', required: true }, + { name: 'message', type: 'string', required: true }, + { name: 'prefix', type: 'string', required: true }, + ]); + return input; + }); + this.params = payload; + } + + getVersionRange() { + return { + model_mini: { + min: '3.2.0', + }, + model_touch: { + min: '4.4.0', + }, + }; + } + + async processTxRequest( + typedCall: TypedCall, + res: TypedResponseMessage<'StacksTxInputRequest'> | TypedResponseMessage<'StacksSignedTx'>, + index: number, + signatures: StacksSignature[] + ): Promise { + const { signature } = res.message; + if (!signature) { + throw ERRORS.TypedError( + HardwareErrorCode.ResponseUnexpectTypeError, + 'signature is not valid' + ); + } + if (res.type === 'StacksSignedTx') { + signatures.push({ + index, + signature, + }); + + return signatures; + } + + if (res.type === 'StacksTxInputRequest') { + signatures.push({ + index, + signature, + }); + + const nextIndex = res.message.request_index; + const input = this.params.inputs[nextIndex]; + const response = await typedCall( + 'StacksTxInputAck', + // @ts-expect-error + ['StacksTxInputRequest', 'StacksSignedTx'], + { + address_n: input.path, + raw_message: input.message, + } + ); + + // @ts-expect-error + return this.processTxRequest(typedCall, response, nextIndex, signatures); + } + + return signatures; + } + + async run() { + const { device, params } = this; + const input = params.inputs[0]; + + const response = await device.commands.typedCall( + 'StacksSignTx', + ['StacksTxInputRequest', 'StacksSignedTx'], + { + address_n: validatePath(input.path, 3), + raw_message: input.message, + prefix: input.prefix, + input_count: params.inputs.length, + } + ); + return this.processTxRequest( + device.commands.typedCall.bind(device.commands), + response as any, + 0, + [] + ); + } +} diff --git a/packages/core/src/inject.ts b/packages/core/src/inject.ts index 8c5ab3b5c..fa3b90d06 100644 --- a/packages/core/src/inject.ts +++ b/packages/core/src/inject.ts @@ -271,7 +271,10 @@ export const createCoreApi = ( call({ ...params, connectId, deviceId, method: 'nexaGetAddress' }), nexaSignTransaction: (connectId, deviceId, params) => call({ ...params, connectId, deviceId, method: 'nexaSignTransaction' }), - + stacksGetAddress: (connectId, deviceId, params) => + call({ ...params, connectId, deviceId, method: 'stacksGetAddress' }), + stacksSignTransaction: (connectId, deviceId, params) => + call({ ...params, connectId, deviceId, method: 'stacksSignTransaction' }), nostrGetPublicKey: (connectId, deviceId, params) => call({ ...params, connectId, deviceId, method: 'nostrGetPublicKey' }), nostrSignEvent: (connectId, deviceId, params) => diff --git a/packages/core/src/types/api/index.ts b/packages/core/src/types/api/index.ts index a83050b10..09058a057 100644 --- a/packages/core/src/types/api/index.ts +++ b/packages/core/src/types/api/index.ts @@ -113,6 +113,9 @@ import { kaspaSignTransaction } from './kaspaSignTransaction'; import { nexaGetAddress } from './nexaGetAddress'; import { nexaSignTransaction } from './nexaSignTransaction'; +import { stacksGetAddress } from './stacksGetAddress'; +import { stacksSignTransaction } from './stacksSignTransaction'; + import { nostrGetPublicKey } from './nostrGetPublicKey'; import { nostrSignEvent } from './nostrSignEvent'; import { nostrEncryptMessage } from './nostrEncryptMessage'; @@ -317,6 +320,12 @@ export type CoreApi = { nexaGetAddress: typeof nexaGetAddress; nexaSignTransaction: typeof nexaSignTransaction; + /** + * stacks function + */ + stacksGetAddress: typeof stacksGetAddress; + stacksSignTransaction: typeof stacksSignTransaction; + /** * Nostr function */ diff --git a/packages/core/src/types/api/stacksGetAddress.ts b/packages/core/src/types/api/stacksGetAddress.ts new file mode 100644 index 000000000..976bef7be --- /dev/null +++ b/packages/core/src/types/api/stacksGetAddress.ts @@ -0,0 +1,26 @@ +import type { CommonParams, Response } from '../params'; + +export type StacksAddress = { + path: string; + pub: string; + address: string; +}; + +export type StacksGetAddressParams = { + path: string | number[]; + prefix?: string; + scheme?: string; + showOnOneKey?: boolean; +}; + +export declare function stacksGetAddress( + connectId: string, + deviceId: string, + params: CommonParams & StacksGetAddressParams +): Response; + +export declare function stacksGetAddress( + connectId: string, + deviceId: string, + params: CommonParams & { bundle?: StacksGetAddressParams[] } +): Response>; diff --git a/packages/core/src/types/api/stacksSignTransaction.ts b/packages/core/src/types/api/stacksSignTransaction.ts new file mode 100644 index 000000000..4982a3c2c --- /dev/null +++ b/packages/core/src/types/api/stacksSignTransaction.ts @@ -0,0 +1,28 @@ +import type { CommonParams, Response } from '../params'; + +export type StacksSignature = { + index: number; + signature: string; +}; + +export type StacksSignInputParams = { + path: string; + message: string; + prefix: string; +}; + +export type StacksSignOutputParams = { + satoshis: number | string; + script: string; + scriptVersion: number; +}; + +export type StacksSignTransactionParams = { + inputs: StacksSignInputParams[]; +}; + +export declare function stacksSignTransaction( + connectId: string, + deviceId: string, + params: CommonParams & StacksSignTransactionParams +): Response;