Skip to content
This repository has been archived by the owner on Sep 25, 2024. It is now read-only.

Check onchain metadata #1528

Merged
merged 9 commits into from
Feb 19, 2024
5 changes: 5 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require": "ts-node/register",
"extension": ["ts"],
"watch-files": ["src/**/*.ts"]
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,36 @@
"license": "MIT",
"scripts": {
"validate-PR": "yarn build && node dist/main.js",
"build": "yarn tsc",
"build": "tsc",
"update-partners": "yarn build && node dist/partners/scripts/get-wormhole.js && node dist/partners/scripts/get-solana-fm.js",
"get-wormhole": "yarn build && node dist/partners/scripts/get-wormhole.js",
"check-wormhole": "yarn build && node dist/partners/scripts/check-wormhole-with-verified.js",
"get-solana-fm": "yarn build && node dist/partners/scripts/get-solana-fm.js",
"format": "yarn prettier --write **/*.ts",
"format-check": "yarn prettier --check **/*.ts",
"lint": "eslint src/**/*.ts"
"lint": "eslint src/**/*.ts",
"test": "vitest --test-timeout 20000 src/**/*.ts"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@solana/web3.js": "^1.73.2",
"@metaplex-foundation/js": "^0.20.1",
"@solana/spl-token": "^0.4.0",
"@solana/web3.js": "^1.90.0",
"@types/minimist": "^1.2.5",
"csv-writer": "^1.6.0",
"minimist": "^1.2.8",
"node-downloader-helper": "^2.1.6",
"node-fetch": "^2.6.6"
"node-fetch": "^2.6.6",
"typescript-node": "^0.1.3"
},
"devDependencies": {
"@types/node": "^18.13.0",
"@types/node-fetch": "^2.6.2",
"csv-parse": "^5.5.3",
"typescript": "^4.9.5"
"ts-node": "^10.9.2",
"typescript": "^4.9.5",
"vitest": "^1.2.2"
}
}
28 changes: 18 additions & 10 deletions src/logic.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as core from "@actions/core";
import { exec } from "@actions/exec";
import { detectDuplicateSymbol, detectDuplicateMints, canOnlyAddOneToken, validMintAddress, noEditsToPreviousLinesAllowed, isCommunityValidated, isSymbolConfusing } from "./utils/validate";
import { detectDuplicateSymbol, detectDuplicateMints, canOnlyAddOneToken, validMintAddress, noEditsToPreviousLinesAllowed, isCommunityValidated, isSymbolConfusing, newTokensHaveMatchingOnchainMeta, findAddedTokens} from "./utils/validate";
import { ValidatedTokensData } from "./types/types";
import { Connection, clusterApiUrl } from "@solana/web3.js";
import { indexToLineNumber } from "./utils/validate";
import { parse } from "csv-parse/sync";
import fs from "fs";
Expand All @@ -13,14 +14,15 @@ export async function validateValidatedTokensCsv(filename: string): Promise<numb
const recordsPreviousRaw = await gitPreviousVersion("validated-tokens.csv");
fs.writeFileSync(".validated-tokens-0.csv", recordsPreviousRaw);
const [recordsPrevious, _] = parseCsv(".validated-tokens-0.csv")

let duplicateSymbols;
let duplicateMints;
let attemptsToAddMultipleTokens;
let invalidMintAddresses;
let notCommunityValidated;
let noEditsAllowed;
let potentiallyConfusingSymbols;
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
let duplicateSymbols = 0;
let duplicateMints = 0;
let attemptsToAddMultipleTokens = 0;
let invalidMintAddresses = 0;
let notCommunityValidated = 0;
let noEditsAllowed = 0;
let potentiallyConfusingSymbols = 0;
let doubleCheckMetadataOnChain = 0;

duplicateSymbols = detectDuplicateSymbol(recordsPrevious, records);
duplicateMints = detectDuplicateMints(records);
Expand All @@ -30,14 +32,20 @@ export async function validateValidatedTokensCsv(filename: string): Promise<numb
notCommunityValidated = isCommunityValidated(records);
potentiallyConfusingSymbols = isSymbolConfusing(recordsPrevious, records);

// other validations have their own way of finding newly added tokens. no
// time to go through all of them, plus they do something different.
const newTokens = findAddedTokens(recordsPrevious, records);
doubleCheckMetadataOnChain = await newTokensHaveMatchingOnchainMeta(connection, newTokens);

console.log("No More Duplicate Symbols:", duplicateSymbols, `(${allowedDuplicateSymbols.length} exceptions)`);
console.log("Duplicate Mints:", duplicateMints);
console.log("Attempts to Add Multiple Tokens:", attemptsToAddMultipleTokens);
console.log("Invalid Mint Addresses:", invalidMintAddresses);
console.log("Not Community Validated:", notCommunityValidated, `(${allowedNotCommunityValidated.length} exceptions)`);
console.log("Edits to Existing Tokens:", noEditsAllowed);
console.log("Issues with Symbols in Added Tokens:", potentiallyConfusingSymbols);
return (duplicateSymbols + duplicateMints + attemptsToAddMultipleTokens + invalidMintAddresses + noEditsAllowed)
console.log("Onchain Metadata Mismatches:", doubleCheckMetadataOnChain);
return (duplicateSymbols + duplicateMints + attemptsToAddMultipleTokens + invalidMintAddresses + noEditsAllowed + doubleCheckMetadataOnChain)
}

