sdk for migration & progress on lp_ui

Change-Id: I4ac855f1795ecffe50c68c428e530b215ba4b8e9
This commit is contained in:
Chase Moran 2021-09-12 21:29:11 -04:00 committed by Evan Gray
parent 5b51dffe6d
commit 8a90b50aeb
18 changed files with 1319 additions and 25 deletions

28
lp_ui/craco.config.js Normal file
View File

@ -0,0 +1,28 @@
const { addBeforeLoader, loaderByName } = require("@craco/craco");
module.exports = {
webpack: {
configure: (webpackConfig) => {
const wasmExtensionRegExp = /\.wasm$/;
webpackConfig.resolve.extensions.push(".wasm");
webpackConfig.module.rules.forEach((rule) => {
(rule.oneOf || []).forEach((oneOf) => {
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
oneOf.exclude.push(wasmExtensionRegExp);
}
});
});
const wasmLoader = {
test: /\.wasm$/,
include: /node_modules\/(bridge|token-bridge)/,
loaders: ["wasm-loader"],
};
addBeforeLoader(webpackConfig, loaderByName("file-loader"), wasmLoader);
return webpackConfig;
},
},
};

237
lp_ui/package-lock.json generated
View File

@ -32,6 +32,10 @@
"react-scripts": "4.0.3",
"typescript": "^4.4.2",
"web-vitals": "^1.0.1"
},
"devDependencies": {
"@craco/craco": "^6.3.0",
"wasm-loader": "^1.3.0"
}
},
"../sdk/js": {
@ -1289,6 +1293,42 @@
"node": ">=0.1.95"
}
},
"node_modules/@craco/craco": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.3.0.tgz",
"integrity": "sha512-SCnfEQxT/6NAbU/3sIWw7gQXtzjjiTp/EZFdJTd8inPURILIy0YajrC2p8qBG2KhFo5cwgOrEDyaGyAFvvuyuA==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"webpack-merge": "^4.2.2"
},
"bin": {
"craco": "bin/craco.js"
},
"engines": {
"node": ">=6"
},
"peerDependencies": {
"react-scripts": "^4.0.0"
}
},
"node_modules/@craco/craco/node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@csstools/convert-colors": {
"version": "1.4.0",
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
@ -11571,6 +11611,15 @@
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/long": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
"integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=",
"dev": true,
"engines": {
"node": ">=0.6"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
@ -17905,6 +17954,58 @@
"makeerror": "1.0.x"
}
},
"node_modules/wasm-dce": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz",
"integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==",
"dev": true,
"dependencies": {
"@babel/core": "^7.0.0-beta.39",
"@babel/traverse": "^7.0.0-beta.39",
"@babel/types": "^7.0.0-beta.39",
"babylon": "^7.0.0-beta.39",
"webassembly-interpreter": "0.0.30"
}
},
"node_modules/wasm-dce/node_modules/babylon": {
"version": "7.0.0-beta.47",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz",
"integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==",
"dev": true,
"bin": {
"babylon": "bin/babylon.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/wasm-loader": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/wasm-loader/-/wasm-loader-1.3.0.tgz",
"integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==",
"dev": true,
"dependencies": {
"loader-utils": "^1.1.0",
"wasm-dce": "^1.0.0"
},
"peerDependencies": {
"wasm-dce": "1.x"
}
},
"node_modules/wasm-loader/node_modules/loader-utils": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/watchpack": {
"version": "1.7.5",
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
@ -18039,6 +18140,34 @@
"version": "1.1.2",
"integrity": "sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig=="
},
"node_modules/webassembly-floating-point-hex-parser": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz",
"integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/webassembly-interpreter": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz",
"integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.0.0-beta.36",
"long": "^3.2.0",
"webassembly-floating-point-hex-parser": "0.1.2"
},
"bin": {
"wasm": "lib/bin/repl.js",
"wasm2wast": "lib/bin/wasm2wast.js",
"wasmast": "lib/bin/wasmast.js",
"wasmdump": "lib/bin/wasmdump.js",
"wasmrun": "lib/bin/wasmrun.js",
"wastast": "lib/bin/wastast.js"
}
},
"node_modules/webidl-conversions": {
"version": "6.1.0",
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
@ -18420,6 +18549,15 @@
"node": ">=6 <7 || >=8"
}
},
"node_modules/webpack-merge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
"integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
"dev": true,
"dependencies": {
"lodash": "^4.17.15"
}
},
"node_modules/webpack-sources": {
"version": "1.4.3",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
@ -19938,6 +20076,29 @@
"minimist": "^1.2.0"
}
},
"@craco/craco": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@craco/craco/-/craco-6.3.0.tgz",
"integrity": "sha512-SCnfEQxT/6NAbU/3sIWw7gQXtzjjiTp/EZFdJTd8inPURILIy0YajrC2p8qBG2KhFo5cwgOrEDyaGyAFvvuyuA==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.0",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@csstools/convert-colors": {
"version": "1.4.0",
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
@ -27780,6 +27941,12 @@
"version": "1.7.1",
"integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw=="
},
"long": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
"integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
@ -32583,6 +32750,50 @@
"makeerror": "1.0.x"
}
},
"wasm-dce": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz",
"integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==",
"dev": true,
"requires": {
"@babel/core": "^7.0.0-beta.39",
"@babel/traverse": "^7.0.0-beta.39",
"@babel/types": "^7.0.0-beta.39",
"babylon": "^7.0.0-beta.39",
"webassembly-interpreter": "0.0.30"
},
"dependencies": {
"babylon": {
"version": "7.0.0-beta.47",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz",
"integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==",
"dev": true
}
}
},
"wasm-loader": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/wasm-loader/-/wasm-loader-1.3.0.tgz",
"integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0",
"wasm-dce": "^1.0.0"
},
"dependencies": {
"loader-utils": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
}
}
}
},
"watchpack": {
"version": "1.7.5",
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
@ -32688,6 +32899,23 @@
"version": "1.1.2",
"integrity": "sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig=="
},
"webassembly-floating-point-hex-parser": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz",
"integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==",
"dev": true
},
"webassembly-interpreter": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz",
"integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0-beta.36",
"long": "^3.2.0",
"webassembly-floating-point-hex-parser": "0.1.2"
}
},
"webidl-conversions": {
"version": "6.1.0",
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
@ -33093,6 +33321,15 @@
}
}
},
"webpack-merge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
"integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
"dev": true,
"requires": {
"lodash": "^4.17.15"
}
},
"webpack-sources": {
"version": "1.4.3",
"integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",

View File

@ -29,9 +29,10 @@
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"preinstall": "npm ci --prefix ../sdk/js && npm run build --prefix ../sdk/js",
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
"eslintConfig": {
@ -51,5 +52,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@craco/craco": "^6.3.0",
"wasm-loader": "^1.3.0"
}
}

