Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6c27c4d
Upgrade to @solana/web3.js 1.93.4
rickyrombo Jul 20, 2024
ac159a1
Recreate SendTransactionError in SDK SolanaRelay service
rickyrombo Jul 20, 2024
7895494
Add error enum to RewardManagerProgram
rickyrombo Jul 20, 2024
b3f076a
Parse Solana errors into friendly RewardManager errors in SDK
rickyrombo Jul 20, 2024
92129a2
Send extra fields from Solana relay to allow for SendTransactionError…
rickyrombo Jul 20, 2024
bff9c4a
Remove @ts-ignore now that deps are resolved properly
rickyrombo Jul 20, 2024
f212e0c
Special analytics for RewardManager errors
rickyrombo Jul 20, 2024
9ee55fa
Upgrade SDK build target to ES2022 for error causes
rickyrombo Jul 20, 2024
2b07ab4
Update @audius/fetch-nft to 0.2.7
rickyrombo Jul 24, 2024
40a141b
Update generated runtime.ts to use Error.cause
rickyrombo Jul 24, 2024
b8c612c
Update module to 2022 to fix verify
rickyrombo Jul 24, 2024
8e2cd2d
remove unnecessary assertion
rickyrombo Jul 24, 2024
e36b5dd
Changeset
rickyrombo Jul 24, 2024
40b54a9
Use CommonJS modules
rickyrombo Jul 24, 2024
49394e2
Back to ES2022
rickyrombo Jul 24, 2024
1eec62e
Use CommonJS to run mocha on libs tests, fix mocha import
rickyrombo Aug 1, 2024
b2e9861
... idk why i need to delete this
rickyrombo Aug 1, 2024
f2c15bf
I give up
rickyrombo Aug 1, 2024
13ac2af
Merge remote-tracking branch 'origin/main' into mjp-challenge-analyti…
rickyrombo Aug 2, 2024
8c31681
testing a theory
rickyrombo Aug 2, 2024
433cdce
Revert "testing a theory"
rickyrombo Aug 2, 2024
9f3ceb5
persist spl
rickyrombo Aug 2, 2024
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
5 changes: 5 additions & 0 deletions .changeset/old-pans-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@audius/sdk': major
---

