Skip to content

Commit

Permalink
feat: added sign-txn functionality to xrp app
Browse files Browse the repository at this point in the history
  • Loading branch information
muzaffarbhat07 committed Oct 5, 2024
1 parent d242be6 commit 591d689
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 1 deletion.
4 changes: 4 additions & 0 deletions packages/app-xrp/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class XrpApp {
);
}

public async signTxn(params: operations.ISignTxnParams) {
return this.sdk.runOperation(() => operations.signTxn(this.sdk, params));
}

public async destroy() {
return this.sdk.destroy();
}
Expand Down
1 change: 1 addition & 0 deletions packages/app-xrp/src/operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './getPublicKeys';
export * from './getUserVerifiedPublicKey';
export * from './runGetPublicKeys';
export * from './signTxn';
105 changes: 105 additions & 0 deletions packages/app-xrp/src/operations/signTxn/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ISDK } from '@cypherock/sdk-core';
import {
createStatusListener,
assert,
hexToUint8Array,
uint8ArrayToHex,
createLoggerWithPrefix,
} from '@cypherock/sdk-utils';
import { APP_VERSION } from '../../constants/appId';
import {
SeedGenerationStatus,
SignTxnStatus,
} from '../../proto/generated/types';
import {
assertOrThrowInvalidResult,
getXrpLib,
OperationHelper,
logger as rootLogger,
} from '../../utils';
import { ISignTxnParams, ISignTxnResult, SignTxnEvent } from './types';

export * from './types';

const logger = createLoggerWithPrefix(rootLogger, 'SignTxn');

export const signTxn = async (
sdk: ISDK,
params: ISignTxnParams,
): Promise<ISignTxnResult> => {
assert(params, 'Params should be defined');
assert(params.walletId, 'walletId should be defined');
assert(params.txn, 'txn should be defined');
assert(typeof params.txn === 'object', 'txn should be an object');
assert(
typeof params.txn.rawTxn === 'object',
'txn.rawTxn should be an object',
);
assert(
typeof params.txn.txnHex === 'string',
'txn.txnHex should be a string',
);
assert(params.derivationPath, 'derivationPath should be defined');
assert(
params.derivationPath.length === 5,
'derivationPath should be equal to 5',
);

await sdk.checkAppCompatibility(APP_VERSION);

const { onStatus, forceStatusUpdate } = createStatusListener({
enums: SignTxnEvent,
operationEnums: SignTxnStatus,
seedGenerationEnums: SeedGenerationStatus,
onEvent: params.onEvent,
logger,
});

const helper = new OperationHelper({
sdk,
queryKey: 'signTxn',
resultKey: 'signTxn',
onStatus,
});

const txnBytes = hexToUint8Array(params.txn.txnHex);

await helper.sendQuery({
initiate: {
walletId: params.walletId,
derivationPath: params.derivationPath,
transactionSize: txnBytes.length,
},
});

const { confirmation } = await helper.waitForResult();
assertOrThrowInvalidResult(confirmation);
forceStatusUpdate(SignTxnEvent.CONFIRM);

await helper.sendInChunks(txnBytes, 'txnData', 'dataAccepted');

await helper.sendQuery({
signature: {},
});
const result = await helper.waitForResult();
assertOrThrowInvalidResult(result.signature);

forceStatusUpdate(SignTxnEvent.PIN_CARD);

const signature = uint8ArrayToHex(result.signature.signature);

let serializedTxn: string | undefined;

if (params.serializeTxn) {
const signedTransaction = {
...params.txn.rawTxn,
TxnSignature: signature,
};
serializedTxn = getXrpLib().encode(signedTransaction);
}

return {
signature,
serializedTxn,
};
};
30 changes: 30 additions & 0 deletions packages/app-xrp/src/operations/signTxn/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Payment as PaymentTransaction } from 'xrpl';

export enum SignTxnEvent {
INIT = 0,
CONFIRM = 1,
VERIFY = 2,
PASSPHRASE = 3,
PIN_CARD = 4,
}

export type SignTxnEventHandler = (event: SignTxnEvent) => void;

export interface IUnsignedTransaction {
rawTxn: PaymentTransaction;
txnHex: string;
}

export interface ISignTxnParams {
onEvent?: SignTxnEventHandler;

walletId: Uint8Array;
derivationPath: number[];
txn: IUnsignedTransaction;
serializeTxn?: boolean;
}

export interface ISignTxnResult {
signature: string;
serializedTxn?: string;
}
1 change: 1 addition & 0 deletions packages/app-xrp/src/operations/types.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './runGetPublicKeys/types';
export * from './getUserVerifiedPublicKey/types';
export * from './signTxn/types';
54 changes: 54 additions & 0 deletions packages/app-xrp/src/utils/operationHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ISDK } from '@cypherock/sdk-core';
import { DeviceAppError, DeviceAppErrorType } from '@cypherock/sdk-interfaces';
import { OnStatus } from '@cypherock/sdk-utils';
import { ChunkPayload, ChunkAck } from '../proto/generated/common';
import { DeepPartial, Exact, Query, Result } from '../proto/generated/xrp/core';
import { assertOrThrowInvalidResult, parseCommonError } from './asserts';

Expand Down Expand Up @@ -33,6 +34,8 @@ export class OperationHelper<Q extends QueryKey, R extends ResultKey> {

private readonly onStatus?: OnStatus;

private static readonly CHUNK_SIZE = 5120;

constructor(params: {
sdk: ISDK;
queryKey: Q;
Expand Down Expand Up @@ -64,4 +67,55 @@ export class OperationHelper<Q extends QueryKey, R extends ResultKey> {

return resultData;
}

private static splitIntoChunks(txn: Uint8Array): Uint8Array[] {
const chunks: Uint8Array[] = [];
const totalChunks = Math.ceil(txn.length / OperationHelper.CHUNK_SIZE);

for (let i = 0; i < totalChunks; i += 1) {
const chunk = txn.slice(
i * OperationHelper.CHUNK_SIZE,
i * OperationHelper.CHUNK_SIZE + OperationHelper.CHUNK_SIZE,
);
chunks.push(chunk);
}

return chunks;
}

public async sendInChunks<
RK extends keyof Exclude<Result[R], null | undefined>,
QK extends keyof Exclude<Query[Q], null | undefined>,
>(data: Uint8Array, queryKey: QK, resultKey: RK) {
const chunks = OperationHelper.splitIntoChunks(data);
let remainingSize = data.length;

for (let i = 0; i < chunks.length; i += 1) {
const chunk = chunks[i];
remainingSize -= chunk.length;

const chunkPayload: ChunkPayload = {
chunk,
chunkIndex: i,
totalChunks: chunks.length,
remainingSize,
};

await this.sendQuery({
[queryKey]: {
chunkPayload,
},
});

const result = await this.waitForResult();
assertOrThrowInvalidResult(result[resultKey]);

const { chunkAck } = result[resultKey] as {
chunkAck: ChunkAck;
};

assertOrThrowInvalidResult(chunkAck);
assertOrThrowInvalidResult(chunkAck.chunkIndex === i);
}
}
}
2 changes: 1 addition & 1 deletion submodules/common

0 comments on commit 591d689

Please sign in to comment.