Skip to content

Commit

Permalink
feat(sdk-coin-icp): added address creation and validation logic
Browse files Browse the repository at this point in the history
TICKET: WIN-4247
  • Loading branch information
mohd-kashif committed Jan 20, 2025
1 parent 5215ce9 commit bcdde18
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 10 deletions.
6 changes: 3 additions & 3 deletions modules/bitgo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@
{
"path": "../sdk-api"
},
{
"path": "../sdk-hmac"
},
{
"path": "../sdk-coin-ada"
},
Expand Down Expand Up @@ -234,6 +231,9 @@
{
"path": "../sdk-core"
},
{
"path": "../sdk-hmac"
},
{
"path": "../sdk-test"
},
Expand Down
34 changes: 32 additions & 2 deletions modules/sdk-coin-icp/src/icp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import {
KeyPair,
SignTransactionOptions,
SignedTransaction,
Environments,
} from '@bitgo/sdk-core';
import * as request from 'superagent';
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
import utils from './lib/utils';

/**
* Class representing the Internet Computer (ICP) coin.
Expand Down Expand Up @@ -79,8 +82,8 @@ export class Icp extends BaseCoin {
throw new Error('Method not implemented.');
}

isValidPub(_: string): boolean {
throw new Error('Method not implemented.');
isValidPub(HexEncodedPublicKey: string): boolean {
return utils.isValidPublicKey(HexEncodedPublicKey);
}

isValidPrv(_: string): boolean {
Expand All @@ -96,4 +99,31 @@ export class Icp extends BaseCoin {
getMPCAlgorithm(): MPCAlgorithm {
return 'ecdsa';
}

private async getAddressFromPublicKey(HexEncodedPublicKey: string) {
const payload = {
network_identifier: utils.getNetworkIdentifier(),
public_key: {
hex_bytes: HexEncodedPublicKey,
curve_type: utils.getCurveType(),
},
};

return this.callRosettaApi('/construction/derive', payload);
}

private async callRosettaApi(endpoint: string, payload: Record<string, any>) {
const nodeURL = `${this.getPublicNodeUrl()}${endpoint}`;
try {
const response = await request.post(nodeURL).set(utils.getHeaders()).send(payload);
return response.body;
} catch (e) {
throw new Error(`Unable to call endpoint: '${endpoint}' from node: ${nodeURL}`);
}
}

/** @inheritDoc **/
protected getPublicNodeUrl(): string {
return Environments[this.bitgo.getEnv()].rosettaNodeURL;
}
}
33 changes: 32 additions & 1 deletion modules/sdk-coin-icp/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,50 @@ export class Utils implements BaseUtils {
isValidAddress(address: string): boolean {
throw new Error('Method not implemented.');
}

isValidTransactionId(txId: string): boolean {
throw new Error('Method not implemented.');
}

isValidPublicKey(key: string): boolean {
throw new Error('Method not implemented.');
const hexRegex = /^[0-9a-fA-F]+$/;
if (!hexRegex.test(key)) return false;

const length = key.length;
if (length !== 130) return false;

return true;
}

isValidPrivateKey(key: string): boolean {
throw new Error('Method not implemented.');
}

isValidSignature(signature: string): boolean {
throw new Error('Method not implemented.');
}

isValidBlockId(hash: string): boolean {
throw new Error('Method not implemented.');
}

getHeaders(): Record<string, string> {
return {
'Content-Type': 'application/json',
};
}

getNetworkIdentifier(): Record<string, string> {
return {
blockchain: 'Internet Computer',
network: '00000000000000020101',
};
}

getCurveType(): string {
return 'secp256k1';
}
}

const utils = new Utils();
export default utils;
62 changes: 61 additions & 1 deletion modules/sdk-coin-icp/test/unit/icp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@ import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';

import { Icp, Ticp } from '../../src/index';
import nock from 'nock';
// import * as assert from 'assert';
nock.enableNetConnect();

const bitgo: TestBitGoAPI = TestBitGo.decorate(BitGoAPI, { env: 'test' });
bitgo.safeRegister('ticp', Ticp.createInstance);