View File

@ -1 +1,11 @@
export const value = "";
import { clusterApiUrl } from "@solana/web3.js";
export const MIGRATION_PROGRAM_ADDRESS =
process.env.REACT_APP_CLUSTER === "testnet"
? ""
: "Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK";
export const SOLANA_URL =
process.env.REACT_APP_CLUSTER === "testnet"
? clusterApiUrl("testnet")
: "http://localhost:8899";

577
lp_ui/src/utils/metaplex.ts Normal file
View File

@ -0,0 +1,577 @@
import { PublicKey, AccountInfo } from "@solana/web3.js";
import BN from "bn.js";
import { deserializeUnchecked } from "borsh";
import { BinaryReader, BinaryWriter } from "borsh";
const base58: any = require("bs58");
// eslint-disable-next-line
export const METADATA_REPLACE = new RegExp("\u0000", "g");
export const EDITION_MARKER_BIT_SIZE = 248;
export const METADATA_PREFIX = "metadata";
export const EDITION = "edition";
export class LazyAccountInfoProxy<T> {
executable: boolean = false;
owner: StringPublicKey = "";
lamports: number = 0;
get data() {
return undefined as unknown as T;
}
}
export interface LazyAccountInfo {
executable: boolean;
owner: StringPublicKey;
lamports: number;
data: [string, string];
}
const PubKeysInternedMap = new Map<string, PublicKey>();
export const toPublicKey = (key: string | PublicKey) => {
if (typeof key !== "string") {
return key;
}
let result = PubKeysInternedMap.get(key);
if (!result) {
result = new PublicKey(key);
PubKeysInternedMap.set(key, result);
}
return result;
};
export interface PublicKeyStringAndAccount<T> {
pubkey: string;
account: AccountInfo<T>;
}
export const WRAPPED_SOL_MINT = new PublicKey(
"So11111111111111111111111111111111111111112"
);
export const TOKEN_PROGRAM_ID = new PublicKey(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
);
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
);
export const BPF_UPGRADE_LOADER_ID = new PublicKey(
"BPFLoaderUpgradeab1e11111111111111111111111"
);
export const MEMO_ID = new PublicKey(
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
);
export const METADATA_PROGRAM_ID =
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" as StringPublicKey;
export const VAULT_ID =
"vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn" as StringPublicKey;
export const AUCTION_ID =
"auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8" as StringPublicKey;
export const METAPLEX_ID =
"p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98" as StringPublicKey;
export const SYSTEM = new PublicKey("11111111111111111111111111111111");
export const getStoreID = async (storeOwnerAddress?: string) => {
if (!storeOwnerAddress) {
return undefined;
}
const programs = await findProgramAddress(
[
Buffer.from("metaplex"),
toPublicKey(METAPLEX_ID).toBuffer(),
toPublicKey(storeOwnerAddress).toBuffer(),
],
toPublicKey(METAPLEX_ID)
);
const storeAddress = programs[0];
return storeAddress;
};
export const setProgramIds = async (store?: string) => {
STORE = store ? toPublicKey(store) : undefined;
};
let STORE: PublicKey | undefined;
export const programIds = () => {
return {
token: TOKEN_PROGRAM_ID,
associatedToken: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
bpf_upgrade_loader: BPF_UPGRADE_LOADER_ID,
system: SYSTEM,
metadata: METADATA_PROGRAM_ID,
memo: MEMO_ID,
vault: VAULT_ID,
auction: AUCTION_ID,
metaplex: METAPLEX_ID,
store: STORE,
};
};
export const findProgramAddress = async (
seeds: (Buffer | Uint8Array)[],
programId: PublicKey
) => {
const key =
"pda-" +
seeds.reduce((agg, item) => agg + item.toString("hex"), "") +
programId.toString();
let cached = localStorage.getItem(key);
if (cached) {
const value = JSON.parse(cached);
return [value.key, parseInt(value.nonce)] as [string, number];
}
const result = await PublicKey.findProgramAddress(seeds, programId);
try {
localStorage.setItem(
key,
JSON.stringify({
key: result[0].toBase58(),
nonce: result[1],
})
);
} catch {
// ignore
}
return [result[0].toBase58(), result[1]] as [string, number];
};
export type StringPublicKey = string;
export enum MetadataKey {
Uninitialized = 0,
MetadataV1 = 4,
EditionV1 = 1,
MasterEditionV1 = 2,
MasterEditionV2 = 6,
EditionMarker = 7,
}
export async function getEdition(
tokenMint: StringPublicKey
): Promise<StringPublicKey> {
const PROGRAM_IDS = programIds();
return (
await findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
toPublicKey(PROGRAM_IDS.metadata).toBuffer(),
toPublicKey(tokenMint).toBuffer(),
Buffer.from(EDITION),
],
toPublicKey(PROGRAM_IDS.metadata)
)
)[0];
}
class CreateMetadataArgs {
instruction: number = 0;
data: Data;
isMutable: boolean;
constructor(args: { data: Data; isMutable: boolean }) {
this.data = args.data;
this.isMutable = args.isMutable;
}
}
class UpdateMetadataArgs {
instruction: number = 1;
data: Data | null;
// Not used by this app, just required for instruction
updateAuthority: StringPublicKey | null;
primarySaleHappened: boolean | null;
constructor(args: {
data?: Data;
updateAuthority?: string;
primarySaleHappened: boolean | null;
}) {
this.data = args.data ? args.data : null;
this.updateAuthority = args.updateAuthority ? args.updateAuthority : null;
this.primarySaleHappened = args.primarySaleHappened;
}
}
export class Creator {
address: StringPublicKey;
verified: boolean;
share: number;
constructor(args: {
address: StringPublicKey;
verified: boolean;
share: number;
}) {
this.address = args.address;
this.verified = args.verified;
this.share = args.share;
}
}
export class Data {
name: string;
symbol: string;
uri: string;
sellerFeeBasisPoints: number;
creators: Creator[] | null;
constructor(args: {
name: string;
symbol: string;
uri: string;
sellerFeeBasisPoints: number;
creators: Creator[] | null;
}) {
this.name = args.name;
this.symbol = args.symbol;
this.uri = args.uri;
this.sellerFeeBasisPoints = args.sellerFeeBasisPoints;
this.creators = args.creators;
}
}
export class Metadata {
key: MetadataKey;
updateAuthority: StringPublicKey;
mint: StringPublicKey;
data: Data;
primarySaleHappened: boolean;
isMutable: boolean;
editionNonce: number | null;
// set lazy
masterEdition?: StringPublicKey;
edition?: StringPublicKey;
constructor(args: {
updateAuthority: StringPublicKey;
mint: StringPublicKey;
data: Data;
primarySaleHappened: boolean;
isMutable: boolean;
editionNonce: number | null;
}) {
this.key = MetadataKey.MetadataV1;
this.updateAuthority = args.updateAuthority;
this.mint = args.mint;
this.data = args.data;
this.primarySaleHappened = args.primarySaleHappened;
this.isMutable = args.isMutable;
this.editionNonce = args.editionNonce;
}
public async init() {
const edition = await getEdition(this.mint);
this.edition = edition;
this.masterEdition = edition;
}
}
export class Edition {
key: MetadataKey;
/// Points at MasterEdition struct
parent: StringPublicKey;
/// Starting at 0 for master record, this is incremented for each edition minted.
edition: BN;
constructor(args: {
key: MetadataKey;
parent: StringPublicKey;
edition: BN;
}) {
this.key = MetadataKey.EditionV1;
this.parent = args.parent;
this.edition = args.edition;
}
}
export class MasterEditionV1 {
key: MetadataKey;
supply: BN;
maxSupply?: BN;
/// Can be used to mint tokens that give one-time permission to mint a single limited edition.
printingMint: StringPublicKey;
/// If you don't know how many printing tokens you are going to need, but you do know
/// you are going to need some amount in the future, you can use a token from this mint.
/// Coming back to token metadata with one of these tokens allows you to mint (one time)
/// any number of printing tokens you want. This is used for instance by Auction Manager
/// with participation NFTs, where we dont know how many people will bid and need participation
/// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over,
/// because when the auction begins we just dont know how many printing tokens we will need,
/// but at the end we will. At the end it then burns this token with token-metadata to
/// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token
/// to get their limited editions.
oneTimePrintingAuthorizationMint: StringPublicKey;
constructor(args: {
key: MetadataKey;
supply: BN;
maxSupply?: BN;
printingMint: StringPublicKey;
oneTimePrintingAuthorizationMint: StringPublicKey;
}) {
this.key = MetadataKey.MasterEditionV1;
this.supply = args.supply;
this.maxSupply = args.maxSupply;
this.printingMint = args.printingMint;
this.oneTimePrintingAuthorizationMint =
args.oneTimePrintingAuthorizationMint;
}
}
export class MasterEditionV2 {
key: MetadataKey;
supply: BN;
maxSupply?: BN;
constructor(args: { key: MetadataKey; supply: BN; maxSupply?: BN }) {
this.key = MetadataKey.MasterEditionV2;
this.supply = args.supply;
this.maxSupply = args.maxSupply;
}
}
class CreateMasterEditionArgs {
instruction: number = 10;
maxSupply: BN | null;
constructor(args: { maxSupply: BN | null }) {
this.maxSupply = args.maxSupply;
}
}
class MintPrintingTokensArgs {
instruction: number = 9;
supply: BN;
constructor(args: { supply: BN }) {
this.supply = args.supply;
}
}
export class EditionMarker {
key: MetadataKey;
ledger: number[];
constructor(args: { key: MetadataKey; ledger: number[] }) {
this.key = MetadataKey.EditionMarker;
this.ledger = args.ledger;
}
editionTaken(edition: number) {
const editionOffset = edition % EDITION_MARKER_BIT_SIZE;
const indexOffset = Math.floor(editionOffset / 8);
if (indexOffset > 30) {
throw Error("bad index for edition");
}
const positionInBitsetFromRight = 7 - (editionOffset % 8);
const mask = Math.pow(2, positionInBitsetFromRight);
const appliedMask = this.ledger[indexOffset] & mask;
// eslint-disable-next-line
return appliedMask != 0;
}
}
export const METADATA_SCHEMA = new Map<any, any>([
[
CreateMetadataArgs,
{
kind: "struct",
fields: [
["instruction", "u8"],
["data", Data],
["isMutable", "u8"], // bool
],
},
],
[
UpdateMetadataArgs,
{
kind: "struct",
fields: [
["instruction", "u8"],
["data", { kind: "option", type: Data }],
["updateAuthority", { kind: "option", type: "pubkeyAsString" }],
["primarySaleHappened", { kind: "option", type: "u8" }],
],
},
],
[
CreateMasterEditionArgs,
{
kind: "struct",
fields: [
["instruction", "u8"],
["maxSupply", { kind: "option", type: "u64" }],
],
},
],
[
MintPrintingTokensArgs,
{
kind: "struct",
fields: [
["instruction", "u8"],
["supply", "u64"],
],
},
],
[
MasterEditionV1,
{
kind: "struct",
fields: [
["key", "u8"],
["supply", "u64"],
["maxSupply", { kind: "option", type: "u64" }],
["printingMint", "pubkeyAsString"],
["oneTimePrintingAuthorizationMint", "pubkeyAsString"],
],
},
],
[
MasterEditionV2,
{
kind: "struct",
fields: [
["key", "u8"],
["supply", "u64"],
["maxSupply", { kind: "option", type: "u64" }],
],
},
],
[
Edition,
{
kind: "struct",
fields: [
["key", "u8"],
["parent", "pubkeyAsString"],
["edition", "u64"],
],
},
],
[
Data,
{
kind: "struct",
fields: [
["name", "string"],
["symbol", "string"],
["uri", "string"],
["sellerFeeBasisPoints", "u16"],
["creators", { kind: "option", type: [Creator] }],
],
},
],
[
Creator,
{
kind: "struct",
fields: [
["address", "pubkeyAsString"],
["verified", "u8"],
["share", "u8"],
],
},
],
[
Metadata,
{
kind: "struct",
fields: [
["key", "u8"],
["updateAuthority", "pubkeyAsString"],
["mint", "pubkeyAsString"],
["data", Data],
["primarySaleHappened", "u8"], // bool
["isMutable", "u8"], // bool
],
},
],
[
EditionMarker,
{
kind: "struct",
fields: [
["key", "u8"],
["ledger", [31]],
],
},
],
]);
export const extendBorsh = () => {
(BinaryReader.prototype as any).readPubkey = function () {
const reader = this as unknown as BinaryReader;
const array = reader.readFixedArray(32);
return new PublicKey(array);
};
(BinaryWriter.prototype as any).writePubkey = function (value: PublicKey) {
const writer = this as unknown as BinaryWriter;
writer.writeFixedArray(value.toBuffer());
};
(BinaryReader.prototype as any).readPubkeyAsString = function () {
const reader = this as unknown as BinaryReader;
const array = reader.readFixedArray(32);
return base58.encode(array) as StringPublicKey;
};
(BinaryWriter.prototype as any).writePubkeyAsString = function (
value: StringPublicKey
) {
const writer = this as unknown as BinaryWriter;
writer.writeFixedArray(base58.decode(value));
};
};
extendBorsh();
export const decodeMetadata = (buffer: Buffer): Metadata => {
const metadata = deserializeUnchecked(
METADATA_SCHEMA,
Metadata,
buffer
) as Metadata;
metadata.data.name = metadata.data.name.replace(METADATA_REPLACE, "");
metadata.data.uri = metadata.data.uri.replace(METADATA_REPLACE, "");
metadata.data.symbol = metadata.data.symbol.replace(METADATA_REPLACE, "");
return metadata;
};
export const getMetadataAddress = async (
mintKey: string
): Promise<[PublicKey, number]> => {
const seeds = [
Buffer.from("metadata"),
new PublicKey(METADATA_PROGRAM_ID).toBuffer(),
new PublicKey(mintKey).toBuffer(),
];
return PublicKey.findProgramAddress(
seeds,
new PublicKey(METADATA_PROGRAM_ID)
);
};

