Skip to content
Open
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
Empty file.
6 changes: 6 additions & 0 deletions ts/packages/anchor-upgrade/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest/presets/default',
testEnvironment: 'node',
testTimeout: 90000,
resolver: "ts-jest-resolver",
};
72 changes: 72 additions & 0 deletions ts/packages/anchor-upgrade/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@anchor-lang/core-upgrade",
"version": "0.1.0",
"description": "Anchor upgraded client",
"module": "./dist/esm/index.js",
"main": "./dist/cjs/index.js",
"browser": "./dist/browser/index.js",
"license": "(MIT OR Apache-2.0)",
"types": "dist/cjs/index.d.ts",
"homepage": "https://github.com/coral-xyz/anchor#readme",
"bugs": {
"url": "https://github.com/coral-xyz/anchor/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/coral-xyz/anchor.git"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=17"
},
"scripts": {
"build": "rimraf dist/ && yarn build:node && yarn build:browser",
"build:node": "tsc && tsc -p tsconfig.cjs.json",
"build:browser": "rollup --config",
"lint:fix": "prettier src/** tests/** -w",
"lint": "prettier src/** tests/** --check",
"watch": "tsc -p tsconfig.cjs.json --watch",
"prepublishOnly": "yarn build",
"docs": "typedoc --excludePrivate --includeVersion --out ../../../docs/src/.vuepress/dist/ts/ --readme none src/index.ts",
"test": "jest tests --detectOpenHandles"
},
"dependencies": {
"@solana/kit": "6.1.0",
"camelcase": "^6.3.0",
"pako": "^2.0.3",
"toml": "^3.0.0"
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.3.0",
"@types/jest": "^27.4.1",
"@types/pako": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"eslint": "^7.12.1",
"eslint-config-prettier": "^6.15.0",
"husky": "^4.3.0",
"jest": "27.3.1",
"jest-config": "27.3.1",
"lint-staged": "^10.5.0",
"prettier": "^2.1.2",
"rimraf": "^3.0.2",
"rollup": "^2.60.2",
"ts-jest": "^27.0.7",
"ts-jest-resolver": "^2.0.0",
"ts-node": "^9.0.0",
"tslib": "^2.3.1",
"typedoc": "^0.22.10",
"typescript": "^5.5.4"
},
"files": [
"dist",
"types"
]
}
37 changes: 37 additions & 0 deletions ts/packages/anchor-upgrade/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import typescript from "@rollup/plugin-typescript";
import replace from "@rollup/plugin-replace";
import commonjs from "@rollup/plugin-commonjs";

const env = process.env.NODE_ENV;

export default {
input: "src/index.ts",
plugins: [
commonjs(),
typescript({
tsconfig: "./tsconfig.base.json",
moduleResolution: "node",
outDir: "types",
target: "es2019",
outputToFilesystem: false,
}),
replace({
preventAssignment: true,
values: {
"process.env.NODE_ENV": JSON.stringify(env),
"process.env.ANCHOR_BROWSER": JSON.stringify(true),
},
}),
],
external: [
"@solana/kit",
"camelcase",
"pako",
"toml",
],
output: {
file: "dist/browser/index.js",
format: "es",
sourcemap: true,
},
};
25 changes: 25 additions & 0 deletions ts/packages/anchor-upgrade/src/clients/account-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { AccountCodec } from '../codec/account-codec';
import type { AccountByName, AccountName, DecodedAccount, Idl } from '../types';

export class AccountClient<IDL extends Idl, N extends AccountName<IDL>> {
private readonly codec: AccountCodec<DecodedAccount<IDL, N>>;

constructor(
private readonly idl: IDL,
private readonly accountDef: AccountByName<IDL, N>,
) {
this.codec = new AccountCodec<DecodedAccount<IDL, N>>(this.idl, this.accountDef);
}

get discriminator(): Uint8Array {
return Uint8Array.from(this.accountDef.discriminator);
}

encode(data: DecodedAccount<IDL, N>): Uint8Array {
return this.codec.encode(data);
}

decode(data: Uint8Array): DecodedAccount<IDL, N> {
return this.codec.decode(data);
}
}
17 changes: 17 additions & 0 deletions ts/packages/anchor-upgrade/src/clients/constant-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { decodeIdlConstValue } from '../codec/type-codec';
import type { ConstantByName, ConstantName, DecodedConstant, Idl } from '../types';

