Skip to content
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
237 changes: 164 additions & 73 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ members = [
"governance/chat/program",
"libraries/math",
"memo/program",
"metadata/program",
"name-service/program",
"record/program",
"shared-memory/program",
Expand Down
9 changes: 9 additions & 0 deletions metadata/js/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
4 changes: 4 additions & 0 deletions metadata/js/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules

docs
lib
14 changes: 14 additions & 0 deletions metadata/js/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"root": true,
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier"],
"rules": {
"@typescript-eslint/ban-ts-comment": "off"
}
}
2 changes: 2 additions & 0 deletions metadata/js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib
docs
5 changes: 5 additions & 0 deletions metadata/js/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extension": ["ts"],
"node-option": ["experimental-specifier-resolution=node", "loader=ts-node/esm"],
"timeout": 5000
}
Empty file added metadata/js/.nojekyll
Empty file.
3 changes: 3 additions & 0 deletions metadata/js/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
docs
lib
test-ledger
7 changes: 7 additions & 0 deletions metadata/js/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"printWidth": 120,
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true
}
111 changes: 111 additions & 0 deletions metadata/js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# @bbachain/spl-token-metadata

BBAChain JavaScript SDK for interacting with a Token Metadata Program on BBAChain.

This library supports:
- Initializing token metadata
- Updating token metadata
- Reading token metadata

> Compatible with SPL Token standard and deployed custom program at:
> `metabUBuFKTWPWAAARaQdNXUH6Sxk5tFGQjGEgWCvaX`

---

## 📦 Installation

```bash
npm install @bbachain/spl-token-metadata
```
---

## 🚀 Usage

### 🧱 Initialize metadata

```ts
import {
initializeMetadata
} from "@bbachain/spl-token-metadata";
import {
Connection,
Keypair,
PublicKey,
clusterApiUrl
} from "@solana/web3.js";

const connection = new Connection(clusterApiUrl("testnet"));
const payer = Keypair.generate(); // Or load from file
const mint = new PublicKey("YourMintAddressHere");

await initializeMetadata(
connection,
payer,
mint,
"My Token",
"MTK",
"https://example.com/metadata.json"
);
```

---

### ✏️ Update metadata

```ts
import { updateMetadata } from "@bbachain/spl-token-metadata";

await updateMetadata(
connection,
payer,
mint,
"New Token Name",
"NTK",
"https://example.com/new-uri.json"
);
```

---

### 🔍 Read metadata

```ts
import { readMetadata } from "@bbachain/spl-token-metadata";

const metadata = await readMetadata(connection, mint);
if (metadata) {
console.log("Name:", metadata.name);
console.log("Symbol:", metadata.symbol);
console.log("URI:", metadata.uri);
}
```

---

## 🔧 API Reference

### `initializeMetadata(connection, payer, mint, name, symbol, uri)`

Creates the metadata account for a token mint (PDA). Only runs once per mint.

### `updateMetadata(connection, payer, mint, name, symbol, uri)`

Updates existing metadata. Only the authority set during initialization can update.

### `readMetadata(connection, mint): TokenMetadata | null`

Fetches and deserializes the on-chain metadata.

---

## 🧠 Notes

- This library uses **Borsh** for serialization (compatible with the Rust program).
- Metadata is stored in a PDA derived from `[b"metadata", mint]`.
- Compatible with any Solana cluster, including BBAChain (mainnet, testnet and localnet).

---

## 🪪 License

MIT © BBAChain Contributors
77 changes: 77 additions & 0 deletions metadata/js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "@bbachain/spl-token-metadata",
"version": "0.0.2",
"author": "BBAChain Labs <[email protected]>",
"repository": "https://github.com/bbachain/program-executor",
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"files": [
"lib",
"src",
"LICENSE",
"README.md"
],
"type": "module",
"sideEffects": false,
"main": "lib/cjs/index.js",
"module": "lib/esm/index.mjs",
"types": "lib/types/index.d.ts",
"exports": {
"require": "./lib/cjs/index.js",
"import": "./lib/esm/index.mjs",
"types": "./lib/types/index.d.ts"
},
"scripts": {
"clean": "shx rm -rf lib",
"build": "yarn clean && tsc -p tsconfig.json; tsc-esm -p tsconfig.json && tsc -p tsconfig.cjs.json",
"postbuild": "echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
"deploy": "yarn docs && gh-pages --dist docs --dest token/js --dotfiles",
"test": "yarn test:unit && yarn test:e2e-built && yarn test:e2e-native && yarn test:e2e-2022",
"test:unit": "mocha test/unit",
"test:e2e-built": "start-server-and-test 'solana-test-validator --bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA ../../target/deploy/spl_token.so --reset --quiet' http://localhost:8899/health 'mocha test/e2e'",
"test:e2e-2022": "TEST_PROGRAM_ID=TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb start-server-and-test 'solana-test-validator --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL ../../target/deploy/spl_associated_token_account.so --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb ../../target/deploy/spl_token_2022.so --reset --quiet' http://localhost:8899/health 'mocha test/e2e*'",
"test:e2e-native": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health 'mocha test/e2e'",
"test:build-programs": "cargo build-bpf --manifest-path ../program/Cargo.toml && cargo build-bpf --manifest-path ../program-2022/Cargo.toml && cargo build-bpf --manifest-path ../../associated-token-account/program/Cargo.toml",
"docs": "shx rm -rf docs && NODE_OPTIONS=--max_old_space_size=4096 typedoc && shx cp .nojekyll docs/",
"fmt": "prettier --write '{*,**/*}.{js,ts,jsx,tsx,json}'",
"lint": "eslint --max-warnings 0 --ext .ts . && prettier --check '{*,**/*}.{js,ts,jsx,tsx,json}'",
"lint:fix": "eslint --fix --ext .ts . && yarn fmt",
"nuke": "shx rm -rf node_modules yarn.lock"
},
"dependencies": {
"@bbachain/buffer-layout": "^1.0.0",
"@bbachain/buffer-layout-utils": "^1.0.0",
"@bbachain/web3.js": "^1.0.3",
"rpc-websockets": "7.11.0"
},
"devDependencies": {
"@bbachain/spl-memo": "^0.0.1",
"@types/bn.js": "^5.1.6",
"@types/chai-as-promised": "^7.1.4",
"@types/eslint": "^8.4.0",
"@types/eslint-plugin-prettier": "^3.1.0",
"@types/mocha": "^9.1.0",
"@types/node": "^16.11.21",
"@types/node-fetch": "^2.6.12",
"@types/prettier": "^2.4.3",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"gh-pages": "^3.2.3",
"mocha": "^9.1.4",
"prettier": "^2.5.1",
"shx": "^0.3.4",
"start-server-and-test": "^1.14.0",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
"typedoc": "^0.22.11",
"typescript": "^4.5.5",
"typescript-esm": "^2.0.0"
}
}
75 changes: 75 additions & 0 deletions metadata/js/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
Connection,
PublicKey,
TransactionInstruction,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
Signer,
} from '@bbachain/web3.js';
import { METADATA_SEED, PROGRAM_ID } from './constants';
import { deserializeMetadata } from './deserialize';
import { encodeInitializeInstruction, encodeUpdateInstruction } from './instructions';
import { TokenMetadata } from './types';