79
lp_ui/src/utils/solana.ts Normal file
View File

@ -0,0 +1,79 @@
import { MintLayout } from "@solana/spl-token";
import { WalletContextState } from "@solana/wallet-adapter-react";
import {
AccountInfo,
Connection,
PublicKey,
Transaction,
TransactionInstruction,
} from "@solana/web3.js";
export async function sendSignAndConfirmInstruction(
wallet: WalletContextState,
connection: Connection,
instruction: TransactionInstruction
) {
console.log("instruction being sent", instruction);
const transaction = new Transaction().add(instruction);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = wallet.publicKey || undefined;
console.log("transaction", transaction);
return signSendAndConfirm(wallet, connection, transaction);
}
export async function signSendAndConfirm(
wallet: WalletContextState,
connection: Connection,
transaction: Transaction
) {
const signed = await wallet.signTransaction(transaction);
const txid = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(txid);
return txid;
}
export function extractMintAuthorityInfo(
account: AccountInfo<Buffer>
): string | null {
const data = Buffer.from(account.data);
const mintInfo = MintLayout.decode(data);
const uintArray = mintInfo?.mintAuthority;
const pubkey = new PublicKey(uintArray);
const output = pubkey?.toString();
return output || null;
}
export async function getMultipleAccountsRPC(
connection: Connection,
pubkeys: PublicKey[]
): Promise<(AccountInfo<Buffer> | null)[]> {
return getMultipleAccounts(connection, pubkeys, "finalized");
}
export const getMultipleAccounts = async (
connection: any,
pubkeys: PublicKey[],
commitment: string
) => {
return (
await Promise.all(
chunks(pubkeys, 99).map((chunk) =>
connection.getMultipleAccountsInfo(chunk, commitment)
)
)
).flat();
};
export function chunks<T>(array: T[], size: number): T[][] {
return Array.apply<number, T[], T[][]>(
0,
new Array(Math.ceil(array.length / size))
).map((_, index) => array.slice(index * size, (index + 1) * size));
}
export function shortenAddress(address: string) {
return `${address.slice(0, 4)}...${address.slice(-4)}`;
}

