diff --git a/lp_ui/craco.config.js b/lp_ui/craco.config.js new file mode 100644 index 00000000..bd80f8af --- /dev/null +++ b/lp_ui/craco.config.js @@ -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; + }, + }, +}; diff --git a/lp_ui/package-lock.json b/lp_ui/package-lock.json index 1394993b..e39858b0 100644 --- a/lp_ui/package-lock.json +++ b/lp_ui/package-lock.json @@ -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==", diff --git a/lp_ui/package.json b/lp_ui/package.json index 982a0c3a..3f633809 100644 --- a/lp_ui/package.json +++ b/lp_ui/package.json @@ -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" } } diff --git a/lp_ui/src/utils/consts.ts b/lp_ui/src/utils/consts.ts index 98117910..e4d683f6 100644 --- a/lp_ui/src/utils/consts.ts +++ b/lp_ui/src/utils/consts.ts @@ -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"; diff --git a/lp_ui/src/utils/metaplex.ts b/lp_ui/src/utils/metaplex.ts new file mode 100644 index 00000000..ed82eb9c --- /dev/null +++ b/lp_ui/src/utils/metaplex.ts @@ -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 { + 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(); + +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 { + pubkey: string; + account: AccountInfo; +} + +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 { + 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([ + [ + 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) + ); +}; diff --git a/lp_ui/src/utils/solana.ts b/lp_ui/src/utils/solana.ts new file mode 100644 index 00000000..9f152313 --- /dev/null +++ b/lp_ui/src/utils/solana.ts @@ -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 +): 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 | 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(array: T[], size: number): T[][] { + return Array.apply( + 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)}`; +} diff --git a/lp_ui/src/views/Main.tsx b/lp_ui/src/views/Main.tsx index f8362ce3..6a132e60 100644 --- a/lp_ui/src/views/Main.tsx +++ b/lp_ui/src/views/Main.tsx @@ -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(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 = ( + <> + + Please enter the mint addresses for the 'To' and 'From' tokens you're + interested in. + + setFromMint(event.target.value)} + label={"From Token"} + fullWidth + style={{ display: "block" }} + > + setToMint(event.target.value)} + label={"To Token"} + fullWidth + style={{ display: "block" }} + > + + ); + + const createPoolButton = ( +
+ +
+ ); + + const addLiquidity = ( + <> + + Add 'to' tokens to this pool, and receive liquidity tokens. + + setToMint(event.target.value)} + label={"To Token"} + > + + ); + + const mainContent = ( + <> + {toAndFromSelector} + {createPoolButton} + ); const content = !wallet.publicKey ? ( Please connect your wallet. ) : ( - - - - - - - Item One - Item Two - Item Three - + mainContent ); return ( - - + + {content} diff --git a/sdk/js/scripts/copyWasm.js b/sdk/js/scripts/copyWasm.js index f16960e3..ed74111a 100644 --- a/sdk/js/scripts/copyWasm.js +++ b/sdk/js/scripts/copyWasm.js @@ -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" +); diff --git a/sdk/js/src/migration/addLiquidity.ts b/sdk/js/src/migration/addLiquidity.ts new file mode 100644 index 00000000..ce0b73af --- /dev/null +++ b/sdk/js/src/migration/addLiquidity.ts @@ -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; +} diff --git a/sdk/js/src/migration/authorityAddress.ts b/sdk/js/src/migration/authorityAddress.ts new file mode 100644 index 00000000..06eaa85f --- /dev/null +++ b/sdk/js/src/migration/authorityAddress.ts @@ -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); +} diff --git a/sdk/js/src/migration/claimShares.ts b/sdk/js/src/migration/claimShares.ts new file mode 100644 index 00000000..625da096 --- /dev/null +++ b/sdk/js/src/migration/claimShares.ts @@ -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; +} diff --git a/sdk/js/src/migration/createPool.ts b/sdk/js/src/migration/createPool.ts new file mode 100644 index 00000000..c533b814 --- /dev/null +++ b/sdk/js/src/migration/createPool.ts @@ -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; +} diff --git a/sdk/js/src/migration/fromCustodyAddress.ts b/sdk/js/src/migration/fromCustodyAddress.ts new file mode 100644 index 00000000..1d605bdf --- /dev/null +++ b/sdk/js/src/migration/fromCustodyAddress.ts @@ -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); +} diff --git a/sdk/js/src/migration/migrateTokens.ts b/sdk/js/src/migration/migrateTokens.ts new file mode 100644 index 00000000..9e7901a6 --- /dev/null +++ b/sdk/js/src/migration/migrateTokens.ts @@ -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; +} diff --git a/sdk/js/src/migration/parsePool.ts b/sdk/js/src/migration/parsePool.ts new file mode 100644 index 00000000..1def9a74 --- /dev/null +++ b/sdk/js/src/migration/parsePool.ts @@ -0,0 +1,4 @@ +export default async function parsePool(data: Uint8Array) { + const { parse_pool } = await import("../solana/migration/wormhole_migration"); + return parse_pool(data); +} diff --git a/sdk/js/src/migration/poolAddress.ts b/sdk/js/src/migration/poolAddress.ts new file mode 100644 index 00000000..2ec2e4c0 --- /dev/null +++ b/sdk/js/src/migration/poolAddress.ts @@ -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); +} diff --git a/sdk/js/src/migration/shareMintAddress.ts b/sdk/js/src/migration/shareMintAddress.ts new file mode 100644 index 00000000..f47f3855 --- /dev/null +++ b/sdk/js/src/migration/shareMintAddress.ts @@ -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); +} diff --git a/sdk/js/src/migration/toCustodyAddress.ts b/sdk/js/src/migration/toCustodyAddress.ts new file mode 100644 index 00000000..30d7b641 --- /dev/null +++ b/sdk/js/src/migration/toCustodyAddress.ts @@ -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); +}