export class ConstantClient<IDL extends Idl, N extends ConstantName<IDL>> {
constructor(
private readonly idl: IDL,
private readonly constantDef: ConstantByName<IDL, N>,
) {}

get value() {
return decodeIdlConstValue(
this.constantDef.type,
this.constantDef.value,
this.idl.types ?? [],
) as DecodedConstant<IDL, N>;
}
}
16 changes: 16 additions & 0 deletions ts/packages/anchor-upgrade/src/clients/event-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { EventCodec } from '../codec/event-codec';
import type { DecodedEvent, EventByName, EventName, Idl } from '../types';

export class EventClient<IDL extends Idl, N extends EventName<IDL>> {
private readonly codec: EventCodec<DecodedEvent<IDL, N>>;
constructor(
private readonly idl: IDL,
private readonly eventDef: EventByName<IDL, N>,
) {
this.codec = new EventCodec<DecodedEvent<IDL, N>>(this.idl, this.eventDef);
}

decode(base64Log: string): DecodedEvent<IDL, N> {
return this.codec.decode(base64Log);
}
}
4 changes: 4 additions & 0 deletions ts/packages/anchor-upgrade/src/clients/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './account-client';
export * from './constant-client';
export * from './event-client';
export * from './instruction-client';
60 changes: 60 additions & 0 deletions ts/packages/anchor-upgrade/src/clients/instruction-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type Address, getAddressDecoder } from '@solana/kit';

import { InstructionCodec } from '../codec/instruction-codec';
import { IdlError } from '../error';
import type {
Idl,
InstructionAccountName,
InstructionArgs,
InstructionByName,
InstructionName,
} from '../types';
import { flattenInstructionAccounts } from '../utils/instruction-accounts';

export class InstructionClient<IDL extends Idl, N extends InstructionName<IDL>> {
private readonly codec: InstructionCodec<InstructionArgs<IDL, N>>;
private readonly accountIndexByName: Map<string, number>;

constructor(
private readonly idl: IDL,
private readonly instructionDef: InstructionByName<IDL, N>,
) {
if (instructionDef.name === '_inner') {
throw new IdlError('The _inner name is reserved');
}

this.accountIndexByName = new Map(
flattenInstructionAccounts(instructionDef.accounts).map((account, index) => [
account.name,
index,
]),
);

this.codec = new InstructionCodec<InstructionArgs<IDL, N>>(this.idl, this.instructionDef);
}

get discriminator(): Uint8Array {
return Uint8Array.from(this.instructionDef.discriminator);
}

encode(data: InstructionArgs<IDL, N>): Uint8Array {
return this.codec.encode(data);
}

decode(data: Uint8Array): InstructionArgs<IDL, N> {
return this.codec.decode(data);
}

getAccount(
accountName: InstructionAccountName<IDL, N>,
instructionAccounts: Uint8Array[],
): Address<string> {
const acc = instructionAccounts[this.accountIndexByName.get(accountName)!];

if (!acc) {
throw new IdlError('Account not found');
}

return getAddressDecoder().decode(acc);
}
}
34 changes: 34 additions & 0 deletions ts/packages/anchor-upgrade/src/codec/account-codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Idl, IdlDiscriminator, NullableIdlAccount } from '../types';
import type { IdlCodec } from './type-codec';
import {
assertDiscriminator,
getTypeDefLayoutByName,
stripDiscriminator,
} from './type-codec/helpers';

