diff --git a/backend/package.json b/backend/package.json index 26f84d63..2d810c55 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,8 +15,10 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.535.0", "@coral-xyz/anchor": "^0.29.0", + "@influxdata/influxdb-client": "^1.33.2", "@solana/web3.js": "^1.91.1", "bs58": "^5.0.0", + "hi-base32": "^0.5.1", "tweetnacl": "^1.0.3" }, "devDependencies": { diff --git a/backend/src/config.ts b/backend/src/config.ts index 9bdba4ca..6557d301 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -22,5 +22,12 @@ export default { process.env.FUNDER_WALLET_KEY_SECRET_NAME ?? 'xli-test-secret-funder-wallet' } + }, + influx: { + url: process.env.INFLUXDB_URL ?? 'http://localhost:8086', + org: process.env.INFLUXDB_ORG ?? 'xl', + bucket: process.env.INFLUXDB_BUCKET ?? 'ad', + token: process.env.INFLUXDB_TOKEN ?? '', + timeout: parseInt(process.env.INFLUXDB_TIMEOUT_MS ?? '2_500') } } diff --git a/backend/src/handlers/fund-transactions.ts b/backend/src/handlers/fund-transactions.ts index fefa51e3..06bae605 100644 --- a/backend/src/handlers/fund-transactions.ts +++ b/backend/src/handlers/fund-transactions.ts @@ -10,6 +10,8 @@ import { Keypair, VersionedTransaction } from '@solana/web3.js' import bs58 from 'bs58' import { HandlerError } from '../utils/errors' import { asJsonResponse } from '../utils/response' +import { ClaimSignature } from '../types' +import { saveSignedTransactions } from '../utils/persistence' export type FundTransactionRequest = Uint8Array[] @@ -31,7 +33,8 @@ export const fundTransactions = async ( const wallet = await loadFunderWallet() const signedTransactions = await wallet.signAllTransactions(transactions) - logSignatures(signedTransactions) + saveSignedTransactions(getSignatures(signedTransactions)) + return asJsonResponse( 200, signedTransactions.map((tx) => Buffer.from(tx.serialize())) @@ -80,13 +83,12 @@ function getSignature(tx: VersionedTransaction): string { return 'unkown signature' } -function logSignatures(signedTransactions: VersionedTransaction[]) { - const sigs: { - sig: string - instruction?: ReturnType - }[] = [] +function getSignatures(signedTransactions: VersionedTransaction[]) { + const sigs: ClaimSignature[] = [] signedTransactions.forEach((tx) => { sigs.push({ sig: getSignature(tx), instruction: extractCallData(tx) }) }) console.log(`Signed transactions: ${JSON.stringify(sigs)}`) + + return sigs } diff --git a/backend/src/types.ts b/backend/src/types.ts index 4d4201da..202726be 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1,3 +1,6 @@ +import { IdlTypes } from '@coral-xyz/anchor' +import { TokenDispenser } from './token-dispenser' + export type SignedMessage = { publicKey: Uint8Array signature: Uint8Array @@ -5,3 +8,10 @@ export type SignedMessage = { recoveryId: number | undefined fullMessage: Uint8Array } + +export type ClaimCertificate = IdlTypes['ClaimCertificate'] + +export type ClaimSignature = { + sig: string + instruction: ClaimCertificate | null +} diff --git a/backend/src/utils/fund-transactions.ts b/backend/src/utils/fund-transactions.ts index 614d4b3d..fee2c986 100644 --- a/backend/src/utils/fund-transactions.ts +++ b/backend/src/utils/fund-transactions.ts @@ -7,10 +7,10 @@ import { TransactionInstruction, VersionedTransaction } from '@solana/web3.js' -import { TokenDispenser, coder } from '../token-dispenser' +import { coder } from '../token-dispenser' import config from '../config' -import { IdlTypes } from '@coral-xyz/anchor' +import { ClaimCertificate } from '../types' const SET_COMPUTE_UNIT_LIMIT_DISCRIMINANT = 2 const SET_COMPUTE_UNIT_PRICE_DISCRIMINANT = 3 @@ -211,8 +211,6 @@ export async function checkTransactions( } } -type ClaimCertificate = IdlTypes['ClaimCertificate'] - export function extractCallData( versionedTx: VersionedTransaction, programId?: string diff --git a/backend/src/utils/persistence.ts b/backend/src/utils/persistence.ts new file mode 100644 index 00000000..06929070 --- /dev/null +++ b/backend/src/utils/persistence.ts @@ -0,0 +1,119 @@ +import { InfluxDB, Point } from '@influxdata/influxdb-client' +import base32 from 'hi-base32' +import config from '../config' +import { ClaimSignature } from '../types' +import { PublicKey } from '@solana/web3.js' + +export async function saveSignedTransactions( + claimSignatures: ClaimSignature[] +) { + try { + const influxWriter = new InfluxDB({ + url: config.influx.url, + token: config.influx.token, + timeout: config.influx.timeout, + writeOptions: {} + }).getWriteApi(config.influx.org, config.influx.bucket) + + const points = claimSignatures.map((claimSignature) => { + const { ecosystem, subEcosystem, identity } = mapIdentity(claimSignature) + const point = new Point('ad_signatures') + .stringField('type', 'transaction_signed') + .stringField('sig', claimSignature.sig) + .stringField('ecosystem', ecosystem) + .stringField('subecosystem', subEcosystem) + .stringField('identity', identity) + .floatField('amount', claimSignature.instruction?.amount?.toNumber()) + + return point + }) + + influxWriter.writePoints(points) + await influxWriter.close() + } catch (err) { + console.error('Error saving signed transactions', err) + } +} + +function mapIdentity(claimSignature: ClaimSignature) { + const ecosystem: string = Object.keys( + claimSignature.instruction?.proofOfIdentity ?? { unknown: 'ecosystem' } + )[0] + let identity: string | undefined + let subEcosystem = ecosystem + + switch (ecosystem) { + case 'discord': { + identity = claimSignature.instruction?.proofOfIdentity?.discord?.username + break + } + case 'solana': { + identity = new PublicKey( + claimSignature.instruction?.proofOfIdentity?.solana?.pubkey ?? [] + ).toBase58() + break + } + case 'evm': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.evm?.pubkey ?? [] + ).toString('hex') + break + } + // TODO: validate correct parsing + case 'osmosis': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.cosmwasm?.pubkey ?? [] + ).toString('hex') + subEcosystem = 'osmosis' + break + } + case 'terra': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.cosmwasm?.pubkey ?? [] + ).toString('hex') + subEcosystem = 'terra' + break + } + case 'injective': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.injective?.pubkey ?? [] + ).toString('hex') + break + } + case 'aptos': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.aptos?.pubkey ?? [] + ).toString('hex') + break + } + case 'sui': { + identity = + '0x' + + Buffer.from( + claimSignature.instruction?.proofOfIdentity?.sui?.pubkey ?? [] + ).toString('hex') + break + } + case 'algorand': { + identity = base32.encode( + claimSignature.instruction?.proofOfIdentity?.algorand?.pubkey ?? [] + ) + break + } + default: { + identity = 'unknown' + } + } + + return { ecosystem, subEcosystem, identity } +} diff --git a/backend/yarn.lock b/backend/yarn.lock index b00f14d5..ce9f0eb0 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -861,6 +861,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@influxdata/influxdb-client@^1.33.2": + version "1.33.2" + resolved "https://registry.yarnpkg.com/@influxdata/influxdb-client/-/influxdb-client-1.33.2.tgz#c68cfcf592e4e042361003143fbab99461410172" + integrity sha512-RT5SxH+grHAazo/YK3UTuWK/frPWRM0N7vkrCUyqVprDgQzlLP+bSK4ak2Jv3QVF/pazTnsxWjvtKZdwskV5Xw== + "@inquirer/confirm@^3.0.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.0.tgz#526cb71ceab28ba827ea287aa81c969e437017b6" @@ -3475,6 +3480,11 @@ headers-polyfill@^4.0.2: resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== +hi-base32@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/hi-base32/-/hi-base32-0.5.1.tgz#1279f2ddae2673219ea5870c2121d2a33132857e" + integrity sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"