diff --git a/core/base/src/utils/misc.ts b/core/base/src/utils/misc.ts index bb8d17f03..e3fb10023 100644 --- a/core/base/src/utils/misc.ts +++ b/core/base/src/utils/misc.ts @@ -4,7 +4,7 @@ export function lazyInstantiate(factory: () => T): () => T { if (!instance) instance = factory(); return instance; - } + }; } export function onlyOnce(fn: (...args: T) => any, ...args: T): () => void { @@ -14,7 +14,7 @@ export function onlyOnce(fn: (...args: T) => any, ...args: T): () called = true; fn(...args); } - } + }; } export function throws(fn: () => any): boolean { @@ -25,3 +25,17 @@ export function throws(fn: () => any): boolean { return true; } } + +/** + * Maps an object to another by applying the given function to every "leaf" property + * (_i.e._ neither object nor array). + */ +export function visitor(input: any, f: (property: any) => any): any { + if (Array.isArray(input)) { + return input.map((v) => visitor(v, f)); + } else if (input && typeof input === "object") { + return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, visitor(v, f)])); + } else { + return f(input); + } +} diff --git a/core/base/src/utils/string.ts b/core/base/src/utils/string.ts new file mode 100644 index 000000000..56645c888 --- /dev/null +++ b/core/base/src/utils/string.ts @@ -0,0 +1,15 @@ +// Camel case a string (from https://stackoverflow.com/a/2970667) +export function camelCase(str: string): string { + return ( + str + .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) { + return index === 0 ? word.toLowerCase() : word.toUpperCase(); + }) + // Replace any spaces, hyphens, or underscores with an empty string + .replace(/[\s\-_]+/g, "") + ); +} + +export function upperFirst(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/core/definitions/src/testing/mocks/guardian.ts b/core/definitions/src/testing/mocks/guardian.ts index 9d8e93bb3..8ffb6944f 100644 --- a/core/definitions/src/testing/mocks/guardian.ts +++ b/core/definitions/src/testing/mocks/guardian.ts @@ -1,15 +1,10 @@ -import type { Chain} from "@wormhole-foundation/sdk-base"; -import { guardians } from "@wormhole-foundation/sdk-base"; +import type { Chain } from "@wormhole-foundation/sdk-base"; +import { guardians, encoding } from "@wormhole-foundation/sdk-base"; import type { PayloadLiteral, VAA } from "../../index.js"; -import { - Signature, - SignatureUtils, - createVAA, - deserialize, - serialize, -} from "../../index.js"; +import { Signature, SignatureUtils, createVAA, deserialize, serialize } from "../../index.js"; import type { UniversalAddress } from "../../universalAddress.js"; import { keccak256 } from "../../utils.js"; +import { computeAddress } from "ethers"; interface Guardian { index: number; @@ -31,6 +26,10 @@ export class MockGuardians { return this.signers.map((guardian) => SignatureUtils.toPubkey(guardian.key)); } + getAddresses() { + return this.getPublicKeys().map((key) => computeAddress(encoding.hex.encode(key))); + } + addSignatures