View File

@ -3,47 +3,233 @@ import {
makeStyles,
Typography,
Paper,
Tab,
TextField,
Button,
} from "@material-ui/core";
import { useCallback, useState } from "react";
//import { pool_address } from "@certusone/wormhole-sdk/lib/solana/migration/wormhole_migration";
import { useCallback, useEffect, useState } from "react";
import LogWatcher from "../components/LogWatcher";
import SolanaWalletKey from "../components/SolanaWalletKey";
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import TabContext from "@material-ui/lab/TabContext";
import TabList from "@material-ui/lab/TabList";
import TabPanel from "@material-ui/lab/TabPanel";
import { MIGRATION_PROGRAM_ADDRESS, SOLANA_URL } from "../utils/consts";
import { PublicKey, Connection } from "@solana/web3.js";
import { useLogger } from "../contexts/Logger";
import { getMultipleAccounts, signSendAndConfirm } from "../utils/solana";
import getAuthorityAddress from "@certusone/wormhole-sdk/lib/migration/authorityAddress";
import createPoolAccount from "@certusone/wormhole-sdk/lib/migration/createPool";
import getPoolAddress from "@certusone/wormhole-sdk/lib/migration/poolAddress";
import getFromCustodyAddress from "@certusone/wormhole-sdk/lib/migration/fromCustodyAddress";
import getToCustodyAddress from "@certusone/wormhole-sdk/lib/migration/toCustodyAddress";
import getShareMintAddress from "@certusone/wormhole-sdk/lib/migration/shareMintAddress";
const useStyles = makeStyles(() => ({}));
const useStyles = makeStyles(() => ({
rootContainer: {},
mainPaper: {
"& > *": {
margin: "1rem",
},
padding: "2rem",
},
}));
function Main() {
const classes = useStyles();
const wallet = useSolanaWallet();
const [selectedTab, setSelectedTab] = useState("createPool");
const handleChange = useCallback(
(event, value) => {
setSelectedTab(value);
},
[setSelectedTab]
const logger = useLogger();
const connection = new Connection(SOLANA_URL, "finalized");
const [fromMint, setFromMint] = useState("");
const [toMint, setToMint] = useState("");
const [poolAddress, setPoolAddress] = useState("");
const [poolExists, setPoolExists] = useState<boolean | null>(null);
const [poolAccountInfo, setPoolAccountInfo] = useState(null);
const [shareTokenMint, setShareTokenMint] = useState(null);
const [toTokenAccount, setToTokenAccount] = useState(null);
const [fromTokenAccount, setFromTokenAccount] = useState(null);
const [shareTokenAccount, setShareTokenAccount] = useState(null);
//these are all the other derived information
const [authorityAddress, setAuthorityAddress] = useState(null);
const [fromCustodyAddress, setFromCustodyAddress] = useState(null);
const [toCustodyAddress, setToCustodyAddress] = useState(null);
const [shareMintAddress, setShareMintAddress] = useState(null);
/*
Effects***
These are generally data fetchers which fire when requisite data populates.
*/
//Retrieve pool address on selectedTokens change
useEffect(() => {
if (toMint && fromMint) {
setPoolAddress("");
getPoolAddress(MIGRATION_PROGRAM_ADDRESS, fromMint, toMint).then(
(result) => {
const key = new PublicKey(result).toString();
logger.log("Calculated the pool address at: " + key);
setPoolAddress(key);
},
(error) => logger.log("ERROR, could not calculate pool address.")
);
}
}, [toMint, fromMint, setPoolAddress]);
//Retrieve the poolAccount every time the pool address changes.
useEffect(() => {
if (poolAddress) {
setPoolAccountInfo(null);
setPoolExists(null);
try {
getMultipleAccounts(
connection,
[new PublicKey(poolAddress)],
"finalized"
).then((result) => {
if (result.length && result[0] !== null) {
setPoolAccountInfo(result[0]);
setPoolExists(true);
logger.log("Successfully found account info for the pool.");
} else if (result.length && result[0] === null) {
logger.log("Confirmed that the pool does not exist.");
setPoolExists(false);
} else {
logger.log(
"unexpected error in fetching pool address. Please reload and try again"
);
}
});
} catch (e) {
logger.log("Could not fetch pool address");
}
}
}, [poolAddress]);
useEffect(() => {
getAuthorityAddress(MIGRATION_PROGRAM_ADDRESS).then((result: any) =>
setAuthorityAddress(result)
);
getToCustodyAddress(MIGRATION_PROGRAM_ADDRESS, poolAddress).then(
(result: any) => setToCustodyAddress(result)
);
getFromCustodyAddress(MIGRATION_PROGRAM_ADDRESS, poolAddress).then(
(result: any) => setFromCustodyAddress(result)
);
getShareMintAddress(MIGRATION_PROGRAM_ADDRESS, poolAddress).then(
(result: any) => setShareMintAddress(result)
);
}, [poolAddress]);
/*
End Effects!
*/
/*
Actions:
These are generally onClick actions which the user can perform. They read things off the state, do something,
and then potentially update something on the state.
*/
const createPool = async () => {
try {
const instruction = await createPoolAccount(
connection,
wallet?.publicKey?.toString() || "",
MIGRATION_PROGRAM_ADDRESS,
wallet?.publicKey?.toString() || "",
fromMint,
toMint
);
signSendAndConfirm(wallet, connection, instruction).then(
(transaction: any) => {
setPoolExists(null); //Set these to null to force a fetch on them
setPoolAccountInfo(null);
logger.log("Successfully created the pool.");
},
(error) => {
logger.log("Could not create the pool");
console.error(error);
}
);
} catch (e) {
logger.log("Failed to create the pool.");
console.error(e);
}
};
/*
End actions!
*/
const toAndFromSelector = (
<>
<Typography>
Please enter the mint addresses for the 'To' and 'From' tokens you're
interested in.
</Typography>
<TextField
value={fromMint}
onChange={(event) => setFromMint(event.target.value)}
label={"From Token"}
fullWidth
style={{ display: "block" }}
></TextField>
<TextField
value={toMint}
onChange={(event) => setToMint(event.target.value)}
label={"To Token"}
fullWidth
style={{ display: "block" }}
></TextField>
</>
);
const createPoolButton = (
<div>
<Button
variant="contained"
onClick={() => createPool()}
disabled={poolExists || !poolAddress}
>
Click here to instantiate the pool for these tokens.
</Button>
</div>
);
const addLiquidity = (
<>
<Typography>
Add 'to' tokens to this pool, and receive liquidity tokens.
</Typography>
<TextField
value={toMint}
onChange={(event) => setToMint(event.target.value)}
label={"To Token"}
></TextField>
</>
);
const mainContent = (
<>
{toAndFromSelector}
{createPoolButton}
</>
);
const content = !wallet.publicKey ? (
<Typography>Please connect your wallet.</Typography>
) : (
<TabContext value={selectedTab}>
<TabList onChange={handleChange} aria-label="simple tabs example">
<Tab label="Create Pool" value="createPool" />
<Tab label="Add Liquidity" value="Add Liquidity" />
<Tab label="Redeem Liquidity" value="Redeem Liquidity" />
</TabList>
<TabPanel value="1">Item One</TabPanel>
<TabPanel value="2">Item Two</TabPanel>
<TabPanel value="3">Item Three</TabPanel>
</TabContext>
mainContent
);
return (
<Container maxWidth="md">
<Paper style={{ padding: "3rem" }}>
<Container maxWidth="md" className={classes.rootContainer}>
<Paper className={classes.mainPaper}>
<SolanaWalletKey />
{content}
</Paper>

View File

@ -23,3 +23,11 @@ fs.copyFileSync(
"src/solana/token/token_bridge_bg.wasm.d.ts",
"lib/solana/token/token_bridge_bg.wasm.d.ts"
);
fs.copyFileSync(
"src/solana/migration/wormhole_migration_bg.wasm",
"lib/solana/migration/wormhole_migration_bg.wasm"
);
fs.copyFileSync(
"src/solana/migration/wormhole_migration_bg.wasm.d.ts",
"lib/solana/migration/wormhole_migration_bg.wasm.d.ts"
);

View File

@ -0,0 +1,32 @@
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ixFromRust } from "../solana";
export default async function addLiquidity(
connection: Connection,
payerAddress: string,
program_id: string,
from_mint: string,
to_mint: string,
liquidity_token_account: string,
lp_share_token_account: string,
amount: BigInt
) {
const { add_liquidity } = await import(
"../solana/migration/wormhole_migration"
);
const ix = ixFromRust(
add_liquidity(
program_id,
from_mint,
to_mint,
liquidity_token_account,
lp_share_token_account,
amount
)
);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(payerAddress);
return transaction;
}

View File

@ -0,0 +1,6 @@
export default async function authorityAddress(program_id: string) {
const { authority_address } = await import(
"../solana/migration/wormhole_migration"
);
return authority_address(program_id);
}

View File

@ -0,0 +1,32 @@
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ixFromRust } from "../solana";
export default async function claimShares(
connection: Connection,
payerAddress: string,
program_id: string,
from_mint: string,
to_mint: string,
output_token_account: string,
lp_share_token_account: string,
amount: BigInt
) {
const { claim_shares } = await import(
"../solana/migration/wormhole_migration"
);
const ix = ixFromRust(
claim_shares(
program_id,
from_mint,
to_mint,
output_token_account,
lp_share_token_account,
amount
)
);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(payerAddress);
return transaction;
}

View File

@ -0,0 +1,21 @@
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ixFromRust } from "../solana";
export default async function createPool(
connection: Connection,
payerAddress: string,
program_id: string,
payer: string,
from_mint: string,
to_mint: string
) {
const { create_pool } = await import(
"../solana/migration/wormhole_migration"
);
const ix = ixFromRust(create_pool(program_id, payer, from_mint, to_mint));
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(payerAddress);
return transaction;
}

