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

Adds Angor UI #13

Merged
merged 18 commits into from
Dec 19, 2024
Merged
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
38 changes: 21 additions & 17 deletions backend/src/angor/AngorTransactionDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,19 @@ export class AngorTransactionDecoder {
const founderKeyHashInt = this.hashToInt(founderKeyHash);
const projectIdDerivation = this.getProjectIdDerivation(founderKeyHashInt);
const projectId = this.getProjectId(projectIdDerivation);
const nostrPubKey = this.getNostrPubKey();
const nostrEventId = this.getNostrEventId();
const addressOnFeeOutput = this.getAddressOnFeeOutput();
const txid = this.transaction.getId();

// Store Angor project in the DB.
await this.storeProjectInfo(
projectId,
nostrPubKey,
addressOnFeeOutput,
transactionStatus,
founderKeyHex,
txid,
createdOnBlock
createdOnBlock,
nostrEventId
);

// If transaction is confirmed (in the block), update statuses
Expand Down Expand Up @@ -224,23 +224,27 @@ export class AngorTransactionDecoder {
const chunks = bitcoinJS.script.toASM(decompiled).split(' ');

// Throw an error if the chunks amount is incorrect.
if (chunks.length !== 3) {
if (chunks.length !== 4) {
throw new Error(`${errorBase} Wrong chunk amount.`);
}

// Throw an error if the first chunk is not OP_RETURN.
if (chunks[0] !== 'OP_RETURN') {
throw new Error(`${errorBase} Wrong first chunk.`);
throw new Error(`${errorBase} Wrong OP_RETURN chunk.`);
}

// Throw an error if the byte length of the second chunk is not 33.
if (Buffer.from(chunks[1], 'hex').byteLength !== 33) {
throw new Error(`${errorBase} Wrong second chunk.`);
throw new Error(`${errorBase} Wrong founder pubkey chunk.`);
}

// Throw an error if the byte length of the third chunk is not 32.
if (Buffer.from(chunks[2], 'hex').byteLength !== 32) {
throw new Error(`${errorBase} Wrong third chunk.`);
if (Buffer.from(chunks[2], 'hex').byteLength !== 2) {
throw new Error(`${errorBase} Wrong key type chunk.`);
}

if (Buffer.from(chunks[3], 'hex').byteLength !== 32) {
throw new Error(`${errorBase} Wrong nostr event ID chunk.`);
}

// Remove the first chunk (OP_RETURN) as it is not useful anymore.
Expand Down Expand Up @@ -405,13 +409,13 @@ export class AngorTransactionDecoder {
}

/**
* Sets Nostr public key.
* @returns - string representing Nostr public key of Angor project.
* Sets Nostr event id.
* @return - string representing the Nostr event ID associated with the current Angor project.
* @private
*/
private getNostrPubKey(): string {
private getNostrEventId(): string {
const chunks = this.decompileProjectCreationOpReturnScript();

return chunks[1];
return chunks[2];
}

/**
Expand All @@ -434,21 +438,21 @@ export class AngorTransactionDecoder {
*/
private async storeProjectInfo(
projectId: string,
nostrPubKey: string,
addressOnFeeOutput: string,
transactionStatus: AngorTransactionStatus,
founderKey: string,
txid: string,
createdOnBlock?: number
createdOnBlock?: number,
nostrEventId?: string
): Promise<void> {
await AngorProjectRepository.$setProject(
projectId,
nostrPubKey,
addressOnFeeOutput,
transactionStatus,
founderKey,
txid,
createdOnBlock
createdOnBlock,
nostrEventId
);
}

Expand Down
62 changes: 35 additions & 27 deletions backend/src/angor/tests/AngorTransactionDecoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ describe('AngorTransactionDecoder', () => {
describe('Decoding transaction for Angor project creation', () => {
const data = {
transactionHex:
'0100000000010138de40ff6a3d27c33d5b84edf8f35911d819c9c547689cc4da6c5603bc3b26990000000000ffffffff0310270000000000001600144282ccfe323dbba535ccdfc8b66aeeb0bd7dd95b0000000000000000446a210352eb18befb145fef4b5c24513608183d7c3d8004d5fcad9e0e6dc2e89a0af6ae20c749d81fc42037e5b9f7b32ae266cbce75019ce6771be6877d3c509e97e39c14669b052a0100000016001431e5c2bdb361b68eb243ec7dbd46bd7cf71a1d86024730440220075dd33e3809a58423257bf23f5632e305387c283f48dfdce7f355787be33d8002201072fa636fb6bad35ac9d7f8e35fb41eea47de8b38346dc4f123260e9c74c3b401210385143acd30d6d05a5bf35ef1028ce4c50eadffa14670716976007fec5ed3e58500000000',
'01000000000101899c7a384fe0e17dfd3d56fcf6bfff796b5d0f40ecb5bc513a92e891bb2018af0200000000ffffffff031127000000000000160014e6285b56cb7cd9a51af2f28cb02762b5298c98db0000000000000000476a2102070d174561688500aac733116dbe70c5ab7480559d25e1c040f480491870c8ba020100200f2d8db8568bd3e12bdab1faa217fffc80459053967eff8bde0a65f14e2b7079d542052a01000000160014ae63769a0b0a5f69b3be5d1e6bb4b00d15eff7d0024830450221008e797faa2ef8c3e91ff03f4a47e76740cbadf4b5061d0508ffd89ab869891cb2022050c624530f5c6afbe6ec0dcba4c81287431595f89370225f7d12d3866cc0499f01210396d79f9c4a836defed971668ea51ed50495d5e2d205da2590e7f6600af03f8c800000000',
founderKeyHex:
'0352eb18befb145fef4b5c24513608183d7c3d8004d5fcad9e0e6dc2e89a0af6ae',
'02070d174561688500aac733116dbe70c5ab7480559d25e1c040f480491870c8ba',
founderKeyHashHex:
'cacedcee9bc28a37b36718ea210fcf7caac182cdb66cc17fafb6027478a221c8',
founderKeyHashInt: 2023891400,
projectIdDeriviation: 1011945700,
projectId: 'angor1qg2pvel3j8ka62dwvmlytv6hwkz7hmk2mms7qll',
nPub: 'c749d81fc42037e5b9f7b32ae266cbce75019ce6771be6877d3c509e97e39c14',
addressOnFeeOutput: 'tb1qg2pvel3j8ka62dwvmlytv6hwkz7hmk2m0ncfee',
txid: '00b78119bb6eff9f64b2d29948ddd830f405b18dfdb802a3ec2df4eacfcd1f40',
'68828edc1c6312c915c8967475be57f42d45764105af8216f2da7170d033240a',
founderKeyHashInt: 3493012490,
projectIdDeriviation: 1746506245,
projectId: 'angor1qzkfpckm2vnhdvfcwr7vdhwt7ns3rd95gr0age0',
nostrEventId: '0f2d8db8568bd3e12bdab1faa217fffc80459053967eff8bde0a65f14e2b7079',
addressOnFeeOutput: 'tb1quc59k4kt0nv62xhj72xtqfmzk55cexxmae8lyc',
txid: '0d28976a42bf7618ad9470cf0202e2eb06d6072e75e139eab012a160b7b480aa',
blockHeight: 40000,
};

Expand Down Expand Up @@ -141,19 +141,23 @@ describe('AngorTransactionDecoder', () => {

describe('decodeAndStoreProjectCreationTransaction', () => {
it('should call $setProject method of AngorProjectRepository', async () => {
const setProjectSpy = jest.spyOn(AngorProjectRepository, '$setProject');
const setProjectSpy = jest
.spyOn(AngorProjectRepository, '$setProject')
.mockImplementation(() => Promise.resolve());

setProjectSpy.mockImplementation(() => Promise.resolve());
const updateInvestmentStatusSpy = jest
.spyOn(AngorInvestmentRepository, '$updateInvestmentsStatus')
.mockImplementation(() => Promise.resolve());

const transactionStatus = AngorTransactionStatus.Confirmed;

const {
projectId,
nPub,
addressOnFeeOutput,
founderKeyHex,
txid,
blockHeight,
nostrEventId
} = data;

await angorDecoder.decodeAndStoreProjectCreationTransaction(
Expand All @@ -163,12 +167,17 @@ describe('AngorTransactionDecoder', () => {

expect(setProjectSpy).toHaveBeenCalledWith(
projectId,
nPub,
addressOnFeeOutput,
transactionStatus,
founderKeyHex,
txid,
blockHeight
blockHeight,
nostrEventId
);

expect(updateInvestmentStatusSpy).toHaveBeenCalledWith(
addressOnFeeOutput,
transactionStatus
);
});
});
Expand All @@ -178,10 +187,10 @@ describe('AngorTransactionDecoder', () => {
jest.restoreAllMocks();
});

it('should return 2 chunks', () => {
it('should return 3 chunks', () => {
const chunks = angorDecoder['decompileProjectCreationOpReturnScript']();

expect(chunks.length).toEqual(2);
expect(chunks.length).toEqual(3);

chunks.forEach((chunk) => expect(typeof chunk).toEqual('string'));
});
Expand Down Expand Up @@ -280,9 +289,9 @@ describe('AngorTransactionDecoder', () => {
});
});

describe('getNostrPubKey', () => {
it('should return Nostr public key', () => {
expect(angorDecoder['getNostrPubKey']()).toEqual(data.nPub);
describe('getNostrEventId', () => {
it('should return nostrEventId', () => {
expect(angorDecoder['getNostrEventId']()).toEqual(data.nostrEventId);
});
});
});
Expand All @@ -307,12 +316,12 @@ describe('AngorTransactionDecoder', () => {

describe('decodeAndStoreInvestmentTransaction', () => {
it('should call $getProject method of AngorProjectRepository', async () => {
const getProjectSpy = jest.spyOn(
const getProjectSpy = jest
.spyOn(
AngorProjectRepository,
'$getProjectByAddressOnFeeOutput'
);

getProjectSpy.mockImplementation(() => Promise.resolve(undefined));
)
.mockImplementation(() => Promise.resolve(undefined));

await angorDecoder.decodeAndStoreInvestmentTransaction(
transactionStatus
Expand All @@ -329,19 +338,18 @@ describe('AngorTransactionDecoder', () => {
.mockImplementation(() =>
Promise.resolve({
founder_key: '',
npub: '',
id: '',
created_on_block: 1,
txid: '',
nostr_event_id: ''
})
);

const setInvestmentSpy = jest.spyOn(
AngorInvestmentRepository,
'$setInvestment'
);

setInvestmentSpy.mockImplementation(() => Promise.resolve());
)
.mockImplementation(() => Promise.resolve());

await angorDecoder.decodeAndStoreInvestmentTransaction(
transactionStatus
Expand Down
18 changes: 9 additions & 9 deletions backend/src/api/angor/angor.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import { fetchAngorVouts, computeAdvancedStats, computeStatsTally } from './ango

interface ProjectsPayloadItem {
founderKey: string;
nostrPubKey: string;
projectIdentifier: string;
createdOnBlock: number;
trxId: string;
nostrEventId: string;
}
interface ProjectPayloadItem {
founderKey: string;
nostrPubKey: string;
projectIdentifier: string;
createdOnBlock: number;
trxId: string;
nostrEventId: string;
totalInvestmentsCount: number;
}

Expand Down Expand Up @@ -150,14 +150,14 @@ class AngorRoutes {
const payload: ProjectsPayloadItem[] = projects
.map((project) => ({
founderKey: project.founder_key,
nostrPubKey: project.npub,
projectIdentifier: project.id,
createdOnBlock: project.created_on_block,
trxId: project.txid,
nostrEventId: project.nostr_event_id
}))
.sort(
(p1: ProjectsPayloadItem, p2: ProjectsPayloadItem) =>
p1.createdOnBlock - p2.createdOnBlock
p2.createdOnBlock - p1.createdOnBlock
);

// Amount of confirmed Angor projects.
Expand Down Expand Up @@ -197,10 +197,10 @@ class AngorRoutes {
// Adjust DB data to confirm ProjectsPayloadItem interface.
const payload: ProjectPayloadItem = {
founderKey: project.founder_key,
nostrPubKey: project.npub,
projectIdentifier: project.id,
createdOnBlock: project.created_on_block,
trxId: project.txid,
nostrEventId: project.nostr_event_id,
totalInvestmentsCount: project.investments_count,
};

Expand Down Expand Up @@ -235,7 +235,6 @@ class AngorRoutes {
const spentVouts: AngorVout[][] = await Promise.all(
investments.map(async (investment) => {
//fetch transaction for each investment, with full info about vouts
console.log('investment: ', investment);
const fullTr = await transactionUtils.$getTransactionExtended(
investment.transaction_id,
true,
Expand Down Expand Up @@ -351,7 +350,6 @@ class AngorRoutes {
return;
}
}

// Angor project investments.
const projectInvestments =
await AngorProjectRepository.$getProjectInvestments(
Expand All @@ -361,15 +359,17 @@ class AngorRoutes {
);

// Adjust DB data to confirm ProjectInvestmentPayloadItem interface.
const payload: ProjectInvestmentPayloadItem[] = projectInvestments
const payload: ProjectInvestmentPayloadItem[] = projectInvestments.length > 0
? projectInvestments
.map((investment) => ({
investorPublicKey: investment.investor_npub,
totalAmount: investment.amount_sats,
transactionId: investment.transaction_id,
hashOfSecret: investment.secret_hash,
isSeeder: investment.is_seeder,
}))
.sort();
.sort()
: [];

// Amount of confirmed Angor project investments.
const investmentsCount =
Expand Down
3 changes: 1 addition & 2 deletions backend/src/api/database-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class DatabaseMigration {
*/
public async $initializeOrMigrateDatabase(): Promise<void> {
logger.debug('MIGRATIONS: Running migrations');

await this.$printDatabaseVersion();

// First of all, if the `state` database does not exist, create it so we can track migration version
Expand Down Expand Up @@ -1285,11 +1284,11 @@ class DatabaseMigration {
private getCreateAngorProjectsTableQuery(): string {
return `CREATE TABLE IF NOT EXISTS angor_projects (
id CHAR(45) NOT NULL,
npub CHAR(64) NOT NULL,
address_on_fee_output CHAR(51) NOT NULL,
creation_transaction_status VARCHAR(10) NOT NULL,
created_on_block INT(10),
txid VARCHAR(64) NOT NULL,
nostr_event_id VARCHAR(64),
founder_key VARCHAR(66) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
Expand Down
Loading
Loading