( message: Uint8Array | VAA

, guardianIndices?: number[], diff --git a/package-lock.json b/package-lock.json index aa8df5582..59bc916ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "@types/chai": "^4.3.5", "@types/jest": "^29.5.12", "@types/mocha": "^10.0.1", - "@types/node": "^20.4.1", + "@types/node": "^20.17.10", "@typescript-eslint/eslint-plugin": "^6.21.0", "chai": "^4.3.7", "jest": "^29.7.0", @@ -837,51 +837,13 @@ "protobufjs": "^6.8.8" } }, - "node_modules/@coral-xyz/anchor": { - "version": "0.29.0", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@coral-xyz/borsh": "^0.29.0", - "@noble/hashes": "^1.3.1", - "@solana/web3.js": "^1.68.0", - "bn.js": "^5.1.2", - "bs58": "^4.0.1", - "buffer-layout": "^1.2.2", - "camelcase": "^6.3.0", - "cross-fetch": "^3.1.5", - "crypto-hash": "^1.3.0", - "eventemitter3": "^4.0.7", - "pako": "^2.0.3", - "snake-case": "^3.0.4", - "superstruct": "^0.15.4", - "toml": "^3.0.0" - }, - "engines": { - "node": ">=11" - } - }, - "node_modules/@coral-xyz/anchor/node_modules/camelcase": { - "version": "6.3.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@coral-xyz/borsh": { - "version": "0.29.0", + "node_modules/@coral-xyz/anchor-errors": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz", + "integrity": "sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==", "license": "Apache-2.0", - "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, "engines": { "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.68.0" } }, "node_modules/@cosmjs/amino": { @@ -2554,8 +2516,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.5.9", - "license": "MIT" + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.3", @@ -8674,6 +8641,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "dev": true, @@ -9193,11 +9166,15 @@ "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@coral-xyz/anchor": "0.29.0", - "@coral-xyz/borsh": "0.29.0", + "@coral-xyz/anchor": "0.30.1", + "@coral-xyz/borsh": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8", - "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-base": "1.1.1", + "@wormhole-foundation/sdk-connect": "1.1.1", + "@wormhole-foundation/sdk-definitions": "1.1.1", + "@wormhole-foundation/sdk-solana-core": "1.1.1", + "@wormhole-foundation/sdk-solana-tokenbridge": "1.1.1", "rpc-websockets": "^7.10.0" }, "devDependencies": { @@ -9207,12 +9184,275 @@ "node": ">=16" } }, + "platforms/solana/node_modules/@coral-xyz/anchor": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.30.1.tgz", + "integrity": "sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/anchor-errors": "^0.30.1", + "@coral-xyz/borsh": "^0.30.1", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.68.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "platforms/solana/node_modules/@coral-xyz/borsh": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.30.1.tgz", + "integrity": "sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.68.0" + } + }, + "platforms/solana/node_modules/@noble/curves": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "platforms/solana/node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-base/-/sdk-base-1.1.1.tgz", + "integrity": "sha512-sERspcuzxV22/pq2nQMBNl1nvDbbjIEGtBcyIoX1T2jF+0mPYJ32zTuLNQ6zmbmw27+4mVuDoBkpb2DiOo+S4w==", + "license": "Apache-2.0", + "dependencies": { + "@scure/base": "^1.1.3" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-connect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-connect/-/sdk-connect-1.1.1.tgz", + "integrity": "sha512-bthVa7G+1LqqC43ijzaz5guKutWwdIsFnkJYWeEmi0/cRrLnGlGIMpCuI+DhQXK3zDns/w6gtwH1y3BR3OExig==", + "license": "Apache-2.0", + "dependencies": { + "@wormhole-foundation/sdk-base": "1.1.1", + "@wormhole-foundation/sdk-definitions": "1.1.1", + "axios": "^1.4.0" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-definitions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-definitions/-/sdk-definitions-1.1.1.tgz", + "integrity": "sha512-d+3jdubUe7Exi9hwV8RwUEX+MpcEqA/yuxZo8a1YtZG6nsSvj2GNh6KVGUNdEr/1eCXsot8PjyM2zC1QsE2tIg==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "@wormhole-foundation/sdk-base": "1.1.1" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana-core/-/sdk-solana-core-1.1.1.tgz", + "integrity": "sha512-4pGTMySa0cQ0+S78UZ1hDK/8MSD8oOpi9jsjr1UxM8Pmc0pZ5JJtU5+g46m+Iz1AVvLdMmStsPUOyIocAQEHjA==", + "license": "Apache-2.0", + "dependencies": { + "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/borsh": "0.29.0", + "@solana/web3.js": "^1.95.2", + "@wormhole-foundation/sdk-connect": "1.1.1", + "@wormhole-foundation/sdk-solana": "1.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@coral-xyz/anchor": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz", + "integrity": "sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/borsh": "^0.29.0", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.68.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@coral-xyz/borsh": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz", + "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.68.0" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-core/node_modules/@wormhole-foundation/sdk-solana": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana/-/sdk-solana-1.1.1.tgz", + "integrity": "sha512-OIbCe0BY233Yhlu3H3BcTfNtmPalZmZ6lVoGZMaCAcdjBXB5iO1qE7A9dnBNM0SOwONM40WZk5hPUUoSrM+RdQ==", + "license": "Apache-2.0", + "dependencies": { + "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/borsh": "0.29.0", + "@solana/spl-token": "0.3.9", + "@solana/web3.js": "^1.95.2", + "@wormhole-foundation/sdk-connect": "1.1.1", + "rpc-websockets": "^7.10.0" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-tokenbridge": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana-tokenbridge/-/sdk-solana-tokenbridge-1.1.1.tgz", + "integrity": "sha512-Zd3D9BXMunwd16mCuzjDm0sf8HyHpvZ/QMekM0jvvp3IkCwv8zqIJuq/M3S84IhEfeli9uZO5VggYECIAWayCA==", + "license": "Apache-2.0", + "dependencies": { + "@coral-xyz/anchor": "0.29.0", + "@solana/spl-token": "0.3.9", + "@solana/web3.js": "^1.95.2", + "@wormhole-foundation/sdk-connect": "1.1.1", + "@wormhole-foundation/sdk-solana": "1.1.1", + "@wormhole-foundation/sdk-solana-core": "1.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-tokenbridge/node_modules/@coral-xyz/anchor": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz", + "integrity": "sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/borsh": "^0.29.0", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.68.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-tokenbridge/node_modules/@coral-xyz/borsh": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz", + "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.68.0" + } + }, + "platforms/solana/node_modules/@wormhole-foundation/sdk-solana-tokenbridge/node_modules/@wormhole-foundation/sdk-solana": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-solana/-/sdk-solana-1.1.1.tgz", + "integrity": "sha512-OIbCe0BY233Yhlu3H3BcTfNtmPalZmZ6lVoGZMaCAcdjBXB5iO1qE7A9dnBNM0SOwONM40WZk5hPUUoSrM+RdQ==", + "license": "Apache-2.0", + "dependencies": { + "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/borsh": "0.29.0", + "@solana/spl-token": "0.3.9", + "@solana/web3.js": "^1.95.2", + "@wormhole-foundation/sdk-connect": "1.1.1", + "rpc-websockets": "^7.10.0" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "platforms/solana/protocols/cctp": { "name": "@wormhole-foundation/sdk-solana-cctp", "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8", "@wormhole-foundation/sdk-connect": "1.0.3", @@ -9222,13 +9462,17 @@ "node": ">=16" } }, + "platforms/solana/protocols/cctp/node_modules/@wormhole-foundation/sdk-connect": { + "resolved": "connect", + "link": true + }, "platforms/solana/protocols/core": { "name": "@wormhole-foundation/sdk-solana-core", "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@coral-xyz/anchor": "0.29.0", - "@coral-xyz/borsh": "0.29.0", + "@coral-xyz/anchor": "0.30.1", + "@coral-xyz/borsh": "0.30.1", "@solana/web3.js": "^1.95.8", "@wormhole-foundation/sdk-connect": "1.0.3", "@wormhole-foundation/sdk-solana": "1.0.3" @@ -9237,12 +9481,34 @@ "node": ">=16" } }, + "platforms/solana/protocols/core/node_modules/@wormhole-foundation/sdk-base": { + "resolved": "core/base", + "link": true + }, + "platforms/solana/protocols/core/node_modules/@wormhole-foundation/sdk-connect": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-connect/-/sdk-connect-1.0.3.tgz", + "integrity": "sha512-edXkPv2NDe1CSs9dcxC03lbknl1pI3YC/IEEmmn3BXD8xDL9wdvOsOzXifOhHAwUO8mGJiSVuTFRzfxiBIs56w==", + "license": "Apache-2.0", + "dependencies": { + "@wormhole-foundation/sdk-base": "1.0.3", + "@wormhole-foundation/sdk-definitions": "1.0.3", + "axios": "^1.4.0" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/protocols/core/node_modules/@wormhole-foundation/sdk-definitions": { + "resolved": "core/definitions", + "link": true + }, "platforms/solana/protocols/tokenBridge": { "name": "@wormhole-foundation/sdk-solana-tokenbridge", "version": "1.0.3", "license": "Apache-2.0", "dependencies": { - "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8", "@wormhole-foundation/sdk-connect": "1.0.3", @@ -9253,6 +9519,43 @@ "node": ">=16" } }, + "platforms/solana/protocols/tokenBridge/node_modules/@wormhole-foundation/sdk-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-base/-/sdk-base-1.0.3.tgz", + "integrity": "sha512-zhieqHzyopDN9Z60tqa1EBaJhjRsY56qgP9qpaXT7GkMgp5HI1j2msTaJrpVtVOnTKBt78uJXatKP7+pFDKiJA==", + "license": "Apache-2.0", + "dependencies": { + "@scure/base": "^1.1.3" + } + }, + "platforms/solana/protocols/tokenBridge/node_modules/@wormhole-foundation/sdk-connect": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-connect/-/sdk-connect-1.0.3.tgz", + "integrity": "sha512-edXkPv2NDe1CSs9dcxC03lbknl1pI3YC/IEEmmn3BXD8xDL9wdvOsOzXifOhHAwUO8mGJiSVuTFRzfxiBIs56w==", + "license": "Apache-2.0", + "dependencies": { + "@wormhole-foundation/sdk-base": "1.0.3", + "@wormhole-foundation/sdk-definitions": "1.0.3", + "axios": "^1.4.0" + }, + "engines": { + "node": ">=16" + } + }, + "platforms/solana/protocols/tokenBridge/node_modules/@wormhole-foundation/sdk-definitions": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wormhole-foundation/sdk-definitions/-/sdk-definitions-1.0.3.tgz", + "integrity": "sha512-ni7msDLSCKOHSTyTTxEI9IPXlOtKtkpdyJ1i4LweOKP5qcncvLo4IJJU69C+kKc+8KwCpmz+VS+z9YAE6e+QHA==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "@wormhole-foundation/sdk-base": "1.0.3" + } + }, + "platforms/solana/protocols/tokenBridge/node_modules/@wormhole-foundation/sdk-solana-core": { + "resolved": "platforms/solana/protocols/core", + "link": true + }, "platforms/sui": { "name": "@wormhole-foundation/sdk-sui", "version": "1.0.3", diff --git a/package.json b/package.json index f897665df..a9ff12748 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@types/chai": "^4.3.5", "@types/jest": "^29.5.12", "@types/mocha": "^10.0.1", - "@types/node": "^20.4.1", + "@types/node": "^20.17.10", "@typescript-eslint/eslint-plugin": "^6.21.0", "chai": "^4.3.7", "jest": "^29.7.0", @@ -68,4 +68,4 @@ "unreleased": [ "tokenRegistry" ] -} \ No newline at end of file +} diff --git a/platforms/solana/README.md b/platforms/solana/README.md index d6fd37f1e..78488a0bc 100644 --- a/platforms/solana/README.md +++ b/platforms/solana/README.md @@ -1 +1,15 @@ -# Solana Context +# Solana Platform Code + +Here is all the code for the Solana Platform. It is divided in 3 sections: + +- In `address.ts`, `chain.ts`, `platform.ts`, `signer.ts`, `types.ts`, `unsignedTransaction.ts`, there + are the implementation of the Wormhole core interfaces. The structure is the same as in the EVM + code. +- The `utils` folder holds a lot of utilities being useful when writing a SDK or calling a program. + Among them is `bpfLoaderUpgradeable` which allows to interact with this program. +- The `testing` folder holds: + - A big helper class `TestsHelper` allowing to do everything that is desirable in an integration + tests package. + - 2 classes allowing to interact with the Wormhole Core program and the Token Bridge one in a testing + environment. They can be instanciated from `TestsHelper` which is supposed to be the one-stop class + for writing integration tests. diff --git a/platforms/solana/package.json b/platforms/solana/package.json index e1ccc0b11..9a0671325 100644 --- a/platforms/solana/package.json +++ b/platforms/solana/package.json @@ -49,11 +49,15 @@ "nock": "^13.3.3" }, "dependencies": { - "@coral-xyz/anchor": "0.29.0", - "@coral-xyz/borsh": "0.29.0", + "@coral-xyz/anchor": "0.30.1", + "@coral-xyz/borsh": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8", - "@wormhole-foundation/sdk-connect": "1.0.3", + "@wormhole-foundation/sdk-connect": "1.1.1", + "@wormhole-foundation/sdk-solana-tokenbridge": "1.1.1", + "@wormhole-foundation/sdk-base": "1.1.1", + "@wormhole-foundation/sdk-definitions": "1.1.1", + "@wormhole-foundation/sdk-solana-core": "1.1.1", "rpc-websockets": "^7.10.0" }, "type": "module", diff --git a/platforms/solana/protocols/cctp/package.json b/platforms/solana/protocols/cctp/package.json index 4ca320a1f..bc7ff16a5 100644 --- a/platforms/solana/protocols/cctp/package.json +++ b/platforms/solana/protocols/cctp/package.json @@ -50,7 +50,7 @@ "dependencies": { "@wormhole-foundation/sdk-connect": "1.0.3", "@wormhole-foundation/sdk-solana": "1.0.3", - "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8" }, @@ -77,4 +77,4 @@ } } } -} \ No newline at end of file +} diff --git a/platforms/solana/protocols/core/package.json b/platforms/solana/protocols/core/package.json index 2af98b20a..9022654d8 100644 --- a/platforms/solana/protocols/core/package.json +++ b/platforms/solana/protocols/core/package.json @@ -47,8 +47,7 @@ "dependencies": { "@wormhole-foundation/sdk-connect": "1.0.3", "@wormhole-foundation/sdk-solana": "1.0.3", - "@coral-xyz/borsh": "0.29.0", - "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/web3.js": "^1.95.8" }, "type": "module", @@ -74,4 +73,4 @@ } } } -} \ No newline at end of file +} diff --git a/platforms/solana/protocols/core/src/accountLayout.ts b/platforms/solana/protocols/core/src/accountLayout.ts new file mode 100644 index 000000000..b40d0677a --- /dev/null +++ b/platforms/solana/protocols/core/src/accountLayout.ts @@ -0,0 +1,97 @@ +import type { Layout } from '@wormhole-foundation/sdk-connect'; +import { layoutItems } from '@wormhole-foundation/sdk-connect'; +import { chainToChainId, enumItem } from '@wormhole-foundation/sdk-base'; + +const versionItem = (custom: N) => + ({ name: 'version', binary: 'uint', size: 1, custom, omit: true }) as const; + +const consistencyLevelItem = { + name: 'consistencyLevel', + binary: 'uint', + size: 1, +} as const; + +const timestampItem = { + name: 'timestamp', + binary: 'uint', + size: 4, + endianness: 'little', +} as const; + +const nonceAndSequenceLayout = [ + { name: 'nonce', binary: 'uint', size: 4, endianness: 'little' }, + { name: 'sequence', binary: 'uint', size: 8, endianness: 'little' }, +] as const satisfies Layout; + +const emitterAddressAndPayloadLayout = [ + { name: 'emitterAddress', ...layoutItems.universalAddressItem }, + { + name: 'payload', + binary: 'bytes', + lengthSize: 4, + lengthEndianness: 'little', + }, +] as const satisfies Layout; + +// From here: https://github.com/wormhole-foundation/wormhole/blob/7bd40b595e22c5512dfaa2ed8e6d7441df743a69/solana/programs/core-bridge/src/legacy/state/posted_message_v1/mod.rs#L17-L35 +const messageStatusItem = { + name: 'messageStatus', + ...enumItem([ + ['Published', 0], + ['Writing', 1], + ['ReadyForPublishing', 2], + ]), +} as const; + +// From here: https://github.com/wormhole-foundation/wormhole/blob/7bd40b595e22c5512dfaa2ed8e6d7441df743a69/solana/programs/core-bridge/src/legacy/state/posted_message_v1/mod.rs#L39-L73 +// reuses unused fields (that were only used for VAAs) from here: https://github.com/wormhole-foundation/wormhole/blob/7247a0fc0c96ab9493b8d0b886a7a54ee2a8fcce/solana/bridge/program/src/accounts/posted_message.rs#L46-L76 +// hence these fields will only have sensible values when parsing posted messages by the solana core bridge rewrite +const postedMessageV1Layout = [ + versionItem(0), + consistencyLevelItem, + { name: 'emitterAuthority', ...layoutItems.universalAddressItem }, + messageStatusItem, + { name: 'unusedGap', binary: 'uint', size: 3, custom: 0, omit: true }, + timestampItem, + ...nonceAndSequenceLayout, + { + name: 'emitterChain', + binary: 'uint', + size: 2, + endianness: 'little', + custom: { from: chainToChainId('Solana'), to: 'Solana' }, + }, + ...emitterAddressAndPayloadLayout, +] as const satisfies Layout; + +//from here: https://github.com/wormhole-foundation/wormhole/blob/7bd40b595e22c5512dfaa2ed8e6d7441df743a69/solana/programs/core-bridge/src/legacy/state/posted_vaa_v1.rs#L12-L43 +//reuses unused fields (that were only used for posted messages) from here: https://github.com/wormhole-foundation/wormhole/blob/7247a0fc0c96ab9493b8d0b886a7a54ee2a8fcce/solana/bridge/program/src/accounts/posted_message.rs#L46-L76 +//hence these fields will only have sensible values when parsing posted messages by the solana core bridge rewrite +const postedVaaV1Layout = [ + versionItem(1), + consistencyLevelItem, + timestampItem, + { name: 'signatureSet', ...layoutItems.universalAddressItem }, + { name: 'guardianSetIndex', binary: 'uint', size: 4, endianness: 'little' }, + ...nonceAndSequenceLayout, + { name: 'emitterChain', ...layoutItems.chainItem(), endianness: 'little' }, + ...emitterAddressAndPayloadLayout, +] as const satisfies Layout; + +/** + * Covers: + * - A VAA; + * - A message; + * - An unreliable message. + */ +export const accountDataLayout = { + binary: 'switch', + idSize: 3, + idTag: 'discriminator', + layouts: [ + //numeric values are ascii->number encoding of strings + [[0x6d7367, 'msg'], postedMessageV1Layout], + [[0x6d7375, 'msu'], postedMessageV1Layout], + [[0x766161, 'vaa'], postedVaaV1Layout], + ], +} as const satisfies Layout; diff --git a/platforms/solana/protocols/core/src/core.ts b/platforms/solana/protocols/core/src/core.ts index e656aa23c..0e1baf3d5 100644 --- a/platforms/solana/protocols/core/src/core.ts +++ b/platforms/solana/protocols/core/src/core.ts @@ -1,16 +1,32 @@ -import type { Program } from '@coral-xyz/anchor'; +import { Program } from '@coral-xyz/anchor'; +import { Keypair, Transaction, PublicKey } from '@solana/web3.js'; +import { + createVAA, + deserializeLayout, + toChain, +} from '@wormhole-foundation/sdk-connect'; +import { + SolanaAddress, + SolanaPlatform, + SolanaUnsignedTransaction, +} from '@wormhole-foundation/sdk-solana'; +import { accountDataLayout } from './accountLayout.js'; +import { IDL } from './types.js'; + import type { CompiledInstruction, Connection, MessageAccountKeys, MessageCompiledInstruction, - PublicKey, TransactionResponse, VersionedTransactionResponse, } from '@solana/web3.js'; -import { Keypair, Transaction } from '@solana/web3.js'; import type { - ChainId, + AnySolanaAddress, + SolanaChains, + SolanaTransaction, +} from '@wormhole-foundation/sdk-solana'; +import type { ChainsConfig, Contracts, Network, @@ -20,44 +36,17 @@ import type { WormholeCore, WormholeMessageId, } from '@wormhole-foundation/sdk-connect'; -import { - createVAA, - toChain, - toChainId, -} from '@wormhole-foundation/sdk-connect'; -import type { - AnySolanaAddress, - SolanaChains, - SolanaTransaction, -} from '@wormhole-foundation/sdk-solana'; -import { - SolanaAddress, - SolanaPlatform, - SolanaUnsignedTransaction, -} from '@wormhole-foundation/sdk-solana'; -import { deserializePostMessage } from './postMessageLayout.js'; import type { Wormhole as WormholeCoreContract } from './types.js'; -import type { BridgeData } from './utils/index.js'; -import { - createBridgeFeeTransferInstruction, - createPostMessageInstruction, - createPostVaaInstruction, - createReadOnlyWormholeProgramInterface, - createVerifySignaturesInstructions, - derivePostedVaaKey, - getGuardianSet, - getWormholeBridgeData, -} from './utils/index.js'; const SOLANA_SEQ_LOG = 'Program log: Sequence: '; export class SolanaWormholeCore implements WormholeCore { - readonly chainId: ChainId; readonly coreBridge: Program; - readonly address: string; - protected bridgeData?: BridgeData; + get address() { + return this.coreBridge.programId; + } constructor( readonly network: N, @@ -65,19 +54,15 @@ export class SolanaWormholeCore readonly connection: Connection, readonly contracts: Contracts, ) { - this.chainId = toChainId(chain); - - const coreBridgeAddress = contracts.coreBridge; - if (!coreBridgeAddress) + if (contracts.coreBridge === undefined) { throw new Error( `CoreBridge contract Address for chain ${chain} not found`, ); + } - this.address = coreBridgeAddress; - - this.coreBridge = createReadOnlyWormholeProgramInterface( - coreBridgeAddress, - connection, + this.coreBridge = new Program( + { ...IDL, address: contracts.coreBridge }, + { connection }, ); } @@ -123,7 +108,9 @@ export class SolanaWormholeCore } async getMessageFee(): Promise { + //return this.coreBridge.account.bridgeData.; await this.ensureBridgeConfig(); + return this.bridgeData!.config.fee; } @@ -356,7 +343,7 @@ export class SolanaWormholeCore sequence, nonce, payload, - } = deserializePostMessage(new Uint8Array(acctInfo?.data!)); + } = deserializeLayout(accountDataLayout, acctInfo?.data); return createVAA('Uint8Array', { guardianSet: await this.getGuardianSetIndex(), diff --git a/platforms/solana/protocols/core/src/index.ts b/platforms/solana/protocols/core/src/index.ts index 38b06f078..1ea0e11a6 100644 --- a/platforms/solana/protocols/core/src/index.ts +++ b/platforms/solana/protocols/core/src/index.ts @@ -6,5 +6,4 @@ registerProtocol(_platform, 'WormholeCore', SolanaWormholeCore); export * from './core.js'; export * from './types.js'; -export * as utils from './utils/index.js'; -export * from './postMessageLayout.js'; +export * from './accountLayout.js'; diff --git a/platforms/solana/protocols/core/src/postMessageLayout.ts b/platforms/solana/protocols/core/src/postMessageLayout.ts deleted file mode 100644 index bdbc7f190..000000000 --- a/platforms/solana/protocols/core/src/postMessageLayout.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Layout, LayoutToType } from '@wormhole-foundation/sdk-connect'; -import { - deserializeLayout, - layoutItems, -} from '@wormhole-foundation/sdk-connect'; - -/** Binary layout for postMessage account */ -export const postMessageLayout = [ - { name: 'discriminator', binary: 'bytes', size: 4 }, - { name: 'consistencyLevel', binary: 'uint', size: 1, endianness: 'little' }, - { name: 'emitterAuthority', ...layoutItems.universalAddressItem }, - { name: 'messageStatus', binary: 'uint', size: 1, endianness: 'little' }, - { name: 'gap', binary: 'uint', size: 3 }, - { name: 'timestamp', binary: 'uint', size: 4, endianness: 'little' }, - { name: 'nonce', binary: 'uint', size: 4, endianness: 'little' }, - { name: 'sequence', binary: 'uint', size: 8, endianness: 'little' }, - { name: 'emitterChain', binary: 'uint', size: 2, endianness: 'little' }, - { name: 'emitterAddress', ...layoutItems.universalAddressItem }, - { name: 'payloadLength', binary: 'uint', size: 4, endianness: 'little' }, - { name: 'payload', binary: 'bytes' }, -] as const satisfies Layout; - -export function deserializePostMessage( - data: Uint8Array, -): LayoutToType { - return deserializeLayout(postMessageLayout, data); -} diff --git a/platforms/solana/protocols/core/src/types.ts b/platforms/solana/protocols/core/src/types.ts index dca47637d..0724632e3 100644 --- a/platforms/solana/protocols/core/src/types.ts +++ b/platforms/solana/protocols/core/src/types.ts @@ -1,9 +1,14 @@ export type Wormhole = { - version: '0.1.0'; - name: 'wormhole'; + address: string; + metadata: { + name: 'wormhole'; + version: '0.1.0'; + spec: '0.1.0'; + }; instructions: [ { name: 'initialize'; + discriminator: [0]; accounts: [ { name: 'bridge'; @@ -62,6 +67,7 @@ export type Wormhole = { }, { name: 'postMessage'; + discriminator: [1]; accounts: [ { name: 'bridge'; @@ -126,6 +132,7 @@ export type Wormhole = { }, { name: 'postVaa'; + discriminator: [2]; accounts: [ { name: 'guardianSet'; @@ -211,6 +218,7 @@ export type Wormhole = { }, { name: 'setFees'; + discriminator: [3]; accounts: [ { name: 'payer'; @@ -242,6 +250,7 @@ export type Wormhole = { }, { name: 'transferFees'; + discriminator: [4]; accounts: [ { name: 'payer'; @@ -288,6 +297,7 @@ export type Wormhole = { }, { name: 'upgradeContract'; + discriminator: [5]; accounts: [ { name: 'payer'; @@ -359,6 +369,7 @@ export type Wormhole = { }, { name: 'upgradeGuardianSet'; + discriminator: [6]; accounts: [ { name: 'payer'; @@ -400,6 +411,7 @@ export type Wormhole = { }, { name: 'verifySignatures'; + discriminator: [7]; accounts: [ { name: 'payer'; @@ -443,6 +455,7 @@ export type Wormhole = { }, { name: 'postMessageUnreliable'; + discriminator: [8]; accounts: [ { name: 'bridge'; @@ -507,8 +520,47 @@ export type Wormhole = { }, ]; accounts: [ + { + name: 'BridgeData'; + discriminator: []; + type: { + kind: 'struct'; + fields: [ + { + name: 'guardianSetIndex'; + type: 'u32'; + }, + { + name: 'lastLamports'; + type: 'u64'; + }, + { + name: 'config'; + type: 'BridgeConfig'; + }, + ]; + }; + }, + { + name: 'BridgeConfig'; + discriminator: []; + type: { + kind: 'struct'; + fields: [ + { + name: 'guardianSetExpirationTime'; + type: 'u32'; + }, + { + name: 'fee'; + type: 'u64'; + }, + ]; + }; + }, { name: 'PostedMessage'; + discriminator: []; type: { kind: 'struct'; fields: [ @@ -526,7 +578,7 @@ export type Wormhole = { }, { name: 'vaaSignatureAccount'; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'submissionTime'; @@ -559,6 +611,7 @@ export type Wormhole = { }, { name: 'PostedVAA'; + discriminator: []; type: { kind: 'struct'; fields: [ @@ -576,7 +629,7 @@ export type Wormhole = { }, { name: 'vaaSignatureAccount'; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'submissionTime'; @@ -611,11 +664,16 @@ export type Wormhole = { }; export const IDL: Wormhole = { - version: '0.1.0', - name: 'wormhole', + address: '', + metadata: { + name: 'wormhole', + version: '0.1.0', + spec: '0.1.0', + }, instructions: [ { name: 'initialize', + discriminator: [0], accounts: [ { name: 'bridge', @@ -674,6 +732,7 @@ export const IDL: Wormhole = { }, { name: 'postMessage', + discriminator: [1], accounts: [ { name: 'bridge', @@ -738,6 +797,7 @@ export const IDL: Wormhole = { }, { name: 'postVaa', + discriminator: [2], accounts: [ { name: 'guardianSet', @@ -823,6 +883,7 @@ export const IDL: Wormhole = { }, { name: 'setFees', + discriminator: [3], accounts: [ { name: 'payer', @@ -854,6 +915,7 @@ export const IDL: Wormhole = { }, { name: 'transferFees', + discriminator: [4], accounts: [ { name: 'payer', @@ -900,6 +962,7 @@ export const IDL: Wormhole = { }, { name: 'upgradeContract', + discriminator: [5], accounts: [ { name: 'payer', @@ -971,6 +1034,7 @@ export const IDL: Wormhole = { }, { name: 'upgradeGuardianSet', + discriminator: [6], accounts: [ { name: 'payer', @@ -1012,6 +1076,7 @@ export const IDL: Wormhole = { }, { name: 'verifySignatures', + discriminator: [7], accounts: [ { name: 'payer', @@ -1055,6 +1120,7 @@ export const IDL: Wormhole = { }, { name: 'postMessageUnreliable', + discriminator: [8], accounts: [ { name: 'bridge', @@ -1119,8 +1185,47 @@ export const IDL: Wormhole = { }, ], accounts: [ + { + name: 'BridgeData', + discriminator: [], + type: { + kind: 'struct', + fields: [ + { + name: 'guardianSetIndex', + type: 'u32', + }, + { + name: 'lastLamports', + type: 'u64', + }, + { + name: 'config', + type: 'BridgeConfig', + }, + ], + }, + }, + { + name: 'BridgeConfig', + discriminator: [], + type: { + kind: 'struct', + fields: [ + { + name: 'guardianSetExpirationTime', + type: 'u32', + }, + { + name: 'fee', + type: 'u64', + }, + ], + }, + }, { name: 'PostedMessage', + discriminator: [], type: { kind: 'struct', fields: [ @@ -1138,7 +1243,7 @@ export const IDL: Wormhole = { }, { name: 'vaaSignatureAccount', - type: 'publicKey', + type: 'pubkey', }, { name: 'submissionTime', @@ -1171,6 +1276,7 @@ export const IDL: Wormhole = { }, { name: 'PostedVAA', + discriminator: [], type: { kind: 'struct', fields: [ @@ -1188,7 +1294,7 @@ export const IDL: Wormhole = { }, { name: 'vaaSignatureAccount', - type: 'publicKey', + type: 'pubkey', }, { name: 'submissionTime', diff --git a/platforms/solana/protocols/core/src/utils/accounts/claim.ts b/platforms/solana/protocols/core/src/utils/accounts/claim.ts deleted file mode 100644 index 5b730bcf3..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/claim.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { - Commitment, - Connection, - PublicKey, - PublicKeyInitData, -} from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveClaimKey( - programId: PublicKeyInitData, - emitterAddress: Buffer | Uint8Array | string, - emitterChain: number, - sequence: bigint | number, -): PublicKey { - const address = - typeof emitterAddress == 'string' - ? Buffer.from(emitterAddress, 'hex') - : Buffer.from(emitterAddress); - if (address.length != 32) { - throw Error('address.length != 32'); - } - const sequenceSerialized = Buffer.alloc(8); - sequenceSerialized.writeBigUInt64BE( - typeof sequence == 'number' ? BigInt(sequence) : sequence, - ); - return utils.deriveAddress( - [ - address, - (() => { - const buf = Buffer.alloc(2); - buf.writeUInt16BE(emitterChain as number); - return buf; - })(), - sequenceSerialized, - ], - programId, - ); -} - -export async function getClaim( - connection: Connection, - programId: PublicKeyInitData, - emitterAddress: Buffer | Uint8Array | string, - emitterChain: number, - sequence: bigint | number, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo( - deriveClaimKey(programId, emitterAddress, emitterChain, sequence), - commitment, - ) - .then((info) => !!utils.getAccountData(info)[0]); -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/config.ts b/platforms/solana/protocols/core/src/utils/accounts/config.ts deleted file mode 100644 index 48500b2a7..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/config.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { - Connection, - PublicKey, - Commitment, - PublicKeyInitData, -} from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveWormholeBridgeDataKey( - wormholeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('Bridge')], wormholeProgramId); -} - -export async function getWormholeBridgeData( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo(deriveWormholeBridgeDataKey(wormholeProgramId), commitment) - .then((info) => BridgeData.deserialize(utils.getAccountData(info))); -} - -export class BridgeConfig { - guardianSetExpirationTime: number; - fee: bigint; - - constructor(guardianSetExpirationTime: number, fee: bigint) { - this.guardianSetExpirationTime = guardianSetExpirationTime; - this.fee = fee; - } - - static deserialize(data: Buffer): BridgeConfig { - if (data.length != 12) { - throw new Error('data.length != 12'); - } - const guardianSetExpirationTime = data.readUInt32LE(0); - const fee = data.readBigUInt64LE(4); - return new BridgeConfig(guardianSetExpirationTime, fee); - } -} - -export class BridgeData { - guardianSetIndex: number; - lastLamports: bigint; - config: BridgeConfig; - - constructor( - guardianSetIndex: number, - lastLamports: bigint, - config: BridgeConfig, - ) { - this.guardianSetIndex = guardianSetIndex; - this.lastLamports = lastLamports; - this.config = config; - } - - static deserialize(data: Buffer): BridgeData { - if (data.length != 24) { - throw new Error('data.length != 24'); - } - const guardianSetIndex = data.readUInt32LE(0); - const lastLamports = data.readBigUInt64LE(4); - const config = BridgeConfig.deserialize(data.subarray(12)); - return new BridgeData(guardianSetIndex, lastLamports, config); - } -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/emitter.ts b/platforms/solana/protocols/core/src/utils/accounts/emitter.ts deleted file mode 100644 index 0d4d4b8dc..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/emitter.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { - Commitment, - Connection, - PublicKey, - PublicKeyInitData, -} from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { SequenceTracker } from './sequence.js'; -import { deriveEmitterSequenceKey, getSequenceTracker } from './sequence.js'; - -export interface EmitterAccounts { - emitter: PublicKey; - sequence: PublicKey; -} - -export function deriveWormholeEmitterKey( - emitterProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('emitter')], emitterProgramId); -} - -export function getEmitterKeys( - emitterProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, -): EmitterAccounts { - const emitter = deriveWormholeEmitterKey(emitterProgramId); - return { - emitter, - sequence: deriveEmitterSequenceKey(emitter, wormholeProgramId), - }; -} - -export async function getProgramSequenceTracker( - connection: Connection, - emitterProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return getSequenceTracker( - connection, - deriveWormholeEmitterKey(emitterProgramId), - wormholeProgramId, - commitment, - ); -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/feeCollector.ts b/platforms/solana/protocols/core/src/utils/accounts/feeCollector.ts deleted file mode 100644 index 882902e37..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/feeCollector.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveFeeCollectorKey( - wormholeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('fee_collector')], wormholeProgramId); -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/guardianSet.ts b/platforms/solana/protocols/core/src/utils/accounts/guardianSet.ts deleted file mode 100644 index a50d82a0f..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/guardianSet.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { - Connection, - PublicKey, - Commitment, - PublicKeyInitData, -} from '@solana/web3.js'; -import { ETHEREUM_KEY_LENGTH } from '../instructions/secp256k1.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveGuardianSetKey( - wormholeProgramId: PublicKeyInitData, - index: number, -): PublicKey { - return utils.deriveAddress( - [ - Buffer.from('GuardianSet'), - (() => { - const buf = Buffer.alloc(4); - buf.writeUInt32BE(index); - return buf; - })(), - ], - wormholeProgramId, - ); -} - -export async function getGuardianSet( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - index: number, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo(deriveGuardianSetKey(wormholeProgramId, index), commitment) - .then((info) => GuardianSetData.deserialize(utils.getAccountData(info))); -} - -export class GuardianSetData { - index: number; - keys: Buffer[]; - creationTime: number; - expirationTime: number; - - constructor( - index: number, - keys: Buffer[], - creationTime: number, - expirationTime: number, - ) { - this.index = index; - this.keys = keys; - this.creationTime = creationTime; - this.expirationTime = expirationTime; - } - - static deserialize(data: Buffer): GuardianSetData { - const index = data.readUInt32LE(0); - const keysLen = data.readUInt32LE(4); - const keysEnd = 8 + keysLen * ETHEREUM_KEY_LENGTH; - const creationTime = data.readUInt32LE(keysEnd); - const expirationTime = data.readUInt32LE(4 + keysEnd); - - const keys = []; - for (let i = 0; i < keysLen; ++i) { - const start = 8 + i * ETHEREUM_KEY_LENGTH; - keys.push(data.subarray(start, start + ETHEREUM_KEY_LENGTH)); - } - return new GuardianSetData(index, keys, creationTime, expirationTime); - } -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/index.ts b/platforms/solana/protocols/core/src/utils/accounts/index.ts deleted file mode 100644 index f8e65b370..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './claim.js'; -export * from './config.js'; -export * from './emitter.js'; -export * from './feeCollector.js'; -export * from './guardianSet.js'; -export * from './postedVaa.js'; -export * from './sequence.js'; -export * from './signatureSet.js'; -export * from './upgrade.js'; diff --git a/platforms/solana/protocols/core/src/utils/accounts/postedVaa.ts b/platforms/solana/protocols/core/src/utils/accounts/postedVaa.ts deleted file mode 100644 index 387c6a1f9..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/postedVaa.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function derivePostedVaaKey( - wormholeProgramId: PublicKeyInitData, - hash: Buffer, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('PostedVAA'), hash], - wormholeProgramId, - ); -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/sequence.ts b/platforms/solana/protocols/core/src/utils/accounts/sequence.ts deleted file mode 100644 index 25c91eba9..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/sequence.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { - Connection, - Commitment, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveEmitterSequenceKey( - emitter: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('Sequence'), new PublicKey(emitter).toBytes()], - wormholeProgramId, - ); -} - -export async function getSequenceTracker( - connection: Connection, - emitter: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo( - deriveEmitterSequenceKey(emitter, wormholeProgramId), - commitment, - ) - .then((info) => SequenceTracker.deserialize(utils.getAccountData(info))); -} - -export class SequenceTracker { - sequence: bigint; - bump?: number; - emitterType?: number; - - constructor(sequence: bigint, bump?: number, emitterType?: number) { - this.sequence = sequence; - this.bump = bump; - this.emitterType = emitterType; - } - - static deserialize(data: Buffer): SequenceTracker { - if (data.length !== 8 && data.length !== 10) { - throw new Error('data.length != 8 or data.length != 10'); - } - - let bump, emitterType; - const sequence = data.readBigUInt64LE(0); - - if (data.length === 10) { - bump = data[8]; - emitterType = data[9]; - } - - return new SequenceTracker(sequence, bump, emitterType); - } - - value(): bigint { - return this.sequence; - } -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/signatureSet.ts b/platforms/solana/protocols/core/src/utils/accounts/signatureSet.ts deleted file mode 100644 index 77094f317..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/signatureSet.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { - Connection, - Commitment, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export async function getSignatureSetData( - connection: Connection, - signatureSet: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo(new PublicKey(signatureSet), commitment) - .then((info) => SignatureSetData.deserialize(utils.getAccountData(info))); -} - -export class SignatureSetData { - signatures: boolean[]; - hash: Buffer; - guardianSetIndex: number; - - constructor(signatures: boolean[], hash: Buffer, guardianSetIndex: number) { - this.signatures = signatures; - this.hash = hash; - this.guardianSetIndex = guardianSetIndex; - } - - static deserialize(data: Buffer): SignatureSetData { - const numSignatures = data.readUInt32LE(0); - const signatures = [...data.subarray(4, 4 + numSignatures)].map( - (x) => x != 0, - ); - const hashIndex = 4 + numSignatures; - const hash = data.subarray(hashIndex, hashIndex + 32); - const guardianSetIndex = data.readUInt32LE(hashIndex + 32); - return new SignatureSetData(signatures, hash, guardianSetIndex); - } -} diff --git a/platforms/solana/protocols/core/src/utils/accounts/upgrade.ts b/platforms/solana/protocols/core/src/utils/accounts/upgrade.ts deleted file mode 100644 index 03b71851b..000000000 --- a/platforms/solana/protocols/core/src/utils/accounts/upgrade.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveUpgradeAuthorityKey( - wormholeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('upgrade')], wormholeProgramId); -} diff --git a/platforms/solana/protocols/core/src/utils/coder/accounts.ts b/platforms/solana/protocols/core/src/utils/coder/accounts.ts deleted file mode 100644 index add2b723c..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/accounts.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { AccountsCoder, Idl } from '@coral-xyz/anchor'; -import { anchor } from '@wormhole-foundation/sdk-solana'; - -export class WormholeAccountsCoder - implements AccountsCoder -{ - constructor(private idl: Idl) {} - - public async encode(accountName: A, account: T): Promise { - switch (accountName) { - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public decode(accountName: A, ix: Buffer): T { - return this.decodeUnchecked(accountName, ix); - } - - public decodeUnchecked(accountName: A, ix: Buffer): T { - switch (accountName) { - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public memcmp(accountName: A, _appendData?: Buffer): any { - switch (accountName) { - case 'postVaa': { - return { - dataSize: 56, // + 4 + payload.length - }; - } - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public size(idlAccount: anchor.IdlTypeDef): number { - return anchor.accountSize(this.idl, idlAccount) ?? 0; - } -} - -export interface PostVAAData { - version: number; - guardianSetIndex: number; - timestamp: number; - nonce: number; - emitterChain: number; - emitterAddress: Buffer; - sequence: bigint; - consistencyLevel: number; - payload: Buffer; -} - -export function encodePostVaaData(account: PostVAAData): Buffer { - const payload = account.payload; - const serialized = Buffer.alloc(60 + payload.length); - serialized.writeUInt8(account.version, 0); - serialized.writeUInt32LE(account.guardianSetIndex, 1); - serialized.writeUInt32LE(account.timestamp, 5); - serialized.writeUInt32LE(account.nonce, 9); - serialized.writeUInt16LE(account.emitterChain, 13); - serialized.write(account.emitterAddress.toString('hex'), 15, 'hex'); - serialized.writeBigUInt64LE(account.sequence, 47); - serialized.writeUInt8(account.consistencyLevel, 55); - serialized.writeUInt32LE(payload.length, 56); - serialized.write(payload.toString('hex'), 60, 'hex'); - - return serialized; -} - -export function decodePostVaaAccount(buf: Buffer): T { - return {} as T; -} diff --git a/platforms/solana/protocols/core/src/utils/coder/events.ts b/platforms/solana/protocols/core/src/utils/coder/events.ts deleted file mode 100644 index b0baf8f79..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/events.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { EventCoder, Event, Idl } from '@coral-xyz/anchor'; -import type { anchor } from '@wormhole-foundation/sdk-solana'; - -export class WormholeEventsCoder implements EventCoder { - constructor(_idl: Idl) {} - - decode< - E extends anchor.IdlEvent = anchor.IdlEvent, - T = Record, - >(_log: string): Event | null { - throw new Error('Wormhole program does not have events'); - } -} diff --git a/platforms/solana/protocols/core/src/utils/coder/idl.ts b/platforms/solana/protocols/core/src/utils/coder/idl.ts deleted file mode 100644 index acce25026..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/idl.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Borrowed from coral-xyz/anchor -// -// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/borsh/idl.ts - -import * as borsh from '@coral-xyz/borsh'; -import type { Layout } from 'buffer-layout'; -import type { anchor } from '@wormhole-foundation/sdk-solana'; -import { camelCase } from '@wormhole-foundation/sdk-solana'; - -export class IdlCoder { - public static fieldLayout( - field: { name?: string } & Pick, - types?: anchor.IdlTypeDef[], - ): Layout { - const fieldName = - field.name !== undefined ? camelCase(field.name) : undefined; - switch (field.type) { - case 'bool': { - return borsh.bool(fieldName); - } - case 'u8': { - return borsh.u8(fieldName); - } - case 'i8': { - return borsh.i8(fieldName); - } - case 'u16': { - return borsh.u16(fieldName); - } - case 'i16': { - return borsh.i16(fieldName); - } - case 'u32': { - return borsh.u32(fieldName); - } - case 'i32': { - return borsh.i32(fieldName); - } - case 'f32': { - return borsh.f32(fieldName); - } - case 'u64': { - return borsh.u64(fieldName); - } - case 'i64': { - return borsh.i64(fieldName); - } - case 'f64': { - return borsh.f64(fieldName); - } - case 'u128': { - return borsh.u128(fieldName); - } - case 'i128': { - return borsh.i128(fieldName); - } - case 'u256': { - return borsh.u256(fieldName); - } - case 'i256': { - return borsh.i256(fieldName); - } - case 'bytes': { - return borsh.vecU8(fieldName); - } - case 'string': { - return borsh.str(fieldName); - } - case 'publicKey': { - return borsh.publicKey(fieldName); - } - default: { - if ('vec' in field.type) { - return borsh.vec( - IdlCoder.fieldLayout( - { - name: undefined, - type: field.type.vec, - }, - types, - ), - fieldName, - ); - } else if ('option' in field.type) { - return borsh.option( - IdlCoder.fieldLayout( - { - name: undefined, - type: field.type.option, - }, - types, - ), - fieldName, - ); - } else if ('array' in field.type) { - let arrayTy = field.type.array[0]; - let arrayLen = field.type.array[1]; - let innerLayout = IdlCoder.fieldLayout( - { - name: undefined, - type: arrayTy, - }, - types, - ); - return borsh.array(innerLayout, arrayLen, fieldName); - } else { - throw new Error(`Not yet implemented: ${field}`); - } - } - } - } -} diff --git a/platforms/solana/protocols/core/src/utils/coder/index.ts b/platforms/solana/protocols/core/src/utils/coder/index.ts deleted file mode 100644 index c3bc9e837..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Coder, Idl } from '@coral-xyz/anchor'; -import { WormholeAccountsCoder } from './accounts.js'; -import { WormholeEventsCoder } from './events.js'; -import { WormholeInstructionCoder } from './instruction.js'; -import { WormholeStateCoder } from './state.js'; -import { WormholeTypesCoder } from './types.js'; - -export * from './instruction.js'; - -export class WormholeCoder implements Coder { - readonly instruction: WormholeInstructionCoder; - readonly accounts: WormholeAccountsCoder; - readonly state: WormholeStateCoder; - readonly events: WormholeEventsCoder; - readonly types: WormholeTypesCoder; - - constructor(idl: Idl) { - this.instruction = new WormholeInstructionCoder(idl); - this.accounts = new WormholeAccountsCoder(idl); - this.state = new WormholeStateCoder(idl); - this.events = new WormholeEventsCoder(idl); - this.types = new WormholeTypesCoder(idl); - } -} diff --git a/platforms/solana/protocols/core/src/utils/coder/instruction.ts b/platforms/solana/protocols/core/src/utils/coder/instruction.ts deleted file mode 100644 index 7af9e1d7d..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/instruction.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { Idl, Instruction, InstructionCoder } from '@coral-xyz/anchor'; -import type { Layout } from 'buffer-layout'; -import { encoding } from '@wormhole-foundation/sdk-connect'; -import type { anchor } from '@wormhole-foundation/sdk-solana'; -import { camelCase, upperFirst } from '@wormhole-foundation/sdk-solana'; -import * as borsh from '@coral-xyz/borsh'; -import { IdlCoder } from './idl.js'; - -// Inspired by coral-xyz/anchor -// -// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/borsh/instruction.ts -export class WormholeInstructionCoder implements InstructionCoder { - private ixLayout: Map; - - constructor(idl: Idl) { - this.ixLayout = WormholeInstructionCoder.parseIxLayout(idl); - } - - private static parseIxLayout(idl: Idl): Map { - const stateMethods = idl.instructions ? idl.instructions : []; - - const ixLayouts = stateMethods - .map((m: anchor.IdlStateMethod): [string, Layout] => { - let fieldLayouts = m.args.map((arg: anchor.IdlField) => { - return IdlCoder.fieldLayout( - arg, - Array.from([...(idl.accounts ?? []), ...(idl.types ?? [])]), - ); - }); - const name = camelCase(m.name); - return [name, borsh.struct(fieldLayouts, name)]; - }) - .concat( - idl.instructions.map((ix) => { - let fieldLayouts = ix.args.map((arg: anchor.IdlField) => - IdlCoder.fieldLayout( - arg, - Array.from([...(idl.accounts ?? []), ...(idl.types ?? [])]), - ), - ); - const name = camelCase(ix.name); - return [name, borsh.struct(fieldLayouts, name)]; - }), - ); - return new Map(ixLayouts); - } - - encode(ixName: string, ix: any): Buffer { - const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer. - const methodName = camelCase(ixName); - const layout = this.ixLayout.get(methodName); - if (!layout) { - throw new Error(`Unknown method: ${methodName}`); - } - const len = layout.encode(ix, buffer); - const data = buffer.slice(0, len); - - return encodeWormholeInstructionData( - (WormholeInstruction as any)[upperFirst(methodName)], - data, - ); - } - - encodeState(_ixName: string, _ix: any): Buffer { - throw new Error('Wormhole program does not have state'); - } - - public decode( - ix: Buffer | Uint8Array, - _encoding: 'hex' | 'base58' = 'hex', - ): Instruction | null { - if (typeof ix === 'string') { - ix = - _encoding === 'hex' ? Buffer.from(ix, 'hex') : encoding.b58.decode(ix); - } - let discriminator = Buffer.from(ix.slice(0, 1)).readInt8(); - let data = Buffer.from(ix.slice(1)); - - let name = camelCase(WormholeInstruction[discriminator] ?? ''); - let layout = this.ixLayout.get(name); - - if (!layout) { - return null; - } - return { data: this.ixLayout.get(name)?.decode(data), name }; - } -} - -/** Solitaire enum of existing the Core Bridge's instructions. - * - * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/lib.rs#L92 - */ -export enum WormholeInstruction { - Initialize, - PostMessage, - PostVaa, - SetFees, - TransferFees, - UpgradeContract, - UpgradeGuardianSet, - VerifySignatures, - PostMessageUnreliable, // sounds useful -} - -function encodeWormholeInstructionData( - discriminator: number, - data?: Buffer, -): Buffer { - const instructionData = Buffer.alloc( - 1 + (data === undefined ? 0 : data.length), - ); - instructionData.writeUInt8(discriminator, 0); - if (data !== undefined) { - instructionData.write(data.toString('hex'), 1, 'hex'); - } - return instructionData; -} diff --git a/platforms/solana/protocols/core/src/utils/coder/state.ts b/platforms/solana/protocols/core/src/utils/coder/state.ts deleted file mode 100644 index b0494a7ad..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/state.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Idl, StateCoder } from '@coral-xyz/anchor'; - -export class WormholeStateCoder implements StateCoder { - constructor(_idl: Idl) {} - - encode(_name: string, _account: T): Promise { - throw new Error('Wormhole program does not have state'); - } - decode(_ix: Buffer): T { - throw new Error('Wormhole program does not have state'); - } -} diff --git a/platforms/solana/protocols/core/src/utils/coder/types.ts b/platforms/solana/protocols/core/src/utils/coder/types.ts deleted file mode 100644 index 038f01fda..000000000 --- a/platforms/solana/protocols/core/src/utils/coder/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Idl, TypesCoder } from '@coral-xyz/anchor'; - -export class WormholeTypesCoder implements TypesCoder { - constructor(_idl: Idl) {} - - encode(_name: string, _type: T): Buffer { - throw new Error('Wormhole program does not have user-defined types'); - } - decode(_name: string, _typeData: Buffer): T { - throw new Error('Wormhole program does not have user-defined types'); - } -} diff --git a/platforms/solana/protocols/core/src/utils/cpi.ts b/platforms/solana/protocols/core/src/utils/cpi.ts deleted file mode 100644 index d5717631a..000000000 --- a/platforms/solana/protocols/core/src/utils/cpi.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { - deriveFeeCollectorKey, - deriveWormholeBridgeDataKey, - getEmitterKeys, -} from './accounts/index.js'; -import { getPostMessageAccounts } from './instructions/index.js'; - -/** - * Accounts derived from Wormhole program. - */ -export interface WormholeDerivedAccounts { - /** - * seeds = ["Bridge"], seeds::program = wormholeProgram - */ - wormholeBridge: PublicKey; - /** - * seeds = ["emitter"], seeds::program = cpiProgramId - */ - wormholeEmitter: PublicKey; - /** - * seeds = ["Sequence", wormholeEmitter], seeds::program = wormholeProgram - */ - wormholeSequence: PublicKey; - /** - * seeds = ["fee_collector"], seeds::program = wormholeProgram - */ - wormholeFeeCollector: PublicKey; -} - -/** - * Generate Wormhole PDAs. - * - * @param cpiProgramId - * @param wormholeProgramId - * @returns - */ -export function getWormholeDerivedAccounts( - cpiProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, -): WormholeDerivedAccounts { - const { emitter: wormholeEmitter, sequence: wormholeSequence } = - getEmitterKeys(cpiProgramId, wormholeProgramId); - return { - wormholeBridge: deriveWormholeBridgeDataKey(wormholeProgramId), - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector: deriveFeeCollectorKey(wormholeProgramId), - }; -} - -/** - * Accounts needed to perform `post_message` instruction. - */ -export interface PostMessageCpiAccounts extends WormholeDerivedAccounts { - payer: PublicKey; - wormholeMessage: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -/** - * Generate accounts needed to perform `post_message` instruction - * as cross-program invocation. - * - * @param cpiProgramId - * @param wormholeProgramId - * @param payer - * @param message - * @returns - */ -export function getPostMessageCpiAccounts( - cpiProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, -): PostMessageCpiAccounts { - const accounts = getPostMessageAccounts( - wormholeProgramId, - payer, - message, - cpiProgramId, - ); - return { - payer: accounts.payer, - wormholeBridge: accounts.bridge, - wormholeMessage: accounts.message, - wormholeEmitter: accounts.emitter, - wormholeSequence: accounts.sequence, - wormholeFeeCollector: accounts.feeCollector, - clock: accounts.clock, - rent: accounts.rent, - systemProgram: accounts.systemProgram, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/index.ts b/platforms/solana/protocols/core/src/utils/index.ts deleted file mode 100644 index 6fb532c35..000000000 --- a/platforms/solana/protocols/core/src/utils/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './accounts/index.js'; -export * from './cpi.js'; -export * from './instructions/index.js'; -export * from './program.js'; diff --git a/platforms/solana/protocols/core/src/utils/instructions/feeTransfer.ts b/platforms/solana/protocols/core/src/utils/instructions/feeTransfer.ts deleted file mode 100644 index e676a6646..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/feeTransfer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram } from '@solana/web3.js'; -import { deriveFeeCollectorKey } from './../accounts/index.js'; - -export function createBridgeFeeTransferInstruction( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - fee: bigint, -): TransactionInstruction { - return SystemProgram.transfer({ - fromPubkey: new PublicKey(payer), - toPubkey: deriveFeeCollectorKey(wormholeProgramId), - lamports: fee, - }); -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/governance.ts b/platforms/solana/protocols/core/src/utils/instructions/governance.ts deleted file mode 100644 index 852a26da3..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/governance.ts +++ /dev/null @@ -1,258 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { - PublicKey, - SystemProgram, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, -} from '@solana/web3.js'; -import type { VAA } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; -import { SolanaAddress, utils } from '@wormhole-foundation/sdk-solana'; -import { - deriveClaimKey, - deriveFeeCollectorKey, - deriveGuardianSetKey, - derivePostedVaaKey, - deriveUpgradeAuthorityKey, - deriveWormholeBridgeDataKey, -} from './../accounts/index.js'; -import { createReadOnlyWormholeProgramInterface } from '../program.js'; - -export function createSetFeesInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:SetMessageFee'>, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.setFees(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getSetFeesAccounts(wormholeProgramId, payer, vaa) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface SetFeesAccounts { - payer: PublicKey; - bridge: PublicKey; - vaa: PublicKey; - claim: PublicKey; - systemProgram: PublicKey; -} - -export function getSetFeesAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:SetMessageFee'>, -): SetFeesAccounts { - return { - payer: new PublicKey(payer), - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - vaa: derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: deriveClaimKey( - wormholeProgramId, - vaa.emitterAddress.toString(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - systemProgram: SystemProgram.programId, - }; -} - -export function createTransferFeesInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - recipient: PublicKeyInitData, - vaa: VAA<'WormholeCore:TransferFees'>, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.transferFees(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getTransferFeesAccounts( - wormholeProgramId, - payer, - recipient, - vaa, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface TransferFeesAccounts { - payer: PublicKey; - bridge: PublicKey; - vaa: PublicKey; - claim: PublicKey; - feeCollector: PublicKey; - recipient: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function getTransferFeesAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - recipient: PublicKeyInitData, - vaa: VAA<'WormholeCore:TransferFees'>, -): TransferFeesAccounts { - return { - payer: new PublicKey(payer), - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - vaa: derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: deriveClaimKey( - wormholeProgramId, - vaa.emitterAddress.toString(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - feeCollector: deriveFeeCollectorKey(wormholeProgramId), - recipient: new PublicKey(recipient), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} - -export function createUpgradeGuardianSetInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:GuardianSetUpgrade'>, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.upgradeGuardianSet(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getUpgradeGuardianSetAccounts( - wormholeProgramId, - payer, - vaa, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface UpgradeGuardianSetAccounts { - payer: PublicKey; - bridge: PublicKey; - vaa: PublicKey; - claim: PublicKey; - guardianSetOld: PublicKey; - guardianSetNew: PublicKey; - systemProgram: PublicKey; -} - -export function getUpgradeGuardianSetAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:GuardianSetUpgrade'>, -): UpgradeGuardianSetAccounts { - return { - payer: new PublicKey(payer), - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - vaa: derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: deriveClaimKey( - wormholeProgramId, - vaa.emitterAddress.toString(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - guardianSetOld: deriveGuardianSetKey(wormholeProgramId, vaa.guardianSet), - guardianSetNew: deriveGuardianSetKey( - wormholeProgramId, - vaa.guardianSet + 1, - ), - systemProgram: SystemProgram.programId, - }; -} - -export function createUpgradeContractInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:UpgradeContract'>, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.upgradeContract(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getUpgradeContractAccounts(wormholeProgramId, payer, vaa) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface UpgradeContractAccounts { - payer: PublicKey; - bridge: PublicKey; - vaa: PublicKey; - claim: PublicKey; - upgradeAuthority: PublicKey; - spill: PublicKey; - implementation: PublicKey; - programData: PublicKey; - wormholeProgram: PublicKey; - rent: PublicKey; - clock: PublicKey; - bpfLoaderUpgradeable: PublicKey; - systemProgram: PublicKey; -} - -export function getUpgradeContractAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'WormholeCore:UpgradeContract'>, - spill?: PublicKeyInitData, -): UpgradeContractAccounts { - const { newContract } = vaa.payload.actionArgs; - - return { - payer: new PublicKey(payer), - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - vaa: derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: deriveClaimKey( - wormholeProgramId, - vaa.emitterAddress.toString(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - upgradeAuthority: deriveUpgradeAuthorityKey(wormholeProgramId), - spill: new PublicKey(spill === undefined ? payer : spill), - implementation: new SolanaAddress(newContract).unwrap(), - programData: utils.deriveProgramDataAddress(wormholeProgramId), - wormholeProgram: new PublicKey(wormholeProgramId), - rent: SYSVAR_RENT_PUBKEY, - clock: SYSVAR_CLOCK_PUBKEY, - bpfLoaderUpgradeable: utils.BPF_LOADER_UPGRADEABLE_PROGRAM_ID, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/index.ts b/platforms/solana/protocols/core/src/utils/instructions/index.ts deleted file mode 100644 index c2950df7d..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './feeTransfer.js'; -export * from './governance.js'; -export * from './initialize.js'; -export * from './postMessage.js'; -export * from './postVaa.js'; -export * from './verifySignature.js'; diff --git a/platforms/solana/protocols/core/src/utils/instructions/initialize.ts b/platforms/solana/protocols/core/src/utils/instructions/initialize.ts deleted file mode 100644 index 259665b75..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/initialize.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { - PublicKey, - SystemProgram, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, -} from '@solana/web3.js'; -import { createReadOnlyWormholeProgramInterface } from '../program.js'; -import { - deriveFeeCollectorKey, - deriveGuardianSetKey, - deriveWormholeBridgeDataKey, -} from './../accounts/index.js'; -import BN from 'bn.js'; - -export function createInitializeInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - guardianSetExpirationTime: number, - fee: bigint, - initialGuardians: Buffer[], -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.initialize(guardianSetExpirationTime, new BN(fee.toString()), [ - ...initialGuardians.map((b) => [...new Uint8Array(b)]), - ]); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getInitializeAccounts(wormholeProgramId, payer) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface InitializeAccounts { - bridge: PublicKey; - guardianSet: PublicKey; - feeCollector: PublicKey; - payer: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function getInitializeAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, -): InitializeAccounts { - return { - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - guardianSet: deriveGuardianSetKey(wormholeProgramId, 0), - feeCollector: deriveFeeCollectorKey(wormholeProgramId), - payer: new PublicKey(payer), - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/postMessage.ts b/platforms/solana/protocols/core/src/utils/instructions/postMessage.ts deleted file mode 100644 index 859d4e812..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/postMessage.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - SystemProgram, -} from '@solana/web3.js'; -import { - deriveWormholeBridgeDataKey, - deriveFeeCollectorKey, - deriveEmitterSequenceKey, - getEmitterKeys, -} from './../accounts/index.js'; -import { createReadOnlyWormholeProgramInterface } from '../program.js'; - -/** All accounts required to make a cross-program invocation with the Core Bridge program */ -export interface PostMessageAccounts { - bridge: PublicKey; - message: PublicKey; - emitter: PublicKey; - sequence: PublicKey; - payer: PublicKey; - feeCollector: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function createPostMessageInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - messageAccount: PublicKeyInitData, - payload: Uint8Array, - nonce: number, - consistencyLevel: number, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.postMessage(nonce, Buffer.from(payload), consistencyLevel); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getPostMessageAccounts(wormholeProgramId, payer, messageAccount), - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export function getPostMessageAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - emitter?: PublicKeyInitData, -): PostMessageAccounts { - let sequence; - if (emitter) { - ({ emitter, sequence } = getEmitterKeys(emitter, wormholeProgramId)); - } else { - emitter = payer; - sequence = deriveEmitterSequenceKey(emitter, wormholeProgramId); - } - return { - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - message: new PublicKey(message), - emitter: new PublicKey(emitter), - sequence, - payer: new PublicKey(payer), - feeCollector: deriveFeeCollectorKey(wormholeProgramId), - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/postVaa.ts b/platforms/solana/protocols/core/src/utils/instructions/postVaa.ts deleted file mode 100644 index d10100927..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/postVaa.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { - PublicKeyInitData, - TransactionInstruction, - Connection, -} from '@solana/web3.js'; -import { - PublicKey, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, - SystemProgram, -} from '@solana/web3.js'; -import { createReadOnlyWormholeProgramInterface } from '../program.js'; -import { - deriveWormholeBridgeDataKey, - deriveGuardianSetKey, - derivePostedVaaKey, -} from './../accounts/index.js'; -import type { VAA } from '@wormhole-foundation/sdk-connect'; -import { serializePayload, toChainId } from '@wormhole-foundation/sdk-connect'; -import BN from 'bn.js'; - -/** - * Make {@link TransactionInstruction} for `post_vaa` instruction. - * - * `signatureSet` is a {@link @solana/web3.Keypair} generated outside of this method, which was used - * to write signatures and the message hash to. - * - * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/api/post_vaa.rs - * - * @param {PublicKeyInitData} wormholeProgramId - wormhole program address - * @param {PublicKeyInitData} payer - transaction signer address - * @param {SignedVaa | ParsedVaa} vaa - either signed VAA bytes or parsed VAA - * @param {PublicKeyInitData} signatureSet - key for signature set account - */ -export function createPostVaaInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA, - signatureSet: PublicKeyInitData, -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.postVaa( - 1, // TODO: hardcoded VAA version - vaa.guardianSet, - vaa.timestamp, - vaa.nonce, - toChainId(vaa.emitterChain), - [...vaa.emitterAddress.toUint8Array()], - new BN(vaa.sequence.toString()), - vaa.consistencyLevel, - // Note: This _must_ be a Buffer, a Uint8Array does not work - Buffer.from(serializePayload(vaa.payloadLiteral, vaa.payload)), - ); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getPostVaaAccounts(wormholeProgramId, payer, signatureSet, vaa), - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface PostVaaAccounts { - guardianSet: PublicKey; - bridge: PublicKey; - signatureSet: PublicKey; - vaa: PublicKey; - payer: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function getPostVaaAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - signatureSet: PublicKeyInitData, - vaa: VAA, -): PostVaaAccounts { - return { - guardianSet: deriveGuardianSetKey(wormholeProgramId, vaa.guardianSet), - bridge: deriveWormholeBridgeDataKey(wormholeProgramId), - signatureSet: new PublicKey(signatureSet), - vaa: derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - payer: new PublicKey(payer), - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/secp256k1.ts b/platforms/solana/protocols/core/src/utils/instructions/secp256k1.ts deleted file mode 100644 index c6d0be051..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/secp256k1.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { TransactionInstruction, Secp256k1Program } from '@solana/web3.js'; - -export const SIGNATURE_LENGTH = 65; -export const ETHEREUM_KEY_LENGTH = 20; - -/** - * Create {@link TransactionInstruction} for {@link Secp256k1Program}. - * - * @param {Buffer[]} signatures - 65-byte signatures (64 bytes + 1 byte recovery id) - * @param {Buffer[]} keys - 20-byte ethereum public keys - * @param {Buffer} message - 32-byte hash - * @returns Solana instruction for Secp256k1 program - */ -export function createSecp256k1Instruction( - signatures: Buffer[], - keys: Buffer[], - message: Buffer, -): TransactionInstruction { - return new TransactionInstruction({ - keys: [], - programId: Secp256k1Program.programId, - data: Secp256k1SignatureOffsets.serialize(signatures, keys, message), - }); -} - -/** - * Secp256k1SignatureOffsets serializer - * - * See {@link https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program} for more info. - */ -export class Secp256k1SignatureOffsets { - // https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program - // - // struct Secp256k1SignatureOffsets { - // secp_signature_key_offset: u16, // offset to [signature,recovery_id,etherum_address] of 64+1+20 bytes - // secp_signature_instruction_index: u8, // instruction index to find data - // secp_pubkey_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes - // secp_signature_instruction_index: u8, // instruction index to find data - // secp_message_data_offset: u16, // offset to start of message data - // secp_message_data_size: u16, // size of message data - // secp_message_instruction_index: u8, // index of instruction data to get message data - // } - // - // Pseudo code of the operation: - // - // process_instruction() { - // for i in 0..count { - // // i'th index values referenced: - // instructions = &transaction.message().instructions - // signature = instructions[secp_signature_instruction_index].data[secp_signature_offset..secp_signature_offset + 64] - // recovery_id = instructions[secp_signature_instruction_index].data[secp_signature_offset + 64] - // ref_eth_pubkey = instructions[secp_pubkey_instruction_index].data[secp_pubkey_offset..secp_pubkey_offset + 32] - // message_hash = keccak256(instructions[secp_message_instruction_index].data[secp_message_data_offset..secp_message_data_offset + secp_message_data_size]) - // pubkey = ecrecover(signature, recovery_id, message_hash) - // eth_pubkey = keccak256(pubkey[1..])[12..] - // if eth_pubkey != ref_eth_pubkey { - // return Error - // } - // } - // return Success - // } - - /** - * Serialize multiple signatures, ethereum public keys and message as Secp256k1 instruction data. - * - * @param {Buffer[]} signatures - 65-byte signatures (64 + 1 recovery id) - * @param {Buffer[]} keys - ethereum public keys - * @param {Buffer} message - 32-byte hash - * @returns serialized Secp256k1 instruction data - */ - static serialize(signatures: Buffer[], keys: Buffer[], message: Buffer) { - if (signatures.length == 0) { - throw Error('signatures.length == 0'); - } - - if (signatures.length != keys.length) { - throw Error('signatures.length != keys.length'); - } - - if (message.length != 32) { - throw Error('message.length != 32'); - } - - const numSignatures = signatures.length; - const offsetSpan = 11; - const dataLoc = 1 + numSignatures * offsetSpan; - - const dataLen = SIGNATURE_LENGTH + ETHEREUM_KEY_LENGTH; // 65 signature size + 20 eth pubkey size - const messageDataOffset = dataLoc + numSignatures * dataLen; - const messageDataSize = 32; - const serialized = Buffer.alloc(messageDataOffset + messageDataSize); - - serialized.writeUInt8(numSignatures, 0); - serialized.write(message.toString('hex'), messageDataOffset, 'hex'); - - for (let i = 0; i < numSignatures; ++i) { - const signature = signatures.at(i); - if (signature?.length != SIGNATURE_LENGTH) { - throw Error(`signatures[${i}].length != 65`); - } - - const key = keys.at(i); - if (key?.length != ETHEREUM_KEY_LENGTH) { - throw Error(`keys[${i}].length != 20`); - } - - const signatureOffset = dataLoc + dataLen * i; - const ethAddressOffset = signatureOffset + 65; - - serialized.writeUInt16LE(signatureOffset, 1 + i * offsetSpan); - serialized.writeUInt8(0, 3 + i * offsetSpan); - serialized.writeUInt16LE(ethAddressOffset, 4 + i * offsetSpan); - serialized.writeUInt8(0, 6 + i * offsetSpan); - serialized.writeUInt16LE(messageDataOffset, 7 + i * offsetSpan); - serialized.writeUInt16LE(messageDataSize, 9 + i * offsetSpan); - serialized.writeUInt8(0, 10 + i * offsetSpan); - - serialized.write(signature.toString('hex'), signatureOffset, 'hex'); - serialized.write(key.toString('hex'), ethAddressOffset, 'hex'); - } - - return serialized; - } -} diff --git a/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts b/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts deleted file mode 100644 index e9be79034..000000000 --- a/platforms/solana/protocols/core/src/utils/instructions/verifySignature.ts +++ /dev/null @@ -1,161 +0,0 @@ -import type { - Commitment, - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { - PublicKey, - SYSVAR_INSTRUCTIONS_PUBKEY, - SYSVAR_RENT_PUBKEY, - SystemProgram, -} from '@solana/web3.js'; -import type { VAA } from '@wormhole-foundation/sdk-connect'; -import { createReadOnlyWormholeProgramInterface } from '../program.js'; -import { deriveGuardianSetKey, getGuardianSet } from './../accounts/index.js'; -import { createSecp256k1Instruction } from './secp256k1.js'; - -const MAX_LEN_GUARDIAN_KEYS = 19; - -/** - * Signatures are batched in groups of 7 due to instruction - * data limits. These signatures are passed through to the Secp256k1 - * program to verify that the guardian public keys can be recovered. - * This instruction is paired with `verify_signatures` to validate the - * pubkey recovery. - * - * There are at most three pairs of instructions created. - * - * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/api/verify_signature.rs - * - * - * @param {Connection} connection - Solana web3 connection - * @param {PublicKeyInitData} wormholeProgramId - wormhole program address - * @param {PublicKeyInitData} payer - transaction signer address - * @param {SignedVaa | ParsedVaa} vaa - either signed VAA bytes or parsed VAA - * @param {PublicKeyInitData} signatureSet - address to account of verified signatures - * @param {web3.ConfirmOptions} [options] - Solana confirmation options - */ -export async function createVerifySignaturesInstructions( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA, - signatureSet: PublicKeyInitData, - commitment?: Commitment, -): Promise { - const guardianSetIndex = vaa.guardianSet; - const guardianSetData = await getGuardianSet( - connection, - wormholeProgramId, - guardianSetIndex, - commitment, - ); - - const guardianSignatures = vaa.signatures; - const guardianKeys = guardianSetData.keys; - - const batchSize = 7; - const instructions: TransactionInstruction[] = []; - for (let i = 0; i < Math.ceil(guardianSignatures.length / batchSize); ++i) { - const start = i * batchSize; - const end = Math.min(guardianSignatures.length, (i + 1) * batchSize); - - const signatureStatus = new Array(MAX_LEN_GUARDIAN_KEYS).fill(-1); - const signatures: Buffer[] = []; - const keys: Buffer[] = []; - for (let j = 0; j < end - start; ++j) { - const item = guardianSignatures.at(j + start)!; - - signatures.push(Buffer.from(item.signature.encode())); - keys.push(guardianKeys.at(item.guardianIndex)!); - - signatureStatus[item.guardianIndex] = j; - } - - instructions.push( - createSecp256k1Instruction(signatures, keys, Buffer.from(vaa.hash)), - ); - - instructions.push( - createVerifySignaturesInstruction( - connection, - wormholeProgramId, - payer, - vaa, - signatureSet, - signatureStatus, - ), - ); - } - return instructions; -} - -/** - * Make {@link TransactionInstruction} for `verify_signatures` instruction. - * - * This is used in {@link createVerifySignaturesInstructions} for each batch of signatures being verified. - * `signatureSet` is a {@link @solana/web3.Keypair} generated outside of this method, used - * for writing signatures and the message hash to. - * - * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/api/verify_signature.rs - * - * @param {PublicKeyInitData} wormholeProgramId - wormhole program address - * @param {PublicKeyInitData} payer - transaction signer address - * @param {SignedVaa | ParsedVaa} vaa - either signed VAA (Buffer) or parsed VAA - * @param {PublicKeyInitData} signatureSet - key for signature set account - * @param {Buffer} signatureStatus - array of guardian indices - * - */ -function createVerifySignaturesInstruction( - connection: Connection, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA, - signatureSet: PublicKeyInitData, - signatureStatus: number[], -): TransactionInstruction { - const methods = createReadOnlyWormholeProgramInterface( - wormholeProgramId, - connection, - ).methods.verifySignatures(signatureStatus); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getVerifySignatureAccounts( - wormholeProgramId, - payer, - signatureSet, - vaa, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface VerifySignatureAccounts { - payer: PublicKey; - guardianSet: PublicKey; - signatureSet: PublicKey; - instructions: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function getVerifySignatureAccounts( - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - signatureSet: PublicKeyInitData, - vaa: VAA, -): VerifySignatureAccounts { - return { - payer: new PublicKey(payer), - guardianSet: deriveGuardianSetKey(wormholeProgramId, vaa.guardianSet), - signatureSet: new PublicKey(signatureSet), - instructions: SYSVAR_INSTRUCTIONS_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/core/src/utils/program.ts b/platforms/solana/protocols/core/src/utils/program.ts deleted file mode 100644 index 2526d7d0a..000000000 --- a/platforms/solana/protocols/core/src/utils/program.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Connection, PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { Provider } from '@coral-xyz/anchor'; -import { Program } from '@coral-xyz/anchor'; -import { utils } from '@wormhole-foundation/sdk-solana'; -import { WormholeCoder } from './coder/index.js'; -import { type Wormhole, IDL } from '../types.js'; - -export function createWormholeProgramInterface( - programId: PublicKeyInitData, - provider?: Provider, -): Program { - return new Program( - IDL as Wormhole, - new PublicKey(programId), - provider === undefined ? ({ connection: null } as any) : provider, - coder(), - ); -} - -export function createReadOnlyWormholeProgramInterface( - programId: PublicKeyInitData, - connection?: Connection, -): Program { - return createWormholeProgramInterface( - programId, - utils.createReadOnlyProvider(connection), - ); -} - -export function coder(): WormholeCoder { - return new WormholeCoder(IDL as Wormhole); -} diff --git a/platforms/solana/protocols/tokenBridge/package.json b/platforms/solana/protocols/tokenBridge/package.json index c4c9a61d8..0ef64e745 100644 --- a/platforms/solana/protocols/tokenBridge/package.json +++ b/platforms/solana/protocols/tokenBridge/package.json @@ -48,7 +48,7 @@ "@wormhole-foundation/sdk-connect": "1.0.3", "@wormhole-foundation/sdk-solana": "1.0.3", "@wormhole-foundation/sdk-solana-core": "1.0.3", - "@coral-xyz/anchor": "0.29.0", + "@coral-xyz/anchor": "0.30.1", "@solana/spl-token": "0.3.9", "@solana/web3.js": "^1.95.8" }, @@ -75,4 +75,4 @@ } } } -} \ No newline at end of file +} diff --git a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts index 699d8ac97..df78937fb 100644 --- a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridge.ts @@ -3,18 +3,13 @@ import type { AutomaticTokenBridge, Chain, ChainAddress, - ChainId, ChainsConfig, Contracts, Network, Platform, TokenAddress, } from '@wormhole-foundation/sdk-connect'; -import { - isNative, - toChainId, - toNative, -} from '@wormhole-foundation/sdk-connect'; +import { isNative, toNative } from '@wormhole-foundation/sdk-connect'; import type { SolanaChains, SolanaTransaction, @@ -24,26 +19,13 @@ import { SolanaPlatform, SolanaUnsignedTransaction, } from '@wormhole-foundation/sdk-solana'; - -import type { Program } from '@coral-xyz/anchor'; +import { Program } from '@coral-xyz/anchor'; +import { TOKEN_BRIDGE_RELAYER_IDL } from './automaticTokenBridgeType.js'; import type { Connection } from '@solana/web3.js'; import { PublicKey, Transaction } from '@solana/web3.js'; import type { TokenBridgeRelayer as TokenBridgeRelayerContract } from './automaticTokenBridgeType.js'; -import type { - ForeignContract, - RedeemerConfig, - RegisteredToken, -} from './utils/automaticTokenBridge/index.js'; -import { - createTokenBridgeRelayerProgramInterface, - createTransferNativeTokensWithRelayInstruction, - createTransferWrappedTokensWithRelayInstruction, - deriveForeignContractAddress, - deriveRedeemerConfigAddress, - deriveRegisteredTokenAddress, -} from './utils/automaticTokenBridge/index.js'; import { NATIVE_MINT, @@ -66,8 +48,6 @@ export class SolanaAutomaticTokenBridge< C extends SolanaChains, > implements AutomaticTokenBridge { - readonly chainId: ChainId; - readonly coreBridgeProgramId: PublicKey; readonly tokenBridgeProgramId: PublicKey; readonly tokenBridgeRelayer: Program; @@ -78,22 +58,21 @@ export class SolanaAutomaticTokenBridge< readonly connection: Connection, readonly contracts: Contracts, ) { - this.chainId = toChainId(chain); - - const tokenBridgeRelayerAddress = contracts.tokenBridgeRelayer; - if (!tokenBridgeRelayerAddress) + if (contracts.tokenBridgeRelayer === undefined) { throw new Error( `TokenBridge contract Address for chain ${chain} not found`, ); + } - this.tokenBridgeRelayer = createTokenBridgeRelayerProgramInterface( - tokenBridgeRelayerAddress, - connection, + this.tokenBridgeRelayer = new Program( + { ...TOKEN_BRIDGE_RELAYER_IDL, address: contracts.tokenBridgeRelayer }, + { connection }, ); this.tokenBridgeProgramId = new PublicKey(contracts.tokenBridge!); this.coreBridgeProgramId = new PublicKey(contracts.coreBridge!); } + static async fromRpc( connection: Connection, config: ChainsConfig, diff --git a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridgeType.ts b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridgeType.ts index 2e5716543..879c6c3ea 100644 --- a/platforms/solana/protocols/tokenBridge/src/automaticTokenBridgeType.ts +++ b/platforms/solana/protocols/tokenBridge/src/automaticTokenBridgeType.ts @@ -1,6 +1,10 @@ export type TokenBridgeRelayer = { - version: '0.1.0'; - name: 'token_bridge_relayer'; + address: string; + metadata: { + name: 'token_bridge_relayer'; + version: '0.1.0'; + spec: '0.1.0'; + }; constants: [ { name: 'SEED_PREFIX_BRIDGED'; @@ -21,6 +25,7 @@ export type TokenBridgeRelayer = { instructions: [ { name: 'initialize'; + discriminator: [0]; docs: [ "This instruction is be used to generate your program's config.", 'And for convenience, we will store Wormhole-related PDAs in the', @@ -116,16 +121,17 @@ export type TokenBridgeRelayer = { args: [ { name: 'feeRecipient'; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'assistant'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }, { name: 'registerForeignContract'; + discriminator: [1]; docs: [ 'This instruction registers a new foreign contract (from another', 'network) and saves the emitter information in a ForeignEmitter account.', @@ -212,6 +218,7 @@ export type TokenBridgeRelayer = { }, { name: 'registerToken'; + discriminator: [2]; docs: [ 'This instruction registers a new token and saves the initial `swap_rate`', 'and `max_native_token_amount` in a RegisteredToken account.', @@ -292,6 +299,7 @@ export type TokenBridgeRelayer = { }, { name: 'deregisterToken'; + discriminator: [3]; docs: [ 'This instruction deregisters a token by closing the existing', '`RegisteredToken` account for a particular mint. This instruction is', @@ -341,6 +349,7 @@ export type TokenBridgeRelayer = { }, { name: 'updateRelayerFee'; + discriminator: [4]; docs: [ 'This instruction updates the `relayer_fee` in the `ForeignContract` account.', 'The `relayer_fee` is scaled by the `relayer_fee_precision`. For example,', @@ -403,6 +412,7 @@ export type TokenBridgeRelayer = { }, { name: 'updateRelayerFeePrecision'; + discriminator: [5]; docs: [ 'This instruction updates the `relayer_fee_precision` in the', '`SenderConfig` and `RedeemerConfig` accounts. The `relayer_fee_precision`', @@ -455,6 +465,7 @@ export type TokenBridgeRelayer = { }, { name: 'updateSwapRate'; + discriminator: [6]; docs: [ 'This instruction updates the `swap_rate` in the `RegisteredToken`', 'account. This instruction can only be called by the owner or', @@ -512,6 +523,7 @@ export type TokenBridgeRelayer = { }, { name: 'updateMaxNativeSwapAmount'; + discriminator: [7]; docs: [ 'This instruction updates the `max_native_swap_amount` in the', '`RegisteredToken` account. This instruction is owner-only,', @@ -574,6 +586,7 @@ export type TokenBridgeRelayer = { }, { name: 'setPauseForTransfers'; + discriminator: [8]; docs: [ 'This instruction updates the `paused` boolean in the `SenderConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -611,6 +624,7 @@ export type TokenBridgeRelayer = { }, { name: 'submitOwnershipTransferRequest'; + discriminator: [9]; docs: [ 'This instruction sets the `pending_owner` field in the `OwnerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -642,12 +656,13 @@ export type TokenBridgeRelayer = { args: [ { name: 'newOwner'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }, { name: 'confirmOwnershipTransferRequest'; + discriminator: [10]; docs: [ 'This instruction confirms that the `pending_owner` is the signer of', 'the transaction and updates the `owner` field in the `SenderConfig`,', @@ -697,6 +712,7 @@ export type TokenBridgeRelayer = { }, { name: 'cancelOwnershipTransferRequest'; + discriminator: [11]; docs: [ 'This instruction cancels the ownership transfer request by setting', 'the `pending_owner` field in the `OwnerConfig` account to `None`.', @@ -725,6 +741,7 @@ export type TokenBridgeRelayer = { }, { name: 'updateAssistant'; + discriminator: [12]; docs: [ 'This instruction updates the `assistant` field in the `OwnerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -756,12 +773,13 @@ export type TokenBridgeRelayer = { args: [ { name: 'newAssistant'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }, { name: 'updateFeeRecipient'; + discriminator: [13]; docs: [ 'This instruction updates the `fee_recipient` field in the `RedeemerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -795,12 +813,13 @@ export type TokenBridgeRelayer = { args: [ { name: 'newFeeRecipient'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }, { name: 'transferNativeTokensWithRelay'; + discriminator: [14]; docs: [ 'This instruction is used to transfer native tokens from Solana to a', 'foreign blockchain. The user can optionally specify a', @@ -1006,6 +1025,7 @@ export type TokenBridgeRelayer = { }, { name: 'transferWrappedTokensWithRelay'; + discriminator: [15]; docs: [ 'This instruction is used to transfer wrapped tokens from Solana to a', 'foreign blockchain. The user can optionally specify a', @@ -1197,6 +1217,7 @@ export type TokenBridgeRelayer = { }, { name: 'completeNativeTransferWithRelay'; + discriminator: [16]; docs: [ 'This instruction is used to redeem token transfers from foreign emitters.', 'It takes custody of the released native tokens and sends the tokens to the', @@ -1387,6 +1408,7 @@ export type TokenBridgeRelayer = { }, { name: 'completeWrappedTransferWithRelay'; + discriminator: [17]; docs: [ 'This instruction is used to redeem token transfers from foreign emitters.', 'It takes custody of the minted wrapped tokens and sends the tokens to the', @@ -1582,6 +1604,7 @@ export type TokenBridgeRelayer = { accounts: [ { name: 'foreignContract'; + discriminator: []; docs: ['Foreign emitter account data.']; type: { kind: 'struct'; @@ -1601,7 +1624,7 @@ export type TokenBridgeRelayer = { { name: 'tokenBridgeForeignEndpoint'; docs: ["Token Bridge program's foreign endpoint account key."]; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'fee'; @@ -1618,6 +1641,7 @@ export type TokenBridgeRelayer = { }, { name: 'ownerConfig'; + discriminator: []; docs: ['Owner account data.']; type: { kind: 'struct'; @@ -1625,14 +1649,14 @@ export type TokenBridgeRelayer = { { name: 'owner'; docs: ["Program's owner."]; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'assistant'; docs: [ "Program's assistant. Can be used to update the relayer fee and swap rate.", ]; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'pendingOwner'; @@ -1640,7 +1664,7 @@ export type TokenBridgeRelayer = { 'Intermediate storage for the pending owner. Is used to transfer ownership.', ]; type: { - option: 'publicKey'; + option: 'pubkey'; }; }, ]; @@ -1648,13 +1672,14 @@ export type TokenBridgeRelayer = { }, { name: 'redeemerConfig'; + discriminator: []; type: { kind: 'struct'; fields: [ { name: 'owner'; docs: ["Program's owner."]; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'bump'; @@ -1669,13 +1694,14 @@ export type TokenBridgeRelayer = { { name: 'feeRecipient'; docs: ['Recipient of all relayer fees and swap proceeds.']; - type: 'publicKey'; + type: 'pubkey'; }, ]; }; }, { name: 'registeredToken'; + discriminator: []; docs: ['Registered token account data.']; type: { kind: 'struct'; @@ -1699,13 +1725,14 @@ export type TokenBridgeRelayer = { }, { name: 'senderConfig'; + discriminator: []; type: { kind: 'struct'; fields: [ { name: 'owner'; docs: ["Program's owner."]; - type: 'publicKey'; + type: 'pubkey'; }, { name: 'bump'; @@ -1734,6 +1761,7 @@ export type TokenBridgeRelayer = { }, { name: 'signerSequence'; + discriminator: []; type: { kind: 'struct'; fields: [ @@ -1753,7 +1781,7 @@ export type TokenBridgeRelayer = { fields: [ { name: 'sequence'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }; @@ -1982,8 +2010,12 @@ export type TokenBridgeRelayer = { }; export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { - version: '0.1.0', - name: 'token_bridge_relayer', + address: '', + metadata: { + name: 'token_bridge_relayer', + version: '0.1.0', + spec: '0.1.0', + }, constants: [ { name: 'SEED_PREFIX_BRIDGED', @@ -2004,6 +2036,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { instructions: [ { name: 'initialize', + discriminator: [0], docs: [ "This instruction is be used to generate your program's config.", 'And for convenience, we will store Wormhole-related PDAs in the', @@ -2099,16 +2132,17 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { args: [ { name: 'feeRecipient', - type: 'publicKey', + type: 'pubkey', }, { name: 'assistant', - type: 'publicKey', + type: 'pubkey', }, ], }, { name: 'registerForeignContract', + discriminator: [1], docs: [ 'This instruction registers a new foreign contract (from another', 'network) and saves the emitter information in a ForeignEmitter account.', @@ -2195,6 +2229,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'registerToken', + discriminator: [2], docs: [ 'This instruction registers a new token and saves the initial `swap_rate`', 'and `max_native_token_amount` in a RegisteredToken account.', @@ -2275,6 +2310,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'deregisterToken', + discriminator: [3], docs: [ 'This instruction deregisters a token by closing the existing', '`RegisteredToken` account for a particular mint. This instruction is', @@ -2324,6 +2360,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'updateRelayerFee', + discriminator: [4], docs: [ 'This instruction updates the `relayer_fee` in the `ForeignContract` account.', 'The `relayer_fee` is scaled by the `relayer_fee_precision`. For example,', @@ -2386,6 +2423,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'updateRelayerFeePrecision', + discriminator: [5], docs: [ 'This instruction updates the `relayer_fee_precision` in the', '`SenderConfig` and `RedeemerConfig` accounts. The `relayer_fee_precision`', @@ -2438,6 +2476,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'updateSwapRate', + discriminator: [6], docs: [ 'This instruction updates the `swap_rate` in the `RegisteredToken`', 'account. This instruction can only be called by the owner or', @@ -2495,6 +2534,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'updateMaxNativeSwapAmount', + discriminator: [7], docs: [ 'This instruction updates the `max_native_swap_amount` in the', '`RegisteredToken` account. This instruction is owner-only,', @@ -2557,6 +2597,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'setPauseForTransfers', + discriminator: [8], docs: [ 'This instruction updates the `paused` boolean in the `SenderConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -2594,6 +2635,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'submitOwnershipTransferRequest', + discriminator: [9], docs: [ 'This instruction sets the `pending_owner` field in the `OwnerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -2625,12 +2667,13 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { args: [ { name: 'newOwner', - type: 'publicKey', + type: 'pubkey', }, ], }, { name: 'confirmOwnershipTransferRequest', + discriminator: [10], docs: [ 'This instruction confirms that the `pending_owner` is the signer of', 'the transaction and updates the `owner` field in the `SenderConfig`,', @@ -2680,6 +2723,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'cancelOwnershipTransferRequest', + discriminator: [11], docs: [ 'This instruction cancels the ownership transfer request by setting', 'the `pending_owner` field in the `OwnerConfig` account to `None`.', @@ -2708,6 +2752,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'updateAssistant', + discriminator: [12], docs: [ 'This instruction updates the `assistant` field in the `OwnerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -2739,12 +2784,13 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { args: [ { name: 'newAssistant', - type: 'publicKey', + type: 'pubkey', }, ], }, { name: 'updateFeeRecipient', + discriminator: [13], docs: [ 'This instruction updates the `fee_recipient` field in the `RedeemerConfig`', 'account. This instruction is owner-only, meaning that only the owner', @@ -2778,12 +2824,13 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { args: [ { name: 'newFeeRecipient', - type: 'publicKey', + type: 'pubkey', }, ], }, { name: 'transferNativeTokensWithRelay', + discriminator: [14], docs: [ 'This instruction is used to transfer native tokens from Solana to a', 'foreign blockchain. The user can optionally specify a', @@ -2989,6 +3036,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'transferWrappedTokensWithRelay', + discriminator: [15], docs: [ 'This instruction is used to transfer wrapped tokens from Solana to a', 'foreign blockchain. The user can optionally specify a', @@ -3180,6 +3228,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'completeNativeTransferWithRelay', + discriminator: [16], docs: [ 'This instruction is used to redeem token transfers from foreign emitters.', 'It takes custody of the released native tokens and sends the tokens to the', @@ -3370,6 +3419,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'completeWrappedTransferWithRelay', + discriminator: [17], docs: [ 'This instruction is used to redeem token transfers from foreign emitters.', 'It takes custody of the minted wrapped tokens and sends the tokens to the', @@ -3565,6 +3615,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { accounts: [ { name: 'foreignContract', + discriminator: [], docs: ['Foreign emitter account data.'], type: { kind: 'struct', @@ -3584,7 +3635,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { { name: 'tokenBridgeForeignEndpoint', docs: ["Token Bridge program's foreign endpoint account key."], - type: 'publicKey', + type: 'pubkey', }, { name: 'fee', @@ -3601,6 +3652,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'ownerConfig', + discriminator: [], docs: ['Owner account data.'], type: { kind: 'struct', @@ -3608,14 +3660,14 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { { name: 'owner', docs: ["Program's owner."], - type: 'publicKey', + type: 'pubkey', }, { name: 'assistant', docs: [ "Program's assistant. Can be used to update the relayer fee and swap rate.", ], - type: 'publicKey', + type: 'pubkey', }, { name: 'pendingOwner', @@ -3623,7 +3675,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { 'Intermediate storage for the pending owner. Is used to transfer ownership.', ], type: { - option: 'publicKey', + option: 'pubkey', }, }, ], @@ -3631,13 +3683,14 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'redeemerConfig', + discriminator: [], type: { kind: 'struct', fields: [ { name: 'owner', docs: ["Program's owner."], - type: 'publicKey', + type: 'pubkey', }, { name: 'bump', @@ -3652,13 +3705,14 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { { name: 'feeRecipient', docs: ['Recipient of all relayer fees and swap proceeds.'], - type: 'publicKey', + type: 'pubkey', }, ], }, }, { name: 'registeredToken', + discriminator: [], docs: ['Registered token account data.'], type: { kind: 'struct', @@ -3682,13 +3736,14 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'senderConfig', + discriminator: [], type: { kind: 'struct', fields: [ { name: 'owner', docs: ["Program's owner."], - type: 'publicKey', + type: 'pubkey', }, { name: 'bump', @@ -3717,6 +3772,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { }, { name: 'signerSequence', + discriminator: [], type: { kind: 'struct', fields: [ @@ -3736,7 +3792,7 @@ export const TOKEN_BRIDGE_RELAYER_IDL: TokenBridgeRelayer = { fields: [ { name: 'sequence', - type: 'publicKey', + type: 'pubkey', }, ], }, diff --git a/platforms/solana/protocols/tokenBridge/src/index.ts b/platforms/solana/protocols/tokenBridge/src/index.ts index 34b2b9211..9ded4496d 100644 --- a/platforms/solana/protocols/tokenBridge/src/index.ts +++ b/platforms/solana/protocols/tokenBridge/src/index.ts @@ -8,6 +8,5 @@ registerProtocol(_platform, 'AutomaticTokenBridge', SolanaAutomaticTokenBridge); export * from './tokenBridgeType.js'; export * from './automaticTokenBridgeType.js'; -export * from './utils/index.js'; export * from './tokenBridge.js'; export * from './automaticTokenBridge.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts index a2f284ab8..2eef2dbd5 100644 --- a/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/solana/protocols/tokenBridge/src/tokenBridge.ts @@ -1,15 +1,3 @@ -import type { - Chain, - ChainAddress, - ChainId, - ChainsConfig, - Contracts, - NativeAddress, - Network, - Platform, - TokenBridge, - TokenId, -} from '@wormhole-foundation/sdk-connect'; import { ErrNotWrapped, UniversalAddress, @@ -19,22 +7,12 @@ import { toChainId, toNative, } from '@wormhole-foundation/sdk-connect'; -import type { - AnySolanaAddress, - SolanaChains, - SolanaTransaction, -} from '@wormhole-foundation/sdk-solana'; -import { - SolanaAddress, - SolanaPlatform, - SolanaUnsignedTransaction, -} from '@wormhole-foundation/sdk-solana'; import { - SolanaWormholeCore, - utils as coreUtils, -} from '@wormhole-foundation/sdk-solana-core'; - -import type { Program } from '@coral-xyz/anchor'; + Keypair, + PublicKey, + SystemProgram, + Transaction, +} from '@solana/web3.js'; import { ACCOUNT_SIZE, NATIVE_MINT, @@ -47,39 +25,47 @@ import { getMinimumBalanceForRentExemptAccount, getMint, } from '@solana/spl-token'; -import type { Connection, TransactionInstruction } from '@solana/web3.js'; +import { Program } from '@coral-xyz/anchor'; import { - Keypair, - PublicKey, - SystemProgram, - Transaction, -} from '@solana/web3.js'; - -import type { TokenBridge as TokenBridgeContract } from './tokenBridgeType.js'; + SolanaAddress, + SolanaPlatform, + SolanaUnsignedTransaction, +} from '@wormhole-foundation/sdk-solana'; import { - createApproveAuthoritySignerInstruction, - createAttestTokenInstruction, - createCompleteTransferNativeInstruction, - createCompleteTransferWrappedInstruction, - createCreateWrappedInstruction, - createReadOnlyTokenBridgeProgramInterface, - createTransferNativeInstruction, - createTransferNativeWithPayloadInstruction, - createTransferWrappedInstruction, - createTransferWrappedWithPayloadInstruction, - deriveWrappedMintKey, - getWrappedMeta, -} from './utils/index.js'; + SolanaWormholeCore, + utils as coreUtils, +} from '@wormhole-foundation/sdk-solana-core'; +import { TOKEN_BRIDGE_IDL } from './tokenBridgeType.js'; +import type { + Chain, + ChainAddress, + ChainId, + ChainsConfig, + Contracts, + NativeAddress, + Network, + Platform, + TokenBridge, + TokenId, +} from '@wormhole-foundation/sdk-connect'; +import type { Connection, TransactionInstruction } from '@solana/web3.js'; +import type { + AnySolanaAddress, + SolanaChains, + SolanaTransaction, +} from '@wormhole-foundation/sdk-solana'; +import type { TokenBridge as TokenBridgeContract } from './tokenBridgeType.js'; import '@wormhole-foundation/sdk-solana-core'; export class SolanaTokenBridge implements TokenBridge { - readonly chainId: ChainId; - readonly coreBridge: SolanaWormholeCore; readonly tokenBridge: Program; + get address() { + return this.tokenBridge.programId; + } constructor( readonly network: N, @@ -87,17 +73,14 @@ export class SolanaTokenBridge readonly connection: Connection, readonly contracts: Contracts, ) { - this.chainId = toChainId(chain); - - const tokenBridgeAddress = contracts.tokenBridge; - if (!tokenBridgeAddress) + if (contracts.tokenBridge === undefined) { throw new Error( `TokenBridge contract Address for chain ${chain} not found`, ); - - this.tokenBridge = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeAddress, - connection, + } + this.tokenBridge = new Program( + { ...TOKEN_BRIDGE_IDL, address: contracts.tokenBridge }, + { connection }, ); this.coreBridge = new SolanaWormholeCore( diff --git a/platforms/solana/protocols/tokenBridge/src/tokenBridgeType.ts b/platforms/solana/protocols/tokenBridge/src/tokenBridgeType.ts index c945be00b..311d52259 100644 --- a/platforms/solana/protocols/tokenBridge/src/tokenBridgeType.ts +++ b/platforms/solana/protocols/tokenBridge/src/tokenBridgeType.ts @@ -1,9 +1,14 @@ export type TokenBridge = { - version: '0.1.0'; - name: 'wormhole'; + address: string; + metadata: { + name: 'wormhole'; + version: '0.1.0'; + spec: '0.1.0'; + }; instructions: [ { name: 'initialize'; + discriminator: [0]; accounts: [ { name: 'payer'; @@ -29,12 +34,13 @@ export type TokenBridge = { args: [ { name: 'wormhole'; - type: 'publicKey'; + type: 'pubkey'; }, ]; }, { name: 'attestToken'; + discriminator: [1]; accounts: [ { name: 'payer'; @@ -116,6 +122,7 @@ export type TokenBridge = { }, { name: 'completeNative'; + discriminator: [2]; accounts: [ { name: 'payer'; @@ -192,6 +199,7 @@ export type TokenBridge = { }, { name: 'completeWrapped'; + discriminator: [3]; accounts: [ { name: 'payer'; @@ -268,6 +276,7 @@ export type TokenBridge = { }, { name: 'transferWrapped'; + discriminator: [4]; accounts: [ { name: 'payer'; @@ -382,6 +391,7 @@ export type TokenBridge = { }, { name: 'transferNative'; + discriminator: [5]; accounts: [ { name: 'payer'; @@ -496,6 +506,7 @@ export type TokenBridge = { }, { name: 'registerChain'; + discriminator: [6]; accounts: [ { name: 'payer'; @@ -542,6 +553,7 @@ export type TokenBridge = { }, { name: 'createWrapped'; + discriminator: [7]; accounts: [ { name: 'payer'; @@ -618,6 +630,7 @@ export type TokenBridge = { }, { name: 'upgradeContract'; + discriminator: [8]; accounts: [ { name: 'payer'; @@ -684,6 +697,7 @@ export type TokenBridge = { }, { name: 'transferWrappedWithPayload'; + discriminator: [9]; accounts: [ { name: 'payer'; @@ -802,13 +816,14 @@ export type TokenBridge = { { name: 'cpiProgramId'; type: { - option: 'publicKey'; + option: 'pubkey'; }; }, ]; }, { name: 'transferNativeWithPayload'; + discriminator: [10]; accounts: [ { name: 'payer'; @@ -927,7 +942,7 @@ export type TokenBridge = { { name: 'cpiProgramId'; type: { - option: 'publicKey'; + option: 'pubkey'; }; }, ]; @@ -937,11 +952,16 @@ export type TokenBridge = { }; export const TOKEN_BRIDGE_IDL: TokenBridge = { - version: '0.1.0', - name: 'wormhole', + address: '', + metadata: { + name: 'wormhole', + version: '0.1.0', + spec: '0.1.0', + }, instructions: [ { name: 'initialize', + discriminator: [0], accounts: [ { name: 'payer', @@ -967,12 +987,13 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { args: [ { name: 'wormhole', - type: 'publicKey', + type: 'pubkey', }, ], }, { name: 'attestToken', + discriminator: [1], accounts: [ { name: 'payer', @@ -1054,6 +1075,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'completeNative', + discriminator: [2], accounts: [ { name: 'payer', @@ -1130,6 +1152,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'completeWrapped', + discriminator: [3], accounts: [ { name: 'payer', @@ -1206,6 +1229,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'transferWrapped', + discriminator: [4], accounts: [ { name: 'payer', @@ -1320,6 +1344,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'transferNative', + discriminator: [5], accounts: [ { name: 'payer', @@ -1434,6 +1459,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'registerChain', + discriminator: [6], accounts: [ { name: 'payer', @@ -1480,6 +1506,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'createWrapped', + discriminator: [7], accounts: [ { name: 'payer', @@ -1556,6 +1583,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'upgradeContract', + discriminator: [8], accounts: [ { name: 'payer', @@ -1622,6 +1650,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { }, { name: 'transferWrappedWithPayload', + discriminator: [9], accounts: [ { name: 'payer', @@ -1740,13 +1769,14 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { { name: 'cpiProgramId', type: { - option: 'publicKey', + option: 'pubkey', }, }, ], }, { name: 'transferNativeWithPayload', + discriminator: [10], accounts: [ { name: 'payer', @@ -1865,7 +1895,7 @@ export const TOKEN_BRIDGE_IDL: TokenBridge = { { name: 'cpiProgramId', type: { - option: 'publicKey', + option: 'pubkey', }, }, ], diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/foreignContract.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/foreignContract.ts deleted file mode 100644 index b75b6fc01..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/foreignContract.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import type { BN } from '@coral-xyz/anchor'; -import type { Chain } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -export interface ForeignContract { - chain: number; - address: number[]; - fee: BN; -} - -export function deriveForeignContractAddress( - programId: PublicKeyInitData, - chainId: Chain, -): PublicKey { - const chainIdBuf = Buffer.alloc(2); - chainIdBuf.writeUInt16BE(toChainId(chainId)); - return utils.deriveAddress( - [Buffer.from('foreign_contract'), chainIdBuf], - programId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/index.ts deleted file mode 100644 index 6e4abb805..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './foreignContract.js'; -export * from './redeemerConfig.js'; -export * from './registeredToken.js'; -export * from './senderConfig.js'; -export * from './tmpTokenAccount.js'; -export * from './tokenTransferMessage.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/redeemerConfig.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/redeemerConfig.ts deleted file mode 100644 index c18ba3ac1..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/redeemerConfig.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export interface RedeemerConfig { - owner: PublicKey; - bump: number; - relayerFeePrecision: number; - feeRecipient: PublicKey; -} - -export function deriveRedeemerConfigAddress( - programId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('redeemer')], programId); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/registeredToken.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/registeredToken.ts deleted file mode 100644 index 8d730ff25..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/registeredToken.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { BN } from '@coral-xyz/anchor'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export interface RegisteredToken { - swapRate: BN; - maxNativeSwapAmount: BN; -} - -export function deriveRegisteredTokenAddress( - programId: PublicKeyInitData, - mint: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('mint'), new PublicKey(mint).toBuffer()], - programId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/senderConfig.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/senderConfig.ts deleted file mode 100644 index c3cda68b9..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/senderConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; - -export interface SenderConfig { - owner: PublicKey; - bump: number; - tokenBridge: any; - relayerFeePrecision: number; - paused: boolean; -} - -export function deriveSenderConfigAddress( - programId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('sender')], programId); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/signerSequence.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/signerSequence.ts deleted file mode 100644 index 561d1430b..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/signerSequence.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { BN } from '@coral-xyz/anchor'; - -export interface SignerSequence { - value: BN; -} - -export function deriveSignerSequenceAddress( - programId: PublicKeyInitData, - payerKey: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('seq'), new PublicKey(payerKey).toBuffer()], - programId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tmpTokenAccount.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tmpTokenAccount.ts deleted file mode 100644 index 4ce5f3825..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tmpTokenAccount.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; - -export function deriveTmpTokenAccountAddress( - programId: PublicKeyInitData, - mint: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('tmp'), new PublicKey(mint).toBuffer()], - programId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tokenTransferMessage.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tokenTransferMessage.ts deleted file mode 100644 index 3529e7f17..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/accounts/tokenTransferMessage.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { utils } from '@wormhole-foundation/sdk-solana'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { BN } from '@coral-xyz/anchor'; -import { encoding } from '@wormhole-foundation/sdk-connect'; - -export function deriveTokenTransferMessageAddress( - programId: PublicKeyInitData, - payer: PublicKeyInitData, - sequence: BN, -): PublicKey { - return utils.deriveAddress( - [ - Buffer.from('bridged'), - new PublicKey(payer).toBuffer(), - Buffer.from(encoding.bignum.toBytes(BigInt(sequence.toString()), 8)), - ], - programId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/index.ts deleted file mode 100644 index 7d75ca0a8..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './accounts/index.js'; -export * from './instructions/index.js'; -export * from './program.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/index.ts deleted file mode 100644 index cb5ce807e..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './transferNativeTokensWithRelay.js'; -export * from './transferWrappedTokensWithRelay.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferNativeTokensWithRelay.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferNativeTokensWithRelay.ts deleted file mode 100644 index 292a10b48..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferNativeTokensWithRelay.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { getTransferNativeWithPayloadCpiAccounts } from '../../tokenBridge/cpi.js'; -import { createTokenBridgeRelayerProgramInterface } from '../program.js'; -import { - deriveForeignContractAddress, - deriveSenderConfigAddress, - deriveTokenTransferMessageAddress, - deriveRegisteredTokenAddress, - deriveTmpTokenAccountAddress, -} from './../accounts/index.js'; -import { getAssociatedTokenAddressSync } from '@solana/spl-token'; -import { deriveSignerSequenceAddress } from '../accounts/signerSequence.js'; -import type { Chain } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -import BN from 'bn.js'; - -export async function createTransferNativeTokensWithRelayInstruction( - connection: Connection, - programId: PublicKeyInitData, - payer: PublicKeyInitData, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - mint: PublicKeyInitData, - amount: bigint, - toNativeTokenAmount: bigint, - recipientAddress: Uint8Array, - recipientChain: Chain, - batchId: number, - wrapNative: boolean, -): Promise { - const { - methods: { transferNativeTokensWithRelay }, - account: { signerSequence }, - } = createTokenBridgeRelayerProgramInterface(programId, connection); - const signerSequenceAddress = deriveSignerSequenceAddress(programId, payer); - const sequence = await signerSequence - .fetch(signerSequenceAddress) - .then(({ value }) => value) - .catch((e) => { - if (e.message?.includes('Account does not exist')) { - // first time transferring - return new BN(0); - } - throw e; - }); - const message = deriveTokenTransferMessageAddress(programId, payer, sequence); - const fromTokenAccount = getAssociatedTokenAddressSync( - new PublicKey(mint), - new PublicKey(payer), - ); - const tmpTokenAccount = deriveTmpTokenAccountAddress(programId, mint); - const tokenBridgeAccounts = getTransferNativeWithPayloadCpiAccounts( - programId, - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - fromTokenAccount, - mint, - ); - return transferNativeTokensWithRelay( - new BN(amount.toString()), - new BN(toNativeTokenAmount.toString()), - toChainId(recipientChain), - [...recipientAddress], - batchId, - wrapNative, - ) - .accounts({ - config: deriveSenderConfigAddress(programId), - payerSequence: signerSequenceAddress, - foreignContract: deriveForeignContractAddress(programId, recipientChain), - registeredToken: deriveRegisteredTokenAddress(programId, mint), - tmpTokenAccount, - tokenBridgeProgram: new PublicKey(tokenBridgeProgramId), - ...tokenBridgeAccounts, - }) - .instruction(); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferWrappedTokensWithRelay.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferWrappedTokensWithRelay.ts deleted file mode 100644 index 09de010e9..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/instructions/transferWrappedTokensWithRelay.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { getTransferWrappedWithPayloadCpiAccounts } from '../../tokenBridge/cpi.js'; -import { createTokenBridgeRelayerProgramInterface } from '../program.js'; -import { - deriveForeignContractAddress, - deriveSenderConfigAddress, - deriveTokenTransferMessageAddress, - deriveTmpTokenAccountAddress, - deriveRegisteredTokenAddress, -} from './../accounts/index.js'; -import { getAssociatedTokenAddressSync } from '@solana/spl-token'; -import { getWrappedMeta } from './../../tokenBridge/index.js'; -import { deriveSignerSequenceAddress } from '../accounts/signerSequence.js'; -import type { Chain } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -import BN from 'bn.js'; - -export async function createTransferWrappedTokensWithRelayInstruction( - connection: Connection, - programId: PublicKeyInitData, - payer: PublicKeyInitData, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - mint: PublicKeyInitData, - amount: bigint, - toNativeTokenAmount: bigint, - recipientAddress: Uint8Array, - recipientChain: Chain, - batchId: number, -): Promise { - const { - methods: { transferWrappedTokensWithRelay }, - account: { signerSequence }, - } = createTokenBridgeRelayerProgramInterface(programId, connection); - const signerSequenceAddress = deriveSignerSequenceAddress(programId, payer); - const sequence = await signerSequence - .fetch(signerSequenceAddress) - .then(({ value }) => value) - .catch((e) => { - if (e.message?.includes('Account does not exist')) { - // first time transferring - return new BN(0); - } - throw e; - }); - - const message = deriveTokenTransferMessageAddress(programId, payer, sequence); - const fromTokenAccount = getAssociatedTokenAddressSync( - new PublicKey(mint), - new PublicKey(payer), - ); - const { chain, tokenAddress } = await getWrappedMeta( - connection, - tokenBridgeProgramId, - mint, - ); - const tmpTokenAccount = deriveTmpTokenAccountAddress(programId, mint); - const tokenBridgeAccounts = getTransferWrappedWithPayloadCpiAccounts( - programId, - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - fromTokenAccount, - chain, - tokenAddress, - ); - - return transferWrappedTokensWithRelay( - new BN(amount.toString()), - new BN(toNativeTokenAmount.toString()), - toChainId(recipientChain), - [...recipientAddress], - batchId, - ) - .accounts({ - config: deriveSenderConfigAddress(programId), - payerSequence: signerSequenceAddress, - foreignContract: deriveForeignContractAddress(programId, recipientChain), - registeredToken: deriveRegisteredTokenAddress( - programId, - new PublicKey(mint), - ), - tmpTokenAccount, - tokenBridgeProgram: new PublicKey(tokenBridgeProgramId), - ...tokenBridgeAccounts, - }) - .instruction(); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/program.ts b/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/program.ts deleted file mode 100644 index d08bc30c6..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/automaticTokenBridge/program.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Connection, PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { Program } from '@coral-xyz/anchor'; -import { - type TokenBridgeRelayer, - TOKEN_BRIDGE_RELAYER_IDL as IDL, -} from '../../automaticTokenBridgeType.js'; - -export function createTokenBridgeRelayerProgramInterface( - programId: PublicKeyInitData, - connection: Connection, -): Program { - return new Program(IDL, new PublicKey(programId), { connection }); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/index.ts deleted file mode 100644 index f03ae45cc..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './automaticTokenBridge/index.js'; -export * from './tokenBridge/index.js'; -export * from './splMetadata.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/splMetadata.ts b/platforms/solana/protocols/tokenBridge/src/utils/splMetadata.ts deleted file mode 100644 index ab044b8e0..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/splMetadata.ts +++ /dev/null @@ -1,324 +0,0 @@ -import type { - AccountMeta, - Commitment, - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export class Creator { - address: PublicKey; - verified: boolean; - share: number; - - constructor(address: PublicKeyInitData, verified: boolean, share: number) { - this.address = new PublicKey(address); - this.verified = verified; - this.share = share; - } - - static size: number = 34; - - serialize() { - const serialized = Buffer.alloc(Creator.size); - serialized.write(this.address.toBuffer().toString('hex'), 0, 'hex'); - if (this.verified) { - serialized.writeUInt8(1, 32); - } - serialized.writeUInt8(this.share, 33); - return serialized; - } - - static deserialize(data: Buffer): Creator { - const address = data.subarray(0, 32); - const verified = data.readUInt8(32) > 0; - const share = data.readUInt8(33); - return new Creator(address, verified, share); - } -} - -export class Data { - name: string; - symbol: string; - uri: string; - sellerFeeBasisPoints: number; - creators: Creator[] | null; - - constructor( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - ) { - this.name = name; - this.symbol = symbol; - this.uri = uri; - this.sellerFeeBasisPoints = sellerFeeBasisPoints; - this.creators = creators; - } - - serialize() { - const nameLen = this.name.length; - const symbolLen = this.symbol.length; - const uriLen = this.uri.length; - const creators = this.creators; - const [creatorsLen, creatorsSize] = (() => { - if (creators === null) { - return [0, 0]; - } - - const creatorsLen = creators.length; - return [creatorsLen, 4 + creatorsLen * Creator.size]; - })(); - const serialized = Buffer.alloc( - 15 + nameLen + symbolLen + uriLen + creatorsSize, - ); - serialized.writeUInt32LE(nameLen, 0); - serialized.write(this.name, 4); - serialized.writeUInt32LE(symbolLen, 4 + nameLen); - serialized.write(this.symbol, 8 + nameLen); - serialized.writeUInt32LE(uriLen, 8 + nameLen + symbolLen); - serialized.write(this.uri, 12 + nameLen + symbolLen); - serialized.writeUInt16LE( - this.sellerFeeBasisPoints, - 12 + nameLen + symbolLen + uriLen, - ); - if (creators === null) { - serialized.writeUInt8(0, 14 + nameLen + symbolLen + uriLen); - } else { - serialized.writeUInt8(1, 14 + nameLen + symbolLen + uriLen); - serialized.writeUInt32LE(creatorsLen, 15 + nameLen + symbolLen + uriLen); - for (let i = 0; i < creatorsLen; ++i) { - const creator = creators.at(i)!; - const idx = 19 + nameLen + symbolLen + uriLen + i * Creator.size; - serialized.write(creator.serialize().toString('hex'), idx, 'hex'); - } - } - return serialized; - } - - static deserialize(data: Buffer): Data { - const nameLen = data.readUInt32LE(0); - const name = data.subarray(4, 4 + nameLen).toString(); - const symbolLen = data.readUInt32LE(4 + nameLen); - const symbol = data - .subarray(8 + nameLen, 8 + nameLen + symbolLen) - .toString(); - const uriLen = data.readUInt32LE(8 + nameLen + symbolLen); - const uri = data - .subarray(12 + nameLen + symbolLen, 12 + nameLen + symbolLen + uriLen) - .toString(); - const sellerFeeBasisPoints = data.readUInt16LE( - 12 + nameLen + symbolLen + uriLen, - ); - const optionCreators = data.readUInt8(14 + nameLen + symbolLen + uriLen); - const creators = (() => { - if (optionCreators == 0) { - return null; - } - - const creators: Creator[] = []; - const creatorsLen = data.readUInt32LE(15 + nameLen + symbolLen + uriLen); - for (let i = 0; i < creatorsLen; ++i) { - const idx = 19 + nameLen + symbolLen + uriLen + i * Creator.size; - creators.push( - Creator.deserialize(data.subarray(idx, idx + Creator.size)), - ); - } - return creators; - })(); - return new Data(name, symbol, uri, sellerFeeBasisPoints, creators); - } -} - -export class CreateMetadataAccountArgs extends Data { - isMutable: boolean; - - constructor( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean, - ) { - super(name, symbol, uri, sellerFeeBasisPoints, creators); - this.isMutable = isMutable; - } - - static serialize( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean, - ) { - return new CreateMetadataAccountArgs( - name, - symbol, - uri, - sellerFeeBasisPoints, - creators, - isMutable, - ).serialize(); - } - - static serializeInstructionData( - name: string, - symbol: string, - uri: string, - sellerFeeBasisPoints: number, - creators: Creator[] | null, - isMutable: boolean, - ) { - return Buffer.concat([ - Buffer.alloc(1, 0), - CreateMetadataAccountArgs.serialize( - name, - symbol, - uri, - sellerFeeBasisPoints, - creators, - isMutable, - ), - ]); - } - - override serialize() { - return Buffer.concat([ - super.serialize(), - Buffer.alloc(1, this.isMutable ? 1 : 0), - ]); - } -} - -export class SplTokenMetadataProgram { - /** - * @internal - */ - constructor() {} - - /** - * Public key that identifies the SPL Token Metadata program - */ - static programId: PublicKey = new PublicKey( - 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s', - ); - - static createMetadataAccounts( - payer: PublicKey, - mint: PublicKey, - mintAuthority: PublicKey, - name: string, - symbol: string, - updateAuthority: PublicKey, - updateAuthorityIsSigner: boolean = false, - uri?: string, - creators?: Creator[] | null, - sellerFeeBasisPoints?: number, - isMutable: boolean = false, - metadataAccount: PublicKey = deriveSplTokenMetadataKey(mint), - ): TransactionInstruction { - const keys: AccountMeta[] = [ - utils.newAccountMeta(metadataAccount, false), - utils.newReadOnlyAccountMeta(mint, false), - utils.newReadOnlyAccountMeta(mintAuthority, true), - utils.newReadOnlyAccountMeta(payer, true), - utils.newReadOnlyAccountMeta(updateAuthority, updateAuthorityIsSigner), - utils.newReadOnlyAccountMeta(SystemProgram.programId, false), - utils.newReadOnlyAccountMeta(SYSVAR_RENT_PUBKEY, false), - ]; - const data = CreateMetadataAccountArgs.serializeInstructionData( - name, - symbol, - uri === undefined ? '' : uri, - sellerFeeBasisPoints === undefined ? 0 : sellerFeeBasisPoints, - creators === undefined ? null : creators, - isMutable, - ); - return { - programId: SplTokenMetadataProgram.programId, - keys, - data, - }; - } -} - -export function deriveSplTokenMetadataKey(mint: PublicKeyInitData): PublicKey { - return utils.deriveAddress( - [ - Buffer.from('metadata'), - SplTokenMetadataProgram.programId.toBuffer(), - new PublicKey(mint).toBuffer(), - ], - SplTokenMetadataProgram.programId, - ); -} - -export enum Key { - Uninitialized, - EditionV1, - MasterEditionV1, - ReservationListV1, - MetadataV1, - ReservationListV2, - MasterEditionV2, - EditionMarker, -} - -export class Metadata { - key: Key; - updateAuthority: PublicKey; - mint: PublicKey; - data: Data; - primarySaleHappened: boolean; - isMutable: boolean; - - constructor( - key: number, - updateAuthority: PublicKeyInitData, - mint: PublicKeyInitData, - data: Data, - primarySaleHappened: boolean, - isMutable: boolean, - ) { - this.key = key as Key; - this.updateAuthority = new PublicKey(updateAuthority); - this.mint = new PublicKey(mint); - this.data = data; - this.primarySaleHappened = primarySaleHappened; - this.isMutable = isMutable; - } - - static deserialize(data: Buffer): Metadata { - const key = data.readUInt8(0); - const updateAuthority = data.subarray(1, 33); - const mint = data.subarray(33, 65); - const meta = Data.deserialize(data.subarray(65)); - const metaLen = meta.serialize().length; - const primarySaleHappened = data.readUInt8(65 + metaLen) > 0; - const isMutable = data.readUInt8(66 + metaLen) > 0; - return new Metadata( - key, - updateAuthority, - mint, - meta, - primarySaleHappened, - isMutable, - ); - } -} - -export async function getMetadata( - connection: Connection, - mint: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo(deriveSplTokenMetadataKey(mint), commitment) - .then((info) => Metadata.deserialize(utils.getAccountData(info))); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/config.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/config.ts deleted file mode 100644 index 8ceb3f126..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - Connection, - Commitment, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveTokenBridgeConfigKey( - tokenBridgeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('config')], tokenBridgeProgramId); -} - -export async function getTokenBridgeConfig( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo( - deriveTokenBridgeConfigKey(tokenBridgeProgramId), - commitment, - ) - .then((info) => TokenBridgeConfig.deserialize(utils.getAccountData(info))); -} - -export class TokenBridgeConfig { - wormhole: PublicKey; - - constructor(wormholeProgramId: Buffer) { - this.wormhole = new PublicKey(wormholeProgramId); - } - - static deserialize(data: Buffer): TokenBridgeConfig { - if (data.length != 32) { - throw new Error('data.length != 32'); - } - const wormholeProgramId = data.subarray(0, 32); - return new TokenBridgeConfig(wormholeProgramId); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/custody.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/custody.ts deleted file mode 100644 index b0d8ec0f8..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/custody.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveCustodyKey( - tokenBridgeProgramId: PublicKeyInitData, - mint: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [new PublicKey(mint).toBuffer()], - tokenBridgeProgramId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/endpoint.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/endpoint.ts deleted file mode 100644 index 6c5a3c37d..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/endpoint.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { - Commitment, - Connection, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { ChainId } from '@wormhole-foundation/sdk-connect'; -import { UniversalAddress, toChainId } from '@wormhole-foundation/sdk-connect'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveEndpointKey( - tokenBridgeProgramId: PublicKeyInitData, - emitterChain: number | ChainId, - emitterAddress: Buffer | Uint8Array | string, -): PublicKey { - if (emitterChain == toChainId('Solana')) { - throw new Error( - 'emitterChain == CHAIN_ID_SOLANA cannot exist as foreign token bridge emitter', - ); - } - const emitterAddr = - typeof emitterAddress === 'string' - ? new UniversalAddress(emitterAddress).toUint8Array() - : emitterAddress; - - return utils.deriveAddress( - [ - (() => { - const buf = Buffer.alloc(2); - buf.writeUInt16BE(emitterChain as number); - return buf; - })(), - emitterAddr, - ], - tokenBridgeProgramId, - ); -} - -export async function getEndpointRegistration( - connection: Connection, - endpointKey: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo(new PublicKey(endpointKey), commitment) - .then((info) => - EndpointRegistration.deserialize(utils.getAccountData(info)), - ); -} - -export class EndpointRegistration { - chain: ChainId; - contract: Buffer; - - constructor(chain: number, contract: Buffer) { - this.chain = chain as ChainId; - this.contract = contract; - } - - static deserialize(data: Buffer): EndpointRegistration { - if (data.length != 34) { - throw new Error('data.length != 34'); - } - const chain = data.readUInt16LE(0); - const contract = data.subarray(2, 34); - return new EndpointRegistration(chain, contract); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/index.ts deleted file mode 100644 index 753d2218c..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './config.js'; -export * from './custody.js'; -export * from './endpoint.js'; -export * from './transferWithPayload.js'; -export * from './signer.js'; -export * from './wrapped.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/signer.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/signer.ts deleted file mode 100644 index 0c58cbeb2..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/signer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveAuthoritySignerKey( - tokenBridgeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('authority_signer')], - tokenBridgeProgramId, - ); -} - -export function deriveCustodySignerKey( - tokenBridgeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('custody_signer')], - tokenBridgeProgramId, - ); -} - -export function deriveMintAuthorityKey( - tokenBridgeProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('mint_signer')], - tokenBridgeProgramId, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/transferWithPayload.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/transferWithPayload.ts deleted file mode 100644 index 3f499240e..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/transferWithPayload.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { PublicKey, PublicKeyInitData } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveSenderAccountKey( - cpiProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('sender')], cpiProgramId); -} - -export function deriveRedeemerAccountKey( - cpiProgramId: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress([Buffer.from('redeemer')], cpiProgramId); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/wrapped.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/wrapped.ts deleted file mode 100644 index 574a50c3b..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/accounts/wrapped.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { - Commitment, - Connection, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { ChainId } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; -import { utils } from '@wormhole-foundation/sdk-solana'; - -export function deriveWrappedMintKey( - tokenBridgeProgramId: PublicKeyInitData, - tokenChain: number | ChainId, - tokenAddress: Buffer | Uint8Array, -): PublicKey { - if (tokenChain == toChainId('Solana')) { - throw new Error( - 'tokenChain == CHAIN_ID_SOLANA does not have wrapped mint key', - ); - } - - return utils.deriveAddress( - [ - Buffer.from('wrapped'), - (() => { - const buf = Buffer.alloc(2); - buf.writeUInt16BE(tokenChain as number); - return buf; - })(), - tokenAddress as Uint8Array, - ], - tokenBridgeProgramId, - ); -} - -export function deriveWrappedMetaKey( - tokenBridgeProgramId: PublicKeyInitData, - mint: PublicKeyInitData, -): PublicKey { - return utils.deriveAddress( - [Buffer.from('meta'), new PublicKey(mint).toBuffer()], - tokenBridgeProgramId, - ); -} - -export async function getWrappedMeta( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - mint: PublicKeyInitData, - commitment?: Commitment, -): Promise { - return connection - .getAccountInfo( - deriveWrappedMetaKey(tokenBridgeProgramId, mint), - commitment, - ) - .then((info) => WrappedMeta.deserialize(utils.getAccountData(info))); -} - -export class WrappedMeta { - chain: number; - tokenAddress: Buffer; - originalDecimals: number; - lastUpdatedSequence?: bigint; - - constructor( - chain: number, - tokenAddress: Buffer, - originalDecimals: number, - lastUpdatedSequence?: bigint, - ) { - this.chain = chain; - this.tokenAddress = tokenAddress; - this.originalDecimals = originalDecimals; - this.lastUpdatedSequence = lastUpdatedSequence; - } - - static deserialize(data: Buffer): WrappedMeta { - if (data.length !== 35 && data.length !== 43) { - throw new Error(`invalid wrapped meta length: ${data.length}`); - } - const chain = data.readUInt16LE(0); - const tokenAddress = data.subarray(2, 34); - const originalDecimals = data.readUInt8(34); - const lastUpdatedSequence = - data.length === 43 ? data.readBigUInt64LE(35) : undefined; - return new WrappedMeta( - chain, - tokenAddress, - originalDecimals, - lastUpdatedSequence, - ); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/accounts.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/accounts.ts deleted file mode 100644 index 6cd312e52..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/accounts.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { AccountsCoder, Idl } from '@coral-xyz/anchor'; -import { anchor } from '@wormhole-foundation/sdk-solana'; - -export class TokenBridgeAccountsCoder - implements AccountsCoder -{ - constructor(private idl: Idl) {} - - public async encode(accountName: A, account: T): Promise { - switch (accountName) { - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public decode(accountName: A, ix: Buffer): T { - return this.decodeUnchecked(accountName, ix); - } - - public decodeUnchecked(accountName: A, ix: Buffer): T { - switch (accountName) { - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public memcmp(accountName: A, _appendData?: Buffer): any { - switch (accountName) { - default: { - throw new Error(`Invalid account name: ${accountName}`); - } - } - } - - public size(idlAccount: anchor.IdlTypeDef): number { - return anchor.accountSize(this.idl, idlAccount) ?? 0; - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/events.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/events.ts deleted file mode 100644 index 27d28e57e..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/events.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { EventCoder, Event, Idl } from '@coral-xyz/anchor'; -import type { anchor } from '@wormhole-foundation/sdk-solana'; - -export class TokenBridgeEventsCoder implements EventCoder { - constructor(_idl: Idl) {} - - decode< - E extends anchor.IdlEvent = anchor.IdlEvent, - T = Record, - >(_log: string): Event | null { - throw new Error('Token Bridge program does not have events'); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/index.ts deleted file mode 100644 index a05691d39..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Coder, Idl } from '@coral-xyz/anchor'; -import { TokenBridgeAccountsCoder } from './accounts.js'; -import { TokenBridgeEventsCoder } from './events.js'; -import { TokenBridgeInstructionCoder } from './instruction.js'; -import { TokenBridgeStateCoder } from './state.js'; -import { TokenBridgeTypesCoder } from './types.js'; - -export * from './instruction.js'; - -export class TokenBridgeCoder implements Coder { - readonly instruction: TokenBridgeInstructionCoder; - readonly accounts: TokenBridgeAccountsCoder; - readonly state: TokenBridgeStateCoder; - readonly events: TokenBridgeEventsCoder; - readonly types: TokenBridgeTypesCoder; - - constructor(idl: Idl) { - this.instruction = new TokenBridgeInstructionCoder(idl); - this.accounts = new TokenBridgeAccountsCoder(idl); - this.state = new TokenBridgeStateCoder(idl); - this.events = new TokenBridgeEventsCoder(idl); - this.types = new TokenBridgeTypesCoder(idl); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/instruction.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/instruction.ts deleted file mode 100644 index 472dfedbb..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/instruction.ts +++ /dev/null @@ -1,254 +0,0 @@ -import type { Idl, InstructionCoder } from '@coral-xyz/anchor'; -import { PublicKey } from '@solana/web3.js'; - -export class TokenBridgeInstructionCoder implements InstructionCoder { - constructor(_: Idl) {} - - encode(ixName: string, ix: any): Buffer { - switch (ixName) { - case 'initialize': { - return encodeInitialize(ix); - } - case 'attestToken': { - return encodeAttestToken(ix); - } - case 'completeNative': { - return encodeCompleteNative(ix); - } - case 'completeWrapped': { - return encodeCompleteWrapped(ix); - } - case 'transferWrapped': { - return encodeTransferWrapped(ix); - } - case 'transferNative': { - return encodeTransferNative(ix); - } - case 'registerChain': { - return encodeRegisterChain(ix); - } - case 'createWrapped': { - return encodeCreateWrapped(ix); - } - case 'upgradeContract': { - return encodeUpgradeContract(ix); - } - case 'transferWrappedWithPayload': { - return encodeTransferWrappedWithPayload(ix); - } - case 'transferNativeWithPayload': { - return encodeTransferNativeWithPayload(ix); - } - default: { - throw new Error(`Invalid instruction: ${ixName}`); - } - } - } - - encodeState(_ixName: string, _ix: any): Buffer { - throw new Error('Token Bridge program does not have state'); - } -} - -/** Solitaire enum of existing the Token Bridge's instructions. - * - * https://github.com/certusone/wormhole/blob/main/solana/modules/token_bridge/program/src/lib.rs#L100 - */ -export enum TokenBridgeInstruction { - Initialize, - AttestToken, - CompleteNative, - CompleteWrapped, - TransferWrapped, - TransferNative, - RegisterChain, - CreateWrapped, - UpgradeContract, - CompleteNativeWithPayload, - CompleteWrappedWithPayload, - TransferWrappedWithPayload, - TransferNativeWithPayload, -} - -function encodeTokenBridgeInstructionData( - instructionType: TokenBridgeInstruction, - data?: Buffer, -): Buffer { - const dataLen = data === undefined ? 0 : data.length; - const instructionData = Buffer.alloc(1 + dataLen); - instructionData.writeUInt8(instructionType, 0); - if (dataLen > 0) { - instructionData.write(data!.toString('hex'), 1, 'hex'); - } - return instructionData; -} - -function encodeInitialize({ wormhole }: any): Buffer { - const serialized = Buffer.alloc(32); - serialized.write( - new PublicKey(wormhole).toBuffer().toString('hex'), - 0, - 'hex', - ); - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.Initialize, - serialized, - ); -} - -function encodeAttestToken({ nonce }: any) { - const serialized = Buffer.alloc(4); - serialized.writeUInt32LE(nonce, 0); - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.AttestToken, - serialized, - ); -} - -function encodeCompleteNative({}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.CompleteNative, - ); -} - -function encodeCompleteWrapped({}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.CompleteWrapped, - ); -} - -function encodeTransferData({ - nonce, - amount, - fee, - targetAddress, - targetChain, -}: any) { - if (typeof amount != 'bigint') { - amount = BigInt(amount); - } - if (typeof fee != 'bigint') { - fee = BigInt(fee); - } - if (!Buffer.isBuffer(targetAddress)) { - throw new Error('targetAddress must be Buffer'); - } - const serialized = Buffer.alloc(54); - serialized.writeUInt32LE(nonce, 0); - serialized.writeBigUInt64LE(amount, 4); - serialized.writeBigUInt64LE(fee, 12); - serialized.write(targetAddress.toString('hex'), 20, 'hex'); - serialized.writeUInt16LE(targetChain, 52); - return serialized; -} - -function encodeTransferWrapped({ - nonce, - amount, - fee, - targetAddress, - targetChain, -}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.TransferWrapped, - encodeTransferData({ nonce, amount, fee, targetAddress, targetChain }), - ); -} - -function encodeTransferNative({ - nonce, - amount, - fee, - targetAddress, - targetChain, -}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.TransferNative, - encodeTransferData({ nonce, amount, fee, targetAddress, targetChain }), - ); -} - -function encodeRegisterChain({}: any) { - return encodeTokenBridgeInstructionData(TokenBridgeInstruction.RegisterChain); -} - -function encodeCreateWrapped({}: any) { - return encodeTokenBridgeInstructionData(TokenBridgeInstruction.CreateWrapped); -} - -function encodeUpgradeContract({}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.UpgradeContract, - ); -} - -function encodeTransferWithPayloadData({ - nonce, - amount, - targetAddress, - targetChain, - payload, -}: any) { - if (typeof amount != 'bigint') { - amount = BigInt(amount); - } - if (!Buffer.isBuffer(targetAddress)) { - throw new Error('targetAddress must be Buffer'); - } - if (!Buffer.isBuffer(payload)) { - throw new Error('payload must be Buffer'); - } - const serializedWithPayloadLen = Buffer.alloc(50); - serializedWithPayloadLen.writeUInt32LE(nonce, 0); - serializedWithPayloadLen.writeBigUInt64LE(amount, 4); - serializedWithPayloadLen.write(targetAddress.toString('hex'), 12, 'hex'); - serializedWithPayloadLen.writeUInt16LE(targetChain, 44); - serializedWithPayloadLen.writeUInt32LE(payload.length, 46); - return Buffer.concat([ - serializedWithPayloadLen, - payload, - Buffer.alloc(1), // option == None - ]); -} - -function encodeTransferWrappedWithPayload({ - nonce, - amount, - fee, - targetAddress, - targetChain, - payload, -}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.TransferWrappedWithPayload, - encodeTransferWithPayloadData({ - nonce, - amount, - fee, - targetAddress, - targetChain, - payload, - }), - ); -} - -function encodeTransferNativeWithPayload({ - nonce, - amount, - fee, - targetAddress, - targetChain, - payload, -}: any) { - return encodeTokenBridgeInstructionData( - TokenBridgeInstruction.TransferNativeWithPayload, - encodeTransferWithPayloadData({ - nonce, - amount, - fee, - targetAddress, - targetChain, - payload, - }), - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/state.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/state.ts deleted file mode 100644 index 21d41dab3..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/state.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Idl, StateCoder } from '@coral-xyz/anchor'; - -export class TokenBridgeStateCoder implements StateCoder { - constructor(_idl: Idl) {} - - encode(_name: string, _account: T): Promise { - throw new Error('Token Bridge program does not have state'); - } - decode(_ix: Buffer): T { - throw new Error('Token Bridge program does not have state'); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/types.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/types.ts deleted file mode 100644 index df924e348..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/coder/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Idl, TypesCoder } from '@coral-xyz/anchor'; - -export class TokenBridgeTypesCoder implements TypesCoder { - constructor(_idl: Idl) {} - - encode(_name: string, _type: T): Buffer { - throw new Error('Token Bridge program does not have user-defined types'); - } - decode(_name: string, _typeData: Buffer): T { - throw new Error('Token Bridge program does not have user-defined types'); - } -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/cpi.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/cpi.ts deleted file mode 100644 index 7da7d6941..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/cpi.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveAuthoritySignerKey, - deriveCustodyKey, - deriveCustodySignerKey, - deriveEndpointKey, - deriveMintAuthorityKey, - deriveRedeemerAccountKey, - deriveSenderAccountKey, - deriveTokenBridgeConfigKey, - deriveWrappedMetaKey, - deriveWrappedMintKey, -} from './accounts/index.js'; -import { - getTransferNativeWithPayloadAccounts, - getTransferWrappedWithPayloadAccounts, -} from './instructions/index.js'; -import type { TokenBridge } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -/** - * Base Config Account for Token Bridge program. - */ -export interface TokenBridgeBaseDerivedAccounts { - /** - * seeds = ["config"], seeds::program = tokenBridgeProgram - */ - tokenBridgeConfig: PublicKey; -} - -/** - * Accounts derived from Token Bridge program for native assets. - */ -export interface TokenBridgeBaseNativeDerivedAccounts - extends TokenBridgeBaseDerivedAccounts { - /** - * seeds = ["custody_signer"], seeds::program = tokenBridgeProgram - */ - tokenBridgeCustodySigner: PublicKey; -} - -export interface TokenBridgeBaseSenderDerivedAccounts - extends TokenBridgeBaseDerivedAccounts { - /** - * seeds = ["authority_signer"], seeds::program = tokenBridgeProgram - */ - tokenBridgeAuthoritySigner: PublicKey; - /** - * seeds = ["sender"], seeds::program = cpiProgramId - */ - tokenBridgeSender: PublicKey; - /** - * seeds = ["Bridge"], seeds::program = wormholeProgram - */ - wormholeBridge: PublicKey; - /** - * seeds = ["emitter"], seeds::program = tokenBridgeProgram - */ - tokenBridgeEmitter: PublicKey; - /** - * seeds = ["Sequence", tokenBridgeEmitter], seeds::program = wormholeProgram - */ - tokenBridgeSequence: PublicKey; - /** - * seeds = ["fee_collector"], seeds::program = wormholeProgram - */ - wormholeFeeCollector: PublicKey; -} - -export interface TokenBridgeNativeSenderDerivedAccounts - extends TokenBridgeBaseNativeDerivedAccounts, - TokenBridgeBaseSenderDerivedAccounts {} - -export interface TokenBridgeWrappedSenderDerivedAccounts - extends TokenBridgeBaseSenderDerivedAccounts {} - -export interface TokenBridgeBaseRedeemerDerivedAccounts - extends TokenBridgeBaseDerivedAccounts { - /** - * seeds = ["redeemer"], seeds::program = cpiProgramId - */ - tokenBridgeRedeemer: PublicKey; -} - -export interface TokenBridgeNativeRedeemerDerivedAccounts - extends TokenBridgeBaseNativeDerivedAccounts, - TokenBridgeBaseRedeemerDerivedAccounts {} - -export interface TokenBridgeWrappedRedeemerDerivedAccounts - extends TokenBridgeBaseRedeemerDerivedAccounts { - /** - * seeds = ["mint_signer"], seeds::program = tokenBridgeProgram - */ - tokenBridgeMintAuthority: PublicKey; -} - -/** - * Accounts derived from Token Bridge program. - */ -export interface TokenBridgeDerivedAccounts - extends TokenBridgeNativeSenderDerivedAccounts, - TokenBridgeWrappedSenderDerivedAccounts, - TokenBridgeNativeRedeemerDerivedAccounts, - TokenBridgeWrappedRedeemerDerivedAccounts {} - -/** - * Generate Token Bridge PDAs. - * - * @param cpiProgramId - * @param tokenBridgeProgramId - * @param wormholeProgramId - * @returns - */ -export function getTokenBridgeDerivedAccounts( - cpiProgramId: PublicKeyInitData, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, -): TokenBridgeDerivedAccounts { - const { - wormholeEmitter: tokenBridgeEmitter, - wormholeBridge, - wormholeFeeCollector, - wormholeSequence: tokenBridgeSequence, - } = utils.getWormholeDerivedAccounts(tokenBridgeProgramId, wormholeProgramId); - return { - tokenBridgeConfig: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - tokenBridgeAuthoritySigner: deriveAuthoritySignerKey(tokenBridgeProgramId), - tokenBridgeCustodySigner: deriveCustodySignerKey(tokenBridgeProgramId), - tokenBridgeMintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId), - tokenBridgeSender: deriveSenderAccountKey(cpiProgramId), - tokenBridgeRedeemer: deriveRedeemerAccountKey(cpiProgramId), - wormholeBridge, - tokenBridgeEmitter, - wormholeFeeCollector, - tokenBridgeSequence, - }; -} - -/** - * Accounts needed to perform `transfer_native_with_payload` instruction. - */ -export interface TransferNativeWithPayloadCpiAccounts - extends TokenBridgeNativeSenderDerivedAccounts { - payer: PublicKey; - /** - * seeds = [mint], seeds::program = tokenBridgeProgram - */ - tokenBridgeCustody: PublicKey; - /** - * Token account where tokens reside - */ - fromTokenAccount: PublicKey; - mint: PublicKey; - wormholeMessage: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -/** - * Generate accounts needed to perform `transfer_wrapped_with_payload` instruction - * as cross-program invocation. - * - * @param cpiProgramId - * @param tokenBridgeProgramId - * @param wormholeProgramId - * @param payer - * @param message - * @param fromTokenAccount - * @param mint - * @returns - */ -export function getTransferNativeWithPayloadCpiAccounts( - cpiProgramId: PublicKeyInitData, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - fromTokenAccount: PublicKeyInitData, - mint: PublicKeyInitData, -): TransferNativeWithPayloadCpiAccounts { - const accounts = getTransferNativeWithPayloadAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - fromTokenAccount, - mint, - cpiProgramId, - ); - return { - payer: accounts.payer, - tokenBridgeConfig: accounts.config, - fromTokenAccount: accounts.from, - mint: accounts.mint, - tokenBridgeCustody: accounts.custody, - tokenBridgeAuthoritySigner: accounts.authoritySigner, - tokenBridgeCustodySigner: accounts.custodySigner, - wormholeBridge: accounts.wormholeBridge, - wormholeMessage: accounts.wormholeMessage, - tokenBridgeEmitter: accounts.wormholeEmitter, - tokenBridgeSequence: accounts.wormholeSequence, - wormholeFeeCollector: accounts.wormholeFeeCollector, - clock: accounts.clock, - tokenBridgeSender: accounts.sender, - rent: accounts.rent, - systemProgram: accounts.systemProgram, - tokenProgram: accounts.tokenProgram, - wormholeProgram: accounts.wormholeProgram, - }; -} - -/** - * Accounts needed to perform `transfer_wrapped_with_payload` instruction. - */ -export interface TransferWrappedWithPayloadCpiAccounts - extends TokenBridgeWrappedSenderDerivedAccounts { - payer: PublicKey; - /** - * Token account where tokens reside - */ - fromTokenAccount: PublicKey; - /** - * Token account owner (usually cpiProgramId) - */ - fromTokenAccountOwner: PublicKey; - /** - * seeds = ["wrapped", token_chain, token_address], seeds::program = tokenBridgeProgram - */ - tokenBridgeWrappedMint: PublicKey; - /** - * seeds = ["meta", mint], seeds::program = tokenBridgeProgram - */ - tokenBridgeWrappedMeta: PublicKey; - wormholeMessage: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -/** - * Generate accounts needed to perform `transfer_wrapped_with_payload` instruction - * as cross-program invocation. - * - * @param cpiProgramId - * @param tokenBridgeProgramId - * @param wormholeProgramId - * @param payer - * @param message - * @param fromTokenAccount - * @param tokenChain - * @param tokenAddress - * @param [fromTokenAccountOwner] - * @returns - */ -export function getTransferWrappedWithPayloadCpiAccounts( - cpiProgramId: PublicKeyInitData, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - fromTokenAccount: PublicKeyInitData, - tokenChain: number, - tokenAddress: Buffer | Uint8Array, - fromTokenAccountOwner?: PublicKeyInitData, -): TransferWrappedWithPayloadCpiAccounts { - const accounts = getTransferWrappedWithPayloadAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - fromTokenAccount, - fromTokenAccountOwner === undefined ? cpiProgramId : fromTokenAccountOwner, - tokenChain, - tokenAddress, - cpiProgramId, - ); - return { - payer: accounts.payer, - tokenBridgeConfig: accounts.config, - fromTokenAccount: accounts.from, - fromTokenAccountOwner: accounts.fromOwner, - tokenBridgeWrappedMint: accounts.mint, - tokenBridgeWrappedMeta: accounts.wrappedMeta, - tokenBridgeAuthoritySigner: accounts.authoritySigner, - wormholeBridge: accounts.wormholeBridge, - wormholeMessage: accounts.wormholeMessage, - tokenBridgeEmitter: accounts.wormholeEmitter, - tokenBridgeSequence: accounts.wormholeSequence, - wormholeFeeCollector: accounts.wormholeFeeCollector, - clock: accounts.clock, - tokenBridgeSender: accounts.sender, - rent: accounts.rent, - systemProgram: accounts.systemProgram, - tokenProgram: accounts.tokenProgram, - wormholeProgram: accounts.wormholeProgram, - }; -} - -/** - * Accounts needed to perform `complete_native_with_payload` instruction. - */ -export interface CompleteTransferNativeWithPayloadCpiAccounts - extends TokenBridgeNativeRedeemerDerivedAccounts { - payer: PublicKey; - /** - * seeds = ["PostedVAA", vaa_hash], seeds::program = wormholeProgram - */ - vaa: PublicKey; - /** - * seeds = [emitter_address, emitter_chain, sequence], seeds::program = tokenBridgeProgram - */ - tokenBridgeClaim: PublicKey; - /** - * seeds = [emitter_chain, emitter_address], seeds::program = tokenBridgeProgram - */ - tokenBridgeForeignEndpoint: PublicKey; - /** - * Token account to receive tokens - */ - toTokenAccount: PublicKey; - toFeesTokenAccount: PublicKey; // this shouldn't exist? - /** - * seeds = [mint], seeds::program = tokenBridgeProgram - */ - tokenBridgeCustody: PublicKey; - mint: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -/** - * Generate accounts needed to perform `complete_native_with_payload` instruction - * as cross-program invocation. - * - * Note: `toFeesTokenAccount` is the same as `toTokenAccount`. For your program, - * you only need to pass your `toTokenAccount` into the complete transfer - * instruction for the `toFeesTokenAccount`. - * - * @param tokenBridgeProgramId - * @param wormholeProgramId - * @param payer - * @param vaa - * @param toTokenAccount - * @returns - */ -export function getCompleteTransferNativeWithPayloadCpiAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - toTokenAccount: PublicKeyInitData, -): CompleteTransferNativeWithPayloadCpiAccounts { - const mint = new PublicKey(vaa.payload.token.address.toUint8Array()); - const cpiProgramId = new PublicKey(vaa.payload.to.address.toUint8Array()); - - return { - payer: new PublicKey(payer), - tokenBridgeConfig: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - vaa: utils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - tokenBridgeClaim: utils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - tokenBridgeForeignEndpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.emitterChain), - vaa.emitterAddress.toUint8Array(), - ), - toTokenAccount: new PublicKey(toTokenAccount), - tokenBridgeRedeemer: deriveRedeemerAccountKey(cpiProgramId), - toFeesTokenAccount: new PublicKey(toTokenAccount), - tokenBridgeCustody: deriveCustodyKey(tokenBridgeProgramId, mint), - mint, - tokenBridgeCustodySigner: deriveCustodySignerKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} - -/** - * Accounts needed to perform `complete_wrapped_with_payload` instruction. - */ -export interface CompleteTransferWrappedWithPayloadCpiAccounts - extends TokenBridgeWrappedRedeemerDerivedAccounts { - payer: PublicKey; - /** - * seeds = ["PostedVAA", vaa_hash], seeds::program = wormholeProgram - */ - vaa: PublicKey; - /** - * seeds = [emitter_address, emitter_chain, sequence], seeds::program = tokenBridgeProgram - */ - tokenBridgeClaim: PublicKey; - /** - * seeds = [emitter_chain, emitter_address], seeds::program = tokenBridgeProgram - */ - tokenBridgeForeignEndpoint: PublicKey; - /** - * Token account to receive tokens - */ - toTokenAccount: PublicKey; - toFeesTokenAccount: PublicKey; // this shouldn't exist? - /** - * seeds = ["wrapped", token_chain, token_address], seeds::program = tokenBridgeProgram - */ - tokenBridgeWrappedMint: PublicKey; - /** - * seeds = ["meta", mint], seeds::program = tokenBridgeProgram - */ - tokenBridgeWrappedMeta: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -/** - * Generate accounts needed to perform `complete_wrapped_with_payload` instruction - * as cross-program invocation. - * - * Note: `toFeesTokenAccount` is the same as `toTokenAccount`. For your program, - * you only need to pass your `toTokenAccount` into the complete transfer - * instruction for the `toFeesTokenAccount`. - * - * @param cpiProgramId - * @param tokenBridgeProgramId - * @param wormholeProgramId - * @param payer - * @param vaa - * @returns - */ -export function getCompleteTransferWrappedWithPayloadCpiAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - toTokenAccount: PublicKeyInitData, -): CompleteTransferWrappedWithPayloadCpiAccounts { - const mint = deriveWrappedMintKey( - tokenBridgeProgramId, - toChainId(vaa.payload.token.chain), - vaa.payload.token.address.toUint8Array(), - ); - const cpiProgramId = new PublicKey(vaa.payload.to.address.toUint8Array()); - return { - payer: new PublicKey(payer), - tokenBridgeConfig: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - vaa: utils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - tokenBridgeClaim: utils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - tokenBridgeForeignEndpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.emitterChain), - vaa.emitterAddress.toUint8Array(), - ), - toTokenAccount: new PublicKey(toTokenAccount), - tokenBridgeRedeemer: deriveRedeemerAccountKey(cpiProgramId), - toFeesTokenAccount: new PublicKey(toTokenAccount), - tokenBridgeWrappedMint: mint, - tokenBridgeWrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - tokenBridgeMintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/index.ts deleted file mode 100644 index 6fb532c35..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './accounts/index.js'; -export * from './cpi.js'; -export * from './instructions/index.js'; -export * from './program.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/approve.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/approve.ts deleted file mode 100644 index a0f36334d..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/approve.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createApproveInstruction } from '@solana/spl-token'; -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { deriveAuthoritySignerKey } from './../accounts/index.js'; - -export function createApproveAuthoritySignerInstruction( - tokenBridgeProgramId: PublicKeyInitData, - tokenAccount: PublicKeyInitData, - owner: PublicKeyInitData, - amount: number | bigint, -) { - return createApproveInstruction( - new PublicKey(tokenAccount), - deriveAuthoritySignerKey(tokenBridgeProgramId), - new PublicKey(owner), - amount, - ); -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/attestToken.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/attestToken.ts deleted file mode 100644 index 07976ceff..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/attestToken.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils as coreUtils } from '@wormhole-foundation/sdk-solana-core'; -import { deriveSplTokenMetadataKey } from '../../splMetadata.js'; -import { - deriveTokenBridgeConfigKey, - deriveWrappedMetaKey, -} from './../accounts/index.js'; - -export function createAttestTokenInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - mint: PublicKeyInitData, - message: PublicKeyInitData, - nonce: number, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.attestToken(nonce); - - console.log( - getAttestTokenAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - mint, - message, - ), - ); - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getAttestTokenAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - mint, - message, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface AttestTokenAccounts { - payer: PublicKey; - config: PublicKey; - mint: PublicKey; - wrappedMeta: PublicKey; - splMetadata: PublicKey; - wormholeBridge: PublicKey; - wormholeMessage: PublicKey; - wormholeEmitter: PublicKey; - wormholeSequence: PublicKey; - wormholeFeeCollector: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getAttestTokenAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - mint: PublicKeyInitData, - message: PublicKeyInitData, -): AttestTokenAccounts { - const { - bridge: wormholeBridge, - emitter: wormholeEmitter, - sequence: wormholeSequence, - feeCollector: wormholeFeeCollector, - clock, - rent, - systemProgram, - } = coreUtils.getPostMessageAccounts( - wormholeProgramId, - payer, - message, - tokenBridgeProgramId, - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - mint: new PublicKey(mint), - wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), - wormholeBridge, - wormholeMessage: new PublicKey(message), - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeNative.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeNative.ts deleted file mode 100644 index c82a5e426..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeNative.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveEndpointKey, - deriveTokenBridgeConfigKey, - deriveCustodyKey, - deriveCustodySignerKey, -} from './../accounts/index.js'; -import type { TokenBridge } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -export function createCompleteTransferNativeInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - feeRecipient?: PublicKeyInitData, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.completeNative(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getCompleteTransferNativeAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - vaa, - feeRecipient, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface CompleteTransferNativeAccounts { - payer: PublicKey; - config: PublicKey; - vaa: PublicKey; - claim: PublicKey; - endpoint: PublicKey; - to: PublicKey; - toFees: PublicKey; - custody: PublicKey; - mint: PublicKey; - custodySigner: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getCompleteTransferNativeAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - feeRecipient?: PublicKeyInitData, -): CompleteTransferNativeAccounts { - const mint = new PublicKey(vaa.payload.token.address.toUint8Array()); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - vaa: utils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: utils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - endpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.emitterChain), - vaa.emitterAddress.toUint8Array(), - ), - to: new PublicKey(vaa.payload.to.address.toUint8Array()), - toFees: new PublicKey( - feeRecipient === undefined - ? vaa.payload.to.address.toUint8Array() - : feeRecipient, - ), - custody: deriveCustodyKey(tokenBridgeProgramId, mint), - mint, - custodySigner: deriveCustodySignerKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeWrapped.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeWrapped.ts deleted file mode 100644 index 441ac1235..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/completeWrapped.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveEndpointKey, - deriveTokenBridgeConfigKey, - deriveWrappedMintKey, - deriveWrappedMetaKey, - deriveMintAuthorityKey, -} from './../accounts/index.js'; -import type { TokenBridge } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; - -export function createCompleteTransferWrappedInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - feeRecipient?: PublicKeyInitData, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.completeWrapped(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getCompleteTransferWrappedAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - vaa, - feeRecipient, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface CompleteTransferWrappedAccounts { - payer: PublicKey; - config: PublicKey; - vaa: PublicKey; - claim: PublicKey; - endpoint: PublicKey; - to: PublicKey; - toFees: PublicKey; - mint: PublicKey; - wrappedMeta: PublicKey; - mintAuthority: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getCompleteTransferWrappedAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.TransferVAA, - feeRecipient?: PublicKeyInitData, -): CompleteTransferWrappedAccounts { - const mint = deriveWrappedMintKey( - tokenBridgeProgramId, - toChainId(vaa.payload.token.chain), - vaa.payload.token.address.toUint8Array(), - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - vaa: utils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: utils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - endpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.emitterChain), - vaa.emitterAddress.toUint8Array(), - ), - to: new PublicKey(vaa.payload.to.address.toUint8Array()), - toFees: new PublicKey( - feeRecipient === undefined - ? vaa.payload.to.address.toUint8Array() - : feeRecipient, - ), - mint, - wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - mintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/createWrapped.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/createWrapped.ts deleted file mode 100644 index fb2c44001..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/createWrapped.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import type { TokenBridge } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; -import { utils as CoreUtils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveSplTokenMetadataKey, - SplTokenMetadataProgram, -} from '../../splMetadata.js'; -import { - deriveEndpointKey, - deriveMintAuthorityKey, - deriveTokenBridgeConfigKey, - deriveWrappedMetaKey, - deriveWrappedMintKey, -} from './../accounts/index.js'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; - -export function createCreateWrappedInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.AttestVAA, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.createWrapped(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getCreateWrappedAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - vaa, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface CreateWrappedAccounts { - payer: PublicKey; - config: PublicKey; - endpoint: PublicKey; - vaa: PublicKey; - claim: PublicKey; - mint: PublicKey; - wrappedMeta: PublicKey; - splMetadata: PublicKey; - mintAuthority: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - splMetadataProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getCreateWrappedAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: TokenBridge.VAA, -): CreateWrappedAccounts { - const mint = deriveWrappedMintKey( - tokenBridgeProgramId, - toChainId(vaa.payload.token.chain), - vaa.payload.token.address.toUint8Array(), - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - endpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.emitterChain), - vaa.emitterAddress.toUint8Array(), - ), - vaa: CoreUtils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: CoreUtils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - mint, - wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - splMetadata: deriveSplTokenMetadataKey(mint), - mintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, - splMetadataProgram: SplTokenMetadataProgram.programId, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/governance.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/governance.ts deleted file mode 100644 index 050288665..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/governance.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { - PublicKey, - SystemProgram, - SYSVAR_CLOCK_PUBKEY, - SYSVAR_RENT_PUBKEY, -} from '@solana/web3.js'; -import type { VAA } from '@wormhole-foundation/sdk-connect'; -import { toChainId } from '@wormhole-foundation/sdk-connect'; -import { utils } from '@wormhole-foundation/sdk-solana'; -import { utils as CoreUtils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveEndpointKey, - deriveTokenBridgeConfigKey, -} from './../accounts/index.js'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; - -export function createRegisterChainInstruction( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'TokenBridge:RegisterChain'>, -): TransactionInstruction { - const methods = - createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - ).methods.registerChain(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getRegisterChainAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - vaa, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface RegisterChainAccounts { - payer: PublicKey; - config: PublicKey; - endpoint: PublicKey; - vaa: PublicKey; - claim: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getRegisterChainAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'TokenBridge:RegisterChain'>, -): RegisterChainAccounts { - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - endpoint: deriveEndpointKey( - tokenBridgeProgramId, - toChainId(vaa.payload.actionArgs.foreignChain), - vaa.payload.actionArgs.foreignAddress.toUint8Array(), - ), - vaa: CoreUtils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: CoreUtils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} - -export function createUpgradeContractInstruction( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'TokenBridge:UpgradeContract'>, - spill?: PublicKeyInitData, -): TransactionInstruction { - const methods = - createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - ).methods.upgradeContract(); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getUpgradeContractAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - vaa, - spill, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface UpgradeContractAccounts { - payer: PublicKey; - vaa: PublicKey; - claim: PublicKey; - upgradeAuthority: PublicKey; - spill: PublicKey; - implementation: PublicKey; - programData: PublicKey; - tokenBridgeProgram: PublicKey; - rent: PublicKey; - clock: PublicKey; - bpfLoaderUpgradeable: PublicKey; - systemProgram: PublicKey; -} - -export function getUpgradeContractAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - vaa: VAA<'TokenBridge:UpgradeContract'>, - spill?: PublicKeyInitData, -): UpgradeContractAccounts { - return { - payer: new PublicKey(payer), - vaa: CoreUtils.derivePostedVaaKey(wormholeProgramId, Buffer.from(vaa.hash)), - claim: CoreUtils.deriveClaimKey( - tokenBridgeProgramId, - vaa.emitterAddress.toUint8Array(), - toChainId(vaa.emitterChain), - vaa.sequence, - ), - upgradeAuthority: CoreUtils.deriveUpgradeAuthorityKey(tokenBridgeProgramId), - spill: new PublicKey(spill === undefined ? payer : spill), - implementation: new PublicKey(vaa.payload.actionArgs.newContract), - programData: utils.deriveProgramDataAddress(tokenBridgeProgramId), - tokenBridgeProgram: new PublicKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - clock: SYSVAR_CLOCK_PUBKEY, - bpfLoaderUpgradeable: utils.BPF_LOADER_UPGRADEABLE_PROGRAM_ID, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/index.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/index.ts deleted file mode 100644 index 14fd27425..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './approve.js'; -export * from './attestToken.js'; -export * from './completeNative.js'; -export * from './completeWrapped.js'; -export * from './createWrapped.js'; -export * from './initialize.js'; -export * from './governance.js'; -export * from './transferNative.js'; -export * from './transferNativeWithPayload.js'; -export * from './transferWrapped.js'; -export * from './transferWrappedWithPayload.js'; diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/initialize.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/initialize.ts deleted file mode 100644 index ba1d582ee..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/initialize.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { deriveTokenBridgeConfigKey } from './../accounts/index.js'; - -export function createInitializeInstruction( - tokenBridgeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - ).methods.initialize(wormholeProgramId as any); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getInitializeAccounts(tokenBridgeProgramId, payer) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface InitializeAccounts { - payer: PublicKey; - config: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; -} - -export function getInitializeAccounts( - tokenBridgeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, -): InitializeAccounts { - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - rent: SYSVAR_RENT_PUBKEY, - systemProgram: SystemProgram.programId, - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNative.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNative.ts deleted file mode 100644 index 8cbd3ab26..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNative.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveAuthoritySignerKey, - deriveCustodySignerKey, - deriveTokenBridgeConfigKey, - deriveCustodyKey, -} from './../accounts/index.js'; - -export function createTransferNativeInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - mint: PublicKeyInitData, - nonce: number, - amount: bigint, - fee: bigint, - targetAddress: Buffer | Uint8Array, - targetChain: number, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.transferNative( - nonce, - amount as any, - fee as any, - Buffer.from(targetAddress) as any, - targetChain, - ); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getTransferNativeAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - from, - mint, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface TransferNativeAccounts { - payer: PublicKey; - config: PublicKey; - from: PublicKey; - mint: PublicKey; - custody: PublicKey; - authoritySigner: PublicKey; - custodySigner: PublicKey; - wormholeBridge: PublicKey; - wormholeMessage: PublicKey; - wormholeEmitter: PublicKey; - wormholeSequence: PublicKey; - wormholeFeeCollector: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getTransferNativeAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - mint: PublicKeyInitData, -): TransferNativeAccounts { - const { - wormholeBridge, - wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - } = utils.getPostMessageCpiAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - from: new PublicKey(from), - mint: new PublicKey(mint), - custody: deriveCustodyKey(tokenBridgeProgramId, mint), - authoritySigner: deriveAuthoritySignerKey(tokenBridgeProgramId), - custodySigner: deriveCustodySignerKey(tokenBridgeProgramId), - wormholeBridge, - wormholeMessage: wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNativeWithPayload.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNativeWithPayload.ts deleted file mode 100644 index a291cf8ab..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferNativeWithPayload.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveAuthoritySignerKey, - deriveCustodySignerKey, - deriveTokenBridgeConfigKey, - deriveCustodyKey, - deriveSenderAccountKey, -} from './../accounts/index.js'; - -export function createTransferNativeWithPayloadInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - mint: PublicKeyInitData, - nonce: number, - amount: bigint, - targetAddress: Buffer | Uint8Array, - targetChain: number, - payload: Buffer | Uint8Array, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.transferNativeWithPayload( - nonce, - amount as any, - Buffer.from(targetAddress) as any, - targetChain, - Buffer.from(payload) as any, - null, - ); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getTransferNativeWithPayloadAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - from, - mint, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface TransferNativeWithPayloadAccounts { - payer: PublicKey; - config: PublicKey; - from: PublicKey; - mint: PublicKey; - custody: PublicKey; - authoritySigner: PublicKey; - custodySigner: PublicKey; - wormholeBridge: PublicKey; - wormholeMessage: PublicKey; - wormholeEmitter: PublicKey; - wormholeSequence: PublicKey; - wormholeFeeCollector: PublicKey; - clock: PublicKey; - sender: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getTransferNativeWithPayloadAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - mint: PublicKeyInitData, - cpiProgramId?: PublicKeyInitData, -): TransferNativeWithPayloadAccounts { - const { - wormholeBridge, - wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - } = utils.getPostMessageCpiAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - from: new PublicKey(from), - mint: new PublicKey(mint), - custody: deriveCustodyKey(tokenBridgeProgramId, mint), - authoritySigner: deriveAuthoritySignerKey(tokenBridgeProgramId), - custodySigner: deriveCustodySignerKey(tokenBridgeProgramId), - wormholeBridge, - wormholeMessage: wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - sender: new PublicKey( - cpiProgramId === undefined ? payer : deriveSenderAccountKey(cpiProgramId), - ), - rent, - systemProgram, - tokenProgram: TOKEN_PROGRAM_ID, - wormholeProgram: new PublicKey(wormholeProgramId), - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrapped.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrapped.ts deleted file mode 100644 index d4833f6b2..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrapped.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveAuthoritySignerKey, - deriveTokenBridgeConfigKey, - deriveWrappedMetaKey, - deriveWrappedMintKey, -} from './../accounts/index.js'; - -export function createTransferWrappedInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - fromOwner: PublicKeyInitData, - tokenChain: number, - tokenAddress: Buffer | Uint8Array, - nonce: number, - amount: bigint, - fee: bigint, - targetAddress: Buffer | Uint8Array, - targetChain: number, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.transferWrapped( - nonce, - amount as any, - fee as any, - Buffer.from(targetAddress) as any, - targetChain, - ); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getTransferWrappedAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - from, - fromOwner, - tokenChain, - tokenAddress, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface TransferWrappedAccounts { - payer: PublicKey; - config: PublicKey; - from: PublicKey; - fromOwner: PublicKey; - mint: PublicKey; - wrappedMeta: PublicKey; - authoritySigner: PublicKey; - wormholeBridge: PublicKey; - wormholeMessage: PublicKey; - wormholeEmitter: PublicKey; - wormholeSequence: PublicKey; - wormholeFeeCollector: PublicKey; - clock: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - wormholeProgram: PublicKey; - tokenProgram: PublicKey; -} - -export function getTransferWrappedAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - fromOwner: PublicKeyInitData, - tokenChain: number, - tokenAddress: Buffer | Uint8Array, -): TransferWrappedAccounts { - const mint = deriveWrappedMintKey( - tokenBridgeProgramId, - tokenChain, - tokenAddress, - ); - const { - wormholeBridge, - wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - } = utils.getPostMessageCpiAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - from: new PublicKey(from), - fromOwner: new PublicKey(fromOwner), - mint: mint, - wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - authoritySigner: deriveAuthoritySignerKey(tokenBridgeProgramId), - wormholeBridge, - wormholeMessage: wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - wormholeProgram: new PublicKey(wormholeProgramId), - tokenProgram: TOKEN_PROGRAM_ID, - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrappedWithPayload.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrappedWithPayload.ts deleted file mode 100644 index 5e87a3940..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/instructions/transferWrappedWithPayload.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { - Connection, - PublicKeyInitData, - TransactionInstruction, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { createReadOnlyTokenBridgeProgramInterface } from '../program.js'; - -import { utils } from '@wormhole-foundation/sdk-solana-core'; -import { - deriveAuthoritySignerKey, - deriveSenderAccountKey, - deriveTokenBridgeConfigKey, - deriveWrappedMetaKey, - deriveWrappedMintKey, -} from './../accounts/index.js'; - -export function createTransferWrappedWithPayloadInstruction( - connection: Connection, - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - fromOwner: PublicKeyInitData, - tokenChain: number, - tokenAddress: Buffer | Uint8Array, - nonce: number, - amount: bigint, - targetAddress: Buffer | Uint8Array, - targetChain: number, - payload: Buffer | Uint8Array, -): TransactionInstruction { - const methods = createReadOnlyTokenBridgeProgramInterface( - tokenBridgeProgramId, - connection, - ).methods.transferWrappedWithPayload( - nonce, - amount as any, - Buffer.from(targetAddress) as any, - targetChain, - Buffer.from(payload) as any, - null, - ); - - // @ts-ignore - return methods._ixFn(...methods._args, { - accounts: getTransferWrappedWithPayloadAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - from, - fromOwner, - tokenChain, - tokenAddress, - ) as any, - signers: undefined, - remainingAccounts: undefined, - preInstructions: undefined, - postInstructions: undefined, - }); -} - -export interface TransferWrappedWithPayloadAccounts { - payer: PublicKey; - config: PublicKey; - from: PublicKey; - fromOwner: PublicKey; - mint: PublicKey; - wrappedMeta: PublicKey; - authoritySigner: PublicKey; - wormholeBridge: PublicKey; - wormholeMessage: PublicKey; - wormholeEmitter: PublicKey; - wormholeSequence: PublicKey; - wormholeFeeCollector: PublicKey; - clock: PublicKey; - sender: PublicKey; - rent: PublicKey; - systemProgram: PublicKey; - tokenProgram: PublicKey; - wormholeProgram: PublicKey; -} - -export function getTransferWrappedWithPayloadAccounts( - tokenBridgeProgramId: PublicKeyInitData, - wormholeProgramId: PublicKeyInitData, - payer: PublicKeyInitData, - message: PublicKeyInitData, - from: PublicKeyInitData, - fromOwner: PublicKeyInitData, - tokenChain: number, - tokenAddress: Buffer | Uint8Array, - cpiProgramId?: PublicKeyInitData, -): TransferWrappedWithPayloadAccounts { - const mint = deriveWrappedMintKey( - tokenBridgeProgramId, - tokenChain, - tokenAddress, - ); - const { - wormholeBridge, - wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - rent, - systemProgram, - } = utils.getPostMessageCpiAccounts( - tokenBridgeProgramId, - wormholeProgramId, - payer, - message, - ); - return { - payer: new PublicKey(payer), - config: deriveTokenBridgeConfigKey(tokenBridgeProgramId), - from: new PublicKey(from), - fromOwner: new PublicKey(fromOwner), - mint: mint, - wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint), - authoritySigner: deriveAuthoritySignerKey(tokenBridgeProgramId), - wormholeBridge, - wormholeMessage: wormholeMessage, - wormholeEmitter, - wormholeSequence, - wormholeFeeCollector, - clock, - sender: new PublicKey( - cpiProgramId === undefined ? payer : deriveSenderAccountKey(cpiProgramId), - ), - rent, - systemProgram, - wormholeProgram: new PublicKey(wormholeProgramId), - tokenProgram: TOKEN_PROGRAM_ID, - }; -} diff --git a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/program.ts b/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/program.ts deleted file mode 100644 index 35ebb8627..000000000 --- a/platforms/solana/protocols/tokenBridge/src/utils/tokenBridge/program.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Connection, PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import type { Provider } from '@coral-xyz/anchor'; -import { Program } from '@coral-xyz/anchor'; -import { utils } from '@wormhole-foundation/sdk-solana'; -import { TokenBridgeCoder } from './coder/index.js'; -import { - type TokenBridge, - TOKEN_BRIDGE_IDL as IDL, -} from '../../tokenBridgeType.js'; - -export function createTokenBridgeProgramInterface( - programId: PublicKeyInitData, - provider?: Provider, -): Program { - return new Program( - IDL as TokenBridge, - new PublicKey(programId), - provider === undefined ? ({ connection: null } as any) : provider, - coder(), - ); -} - -export function createReadOnlyTokenBridgeProgramInterface( - programId: PublicKeyInitData, - connection?: Connection, -): Program { - return createTokenBridgeProgramInterface( - programId, - utils.createReadOnlyProvider(connection), - ); -} - -export function coder(): TokenBridgeCoder { - return new TokenBridgeCoder(IDL as TokenBridge); -} diff --git a/platforms/solana/src/index.ts b/platforms/solana/src/index.ts index 144bf697f..1e3e70364 100644 --- a/platforms/solana/src/index.ts +++ b/platforms/solana/src/index.ts @@ -3,5 +3,7 @@ export * from './platform.js'; export * from './chain.js'; export * from './unsignedTransaction.js'; export * from './types.js'; -export * from './utils/index.js'; export * from './signer.js'; + +export * as testing from './testing/index.js'; +export * from './utils/index.js'; diff --git a/platforms/solana/src/testing/client/token-bridge.ts b/platforms/solana/src/testing/client/token-bridge.ts new file mode 100644 index 000000000..f57b1144a --- /dev/null +++ b/platforms/solana/src/testing/client/token-bridge.ts @@ -0,0 +1,305 @@ +import { SolanaTokenBridge } from '@wormhole-foundation/sdk-solana-tokenbridge'; +import { + Connection, + Keypair, + PublicKey, + Signer, + SystemProgram, + SYSVAR_RENT_PUBKEY, +} from '@solana/web3.js'; +import { Chain, Network, serializeLayout } from '@wormhole-foundation/sdk-base'; +import { + Contracts, + UniversalAddress, + createVAA, + VAA, +} from '@wormhole-foundation/sdk-definitions'; +import { utils as coreUtils } from '@wormhole-foundation/sdk-solana-core'; + +import { getBlockTime, sendAndConfirm } from './../helper.js'; +import { SolanaSendSigner } from '@wormhole-foundation/sdk-solana'; +import { signAndSendWait } from '@wormhole-foundation/sdk-connect'; +import { layoutItems } from '@wormhole-foundation/sdk-definitions'; +import { TestingWormholeCore } from './wormhole-core.js'; + +/** A Token Bridge wrapper allowing to write tests using this program in a local environment. */ +export class TestingTokenBridge { + static sequence = 100n; + public readonly client: SolanaTokenBridge; + + public signer: Signer; + public solanaProgram: PublicKey; + public testingCoreClient: TestingWormholeCore; + + /** + * + * @param solanaProgram The Solana Program used as a destination for the VAAs, _i.e._ the program being tested. + * @param contracts At least the addresses `coreBridge` and `tokenBridge` must be provided. + */ + constructor( + connection: Connection, + signer: Signer, + network: N, + contracts: Contracts, + coreClient: TestingWormholeCore, + solanaProgram: PublicKey, + ) { + this.signer = signer; + this.testingCoreClient = coreClient; + this.solanaProgram = solanaProgram; + this.client = new SolanaTokenBridge( + network, + 'Solana', + connection, + contracts, + ); + } + + get coreBridgeId() { + return this.client.coreBridge.coreBridge.programId; + } + + get keypair() { + return Keypair.fromSecretKey(this.signer.secretKey); + } + + get pda() { + return { + config: () => this.findPda(Buffer.from('config')), + + endpoint: (chain: Chain, address: UniversalAddress) => { + return this.findPda( + serializeLayout( + { ...layoutItems.chainItem(), endianness: 'big' }, + chain, + ), + address.toUint8Array(), + ); + }, + + claim: (emitterAddress: UniversalAddress, sequence: bigint) => { + const sequenceBytes = Buffer.alloc(8); + sequenceBytes.writeBigUInt64BE(sequence); + + return this.findPda( + emitterAddress.toUint8Array(), + Buffer.from([0, 1]), + sequenceBytes, + ); + }, + }; + } + + async initialize() { + const ix = await this.client.tokenBridge.methods + .initialize(this.coreBridgeId) + .accounts({ + payer: this.signer.publicKey, + config: this.pda.config(), + }) + .instruction(); + + return await sendAndConfirm(this.client.connection, ix, this.signer); + } + + async registerPeer(chain: Chain, address: UniversalAddress) { + const sequence = TestingTokenBridge.sequence++; + const timestamp = await getBlockTime(this.client.connection); + const emitterAddress = new UniversalAddress('00'.repeat(31) + '04'); + const rawVaa = createVAA('TokenBridge:RegisterChain', { + guardianSet: 0, + timestamp, + nonce: 0, + emitterChain: 'Solana', + emitterAddress, + sequence, + consistencyLevel: 1, + signatures: [], + payload: { + chain: 'Solana', + actionArgs: { foreignChain: chain, foreignAddress: address }, + }, + }); + const vaa = this.testingCoreClient.guardians.addSignatures(rawVaa, [0]); + const txs = this.client.coreBridge.postVaa(this.signer.publicKey, vaa); + const signer = new SolanaSendSigner( + this.client.connection, + 'Solana', + this.keypair, + false, + {}, + ); + await signAndSendWait(txs, signer); + + const vaaAddress = coreUtils.derivePostedVaaKey( + this.coreBridgeId, + Buffer.from(vaa.hash), + ); + + const ix = await this.client.tokenBridge.methods + .registerChain() + .accounts({ + payer: this.signer.publicKey, + vaa: vaaAddress, + endpoint: this.pda.endpoint(chain, address), + config: this.pda.config(), + claim: this.pda.claim(emitterAddress, sequence), + wormholeProgram: this.coreBridgeId, + rent: SYSVAR_RENT_PUBKEY, + systemProgram: SystemProgram.programId, + }) + .instruction(); + await sendAndConfirm(this.client.connection, ix, this.signer); + } + + async attestToken( + emitter: UniversalAddress, + chain: Chain, + mint: UniversalAddress, + info: { decimals: number }, + ) { + const signer = new SolanaSendSigner( + this.client.connection, + 'Solana', + this.keypair, + false, + {}, + ); + + const sequence = TestingTokenBridge.sequence++; + const timestamp = await getBlockTime(this.client.connection); + const rawVaa = createVAA('TokenBridge:AttestMeta', { + guardianSet: 0, + timestamp, + nonce: 0, + emitterChain: chain, + emitterAddress: emitter, + sequence, + consistencyLevel: 1, + signatures: [], + payload: { + decimals: info.decimals, + symbol: '12345678901234567890123456789012', + name: '12345678901234567890123456789012', + token: { chain, address: mint }, + }, + }); + const vaa = this.testingCoreClient.guardians.addSignatures(rawVaa, [0]); + const txsPostVaa = this.client.coreBridge.postVaa( + this.signer.publicKey, + vaa, + ); + await signAndSendWait(txsPostVaa, signer); + + const txsAttest = this.client.submitAttestation( + rawVaa, + this.signer.publicKey, + ); + await signAndSendWait(txsAttest, signer); + } + + /** + * Parse a VAA generated from the postVaa method, or from the Token Bridge during + * and outbound transfer + */ + async parseVaaTransferWithPayload( + account: PublicKey, + ): Promise> { + return this.testingCoreClient.parseVaa( + 'TokenBridge:TransferWithPayload', + account, + ); + } + + /** + * @param token Where the token comes from: chain, and mint/address. + * @param source Who emits the transfer: + * @param `source.tokenBridge` The foreign Token Bridge emitting the transfer; + * @param `source.relayer` The foreign smart contract having initiated the transfer (for example, and EVM relayer contract). + * @param innerPayload The payload associated with the transfer. + * @returns The address of the stored vaa/message. + */ + async postTransferWithPayload( + token: { + amount: bigint; + chain: Chain; + address: UniversalAddress; + }, + source: { + chain: Chain; + tokenBridge: UniversalAddress; + relayer: UniversalAddress; + }, + innerPayload: Uint8Array, + ): Promise { + const payload = { + token, + to: { + address: new UniversalAddress(this.solanaProgram.toBytes()), + chain: 'Solana' as const, + }, + from: source.relayer, + payload: innerPayload, + }; + + return this.testingCoreClient.postVaa( + this.signer, + source, + 'TokenBridge:TransferWithPayload', + payload, + ); + } + + /** + * Parse a VAA generated from the postVaa method, or from the Token Bridge during + * and outbound transfer + */ + async parseVaaTransfer( + account: PublicKey, + ): Promise> { + return this.testingCoreClient.parseVaa('TokenBridge:Transfer', account); + } + + /** + * @param token Where the token comes from: chain, and mint/address. + * @param source The foreign Token Bridge emitting the transfer. + * @param fee The fee to pay to get the tokens. + * @returns The address of the stored vaa/message. + */ + async postTransfer( + payer: Keypair, + token: { + amount: bigint; + chain: Chain; + address: UniversalAddress; + }, + source: { + chain: Chain; + tokenBridge: UniversalAddress; + }, + fee: bigint, + ): Promise { + const payload = { + token, + to: { + address: new UniversalAddress(this.solanaProgram.toBytes()), + chain: 'Solana' as const, + }, + fee, + }; + + return this.testingCoreClient.postVaa( + payer, + source, + 'TokenBridge:Transfer', + payload, + ); + } + + private findPda(...seeds: Array) { + return PublicKey.findProgramAddressSync( + seeds, + this.client.tokenBridge.programId, + )[0]; + } +} diff --git a/platforms/solana/src/testing/client/wormhole-core.ts b/platforms/solana/src/testing/client/wormhole-core.ts new file mode 100644 index 000000000..d7a46ba4e --- /dev/null +++ b/platforms/solana/src/testing/client/wormhole-core.ts @@ -0,0 +1,183 @@ +import anchor from '@coral-xyz/anchor'; +import { Connection, Keypair, PublicKey, Signer } from '@solana/web3.js'; +import { + Chain, + encoding, + layout, + Network, + signAndSendWait, +} from '@wormhole-foundation/sdk-connect'; +import { SolanaSendSigner } from '@wormhole-foundation/sdk-solana'; +import { + SolanaWormholeCore, + utils as coreUtils, +} from '@wormhole-foundation/sdk-solana-core'; +import { + serializePayload, + serialize as serializeVaa, + deserialize as deserializeVaa, + UniversalAddress, + createVAA, + Contracts, + PayloadLiteral, + Payload, + DistributiveVAA, +} from '@wormhole-foundation/sdk-definitions'; +import { mocks } from '@wormhole-foundation/sdk-definitions/testing'; + +import { getBlockTime, sendAndConfirm } from './../helper.js'; +import { accountDataLayout } from '../accountVaaLayout.js'; + +const guardianKey = + 'cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0'; +const guardianAddress = 'beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe'; +const guardians = new mocks.MockGuardians(0, [guardianKey]); + +/** A Wormhole Core wrapper allowing to write tests using this program in a local environment. */ +export class TestingWormholeCore { + public readonly signer: Signer; + public readonly client: SolanaWormholeCore; + private sequence = 0n; + + /** + * + * @param solanaProgram The Solana Program used as a destination for the VAAs, _i.e._ the program being tested. + * @param contracts At least the core program address `coreBridge` must be provided. + */ + constructor( + connection: Connection, + signer: Signer, + network: N, + contracts: Contracts, + ) { + this.signer = signer; + this.client = new SolanaWormholeCore( + network, + 'Solana', + connection, + contracts, + ); + } + + get guardians(): mocks.MockGuardians { + return guardians; + } + + get pda() { + return { + guardianSet: (): PublicKey => + this.findPda(Buffer.from('GuardianSet'), Buffer.from([0, 0, 0, 0])), + bridge: (): PublicKey => this.findPda(Buffer.from('Bridge')), + feeCollector: (): PublicKey => this.findPda(Buffer.from('fee_collector')), + }; + } + + async initialize() { + const guardianSetExpirationTime = 1_000_000; + const fee = new anchor.BN(1_000_000); + const initialGuardians = [Array.from(encoding.hex.decode(guardianAddress))]; + + // https://github.com/wormhole-foundation/wormhole/blob/main/solana/bridge/program/src/api/initialize.rs + const ix = await this.client.coreBridge.methods + .initialize(guardianSetExpirationTime, fee, initialGuardians) + .accounts({ + bridge: this.pda.bridge(), + guardianSet: this.pda.guardianSet(), + feeCollector: this.pda.feeCollector(), + payer: this.signer.publicKey, + }) + .instruction(); + + return await sendAndConfirm(this.client.connection, ix, this.signer); + } + + /** + * Parse a VAA generated from the postVaa method, or from the Token Bridge during + * and outbound transfer + */ + async parseVaa( + vaaType: PL, + account: PublicKey, + ): Promise> { + const info = await this.client.connection.getAccountInfo(account); + if (info === null) { + throw new Error( + `No message account exists at that address: ${account.toString()}`, + ); + } + + const message = layout.deserializeLayout(accountDataLayout, info.data); + + const vaa = createVAA('Uint8Array', { + guardianSet: 0, + timestamp: message.timestamp, + nonce: message.nonce, + emitterChain: message.emitterChain, + emitterAddress: message.emitterAddress, + sequence: message.sequence, + consistencyLevel: message.consistencyLevel, + signatures: [], + payload: message.payload, + }); + + return deserializeVaa( + vaaType, + serializeVaa(vaa), + ) as DistributiveVAA; + } + + /** + * `source`: the peers (Token Bridge and TBR) emitting the transfer. + * `token`: the origin of the token (chain and mint). + */ + async postVaa( + payer: Signer, + source: { + chain: Chain; + tokenBridge: UniversalAddress; + }, + payloadType: PL, + payload: Payload, + ): Promise { + const seq = this.sequence++; + const timestamp = await getBlockTime(this.client.connection); + + const emittingPeer = new mocks.MockEmitter( + source.tokenBridge, + source.chain, + seq, + ); + + const serializedPayload = serializePayload(payloadType, payload); + + const published = emittingPeer.publishMessage( + 0, // nonce, + serializedPayload, + 1, // consistencyLevel + timestamp, + ); + const vaa = guardians.addSignatures(published, [0]); + + const txs = this.client.postVaa(payer.publicKey, vaa); + const signer = new SolanaSendSigner( + this.client.connection, + 'Solana', + Keypair.fromSecretKey(payer.secretKey), + false, + {}, + ); + await signAndSendWait(txs, signer); + + return coreUtils.derivePostedVaaKey( + this.client.coreBridge.programId, + Buffer.from(vaa.hash), + ); + } + + private findPda(...seeds: Array) { + return PublicKey.findProgramAddressSync( + seeds, + this.client.coreBridge.programId, + )[0]; + } +} diff --git a/platforms/solana/src/testing/helper.ts b/platforms/solana/src/testing/helper.ts new file mode 100644 index 000000000..2475329cd --- /dev/null +++ b/platforms/solana/src/testing/helper.ts @@ -0,0 +1,409 @@ +// Solana +import { + AccountInfo, + Connection, + Finality, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + sendAndConfirmTransaction, + Signer, + SystemProgram, + Transaction, + TransactionInstruction, + TransactionSignature, + VersionedTransactionResponse, +} from '@solana/web3.js'; +import * as spl from '@solana/spl-token'; +// Node +import fs from 'fs/promises'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +// Wormhole +import { extractPubkey, HasPublicKey } from './index.js'; +import { UniversalAddress } from '../../../../core/definitions/src/universalAddress.js'; +import { Network } from '@wormhole-foundation/sdk-base'; +import { Contracts } from '@wormhole-foundation/sdk-definitions'; +import { TestingWormholeCore } from './client/wormhole-core.js'; +import { TestingTokenBridge } from './client/token-bridge.js'; +import { encoding } from '@wormhole-foundation/sdk-base'; + +const execAsync = promisify(exec); + +type Tuple = R['length'] extends N + ? R + : Tuple; + +/** + * A helper to be used for Solana integration tests (Node environment). + * + * It is recomended to create one instance with a short name, allowing to chain calls easily: + * + * ```ts + * const h = new TestsHelper(); + * const [keypair1, keypair2] = await h.airdrop(h.keypair.several(2)); + * ``` + */ +export class TestHelper { + static readonly LOCALHOST = 'http://localhost:8899'; + + readonly connection: Connection; + readonly finality: Finality; + + private static readonly connectionsCache: Partial< + Record + > = {}; + + constructor(finality: Finality = 'confirmed') { + if (TestHelper.connectionsCache[finality] === undefined) { + TestHelper.connectionsCache[finality] = new Connection( + TestHelper.LOCALHOST, + finality, + ); + } + this.connection = TestHelper.connectionsCache[finality]; + this.finality = finality; + } + + pubkey = { + generate: (): PublicKey => PublicKey.unique(), + read: async (path: string): Promise => + this.keypair.read(path).then((kp) => kp.publicKey), + from: (hasPublicKey: HasPublicKey): PublicKey => + extractPubkey(hasPublicKey), + several: (amount: number): Tuple => + Array.from({ length: amount }).map(PublicKey.unique) as Tuple< + PublicKey, + N + >, + }; + + keypair = { + generate: (): Keypair => Keypair.generate(), + read: async (path: string): Promise => + this.keypair.from( + JSON.parse(await fs.readFile(path, { encoding: 'utf8' })), + ), + from: (bytes: number[]): Keypair => + Keypair.fromSecretKey(Uint8Array.from(bytes)), + several: (amount: N): Tuple => + Array.from({ length: amount }).map(Keypair.generate) as Tuple, + }; + + universalAddress = { + generate: (ethereum?: 'ethereum'): UniversalAddress => + new UniversalAddress( + ethereum === 'ethereum' + ? encoding.bytes.concat( + new Uint8Array(12), + PublicKey.unique().toBytes().subarray(12), + ) + : PublicKey.unique().toBytes(), + ), + several: ( + amount: number, + ethereum?: 'ethereum', + ): Tuple => + Array.from({ length: amount }).map(() => + this.universalAddress.generate(ethereum), + ) as Tuple, + }; + + /** Gets the transaction detail from a transaction signature. */ + async getTransaction( + signature: TransactionSignature | Promise, + ): Promise { + return this.connection.getTransaction(await signature, { + commitment: this.finality, + maxSupportedTransactionVersion: 1, + }); + } + + async getBlockTime(): Promise { + return getBlockTime(this.connection); + } + + /** Waits that a transaction is confirmed. */ + async confirm(signature: TransactionSignature) { + const latestBlockHash = await this.connection.getLatestBlockhash(); + + return this.connection.confirmTransaction({ + signature, + blockhash: latestBlockHash.blockhash, + lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, + }); + } + + /** Sends a transaction, then wait that it is confirmed. */ + async sendAndConfirm( + ixs: TransactionInstruction | Transaction | Array, + payer: Signer, + ...signers: Signer[] + ): Promise { + return sendAndConfirm(this.connection, ixs, payer, ...signers); + } + + /** Airdrops lamports to an account or several ones. */ + async airdrop>( + to: T, + lamports: number = 50 * LAMPORTS_PER_SOL, + ): Promise { + function isArrayLikePubkey( + o: HasPublicKey | ArrayLike, + ): o is ArrayLike { + return ( + o != null && + isArrayLike(o) && + Array.from(o).every( + (value) => typeof value !== 'number', + ) + ); + } + + const request = async (account: PublicKey) => + this.confirm(await this.connection.requestAirdrop(account, lamports)); + + if (isArrayLikePubkey(to)) { + await Promise.all( + Array.from(to).map((account) => request(extractPubkey(account))), + ); + } else { + await request(extractPubkey(to)); + } + + return to; + } + + /** Deploys a new program. */ + async deploy(paths: { + programKeypair: string; + authorityKeypair: string; + binary: string; + }) { + const { programKeypair, authorityKeypair, binary } = paths; + const BpfProgramId = new PublicKey( + 'BPFLoaderUpgradeab1e11111111111111111111111', + ); + + // Deploy: + await execAsync( + `solana --url ${TestHelper.LOCALHOST} -k ${authorityKeypair} program deploy ${binary} --program-id ${programKeypair}`, + ); + + // Wait for deploy to be finalized (otherwise there can be a problem where the + // function returns, but calling a program method fails): + const programId = await this.pubkey.read(programKeypair); + const programDataAddress = PublicKey.findProgramAddressSync( + [programId.toBuffer()], + BpfProgramId, + )[0]; + let programAccount: AccountInfo | null = null; + while (programAccount === null) { + programAccount = await this.connection.getAccountInfo( + programDataAddress, + 'finalized', + ); + } + } + + // SPL + + /** + * Creates a new token account and transfers wrapped SOL to it. + * If none is provided, a new keypair is generated. + */ + async wrapSol( + signer: Signer, + amount: number, + tokenAccount: Keypair = Keypair.generate(), + ): Promise { + const tx = new Transaction().add( + // Allocate account: + SystemProgram.createAccount({ + fromPubkey: signer.publicKey, + newAccountPubkey: tokenAccount.publicKey, + space: spl.ACCOUNT_SIZE, + lamports: await spl.getMinimumBalanceForRentExemptAccount( + this.connection, + ), + programId: spl.TOKEN_PROGRAM_ID, + }), + // Initialize token account: + spl.createInitializeAccountInstruction( + tokenAccount.publicKey, + spl.NATIVE_MINT, + signer.publicKey, + ), + // Transfer SOL: + SystemProgram.transfer({ + fromPubkey: signer.publicKey, + toPubkey: tokenAccount.publicKey, + lamports: amount, + }), + // Move the lamports as wSOL: + spl.createSyncNativeInstruction(tokenAccount.publicKey), + ); + + await this.sendAndConfirm(tx, signer, tokenAccount); + + return tokenAccount; + } + + /** Creates a new mint, and returns an object allowing to run common operations against it. */ + createMint(authority: Signer, decimals: number): Promise { + return TestsMint.create(this.connection, authority, decimals); + } + + createCoreClient( + connection: Connection, + defaultSigner: Signer, + network: N, + contracts: Contracts, + ): TestingWormholeCore { + return new TestingWormholeCore( + connection, + defaultSigner, + network, + contracts, + ); + } + + /** + * + * @param solanaProgram The Solana Program used as a destination for the VAAs, _i.e._ the program being tested. + * @param contracts At least the addresses `coreBridge` and `tokenBridge` must be provided. + */ + createTokenBridgeClient( + connection: Connection, + defaultSigner: Signer, + network: N, + contracts: Contracts, + solanaProgram: PublicKey, + ): TestingTokenBridge { + return new TestingTokenBridge( + connection, + defaultSigner, + network, + contracts, + this.createCoreClient(connection, defaultSigner, network, contracts), + solanaProgram, + ); + } +} + +export async function getBlockTime(connection: Connection): Promise { + // This should never fail. + return connection + .getSlot() + .then(async (slot) => connection.getBlockTime(slot)) + .then((value) => value ?? 0); +} + +/** Sends a transaction, then wait that it is confirmed. */ +export async function sendAndConfirm( + connection: Connection, + ixs: TransactionInstruction | Transaction | Array, + payer: Signer, + ...signers: Signer[] +): Promise { + const { value } = await connection.getLatestBlockhashAndContext(); + const tx = new Transaction({ + ...value, + feePayer: payer.publicKey, + }).add(...(Array.isArray(ixs) ? ixs : [ixs])); + + return sendAndConfirmTransaction(connection, tx, [payer, ...signers], {}); +} + +export class TestsMint { + readonly connection: Connection; + readonly address: PublicKey; + readonly authority: Signer; + readonly decimals: number; + + private constructor( + connection: Connection, + authority: Signer, + address: PublicKey, + decimals: number, + ) { + this.authority = authority; + this.connection = connection; + this.address = address; + this.decimals = decimals; + } + + static async create( + connection: Connection, + authority: Signer, + decimals: number, + ): Promise { + return new TestsMint( + connection, + authority, + await spl.createMint( + connection, + authority, + authority.publicKey, + authority.publicKey, + decimals, + ), + decimals, + ); + } + + /** + * Mints new tokens to the token account (if none is provided, a new one is generated). + * Returns the token account keypair. + */ + async mint( + amount: number | bigint, + accountAuthority: Signer, + tokenAccount: Keypair = Keypair.generate(), + ): Promise { + const tx = new Transaction().add( + // Allocate account: + SystemProgram.createAccount({ + fromPubkey: accountAuthority.publicKey, + newAccountPubkey: tokenAccount.publicKey, + space: spl.ACCOUNT_SIZE, + lamports: await spl.getMinimumBalanceForRentExemptAccount( + this.connection, + ), + programId: spl.TOKEN_PROGRAM_ID, + }), + + // Initialize token account: + spl.createInitializeAccountInstruction( + tokenAccount.publicKey, + this.address, + accountAuthority.publicKey, + ), + + // Mint the tokens to the newly created account: + spl.createMintToCheckedInstruction( + this.address, + tokenAccount.publicKey, + this.authority.publicKey, + amount, + this.decimals, + ), + ); + + await sendAndConfirm( + this.connection, + tx, + accountAuthority, + this.authority, + tokenAccount, + ); + + return tokenAccount; + } +} + +// PRIVATE HELPERS: + +function isArrayLike(o: any): o is ArrayLike { + return o != null && typeof o[Symbol.iterator] === 'function'; +} diff --git a/platforms/solana/src/testing/index.ts b/platforms/solana/src/testing/index.ts new file mode 100644 index 000000000..285aa3a10 --- /dev/null +++ b/platforms/solana/src/testing/index.ts @@ -0,0 +1,36 @@ +export * from './client/token-bridge.js'; +export * from './client/wormhole-core.js'; +export * from './accountVaaLayout.js'; +export * from './helper.js'; + +import { Keypair, PublicKey, Signer } from '@solana/web3.js'; +import { UniversalAddress } from '../../../../core/definitions/src/universalAddress.js'; + +/** + * Accepted as public key are: + * - The Solana `PublicKey`; + * - The Wormhole `UniversalAddress`; + * - A type implementing the interface `Signer` from Solana; + * - Any kind of array of bytes holding a secret key. + */ +export type HasPublicKey = + | PublicKey + | UniversalAddress + | Signer + | ArrayLike; + +/* Returns the public key from a piece of data holding one. */ +export function extractPubkey(from: HasPublicKey): PublicKey { + const isSigner = (from: HasPublicKey): from is Signer => + from.hasOwnProperty('publicKey'); + + if (from instanceof PublicKey || from?.constructor.name === 'PublicKey') { + return from as PublicKey; + } else if (from instanceof UniversalAddress) { + return new PublicKey(from.toUint8Array()); + } else if (isSigner(from)) { + return from.publicKey; + } else { + return Keypair.fromSecretKey(Uint8Array.from(from)).publicKey; + } +} diff --git a/platforms/solana/src/utils/account.ts b/platforms/solana/src/utils/account.ts new file mode 100644 index 000000000..b8a95fb4b --- /dev/null +++ b/platforms/solana/src/utils/account.ts @@ -0,0 +1,93 @@ +import type { PublicKeyInitData } from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; +import * as spl from '@solana/spl-token'; + +type Seed = string | Uint8Array; + +/** + * Find the first valid program address. See {@link PublicKey.findProgramAddressSync} for details. + * + * @param {string | Uint8Array | + * readonly (string | Uint8Array)[]} seeds - seeds for PDA + * @param {PublicKeyInitData} programId - program address + * @returns PDA + */ +export function deriveAddress( + seeds: Seed | readonly Seed[], + programId: PublicKeyInitData, +): PublicKey { + const toBytes = (s: Seed) => (typeof s === 'string' ? Buffer.from(s) : s); + + return PublicKey.findProgramAddressSync( + Array.isArray(seeds) ? seeds.map(toBytes) : [toBytes(seeds as Seed)], + new PublicKey(programId), + )[0]; +} + +/** + * Allows to create an `AccountMeta` object with a simplified syntax. By default, + * `isSigner` and `isWritable` are false, and they can be activated with a function: + * + * - `meta(pubkey)`: non signer, non writable; + * - `meta(pubkey).signer()`: signer only, non writable; + * - `meta(pubkey).writable()`: writable only, non signer; + * - `meta(pubkey).signer().writable()` or `meta(pubkey).writable().signer()`. + * @param pubkey The public key used to create the meta object. + * @returns + */ +export function meta(pubkey: PublicKeyInitData) { + class AccountMetaBuilder { + constructor( + public pubkey: PublicKey, + public isSigner: boolean = false, + public isWritable: boolean = false, + ) {} + + public signer() { + return new AccountMetaSigner(this.pubkey); + } + + public writable() { + return new AccountMetaWritable(this.pubkey); + } + } + + class AccountMetaSigner { + constructor( + public pubkey: PublicKey, + public isSigner: boolean = true, + public isWritable: boolean = false, + ) {} + + public writable() { + return new AccountMetaSignerWritable(this.pubkey); + } + } + + class AccountMetaWritable { + constructor( + public pubkey: PublicKey, + public isSigner: boolean = false, + public isWritable: boolean = true, + ) {} + + public signer() { + return new AccountMetaSignerWritable(this.pubkey); + } + } + + class AccountMetaSignerWritable { + constructor( + public pubkey: PublicKey, + public isSigner: boolean = true, + public isWritable: boolean = true, + ) {} + } + + return new AccountMetaBuilder(new PublicKey(pubkey)); +} + +export function isNativeMint(mintAddress: PublicKeyInitData): boolean { + const mint = new PublicKey(mintAddress); + return mint.equals(spl.NATIVE_MINT) || mint.equals(spl.NATIVE_MINT_2022); +} diff --git a/platforms/solana/src/utils/anchor/common.ts b/platforms/solana/src/utils/anchor/common.ts deleted file mode 100644 index 2fb0119a5..000000000 --- a/platforms/solana/src/utils/anchor/common.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { Idl, IdlField, IdlTypeDef, IdlType } from './idl.js'; -import { IdlError } from './error.js'; - -export function accountSize(idl: Idl, idlAccount: IdlTypeDef) { - switch (idlAccount.type.kind) { - case 'struct': { - return idlAccount.type.fields - .map((f) => typeSize(idl, f.type)) - .reduce((acc, size) => acc + size, 0); - } - - case 'enum': { - const variantSizes = idlAccount.type.variants.map((variant) => { - if (!variant.fields) { - return 0; - } - - return variant.fields - .map((f: IdlField | IdlType) => { - // Unnamed enum variant - if (!(typeof f === 'object' && 'name' in f)) { - return typeSize(idl, f); - } - - // Named enum variant - return typeSize(idl, f.type); - }) - .reduce((acc, size) => acc + size, 0); - }); - - return Math.max(...variantSizes) + 1; - } - - case 'alias': { - return typeSize(idl, idlAccount.type.value); - } - } -} - -// Returns the size of the type in bytes. For variable length types, just return -// 1. Users should override this value in such cases. -function typeSize(idl: Idl, ty: IdlType): number { - switch (ty) { - case 'bool': - return 1; - case 'u8': - return 1; - case 'i8': - return 1; - case 'i16': - return 2; - case 'u16': - return 2; - case 'u32': - return 4; - case 'i32': - return 4; - case 'f32': - return 4; - case 'u64': - return 8; - case 'i64': - return 8; - case 'f64': - return 8; - case 'u128': - return 16; - case 'i128': - return 16; - case 'u256': - return 32; - case 'i256': - return 32; - case 'bytes': - return 1; - case 'string': - return 1; - case 'publicKey': - return 32; - default: - if ('vec' in ty) { - return 1; - } - if ('option' in ty) { - return 1 + typeSize(idl, ty.option); - } - if ('coption' in ty) { - return 4 + typeSize(idl, ty.coption); - } - if ('defined' in ty) { - const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? []; - if (filtered.length !== 1) { - throw new IdlError(`Type not found: ${JSON.stringify(ty)}`); - } - let typeDef = filtered[0]; - - return accountSize(idl, typeDef!); - } - if ('array' in ty) { - let arrayTy = ty.array[0]; - let arraySize = ty.array[1]; - return typeSize(idl, arrayTy) * arraySize; - } - throw new Error(`Invalid type ${JSON.stringify(ty)}`); - } -} diff --git a/platforms/solana/src/utils/anchor/error.ts b/platforms/solana/src/utils/anchor/error.ts deleted file mode 100644 index df6337ee9..000000000 --- a/platforms/solana/src/utils/anchor/error.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Borrowed from coral-xyz/anchor -// -// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/error.ts - -export class IdlError extends Error { - constructor(message: string) { - super(message); - this.name = 'IdlError'; - } -} diff --git a/platforms/solana/src/utils/anchor/idl.ts b/platforms/solana/src/utils/anchor/idl.ts deleted file mode 100644 index ea0426e32..000000000 --- a/platforms/solana/src/utils/anchor/idl.ts +++ /dev/null @@ -1,216 +0,0 @@ -// Borrowed from coral-xyz/anchor -// -// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/idl.ts - -import * as borsh from '@coral-xyz/borsh'; -import { PublicKey } from '@solana/web3.js'; -import { Buffer } from 'buffer'; - -export type Idl = { - version: string; - name: string; - docs?: string[]; - instructions: IdlInstruction[]; - accounts?: IdlAccountDef[]; - types?: IdlTypeDef[]; - events?: IdlEvent[]; - errors?: IdlErrorCode[]; - constants?: IdlConstant[]; - metadata?: IdlMetadata; -}; - -export type IdlMetadata = any; - -export type IdlConstant = { - name: string; - type: IdlType; - value: string; -}; - -export type IdlEvent = { - name: string; - fields: IdlEventField[]; -}; - -export type IdlEventField = { - name: string; - type: IdlType; - index: boolean; -}; - -export type IdlInstruction = { - name: string; - docs?: string[]; - accounts: IdlAccountItem[]; - args: IdlField[]; - returns?: IdlType; -}; - -export type IdlStateMethod = IdlInstruction; - -export type IdlAccountItem = IdlAccount | IdlAccounts; - -export function isIdlAccounts( - accountItem: IdlAccountItem, -): accountItem is IdlAccounts { - return 'accounts' in accountItem; -} - -export type IdlAccount = { - name: string; - isMut: boolean; - isSigner: boolean; - isOptional?: boolean; - docs?: string[]; - relations?: string[]; - pda?: IdlPda; -}; - -export type IdlPda = { - seeds: IdlSeed[]; - programId?: IdlSeed; -}; - -export type IdlSeed = any; // TODO - -// A nested/recursive version of IdlAccount. -export type IdlAccounts = { - name: string; - docs?: string[]; - accounts: IdlAccountItem[]; -}; - -export type IdlField = { - name: string; - docs?: string[]; - type: IdlType; -}; - -export type IdlTypeDef = { - name: string; - docs?: string[]; - type: IdlTypeDefTy; -}; - -export type IdlAccountDef = { - name: string; - docs?: string[]; - type: IdlTypeDefTyStruct; -}; - -export type IdlTypeDefTyStruct = { - kind: 'struct'; - fields: IdlTypeDefStruct; -}; - -export type IdlTypeDefTyEnum = { - kind: 'enum'; - variants: IdlEnumVariant[]; -}; - -export type IdlTypeDefTyAlias = { - kind: 'alias'; - value: IdlType; -}; - -export type IdlTypeDefTy = - | IdlTypeDefTyEnum - | IdlTypeDefTyStruct - | IdlTypeDefTyAlias; - -export type IdlTypeDefStruct = Array; - -export type IdlType = - | 'bool' - | 'u8' - | 'i8' - | 'u16' - | 'i16' - | 'u32' - | 'i32' - | 'f32' - | 'u64' - | 'i64' - | 'f64' - | 'u128' - | 'i128' - | 'u256' - | 'i256' - | 'bytes' - | 'string' - | 'publicKey' - | IdlTypeDefined - | IdlTypeOption - | IdlTypeCOption - | IdlTypeVec - | IdlTypeArray; - -// User defined type. -export type IdlTypeDefined = { - defined: string; -}; - -export type IdlTypeOption = { - option: IdlType; -}; - -export type IdlTypeCOption = { - coption: IdlType; -}; - -export type IdlTypeVec = { - vec: IdlType; -}; - -export type IdlTypeArray = { - array: [idlType: IdlType, size: number]; -}; - -export type IdlEnumVariant = { - name: string; - fields?: IdlEnumFields; -}; - -export type IdlEnumFields = IdlEnumFieldsNamed | IdlEnumFieldsTuple; - -export type IdlEnumFieldsNamed = IdlField[]; - -export type IdlEnumFieldsTuple = IdlType[]; - -export type IdlErrorCode = { - code: number; - name: string; - msg?: string; -}; - -// Deterministic IDL address as a function of the program id. -export async function idlAddress(programId: PublicKey): Promise { - const base = (await PublicKey.findProgramAddress([], programId))[0]; - return await PublicKey.createWithSeed(base, seed(), programId); -} - -// Seed for generating the idlAddress. -export function seed(): string { - return 'anchor:idl'; -} - -// The on-chain account of the IDL. -export interface IdlProgramAccount { - authority: PublicKey; - data: Buffer; -} - -const IDL_ACCOUNT_LAYOUT: borsh.Layout = borsh.struct([ - borsh.publicKey('authority'), - borsh.vecU8('data'), -]); - -export function decodeIdlAccount(data: Buffer): IdlProgramAccount { - return IDL_ACCOUNT_LAYOUT.decode(data); -} - -export function encodeIdlAccount(acc: IdlProgramAccount): Buffer { - const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer. - const len = IDL_ACCOUNT_LAYOUT.encode(acc, buffer); - return buffer.slice(0, len); -} diff --git a/platforms/solana/src/utils/anchor/index.ts b/platforms/solana/src/utils/anchor/index.ts deleted file mode 100644 index 7dfe2f14d..000000000 --- a/platforms/solana/src/utils/anchor/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './error.js'; -export * from './idl.js'; -export * from './common.js'; diff --git a/platforms/solana/src/utils/bpfLoaderUpgradeable.ts b/platforms/solana/src/utils/bpfLoaderUpgradeable.ts new file mode 100644 index 000000000..b6d387fe5 --- /dev/null +++ b/platforms/solana/src/utils/bpfLoaderUpgradeable.ts @@ -0,0 +1,135 @@ +import { + Connection, + PublicKeyInitData, + TransactionInstruction, +} from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; +import { + deserializeLayout, + serializeLayout, + type CustomConversion, + type Layout, +} from '@wormhole-foundation/sdk-connect'; +import { meta } from './account.js'; + +export const BPF_LOADER_UPGRADEABLE_PROGRAM_ID = new PublicKey( + 'BPFLoaderUpgradeab1e11111111111111111111111', +); + +//The program data pda coincides with the address that's stored in the program id account (i.e. the +// account that's found at the program id address), which is of type UpgradeLoaderState::Program: +// https://docs.rs/solana-program/latest/src/solana_program/bpf_loader_upgradeable.rs.html#40-43 +export function programDataAddress(programId: PublicKeyInitData) { + return PublicKey.findProgramAddressSync( + [new PublicKey(programId).toBytes()], + BPF_LOADER_UPGRADEABLE_PROGRAM_ID, + )[0]; +} + +export async function fetchProgramData( + connection: Connection, + programId: PublicKeyInitData, +): Promise<{ + slot: bigint; + upgradeAuthority: PublicKey | undefined; +}> { + const accountInfo = await connection.getAccountInfo(new PublicKey(programId)); + if (accountInfo === null) { + throw new Error(`Could not read the deployed program: ${programId}`); + } + + const data = deserializeLayout(programDataLayout, accountInfo.data); + const upgradeAuthority = data.upgradeAuthority.isSome + ? data.upgradeAuthority.value + : undefined; + + return { + slot: data.slot, + upgradeAuthority, + }; +} + +export async function setProgramAuthority( + connection: Connection, + programId: PublicKeyInitData, + newAuthority: PublicKeyInitData, +): Promise { + const SET_AUTHORITY_CODE = 4; + + const { upgradeAuthority } = await fetchProgramData(connection, programId); + if (upgradeAuthority === undefined) { + throw new Error( + `Cannot set a new authority for the program ${programId} as the program is not upgradeable`, + ); + } + + const accountsInfo = [ + meta(programDataAddress(programId)).writable(), + meta(upgradeAuthority).signer(), + meta(new PublicKey(newAuthority)), + ]; + + return new TransactionInstruction({ + keys: accountsInfo, + programId: BPF_LOADER_UPGRADEABLE_PROGRAM_ID, + data: Buffer.from( + serializeLayout({ ...onChainUint, size: 4 }, SET_AUTHORITY_CODE), + ), + }); +} + +const onChainUint = { binary: 'uint', endianness: 'little' } as const; + +const pubKeyConversion = { + //TODO find a better place for this + to: (encoded: Uint8Array) => new PublicKey(encoded), + from: (decoded: PublicKey) => decoded.toBytes(), +} as const satisfies CustomConversion; + +//Describes the layout of an account that holds a UpgradeableLoaderState::ProgramData enum: +// https://docs.rs/solana-program/latest/src/solana_program/bpf_loader_upgradeable.rs.html#45-52 +// because neither Anchor nor Solana web3 seem to have a built-in way to parse this. +//The bpf_loader_upgradeable program uses Rust's serde crate and bincode to serialize its structs, +// which encodes enum variants as 4 byte little endian uints: +// https://github.com/serde-rs/serde/blob/9f8c579bf5f7478f91108c1186cd0d3f85aff29d/serde_derive/src/ser.rs#L399-L408 +// and Options with a single byte 0 or 1 tag: +// https://docs.rs/bincode/latest/src/bincode/ser/mod.rs.html#137-147 +//However, even if the program is made immutable the bpf_loader_upgradeable program will keep the +// last value of the enum variant and only set the option byte tag to 0, presumably so they don't +// have to memcopy the entire subsequent bytecode (they didn't really think that one through). +//See https://explorer.solana.com/address/GDDMwNyyx8uB6zrqwBFHjLLG3TBYk2F8Az4yrQC5RzMp +// as an example of an immutable program data account. +export const programDataLayout = [ + { + name: 'programDataEnumVariant', + ...onChainUint, + size: 4, + custom: 3, + omit: true, + }, + { name: 'slot', ...onChainUint, size: 8 }, + { + name: 'upgradeAuthority', + binary: 'switch', + idSize: 1, + idTag: 'isSome', + layouts: [ + [ + [0, false], + [{ name: '_lastValueBeforeImmutability', binary: 'bytes', size: 32 }], + ], + [ + [1, true], + [ + { + name: 'value', + binary: 'bytes', + size: 32, + custom: pubKeyConversion, + }, + ], + ], + ], + }, + { name: 'bytecode', binary: 'bytes' }, +] as const satisfies Layout; diff --git a/platforms/solana/src/utils/index.ts b/platforms/solana/src/utils/index.ts index dd96fa4ca..302598c8c 100644 --- a/platforms/solana/src/utils/index.ts +++ b/platforms/solana/src/utils/index.ts @@ -1,18 +1,2 @@ -export * as utils from './utils/index.js'; -export * as anchor from './anchor/index.js'; - -// camel case a string (from https://stackoverflow.com/a/2970667) -export function camelCase(str: string): string { - return ( - str - .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) { - return index === 0 ? word.toLowerCase() : word.toUpperCase(); - }) - // replace any spaces, hyphens, or underscores with an empty string - .replace(/[\s\-_]+/g, '') - ); -} - -export function upperFirst(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); -} +export * from './account.js'; +export * as bpfLoaderUpgradeable from './bpfLoaderUpgradeable.js'; diff --git a/platforms/solana/src/utils/misc.ts b/platforms/solana/src/utils/misc.ts new file mode 100644 index 000000000..cc1c8a20a --- /dev/null +++ b/platforms/solana/src/utils/misc.ts @@ -0,0 +1,160 @@ +import { + Cluster, + clusterApiUrl, + Connection, + PublicKey, + PublicKeyInitData, + SimulatedTransactionResponse, + TransactionInstruction, + TransactionMessage, + TransactionResponse, + VersionedTransaction, + VersionedTransactionResponse, +} from '@solana/web3.js'; +import { + deserializeLayout, + Layout, + LayoutToType, + Network, +} from '@wormhole-foundation/sdk-base'; + +/** + * Simulates the transaction and returns the result. Throws if it failed. + * @param connection The connection used to run the simulation. + * @param payer The payer. No signature is needed, so no fee will be payed. + * @param instructions The instructions to simulate. + * @returns + */ +export async function simulateTransaction( + connection: Connection, + payer: PublicKeyInitData, + instructions: TransactionInstruction[], +): Promise { + const { + value: { blockhash }, + } = await connection.getLatestBlockhashAndContext(); + const txMessage = new TransactionMessage({ + payerKey: new PublicKey(payer), + recentBlockhash: blockhash, + instructions, + }).compileToV0Message(); + + const { value: response } = await connection.simulateTransaction( + new VersionedTransaction(txMessage), + { + sigVerify: false, + }, + ); + + if (response.err !== null) { + throw new Error('Transaction simulation failed', { cause: response.err }); + } + + return response; +} + +/** + * Finds the Solana cluster targeted by a connection by comparing its genesis hash with each + * cluster's. + * + * @NOTE The Solana cluster name does not match the Wormhole network name: + * ```md + * Environment | Solana | Wormhole + * ------------------------------------- + * Production | mainnet-beta | Mainnet + * Staging | devnet | Testnet + * Local | undefined | Devnet + * ``` + * + * @NOTE To get the Wormhole network, call `wormholeNetworkFromConnection`. + * @param connection + * @returns The corresponding Solana cluster, or `undefined` if there is no match, typically in the + * case of a local connection. + */ +export async function solanaClusterFromConnection( + connection: Connection, +): Promise { + const initializeCache = async () => + ( + await Promise.all( + (['testnet', 'devnet', 'mainnet-beta'] as const).map((cluster) => + new Connection(clusterApiUrl(cluster), 'singleGossip') + .getGenesisHash() + .then((genesis) => ({ [genesis]: cluster })), + ), + ) + ).reduce((acc, obj) => ({ ...obj, ...acc }), {}); + + genesisHashCache ??= await initializeCache(); + + return genesisHashCache[await connection.getGenesisHash()]; +} +let genesisHashCache: Record | undefined; + +/** + * Finds the Wormhole network targeted by a connection by comparing its genesis hash with each + * cluster's. + * + * @NOTE The Wormhole network name does not match the Solana cluster name: + * ```md + * Environment | Wormhole | Solana + * ------------------------------------- + * Production | Mainnet | mainnet-beta + * Staging | Testnet | devnet + * Local | Devnet | undefined + * ``` + * + * @NOTE To get the Solana cluster, call `solanaClusterFromConnection`. + * @param connection + * @returns The corresponding Wormhole network, or `undefined` if there is no match, typically in the + * case of a local connection. + */ +export async function wormholeNetworkFromConnection( + connection: Connection, +): Promise { + switch (await solanaClusterFromConnection(connection)) { + case 'mainnet-beta': + return 'Mainnet'; + case 'devnet': + return 'Testnet'; + case 'testnet': // Solana Testnet isn't taken into account by Wormhole. + return undefined; + default: // By default, we assume it is a local environment. + return 'Devnet'; + } +} + +/** + * Gets the data returned from a transaction runned against an Anchor program. + * @param typeLayout The layout of the returned data. + * @param confirmedTransaction The transaction having returned the data. + * @returns + */ +export function returnedDataFromTransaction( + typeLayout: L, + confirmedTransaction: + | VersionedTransactionResponse + | TransactionResponse + | SimulatedTransactionResponse, +): LayoutToType { + const prefix = 'Program return: '; + const logs = + 'meta' in confirmedTransaction + ? confirmedTransaction.meta?.logMessages + : confirmedTransaction.logs; + if (logs == null) { + throw new Error('Internal error: No logs in this transaction'); + } + + const log = logs.find((log) => log.startsWith(prefix)); + if (log === undefined) { + throw new Error('No returned value specified in these logs'); + } + + // The line looks like 'Program return: ': + const [, data] = log.slice(prefix.length).split(' ', 2); + + return deserializeLayout(typeLayout, Buffer.from(data ?? '', 'base64'), { + consumeAll: true, + }); +} diff --git a/platforms/solana/src/utils/utils/account.ts b/platforms/solana/src/utils/utils/account.ts deleted file mode 100644 index f8320e79b..000000000 --- a/platforms/solana/src/utils/utils/account.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { - AccountMeta, - AccountInfo, - PublicKeyInitData, -} from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; - -/** - * Find valid program address. See {@link PublicKey.findProgramAddressSync} for details. - * - * @param {string | Buffer | Uint8Array | - * readonly (string | Buffer | Uint8Array)[]} seeds - seeds for PDA - * @param {PublicKeyInitData} programId - program address - * @returns PDA - */ -type Seed = string | Buffer | Uint8Array; -const toBytes = (s: Seed) => typeof s === "string" ? Buffer.from(s) : s; -export function deriveAddress( - seeds: Seed | readonly Seed[], - programId: PublicKeyInitData, -): PublicKey { - return PublicKey.findProgramAddressSync( - Array.isArray(seeds) ? seeds.map(toBytes) : [toBytes(seeds as Seed)], - new PublicKey(programId) - )[0]; -} - -/** - * Factory to create AccountMeta with `isWritable` set to `true` - * - * @param {PublicKEyInitData} pubkey - account address - * @param {boolean} isSigner - whether account authorized transaction - * @returns metadata for writable account - */ -export function newAccountMeta( - pubkey: PublicKeyInitData, - isSigner: boolean, -): AccountMeta { - return { - pubkey: new PublicKey(pubkey), - isWritable: true, - isSigner, - }; -} - -/** - * Factory to create AccountMeta with `isWritable` set to `false` - * - * @param {PublicKEyInitData} pubkey - account address - * @param {boolean} isSigner - whether account authorized transaction - * @returns metadata for read-only account - */ -export function newReadOnlyAccountMeta( - pubkey: PublicKeyInitData, - isSigner: boolean, -): AccountMeta { - return { - pubkey: new PublicKey(pubkey), - isWritable: false, - isSigner, - }; -} - -/** - * Get serialized data from account - * - * @param {AccountInfo} info - Solana AccountInfo - * @returns serialized data as Buffer - */ -export function getAccountData(info: AccountInfo | null): Buffer { - if (info === null) { - throw Error('account info is null'); - } - return info.data; -} diff --git a/platforms/solana/src/utils/utils/bpfLoaderUpgradeable.ts b/platforms/solana/src/utils/utils/bpfLoaderUpgradeable.ts deleted file mode 100644 index b163e8413..000000000 --- a/platforms/solana/src/utils/utils/bpfLoaderUpgradeable.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { PublicKeyInitData } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import { deriveAddress } from './account.js'; - -import type { CustomConversion, Layout } from '@wormhole-foundation/sdk-connect'; - -export const BPF_LOADER_UPGRADEABLE_PROGRAM_ID = new PublicKey( - "BPFLoaderUpgradeab1e11111111111111111111111" -); - -export function deriveProgramDataAddress(programId: PublicKeyInitData): PublicKey { - return deriveAddress( - [new PublicKey(programId).toBuffer()], - BPF_LOADER_UPGRADEABLE_PROGRAM_ID, - ); -} - -//the program data pda coincides with the address that's stored in the program id account (i.e. the -// account that's found at the program id address), which is of type UpgradeLoaderState::Program: -// https://docs.rs/solana-program/latest/src/solana_program/bpf_loader_upgradeable.rs.html#40-43 -export function programDataAddress(programId: PublicKeyInitData) { - return PublicKey.findProgramAddressSync([new PublicKey(programId).toBytes()], BPF_LOADER_UPGRADEABLE_PROGRAM_ID)[0]; -} - -const onChainUint = { binary: "uint", endianness: "little" } as const; - -const pubKeyConversion = { //TODO find a better place for this - to: (encoded: Uint8Array) => new PublicKey(encoded), - from: (decoded: PublicKey) => decoded.toBytes(), -} as const satisfies CustomConversion; - -//Describes the layout of an account that holds a UpgradeableLoaderState::ProgramData enum: -// https://docs.rs/solana-program/latest/src/solana_program/bpf_loader_upgradeable.rs.html#45-52 -// because neither Anchor nor Solana web3 seem to have a built-in way to parse this. -//The bpf_loader_upgradeable program uses Rust's serde crate and bincode to serialize its structs, -// which encodes enum variants as 4 byte little endian uints: -// https://github.com/serde-rs/serde/blob/9f8c579bf5f7478f91108c1186cd0d3f85aff29d/serde_derive/src/ser.rs#L399-L408 -// and Options with a single byte 0 or 1 tag: -// https://docs.rs/bincode/latest/src/bincode/ser/mod.rs.html#137-147 -//However, even if the program is made immutable the bpf_loader_upgradeable program will keep the -// last value of the enum variant and only set the option byte tag to 0, presumably so they don't -// have to memcopy the entire subsequent bytecode (they didn't really think that one through). -//See https://explorer.solana.com/address/GDDMwNyyx8uB6zrqwBFHjLLG3TBYk2F8Az4yrQC5RzMp -// as an example of an immutable program data account. -export const programDataLayout = [ - { name: "programDataEnumVariant", ...onChainUint, size: 4, custom: 3, omit: true }, - { name: "slot", ...onChainUint, size: 8 }, - { - name: "upgradeAuthority", - binary: "switch", - idSize: 1, - idTag: "isSome", - layouts: [ - [[0, false], [{ name: "_lastValueBeforeImmutability", binary: "bytes", size: 32 }]], - [[1, true], [{ name: "value", binary: "bytes", size: 32, custom: pubKeyConversion }]], - ], - }, - { name: "bytecode", binary: "bytes" }, -] as const satisfies Layout; diff --git a/platforms/solana/src/utils/utils/index.ts b/platforms/solana/src/utils/utils/index.ts deleted file mode 100644 index 69cef0788..000000000 --- a/platforms/solana/src/utils/utils/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Provider } from '@coral-xyz/anchor'; -import type { Connection } from '@solana/web3.js'; - -export function createReadOnlyProvider( - connection?: Connection, -): Provider | undefined { - if (connection === undefined) { - return undefined; - } - return { connection }; -} - -export * from './account.js'; -export * from './bpfLoaderUpgradeable.js';