View File

@ -0,0 +1,9 @@
export default async function fromCustodyAddress(
program_id: string,
pool: string
) {
const { from_custody_address } = await import(
"../solana/migration/wormhole_migration"
);
return from_custody_address(program_id, pool);
}

View File

@ -0,0 +1,32 @@
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ixFromRust } from "../solana";
export default async function migrateTokens(
connection: Connection,
payerAddress: string,
program_id: string,
from_mint: string,
to_mint: string,
input_token_account: string,
output_token_account: string,
amount: BigInt
) {
const { migrate_tokens } = await import(
"../solana/migration/wormhole_migration"
);
const ix = ixFromRust(
migrate_tokens(
program_id,
from_mint,
to_mint,
input_token_account,
output_token_account,
amount
)
);
const transaction = new Transaction().add(ix);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(payerAddress);
return transaction;
}

View File

@ -0,0 +1,4 @@
export default async function parsePool(data: Uint8Array) {
const { parse_pool } = await import("../solana/migration/wormhole_migration");
return parse_pool(data);
}

View File

@ -0,0 +1,10 @@
export default async function poolAddress(
program_id: string,
from_mint: string,
to_mint: string
) {
const { pool_address } = await import(
"../solana/migration/wormhole_migration"
);
return pool_address(program_id, from_mint, to_mint);
}

View File

@ -0,0 +1,9 @@
export default async function shareMintAddress(
program_id: string,
pool: string
) {
const { share_mint_address } = await import(
"../solana/migration/wormhole_migration"
);
return share_mint_address(program_id, pool);
}

View File

@ -0,0 +1,9 @@
export default async function toCustodyAddress(
program_id: string,
pool: string
) {
const { to_custody_address } = await import(
"../solana/migration/wormhole_migration"
);
return to_custody_address(program_id, pool);
}