export async function getMetadataPDA(mint: PublicKey): Promise<[PublicKey, number]> {
return await PublicKey.findProgramAddress([Buffer.from(METADATA_SEED), mint.toBuffer()], PROGRAM_ID);
}

export async function initializeMetadata(
connection: Connection,
payer: Signer,
mint: PublicKey,
name: string,
symbol: string,
uri: string
) {
const [metadataPDA, bump] = await getMetadataPDA(mint);

const ix = new TransactionInstruction({
programId: PROGRAM_ID,
keys: [
{ pubkey: metadataPDA, isSigner: false, isWritable: true },
{ pubkey: mint, isSigner: false, isWritable: false },
{ pubkey: payer.publicKey, isSigner: true, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
],
data: encodeInitializeInstruction(name, symbol, uri),
});

const tx = new Transaction().add(ix);
return await sendAndConfirmTransaction(connection, tx, [payer]);
}

export async function updateMetadata(
connection: Connection,
payer: Signer,
mint: PublicKey,
name: string,
symbol: string,
uri: string
) {
const [metadataPDA, _] = await getMetadataPDA(mint);

const ix = new TransactionInstruction({
programId: PROGRAM_ID,
keys: [
{ pubkey: metadataPDA, isSigner: false, isWritable: true },
{ pubkey: mint, isSigner: false, isWritable: false },
{ pubkey: payer.publicKey, isSigner: true, isWritable: false },
],
data: encodeUpdateInstruction(name, symbol, uri),
});

const tx = new Transaction().add(ix);
return await sendAndConfirmTransaction(connection, tx, [payer]);
}

export async function readMetadata(connection: Connection, mint: PublicKey): Promise<TokenMetadata | null> {
const [metadataPDA] = await getMetadataPDA(mint);
const accountInfo = await connection.getAccountInfo(metadataPDA);

if (!accountInfo) return null;

return deserializeMetadata(accountInfo.data);
}
4 changes: 4 additions & 0 deletions metadata/js/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PublicKey } from '@bbachain/web3.js';

export const PROGRAM_ID = new PublicKey('metabUBuFKTWPWAAARaQdNXUH6Sxk5tFGQjGEgWCvaX');
export const METADATA_SEED = 'metadata';
48 changes: 48 additions & 0 deletions metadata/js/src/deserialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { PublicKey } from '@bbachain/web3.js';
import * as borsh from 'borsh';
import { TokenMetadata } from './types';

// Classify for borsh.deserialize
class TokenMetadataBorsh {
mint: Uint8Array;
name: string;
symbol: string;
uri: string;
authority: Uint8Array;

constructor(props: any) {
this.mint = props.mint;
this.name = props.name;
this.symbol = props.symbol;
this.uri = props.uri;
this.authority = props.authority;
}
}

const METADATA_SCHEMA = new Map([
[
TokenMetadataBorsh,
{
kind: 'struct',
fields: [
['mint', [32]],
['name', 'string'],
['symbol', 'string'],
['uri', 'string'],
['authority', [32]],
],
},
],
]);

export function deserializeMetadata(data: Buffer): TokenMetadata {
const raw = borsh.deserialize(METADATA_SCHEMA, TokenMetadataBorsh, data);

return {
mint: new PublicKey(raw.mint),
name: raw.name,
symbol: raw.symbol,
uri: raw.uri,
authority: new PublicKey(raw.authority),
};
}
3 changes: 3 additions & 0 deletions metadata/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './client';
export * from './deserialize';
export * from './types';
Loading
Loading