// Get previous version of validated-tokens.csv from last commit
Expand Down
1 change: 1 addition & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export enum ValidationError {
INVALID_MINT = "Invalid mint address, not base58 decodable",
INVALID_DECIMALS = "Invalid decimals",
INVALID_IMAGE_URL = "Invalid image URL",
INVALID_METADATA = "Metadata does not match on-chain data",
INVALID_COMMUNITY_VALIDATED = "Invalid community validated",
CHANGES_DISCOURAGED = "Tokens already in the CSV should not be edited"
}
Expand Down
75 changes: 75 additions & 0 deletions src/utils/__snapshots__/metadata.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`should work with a Token/Metaplex-metadata mint 1`] = `
[
[
{
"decimals": 6,
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"uri": "https://static.jup.ag/jup/metadata.json",
},
],
0,
]
`;

exports[`should work with a Token2022 mint 1`] = `
[
[
{
"decimals": 9,
"mint": "HbxiDXQxBKMNJqDsTavQE7LVwrTR36wjV2EaYEqUw6qH",
"name": "GH0ST",
"symbol": "GH0ST",
"uri": "https://bafybeialzeyqbeg7fzhebnknqxancgx3fi7n6xa4uq5lny62sa4xsuouiy.ipfs.dweb.link",
},
],
0,
]
`;

exports[`should work with a Token2022/Community-metadata mint 1`] = `
[
[
{
"decimals": 5,
"mint": "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo",
"name": "BonkEarn",
"symbol": "BERN",
"uri": "https://api.npoint.io/6276c0cc3ab046e9b770",
},
],
0,
]
`;

exports[`should work with a list of mints 1`] = `
[
[
{
"decimals": 6,
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"uri": "https://static.jup.ag/jup/metadata.json",
},
{
"decimals": 5,
"mint": "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo",
"name": "BonkEarn",
"symbol": "BERN",
"uri": "https://api.npoint.io/6276c0cc3ab046e9b770",
},
{
"decimals": 9,
"mint": "HbxiDXQxBKMNJqDsTavQE7LVwrTR36wjV2EaYEqUw6qH",
"name": "GH0ST",
"symbol": "GH0ST",
"uri": "https://bafybeialzeyqbeg7fzhebnknqxancgx3fi7n6xa4uq5lny62sa4xsuouiy.ipfs.dweb.link",
},
],
0,
]
`;
90 changes: 90 additions & 0 deletions src/utils/__snapshots__/validate.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`should work with a Token/Metaplex-metadata mint 1`] = `
[
[
{
"decimals": 6,
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"uri": "https://static.jup.ag/jup/metadata.json",
},
],
0,
]
`;

exports[`should work with a Token/Metaplex-metadata mint 2`] = `
[
[
{
"decimals": 6,
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"uri": "https://static.jup.ag/jup/metadata.json",
},
],
0,
]
`;

exports[`should work with a Token2022 mint 1`] = `
[
[
{
"decimals": 9,
"mint": "HbxiDXQxBKMNJqDsTavQE7LVwrTR36wjV2EaYEqUw6qH",
"name": "GH0ST",
"symbol": "GH0ST",
"uri": "https://bafybeialzeyqbeg7fzhebnknqxancgx3fi7n6xa4uq5lny62sa4xsuouiy.ipfs.dweb.link",
},
],
0,
]
`;

exports[`should work with a Token2022/Community-metadata mint 1`] = `
[
[
{
"decimals": 5,
"mint": "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo",
"name": "BonkEarn",
"symbol": "BERN",
"uri": "https://api.npoint.io/6276c0cc3ab046e9b770",
},
],
0,
]
`;

exports[`should work with a list of mints 1`] = `
[
[
{
"decimals": 6,
"mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"uri": "https://static.jup.ag/jup/metadata.json",
},
{
"decimals": 5,
"mint": "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo",
"name": "BonkEarn",
"symbol": "BERN",
"uri": "https://api.npoint.io/6276c0cc3ab046e9b770",
},
{
"decimals": 9,
"mint": "HbxiDXQxBKMNJqDsTavQE7LVwrTR36wjV2EaYEqUw6qH",
"name": "GH0ST",
"symbol": "GH0ST",
"uri": "https://bafybeialzeyqbeg7fzhebnknqxancgx3fi7n6xa4uq5lny62sa4xsuouiy.ipfs.dweb.link",
},
],
0,
]
`;
27 changes: 27 additions & 0 deletions src/utils/metadata.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect, test } from 'vitest';
import { PublicKey, Connection, clusterApiUrl, AccountInfo } from "@solana/web3.js";
import { findMetadata } from './metadata';

const JUP = new PublicKey('JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN'); // Metaplex metadata
const BERN = new PublicKey('CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo'); // Community metadata
const GHOST = new PublicKey('HbxiDXQxBKMNJqDsTavQE7LVwrTR36wjV2EaYEqUw6qH'); // Token2022 Metadata extension
test('should work with a Token2022 mint', async () => {
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
let t2022Meta = await findMetadata(connection, [GHOST])
expect(t2022Meta).toMatchSnapshot()
})
test('should work with a Token/Metaplex-metadata mint', async () => {
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
let t2022Meta = await findMetadata(connection, [JUP])
expect(t2022Meta).toMatchSnapshot()
})
test('should work with a Token2022/Community-metadata mint', async () => {
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
let metadata = await findMetadata(connection, [BERN])
expect(metadata).toMatchSnapshot()
})
test('should work with a list of mints', async () => {
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
let metadata = await findMetadata(connection, [JUP, BERN, GHOST])
expect(metadata).toMatchSnapshot()
})
Loading
Loading