Ver código fonte

sdk for migration & progress on lp_ui

Change-Id: I4ac855f1795ecffe50c68c428e530b215ba4b8e9
Chase Moran 4 anos atrás
pai
commit
8a90b50aeb

+ 28 - 0
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;
+    },
+  },
+};

+ 237 - 0
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==",

+ 8 - 3
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"
   }
 }

+ 11 - 1
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";

+ 577 - 0
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<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 - 0
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<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)}`;
+}

+ 207 - 21
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<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>

+ 8 - 0
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"
+);

+ 32 - 0
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;
+}

+ 6 - 0
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);
+}

+ 32 - 0
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;
+}

+ 21 - 0
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;
+}

+ 9 - 0
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);
+}

+ 32 - 0
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;
+}

+ 4 - 0
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);
+}

+ 10 - 0
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);
+}

+ 9 - 0
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);
+}

+ 9 - 0
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);
+}