describe('Internet computer', function () {
let bitgo;
let basecoin;

describe('Icp', function () {
before(function () {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
bitgo.safeRegister('icp', Icp.createInstance);
bitgo.safeRegister('ticp', Ticp.createInstance);
bitgo.initializeTestVars();
basecoin = bitgo.coin('ticp');
});

after(function () {
nock.pendingMocks().should.be.empty();
nock.cleanAll();
});

it('should return the right info', function () {
Expand All @@ -30,4 +44,50 @@ describe('Icp', function () {
ticp.getBaseFactor().should.equal(1e8);
icp.supportsTss().should.equal(true);
});

describe('Address creation', () => {
const hexEncodedPublicKey =
'047a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7131b778317c0db04de661c7d08321d60c0507868af41fe3150d21b3c6c757367';
const invalidPublicKey = '02a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7';
const validResponse = {
account_identifier: { address: '8b84c3a3529d02a9decb5b1a27e7c8d886e17e07ea0a538269697ef09c2a27b4' },
};

it('should return true when validating a hex encoded public key', function () {
basecoin.isValidPub(hexEncodedPublicKey).should.equal(true);
});

it('should return false when validating a invalid public key', function () {
basecoin.isValidPub(invalidPublicKey).should.equal(false);
});

it('should return valid address from a valid hex encoded public key', async function () {
const url = basecoin.getPublicNodeUrl();
nock(url).post('/construction/derive').reply(200, validResponse);

const response = await basecoin.getAddressFromPublicKey(hexEncodedPublicKey);
response.should.deepEqual(validResponse);
});

it('should throw an error when API call fails with status code 500', async function () {
const url = basecoin.getPublicNodeUrl();
const errorResponse = {
code: 713,
message: 'Invalid public key',
retriable: false,
details: {
error_message:
'Could not decode hex public key 02a83e378053f87b49aeae53b3ed274c8b2ffbe59d9a51e3c4d850ca8ac1684f7\n\nCaused by:\n Odd number of digits',
},
};

nock(url).post('/construction/derive').reply(500, errorResponse);

await basecoin
.getAddressFromPublicKey(invalidPublicKey)
.should.be.rejectedWith(
`Unable to call endpoint: '/construction/derive' from node: ${url}/construction/derive`
);
});
});
});
3 changes: 3 additions & 0 deletions modules/sdk-core/src/bitgo/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ interface EnvironmentTemplate {
etcNodeUrl: string;
coredaoExplorerBaseUrl?: string;
coredaoExplorerApiToken?: string;
rosettaNodeURL: string;
}

export interface Environment extends EnvironmentTemplate {
Expand Down Expand Up @@ -155,6 +156,7 @@ const mainnetBase: EnvironmentTemplate = {
etcNodeUrl: 'https://etc.blockscout.com',
coredaoExplorerBaseUrl: 'https://scan.coredao.org/',
oasExplorerBaseUrl: 'https://explorer.oasys.games',
rosettaNodeURL: 'http://localhost:8081', //TODO(WIN-4242): update when rosetta node is available
};

const testnetBase: EnvironmentTemplate = {
Expand Down Expand Up @@ -209,6 +211,7 @@ const testnetBase: EnvironmentTemplate = {
etcNodeUrl: 'https://etc-mordor.blockscout.com',
coredaoExplorerBaseUrl: 'https://scan.test.btcs.network',
oasExplorerBaseUrl: 'https://explorer.testnet.oasys.games',
rosettaNodeURL: 'http://localhost:8081', //TODO(WIN-4242): update when rosetta node is available
};

const devBase: EnvironmentTemplate = Object.assign({}, testnetBase, {
Expand Down
6 changes: 3 additions & 3 deletions tsconfig.packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
{
"path": "./modules/abstract-cosmos"
},
{
"path": "./modules/abstract-substrate"
},
{
"path": "./modules/abstract-eth"
},
{
"path": "./modules/abstract-lightning"
},
{
"path": "./modules/abstract-substrate"
},
{
"path": "./modules/abstract-utxo"
},
Expand Down

0 comments on commit bcdde18

Please sign in to comment.