From a9b87c7f663d54c29b5574272df2d42e434a663e Mon Sep 17 00:00:00 2001 From: raksha-r7 Date: Fri, 5 Jun 2026 14:03:58 +0530 Subject: [PATCH] chore: revert irys package from sdk Ticket: SI-695 --- modules/sdk-coin-irys/.mocharc.yml | 8 - modules/sdk-coin-irys/.npmignore | 14 - modules/sdk-coin-irys/CHANGELOG.md | 22 - modules/sdk-coin-irys/package.json | 58 --- modules/sdk-coin-irys/src/index.ts | 1 - .../src/lib/commitmentTransactionBuilder.ts | 253 ---------- modules/sdk-coin-irys/src/lib/iface.ts | 85 ---- modules/sdk-coin-irys/src/lib/index.ts | 3 - modules/sdk-coin-irys/src/lib/utils.ts | 46 -- .../test/unit/commitmentTransactionBuilder.ts | 444 ------------------ modules/sdk-coin-irys/tsconfig.json | 13 - modules/statics/src/allCoinsAndTokens.ts | 5 +- 12 files changed, 4 insertions(+), 948 deletions(-) delete mode 100644 modules/sdk-coin-irys/.mocharc.yml delete mode 100644 modules/sdk-coin-irys/.npmignore delete mode 100644 modules/sdk-coin-irys/CHANGELOG.md delete mode 100644 modules/sdk-coin-irys/package.json delete mode 100644 modules/sdk-coin-irys/src/index.ts delete mode 100644 modules/sdk-coin-irys/src/lib/commitmentTransactionBuilder.ts delete mode 100644 modules/sdk-coin-irys/src/lib/iface.ts delete mode 100644 modules/sdk-coin-irys/src/lib/index.ts delete mode 100644 modules/sdk-coin-irys/src/lib/utils.ts delete mode 100644 modules/sdk-coin-irys/test/unit/commitmentTransactionBuilder.ts delete mode 100644 modules/sdk-coin-irys/tsconfig.json diff --git a/modules/sdk-coin-irys/.mocharc.yml b/modules/sdk-coin-irys/.mocharc.yml deleted file mode 100644 index f499ec0a83..0000000000 --- a/modules/sdk-coin-irys/.mocharc.yml +++ /dev/null @@ -1,8 +0,0 @@ -require: 'tsx' -timeout: '60000' -reporter: 'min' -reporter-option: - - 'cdn=true' - - 'json=false' -exit: true -spec: ['test/unit/**/*.ts'] diff --git a/modules/sdk-coin-irys/.npmignore b/modules/sdk-coin-irys/.npmignore deleted file mode 100644 index d5fb3a098c..0000000000 --- a/modules/sdk-coin-irys/.npmignore +++ /dev/null @@ -1,14 +0,0 @@ -!dist/ -dist/test/ -dist/tsconfig.tsbuildinfo -.idea/ -.prettierrc.yml -tsconfig.json -src/ -test/ -scripts/ -.nyc_output -CODEOWNERS -node_modules/ -.prettierignore -.mocharc.js diff --git a/modules/sdk-coin-irys/CHANGELOG.md b/modules/sdk-coin-irys/CHANGELOG.md deleted file mode 100644 index 57bf67f191..0000000000 --- a/modules/sdk-coin-irys/CHANGELOG.md +++ /dev/null @@ -1,22 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [1.2.0](https://github.com/BitGo/BitGoJS/compare/@bitgo/sdk-coin-irys@1.1.0...@bitgo/sdk-coin-irys@1.2.0) (2026-03-29) - - -### Features - -* **root:** remove node engine upper bound ([9ef2ade](https://github.com/BitGo/BitGoJS/commit/9ef2adee1e8de4db6b194521a8000eb676a09db0)) - - - - - -# 1.1.0 (2026-03-10) - - -### Features - -* **sdk-coin-irys:** add commitment transaction support ([1e6f529](https://github.com/BitGo/BitGoJS/commit/1e6f52925a5899814234e642f7cde327aec50ccc)) diff --git a/modules/sdk-coin-irys/package.json b/modules/sdk-coin-irys/package.json deleted file mode 100644 index a42937e8e8..0000000000 --- a/modules/sdk-coin-irys/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@bitgo/sdk-coin-irys", - "version": "1.2.0", - "description": "BitGo SDK coin library for Irys", - "main": "./dist/src/index.js", - "types": "./dist/src/index.d.ts", - "scripts": { - "build": "yarn tsc --build --incremental --verbose .", - "fmt": "prettier --write .", - "check-fmt": "prettier --check '**/*.{ts,js,json}'", - "clean": "rm -r ./dist", - "lint": "eslint --quiet .", - "prepare": "npm run build", - "test": "npm run coverage", - "coverage": "nyc -- npm run unit-test", - "unit-test": "mocha" - }, - "author": "BitGo SDK Team ", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "repository": { - "type": "git", - "url": "https://github.com/BitGo/BitGoJS.git", - "directory": "modules/sdk-coin-irys" - }, - "lint-staged": { - "*.{js,ts}": [ - "yarn prettier --write", - "yarn eslint --fix" - ] - }, - "publishConfig": { - "access": "public" - }, - "nyc": { - "extension": [ - ".ts" - ] - }, - "dependencies": { - "@ethereumjs/rlp": "^4.0.0", - "bs58": "^4.0.1", - "ethers": "^5.1.3", - "superagent": "^9.0.1" - }, - "devDependencies": { - "@types/sinon": "^10.0.11", - "@types/superagent": "^8.1.0", - "nock": "^13.3.1", - "should": "^13.2.3", - "sinon": "^13.0.1" - }, - "files": [ - "dist" - ] -} diff --git a/modules/sdk-coin-irys/src/index.ts b/modules/sdk-coin-irys/src/index.ts deleted file mode 100644 index f41a696fd2..0000000000 --- a/modules/sdk-coin-irys/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './lib'; diff --git a/modules/sdk-coin-irys/src/lib/commitmentTransactionBuilder.ts b/modules/sdk-coin-irys/src/lib/commitmentTransactionBuilder.ts deleted file mode 100644 index 4785117c0b..0000000000 --- a/modules/sdk-coin-irys/src/lib/commitmentTransactionBuilder.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { RLP } from '@ethereumjs/rlp'; -import { arrayify, keccak256 } from 'ethers/lib/utils'; -import request from 'superagent'; -import { - CommitmentType, - CommitmentTypeId, - CommitmentTransactionFields, - CommitmentTransactionBuildResult, - EncodedSignedCommitmentTransaction, - EncodedCommitmentType, - AnchorInfo, - COMMITMENT_TX_VERSION, -} from './iface'; -import { encodeBase58, decodeBase58ToFixed } from './utils'; - -/** - * Builder for Irys commitment transactions (STAKE, PLEDGE). - * - * Commitment transactions are NOT standard EVM transactions. They use a custom - * 7-field RLP encoding with keccak256 prehash and raw ECDSA signing. - * - * Usage (STAKE): - * const builder = new IrysCommitmentTransactionBuilder(apiUrl, chainId); - * builder.setCommitmentType({ type: CommitmentTypeId.STAKE }); - * builder.setFee(fee); - * builder.setValue(value); - * builder.setSigner(signerAddress); - * const result = await builder.build(); // fetches anchor, RLP encodes, returns prehash - * - * Usage (PLEDGE): - * builder.setCommitmentType({ type: CommitmentTypeId.PLEDGE, pledgeCount: 0n }); - */ -export class IrysCommitmentTransactionBuilder { - private _irysApiUrl: string; - private _chainId: bigint; - private _commitmentType?: CommitmentType; - private _fee?: bigint; - private _value?: bigint; - private _signer?: Uint8Array; // 20 bytes - private _anchor?: Uint8Array; // 32 bytes (set during build, or manually for testing) - - constructor(irysApiUrl: string, chainId: bigint) { - this._irysApiUrl = irysApiUrl; - this._chainId = chainId; - } - - /** - * Set the commitment type for this transaction. - * STAKE is a single-operation type. - * PLEDGE requires pledgeCount. - */ - setCommitmentType(type: CommitmentType): this { - this._commitmentType = type; - return this; - } - - /** Set the transaction fee (from Irys price API) */ - setFee(fee: bigint): this { - this._fee = fee; - return this; - } - - /** Set the transaction value (from Irys price API) */ - setValue(value: bigint): this { - this._value = value; - return this; - } - - /** Set the signer address (20-byte Ethereum address as Uint8Array) */ - setSigner(signer: Uint8Array): this { - if (signer.length !== 20) { - throw new Error(`Signer must be 20 bytes, got ${signer.length}`); - } - this._signer = signer; - return this; - } - - /** - * Manually set the anchor (for testing). If not set, build() fetches it from the API. - */ - setAnchor(anchor: Uint8Array): this { - if (anchor.length !== 32) { - throw new Error(`Anchor must be 32 bytes, got ${anchor.length}`); - } - this._anchor = anchor; - return this; - } - - /** - * Fetch the current anchor (block hash) from the Irys API. - * This is the nonce equivalent for commitment transactions. - * Called during build() if anchor hasn't been manually set. - */ - async fetchAnchor(): Promise { - const response = await request.get(`${this._irysApiUrl}/anchor`).accept('json'); - - if (!response.ok) { - throw new Error(`Failed to fetch anchor: ${response.status} ${response.text}`); - } - - const anchorInfo: AnchorInfo = response.body; - return decodeBase58ToFixed(anchorInfo.blockHash, 32); - } - - /** - * Encode the commitment type for RLP signing. - * - * CRITICAL: STAKE (1) MUST be a flat number, NOT an array. - * PLEDGE MUST be a nested array. The Irys Rust decoder - * rejects non-canonical encoding. - * - * Reference: irys-js/src/common/commitmentTransaction.ts lines 180-199 - */ - static encodeCommitmentTypeForSigning( - type: CommitmentType - ): number | bigint | Uint8Array | (number | bigint | Uint8Array)[] { - switch (type.type) { - case CommitmentTypeId.STAKE: - return CommitmentTypeId.STAKE; // flat number - case CommitmentTypeId.PLEDGE: - return [CommitmentTypeId.PLEDGE, type.pledgeCount]; // nested array - default: - throw new Error(`Unknown commitment type`); - } - } - - /** - * Encode the commitment type for the JSON broadcast payload. - */ - static encodeCommitmentTypeForBroadcast(type: CommitmentType): EncodedCommitmentType { - switch (type.type) { - case CommitmentTypeId.STAKE: - return { type: 'stake' }; - case CommitmentTypeId.PLEDGE: - return { type: 'pledge', pledgeCountBeforeExecuting: type.pledgeCount.toString() }; - default: - throw new Error(`Unknown commitment type`); - } - } - - /** - * Validate that all required fields are set before building. - */ - private validateFields(): void { - if (!this._commitmentType) throw new Error('Commitment type is required'); - if (this._fee === undefined) throw new Error('Fee is required'); - if (this._value === undefined) throw new Error('Value is required'); - if (!this._signer) throw new Error('Signer is required'); - } - - /** - * Build the unsigned commitment transaction. - * - * 1. Validates all fields are set - * 2. Fetches anchor from Irys API (if not manually set) -- done LAST to minimize expiration - * 3. RLP encodes the 7 fields in exact order - * 4. Computes keccak256 prehash - * 5. Returns prehash (for HSM) and rlpEncoded (for HSM validation) - */ - async build(): Promise { - this.validateFields(); - - // Fetch anchor LAST -- it expires in ~45 blocks (~9 min) - if (!this._anchor) { - this._anchor = await this.fetchAnchor(); - } - - const fields: CommitmentTransactionFields = { - version: COMMITMENT_TX_VERSION, - anchor: this._anchor, - signer: this._signer!, - commitmentType: this._commitmentType!, - chainId: this._chainId, - fee: this._fee!, - value: this._value!, - }; - - const rlpEncoded = this.rlpEncode(fields); - const prehash = this.computePrehash(rlpEncoded); - - return { prehash, rlpEncoded, fields }; - } - - /** - * RLP encode the 7 commitment transaction fields. - * - * Field order is CRITICAL and must match the Irys protocol exactly: - * [version, anchor, signer, commitmentType, chainId, fee, value] - * - * Reference: irys-js/src/common/commitmentTransaction.ts lines 405-419 - */ - rlpEncode(fields: CommitmentTransactionFields): Uint8Array { - const rlpFields = [ - fields.version, - fields.anchor, - fields.signer, - IrysCommitmentTransactionBuilder.encodeCommitmentTypeForSigning(fields.commitmentType), - fields.chainId, - fields.fee, - fields.value, - ]; - - return RLP.encode(rlpFields as any); - } - - /** - * Compute the prehash: keccak256(rlpEncoded). - * Returns 32 bytes. - */ - computePrehash(rlpEncoded: Uint8Array): Uint8Array { - const hash = keccak256(rlpEncoded); - return arrayify(hash); - } - - /** - * Compute the transaction ID from a signature. - * txId = base58(keccak256(signature)) - * - * @param signature - 65-byte raw ECDSA signature (r || s || v) - */ - static computeTxId(signature: Uint8Array): string { - if (signature.length !== 65) { - throw new Error(`Signature must be 65 bytes, got ${signature.length}`); - } - const idBytes = arrayify(keccak256(signature)); - return encodeBase58(idBytes); - } - - /** - * Create the JSON broadcast payload from a signed transaction. - * - * @param fields - The transaction fields used to build the transaction - * @param signature - 65-byte raw ECDSA signature - * @returns JSON payload ready for POST /v1/commitment-tx - */ - static createBroadcastPayload( - fields: CommitmentTransactionFields, - signature: Uint8Array - ): EncodedSignedCommitmentTransaction { - const txId = IrysCommitmentTransactionBuilder.computeTxId(signature); - return { - version: fields.version, - anchor: encodeBase58(fields.anchor), - signer: encodeBase58(fields.signer), - commitmentType: IrysCommitmentTransactionBuilder.encodeCommitmentTypeForBroadcast(fields.commitmentType), - chainId: fields.chainId.toString(), - fee: fields.fee.toString(), - value: fields.value.toString(), - id: txId, - signature: encodeBase58(signature), - }; - } -} diff --git a/modules/sdk-coin-irys/src/lib/iface.ts b/modules/sdk-coin-irys/src/lib/iface.ts deleted file mode 100644 index 122f01742d..0000000000 --- a/modules/sdk-coin-irys/src/lib/iface.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Commitment type IDs matching the Irys protocol. - * STAKE is a flat value in RLP encoding. - * PLEDGE is encoded as a nested array. - */ -export enum CommitmentTypeId { - STAKE = 1, - PLEDGE = 2, -} - -export type StakeCommitmentType = { type: CommitmentTypeId.STAKE }; -export type PledgeCommitmentType = { type: CommitmentTypeId.PLEDGE; pledgeCount: bigint }; - -export type CommitmentType = StakeCommitmentType | PledgeCommitmentType; - -/** Version 2 is the current commitment transaction version */ -export const COMMITMENT_TX_VERSION = 2; - -/** Irys chain IDs */ -export const IRYS_MAINNET_CHAIN_ID = 3282n; -export const IRYS_TESTNET_CHAIN_ID = 1270n; - -/** - * The 7 fields of an unsigned commitment transaction, - * in the exact order required for RLP encoding. - */ -export interface CommitmentTransactionFields { - version: number; // 1 byte, always 2 (V2) - anchor: Uint8Array; // 32 bytes (block hash from /v1/anchor) - signer: Uint8Array; // 20 bytes (Ethereum address) - commitmentType: CommitmentType; - chainId: bigint; - fee: bigint; - value: bigint; -} - -/** - * JSON payload for broadcasting a signed commitment transaction - * via POST /v1/commitment-tx - */ -export interface EncodedSignedCommitmentTransaction { - version: number; - anchor: string; // base58 - signer: string; // base58 - commitmentType: EncodedCommitmentType; - chainId: string; // decimal string - fee: string; // decimal string - value: string; // decimal string - id: string; // base58(keccak256(signature)) - signature: string; // base58(65-byte raw signature) -} - -export type EncodedCommitmentType = { type: 'stake' } | { type: 'pledge'; pledgeCountBeforeExecuting: string }; - -/** - * Anchor info returned by GET /v1/anchor - */ -export interface AnchorInfo { - blockHash: string; // base58-encoded 32-byte block hash -} - -/** - * Result of building an unsigned commitment transaction. - * Contains the prehash (for HSM signing) and the RLP-encoded bytes (for HSM validation). - */ -export interface CommitmentTransactionBuildResult { - /** keccak256(rlpEncoded) - 32 bytes, used as prehash for signing */ - prehash: Uint8Array; - /** Full RLP-encoded transaction bytes - sent to HSM for validation before signing */ - rlpEncoded: Uint8Array; - /** The transaction fields used to build this result */ - fields: CommitmentTransactionFields; -} - -/** - * Result after signing. Contains everything needed for broadcast. - */ -export interface SignedCommitmentTransactionResult { - /** Transaction ID: base58(keccak256(signature)) */ - txId: string; - /** 65-byte raw ECDSA signature (r || s || v) */ - signature: Uint8Array; - /** JSON payload ready for POST /v1/commitment-tx */ - broadcastPayload: EncodedSignedCommitmentTransaction; -} diff --git a/modules/sdk-coin-irys/src/lib/index.ts b/modules/sdk-coin-irys/src/lib/index.ts deleted file mode 100644 index f8fa6a5c50..0000000000 --- a/modules/sdk-coin-irys/src/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './iface'; -export * from './commitmentTransactionBuilder'; -export * from './utils'; diff --git a/modules/sdk-coin-irys/src/lib/utils.ts b/modules/sdk-coin-irys/src/lib/utils.ts deleted file mode 100644 index ca7eae5aea..0000000000 --- a/modules/sdk-coin-irys/src/lib/utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import bs58 from 'bs58'; - -/** - * Encode a byte array to Base58 string. - * Used for encoding addresses, anchors, and signatures for the Irys API. - */ -export function encodeBase58(bytes: Uint8Array): string { - return bs58.encode(Buffer.from(bytes)); -} - -/** - * Decode a Base58 string to a byte array. - */ -export function decodeBase58(str: string): Uint8Array { - return Uint8Array.from(bs58.decode(str)); -} - -/** - * Decode a Base58 string to a fixed-length byte array. - * Throws if decoded length doesn't match expected length. - */ -export function decodeBase58ToFixed(str: string, expectedLength: number): Uint8Array { - const decoded = bs58.decode(str); - if (decoded.length !== expectedLength) { - throw new Error(`Expected ${expectedLength} bytes, got ${decoded.length}`); - } - return Uint8Array.from(decoded); -} - -/** - * Convert a hex address (0x-prefixed or not) to a 20-byte Uint8Array. - */ -export function hexAddressToBytes(hexAddress: string): Uint8Array { - const cleaned = hexAddress.startsWith('0x') ? hexAddress.slice(2) : hexAddress; - if (cleaned.length !== 40) { - throw new Error(`Invalid hex address length: ${cleaned.length}`); - } - return Uint8Array.from(Buffer.from(cleaned, 'hex')); -} - -/** - * Convert a hex address to Base58 (for Irys API calls). - */ -export function hexAddressToBase58(hexAddress: string): string { - return encodeBase58(hexAddressToBytes(hexAddress)); -} diff --git a/modules/sdk-coin-irys/test/unit/commitmentTransactionBuilder.ts b/modules/sdk-coin-irys/test/unit/commitmentTransactionBuilder.ts deleted file mode 100644 index a55d33dc9b..0000000000 --- a/modules/sdk-coin-irys/test/unit/commitmentTransactionBuilder.ts +++ /dev/null @@ -1,444 +0,0 @@ -import should from 'should'; -import * as sinon from 'sinon'; -import nock from 'nock'; -import { IrysCommitmentTransactionBuilder } from '../../src/lib/commitmentTransactionBuilder'; -import { CommitmentTypeId, COMMITMENT_TX_VERSION, IRYS_TESTNET_CHAIN_ID } from '../../src/lib/iface'; -import { encodeBase58, decodeBase58 } from '../../src/lib/utils'; - -describe('IrysCommitmentTransactionBuilder', function () { - // Common test fixtures - const testAnchor = new Uint8Array(32).fill(1); // 32 bytes of 0x01 - const testSigner = new Uint8Array(20).fill(2); // 20 bytes of 0x02 - const testChainId = IRYS_TESTNET_CHAIN_ID; // 1270n - const testFee = 1000n; - const testValue = 5000n; - const testApiUrl = 'https://testnet-node1.irys.xyz/v1'; - - let builder: IrysCommitmentTransactionBuilder; - - beforeEach(function () { - builder = new IrysCommitmentTransactionBuilder(testApiUrl, testChainId); - }); - - afterEach(function () { - sinon.restore(); - nock.cleanAll(); - }); - - // === Commitment Type Encoding Tests === - - describe('encodeCommitmentTypeForSigning', function () { - it('should encode STAKE as a flat number (not array)', function () { - const result = IrysCommitmentTransactionBuilder.encodeCommitmentTypeForSigning({ - type: CommitmentTypeId.STAKE, - }); - result.should.equal(1); - Array.isArray(result).should.be.false(); - }); - - it('should encode PLEDGE as a nested array [type, pledgeCount]', function () { - const result = IrysCommitmentTransactionBuilder.encodeCommitmentTypeForSigning({ - type: CommitmentTypeId.PLEDGE, - pledgeCount: 42n, - }); - Array.isArray(result).should.be.true(); - (result as any[]).length.should.equal(2); - (result as any[])[0].should.equal(2); - (result as any[])[1].should.equal(42n); - }); - }); - - // === Commitment Type Broadcast Encoding Tests === - - describe('encodeCommitmentTypeForBroadcast', function () { - it('should encode STAKE as { type: "stake" }', function () { - const result = IrysCommitmentTransactionBuilder.encodeCommitmentTypeForBroadcast({ - type: CommitmentTypeId.STAKE, - }); - should.deepEqual(result, { type: 'stake' }); - }); - - it('should encode PLEDGE with pledgeCountBeforeExecuting', function () { - const result = IrysCommitmentTransactionBuilder.encodeCommitmentTypeForBroadcast({ - type: CommitmentTypeId.PLEDGE, - pledgeCount: 42n, - }); - should.deepEqual(result, { type: 'pledge', pledgeCountBeforeExecuting: '42' }); - }); - }); - - // === RLP Encoding Tests === - - describe('rlpEncode', function () { - it('should RLP encode a STAKE transaction with correct field order', function () { - const fields = { - version: COMMITMENT_TX_VERSION, - anchor: testAnchor, - signer: testSigner, - commitmentType: { type: CommitmentTypeId.STAKE as const }, - chainId: testChainId, - fee: testFee, - value: testValue, - }; - - const encoded = builder.rlpEncode(fields); - encoded.should.be.instanceOf(Uint8Array); - encoded.length.should.be.greaterThan(0); - - // The encoded output should be deterministic - const encoded2 = builder.rlpEncode(fields); - Buffer.from(encoded).equals(Buffer.from(encoded2)).should.be.true(); - }); - - it('should RLP encode a PLEDGE transaction with nested array commitment type', function () { - const fields = { - version: COMMITMENT_TX_VERSION, - anchor: testAnchor, - signer: testSigner, - commitmentType: { type: CommitmentTypeId.PLEDGE as const, pledgeCount: 42n }, - chainId: testChainId, - fee: testFee, - value: testValue, - }; - - const encoded = builder.rlpEncode(fields); - encoded.should.be.instanceOf(Uint8Array); - encoded.length.should.be.greaterThan(0); - }); - - it('should produce different encodings for STAKE vs PLEDGE', function () { - const stakeFields = { - version: COMMITMENT_TX_VERSION, - anchor: testAnchor, - signer: testSigner, - commitmentType: { type: CommitmentTypeId.STAKE as const }, - chainId: testChainId, - fee: testFee, - value: testValue, - }; - - const pledgeFields = { - ...stakeFields, - commitmentType: { type: CommitmentTypeId.PLEDGE as const, pledgeCount: 1n }, - }; - - const stakeEncoded = builder.rlpEncode(stakeFields); - const pledgeEncoded = builder.rlpEncode(pledgeFields); - Buffer.from(stakeEncoded).equals(Buffer.from(pledgeEncoded)).should.be.false(); - }); - }); - - // === Prehash Tests === - - describe('computePrehash', function () { - it('should return a 32-byte keccak256 hash', function () { - const rlpEncoded = new Uint8Array([0xc0]); // minimal RLP - const prehash = builder.computePrehash(rlpEncoded); - prehash.should.be.instanceOf(Uint8Array); - prehash.length.should.equal(32); - }); - - it('should produce deterministic output', function () { - const rlpEncoded = new Uint8Array([0xc8, 0x02, 0x01, 0x02, 0x03]); - const hash1 = builder.computePrehash(rlpEncoded); - const hash2 = builder.computePrehash(rlpEncoded); - Buffer.from(hash1).equals(Buffer.from(hash2)).should.be.true(); - }); - - it('should produce different hashes for different inputs', function () { - const input1 = new Uint8Array([0x01]); - const input2 = new Uint8Array([0x02]); - const hash1 = builder.computePrehash(input1); - const hash2 = builder.computePrehash(input2); - Buffer.from(hash1).equals(Buffer.from(hash2)).should.be.false(); - }); - }); - - // === Build Tests === - - describe('build', function () { - it('should build a STAKE transaction with manually set anchor', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(testFee) - .setValue(testValue) - .setSigner(testSigner) - .setAnchor(testAnchor); - - const result = await builder.build(); - result.prehash.should.be.instanceOf(Uint8Array); - result.prehash.length.should.equal(32); - result.rlpEncoded.should.be.instanceOf(Uint8Array); - result.rlpEncoded.length.should.be.greaterThan(0); - result.fields.version.should.equal(COMMITMENT_TX_VERSION); - result.fields.chainId.should.equal(testChainId); - }); - - it('should build a PLEDGE transaction with manually set anchor', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.PLEDGE, pledgeCount: 5n }) - .setFee(testFee) - .setValue(testValue) - .setSigner(testSigner) - .setAnchor(testAnchor); - - const result = await builder.build(); - result.prehash.length.should.equal(32); - result.fields.commitmentType.should.deepEqual({ type: CommitmentTypeId.PLEDGE, pledgeCount: 5n }); - }); - - it('should fetch anchor from API when not manually set', async function () { - const mockAnchorBase58 = encodeBase58(testAnchor); - const scope = nock('https://testnet-node1.irys.xyz') - .get('/v1/anchor') - .reply(200, { blockHash: mockAnchorBase58 }); - - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(testFee) - .setValue(testValue) - .setSigner(testSigner); - - const result = await builder.build(); - result.prehash.length.should.equal(32); - Buffer.from(result.fields.anchor).equals(Buffer.from(testAnchor)).should.be.true(); - scope.done(); - }); - - it('should throw if commitment type is not set', async function () { - builder.setFee(testFee).setValue(testValue).setSigner(testSigner).setAnchor(testAnchor); - - await builder.build().should.be.rejectedWith('Commitment type is required'); - }); - - it('should throw if fee is not set', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setValue(testValue) - .setSigner(testSigner) - .setAnchor(testAnchor); - - await builder.build().should.be.rejectedWith('Fee is required'); - }); - - it('should throw if value is not set', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(testFee) - .setSigner(testSigner) - .setAnchor(testAnchor); - - await builder.build().should.be.rejectedWith('Value is required'); - }); - - it('should throw if signer is not set', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(testFee) - .setValue(testValue) - .setAnchor(testAnchor); - - await builder.build().should.be.rejectedWith('Signer is required'); - }); - }); - - // === Validation Tests === - - describe('input validation', function () { - it('should reject signer with wrong length', function () { - (() => builder.setSigner(new Uint8Array(19))).should.throw(/Signer must be 20 bytes/); - (() => builder.setSigner(new Uint8Array(21))).should.throw(/Signer must be 20 bytes/); - }); - - it('should reject anchor with wrong length', function () { - (() => builder.setAnchor(new Uint8Array(31))).should.throw(/Anchor must be 32 bytes/); - (() => builder.setAnchor(new Uint8Array(33))).should.throw(/Anchor must be 32 bytes/); - }); - }); - - // === Transaction ID Tests === - - describe('computeTxId', function () { - it('should compute base58(keccak256(signature))', function () { - const fakeSignature = new Uint8Array(65).fill(0xab); - const txId = IrysCommitmentTransactionBuilder.computeTxId(fakeSignature); - txId.should.be.a.String(); - txId.length.should.be.greaterThan(0); - }); - - it('should produce deterministic output', function () { - const sig = new Uint8Array(65).fill(0xcd); - const id1 = IrysCommitmentTransactionBuilder.computeTxId(sig); - const id2 = IrysCommitmentTransactionBuilder.computeTxId(sig); - id1.should.equal(id2); - }); - - it('should reject non-65-byte signatures', function () { - (() => IrysCommitmentTransactionBuilder.computeTxId(new Uint8Array(64))).should.throw( - /Signature must be 65 bytes/ - ); - }); - }); - - // === Broadcast Payload Tests === - - describe('createBroadcastPayload', function () { - it('should create valid JSON payload for STAKE', function () { - const fields = { - version: COMMITMENT_TX_VERSION, - anchor: testAnchor, - signer: testSigner, - commitmentType: { type: CommitmentTypeId.STAKE as const }, - chainId: testChainId, - fee: testFee, - value: testValue, - }; - const signature = new Uint8Array(65).fill(0xab); - - const payload = IrysCommitmentTransactionBuilder.createBroadcastPayload(fields, signature); - - payload.version.should.equal(2); - payload.anchor.should.be.a.String(); - payload.signer.should.be.a.String(); - should.deepEqual(payload.commitmentType, { type: 'stake' }); - payload.chainId.should.equal('1270'); - payload.fee.should.equal('1000'); - payload.value.should.equal('5000'); - payload.id.should.be.a.String(); - payload.signature.should.be.a.String(); - }); - - it('should create valid JSON payload for PLEDGE with pledgeCountBeforeExecuting', function () { - const fields = { - version: COMMITMENT_TX_VERSION, - anchor: testAnchor, - signer: testSigner, - commitmentType: { type: CommitmentTypeId.PLEDGE as const, pledgeCount: 42n }, - chainId: testChainId, - fee: testFee, - value: testValue, - }; - const signature = new Uint8Array(65).fill(0xcd); - - const payload = IrysCommitmentTransactionBuilder.createBroadcastPayload(fields, signature); - - should.deepEqual(payload.commitmentType, { type: 'pledge', pledgeCountBeforeExecuting: '42' }); - }); - }); - - // === Known-Good Test Vectors (from successful testnet transactions) === - // - // These vectors were captured from actual STAKE and PLEDGE transactions - // submitted to the Irys testnet using coins-sandbox/eth/irys/stake.ts. - // They verify our RLP encoding + prehash match the protocol exactly. - - describe('known-good test vectors', function () { - const testnetSigner = '0x22f9C9f1845D9b6C22b96Ef35E46E265aC4Af30c'; - const testnetSignerBytes = Uint8Array.from(Buffer.from(testnetSigner.slice(2), 'hex')); - const testnetChainId = 1270n; - - it('should match known STAKE RLP encoding and prehash', async function () { - // From stake_pledge.txt - successful STAKE transaction - // TX ID: 4XhUTrkhxr1RmUQbXUVRwbNZ6pKEYrAVo5ymdMY41fS5 - const anchorBase58 = '8JR2rD5DejnM2NuVSqqGa68dfye6ZKruT9rdh2Cn4B8y'; - const anchorBytes = decodeBase58(anchorBase58); - - const stakeBuilder = new IrysCommitmentTransactionBuilder(testApiUrl, testnetChainId); - - stakeBuilder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(100n) - .setValue(20000000000000000000000n) // 20000 IRYS - .setSigner(testnetSignerBytes) - .setAnchor(anchorBytes); - - const result = await stakeBuilder.build(); - - const expectedRlp = - '0xf84702a06c77daebc2db4e572e4f296983d1413fc10d4852e0fabfdb8323c9c69a2b85' + - '9e9422f9c9f1845d9b6c22b96ef35e46e265ac4af30c018204f6648a043c33c1937564800000'; - const actualRlp = '0x' + Buffer.from(result.rlpEncoded).toString('hex'); - actualRlp.should.equal(expectedRlp); - - const expectedPrehash = '0xe6fe57810c12785e3ce5fa64e2eb4da120b89ec0e469213715916abf36358d01'; - const actualPrehash = '0x' + Buffer.from(result.prehash).toString('hex'); - actualPrehash.should.equal(expectedPrehash); - }); - - it('should match known PLEDGE RLP encoding and prehash', async function () { - // From stake_pledge.txt - successful PLEDGE transaction - // TX ID: EsdiesC58S8eeY1SHM5jTfy84zYxFMUdKF89Ytr6PyNb - const anchorBase58 = 'jUShJPUACW4bxUSvZji65Q96MaqKDh7AFFALKnkapBn'; - const anchorBytes = decodeBase58(anchorBase58); - - const pledgeBuilder = new IrysCommitmentTransactionBuilder(testApiUrl, testnetChainId); - - pledgeBuilder - .setCommitmentType({ type: CommitmentTypeId.PLEDGE, pledgeCount: 0n }) - .setFee(100n) - .setValue(950000000000000000000n) // 950 IRYS - .setSigner(testnetSignerBytes) - .setAnchor(anchorBytes); - - const result = await pledgeBuilder.build(); - - const expectedRlp = - '0xf84802a00ae16c8476bbde2f28b2e4629d393dfe6fa7affcf0a0c4654f8246a9ba78970594' + - '22f9c9f1845d9b6c22b96ef35e46e265ac4af30cc202808204f66489337fe5feaf2d180000'; - const actualRlp = '0x' + Buffer.from(result.rlpEncoded).toString('hex'); - actualRlp.should.equal(expectedRlp); - - const expectedPrehash = '0xfe07c2f3c6e50d9c9e2cff57f6d7015b4528f425b6132f567e26bba745228102'; - const actualPrehash = '0x' + Buffer.from(result.prehash).toString('hex'); - actualPrehash.should.equal(expectedPrehash); - }); - }); - - // === Edge Case Tests === - - describe('edge cases', function () { - it('should handle zero fee and value', async function () { - builder - .setCommitmentType({ type: CommitmentTypeId.STAKE }) - .setFee(0n) - .setValue(0n) - .setSigner(testSigner) - .setAnchor(testAnchor); - - const result = await builder.build(); - result.prehash.length.should.equal(32); - }); - }); - - // === Anchor Fetch Tests === - - describe('fetchAnchor', function () { - it('should fetch and decode base58 anchor from API', async function () { - const mockAnchorBase58 = encodeBase58(testAnchor); - const scope = nock('https://testnet-node1.irys.xyz') - .get('/v1/anchor') - .reply(200, { blockHash: mockAnchorBase58 }); - - const anchor = await builder.fetchAnchor(); - anchor.should.be.instanceOf(Uint8Array); - anchor.length.should.equal(32); - Buffer.from(anchor).equals(Buffer.from(testAnchor)).should.be.true(); - scope.done(); - }); - - it('should throw on non-200 response', async function () { - const scope = nock('https://testnet-node1.irys.xyz').get('/v1/anchor').reply(500, 'Internal Server Error'); - - await builder.fetchAnchor().should.be.rejectedWith(/Internal Server Error/); - scope.done(); - }); - - it('should throw if anchor decodes to wrong length', async function () { - const shortAnchor = encodeBase58(new Uint8Array(16)); // 16 bytes instead of 32 - const scope = nock('https://testnet-node1.irys.xyz').get('/v1/anchor').reply(200, { blockHash: shortAnchor }); - - await builder.fetchAnchor().should.be.rejectedWith(/Expected 32 bytes/); - scope.done(); - }); - }); -}); diff --git a/modules/sdk-coin-irys/tsconfig.json b/modules/sdk-coin-irys/tsconfig.json deleted file mode 100644 index ead84fd1ab..0000000000 --- a/modules/sdk-coin-irys/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./", - "strictPropertyInitialization": false, - "esModuleInterop": true, - "typeRoots": ["../../types", "./node_modules/@types", "../../node_modules/@types"] - }, - "include": ["src/**/*", "test/**/*"], - "exclude": ["node_modules"], - "references": [] -} diff --git a/modules/statics/src/allCoinsAndTokens.ts b/modules/statics/src/allCoinsAndTokens.ts index b995739a04..ad316c2b9f 100644 --- a/modules/statics/src/allCoinsAndTokens.ts +++ b/modules/statics/src/allCoinsAndTokens.ts @@ -2097,6 +2097,8 @@ export const allCoinsAndTokens = [ BaseUnit.ETH, [ ...EVM_FEATURES, + CoinFeature.SHARED_EVM_SIGNING, + CoinFeature.SHARED_EVM_SDK, CoinFeature.EVM_COMPATIBLE_IMS, CoinFeature.EVM_COMPATIBLE_UI, CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY, @@ -2113,11 +2115,12 @@ export const allCoinsAndTokens = [ BaseUnit.ETH, [ ...EVM_FEATURES, + CoinFeature.SHARED_EVM_SIGNING, + CoinFeature.SHARED_EVM_SDK, CoinFeature.EVM_COMPATIBLE_IMS, CoinFeature.EVM_COMPATIBLE_UI, CoinFeature.EVM_UNSIGNED_SWEEP_RECOVERY, CoinFeature.EVM_NON_BITGO_RECOVERY, - CoinFeature.STAKING, ] ), account(