-
Notifications
You must be signed in to change notification settings - Fork 131
Improve SDK rewards claiming errors with RewardManagerError abstraction #9214
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
Changes from all commits
6c27c4d
ac159a1
7895494
b3f076a
92129a2
bff9c4a
f212e0c
9ee55fa
2b07ab4
40a141b
b8c612c
8e2cd2d
e36b5dd
40b54a9
49394e2
1eec62e
b2e9861
f2c15bf
13ac2af
8c31681
433cdce
9f3ceb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "module": "commonjs" | ||
| } | ||
| } |
| 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' | ||
|
|
@@ -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", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| * | ||
|
|
@@ -245,4 +311,74 @@ export class RewardManagerClient extends BaseSolanaProgramClient { | |
| } | ||
| return this.rewardManagerState | ||
| } | ||
|
|
||
| /** | ||
| * Override the sendTransaction method to provide some more friendly errors | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' |
Uh oh!
There was an error while loading. Please reload this page.