Updates target, module, and lib to ES2022 to access new features such as Error.cause, updates @solana/web3.js, and updates SolanaRelay service to throw SendTransactionError on failure
2 changes: 2 additions & 0 deletions .circleci/src/jobs/@identity-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ identity-init:
- node_modules
- packages/libs/node_modules
- packages/libs/dist
- packages/spl/dist
- packages/spl/node_modules
260 changes: 110 additions & 150 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/commands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@audius/sdk": "*",
"@audius/spl": "*",
"@solana/spl-token": "0.3.8",
"@solana/web3.js": "1.78.4",
"@solana/web3.js": "1.93.4",
"bn.js": "^5.2.1",
"chalk": "^5.0.1",
"commander": "^9.4.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"url": "https://github.com/AudiusProject/audius-protocol/issues"
},
"dependencies": {
"@audius/fetch-nft": "0.2.6",
"@audius/fetch-nft": "0.2.7",
"@audius/fixed-decimal": "*",
"@audius/sdk": "*",
"@audius/trpc-server": "*",
Expand Down Expand Up @@ -92,7 +92,7 @@
"peerDependencies": {
"@reduxjs/toolkit": "1.6.1",
"@solana/spl-token": "0.3.8",
"@solana/web3.js": "1.78.4",
"@solana/web3.js": "1.93.4",
"@stripe/crypto": "0.0.4",
"react": "^18.2.0",
"redux-saga": "1.1.3"
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/models/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,7 @@ type RewardsClaimFailure = {
amount: number
url?: string
error: string
instruction?: string
}

type RewardsClaimBlocked = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ export const errorHandlerMiddleware = (
_next: NextFunction
) => {
const status = error instanceof ResponseError ? error.status : 500
const meta = error instanceof Error ? { ...error } : {}
if (!res.headersSent) {
res
.status(status)
.set('X-Request-ID', res.locals.requestId)
.set('Access-Control-Expose-Headers', 'X-Request-ID')
.send({ error: (error as any).toString() })
.send({ error: (error as any).toString(), ...meta })
}
// in milliseconds
const responseTime = new Date().getTime() - res.locals.requestStartTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,11 @@ export const sendTransactionWithRetries = async ({
if (!sendOptions?.skipPreflight) {
const simulatedRes = await connection.simulateTransaction(transaction)
if (simulatedRes.value.err) {
// @ts-ignore Typescript is confused about deps
throw new SendTransactionError({
action: 'simulate',
signature: confirmationStrategy.signature,
transactionMessage: JSON.stringify(simulatedRes.value.err),
logs: simulatedRes.value.logs
logs: simulatedRes.value.logs ?? undefined
Comment thread
rickyrombo marked this conversation as resolved.
})
}
}
Expand All @@ -132,7 +131,6 @@ export const sendTransactionWithRetries = async ({
throw new Error('Failed to get transaction confirmation result')
}
if (res.value.err) {
// @ts-ignore Typescript is confused about deps
throw new SendTransactionError({
action: 'send',
signature: confirmationStrategy.signature,
Expand Down
2 changes: 1 addition & 1 deletion packages/embed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"webpack-cli": "4.5.0"
},
"dependencies": {
"@audius/fetch-nft": "0.2.6",
"@audius/fetch-nft": "0.2.7",
"@audius/fixed-decimal": "*",
"@audius/harmony": "*",
"@audius/sdk": "*",
Expand Down
2 changes: 1 addition & 1 deletion packages/identity-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@solana/buffer-layout": "4.0.1",
"@solana/buffer-layout-utils": "0.2.0",
"@solana/spl-token": "0.3.8",
"@solana/web3.js": "1.78.4",
"@solana/web3.js": "1.93.4",
"apn": "2.2.0",
"async-retry": "1.3.3",
"aws-sdk": "2.595.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/libs/libs.test.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
4 changes: 2 additions & 2 deletions packages/libs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"test:sdk:browser": "./node_modules/.bin/jest --env jsdom",
"test": "npm run test:unit && npm run test:sdk",
"test:libs": "./scripts/test.sh",
"test:unit": "ts-mocha 'src/**/*.test.{js,ts}' --ignore 'src/sdk/**/*.test.{js,ts}' --exit",
"test:unit": "export TS_NODE_PROJECT=libs.test.tsconfig.json; ts-mocha 'src/**/*.test.{js,ts}' --ignore 'src/sdk/**/*.test.{js,ts}' --exit",
"test:unit:watch": "ts-mocha 'src/[!sdk]**/*.test.{js,ts}' --ignore 'src/sdk/**/*.test.{js,ts}' --watch",
"test:integration": "ts-mocha tests/index.js",
"setup": "./scripts/migrate_contracts.sh",
Expand Down Expand Up @@ -79,7 +79,7 @@
"@project-serum/anchor": "0.24.1",
"@scure/base": "1.1.1",
"@solana/spl-token": "0.3.8",
"@solana/web3.js": "1.78.4",
"@solana/web3.js": "1.93.4",
"abi-decoder": "2.4.0",
"ajv": "6.12.2",
"assert": "2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/libs/src/sdk/api/generated/default/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ export class ResponseError extends Error {

export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
constructor(cause: Error, msg?: string) {
super(msg, { cause });
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/libs/src/sdk/api/generated/full/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ export class ResponseError extends Error {

export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
constructor(cause: Error, msg?: string) {
super(msg, { cause });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ export class ResponseError extends Error {

export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
constructor(cause: Error, msg?: string) {
super(msg, { cause });
}
}

Expand Down
47 changes: 37 additions & 10 deletions packages/libs/src/sdk/services/Solana/SolanaRelay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import {
PublicKey,
SendTransactionError,
TransactionInstruction
} from '@solana/web3.js'

import { BaseAPI } from '../../api/generated/default'
import * as runtime from '../../api/generated/default/runtime'
Expand Down Expand Up @@ -102,15 +106,38 @@ export class SolanaRelay extends BaseAPI {
sendOptions
}

const response = await this.request(
{
path: '/relay',
method: 'POST',
headers: headerParameters,
body
},
initOverrides
)
let response: Response
try {
response = await this.request(
{
path: '/relay',
method: 'POST',
headers: headerParameters,
body
},
initOverrides
)
} catch (e) {
// Catch response errors, and if possible, recreate the original
// SendTransactionError to transparently raise to the caller.
Comment thread
rickyrombo marked this conversation as resolved.
if (e instanceof Error && e.name === 'ResponseError') {
const resp = (e as runtime.ResponseError).response.clone()
const body = await resp.json()
if (
'error' in body &&
'transactionMessage' in body &&
'signature' in body
) {
throw new SendTransactionError({
action: body.error.indexOf('Simulation') > -1 ? 'simulate' : 'send',
signature: body.signature,
transactionMessage: body.transactionMessage,
logs: body.transactionLogs
})
}
}
throw e
}

return await new runtime.JSONApiResponse(response, (json) => {
if (!runtime.exists(json, 'signature')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { RewardManagerProgram } from '@audius/spl'
import {
RewardManagerInstruction,
RewardManagerErrorCode,
RewardManagerProgram
} from '@audius/spl'
import type { RewardManagerStateData } from '@audius/spl/dist/types/reward-manager/types'
import { Secp256k1Program, type PublicKey } from '@solana/web3.js'
import { SendTransactionOptions } from '@solana/wallet-adapter-base'
import {
Secp256k1Program,
SendTransactionError,
Transaction,
VersionedTransaction,
type PublicKey
} from '@solana/web3.js'

import { productionConfig } from '../../../../config/production'
import { mergeConfigWithDefaults } from '../../../../utils/mergeConfigs'
Expand All @@ -22,6 +33,61 @@ import {
GetSubmittedAttestationsSchema
} from './types'

type CustomInstructionErrorMessage = {
InstructionError: [number, { Custom: number }]
}

/**
* Mapping of custom instruction error codes to error messages
* @see {@link https://github.com/AudiusProject/audius-protocol/blob/2a37bcff1bb1a82efdf187d1723b3457dc0dcb9b/solana-programs/reward-manager/program/src/error.rs solana-programs/reward-manager/program/src/errors.rs}
*/
const codeMessageMap: Record<RewardManagerErrorCode, string> = {
[RewardManagerErrorCode.IncorrectOwner]:
'Input account owner is not the program address',
[RewardManagerErrorCode.SignCollision]:
'Signature with an already met principal',
[RewardManagerErrorCode.WrongSigner]: 'Unexpected signer met',
[RewardManagerErrorCode.NotEnoughSigners]: "Isn't enough signers keys",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I know the grammar here is wrong. It's a direct 1:1 copy from the program itself, which outputs this in the program logs on chain.

[RewardManagerErrorCode.Secp256InstructionMissing]:
'Secp256 instruction missing',
[RewardManagerErrorCode.InstructionLoadError]: 'Instruction load error',
[RewardManagerErrorCode.RepeatedSenders]: 'Repeated sender',
[RewardManagerErrorCode.SignatureVerificationFailed]:
'Signature verification failed',
[RewardManagerErrorCode.OperatorCollision]:
'Some signers have same operators',
[RewardManagerErrorCode.AlreadySent]: 'Funds already sent',
[RewardManagerErrorCode.IncorrectMessages]: 'Incorrect messages',
[RewardManagerErrorCode.MessagesOverflow]: 'Messages overflow',
[RewardManagerErrorCode.MathOverflow]: 'Math overflow',
[RewardManagerErrorCode.InvalidRecipient]: 'Invalid Recipient'
}

export class RewardManagerError extends Error {
override name = 'RewardManagerError'
public code: number
public instructionName: string
public customErrorName?: string
constructor({
code,
instructionName,
cause
}: {
code: number
instructionName: string
cause?: Error
}) {
super(
codeMessageMap[code as RewardManagerErrorCode] ??
`Unknown error: ${code}`,
{ cause }
)
this.code = code
this.instructionName = instructionName
this.customErrorName = RewardManagerErrorCode[code]
}
}

/**
* Connected client to the Solana RewardManager program.
*
Expand Down Expand Up @@ -245,4 +311,74 @@ export class RewardManagerClient extends BaseSolanaProgramClient {
}
return this.rewardManagerState
}

/**
* Override the sendTransaction method to provide some more friendly errors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed for this PR but is this something that can be generalized for other programs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think so - I think each client should do similar things here

* back to the consumer for RewardManager instructions
*/
public override async sendTransaction(
transaction: Transaction | VersionedTransaction,
sendOptions?: SendTransactionOptions | undefined
): Promise<string> {
try {
return await super.sendTransaction(transaction, sendOptions)
} catch (e) {
if (e instanceof SendTransactionError) {
try {
const error = JSON.parse(
e.transactionError.message
) as CustomInstructionErrorMessage
if (error && error.InstructionError) {
const instructionIndex = error.InstructionError[0]
const code = error.InstructionError[1]?.Custom

// Parse the different transaction types differently
if ('instructions' in transaction) {
// Legacy Transaction
const instruction = transaction.instructions[instructionIndex]
// Check error instruction is from RewardManagerProgram
if (instruction && instruction.programId.equals(this.programId)) {
const decodedInstruction =
RewardManagerProgram.decodeInstruction(instruction)
throw new RewardManagerError({
code,
instructionName:
RewardManagerInstruction[
decodedInstruction.data.instruction
] ?? 'Unknown',
cause: e
})
}
} else {
// VersionedTransaction
const instruction =
transaction.message.compiledInstructions[instructionIndex]
// Check error instruction is from RewardManagerProgram
if (
instruction &&
transaction.message.staticAccountKeys[
instruction.programIdIndex
]?.equals(this.programId)
) {
throw new RewardManagerError({
code,
instructionName:
RewardManagerInstruction[instruction!.data[0] as number] ??
'Unknown',
cause: e
})
}
}
}
} catch (e) {
if (e instanceof RewardManagerError) {
throw e
}
// If failed to provide user friendly error, surface original error
console.warn('Failed to parse RewardManagerError error', e)
}
}
throw e
}
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { RewardManagerClient } from './RewardManagerClient'
export { RewardManagerClient, RewardManagerError } from './RewardManagerClient'
export { getDefaultRewardManagerClentConfig } from './getDefaultConfig'
Loading