export class AccountCodec<T = unknown> {
private readonly discriminator: IdlDiscriminator;
private readonly layout: IdlCodec;

constructor(idl: Idl, accountDef: NullableIdlAccount<Idl>) {
this.discriminator = accountDef.discriminator;
this.layout = getTypeDefLayoutByName(idl, accountDef.name, 'account').layout;
}

encode(accountData: T): Uint8Array {
const encodedRaw = this.layout.encode(accountData);
const encoded = new Uint8Array(
encodedRaw.buffer,
encodedRaw.byteOffset,
encodedRaw.byteLength
);

return Uint8Array.from([...this.discriminator, ...encoded])
}

decode(encodedData: Uint8Array): T {
assertDiscriminator(encodedData, this.discriminator, 'Invalid account discriminator');

return this.layout.decode(stripDiscriminator(encodedData, this.discriminator)) as T;
}
}
25 changes: 25 additions & 0 deletions ts/packages/anchor-upgrade/src/codec/event-codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Idl, IdlDiscriminator, NullableIdlEvent } from '../types';
import type { IdlCodec } from './type-codec';
import {
assertDiscriminator,
getTypeDefLayoutByName,
stripDiscriminator,
} from './type-codec/helpers';

export class EventCodec<T = unknown> {
private readonly discriminator: IdlDiscriminator;
private readonly layout: IdlCodec;

constructor(idl: Idl, eventDef: NullableIdlEvent<Idl>) {
this.discriminator = eventDef.discriminator;
this.layout = getTypeDefLayoutByName(idl, eventDef.name, 'event').layout;
}

decode(base64Log: string): T {
const encodedData = Uint8Array.from(Buffer.from(base64Log, 'base64'));

assertDiscriminator(encodedData, this.discriminator, 'Invalid event discriminator');

return this.layout.decode(stripDiscriminator(encodedData, this.discriminator)) as T;
}
}
3 changes: 3 additions & 0 deletions ts/packages/anchor-upgrade/src/codec/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './account-codec';
export * from './event-codec';
export * from './instruction-codec';
36 changes: 36 additions & 0 deletions ts/packages/anchor-upgrade/src/codec/instruction-codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getStructCodec } from '@solana/kit';

import type { Idl, IdlDiscriminator, IdlInstruction } from '../types';
import { fieldLayout, type IdlCodec, type IdlNamedCodec } from './type-codec';
import { assertDiscriminator, stripDiscriminator } from './type-codec/helpers';

export class InstructionCodec<T = unknown> {
private readonly discriminator: IdlDiscriminator;
private readonly layout: IdlCodec;

constructor(idl: Idl, instructionDef: IdlInstruction) {
const fieldCodecs = instructionDef.args.map(
(arg) => fieldLayout(arg, idl.types) as IdlNamedCodec,
);

this.discriminator = instructionDef.discriminator;
this.layout = getStructCodec(fieldCodecs);
}

encode(instructionData: T): Uint8Array {
const encodedRaw = this.layout.encode(instructionData);
const encoded = new Uint8Array(
encodedRaw.buffer,
encodedRaw.byteOffset,
encodedRaw.byteLength
);

return Uint8Array.from([...this.discriminator, ...encoded])
}

decode(encodedData: Uint8Array): T {
assertDiscriminator(encodedData, this.discriminator, 'Invalid instruction discriminator');

return this.layout.decode(stripDiscriminator(encodedData, this.discriminator)) as T;
}
}
46 changes: 46 additions & 0 deletions ts/packages/anchor-upgrade/src/codec/type-codec/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { IdlError } from '../../error';
import type { Idl, IdlDiscriminator } from '../../types';
import { type IdlCodec, typeDefLayout } from './idl';

export function getTypeDefLayoutByName(
idl: Idl,
name: string,
kind: 'account' | 'event',
): { discriminator?: IdlDiscriminator; layout: IdlCodec } {
const { types } = idl;

if (!types) {
throw new IdlError(`${capitalize(kind)}s require \`idl.types\``);
}

const typeDef = types.find((item) => item.name === name);

if (!typeDef) {
throw new IdlError(`${capitalize(kind)} not found: ${name}`);
}

return {
layout: typeDefLayout({ typeDef, types }),
};
}

export function assertDiscriminator(
actualData: Uint8Array,
expectedDiscriminator: IdlDiscriminator,
errorMessage: string,
) {
const expected = Buffer.from(expectedDiscriminator);
const actual = actualData.subarray(0, expected.length);

if (expected.compare(actual) !== 0) {
throw new IdlError(errorMessage);
}
}

export function stripDiscriminator(data: Uint8Array, discriminator: IdlDiscriminator): Uint8Array {
return data.subarray(discriminator.length);
}

function capitalize(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
Loading