sdk/js: Remove Solana WASM Dependencies (#1375)
* sdk/js: Remove Solana WASM Dependencies * sdk/js: Uptick @solana/web3.js and @solana/spl-token versions * solana: Add IDLs for Wormhole, Token Bridge and NFT Bridge * sdk/js: Remove Solana WASM to Use IDL Coders * sdk/js: Remove src/migration * sdk/js: Add CPI Account Getters * testing: Add solana-test-validator SDK tests * sdk/js: add CHANGELOG.md * sdk/js: remove await * sdk/js: fix getIsTransferCompletedAptos * sdk/js: aptos integration test waits for tx * sdk/js: remove commented out code * sdk/js: fix inferred type Co-authored-by: A5 Pickle <a5-pickle@users.noreply.github.com> Co-authored-by: Evan Gray <battledingo@gmail.com>
This commit is contained in:
parent
043d4511a1
commit
e109024e99
|
@ -30,4 +30,8 @@ yarn-error.log*
|
||||||
/src/proto
|
/src/proto
|
||||||
|
|
||||||
# build
|
# build
|
||||||
/lib
|
/lib
|
||||||
|
|
||||||
|
# solana idl
|
||||||
|
/src/anchor-idl
|
||||||
|
/src/solana/types
|
||||||
|
|
|
@ -2,10 +2,30 @@
|
||||||
|
|
||||||
## 0.9.0
|
## 0.9.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
Methods to create transaction instructions for Wormhole (Core Bridge), Token Bridge and NFT Bridge
|
||||||
|
|
||||||
|
Methods to generate PDAs for Wormhole (Core Bridge), Token Bridge and NFT Bridge
|
||||||
|
|
||||||
|
Methods to deserialize account data for Wormhole (Core Bridge), Token Bridge and NFT Bridge
|
||||||
|
|
||||||
|
Other Solana utility objects and methods
|
||||||
|
|
||||||
|
VAA (Verified Wormhole Message) deserializers
|
||||||
|
|
||||||
|
Optional Confirmation arguments for account retrieval and wherever they are relevant
|
||||||
|
|
||||||
|
Mock objects to be used in local integration tests (e.g. MockGuardians)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
Use FQTs in Aptos SDK
|
Use FQTs in Aptos SDK
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
Dependency: @certusone/wormhole-sdk-wasm
|
||||||
|
|
||||||
## 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -17,6 +37,7 @@ Aptos support
|
||||||
Wormchain rename
|
Wormchain rename
|
||||||
|
|
||||||
## 0.7.2
|
## 0.7.2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
XPLA mainnet support and functions
|
XPLA mainnet support and functions
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-contracts": "npm run build --prefix ../../ethereum && node scripts/copyContracts.js && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
|
"build-contracts": "npm run build --prefix ../../ethereum && node scripts/copyContracts.js && typechain --target=ethers-v5 --out-dir=src/ethers-contracts contracts/*.json",
|
||||||
"build-abis": "typechain --target=ethers-v5 --out-dir=src/ethers-contracts/abi src/abi/Wormhole.abi.json",
|
"build-abis": "typechain --target=ethers-v5 --out-dir=src/ethers-contracts/abi src/abi/Wormhole.abi.json",
|
||||||
"build-deps": "npm run build-abis && npm run build-contracts",
|
"build-idl": "node scripts/compileAnchorIdls.js",
|
||||||
|
"build-deps": "npm run build-abis && npm run build-contracts && npm run build-idl",
|
||||||
"build-lib": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && node scripts/copyEthersTypes.js",
|
"build-lib": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && node scripts/copyEthersTypes.js",
|
||||||
"build-all": "npm run build-deps && npm run build-lib",
|
"build-all": "npm run build-deps && npm run build-lib",
|
||||||
"test": "jest --config jestconfig.json --verbose",
|
"test": "jest --config jestconfig.json --verbose",
|
||||||
|
@ -43,9 +44,11 @@
|
||||||
"@injectivelabs/tx-ts": "^1.0.22",
|
"@injectivelabs/tx-ts": "^1.0.22",
|
||||||
"@openzeppelin/contracts": "^4.2.0",
|
"@openzeppelin/contracts": "^4.2.0",
|
||||||
"@typechain/ethers-v5": "^7.0.1",
|
"@typechain/ethers-v5": "^7.0.1",
|
||||||
|
"@types/elliptic": "^6.4.14",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
"@types/long": "^4.0.1",
|
"@types/long": "^4.0.1",
|
||||||
"@types/node": "^16.6.1",
|
"@types/node": "^16.6.1",
|
||||||
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/react": "^17.0.19",
|
"@types/react": "^17.0.19",
|
||||||
"copy-dir": "^1.3.0",
|
"copy-dir": "^1.3.0",
|
||||||
"ethers": "^5.6.8",
|
"ethers": "^5.6.8",
|
||||||
|
@ -61,14 +64,17 @@
|
||||||
"@certusone/wormhole-sdk-proto-web": "^0.0.5",
|
"@certusone/wormhole-sdk-proto-web": "^0.0.5",
|
||||||
"@certusone/wormhole-sdk-wasm": "^0.0.1",
|
"@certusone/wormhole-sdk-wasm": "^0.0.1",
|
||||||
"@injectivelabs/sdk-ts": "^1.0.75",
|
"@injectivelabs/sdk-ts": "^1.0.75",
|
||||||
"@solana/spl-token": "^0.1.8",
|
"@project-serum/anchor": "^0.25.0",
|
||||||
"@solana/web3.js": "^1.24.0",
|
"@solana/spl-token": "^0.3.5",
|
||||||
|
"@solana/web3.js": "^1.66.2",
|
||||||
"@terra-money/terra.js": "^3.1.3",
|
"@terra-money/terra.js": "^3.1.3",
|
||||||
"@xpla/xpla.js": "^0.2.1",
|
"@xpla/xpla.js": "^0.2.1",
|
||||||
"algosdk": "^1.15.0",
|
"algosdk": "^1.15.0",
|
||||||
"aptos": "^1.3.16",
|
"aptos": "^1.3.16",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
"binary-parser": "^2.2.1",
|
||||||
|
"elliptic": "^6.5.4",
|
||||||
"js-base64": "^3.6.1",
|
"js-base64": "^3.6.1",
|
||||||
"near-api-js": "^1.0.0"
|
"near-api-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const SRC_IDL = __dirname + "/../../../solana/idl";
|
||||||
|
const DST_IDL = __dirname + "/../src/anchor-idl";
|
||||||
|
const TS = __dirname + "/../src/solana/types";
|
||||||
|
|
||||||
|
const programs = {
|
||||||
|
"wormhole.json": "Wormhole",
|
||||||
|
"token_bridge.json": "TokenBridge",
|
||||||
|
"nft_bridge.json": "NftBridge",
|
||||||
|
};
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
if (!fs.existsSync(DST_IDL)) {
|
||||||
|
fs.mkdirSync(DST_IDL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(TS)) {
|
||||||
|
fs.mkdirSync(TS);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const basename of fs.readdirSync(SRC_IDL)) {
|
||||||
|
const idl = DST_IDL + "/" + basename;
|
||||||
|
fs.copyFileSync(SRC_IDL + "/" + basename, idl);
|
||||||
|
|
||||||
|
const targetTypescript = TS + "/" + snakeToCamel(basename).replace("json", "ts");
|
||||||
|
|
||||||
|
const programType = programs[basename];
|
||||||
|
fs.writeFileSync(targetTypescript, `export type ${programType} = `);
|
||||||
|
fs.appendFileSync(targetTypescript, fs.readFileSync(idl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const snakeToCamel = str =>
|
||||||
|
str.toLowerCase().replace(/([-_][a-z])/g, group =>
|
||||||
|
group
|
||||||
|
.toUpperCase()
|
||||||
|
.replace('-', '')
|
||||||
|
.replace('_', '')
|
||||||
|
);
|
||||||
|
|
||||||
|
main();
|
|
@ -4,7 +4,6 @@ import algosdk, {
|
||||||
decodeAddress,
|
decodeAddress,
|
||||||
getApplicationAddress,
|
getApplicationAddress,
|
||||||
} from "algosdk";
|
} from "algosdk";
|
||||||
import { setDefaultWasm } from "../../solana/wasm";
|
|
||||||
import { hexToUint8Array, uint8ArrayToHex } from "../../utils";
|
import { hexToUint8Array, uint8ArrayToHex } from "../../utils";
|
||||||
import {
|
import {
|
||||||
accountExists,
|
accountExists,
|
||||||
|
@ -23,8 +22,6 @@ import { PopulateData, TmplSig } from "../TmplSig";
|
||||||
const CORE_ID = BigInt(4);
|
const CORE_ID = BigInt(4);
|
||||||
const TOKEN_BRIDGE_ID = BigInt(6);
|
const TOKEN_BRIDGE_ID = BigInt(6);
|
||||||
|
|
||||||
setDefaultWasm("node");
|
|
||||||
|
|
||||||
jest.setTimeout(60000);
|
jest.setTimeout(60000);
|
||||||
|
|
||||||
describe("Unit Tests", () => {
|
describe("Unit Tests", () => {
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKeyInitData } from "@solana/web3.js";
|
||||||
import { importCoreWasm } from "../solana/wasm";
|
import { deriveClaimKey } from "../solana/wormhole";
|
||||||
|
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||||
|
|
||||||
export async function getClaimAddressSolana(
|
export async function getClaimAddressSolana(
|
||||||
programAddress: string,
|
programAddress: PublicKeyInitData,
|
||||||
signedVAA: Uint8Array
|
signedVaa: SignedVaa
|
||||||
) {
|
) {
|
||||||
const { claim_address } = await importCoreWasm();
|
const parsed = parseVaa(signedVaa);
|
||||||
return new PublicKey(claim_address(programAddress, signedVAA));
|
return deriveClaimKey(
|
||||||
|
programAddress,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKeyInitData } from "@solana/web3.js";
|
||||||
import { decodeAddress, getApplicationAddress } from "algosdk";
|
import { decodeAddress, getApplicationAddress } from "algosdk";
|
||||||
import { bech32 } from "bech32";
|
import { bech32 } from "bech32";
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +8,7 @@ import {
|
||||||
Hexable,
|
Hexable,
|
||||||
zeroPad,
|
zeroPad,
|
||||||
} from "ethers/lib/utils";
|
} from "ethers/lib/utils";
|
||||||
import { importTokenWasm } from "../solana/wasm";
|
import { deriveWormholeEmitterKey } from "../solana/wormhole";
|
||||||
import { uint8ArrayToHex } from "../utils";
|
import { uint8ArrayToHex } from "../utils";
|
||||||
|
|
||||||
export function getEmitterAddressEth(
|
export function getEmitterAddressEth(
|
||||||
|
@ -17,11 +17,10 @@ export function getEmitterAddressEth(
|
||||||
return Buffer.from(zeroPad(arrayify(contractAddress), 32)).toString("hex");
|
return Buffer.from(zeroPad(arrayify(contractAddress), 32)).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEmitterAddressSolana(programAddress: string) {
|
export async function getEmitterAddressSolana(
|
||||||
const { emitter_address } = await importTokenWasm();
|
programAddress: PublicKeyInitData
|
||||||
return Buffer.from(
|
) {
|
||||||
zeroPad(new PublicKey(emitter_address(programAddress)).toBytes(), 32)
|
return deriveWormholeEmitterKey(programAddress).toBuffer().toString("hex");
|
||||||
).toString("hex");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEmitterAddressTerra(programAddress: string) {
|
export async function getEmitterAddressTerra(programAddress: string) {
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
import { importCoreWasm } from "../solana/wasm";
|
import { keccak256 } from "../utils";
|
||||||
import { ethers } from "ethers";
|
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||||
import { uint8ArrayToHex } from "..";
|
|
||||||
|
|
||||||
export async function getSignedVAAHash(signedVAA: Uint8Array) {
|
export function getSignedVAAHash(signedVaa: SignedVaa): string {
|
||||||
const { parse_vaa } = await importCoreWasm();
|
return `0x${keccak256(parseVaa(signedVaa).hash).toString("hex")}`;
|
||||||
const parsedVAA = parse_vaa(signedVAA);
|
|
||||||
const body = [
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["uint32"], [parsedVAA.timestamp]).substring(2 + (64 - 8)),
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["uint32"], [parsedVAA.nonce]).substring(2 + (64 - 8)),
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["uint16"], [parsedVAA.emitter_chain]).substring(2 + (64 - 4)),
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["bytes32"], [parsedVAA.emitter_address]).substring(2),
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["uint64"], [parsedVAA.sequence]).substring(2 + (64 - 16)),
|
|
||||||
ethers.utils.defaultAbiCoder.encode(["uint8"], [parsedVAA.consistency_level]).substring(2 + (64 - 2)),
|
|
||||||
uint8ArrayToHex(parsedVAA.payload),
|
|
||||||
];
|
|
||||||
return ethers.utils.solidityKeccak256(["bytes"], [ethers.utils.solidityKeccak256(["bytes"], ["0x" + body.join("")])]);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
require("dotenv").config({ path: ".env" });
|
|
||||||
import { getNetworkInfo, Network } from "@injectivelabs/networks";
|
import { getNetworkInfo, Network } from "@injectivelabs/networks";
|
||||||
import {
|
import {
|
||||||
ChainGrpcWasmApi,
|
ChainGrpcWasmApi,
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
export * from "./cosmos";
|
|
||||||
export * from "./ethers-contracts";
|
|
||||||
export * from "./solana";
|
|
||||||
export * from "./terra";
|
|
||||||
export * from "./rpc";
|
export * from "./rpc";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * from "./bridge";
|
export * from "./bridge";
|
||||||
export * from "./token_bridge";
|
export * from "./token_bridge";
|
||||||
export * from "./cosmwasm";
|
export * from "./cosmwasm";
|
||||||
|
export * from "./vaa";
|
||||||
|
|
||||||
export * as cosmos from "./cosmos";
|
export * as cosmos from "./cosmos";
|
||||||
export * as ethers_contracts from "./ethers-contracts";
|
export * as ethers_contracts from "./ethers-contracts";
|
||||||
|
@ -18,3 +15,5 @@ export * as bridge from "./bridge";
|
||||||
export * as token_bridge from "./token_bridge";
|
export * as token_bridge from "./token_bridge";
|
||||||
export * as nft_bridge from "./nft_bridge";
|
export * as nft_bridge from "./nft_bridge";
|
||||||
export * as algorand from "./algorand";
|
export * as algorand from "./algorand";
|
||||||
|
|
||||||
|
export { postVaaSolana, postVaaSolanaWithRetry } from "./solana";
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
|
||||||
import { ixFromRust } from "../solana";
|
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function addLiquidity(
|
|
||||||
connection: Connection,
|
|
||||||
payerAddress: string,
|
|
||||||
program_id: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string,
|
|
||||||
liquidity_token_account: string,
|
|
||||||
lp_share_token_account: string,
|
|
||||||
amount: BigInt
|
|
||||||
) {
|
|
||||||
const { authority_address, add_liquidity } = await importMigrationWasm();
|
|
||||||
const approvalIx = Token.createApproveInstruction(
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(liquidity_token_account),
|
|
||||||
new PublicKey(authority_address(program_id)),
|
|
||||||
new PublicKey(payerAddress),
|
|
||||||
[],
|
|
||||||
new u64(amount.toString(16), 16)
|
|
||||||
);
|
|
||||||
const ix = ixFromRust(
|
|
||||||
add_liquidity(
|
|
||||||
program_id,
|
|
||||||
from_mint,
|
|
||||||
to_mint,
|
|
||||||
liquidity_token_account,
|
|
||||||
lp_share_token_account,
|
|
||||||
amount.valueOf()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const transaction = new Transaction().add(approvalIx, ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function authorityAddress(program_id: string) {
|
|
||||||
const { authority_address } = await importMigrationWasm();
|
|
||||||
return authority_address(program_id);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
|
||||||
import { ixFromRust } from "../solana";
|
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function claimShares(
|
|
||||||
connection: Connection,
|
|
||||||
payerAddress: string,
|
|
||||||
program_id: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string,
|
|
||||||
output_token_account: string,
|
|
||||||
lp_share_token_account: string,
|
|
||||||
amount: BigInt
|
|
||||||
) {
|
|
||||||
const { authority_address, claim_shares } = await importMigrationWasm();
|
|
||||||
const approvalIx = Token.createApproveInstruction(
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(lp_share_token_account),
|
|
||||||
new PublicKey(authority_address(program_id)),
|
|
||||||
new PublicKey(payerAddress),
|
|
||||||
[],
|
|
||||||
new u64(amount.toString(16), 16)
|
|
||||||
);
|
|
||||||
const ix = ixFromRust(
|
|
||||||
claim_shares(
|
|
||||||
program_id,
|
|
||||||
from_mint,
|
|
||||||
to_mint,
|
|
||||||
output_token_account,
|
|
||||||
lp_share_token_account,
|
|
||||||
amount.valueOf()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const transaction = new Transaction().add(approvalIx, ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
|
||||||
import { ixFromRust } from "../solana";
|
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function createPool(
|
|
||||||
connection: Connection,
|
|
||||||
payerAddress: string,
|
|
||||||
program_id: string,
|
|
||||||
payer: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string
|
|
||||||
) {
|
|
||||||
const { create_pool } = await importMigrationWasm();
|
|
||||||
const ix = ixFromRust(create_pool(program_id, payer, from_mint, to_mint));
|
|
||||||
const transaction = new Transaction().add(ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function fromCustodyAddress(
|
|
||||||
program_id: string,
|
|
||||||
pool: string
|
|
||||||
) {
|
|
||||||
const { from_custody_address } = await importMigrationWasm();
|
|
||||||
return from_custody_address(program_id, pool);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
|
||||||
import { ixFromRust } from "../solana";
|
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function migrateTokens(
|
|
||||||
connection: Connection,
|
|
||||||
payerAddress: string,
|
|
||||||
program_id: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string,
|
|
||||||
input_token_account: string,
|
|
||||||
output_token_account: string,
|
|
||||||
amount: BigInt
|
|
||||||
) {
|
|
||||||
const { authority_address, migrate_tokens } = await importMigrationWasm();
|
|
||||||
const approvalIx = Token.createApproveInstruction(
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(input_token_account),
|
|
||||||
new PublicKey(authority_address(program_id)),
|
|
||||||
new PublicKey(payerAddress),
|
|
||||||
[],
|
|
||||||
new u64(amount.toString(16), 16)
|
|
||||||
);
|
|
||||||
const ix = ixFromRust(
|
|
||||||
migrate_tokens(
|
|
||||||
program_id,
|
|
||||||
from_mint,
|
|
||||||
to_mint,
|
|
||||||
input_token_account,
|
|
||||||
output_token_account,
|
|
||||||
amount.valueOf()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const transaction = new Transaction().add(approvalIx, ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function parsePool(data: Uint8Array) {
|
|
||||||
const { parse_pool } = await importMigrationWasm();
|
|
||||||
return parse_pool(data);
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function poolAddress(
|
|
||||||
program_id: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string
|
|
||||||
) {
|
|
||||||
const { pool_address } = await importMigrationWasm();
|
|
||||||
return pool_address(program_id, from_mint, to_mint);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
|
||||||
import { ixFromRust } from "../solana";
|
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function removeLiquidity(
|
|
||||||
connection: Connection,
|
|
||||||
payerAddress: string,
|
|
||||||
program_id: string,
|
|
||||||
from_mint: string,
|
|
||||||
to_mint: string,
|
|
||||||
liquidity_token_account: string,
|
|
||||||
lp_share_token_account: string,
|
|
||||||
amount: BigInt
|
|
||||||
) {
|
|
||||||
const { authority_address, remove_liquidity } = await importMigrationWasm();
|
|
||||||
const approvalIx = Token.createApproveInstruction(
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(lp_share_token_account),
|
|
||||||
new PublicKey(authority_address(program_id)),
|
|
||||||
new PublicKey(payerAddress),
|
|
||||||
[],
|
|
||||||
new u64(amount.toString(16), 16)
|
|
||||||
);
|
|
||||||
const ix = ixFromRust(
|
|
||||||
remove_liquidity(
|
|
||||||
program_id,
|
|
||||||
from_mint,
|
|
||||||
to_mint,
|
|
||||||
liquidity_token_account,
|
|
||||||
lp_share_token_account,
|
|
||||||
amount.valueOf()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const transaction = new Transaction().add(approvalIx, ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function shareMintAddress(
|
|
||||||
program_id: string,
|
|
||||||
pool: string
|
|
||||||
) {
|
|
||||||
const { share_mint_address } = await importMigrationWasm();
|
|
||||||
return share_mint_address(program_id, pool);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { importMigrationWasm } from "../solana/wasm";
|
|
||||||
|
|
||||||
export default async function toCustodyAddress(
|
|
||||||
program_id: string,
|
|
||||||
pool: string
|
|
||||||
) {
|
|
||||||
const { to_custody_address } = await importMigrationWasm();
|
|
||||||
return to_custody_address(program_id, pool);
|
|
||||||
}
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { BN } from "@project-serum/anchor";
|
||||||
|
import { ChainId, tryNativeToHexString } from "../utils";
|
||||||
|
import { MockEmitter } from "./wormhole";
|
||||||
|
|
||||||
|
const ETHEREUM_KEY_LENGTH = 20;
|
||||||
|
|
||||||
|
export class GovernanceEmitter extends MockEmitter {
|
||||||
|
constructor(emitterAddress: string, startSequence?: number) {
|
||||||
|
super(emitterAddress, 1, startSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishGovernanceMessage(
|
||||||
|
timestamp: number,
|
||||||
|
module: string,
|
||||||
|
payload: Buffer,
|
||||||
|
action: number,
|
||||||
|
chain: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const serialized = Buffer.alloc(35 + payload.length);
|
||||||
|
|
||||||
|
const moduleBytes = Buffer.alloc(32);
|
||||||
|
moduleBytes.write(module, 32 - module.length);
|
||||||
|
serialized.write(moduleBytes.toString("hex"), 0, "hex");
|
||||||
|
serialized.writeUInt8(action, 32); // action
|
||||||
|
serialized.writeUInt16BE(chain, 33);
|
||||||
|
serialized.write(payload.toString("hex"), 35, "hex");
|
||||||
|
return this.publishMessage(0, serialized, 1, timestamp, uptickSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishWormholeSetMessageFee(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
amount: bigint,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(32);
|
||||||
|
const amountBytes = new BN(amount.toString()).toBuffer();
|
||||||
|
payload.write(amountBytes.toString("hex"), 32 - amountBytes.length, "hex");
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"Core",
|
||||||
|
payload,
|
||||||
|
3,
|
||||||
|
chain,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishWormholeTransferFees(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
amount: bigint,
|
||||||
|
recipient: Buffer,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(64);
|
||||||
|
const amountBytes = new BN(amount.toString()).toBuffer();
|
||||||
|
payload.write(amountBytes.toString("hex"), 32 - amountBytes.length, "hex");
|
||||||
|
payload.write(recipient.toString("hex"), 32, "hex");
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"Core",
|
||||||
|
payload,
|
||||||
|
4,
|
||||||
|
chain,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishWormholeGuardianSetUpgrade(
|
||||||
|
timestamp: number,
|
||||||
|
newGuardianSetIndex: number,
|
||||||
|
publicKeys: Buffer[],
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const numKeys = publicKeys.length;
|
||||||
|
const payload = Buffer.alloc(5 + ETHEREUM_KEY_LENGTH * numKeys);
|
||||||
|
payload.writeUInt32BE(newGuardianSetIndex, 0);
|
||||||
|
payload.writeUInt8(numKeys, 4);
|
||||||
|
for (let i = 0; i < numKeys; ++i) {
|
||||||
|
const publicKey = publicKeys.at(i);
|
||||||
|
if (publicKey == undefined) {
|
||||||
|
throw Error("publicKey == undefined");
|
||||||
|
}
|
||||||
|
payload.write(
|
||||||
|
publicKey.toString("hex"),
|
||||||
|
5 + ETHEREUM_KEY_LENGTH * i,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"Core",
|
||||||
|
payload,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishWormholeUpgradeContract(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
newContract: string,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(32);
|
||||||
|
payload.write(
|
||||||
|
tryNativeToHexString(newContract, chain as ChainId),
|
||||||
|
0,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"Core",
|
||||||
|
payload,
|
||||||
|
1,
|
||||||
|
chain,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTokenBridgeRegisterChain(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
address: string,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(34);
|
||||||
|
payload.writeUInt16BE(chain, 0);
|
||||||
|
payload.write(tryNativeToHexString(address, chain as ChainId), 2, "hex");
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"TokenBridge",
|
||||||
|
payload,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTokenBridgeUpgradeContract(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
newContract: string,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(32);
|
||||||
|
payload.write(
|
||||||
|
tryNativeToHexString(newContract, this.chain as ChainId),
|
||||||
|
0,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"TokenBridge",
|
||||||
|
payload,
|
||||||
|
2,
|
||||||
|
chain,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishNftBridgeRegisterChain(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
address: string,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(34);
|
||||||
|
payload.writeUInt16BE(chain, 0);
|
||||||
|
payload.write(tryNativeToHexString(address, chain as ChainId), 2, "hex");
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"NFTBridge",
|
||||||
|
payload,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishNftBridgeUpgradeContract(
|
||||||
|
timestamp: number,
|
||||||
|
chain: number,
|
||||||
|
newContract: string,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const payload = Buffer.alloc(32);
|
||||||
|
payload.write(
|
||||||
|
tryNativeToHexString(newContract, this.chain as ChainId),
|
||||||
|
0,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
return this.publishGovernanceMessage(
|
||||||
|
timestamp,
|
||||||
|
"NFTBridge",
|
||||||
|
payload,
|
||||||
|
2,
|
||||||
|
chain,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./governance";
|
||||||
|
export * from "./misc";
|
||||||
|
export * from "./nftBridge";
|
||||||
|
export * from "./tokenBridge";
|
||||||
|
export * from "./wormhole";
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { keccak256 } from "../utils";
|
||||||
|
import * as elliptic from "elliptic";
|
||||||
|
|
||||||
|
export function ethPrivateToPublic(key: string) {
|
||||||
|
const ecdsa = new elliptic.ec("secp256k1");
|
||||||
|
const publicKey = ecdsa.keyFromPrivate(key).getPublic("hex");
|
||||||
|
return keccak256(Buffer.from(publicKey, "hex").subarray(1)).subarray(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ethSignWithPrivate(privateKey: string, hash: Buffer) {
|
||||||
|
if (hash.length != 32) {
|
||||||
|
throw new Error("hash.length != 32");
|
||||||
|
}
|
||||||
|
const ecdsa = new elliptic.ec("secp256k1");
|
||||||
|
const key = ecdsa.keyFromPrivate(privateKey);
|
||||||
|
return key.sign(hash, { canonical: true });
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { NodePrivilegedServiceChainGovernorReleasePendingVAADesc } from "@certusone/wormhole-sdk-proto-web/lib/cjs/node/v1/node";
|
||||||
|
import { BN } from "@project-serum/anchor";
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { ChainId, tryNativeToHexString } from "../utils";
|
||||||
|
import { MockEmitter } from "./wormhole";
|
||||||
|
|
||||||
|
export class MockNftBridge extends MockEmitter {
|
||||||
|
consistencyLevel: number;
|
||||||
|
|
||||||
|
constructor(emitterAddress: string, chain: number, consistencyLevel: number) {
|
||||||
|
super(emitterAddress, chain);
|
||||||
|
this.consistencyLevel = consistencyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishNftBridgeMessage(
|
||||||
|
serialized: Buffer,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return this.publishMessage(
|
||||||
|
nonce == undefined ? 0 : nonce,
|
||||||
|
serialized,
|
||||||
|
this.consistencyLevel,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTransferNft(
|
||||||
|
tokenAddress: string,
|
||||||
|
tokenChain: number,
|
||||||
|
name: string,
|
||||||
|
symbol: string,
|
||||||
|
tokenId: bigint,
|
||||||
|
uri: string,
|
||||||
|
recipientChain: number,
|
||||||
|
recipient: string,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
if (uri.length > 200) {
|
||||||
|
throw new Error("uri.length > 200");
|
||||||
|
}
|
||||||
|
const serialized = Buffer.alloc(166 + uri.length);
|
||||||
|
serialized.writeUInt8(1, 0);
|
||||||
|
serialized.write(tokenAddress, 1, "hex");
|
||||||
|
serialized.writeUInt16BE(tokenChain, 33);
|
||||||
|
// truncate to 32 characters
|
||||||
|
symbol = symbol.substring(0, 32);
|
||||||
|
serialized.write(symbol, 35);
|
||||||
|
// truncate to 32 characters
|
||||||
|
name = name.substring(0, 32);
|
||||||
|
serialized.write(name, 67);
|
||||||
|
const tokenIdBytes = new BN(tokenId.toString()).toBuffer();
|
||||||
|
serialized.write(
|
||||||
|
tokenIdBytes.toString("hex"),
|
||||||
|
131 - tokenIdBytes.length,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
serialized.writeUInt8(uri.length, 131);
|
||||||
|
serialized.write(uri, 132);
|
||||||
|
const uriEnd = 132 + uri.length;
|
||||||
|
serialized.write(recipient, uriEnd, "hex");
|
||||||
|
serialized.writeUInt16BE(recipientChain, uriEnd + 32);
|
||||||
|
return this.publishNftBridgeMessage(
|
||||||
|
serialized,
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockEthereumNftBridge extends MockNftBridge {
|
||||||
|
constructor(emitterAddress: string) {
|
||||||
|
const chain = 2;
|
||||||
|
super(tryNativeToHexString(emitterAddress, chain as ChainId), chain, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockSolanaNftBridge extends MockNftBridge {
|
||||||
|
constructor(emitterAddress: PublicKeyInitData) {
|
||||||
|
super(new PublicKey(emitterAddress).toBuffer().toString("hex"), 1, 32);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
import { BN } from "@project-serum/anchor";
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { ChainId, tryNativeToHexString } from "../utils";
|
||||||
|
import { MockEmitter } from "./wormhole";
|
||||||
|
|
||||||
|
export class MockTokenBridge extends MockEmitter {
|
||||||
|
consistencyLevel: number;
|
||||||
|
|
||||||
|
constructor(emitterAddress: string, chain: number, consistencyLevel: number) {
|
||||||
|
super(emitterAddress, chain);
|
||||||
|
this.consistencyLevel = consistencyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTokenBridgeMessage(
|
||||||
|
serialized: Buffer,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return this.publishMessage(
|
||||||
|
nonce == undefined ? 0 : nonce,
|
||||||
|
serialized,
|
||||||
|
this.consistencyLevel,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishAttestMeta(
|
||||||
|
tokenAddress: string,
|
||||||
|
decimals: number,
|
||||||
|
symbol: string,
|
||||||
|
name: string,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
const serialized = Buffer.alloc(100);
|
||||||
|
serialized.writeUInt8(2, 0);
|
||||||
|
const hexlified = Buffer.from(tokenAddress, "hex");
|
||||||
|
if (hexlified.length != 32) {
|
||||||
|
throw new Error("tokenAddress must be 32 bytes");
|
||||||
|
}
|
||||||
|
serialized.write(hexlified.toString("hex"), 1, "hex");
|
||||||
|
serialized.writeUInt16BE(this.chain, 33);
|
||||||
|
serialized.writeUInt8(decimals, 35);
|
||||||
|
// truncate to 32 characters
|
||||||
|
symbol = symbol.substring(0, 32);
|
||||||
|
serialized.write(symbol, 68 - symbol.length);
|
||||||
|
// truncate to 32 characters
|
||||||
|
name = name.substring(0, 32);
|
||||||
|
serialized.write(name, 100 - name.length);
|
||||||
|
return this.publishTokenBridgeMessage(
|
||||||
|
serialized,
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeTransferOnly(
|
||||||
|
withPayload: boolean,
|
||||||
|
tokenAddress: string,
|
||||||
|
tokenChain: number,
|
||||||
|
amount: bigint,
|
||||||
|
recipientChain: number,
|
||||||
|
recipient: string,
|
||||||
|
fee?: bigint,
|
||||||
|
fromAddress?: Buffer
|
||||||
|
) {
|
||||||
|
const serialized = Buffer.alloc(133);
|
||||||
|
serialized.writeUInt8(1, 0);
|
||||||
|
const amountBytes = new BN(amount.toString()).toBuffer();
|
||||||
|
serialized.write(
|
||||||
|
amountBytes.toString("hex"),
|
||||||
|
33 - amountBytes.length,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
serialized.write(tokenAddress, 33, "hex");
|
||||||
|
serialized.writeUInt16BE(tokenChain, 65);
|
||||||
|
serialized.write(recipient, 67, "hex");
|
||||||
|
serialized.writeUInt16BE(recipientChain, 99);
|
||||||
|
if (withPayload) {
|
||||||
|
if (fromAddress === undefined) {
|
||||||
|
throw new Error("fromAddress === undefined");
|
||||||
|
}
|
||||||
|
serialized.write(fromAddress.toString("hex"), 101, "hex");
|
||||||
|
} else {
|
||||||
|
if (fee === undefined) {
|
||||||
|
throw new Error("fee === undefined");
|
||||||
|
}
|
||||||
|
const feeBytes = new BN(fee.toString()).toBuffer();
|
||||||
|
serialized.write(feeBytes.toString("hex"), 133 - feeBytes.length, "hex");
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTransferTokens(
|
||||||
|
tokenAddress: string,
|
||||||
|
tokenChain: number,
|
||||||
|
amount: bigint,
|
||||||
|
recipientChain: number,
|
||||||
|
recipient: string,
|
||||||
|
fee: bigint,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return this.publishTokenBridgeMessage(
|
||||||
|
this.serializeTransferOnly(
|
||||||
|
false, // withPayload
|
||||||
|
tokenAddress,
|
||||||
|
tokenChain,
|
||||||
|
amount,
|
||||||
|
recipientChain,
|
||||||
|
recipient,
|
||||||
|
fee
|
||||||
|
),
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishTransferTokensWithPayload(
|
||||||
|
tokenAddress: string,
|
||||||
|
tokenChain: number,
|
||||||
|
amount: bigint,
|
||||||
|
recipientChain: number,
|
||||||
|
recipient: string,
|
||||||
|
fromAddress: Buffer,
|
||||||
|
payload: Buffer,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return this.publishTokenBridgeMessage(
|
||||||
|
Buffer.concat([
|
||||||
|
this.serializeTransferOnly(
|
||||||
|
true, // withPayload
|
||||||
|
tokenAddress,
|
||||||
|
tokenChain,
|
||||||
|
amount,
|
||||||
|
recipientChain,
|
||||||
|
recipient,
|
||||||
|
undefined, // fee
|
||||||
|
fromAddress
|
||||||
|
),
|
||||||
|
payload,
|
||||||
|
]),
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockEthereumTokenBridge extends MockTokenBridge {
|
||||||
|
constructor(emitterAddress: string) {
|
||||||
|
const chain = 2;
|
||||||
|
super(tryNativeToHexString(emitterAddress, chain as ChainId), chain, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishAttestMeta(
|
||||||
|
tokenAddress: string,
|
||||||
|
decimals: number,
|
||||||
|
symbol: string,
|
||||||
|
name: string,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return super.publishAttestMeta(
|
||||||
|
tryNativeToHexString(tokenAddress, this.chain as ChainId),
|
||||||
|
decimals,
|
||||||
|
symbol == undefined ? "" : symbol,
|
||||||
|
name == undefined ? "" : name,
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockSolanaTokenBridge extends MockTokenBridge {
|
||||||
|
constructor(emitterAddress: PublicKeyInitData) {
|
||||||
|
super(new PublicKey(emitterAddress).toBuffer().toString("hex"), 1, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
publishAttestMeta(
|
||||||
|
mint: PublicKeyInitData,
|
||||||
|
decimals: number,
|
||||||
|
symbol?: string,
|
||||||
|
name?: string,
|
||||||
|
nonce?: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
return super.publishAttestMeta(
|
||||||
|
new PublicKey(mint).toBuffer().toString("hex"),
|
||||||
|
decimals,
|
||||||
|
symbol == undefined ? "" : symbol,
|
||||||
|
name == undefined ? "" : name,
|
||||||
|
nonce,
|
||||||
|
timestamp,
|
||||||
|
uptickSequence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { keccak256 } from "../utils";
|
||||||
|
import { ethPrivateToPublic, ethSignWithPrivate } from "./misc";
|
||||||
|
|
||||||
|
const SIGNATURE_PAYLOAD_LEN = 66;
|
||||||
|
|
||||||
|
interface Guardian {
|
||||||
|
index: number;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockGuardians {
|
||||||
|
setIndex: number;
|
||||||
|
signers: Guardian[];
|
||||||
|
|
||||||
|
constructor(setIndex: number, keys: string[]) {
|
||||||
|
this.setIndex = setIndex;
|
||||||
|
this.signers = keys.map((key, index): Guardian => {
|
||||||
|
return { index, key };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublicKeys() {
|
||||||
|
return this.signers.map((guardian) => ethPrivateToPublic(guardian.key));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGuardianSetIndex(setIndex: number) {
|
||||||
|
this.setIndex = setIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSignatures(message: Buffer, guardianIndices: number[]) {
|
||||||
|
if (guardianIndices.length == 0) {
|
||||||
|
throw Error("guardianIndices.length == 0");
|
||||||
|
}
|
||||||
|
const signers = this.signers.filter((signer) =>
|
||||||
|
guardianIndices.includes(signer.index)
|
||||||
|
);
|
||||||
|
|
||||||
|
const sigStart = 6;
|
||||||
|
const numSigners = signers.length;
|
||||||
|
|
||||||
|
const signedVaa = Buffer.alloc(
|
||||||
|
sigStart + SIGNATURE_PAYLOAD_LEN * numSigners + message.length
|
||||||
|
);
|
||||||
|
signedVaa.write(
|
||||||
|
message.toString("hex"),
|
||||||
|
sigStart + SIGNATURE_PAYLOAD_LEN * numSigners,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
|
||||||
|
signedVaa.writeUInt8(1, 0);
|
||||||
|
signedVaa.writeUInt32BE(this.setIndex, 1);
|
||||||
|
signedVaa.writeUInt8(numSigners, 5);
|
||||||
|
|
||||||
|
// signatures
|
||||||
|
const hash = keccak256(keccak256(message));
|
||||||
|
|
||||||
|
for (let i = 0; i < numSigners; ++i) {
|
||||||
|
const signer = signers.at(i);
|
||||||
|
if (signer == undefined) {
|
||||||
|
throw Error("signer == undefined");
|
||||||
|
}
|
||||||
|
const signature = ethSignWithPrivate(signer.key, hash);
|
||||||
|
|
||||||
|
const start = sigStart + i * SIGNATURE_PAYLOAD_LEN;
|
||||||
|
signedVaa.writeUInt8(signer.index, start);
|
||||||
|
signedVaa.write(
|
||||||
|
signature.r.toString(16).padStart(64, "0"),
|
||||||
|
start + 1,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
signedVaa.write(
|
||||||
|
signature.s.toString(16).padStart(64, "0"),
|
||||||
|
start + 33,
|
||||||
|
"hex"
|
||||||
|
);
|
||||||
|
signedVaa.writeUInt8(signature.recoveryParam!, start + 65);
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedVaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockEmitter {
|
||||||
|
chain: number;
|
||||||
|
address: Buffer;
|
||||||
|
|
||||||
|
sequence: number;
|
||||||
|
|
||||||
|
constructor(emitterAddress: string, chain: number, startSequence?: number) {
|
||||||
|
this.chain = chain;
|
||||||
|
const address = Buffer.from(emitterAddress, "hex");
|
||||||
|
if (address.length != 32) {
|
||||||
|
throw Error("emitterAddress.length != 32");
|
||||||
|
}
|
||||||
|
this.address = address;
|
||||||
|
|
||||||
|
this.sequence = startSequence == undefined ? 0 : startSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
publishMessage(
|
||||||
|
nonce: number,
|
||||||
|
payload: Buffer,
|
||||||
|
consistencyLevel: number,
|
||||||
|
timestamp?: number,
|
||||||
|
uptickSequence: boolean = true
|
||||||
|
) {
|
||||||
|
if (uptickSequence) {
|
||||||
|
++this.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = Buffer.alloc(51 + payload.length);
|
||||||
|
|
||||||
|
message.writeUInt32BE(timestamp == undefined ? 0 : timestamp, 0);
|
||||||
|
message.writeUInt32BE(nonce, 4);
|
||||||
|
message.writeUInt16BE(this.chain, 8);
|
||||||
|
message.write(this.address.toString("hex"), 10, "hex");
|
||||||
|
message.writeBigUInt64BE(BigInt(this.sequence), 42);
|
||||||
|
message.writeUInt8(consistencyLevel, 50);
|
||||||
|
message.write(payload.toString("hex"), 51, "hex");
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockEthereumEmitter extends MockEmitter {
|
||||||
|
constructor(emitterAddress: string, chain?: number) {
|
||||||
|
super(emitterAddress, chain == undefined ? 2 : chain);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from "@jest/globals";
|
} from "@jest/globals";
|
||||||
import {
|
import {
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
Token,
|
getAssociatedTokenAddress,
|
||||||
TOKEN_PROGRAM_ID,
|
TOKEN_PROGRAM_ID,
|
||||||
} from "@solana/spl-token";
|
} from "@solana/spl-token";
|
||||||
import { BigNumber, BigNumberish, ethers } from "ethers";
|
import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||||
|
@ -28,10 +28,8 @@ import {
|
||||||
parseSequenceFromLogSolana,
|
parseSequenceFromLogSolana,
|
||||||
getEmitterAddressSolana,
|
getEmitterAddressSolana,
|
||||||
CHAIN_ID_SOLANA,
|
CHAIN_ID_SOLANA,
|
||||||
parseNFTPayload,
|
|
||||||
} from "../..";
|
} from "../..";
|
||||||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||||
import { importCoreWasm, setDefaultWasm } from "../../solana/wasm";
|
|
||||||
import {
|
import {
|
||||||
ETH_NODE_URL,
|
ETH_NODE_URL,
|
||||||
ETH_PRIVATE_KEY,
|
ETH_PRIVATE_KEY,
|
||||||
|
@ -59,10 +57,10 @@ import {
|
||||||
import { postVaaSolanaWithRetry } from "../../solana";
|
import { postVaaSolanaWithRetry } from "../../solana";
|
||||||
import { tryNativeToUint8Array } from "../../utils";
|
import { tryNativeToUint8Array } from "../../utils";
|
||||||
import { arrayify } from "ethers/lib/utils";
|
import { arrayify } from "ethers/lib/utils";
|
||||||
|
import { parseVaa } from "../../vaa/wormhole";
|
||||||
|
import { parseNftTransferVaa } from "../../vaa";
|
||||||
const ERC721 = require("@openzeppelin/contracts/build/contracts/ERC721PresetMinterPauserAutoId.json");
|
const ERC721 = require("@openzeppelin/contracts/build/contracts/ERC721PresetMinterPauserAutoId.json");
|
||||||
|
|
||||||
setDefaultWasm("node");
|
|
||||||
|
|
||||||
jest.setTimeout(60000);
|
jest.setTimeout(60000);
|
||||||
|
|
||||||
type Address = string;
|
type Address = string;
|
||||||
|
@ -158,11 +156,7 @@ describe("Integration Tests", () => {
|
||||||
test("Send Solana SPL to Ethereum and back", (done) => {
|
test("Send Solana SPL to Ethereum and back", (done) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const { parse_vaa } = await importCoreWasm();
|
const fromAddress = await getAssociatedTokenAddress(
|
||||||
|
|
||||||
const fromAddress = await Token.getAssociatedTokenAddress(
|
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(TEST_SOLANA_TOKEN),
|
new PublicKey(TEST_SOLANA_TOKEN),
|
||||||
keypair.publicKey
|
keypair.publicKey
|
||||||
);
|
);
|
||||||
|
@ -177,9 +171,7 @@ describe("Integration Tests", () => {
|
||||||
let signedVAA = await waitUntilSolanaTxObserved(transaction1);
|
let signedVAA = await waitUntilSolanaTxObserved(transaction1);
|
||||||
|
|
||||||
// we get the solana token id from the VAA
|
// we get the solana token id from the VAA
|
||||||
const { tokenId } = parseNFTPayload(
|
const { tokenId } = parseNftTransferVaa(signedVAA);
|
||||||
Buffer.from(new Uint8Array(parse_vaa(signedVAA).payload))
|
|
||||||
);
|
|
||||||
|
|
||||||
await _redeemOnEth(signedVAA);
|
await _redeemOnEth(signedVAA);
|
||||||
const eth_addr = await nft_bridge.getForeignAssetEth(
|
const eth_addr = await nft_bridge.getForeignAssetEth(
|
||||||
|
@ -200,9 +192,7 @@ describe("Integration Tests", () => {
|
||||||
);
|
);
|
||||||
signedVAA = await waitUntilEthTxObserved(transaction3);
|
signedVAA = await waitUntilEthTxObserved(transaction3);
|
||||||
|
|
||||||
const { name, symbol } = parseNFTPayload(
|
const { name, symbol } = parseNftTransferVaa(signedVAA);
|
||||||
Buffer.from(new Uint8Array(parse_vaa(signedVAA).payload))
|
|
||||||
);
|
|
||||||
|
|
||||||
// if the names match up here, it means all the spl caches work
|
// if the names match up here, it means all the spl caches work
|
||||||
expect(name).toBe("Not a PUNK🎸");
|
expect(name).toBe("Not a PUNK🎸");
|
||||||
|
|
|
@ -1,28 +1,30 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { BN } from "@project-serum/anchor";
|
||||||
|
import { PublicKeyInitData } from "@solana/web3.js";
|
||||||
import { LCDClient } from "@terra-money/terra.js";
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
|
import { isBytes } from "ethers/lib/utils";
|
||||||
import { fromUint8Array } from "js-base64";
|
import { fromUint8Array } from "js-base64";
|
||||||
import { CHAIN_ID_SOLANA } from "..";
|
import { CHAIN_ID_SOLANA } from "..";
|
||||||
import { NFTBridge__factory } from "../ethers-contracts";
|
import { NFTBridge__factory } from "../ethers-contracts";
|
||||||
import { importNftWasm } from "../solana/wasm";
|
import { deriveWrappedMintKey } from "../solana/nftBridge";
|
||||||
import { ChainId, ChainName, coalesceChainId } from "../utils";
|
import { ChainId, ChainName, coalesceChainId } from "../utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
|
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param provider
|
* @param provider
|
||||||
* @param originChain
|
* @param originChain
|
||||||
* @param originAsset zero pad to 32 bytes
|
* @param originAsset zero pad to 32 bytes
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getForeignAssetEth(
|
export async function getForeignAssetEth(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
provider: ethers.Signer | ethers.providers.Provider,
|
provider: ethers.Signer | ethers.providers.Provider,
|
||||||
originChain: ChainId | ChainName,
|
originChain: ChainId | ChainName,
|
||||||
originAsset: Uint8Array
|
originAsset: Uint8Array
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const originChainId = coalesceChainId(originChain);
|
const originChainId = coalesceChainId(originChain);
|
||||||
const tokenBridge = NFTBridge__factory.connect(tokenBridgeAddress, provider);
|
const tokenBridge = NFTBridge__factory.connect(nftBridgeAddress, provider);
|
||||||
try {
|
try {
|
||||||
if (originChainId === CHAIN_ID_SOLANA) {
|
if (originChainId === CHAIN_ID_SOLANA) {
|
||||||
// All NFTs from Solana are minted to the same address, the originAsset is encoded as the tokenId as
|
// All NFTs from Solana are minted to the same address, the originAsset is encoded as the tokenId as
|
||||||
|
@ -41,14 +43,14 @@ export async function getForeignAssetEth(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a foreign asset address on Terra for a provided native chain and asset address
|
* Returns a foreign asset address on Terra for a provided native chain and asset address
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param client
|
* @param client
|
||||||
* @param originChain
|
* @param originChain
|
||||||
* @param originAsset
|
* @param originAsset
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getForeignAssetTerra(
|
export async function getForeignAssetTerra(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
client: LCDClient,
|
client: LCDClient,
|
||||||
originChain: ChainId,
|
originChain: ChainId,
|
||||||
originAsset: Uint8Array
|
originAsset: Uint8Array
|
||||||
|
@ -60,7 +62,7 @@ export async function getForeignAssetTerra(
|
||||||
? "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE="
|
? "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE="
|
||||||
: fromUint8Array(originAsset);
|
: fromUint8Array(originAsset);
|
||||||
const result: { address: string } = await client.wasm.contractQuery(
|
const result: { address: string } = await client.wasm.contractQuery(
|
||||||
tokenBridgeAddress,
|
nftBridgeAddress,
|
||||||
{
|
{
|
||||||
wrapped_registry: {
|
wrapped_registry: {
|
||||||
chain: originChainId,
|
chain: originChainId,
|
||||||
|
@ -76,26 +78,24 @@ export async function getForeignAssetTerra(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a foreign asset address on Solana for a provided native chain and asset address
|
* Returns a foreign asset address on Solana for a provided native chain and asset address
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param originChain
|
* @param originChain
|
||||||
* @param originAsset zero pad to 32 bytes
|
* @param originAsset zero pad to 32 bytes
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getForeignAssetSol(
|
export async function getForeignAssetSolana(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
originChain: ChainId | ChainName,
|
originChain: ChainId | ChainName,
|
||||||
originAsset: Uint8Array,
|
originAsset: string | Uint8Array | Buffer,
|
||||||
tokenId: Uint8Array
|
tokenId: Uint8Array | Buffer | bigint
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const originChainId = coalesceChainId(originChain);
|
|
||||||
const { wrapped_address } = await importNftWasm();
|
|
||||||
const wrappedAddress = wrapped_address(
|
|
||||||
tokenBridgeAddress,
|
|
||||||
originAsset,
|
|
||||||
originChainId,
|
|
||||||
tokenId
|
|
||||||
);
|
|
||||||
const wrappedAddressPK = new PublicKey(wrappedAddress);
|
|
||||||
// we don't require NFT accounts to exist, so don't check them.
|
// we don't require NFT accounts to exist, so don't check them.
|
||||||
return wrappedAddressPK.toString();
|
return deriveWrappedMintKey(
|
||||||
|
nftBridgeAddress,
|
||||||
|
coalesceChainId(originChain) as number,
|
||||||
|
originAsset,
|
||||||
|
isBytes(tokenId) ? BigInt(new BN(tokenId).toString()) : tokenId
|
||||||
|
).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getForeignAssetSol = getForeignAssetSolana;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { NFTBridge__factory } from "../ethers-contracts";
|
import { NFTBridge__factory } from "../ethers-contracts";
|
||||||
import { getSignedVAAHash } from "../bridge";
|
import { getSignedVAAHash } from "../bridge";
|
||||||
import { importCoreWasm } from "../solana/wasm";
|
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
|
||||||
import { LCDClient } from "@terra-money/terra.js";
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { redeemOnTerra } from ".";
|
import { redeemOnTerra } from ".";
|
||||||
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS } from "..";
|
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS } from "..";
|
||||||
|
import { getClaim } from "../solana/wormhole";
|
||||||
|
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||||
|
|
||||||
export async function getIsTransferCompletedEth(
|
export async function getIsTransferCompletedEth(
|
||||||
nftBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
|
@ -14,7 +15,7 @@ export async function getIsTransferCompletedEth(
|
||||||
signedVAA: Uint8Array
|
signedVAA: Uint8Array
|
||||||
) {
|
) {
|
||||||
const nftBridge = NFTBridge__factory.connect(nftBridgeAddress, provider);
|
const nftBridge = NFTBridge__factory.connect(nftBridgeAddress, provider);
|
||||||
const signedVAAHash = await getSignedVAAHash(signedVAA);
|
const signedVAAHash = getSignedVAAHash(signedVAA);
|
||||||
return await nftBridge.isTransferCompleted(signedVAAHash);
|
return await nftBridge.isTransferCompleted(signedVAAHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,15 +58,18 @@ export async function getIsTransferCompletedTerra(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getIsTransferCompletedSolana(
|
export async function getIsTransferCompletedSolana(
|
||||||
nftBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
signedVAA: Uint8Array,
|
signedVAA: SignedVaa,
|
||||||
connection: Connection
|
connection: Connection,
|
||||||
|
commitment?: Commitment
|
||||||
) {
|
) {
|
||||||
const { claim_address } = await importCoreWasm();
|
const parsed = parseVaa(signedVAA);
|
||||||
const claimAddress = await claim_address(nftBridgeAddress, signedVAA);
|
return getClaim(
|
||||||
const claimInfo = await connection.getAccountInfo(
|
connection,
|
||||||
new PublicKey(claimAddress),
|
nftBridgeAddress,
|
||||||
"confirmed"
|
parsed.emitterAddress,
|
||||||
);
|
parsed.emitterChain,
|
||||||
return !!claimInfo;
|
parsed.sequence,
|
||||||
|
commitment
|
||||||
|
).catch((e) => false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { Bridge__factory } from "../ethers-contracts";
|
import { Bridge__factory } from "../ethers-contracts";
|
||||||
import { importNftWasm } from "../solana/wasm";
|
import { getWrappedMeta } from "../solana/nftBridge";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
|
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param provider
|
* @param provider
|
||||||
* @param assetAddress
|
* @param assetAddress
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getIsWrappedAssetEth(
|
export async function getIsWrappedAssetEth(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
provider: ethers.Signer | ethers.providers.Provider,
|
provider: ethers.Signer | ethers.providers.Provider,
|
||||||
assetAddress: string
|
assetAddress: string
|
||||||
) {
|
) {
|
||||||
if (!assetAddress) return false;
|
if (!assetAddress) return false;
|
||||||
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
const tokenBridge = Bridge__factory.connect(nftBridgeAddress, provider);
|
||||||
return await tokenBridge.isWrappedAsset(assetAddress);
|
return await tokenBridge.isWrappedAsset(assetAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not an asset on Solana is a wormhole wrapped asset
|
* Returns whether or not an asset on Solana is a wormhole wrapped asset
|
||||||
* @param connection
|
* @param connection
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param mintAddress
|
* @param mintAddress
|
||||||
|
* @param [commitment]
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getIsWrappedAssetSol(
|
export async function getIsWrappedAssetSolana(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
mintAddress: string
|
mintAddress: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
) {
|
) {
|
||||||
if (!mintAddress) return false;
|
if (!mintAddress) {
|
||||||
const { wrapped_meta_address } = await importNftWasm();
|
return false;
|
||||||
const wrappedMetaAddress = wrapped_meta_address(
|
}
|
||||||
tokenBridgeAddress,
|
return getWrappedMeta(connection, nftBridgeAddress, mintAddress, commitment)
|
||||||
new PublicKey(mintAddress).toBytes()
|
.catch((_) => null)
|
||||||
);
|
.then((meta) => meta != null);
|
||||||
const wrappedMetaAddressPK = new PublicKey(wrappedMetaAddress);
|
|
||||||
const wrappedMetaAccountInfo = await connection.getAccountInfo(
|
|
||||||
wrappedMetaAddressPK
|
|
||||||
);
|
|
||||||
return !!wrappedMetaAccountInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getIsWrappedAssetSol = getIsWrappedAssetSolana;
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import {
|
||||||
|
Commitment,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import { LCDClient } from "@terra-money/terra.js";
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
import { BigNumber, ethers } from "ethers";
|
import { BigNumber, ethers } from "ethers";
|
||||||
import { arrayify, zeroPad } from "ethers/lib/utils";
|
import { arrayify, zeroPad } from "ethers/lib/utils";
|
||||||
import { canonicalAddress, WormholeWrappedInfo } from "..";
|
import { WormholeWrappedInfo } from "..";
|
||||||
|
import { canonicalAddress } from "../cosmos";
|
||||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||||
import { importNftWasm } from "../solana/wasm";
|
import { getWrappedMeta } from "../solana/nftBridge";
|
||||||
import {
|
import {
|
||||||
ChainId,
|
ChainId,
|
||||||
ChainName,
|
ChainName,
|
||||||
|
@ -24,20 +30,20 @@ export interface WormholeWrappedNFTInfo {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param provider
|
* @param provider
|
||||||
* @param wrappedAddress
|
* @param wrappedAddress
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getOriginalAssetEth(
|
export async function getOriginalAssetEth(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
provider: ethers.Signer | ethers.providers.Provider,
|
provider: ethers.Signer | ethers.providers.Provider,
|
||||||
wrappedAddress: string,
|
wrappedAddress: string,
|
||||||
tokenId: string,
|
tokenId: string,
|
||||||
lookupChain: ChainId | ChainName
|
lookupChain: ChainId | ChainName
|
||||||
): Promise<WormholeWrappedNFTInfo> {
|
): Promise<WormholeWrappedNFTInfo> {
|
||||||
const isWrapped = await getIsWrappedAssetEth(
|
const isWrapped = await getIsWrappedAssetEth(
|
||||||
tokenBridgeAddress,
|
nftBridgeAddress,
|
||||||
provider,
|
provider,
|
||||||
wrappedAddress
|
wrappedAddress
|
||||||
);
|
);
|
||||||
|
@ -69,56 +75,49 @@ export async function getOriginalAssetEth(
|
||||||
/**
|
/**
|
||||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||||
* @param connection
|
* @param connection
|
||||||
* @param tokenBridgeAddress
|
* @param nftBridgeAddress
|
||||||
* @param mintAddress
|
* @param mintAddress
|
||||||
|
* @param [commitment]
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function getOriginalAssetSol(
|
export async function getOriginalAssetSolana(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
mintAddress: string
|
mintAddress: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
): Promise<WormholeWrappedNFTInfo> {
|
): Promise<WormholeWrappedNFTInfo> {
|
||||||
if (mintAddress) {
|
|
||||||
// TODO: share some of this with getIsWrappedAssetSol, like a getWrappedMetaAccountAddress or something
|
|
||||||
const { parse_wrapped_meta, wrapped_meta_address } = await importNftWasm();
|
|
||||||
const wrappedMetaAddress = wrapped_meta_address(
|
|
||||||
tokenBridgeAddress,
|
|
||||||
new PublicKey(mintAddress).toBytes()
|
|
||||||
);
|
|
||||||
const wrappedMetaAddressPK = new PublicKey(wrappedMetaAddress);
|
|
||||||
const wrappedMetaAccountInfo = await connection.getAccountInfo(
|
|
||||||
wrappedMetaAddressPK
|
|
||||||
);
|
|
||||||
if (wrappedMetaAccountInfo) {
|
|
||||||
const parsed = parse_wrapped_meta(wrappedMetaAccountInfo.data);
|
|
||||||
const token_id_arr = parsed.token_id as BigUint64Array;
|
|
||||||
const token_id_bytes = [];
|
|
||||||
for (let elem of token_id_arr.reverse()) {
|
|
||||||
token_id_bytes.push(...bigToUint8Array(elem));
|
|
||||||
}
|
|
||||||
const token_id = BigNumber.from(token_id_bytes).toString();
|
|
||||||
return {
|
|
||||||
isWrapped: true,
|
|
||||||
chainId: parsed.chain,
|
|
||||||
assetAddress: parsed.token_address,
|
|
||||||
tokenId: token_id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
const mint = new PublicKey(mintAddress);
|
||||||
|
|
||||||
|
return getWrappedMeta(connection, nftBridgeAddress, mintAddress, commitment)
|
||||||
|
.catch((_) => null)
|
||||||
|
.then((meta) => {
|
||||||
|
if (meta === null) {
|
||||||
|
return {
|
||||||
|
isWrapped: false,
|
||||||
|
chainId: CHAIN_ID_SOLANA,
|
||||||
|
assetAddress: mint.toBytes(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
isWrapped: true,
|
||||||
|
chainId: meta.chain as ChainId,
|
||||||
|
assetAddress: Uint8Array.from(meta.tokenAddress),
|
||||||
|
tokenId: meta.tokenId.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
return {
|
return {
|
||||||
isWrapped: false,
|
isWrapped: false,
|
||||||
chainId: CHAIN_ID_SOLANA,
|
chainId: CHAIN_ID_SOLANA,
|
||||||
assetAddress: new PublicKey(mintAddress).toBytes(),
|
assetAddress: new Uint8Array(32),
|
||||||
};
|
};
|
||||||
} catch (e) {}
|
}
|
||||||
return {
|
|
||||||
isWrapped: false,
|
|
||||||
chainId: CHAIN_ID_SOLANA,
|
|
||||||
assetAddress: new Uint8Array(32),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getOriginalAssetSol = getOriginalAssetSolana;
|
||||||
|
|
||||||
// Derived from https://www.jackieli.dev/posts/bigint-to-uint8array/
|
// Derived from https://www.jackieli.dev/posts/bigint-to-uint8array/
|
||||||
const big0 = BigInt(0);
|
const big0 = BigInt(0);
|
||||||
const big1 = BigInt(1);
|
const big1 = BigInt(1);
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
import {
|
||||||
|
Commitment,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
Transaction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||||
import { ethers, Overrides } from "ethers";
|
import { ethers, Overrides } from "ethers";
|
||||||
import { fromUint8Array } from "js-base64";
|
import { fromUint8Array } from "js-base64";
|
||||||
import { CHAIN_ID_SOLANA } from "..";
|
import { CHAIN_ID_SOLANA } from "..";
|
||||||
import { Bridge__factory } from "../ethers-contracts";
|
import { Bridge__factory } from "../ethers-contracts";
|
||||||
import { ixFromRust } from "../solana";
|
import {
|
||||||
import { importCoreWasm, importNftWasm } from "../solana/wasm";
|
createCompleteTransferNativeInstruction,
|
||||||
|
createCompleteTransferWrappedInstruction,
|
||||||
|
createCompleteWrappedMetaInstruction,
|
||||||
|
} from "../solana/nftBridge";
|
||||||
|
import { parseNftTransferVaa, parseVaa, SignedVaa } from "../vaa";
|
||||||
|
|
||||||
export async function redeemOnEth(
|
export async function redeemOnEth(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
signer: ethers.Signer,
|
signer: ethers.Signer,
|
||||||
signedVAA: Uint8Array,
|
signedVAA: Uint8Array,
|
||||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||||
): Promise<ethers.ContractReceipt> {
|
): Promise<ethers.ContractReceipt> {
|
||||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
const bridge = Bridge__factory.connect(nftBridgeAddress, signer);
|
||||||
const v = await bridge.completeTransfer(signedVAA, overrides);
|
const v = await bridge.completeTransfer(signedVAA, overrides);
|
||||||
const receipt = await v.wait();
|
const receipt = await v.wait();
|
||||||
return receipt;
|
return receipt;
|
||||||
|
@ -22,52 +32,33 @@ export async function redeemOnEth(
|
||||||
export async function isNFTVAASolanaNative(
|
export async function isNFTVAASolanaNative(
|
||||||
signedVAA: Uint8Array
|
signedVAA: Uint8Array
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { parse_vaa } = await importCoreWasm();
|
return parseVaa(signedVAA).payload.readUInt16BE(33) === CHAIN_ID_SOLANA;
|
||||||
const parsedVAA = parse_vaa(signedVAA);
|
|
||||||
const isSolanaNative =
|
|
||||||
Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(33) ===
|
|
||||||
CHAIN_ID_SOLANA;
|
|
||||||
return isSolanaNative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function redeemOnSolana(
|
export async function redeemOnSolana(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
bridgeAddress: string,
|
bridgeAddress: PublicKeyInitData,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
payerAddress: string,
|
payerAddress: PublicKeyInitData,
|
||||||
signedVAA: Uint8Array
|
signedVaa: SignedVaa,
|
||||||
|
toAuthorityAddress?: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
): Promise<Transaction> {
|
): Promise<Transaction> {
|
||||||
const isSolanaNative = await isNFTVAASolanaNative(signedVAA);
|
const parsed = parseNftTransferVaa(signedVaa);
|
||||||
const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
|
const createCompleteTransferInstruction =
|
||||||
await importNftWasm();
|
parsed.tokenChain == CHAIN_ID_SOLANA
|
||||||
const ixs = [];
|
? createCompleteTransferNativeInstruction
|
||||||
if (isSolanaNative) {
|
: createCompleteTransferWrappedInstruction;
|
||||||
ixs.push(
|
const transaction = new Transaction().add(
|
||||||
ixFromRust(
|
createCompleteTransferInstruction(
|
||||||
complete_transfer_native_ix(
|
nftBridgeAddress,
|
||||||
tokenBridgeAddress,
|
bridgeAddress,
|
||||||
bridgeAddress,
|
payerAddress,
|
||||||
payerAddress,
|
parsed,
|
||||||
payerAddress, //TODO: allow for a different address than payer
|
toAuthorityAddress
|
||||||
signedVAA
|
)
|
||||||
)
|
);
|
||||||
)
|
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ixs.push(
|
|
||||||
ixFromRust(
|
|
||||||
complete_transfer_wrapped_ix(
|
|
||||||
tokenBridgeAddress,
|
|
||||||
bridgeAddress,
|
|
||||||
payerAddress,
|
|
||||||
payerAddress, //TODO: allow for a different address than payer
|
|
||||||
signedVAA
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const transaction = new Transaction().add(...ixs);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
transaction.recentBlockhash = blockhash;
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
transaction.feePayer = new PublicKey(payerAddress);
|
||||||
return transaction;
|
return transaction;
|
||||||
|
@ -75,33 +66,36 @@ export async function redeemOnSolana(
|
||||||
|
|
||||||
export async function createMetaOnSolana(
|
export async function createMetaOnSolana(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
bridgeAddress: string,
|
bridgeAddress: PublicKeyInitData,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
payerAddress: string,
|
payerAddress: PublicKeyInitData,
|
||||||
signedVAA: Uint8Array
|
signedVaa: SignedVaa,
|
||||||
|
commitment?: Commitment
|
||||||
): Promise<Transaction> {
|
): Promise<Transaction> {
|
||||||
const { complete_transfer_wrapped_meta_ix } = await importNftWasm();
|
const parsed = parseNftTransferVaa(signedVaa);
|
||||||
const ix = ixFromRust(
|
if (parsed.tokenChain == CHAIN_ID_SOLANA) {
|
||||||
complete_transfer_wrapped_meta_ix(
|
return Promise.reject("parsed.tokenChain == CHAIN_ID_SOLANA");
|
||||||
tokenBridgeAddress,
|
}
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
createCompleteWrappedMetaInstruction(
|
||||||
|
nftBridgeAddress,
|
||||||
bridgeAddress,
|
bridgeAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
signedVAA
|
parsed
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const transaction = new Transaction().add(ix);
|
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
transaction.recentBlockhash = blockhash;
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
transaction.feePayer = new PublicKey(payerAddress);
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function redeemOnTerra(
|
export async function redeemOnTerra(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
walletAddress: string,
|
walletAddress: string,
|
||||||
signedVAA: Uint8Array
|
signedVAA: Uint8Array
|
||||||
): Promise<MsgExecuteContract> {
|
): Promise<MsgExecuteContract> {
|
||||||
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
return new MsgExecuteContract(walletAddress, nftBridgeAddress, {
|
||||||
submit_vaa: {
|
submit_vaa: {
|
||||||
data: fromUint8Array(signedVAA),
|
data: fromUint8Array(signedVAA),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,17 +1,36 @@
|
||||||
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
import { createApproveInstruction } from "@solana/spl-token";
|
||||||
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
import {
|
||||||
|
Commitment,
|
||||||
|
Connection,
|
||||||
|
Keypair,
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
Transaction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||||
import { ethers, Overrides } from "ethers";
|
import { ethers, Overrides } from "ethers";
|
||||||
|
import { BN } from "@project-serum/anchor";
|
||||||
import {
|
import {
|
||||||
NFTBridge__factory,
|
NFTBridge__factory,
|
||||||
NFTImplementation__factory,
|
NFTImplementation__factory,
|
||||||
} from "../ethers-contracts";
|
} from "../ethers-contracts";
|
||||||
import { getBridgeFeeIx, ixFromRust } from "../solana";
|
import { createBridgeFeeTransferInstruction } from "../solana";
|
||||||
import { importNftWasm } from "../solana/wasm";
|
import {
|
||||||
import { ChainId, ChainName, CHAIN_ID_SOLANA, coalesceChainId, createNonce } from "../utils";
|
createApproveAuthoritySignerInstruction,
|
||||||
|
createTransferNativeInstruction,
|
||||||
|
createTransferWrappedInstruction,
|
||||||
|
} from "../solana/nftBridge";
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
ChainName,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
coalesceChainId,
|
||||||
|
createNonce,
|
||||||
|
} from "../utils";
|
||||||
|
import { isBytes } from "ethers/lib/utils";
|
||||||
|
|
||||||
export async function transferFromEth(
|
export async function transferFromEth(
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
signer: ethers.Signer,
|
signer: ethers.Signer,
|
||||||
tokenAddress: string,
|
tokenAddress: string,
|
||||||
tokenID: ethers.BigNumberish,
|
tokenID: ethers.BigNumberish,
|
||||||
|
@ -19,11 +38,11 @@ export async function transferFromEth(
|
||||||
recipientAddress: Uint8Array,
|
recipientAddress: Uint8Array,
|
||||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||||
): Promise<ethers.ContractReceipt> {
|
): Promise<ethers.ContractReceipt> {
|
||||||
const recipientChainId = coalesceChainId(recipientChain)
|
const recipientChainId = coalesceChainId(recipientChain);
|
||||||
//TODO: should we check if token attestation exists on the target chain
|
//TODO: should we check if token attestation exists on the target chain
|
||||||
const token = NFTImplementation__factory.connect(tokenAddress, signer);
|
const token = NFTImplementation__factory.connect(tokenAddress, signer);
|
||||||
await (await token.approve(tokenBridgeAddress, tokenID, overrides)).wait();
|
await (await token.approve(nftBridgeAddress, tokenID, overrides)).wait();
|
||||||
const bridge = NFTBridge__factory.connect(tokenBridgeAddress, signer);
|
const bridge = NFTBridge__factory.connect(nftBridgeAddress, signer);
|
||||||
const v = await bridge.transferNFT(
|
const v = await bridge.transferNFT(
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
tokenID,
|
tokenID,
|
||||||
|
@ -38,90 +57,89 @@ export async function transferFromEth(
|
||||||
|
|
||||||
export async function transferFromSolana(
|
export async function transferFromSolana(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
bridgeAddress: string,
|
bridgeAddress: PublicKeyInitData,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: PublicKeyInitData,
|
||||||
payerAddress: string,
|
payerAddress: PublicKeyInitData,
|
||||||
fromAddress: string,
|
fromAddress: PublicKeyInitData,
|
||||||
mintAddress: string,
|
mintAddress: PublicKeyInitData,
|
||||||
targetAddress: Uint8Array,
|
targetAddress: Uint8Array | Buffer,
|
||||||
targetChain: ChainId | ChainName,
|
targetChain: ChainId | ChainName,
|
||||||
originAddress?: Uint8Array,
|
originAddress?: Uint8Array | Buffer,
|
||||||
originChain?: ChainId | ChainName,
|
originChain?: ChainId | ChainName,
|
||||||
originTokenId?: Uint8Array
|
originTokenId?: Uint8Array | Buffer | number | bigint,
|
||||||
|
commitment?: Commitment
|
||||||
): Promise<Transaction> {
|
): Promise<Transaction> {
|
||||||
const originChainId: ChainId | undefined = originChain ? coalesceChainId(originChain) : undefined
|
const originChainId: ChainId | undefined = originChain
|
||||||
|
? coalesceChainId(originChain)
|
||||||
|
: undefined;
|
||||||
const nonce = createNonce().readUInt32LE(0);
|
const nonce = createNonce().readUInt32LE(0);
|
||||||
const transferIx = await getBridgeFeeIx(
|
const transferIx = await createBridgeFeeTransferInstruction(
|
||||||
connection,
|
connection,
|
||||||
bridgeAddress,
|
bridgeAddress,
|
||||||
payerAddress
|
payerAddress
|
||||||
);
|
);
|
||||||
const {
|
const approvalIx = createApproveAuthoritySignerInstruction(
|
||||||
transfer_native_ix,
|
nftBridgeAddress,
|
||||||
transfer_wrapped_ix,
|
fromAddress,
|
||||||
approval_authority_address,
|
payerAddress
|
||||||
} = await importNftWasm();
|
|
||||||
const approvalIx = Token.createApproveInstruction(
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
new PublicKey(fromAddress),
|
|
||||||
new PublicKey(approval_authority_address(tokenBridgeAddress)),
|
|
||||||
new PublicKey(payerAddress),
|
|
||||||
[],
|
|
||||||
Number(1)
|
|
||||||
);
|
);
|
||||||
let messageKey = Keypair.generate();
|
let message = Keypair.generate();
|
||||||
const isSolanaNative =
|
const isSolanaNative =
|
||||||
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
||||||
if (!isSolanaNative && (!originAddress || !originTokenId)) {
|
if (!isSolanaNative && (!originAddress || !originTokenId)) {
|
||||||
throw new Error(
|
return Promise.reject(
|
||||||
"originAddress and originTokenId are required when specifying originChain"
|
"originAddress and originTokenId are required when specifying originChain"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const ix = ixFromRust(
|
const nftBridgeTransferIx = isSolanaNative
|
||||||
isSolanaNative
|
? createTransferNativeInstruction(
|
||||||
? transfer_native_ix(
|
nftBridgeAddress,
|
||||||
tokenBridgeAddress,
|
bridgeAddress,
|
||||||
bridgeAddress,
|
payerAddress,
|
||||||
payerAddress,
|
message.publicKey,
|
||||||
messageKey.publicKey.toString(),
|
fromAddress,
|
||||||
fromAddress,
|
mintAddress,
|
||||||
mintAddress,
|
nonce,
|
||||||
nonce,
|
targetAddress,
|
||||||
targetAddress,
|
coalesceChainId(targetChain)
|
||||||
coalesceChainId(targetChain)
|
)
|
||||||
)
|
: createTransferWrappedInstruction(
|
||||||
: transfer_wrapped_ix(
|
nftBridgeAddress,
|
||||||
tokenBridgeAddress,
|
bridgeAddress,
|
||||||
bridgeAddress,
|
payerAddress,
|
||||||
payerAddress,
|
message.publicKey,
|
||||||
messageKey.publicKey.toString(),
|
fromAddress,
|
||||||
fromAddress,
|
payerAddress,
|
||||||
payerAddress,
|
originChainId!,
|
||||||
originChainId as number, // checked by isSolanaNative
|
originAddress!,
|
||||||
originAddress as Uint8Array, // checked by throw
|
isBytes(originTokenId)
|
||||||
originTokenId as Uint8Array, // checked by throw
|
? BigInt(new BN(originTokenId).toString())
|
||||||
nonce,
|
: originTokenId!,
|
||||||
targetAddress,
|
nonce,
|
||||||
coalesceChainId(targetChain)
|
targetAddress,
|
||||||
)
|
coalesceChainId(targetChain)
|
||||||
|
);
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
transferIx,
|
||||||
|
approvalIx,
|
||||||
|
nftBridgeTransferIx
|
||||||
);
|
);
|
||||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
transaction.recentBlockhash = blockhash;
|
||||||
transaction.feePayer = new PublicKey(payerAddress);
|
transaction.feePayer = new PublicKey(payerAddress);
|
||||||
transaction.partialSign(messageKey);
|
transaction.partialSign(message);
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function transferFromTerra(
|
export async function transferFromTerra(
|
||||||
walletAddress: string,
|
walletAddress: string,
|
||||||
tokenBridgeAddress: string,
|
nftBridgeAddress: string,
|
||||||
tokenAddress: string,
|
tokenAddress: string,
|
||||||
tokenID: string,
|
tokenID: string,
|
||||||
recipientChain: ChainId | ChainName,
|
recipientChain: ChainId | ChainName,
|
||||||
recipientAddress: Uint8Array
|
recipientAddress: Uint8Array
|
||||||
): Promise<MsgExecuteContract[]> {
|
): Promise<MsgExecuteContract[]> {
|
||||||
const recipientChainId = coalesceChainId(recipientChain)
|
const recipientChainId = coalesceChainId(recipientChain);
|
||||||
const nonce = Math.round(Math.random() * 100000);
|
const nonce = Math.round(Math.random() * 100000);
|
||||||
return [
|
return [
|
||||||
new MsgExecuteContract(
|
new MsgExecuteContract(
|
||||||
|
@ -129,7 +147,7 @@ export async function transferFromTerra(
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
{
|
{
|
||||||
approve: {
|
approve: {
|
||||||
spender: tokenBridgeAddress,
|
spender: nftBridgeAddress,
|
||||||
token_id: tokenID,
|
token_id: tokenID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -137,7 +155,7 @@ export async function transferFromTerra(
|
||||||
),
|
),
|
||||||
new MsgExecuteContract(
|
new MsgExecuteContract(
|
||||||
walletAddress,
|
walletAddress,
|
||||||
tokenBridgeAddress,
|
nftBridgeAddress,
|
||||||
{
|
{
|
||||||
initiate_transfer: {
|
initiate_transfer: {
|
||||||
contract_addr: tokenAddress,
|
contract_addr: tokenAddress,
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Borrowed from coral-xyz/anchor
|
||||||
|
//
|
||||||
|
// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/coder/common.ts
|
||||||
|
|
||||||
|
import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "./idl";
|
||||||
|
import { IdlError } from "./error";
|
||||||
|
|
||||||
|
export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number {
|
||||||
|
if (idlAccount.type.kind === "enum") {
|
||||||
|
let variantSizes = idlAccount.type.variants.map(
|
||||||
|
(variant: IdlEnumVariant) => {
|
||||||
|
if (variant.fields === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return variant.fields
|
||||||
|
.map((f: IdlField | IdlType) => {
|
||||||
|
if (!(typeof f === "object" && "name" in f)) {
|
||||||
|
throw new Error("Tuple enum variants not yet implemented.");
|
||||||
|
}
|
||||||
|
return typeSize(idl, f.type);
|
||||||
|
})
|
||||||
|
.reduce((a: number, b: number) => a + b);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Math.max(...variantSizes) + 1;
|
||||||
|
}
|
||||||
|
if (idlAccount.type.fields === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return idlAccount.type.fields
|
||||||
|
.map((f) => typeSize(idl, f.type))
|
||||||
|
.reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 "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)}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
// Borrowed from coral-xyz/anchor
|
||||||
|
//
|
||||||
|
// https://github.com/coral-xyz/anchor/blob/master/ts/packages/anchor/src/idl.ts
|
||||||
|
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import * as borsh from "@project-serum/borsh";
|
||||||
|
|
||||||
|
export type Idl = {
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
docs?: string[];
|
||||||
|
instructions: IdlInstruction[];
|
||||||
|
state?: IdlState;
|
||||||
|
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 IdlState = {
|
||||||
|
struct: IdlTypeDef;
|
||||||
|
methods: IdlStateMethod[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IdlStateMethod = IdlInstruction;
|
||||||
|
|
||||||
|
export type IdlAccountItem = IdlAccount | IdlAccounts;
|
||||||
|
|
||||||
|
export type IdlAccount = {
|
||||||
|
name: string;
|
||||||
|
isMut: boolean;
|
||||||
|
isSigner: boolean;
|
||||||
|
docs?: 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 IdlTypeDefTy = IdlTypeDefTyEnum | IdlTypeDefTyStruct;
|
||||||
|
|
||||||
|
export type IdlTypeDefStruct = Array<IdlField>;
|
||||||
|
|
||||||
|
export type IdlType =
|
||||||
|
| "bool"
|
||||||
|
| "u8"
|
||||||
|
| "i8"
|
||||||
|
| "u16"
|
||||||
|
| "i16"
|
||||||
|
| "u32"
|
||||||
|
| "i32"
|
||||||
|
| "f32"
|
||||||
|
| "u64"
|
||||||
|
| "i64"
|
||||||
|
| "f64"
|
||||||
|
| "u128"
|
||||||
|
| "i128"
|
||||||
|
| "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<PublicKey> {
|
||||||
|
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<IdlProgramAccount> = 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);
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./common";
|
||||||
|
export * from "./error";
|
||||||
|
export * from "./idl";
|
|
@ -1,25 +0,0 @@
|
||||||
import { Connection, PublicKey, SystemProgram } from "@solana/web3.js";
|
|
||||||
import { importCoreWasm } from "./wasm";
|
|
||||||
|
|
||||||
export async function getBridgeFeeIx(
|
|
||||||
connection: Connection,
|
|
||||||
bridgeAddress: string,
|
|
||||||
payerAddress: string
|
|
||||||
) {
|
|
||||||
const bridge = await importCoreWasm();
|
|
||||||
const feeAccount = await bridge.fee_collector_address(bridgeAddress);
|
|
||||||
const bridgeStatePK = new PublicKey(bridge.state_address(bridgeAddress));
|
|
||||||
const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK);
|
|
||||||
if (bridgeStateAccountInfo?.data === undefined) {
|
|
||||||
throw new Error("bridge state not found");
|
|
||||||
}
|
|
||||||
const bridgeState = bridge.parse_state(
|
|
||||||
new Uint8Array(bridgeStateAccountInfo?.data)
|
|
||||||
);
|
|
||||||
const transferIx = SystemProgram.transfer({
|
|
||||||
fromPubkey: new PublicKey(payerAddress),
|
|
||||||
toPubkey: new PublicKey(feeAccount),
|
|
||||||
lamports: bridgeState.config.fee,
|
|
||||||
});
|
|
||||||
return transferIx;
|
|
||||||
}
|
|
|
@ -1,9 +1,15 @@
|
||||||
export * from "./getBridgeFeeIx";
|
export * from "./utils";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createPostVaaInstruction as createPostVaaInstructionSolana,
|
|
||||||
createVerifySignaturesInstructions as createVerifySignaturesInstructionsSolana,
|
|
||||||
postVaa as postVaaSolana,
|
postVaa as postVaaSolana,
|
||||||
postVaaWithRetry as postVaaSolanaWithRetry,
|
postVaaWithRetry as postVaaSolanaWithRetry,
|
||||||
} from "./postVaa";
|
} from "./sendAndConfirmPostVaa";
|
||||||
export * from "./rust";
|
export {
|
||||||
export * from "./wasm";
|
createVerifySignaturesInstructions as createVerifySignaturesInstructionsSolana,
|
||||||
|
createPostVaaInstruction as createPostVaaInstructionSolana,
|
||||||
|
createBridgeFeeTransferInstruction,
|
||||||
|
getPostMessageAccounts as getWormholeCpiAccounts,
|
||||||
|
} from "./wormhole";
|
||||||
|
|
||||||
|
export * from "./wormhole/cpi";
|
||||||
|
export * from "./tokenBridge/cpi";
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Connection, Commitment, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
getTokenBridgeConfig,
|
||||||
|
TokenBridgeConfig,
|
||||||
|
} from "../../tokenBridge";
|
||||||
|
|
||||||
|
export const deriveNftBridgeConfigKey = deriveTokenBridgeConfigKey;
|
||||||
|
|
||||||
|
export async function getNftBridgeConfig(
|
||||||
|
connection: Connection,
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<NftBridgeConfig> {
|
||||||
|
return getTokenBridgeConfig(connection, nftBridgeProgramId, commitment);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NftBridgeConfig extends TokenBridgeConfig {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
export * from "./config";
|
||||||
|
export * from "./wrapped";
|
||||||
|
|
||||||
|
export {
|
||||||
|
EndpointRegistration,
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
deriveUpgradeAuthorityKey,
|
||||||
|
getEndpointRegistration,
|
||||||
|
} from "../../tokenBridge";
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { BN } from "@project-serum/anchor";
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
Commitment,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
tryNativeToUint8Array,
|
||||||
|
} from "../../../utils";
|
||||||
|
import { deriveAddress, getAccountData } from "../../utils";
|
||||||
|
import { deriveWrappedMetaKey } from "../../tokenBridge";
|
||||||
|
|
||||||
|
export { deriveWrappedMetaKey } from "../../tokenBridge";
|
||||||
|
|
||||||
|
export function deriveWrappedMintKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
tokenChain: number | ChainId,
|
||||||
|
tokenAddress: Buffer | Uint8Array | string,
|
||||||
|
tokenId: bigint | number
|
||||||
|
): PublicKey {
|
||||||
|
if (tokenChain == CHAIN_ID_SOLANA) {
|
||||||
|
throw new Error(
|
||||||
|
"tokenChain == CHAIN_ID_SOLANA does not have wrapped mint key"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof tokenAddress == "string") {
|
||||||
|
tokenAddress = tryNativeToUint8Array(tokenAddress, tokenChain as ChainId);
|
||||||
|
}
|
||||||
|
return deriveAddress(
|
||||||
|
[
|
||||||
|
Buffer.from("wrapped"),
|
||||||
|
(() => {
|
||||||
|
const buf = Buffer.alloc(2);
|
||||||
|
buf.writeUInt16BE(tokenChain as number);
|
||||||
|
return buf;
|
||||||
|
})(),
|
||||||
|
tokenAddress,
|
||||||
|
new BN(tokenId.toString()).toBuffer("be", 32),
|
||||||
|
],
|
||||||
|
tokenBridgeProgramId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWrappedMeta(
|
||||||
|
connection: Connection,
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<WrappedMeta> {
|
||||||
|
return connection
|
||||||
|
.getAccountInfo(
|
||||||
|
deriveWrappedMetaKey(tokenBridgeProgramId, mint),
|
||||||
|
commitment
|
||||||
|
)
|
||||||
|
.then((info) => WrappedMeta.deserialize(getAccountData(info)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WrappedMeta {
|
||||||
|
chain: number;
|
||||||
|
tokenAddress: Buffer;
|
||||||
|
tokenId: bigint;
|
||||||
|
|
||||||
|
constructor(chain: number, tokenAddress: Buffer, tokenId: bigint) {
|
||||||
|
this.chain = chain;
|
||||||
|
this.tokenAddress = tokenAddress;
|
||||||
|
this.tokenId = tokenId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserialize(data: Buffer): WrappedMeta {
|
||||||
|
if (data.length != 66) {
|
||||||
|
throw new Error("data.length != 66");
|
||||||
|
}
|
||||||
|
const chain = data.readUInt16LE(0);
|
||||||
|
const tokenAddress = data.subarray(2, 34);
|
||||||
|
const tokenId = BigInt(
|
||||||
|
new BN(data.subarray(34, 66), undefined, "le").toString()
|
||||||
|
);
|
||||||
|
return new WrappedMeta(chain, tokenAddress, tokenId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { AccountsCoder, Idl } from "@project-serum/anchor";
|
||||||
|
import { accountSize, IdlTypeDef } from "../../anchor";
|
||||||
|
|
||||||
|
export class NftBridgeAccountsCoder<A extends string = string>
|
||||||
|
implements AccountsCoder
|
||||||
|
{
|
||||||
|
constructor(private idl: Idl) {}
|
||||||
|
|
||||||
|
public async encode<T = any>(accountName: A, account: T): Promise<Buffer> {
|
||||||
|
switch (accountName) {
|
||||||
|
default: {
|
||||||
|
throw new Error(`Invalid account name: ${accountName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode<T = any>(accountName: A, ix: Buffer): T {
|
||||||
|
return this.decodeUnchecked(accountName, ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decodeUnchecked<T = any>(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: IdlTypeDef): number {
|
||||||
|
return accountSize(this.idl, idlAccount) ?? 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { EventCoder, Event, Idl } from "@project-serum/anchor";
|
||||||
|
import { IdlEvent } from "../../anchor";
|
||||||
|
|
||||||
|
export class NftBridgeEventsCoder implements EventCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
|
||||||
|
_log: string
|
||||||
|
): Event<E, T> | null {
|
||||||
|
throw new Error("NFT Bridge program does not have events");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Coder, Idl } from "@project-serum/anchor";
|
||||||
|
import { NftBridgeAccountsCoder } from "./accounts";
|
||||||
|
import { NftBridgeEventsCoder } from "./events";
|
||||||
|
import { NftBridgeInstructionCoder } from "./instruction";
|
||||||
|
import { NftBridgeStateCoder } from "./state";
|
||||||
|
import { NftBridgeTypesCoder } from "./types";
|
||||||
|
|
||||||
|
export { NftBridgeInstruction } from "./instruction";
|
||||||
|
|
||||||
|
export class NftBridgeCoder implements Coder {
|
||||||
|
readonly instruction: NftBridgeInstructionCoder;
|
||||||
|
readonly accounts: NftBridgeAccountsCoder;
|
||||||
|
readonly state: NftBridgeStateCoder;
|
||||||
|
readonly events: NftBridgeEventsCoder;
|
||||||
|
readonly types: NftBridgeTypesCoder;
|
||||||
|
|
||||||
|
constructor(idl: Idl) {
|
||||||
|
this.instruction = new NftBridgeInstructionCoder(idl);
|
||||||
|
this.accounts = new NftBridgeAccountsCoder(idl);
|
||||||
|
this.state = new NftBridgeStateCoder(idl);
|
||||||
|
this.events = new NftBridgeEventsCoder(idl);
|
||||||
|
this.types = new NftBridgeTypesCoder(idl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { Idl, InstructionCoder } from "@project-serum/anchor";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
|
||||||
|
export class NftBridgeInstructionCoder implements InstructionCoder {
|
||||||
|
constructor(_: Idl) {}
|
||||||
|
|
||||||
|
encode(ixName: string, ix: any): Buffer {
|
||||||
|
switch (ixName) {
|
||||||
|
case "initialize": {
|
||||||
|
return encodeInitialize(ix);
|
||||||
|
}
|
||||||
|
case "completeNative": {
|
||||||
|
return encodeCompleteNative(ix);
|
||||||
|
}
|
||||||
|
case "completeWrapped": {
|
||||||
|
return encodeCompleteWrapped(ix);
|
||||||
|
}
|
||||||
|
case "completeWrappedMeta": {
|
||||||
|
return encodeCompleteWrappedMeta(ix);
|
||||||
|
}
|
||||||
|
case "transferWrapped": {
|
||||||
|
return encodeTransferWrapped(ix);
|
||||||
|
}
|
||||||
|
case "transferNative": {
|
||||||
|
return encodeTransferNative(ix);
|
||||||
|
}
|
||||||
|
case "registerChain": {
|
||||||
|
return encodeRegisterChain(ix);
|
||||||
|
}
|
||||||
|
case "upgradeContract": {
|
||||||
|
return encodeUpgradeContract(ix);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Invalid instruction: ${ixName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeState(_ixName: string, _ix: any): Buffer {
|
||||||
|
throw new Error("NFT Bridge program does not have state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Solitaire enum of existing the NFT Bridge's instructions.
|
||||||
|
*
|
||||||
|
* https://github.com/certusone/wormhole/blob/dev.v2/solana/modules/nft_bridge/program/src/lib.rs#L74
|
||||||
|
*/
|
||||||
|
export enum NftBridgeInstruction {
|
||||||
|
Initialize,
|
||||||
|
CompleteNative,
|
||||||
|
CompleteWrapped,
|
||||||
|
CompleteWrappedMeta,
|
||||||
|
TransferWrapped,
|
||||||
|
TransferNative,
|
||||||
|
RegisterChain,
|
||||||
|
UpgradeContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeNftBridgeInstructionData(
|
||||||
|
instructionType: NftBridgeInstruction,
|
||||||
|
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 encodeNftBridgeInstructionData(
|
||||||
|
NftBridgeInstruction.Initialize,
|
||||||
|
serialized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeCompleteNative({}: any) {
|
||||||
|
return encodeNftBridgeInstructionData(NftBridgeInstruction.CompleteNative);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeCompleteWrapped({}: any) {
|
||||||
|
return encodeNftBridgeInstructionData(NftBridgeInstruction.CompleteWrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeCompleteWrappedMeta({}: any) {
|
||||||
|
return encodeNftBridgeInstructionData(
|
||||||
|
NftBridgeInstruction.CompleteWrappedMeta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeTransferData({ nonce, targetAddress, targetChain }: any) {
|
||||||
|
if (!Buffer.isBuffer(targetAddress)) {
|
||||||
|
throw new Error("targetAddress must be Buffer");
|
||||||
|
}
|
||||||
|
const serialized = Buffer.alloc(38);
|
||||||
|
serialized.writeUInt32LE(nonce, 0);
|
||||||
|
serialized.write(targetAddress.toString("hex"), 4, "hex");
|
||||||
|
serialized.writeUInt16LE(targetChain, 36);
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeTransferWrapped({ nonce, targetAddress, targetChain }: any) {
|
||||||
|
return encodeNftBridgeInstructionData(
|
||||||
|
NftBridgeInstruction.TransferWrapped,
|
||||||
|
encodeTransferData({ nonce, targetAddress, targetChain })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeTransferNative({ nonce, targetAddress, targetChain }: any) {
|
||||||
|
return encodeNftBridgeInstructionData(
|
||||||
|
NftBridgeInstruction.TransferNative,
|
||||||
|
encodeTransferData({ nonce, targetAddress, targetChain })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeRegisterChain({}: any) {
|
||||||
|
return encodeNftBridgeInstructionData(NftBridgeInstruction.RegisterChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeUpgradeContract({}: any) {
|
||||||
|
return encodeNftBridgeInstructionData(NftBridgeInstruction.UpgradeContract);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Idl, StateCoder } from "@project-serum/anchor";
|
||||||
|
|
||||||
|
export class NftBridgeStateCoder implements StateCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
encode<T = any>(_name: string, _account: T): Promise<Buffer> {
|
||||||
|
throw new Error("NFT Bridge program does not have state");
|
||||||
|
}
|
||||||
|
decode<T = any>(_ix: Buffer): T {
|
||||||
|
throw new Error("NFT Bridge program does not have state");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Idl, TypesCoder } from "@project-serum/anchor";
|
||||||
|
|
||||||
|
export class NftBridgeTypesCoder implements TypesCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
encode<T = any>(_name: string, _type: T): Buffer {
|
||||||
|
throw new Error("NFT Bridge program does not have user-defined types");
|
||||||
|
}
|
||||||
|
decode<T = any>(_name: string, _typeData: Buffer): T {
|
||||||
|
throw new Error("NFT Bridge program does not have user-defined types");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./accounts";
|
||||||
|
export * from "./instructions";
|
||||||
|
export * from "./program";
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { createApproveAuthoritySignerInstruction as _createApproveAuthoritySignerInstruction } from "../../tokenBridge";
|
||||||
|
|
||||||
|
export function createApproveAuthoritySignerInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
tokenAccount: PublicKeyInitData,
|
||||||
|
owner: PublicKeyInitData
|
||||||
|
) {
|
||||||
|
return _createApproveAuthoritySignerInstruction(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
tokenAccount,
|
||||||
|
owner,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import {
|
||||||
|
createReadOnlyNftBridgeProgramInterface,
|
||||||
|
tokenIdToMint,
|
||||||
|
} from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedNftTransferVaa,
|
||||||
|
parseNftTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
|
||||||
|
export function createCompleteTransferNativeInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa,
|
||||||
|
toAuthority?: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.completeNative();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getCompleteTransferNativeAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
vaa,
|
||||||
|
toAuthority
|
||||||
|
) 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;
|
||||||
|
toAuthority: PublicKey;
|
||||||
|
custody: PublicKey;
|
||||||
|
mint: PublicKey;
|
||||||
|
custodySigner: PublicKey;
|
||||||
|
rent: PublicKey;
|
||||||
|
systemProgram: PublicKey;
|
||||||
|
tokenProgram: PublicKey;
|
||||||
|
wormholeProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompleteTransferNativeAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa,
|
||||||
|
toAuthority?: PublicKeyInitData
|
||||||
|
): CompleteTransferNativeAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseNftTransferVaa(vaa) : vaa;
|
||||||
|
// the mint key is encoded in the tokenId when it was transferred out
|
||||||
|
const mint = tokenIdToMint(parsed.tokenId);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
to: new PublicKey(parsed.to),
|
||||||
|
toAuthority: new PublicKey(toAuthority === undefined ? payer : toAuthority),
|
||||||
|
custody: deriveCustodyKey(nftBridgeProgramId, mint),
|
||||||
|
mint,
|
||||||
|
custodySigner: deriveCustodySignerKey(nftBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
} from "@solana/spl-token";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedNftTransferVaa,
|
||||||
|
parseNftTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
import { SplTokenMetadataProgram } from "../../utils";
|
||||||
|
|
||||||
|
export function createCompleteTransferWrappedInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa,
|
||||||
|
toAuthority?: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.completeWrapped();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getCompleteTransferWrappedAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
vaa,
|
||||||
|
toAuthority
|
||||||
|
) 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;
|
||||||
|
toAuthority: PublicKey;
|
||||||
|
mint: PublicKey;
|
||||||
|
wrappedMeta: PublicKey;
|
||||||
|
mintAuthority: PublicKey;
|
||||||
|
rent: PublicKey;
|
||||||
|
systemProgram: PublicKey;
|
||||||
|
tokenProgram: PublicKey;
|
||||||
|
splMetadataProgram: PublicKey;
|
||||||
|
associatedTokenProgram: PublicKey;
|
||||||
|
wormholeProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompleteTransferWrappedAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa,
|
||||||
|
toAuthority?: PublicKeyInitData
|
||||||
|
): CompleteTransferWrappedAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseNftTransferVaa(vaa) : vaa;
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.tokenChain,
|
||||||
|
parsed.tokenAddress,
|
||||||
|
parsed.tokenId
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
to: new PublicKey(parsed.to),
|
||||||
|
toAuthority: new PublicKey(toAuthority === undefined ? payer : toAuthority),
|
||||||
|
mint,
|
||||||
|
wrappedMeta: deriveWrappedMetaKey(nftBridgeProgramId, mint),
|
||||||
|
mintAuthority: deriveMintAuthorityKey(nftBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
splMetadataProgram: SplTokenMetadataProgram.programId,
|
||||||
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedNftTransferVaa,
|
||||||
|
parseNftTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
import {
|
||||||
|
deriveSplTokenMetadataKey,
|
||||||
|
SplTokenMetadataProgram,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
|
export function createCompleteWrappedMetaInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.completeWrappedMeta();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getCompleteWrappedMetaAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
vaa
|
||||||
|
) as any,
|
||||||
|
signers: undefined,
|
||||||
|
remainingAccounts: undefined,
|
||||||
|
preInstructions: undefined,
|
||||||
|
postInstructions: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompleteWrappedMetaAccounts {
|
||||||
|
payer: PublicKey;
|
||||||
|
config: PublicKey;
|
||||||
|
vaa: PublicKey;
|
||||||
|
endpoint: PublicKey;
|
||||||
|
mint: PublicKey;
|
||||||
|
wrappedMeta: PublicKey;
|
||||||
|
splMetadata: PublicKey;
|
||||||
|
mintAuthority: PublicKey;
|
||||||
|
rent: PublicKey;
|
||||||
|
systemProgram: PublicKey;
|
||||||
|
tokenProgram: PublicKey;
|
||||||
|
splMetadataProgram: PublicKey;
|
||||||
|
wormholeProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompleteWrappedMetaAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftTransferVaa
|
||||||
|
): CompleteWrappedMetaAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseNftTransferVaa(vaa) : vaa;
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.tokenChain,
|
||||||
|
parsed.tokenAddress,
|
||||||
|
parsed.tokenId
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
mint,
|
||||||
|
wrappedMeta: deriveWrappedMetaKey(nftBridgeProgramId, mint),
|
||||||
|
splMetadata: deriveSplTokenMetadataKey(mint),
|
||||||
|
mintAuthority: deriveMintAuthorityKey(nftBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
splMetadataProgram: SplTokenMetadataProgram.programId,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveUpgradeAuthorityKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedNftBridgeRegisterChainVaa,
|
||||||
|
ParsedNftBridgeUpgradeContractVaa,
|
||||||
|
parseNftBridgeRegisterChainVaa,
|
||||||
|
parseNftBridgeUpgradeContractVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
import { BpfLoaderUpgradeable, deriveUpgradeableProgramKey } from "../../utils";
|
||||||
|
|
||||||
|
export function createRegisterChainInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftBridgeRegisterChainVaa
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.registerChain();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getRegisterChainAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
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(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftBridgeRegisterChainVaa
|
||||||
|
): RegisterChainAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseNftBridgeRegisterChainVaa(vaa) : vaa;
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.foreignChain,
|
||||||
|
parsed.foreignAddress
|
||||||
|
),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUpgradeContractInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftBridgeUpgradeContractVaa,
|
||||||
|
spill?: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.upgradeContract();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getUpgradeContractAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
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;
|
||||||
|
nftBridgeProgram: PublicKey;
|
||||||
|
rent: PublicKey;
|
||||||
|
clock: PublicKey;
|
||||||
|
bpfLoaderUpgradeable: PublicKey;
|
||||||
|
systemProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUpgradeContractAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedNftBridgeUpgradeContractVaa,
|
||||||
|
spill?: PublicKeyInitData
|
||||||
|
): UpgradeContractAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseNftBridgeUpgradeContractVaa(vaa) : vaa;
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
upgradeAuthority: deriveUpgradeAuthorityKey(nftBridgeProgramId),
|
||||||
|
spill: new PublicKey(spill === undefined ? payer : spill),
|
||||||
|
implementation: new PublicKey(parsed.newContract),
|
||||||
|
programData: deriveUpgradeableProgramKey(nftBridgeProgramId),
|
||||||
|
nftBridgeProgram: new PublicKey(nftBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
clock: SYSVAR_CLOCK_PUBKEY,
|
||||||
|
bpfLoaderUpgradeable: BpfLoaderUpgradeable.programId,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export * from "./approve";
|
||||||
|
export * from "./completeNative";
|
||||||
|
export * from "./completeWrapped";
|
||||||
|
export * from "./completeWrappedMeta";
|
||||||
|
export * from "./initialize";
|
||||||
|
export * from "./governance";
|
||||||
|
export * from "./transferNative";
|
||||||
|
export * from "./transferWrapped";
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveNftBridgeConfigKey } from "../accounts";
|
||||||
|
|
||||||
|
export function createInitializeInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods = createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.initialize(wormholeProgramId as any);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getInitializeAccounts(nftBridgeProgramId, 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(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData
|
||||||
|
): InitializeAccounts {
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
deriveSplTokenMetadataKey,
|
||||||
|
SplTokenMetadataProgram,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
|
export function createTransferNativeInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
message: PublicKeyInitData,
|
||||||
|
from: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData,
|
||||||
|
nonce: number,
|
||||||
|
targetAddress: Buffer | Uint8Array,
|
||||||
|
targetChain: number
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods = createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.transferNative(
|
||||||
|
nonce,
|
||||||
|
Buffer.from(targetAddress) as any,
|
||||||
|
targetChain
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getTransferNativeAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
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;
|
||||||
|
splMetadata: 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;
|
||||||
|
splMetadataProgram: PublicKey;
|
||||||
|
wormholeProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTransferNativeAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
message: PublicKeyInitData,
|
||||||
|
from: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData
|
||||||
|
): TransferNativeAccounts {
|
||||||
|
const {
|
||||||
|
bridge: wormholeBridge,
|
||||||
|
message: wormholeMessage,
|
||||||
|
emitter: wormholeEmitter,
|
||||||
|
sequence: wormholeSequence,
|
||||||
|
feeCollector: wormholeFeeCollector,
|
||||||
|
clock,
|
||||||
|
rent,
|
||||||
|
systemProgram,
|
||||||
|
} = getPostMessageAccounts(
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
nftBridgeProgramId,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
from: new PublicKey(from),
|
||||||
|
mint: new PublicKey(mint),
|
||||||
|
splMetadata: deriveSplTokenMetadataKey(mint),
|
||||||
|
custody: deriveCustodyKey(nftBridgeProgramId, mint),
|
||||||
|
authoritySigner: deriveAuthoritySignerKey(nftBridgeProgramId),
|
||||||
|
custodySigner: deriveCustodySignerKey(nftBridgeProgramId),
|
||||||
|
wormholeBridge,
|
||||||
|
wormholeMessage,
|
||||||
|
wormholeEmitter,
|
||||||
|
wormholeSequence,
|
||||||
|
wormholeFeeCollector,
|
||||||
|
clock,
|
||||||
|
rent,
|
||||||
|
systemProgram,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
splMetadataProgram: SplTokenMetadataProgram.programId,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyNftBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveNftBridgeConfigKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
deriveSplTokenMetadataKey,
|
||||||
|
SplTokenMetadataProgram,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
|
export function createTransferWrappedInstruction(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
message: PublicKeyInitData,
|
||||||
|
from: PublicKeyInitData,
|
||||||
|
fromOwner: PublicKeyInitData,
|
||||||
|
tokenChain: number,
|
||||||
|
tokenAddress: Buffer | Uint8Array,
|
||||||
|
tokenId: bigint | number,
|
||||||
|
nonce: number,
|
||||||
|
targetAddress: Buffer | Uint8Array,
|
||||||
|
targetChain: number
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods = createReadOnlyNftBridgeProgramInterface(
|
||||||
|
nftBridgeProgramId
|
||||||
|
).methods.transferWrapped(
|
||||||
|
nonce,
|
||||||
|
Buffer.from(targetAddress) as any,
|
||||||
|
targetChain
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return methods._ixFn(...methods._args, {
|
||||||
|
accounts: getTransferWrappedAccounts(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
message,
|
||||||
|
from,
|
||||||
|
fromOwner,
|
||||||
|
tokenChain,
|
||||||
|
tokenAddress,
|
||||||
|
tokenId
|
||||||
|
) 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;
|
||||||
|
splMetadata: PublicKey;
|
||||||
|
authoritySigner: PublicKey;
|
||||||
|
wormholeBridge: PublicKey;
|
||||||
|
wormholeMessage: PublicKey;
|
||||||
|
wormholeEmitter: PublicKey;
|
||||||
|
wormholeSequence: PublicKey;
|
||||||
|
wormholeFeeCollector: PublicKey;
|
||||||
|
clock: PublicKey;
|
||||||
|
rent: PublicKey;
|
||||||
|
systemProgram: PublicKey;
|
||||||
|
tokenProgram: PublicKey;
|
||||||
|
splMetadataProgram: PublicKey;
|
||||||
|
wormholeProgram: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTransferWrappedAccounts(
|
||||||
|
nftBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
message: PublicKeyInitData,
|
||||||
|
from: PublicKeyInitData,
|
||||||
|
fromOwner: PublicKeyInitData,
|
||||||
|
tokenChain: number,
|
||||||
|
tokenAddress: Buffer | Uint8Array,
|
||||||
|
tokenId: bigint | number
|
||||||
|
): TransferWrappedAccounts {
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
nftBridgeProgramId,
|
||||||
|
tokenChain,
|
||||||
|
tokenAddress,
|
||||||
|
tokenId
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
bridge: wormholeBridge,
|
||||||
|
message: wormholeMessage,
|
||||||
|
emitter: wormholeEmitter,
|
||||||
|
sequence: wormholeSequence,
|
||||||
|
feeCollector: wormholeFeeCollector,
|
||||||
|
clock,
|
||||||
|
rent,
|
||||||
|
systemProgram,
|
||||||
|
} = getPostMessageAccounts(
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
nftBridgeProgramId,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveNftBridgeConfigKey(nftBridgeProgramId),
|
||||||
|
from: new PublicKey(from),
|
||||||
|
fromOwner: new PublicKey(fromOwner),
|
||||||
|
mint,
|
||||||
|
wrappedMeta: deriveWrappedMetaKey(nftBridgeProgramId, mint),
|
||||||
|
splMetadata: deriveSplTokenMetadataKey(mint),
|
||||||
|
authoritySigner: deriveAuthoritySignerKey(nftBridgeProgramId),
|
||||||
|
wormholeBridge,
|
||||||
|
wormholeMessage,
|
||||||
|
wormholeEmitter,
|
||||||
|
wormholeSequence,
|
||||||
|
wormholeFeeCollector,
|
||||||
|
clock,
|
||||||
|
rent,
|
||||||
|
systemProgram,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
splMetadataProgram: SplTokenMetadataProgram.programId,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Connection, PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { BN, Program, Provider } from "@project-serum/anchor";
|
||||||
|
import { createReadOnlyProvider } from "../utils";
|
||||||
|
import { NftBridgeCoder } from "./coder";
|
||||||
|
import { NftBridge } from "../types/nftBridge";
|
||||||
|
|
||||||
|
import IDL from "../../anchor-idl/nft_bridge.json";
|
||||||
|
|
||||||
|
export const NFT_TRANSFER_NATIVE_TOKEN_ADDRESS = Buffer.alloc(32, 1);
|
||||||
|
|
||||||
|
export function createNftBridgeProgramInterface(
|
||||||
|
programId: PublicKeyInitData,
|
||||||
|
provider?: Provider
|
||||||
|
): Program<NftBridge> {
|
||||||
|
return new Program<NftBridge>(
|
||||||
|
IDL as NftBridge,
|
||||||
|
new PublicKey(programId),
|
||||||
|
provider === undefined ? ({ connection: null } as any) : provider,
|
||||||
|
coder()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReadOnlyNftBridgeProgramInterface(
|
||||||
|
programId: PublicKeyInitData,
|
||||||
|
connection?: Connection
|
||||||
|
): Program<NftBridge> {
|
||||||
|
return createNftBridgeProgramInterface(
|
||||||
|
programId,
|
||||||
|
createReadOnlyProvider(connection)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function coder(): NftBridgeCoder {
|
||||||
|
return new NftBridgeCoder(IDL as NftBridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenIdToMint(tokenId: bigint) {
|
||||||
|
return new PublicKey(new BN(tokenId.toString()).toBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mintToTokenId(mint: PublicKeyInitData) {
|
||||||
|
return BigInt(new BN(new PublicKey(mint).toBuffer()).toString());
|
||||||
|
}
|
|
@ -1,212 +0,0 @@
|
||||||
import {
|
|
||||||
Connection,
|
|
||||||
Keypair,
|
|
||||||
PublicKey,
|
|
||||||
Transaction,
|
|
||||||
TransactionInstruction,
|
|
||||||
} from "@solana/web3.js";
|
|
||||||
import { chunks } from "..";
|
|
||||||
import { sendAndConfirmTransactionsWithRetry } from "../utils/solana";
|
|
||||||
import { ixFromRust } from "./rust";
|
|
||||||
import { importCoreWasm } from "./wasm";
|
|
||||||
|
|
||||||
export async function postVaaWithRetry(
|
|
||||||
connection: Connection,
|
|
||||||
signTransaction: (transaction: Transaction) => Promise<Transaction>,
|
|
||||||
bridge_id: string,
|
|
||||||
payer: string,
|
|
||||||
vaa: Buffer,
|
|
||||||
maxRetries: number
|
|
||||||
) {
|
|
||||||
const unsignedTransactions: Transaction[] = [];
|
|
||||||
const signature_set = Keypair.generate();
|
|
||||||
const instructions = await createVerifySignaturesInstructions(
|
|
||||||
connection,
|
|
||||||
bridge_id,
|
|
||||||
payer,
|
|
||||||
vaa,
|
|
||||||
signature_set
|
|
||||||
);
|
|
||||||
const finalInstruction = await createPostVaaInstruction(
|
|
||||||
bridge_id,
|
|
||||||
payer,
|
|
||||||
vaa,
|
|
||||||
signature_set
|
|
||||||
);
|
|
||||||
if (!finalInstruction) {
|
|
||||||
return Promise.reject("Failed to construct the transaction.");
|
|
||||||
}
|
|
||||||
|
|
||||||
//The verify signatures instructions can be batched into groups of 2 safely,
|
|
||||||
//reducing the total number of transactions.
|
|
||||||
const batchableChunks = chunks(instructions, 2);
|
|
||||||
batchableChunks.forEach((chunk) => {
|
|
||||||
let transaction;
|
|
||||||
if (chunk.length === 1) {
|
|
||||||
transaction = new Transaction().add(chunk[0]);
|
|
||||||
} else {
|
|
||||||
transaction = new Transaction().add(chunk[0], chunk[1]);
|
|
||||||
}
|
|
||||||
unsignedTransactions.push(transaction);
|
|
||||||
});
|
|
||||||
|
|
||||||
//the postVaa instruction can only execute after the verifySignature transactions have
|
|
||||||
//successfully completed.
|
|
||||||
const finalTransaction = new Transaction().add(finalInstruction);
|
|
||||||
|
|
||||||
//The signature_set keypair also needs to sign the verifySignature transactions, thus a wrapper is needed.
|
|
||||||
const partialSignWrapper = (transaction: Transaction) => {
|
|
||||||
transaction.partialSign(signature_set);
|
|
||||||
return signTransaction(transaction);
|
|
||||||
};
|
|
||||||
|
|
||||||
await sendAndConfirmTransactionsWithRetry(
|
|
||||||
connection,
|
|
||||||
partialSignWrapper,
|
|
||||||
payer,
|
|
||||||
unsignedTransactions,
|
|
||||||
maxRetries
|
|
||||||
);
|
|
||||||
//While the signature_set is used to create the final instruction, it doesn't need to sign it.
|
|
||||||
await sendAndConfirmTransactionsWithRetry(
|
|
||||||
connection,
|
|
||||||
signTransaction,
|
|
||||||
payer,
|
|
||||||
[finalTransaction],
|
|
||||||
maxRetries
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This returns an array of instructions required to verify the signatures of a VAA, and upload it to the blockchain.
|
|
||||||
signature_set should be a new keypair, and also needs to partial sign the transaction when these instructions are submitted.
|
|
||||||
*/
|
|
||||||
export async function createVerifySignaturesInstructions(
|
|
||||||
connection: Connection,
|
|
||||||
bridge_id: string,
|
|
||||||
payer: string,
|
|
||||||
vaa: Buffer,
|
|
||||||
signature_set: Keypair
|
|
||||||
): Promise<TransactionInstruction[]> {
|
|
||||||
const output: TransactionInstruction[] = [];
|
|
||||||
const {
|
|
||||||
guardian_set_address,
|
|
||||||
parse_guardian_set,
|
|
||||||
parse_vaa,
|
|
||||||
verify_signatures_ix,
|
|
||||||
} = await importCoreWasm();
|
|
||||||
const { guardian_set_index } = parse_vaa(new Uint8Array(vaa));
|
|
||||||
let guardian_addr = new PublicKey(
|
|
||||||
guardian_set_address(bridge_id, guardian_set_index)
|
|
||||||
);
|
|
||||||
let acc = await connection.getAccountInfo(guardian_addr);
|
|
||||||
if (acc?.data === undefined) {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
let guardian_data = parse_guardian_set(new Uint8Array(acc?.data));
|
|
||||||
|
|
||||||
let txs = verify_signatures_ix(
|
|
||||||
bridge_id,
|
|
||||||
payer,
|
|
||||||
guardian_set_index,
|
|
||||||
guardian_data,
|
|
||||||
signature_set.publicKey.toString(),
|
|
||||||
vaa
|
|
||||||
);
|
|
||||||
// Add transfer instruction to transaction
|
|
||||||
for (let tx of txs) {
|
|
||||||
let ixs: Array<TransactionInstruction> = tx.map((v: any) => {
|
|
||||||
return ixFromRust(v);
|
|
||||||
});
|
|
||||||
output.push(ixs[0], ixs[1]);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This will return the postVaaInstruction. This should only be executed after the verifySignaturesInstructions have been executed.
|
|
||||||
signatureSetKeypair should be the same keypair used for verifySignaturesInstructions, but does not need to partialSign the transaction
|
|
||||||
when this instruction is submitted.
|
|
||||||
*/
|
|
||||||
export async function createPostVaaInstruction(
|
|
||||||
bridge_id: string,
|
|
||||||
payer: string,
|
|
||||||
vaa: Buffer,
|
|
||||||
signatureSetKeypair: Keypair
|
|
||||||
): Promise<TransactionInstruction> {
|
|
||||||
const { post_vaa_ix } = await importCoreWasm();
|
|
||||||
return ixFromRust(
|
|
||||||
post_vaa_ix(bridge_id, payer, signatureSetKeypair.publicKey.toString(), vaa)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@deprecated
|
|
||||||
Instead, either use postVaaWithRetry or create, sign, and send the verifySignaturesInstructions & postVaaInstruction yourself.
|
|
||||||
|
|
||||||
This function is equivalent to a postVaaWithRetry with a maxRetries of 0.
|
|
||||||
*/
|
|
||||||
export async function postVaa(
|
|
||||||
connection: Connection,
|
|
||||||
signTransaction: (transaction: Transaction) => Promise<Transaction>,
|
|
||||||
bridge_id: string,
|
|
||||||
payer: string,
|
|
||||||
vaa: Buffer
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
guardian_set_address,
|
|
||||||
parse_guardian_set,
|
|
||||||
parse_vaa,
|
|
||||||
post_vaa_ix,
|
|
||||||
verify_signatures_ix,
|
|
||||||
} = await importCoreWasm();
|
|
||||||
const { guardian_set_index } = parse_vaa(new Uint8Array(vaa));
|
|
||||||
let guardian_addr = new PublicKey(
|
|
||||||
guardian_set_address(bridge_id, guardian_set_index)
|
|
||||||
);
|
|
||||||
let acc = await connection.getAccountInfo(guardian_addr);
|
|
||||||
if (acc?.data === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let guardian_data = parse_guardian_set(new Uint8Array(acc?.data));
|
|
||||||
|
|
||||||
let signature_set = Keypair.generate();
|
|
||||||
let txs = verify_signatures_ix(
|
|
||||||
bridge_id,
|
|
||||||
payer,
|
|
||||||
guardian_set_index,
|
|
||||||
guardian_data,
|
|
||||||
signature_set.publicKey.toString(),
|
|
||||||
vaa
|
|
||||||
);
|
|
||||||
// Add transfer instruction to transaction
|
|
||||||
for (let tx of txs) {
|
|
||||||
let ixs: Array<TransactionInstruction> = tx.map((v: any) => {
|
|
||||||
return ixFromRust(v);
|
|
||||||
});
|
|
||||||
let transaction = new Transaction().add(...ixs);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payer);
|
|
||||||
transaction.partialSign(signature_set);
|
|
||||||
|
|
||||||
// Sign transaction, broadcast, and confirm
|
|
||||||
const signed = await signTransaction(transaction);
|
|
||||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
|
||||||
await connection.confirmTransaction(txid);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ix = ixFromRust(
|
|
||||||
post_vaa_ix(bridge_id, payer, signature_set.publicKey.toString(), vaa)
|
|
||||||
);
|
|
||||||
let transaction = new Transaction().add(ix);
|
|
||||||
const { blockhash } = await connection.getRecentBlockhash();
|
|
||||||
transaction.recentBlockhash = blockhash;
|
|
||||||
transaction.feePayer = new PublicKey(payer);
|
|
||||||
|
|
||||||
const signed = await signTransaction(transaction);
|
|
||||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
|
||||||
await connection.confirmTransaction(txid);
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import {
|
|
||||||
AccountMeta,
|
|
||||||
PublicKey,
|
|
||||||
TransactionInstruction,
|
|
||||||
} from "@solana/web3.js";
|
|
||||||
// begin from clients\solana\main.ts
|
|
||||||
export function ixFromRust(data: any): TransactionInstruction {
|
|
||||||
const keys: AccountMeta[] = data.accounts.map(accountMetaFromRust);
|
|
||||||
return new TransactionInstruction({
|
|
||||||
programId: new PublicKey(data.program_id),
|
|
||||||
data: Buffer.from(data.data),
|
|
||||||
keys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function accountMetaFromRust(meta: any): AccountMeta {
|
|
||||||
return {
|
|
||||||
pubkey: new PublicKey(meta.pubkey),
|
|
||||||
isSigner: meta.is_signer,
|
|
||||||
isWritable: meta.is_writable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// end from clients\solana\main.ts
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
import {
|
||||||
|
Commitment,
|
||||||
|
ConfirmOptions,
|
||||||
|
Connection,
|
||||||
|
Keypair,
|
||||||
|
PublicKeyInitData,
|
||||||
|
Transaction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
signSendAndConfirmTransaction,
|
||||||
|
SignTransaction,
|
||||||
|
sendAndConfirmTransactionsWithRetry,
|
||||||
|
modifySignTransaction,
|
||||||
|
TransactionSignatureAndResponse,
|
||||||
|
PreparedTransactions,
|
||||||
|
} from "./utils";
|
||||||
|
import {
|
||||||
|
createPostVaaInstruction,
|
||||||
|
createVerifySignaturesInstructions,
|
||||||
|
} from "./wormhole";
|
||||||
|
import { isBytes, ParsedVaa, parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||||
|
|
||||||
|
export async function postVaaWithRetry(
|
||||||
|
connection: Connection,
|
||||||
|
signTransaction: SignTransaction,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: Buffer,
|
||||||
|
maxRetries?: number,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<TransactionSignatureAndResponse[]> {
|
||||||
|
const { unsignedTransactions, signers } =
|
||||||
|
await createPostSignedVaaTransactions(
|
||||||
|
connection,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
vaa,
|
||||||
|
commitment
|
||||||
|
);
|
||||||
|
|
||||||
|
const postVaaTransaction = unsignedTransactions.pop()!;
|
||||||
|
|
||||||
|
const responses = await sendAndConfirmTransactionsWithRetry(
|
||||||
|
connection,
|
||||||
|
modifySignTransaction(signTransaction, ...signers),
|
||||||
|
payer.toString(),
|
||||||
|
unsignedTransactions,
|
||||||
|
maxRetries
|
||||||
|
);
|
||||||
|
//While the signature_set is used to create the final instruction, it doesn't need to sign it.
|
||||||
|
responses.push(
|
||||||
|
...(await sendAndConfirmTransactionsWithRetry(
|
||||||
|
connection,
|
||||||
|
signTransaction,
|
||||||
|
payer.toString(),
|
||||||
|
[postVaaTransaction],
|
||||||
|
maxRetries
|
||||||
|
))
|
||||||
|
);
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postVaa(
|
||||||
|
connection: Connection,
|
||||||
|
signTransaction: SignTransaction,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: Buffer,
|
||||||
|
options?: ConfirmOptions,
|
||||||
|
asyncVerifySignatures: boolean = true
|
||||||
|
): Promise<TransactionSignatureAndResponse[]> {
|
||||||
|
const { unsignedTransactions, signers } =
|
||||||
|
await createPostSignedVaaTransactions(
|
||||||
|
connection,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
vaa,
|
||||||
|
options?.commitment
|
||||||
|
);
|
||||||
|
|
||||||
|
const postVaaTransaction = unsignedTransactions.pop()!;
|
||||||
|
|
||||||
|
const verifySignatures = async (transaction: Transaction) =>
|
||||||
|
signSendAndConfirmTransaction(
|
||||||
|
connection,
|
||||||
|
payer,
|
||||||
|
modifySignTransaction(signTransaction, ...signers),
|
||||||
|
transaction,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const output: TransactionSignatureAndResponse[] = [];
|
||||||
|
if (asyncVerifySignatures) {
|
||||||
|
const verified = await Promise.all(
|
||||||
|
unsignedTransactions.map(async (transaction) =>
|
||||||
|
verifySignatures(transaction)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
output.push(...verified);
|
||||||
|
} else {
|
||||||
|
for (const transaction of unsignedTransactions) {
|
||||||
|
output.push(await verifySignatures(transaction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.push(
|
||||||
|
await signSendAndConfirmTransaction(
|
||||||
|
connection,
|
||||||
|
payer,
|
||||||
|
signTransaction,
|
||||||
|
postVaaTransaction,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send transactions for `verify_signatures` and `post_vaa` instructions.
|
||||||
|
*
|
||||||
|
* Using a signed VAA, execute transactions generated by {@link verifySignatures} and
|
||||||
|
* {@link postVaa}. At most 4 transactions are sent (up to 3 from signature verification
|
||||||
|
* and 1 to post VAA data to an account).
|
||||||
|
*
|
||||||
|
* @param {Connection} connection - Solana web3 connection
|
||||||
|
* @param {PublicKeyInitData} wormholeProgramId - wormhole program address
|
||||||
|
* @param {web3.Keypair} payer - transaction signer address
|
||||||
|
* @param {Buffer} signedVaa - bytes of signed VAA
|
||||||
|
* @param {Commitment} [options] - Solana commitment
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export async function createPostSignedVaaTransactions(
|
||||||
|
connection: Connection,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedVaa,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<PreparedTransactions> {
|
||||||
|
const parsed = isBytes(vaa) ? parseVaa(vaa) : vaa;
|
||||||
|
const signatureSet = Keypair.generate();
|
||||||
|
|
||||||
|
const verifySignaturesInstructions = await createVerifySignaturesInstructions(
|
||||||
|
connection,
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
parsed,
|
||||||
|
signatureSet.publicKey,
|
||||||
|
commitment
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsignedTransactions: Transaction[] = [];
|
||||||
|
for (let i = 0; i < verifySignaturesInstructions.length; i += 2) {
|
||||||
|
unsignedTransactions.push(
|
||||||
|
new Transaction().add(...verifySignaturesInstructions.slice(i, i + 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsignedTransactions.push(
|
||||||
|
new Transaction().add(
|
||||||
|
createPostVaaInstruction(
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
parsed,
|
||||||
|
signatureSet.publicKey
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
unsignedTransactions,
|
||||||
|
signers: [signatureSet],
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
Commitment,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { deriveAddress, getAccountData } from "../../utils";
|
||||||
|
|
||||||
|
export function deriveTokenBridgeConfigKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("config")], tokenBridgeProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTokenBridgeConfig(
|
||||||
|
connection: Connection,
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<TokenBridgeConfig> {
|
||||||
|
return connection
|
||||||
|
.getAccountInfo(
|
||||||
|
deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
commitment
|
||||||
|
)
|
||||||
|
.then((info) => TokenBridgeConfig.deserialize(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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { deriveAddress } from "../../utils";
|
||||||
|
|
||||||
|
export function deriveCustodyKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([new PublicKey(mint).toBuffer()], tokenBridgeProgramId);
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
Commitment,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
tryNativeToUint8Array,
|
||||||
|
} from "../../../utils";
|
||||||
|
import { deriveAddress, getAccountData } from "../../utils";
|
||||||
|
|
||||||
|
export function deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
emitterChain: number | ChainId,
|
||||||
|
emitterAddress: Buffer | Uint8Array | string
|
||||||
|
): PublicKey {
|
||||||
|
if (emitterChain == CHAIN_ID_SOLANA) {
|
||||||
|
throw new Error(
|
||||||
|
"emitterChain == CHAIN_ID_SOLANA cannot exist as foreign token bridge emitter"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof emitterAddress == "string") {
|
||||||
|
emitterAddress = tryNativeToUint8Array(
|
||||||
|
emitterAddress,
|
||||||
|
emitterChain as ChainId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return deriveAddress(
|
||||||
|
[
|
||||||
|
(() => {
|
||||||
|
const buf = Buffer.alloc(2);
|
||||||
|
buf.writeUInt16BE(emitterChain as number);
|
||||||
|
return buf;
|
||||||
|
})(),
|
||||||
|
emitterAddress,
|
||||||
|
],
|
||||||
|
tokenBridgeProgramId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEndpointRegistration(
|
||||||
|
connection: Connection,
|
||||||
|
endpointKey: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<EndpointRegistration> {
|
||||||
|
return connection
|
||||||
|
.getAccountInfo(new PublicKey(endpointKey), commitment)
|
||||||
|
.then((info) => EndpointRegistration.deserialize(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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from "./config";
|
||||||
|
export * from "./custody";
|
||||||
|
export * from "./endpoint";
|
||||||
|
export * from "./transferWithPayload";
|
||||||
|
export * from "./signer";
|
||||||
|
export * from "./wrapped";
|
||||||
|
export { deriveUpgradeAuthorityKey } from "../../wormhole";
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { deriveAddress } from "../../utils";
|
||||||
|
|
||||||
|
export function deriveAuthoritySignerKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("authority_signer")], tokenBridgeProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveCustodySignerKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("custody_signer")], tokenBridgeProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveMintAuthorityKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("mint_signer")], tokenBridgeProgramId);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { deriveAddress } from "../../utils";
|
||||||
|
|
||||||
|
export function deriveSenderAccountKey(
|
||||||
|
cpiProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("sender")], cpiProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveRedeemerAccountKey(
|
||||||
|
cpiProgramId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress([Buffer.from("redeemer")], cpiProgramId);
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
Commitment,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
tryNativeToUint8Array,
|
||||||
|
} from "../../../utils";
|
||||||
|
import { deriveAddress, getAccountData } from "../../utils";
|
||||||
|
|
||||||
|
export { deriveSplTokenMetadataKey } from "../../utils/splMetadata";
|
||||||
|
|
||||||
|
export function deriveWrappedMintKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
tokenChain: number | ChainId,
|
||||||
|
tokenAddress: Buffer | Uint8Array | string
|
||||||
|
): PublicKey {
|
||||||
|
if (tokenChain == CHAIN_ID_SOLANA) {
|
||||||
|
throw new Error(
|
||||||
|
"tokenChain == CHAIN_ID_SOLANA does not have wrapped mint key"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof tokenAddress == "string") {
|
||||||
|
tokenAddress = tryNativeToUint8Array(tokenAddress, tokenChain as ChainId);
|
||||||
|
}
|
||||||
|
return deriveAddress(
|
||||||
|
[
|
||||||
|
Buffer.from("wrapped"),
|
||||||
|
(() => {
|
||||||
|
const buf = Buffer.alloc(2);
|
||||||
|
buf.writeUInt16BE(tokenChain as number);
|
||||||
|
return buf;
|
||||||
|
})(),
|
||||||
|
tokenAddress,
|
||||||
|
],
|
||||||
|
tokenBridgeProgramId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveWrappedMetaKey(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return deriveAddress(
|
||||||
|
[Buffer.from("meta"), new PublicKey(mint).toBuffer()],
|
||||||
|
tokenBridgeProgramId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWrappedMeta(
|
||||||
|
connection: Connection,
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData,
|
||||||
|
commitment?: Commitment
|
||||||
|
): Promise<WrappedMeta> {
|
||||||
|
return connection
|
||||||
|
.getAccountInfo(
|
||||||
|
deriveWrappedMetaKey(tokenBridgeProgramId, mint),
|
||||||
|
commitment
|
||||||
|
)
|
||||||
|
.then((info) => WrappedMeta.deserialize(getAccountData(info)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WrappedMeta {
|
||||||
|
chain: number;
|
||||||
|
tokenAddress: Buffer;
|
||||||
|
originalDecimals: number;
|
||||||
|
|
||||||
|
constructor(chain: number, tokenAddress: Buffer, originalDecimals: number) {
|
||||||
|
this.chain = chain;
|
||||||
|
this.tokenAddress = tokenAddress;
|
||||||
|
this.originalDecimals = originalDecimals;
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserialize(data: Buffer): WrappedMeta {
|
||||||
|
if (data.length != 35) {
|
||||||
|
throw new Error("data.length != 35");
|
||||||
|
}
|
||||||
|
const chain = data.readUInt16LE(0);
|
||||||
|
const tokenAddress = data.subarray(2, 34);
|
||||||
|
const originalDecimals = data.readUInt8(34);
|
||||||
|
return new WrappedMeta(chain, tokenAddress, originalDecimals);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { AccountsCoder, Idl } from "@project-serum/anchor";
|
||||||
|
import { accountSize, IdlTypeDef } from "../../anchor";
|
||||||
|
|
||||||
|
export class TokenBridgeAccountsCoder<A extends string = string>
|
||||||
|
implements AccountsCoder
|
||||||
|
{
|
||||||
|
constructor(private idl: Idl) {}
|
||||||
|
|
||||||
|
public async encode<T = any>(accountName: A, account: T): Promise<Buffer> {
|
||||||
|
switch (accountName) {
|
||||||
|
default: {
|
||||||
|
throw new Error(`Invalid account name: ${accountName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode<T = any>(accountName: A, ix: Buffer): T {
|
||||||
|
return this.decodeUnchecked(accountName, ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decodeUnchecked<T = any>(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: IdlTypeDef): number {
|
||||||
|
return accountSize(this.idl, idlAccount) ?? 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { EventCoder, Event, Idl } from "@project-serum/anchor";
|
||||||
|
import { IdlEvent } from "../../anchor";
|
||||||
|
|
||||||
|
export class TokenBridgeEventsCoder implements EventCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
|
||||||
|
_log: string
|
||||||
|
): Event<E, T> | null {
|
||||||
|
throw new Error("Token Bridge program does not have events");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Coder, Idl } from "@project-serum/anchor";
|
||||||
|
import { TokenBridgeAccountsCoder } from "./accounts";
|
||||||
|
import { TokenBridgeEventsCoder } from "./events";
|
||||||
|
import { TokenBridgeInstructionCoder } from "./instruction";
|
||||||
|
import { TokenBridgeStateCoder } from "./state";
|
||||||
|
import { TokenBridgeTypesCoder } from "./types";
|
||||||
|
|
||||||
|
export { TokenBridgeInstruction } from "./instruction";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
import { Idl, InstructionCoder } from "@project-serum/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/dev.v2/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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Idl, StateCoder } from "@project-serum/anchor";
|
||||||
|
|
||||||
|
export class TokenBridgeStateCoder implements StateCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
encode<T = any>(_name: string, _account: T): Promise<Buffer> {
|
||||||
|
throw new Error("Token Bridge program does not have state");
|
||||||
|
}
|
||||||
|
decode<T = any>(_ix: Buffer): T {
|
||||||
|
throw new Error("Token Bridge program does not have state");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Idl, TypesCoder } from "@project-serum/anchor";
|
||||||
|
|
||||||
|
export class TokenBridgeTypesCoder implements TypesCoder {
|
||||||
|
constructor(_idl: Idl) {}
|
||||||
|
|
||||||
|
encode<T = any>(_name: string, _type: T): Buffer {
|
||||||
|
throw new Error("Token Bridge program does not have user-defined types");
|
||||||
|
}
|
||||||
|
decode<T = any>(_name: string, _typeData: Buffer): T {
|
||||||
|
throw new Error("Token Bridge program does not have user-defined types");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,477 @@
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedTokenTransferVaa,
|
||||||
|
parseTokenTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../vaa";
|
||||||
|
import {
|
||||||
|
deriveClaimKey,
|
||||||
|
derivePostedVaaKey,
|
||||||
|
getWormholeDerivedAccounts,
|
||||||
|
} from "../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
deriveRedeemerAccountKey,
|
||||||
|
deriveSenderAccountKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
} from "./accounts";
|
||||||
|
import {
|
||||||
|
getTransferNativeWithPayloadAccounts,
|
||||||
|
getTransferWrappedWithPayloadAccounts,
|
||||||
|
} from "./instructions";
|
||||||
|
|
||||||
|
export interface TokenBridgeBaseDerivedAccounts {
|
||||||
|
/**
|
||||||
|
* seeds = ["config"], seeds::program = tokenBridgeProgram
|
||||||
|
*/
|
||||||
|
tokenBridgeConfig: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
} = 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cpiProgramId
|
||||||
|
* @param tokenBridgeProgramId
|
||||||
|
* @param wormholeProgramId
|
||||||
|
* @param payer
|
||||||
|
* @param vaa
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getCompleteTransferNativeWithPayloadCpiAccounts(
|
||||||
|
cpiProgramId: PublicKeyInitData,
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenTransferVaa
|
||||||
|
): CompleteTransferNativeWithPayloadCpiAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenTransferVaa(vaa) : vaa;
|
||||||
|
const mint = new PublicKey(parsed.tokenAddress);
|
||||||
|
const toTokenAccount = new PublicKey(parsed.to);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
tokenBridgeConfig: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
tokenBridgeClaim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
tokenBridgeForeignEndpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
toTokenAccount,
|
||||||
|
tokenBridgeRedeemer: deriveRedeemerAccountKey(cpiProgramId),
|
||||||
|
toFeesTokenAccount: toTokenAccount,
|
||||||
|
tokenBridgeCustody: deriveCustodyKey(tokenBridgeProgramId, mint),
|
||||||
|
mint,
|
||||||
|
tokenBridgeCustodySigner: deriveCustodySignerKey(tokenBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
cpiProgramId: PublicKeyInitData,
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenTransferVaa
|
||||||
|
): CompleteTransferWrappedWithPayloadCpiAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenTransferVaa(vaa) : vaa;
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.tokenChain,
|
||||||
|
parsed.tokenAddress
|
||||||
|
);
|
||||||
|
const toTokenAccount = new PublicKey(parsed.to);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
tokenBridgeConfig: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
tokenBridgeClaim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
tokenBridgeForeignEndpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
toTokenAccount,
|
||||||
|
tokenBridgeRedeemer: deriveRedeemerAccountKey(cpiProgramId),
|
||||||
|
toFeesTokenAccount: 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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./accounts";
|
||||||
|
export * from "./cpi";
|
||||||
|
export * from "./instructions";
|
||||||
|
export * from "./program";
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createApproveInstruction } from "@solana/spl-token";
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { deriveAuthoritySignerKey } from "../accounts";
|
||||||
|
|
||||||
|
export function createApproveAuthoritySignerInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
tokenAccount: PublicKeyInitData,
|
||||||
|
owner: PublicKeyInitData,
|
||||||
|
amount: number | bigint
|
||||||
|
) {
|
||||||
|
return createApproveInstruction(
|
||||||
|
new PublicKey(tokenAccount),
|
||||||
|
deriveAuthoritySignerKey(tokenBridgeProgramId),
|
||||||
|
new PublicKey(owner),
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveSplTokenMetadataKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
} from "../accounts";
|
||||||
|
|
||||||
|
export function createAttestTokenInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
mint: PublicKeyInitData,
|
||||||
|
message: PublicKeyInitData,
|
||||||
|
nonce: number
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyTokenBridgeProgramInterface(
|
||||||
|
tokenBridgeProgramId
|
||||||
|
).methods.attestToken(nonce);
|
||||||
|
|
||||||
|
// @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,
|
||||||
|
} = getPostMessageAccounts(
|
||||||
|
wormholeProgramId,
|
||||||
|
payer,
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedTokenTransferVaa,
|
||||||
|
parseTokenTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
|
||||||
|
export function createCompleteTransferNativeInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenTransferVaa,
|
||||||
|
feeRecipient?: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyTokenBridgeProgramInterface(
|
||||||
|
tokenBridgeProgramId
|
||||||
|
).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: SignedVaa | ParsedTokenTransferVaa,
|
||||||
|
feeRecipient?: PublicKeyInitData
|
||||||
|
): CompleteTransferNativeAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenTransferVaa(vaa) : vaa;
|
||||||
|
const mint = new PublicKey(parsed.tokenAddress);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
to: new PublicKey(parsed.to),
|
||||||
|
toFees: new PublicKey(
|
||||||
|
feeRecipient === undefined ? parsed.to : feeRecipient
|
||||||
|
),
|
||||||
|
custody: deriveCustodyKey(tokenBridgeProgramId, mint),
|
||||||
|
mint,
|
||||||
|
custodySigner: deriveCustodySignerKey(tokenBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedTokenTransferVaa,
|
||||||
|
parseTokenTransferVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
|
||||||
|
export function createCompleteTransferWrappedInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenTransferVaa,
|
||||||
|
feeRecipient?: PublicKeyInitData
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyTokenBridgeProgramInterface(
|
||||||
|
tokenBridgeProgramId
|
||||||
|
).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: SignedVaa | ParsedTokenTransferVaa,
|
||||||
|
feeRecipient?: PublicKeyInitData
|
||||||
|
): CompleteTransferWrappedAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenTransferVaa(vaa) : vaa;
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.tokenChain,
|
||||||
|
parsed.tokenAddress
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
to: new PublicKey(parsed.to),
|
||||||
|
toFees: new PublicKey(
|
||||||
|
feeRecipient === undefined ? parsed.to : feeRecipient
|
||||||
|
),
|
||||||
|
mint,
|
||||||
|
wrappedMeta: deriveWrappedMetaKey(tokenBridgeProgramId, mint),
|
||||||
|
mintAuthority: deriveMintAuthorityKey(tokenBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
tokenProgram: TOKEN_PROGRAM_ID,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveMintAuthorityKey,
|
||||||
|
deriveSplTokenMetadataKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
parseAttestMetaVaa,
|
||||||
|
ParsedAttestMetaVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
import { SplTokenMetadataProgram } from "../../utils";
|
||||||
|
|
||||||
|
export function createCreateWrappedInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedAttestMetaVaa
|
||||||
|
): TransactionInstruction {
|
||||||
|
const methods =
|
||||||
|
createReadOnlyTokenBridgeProgramInterface(
|
||||||
|
tokenBridgeProgramId
|
||||||
|
).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: SignedVaa | ParsedAttestMetaVaa
|
||||||
|
): CreateWrappedAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseAttestMetaVaa(vaa) : vaa;
|
||||||
|
const mint = deriveWrappedMintKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.tokenChain,
|
||||||
|
parsed.tokenAddress
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.emitterAddress
|
||||||
|
),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_CLOCK_PUBKEY,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveClaimKey, derivePostedVaaKey } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveEndpointKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveUpgradeAuthorityKey,
|
||||||
|
} from "../accounts";
|
||||||
|
import {
|
||||||
|
isBytes,
|
||||||
|
ParsedTokenBridgeRegisterChainVaa,
|
||||||
|
ParsedTokenBridgeUpgradeContractVaa,
|
||||||
|
parseTokenBridgeRegisterChainVaa,
|
||||||
|
parseTokenBridgeUpgradeContractVaa,
|
||||||
|
SignedVaa,
|
||||||
|
} from "../../../vaa";
|
||||||
|
import { BpfLoaderUpgradeable, deriveUpgradeableProgramKey } from "../../utils";
|
||||||
|
|
||||||
|
export function createRegisterChainInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenBridgeRegisterChainVaa
|
||||||
|
): 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: SignedVaa | ParsedTokenBridgeRegisterChainVaa
|
||||||
|
): RegisterChainAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenBridgeRegisterChainVaa(vaa) : vaa;
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
config: deriveTokenBridgeConfigKey(tokenBridgeProgramId),
|
||||||
|
endpoint: deriveEndpointKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.foreignChain,
|
||||||
|
parsed.foreignAddress
|
||||||
|
),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
wormholeProgram: new PublicKey(wormholeProgramId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUpgradeContractInstruction(
|
||||||
|
tokenBridgeProgramId: PublicKeyInitData,
|
||||||
|
wormholeProgramId: PublicKeyInitData,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
vaa: SignedVaa | ParsedTokenBridgeUpgradeContractVaa,
|
||||||
|
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: SignedVaa | ParsedTokenBridgeUpgradeContractVaa,
|
||||||
|
spill?: PublicKeyInitData
|
||||||
|
): UpgradeContractAccounts {
|
||||||
|
const parsed = isBytes(vaa) ? parseTokenBridgeUpgradeContractVaa(vaa) : vaa;
|
||||||
|
return {
|
||||||
|
payer: new PublicKey(payer),
|
||||||
|
vaa: derivePostedVaaKey(wormholeProgramId, parsed.hash),
|
||||||
|
claim: deriveClaimKey(
|
||||||
|
tokenBridgeProgramId,
|
||||||
|
parsed.emitterAddress,
|
||||||
|
parsed.emitterChain,
|
||||||
|
parsed.sequence
|
||||||
|
),
|
||||||
|
upgradeAuthority: deriveUpgradeAuthorityKey(tokenBridgeProgramId),
|
||||||
|
spill: new PublicKey(spill === undefined ? payer : spill),
|
||||||
|
implementation: new PublicKey(parsed.newContract),
|
||||||
|
programData: deriveUpgradeableProgramKey(tokenBridgeProgramId),
|
||||||
|
tokenBridgeProgram: new PublicKey(tokenBridgeProgramId),
|
||||||
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
clock: SYSVAR_CLOCK_PUBKEY,
|
||||||
|
bpfLoaderUpgradeable: BpfLoaderUpgradeable.programId,
|
||||||
|
systemProgram: SystemProgram.programId,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export * from "./approve";
|
||||||
|
export * from "./attestToken";
|
||||||
|
export * from "./completeNative";
|
||||||
|
export * from "./completeWrapped";
|
||||||
|
export * from "./createWrapped";
|
||||||
|
export * from "./initialize";
|
||||||
|
export * from "./governance";
|
||||||
|
export * from "./transferNative";
|
||||||
|
export * from "./transferNativeWithPayload";
|
||||||
|
export * from "./transferWrapped";
|
||||||
|
export * from "./transferWrappedWithPayload";
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { deriveTokenBridgeConfigKey } from "../accounts";
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageCpiAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
} from "../accounts";
|
||||||
|
|
||||||
|
export function createTransferNativeInstruction(
|
||||||
|
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
|
||||||
|
).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,
|
||||||
|
} = 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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageCpiAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveCustodySignerKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveCustodyKey,
|
||||||
|
deriveSenderAccountKey,
|
||||||
|
} from "../accounts";
|
||||||
|
|
||||||
|
export function createTransferNativeWithPayloadInstruction(
|
||||||
|
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
|
||||||
|
).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,
|
||||||
|
} = 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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageCpiAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
} from "../accounts";
|
||||||
|
|
||||||
|
export function createTransferWrappedInstruction(
|
||||||
|
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
|
||||||
|
).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,
|
||||||
|
} = 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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { createReadOnlyTokenBridgeProgramInterface } from "../program";
|
||||||
|
import { getPostMessageCpiAccounts } from "../../wormhole";
|
||||||
|
import {
|
||||||
|
deriveAuthoritySignerKey,
|
||||||
|
deriveSenderAccountKey,
|
||||||
|
deriveTokenBridgeConfigKey,
|
||||||
|
deriveWrappedMetaKey,
|
||||||
|
deriveWrappedMintKey,
|
||||||
|
} from "../accounts";
|
||||||
|
|
||||||
|
export function createTransferWrappedWithPayloadInstruction(
|
||||||
|
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
|
||||||
|
).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,
|
||||||
|
} = 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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Connection, PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { Program, Provider } from "@project-serum/anchor";
|
||||||
|
import { createReadOnlyProvider } from "../utils";
|
||||||
|
import { TokenBridgeCoder } from "./coder";
|
||||||
|
import { TokenBridge } from "../types/tokenBridge";
|
||||||
|
|
||||||
|
import IDL from "../../anchor-idl/token_bridge.json";
|
||||||
|
|
||||||
|
export function createTokenBridgeProgramInterface(
|
||||||
|
programId: PublicKeyInitData,
|
||||||
|
provider?: Provider
|
||||||
|
): Program<TokenBridge> {
|
||||||
|
return new Program<TokenBridge>(
|
||||||
|
IDL as TokenBridge,
|
||||||
|
new PublicKey(programId),
|
||||||
|
provider === undefined ? ({ connection: null } as any) : provider,
|
||||||
|
coder()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReadOnlyTokenBridgeProgramInterface(
|
||||||
|
programId: PublicKeyInitData,
|
||||||
|
connection?: Connection
|
||||||
|
): Program<TokenBridge> {
|
||||||
|
return createTokenBridgeProgramInterface(
|
||||||
|
programId,
|
||||||
|
createReadOnlyProvider(connection)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function coder(): TokenBridgeCoder {
|
||||||
|
return new TokenBridgeCoder(IDL as TokenBridge);
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
AccountMeta,
|
||||||
|
AccountInfo,
|
||||||
|
PublicKeyInitData,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find valid program address. See {@link PublicKey.findProgramAddressSync} for details.
|
||||||
|
*
|
||||||
|
* @param {(Buffer | Uint8Array)[]} seeds - seeds for PDA
|
||||||
|
* @param {PublicKeyInitData} programId - program address
|
||||||
|
* @returns PDA
|
||||||
|
*/
|
||||||
|
export function deriveAddress(
|
||||||
|
seeds: (Buffer | Uint8Array)[],
|
||||||
|
programId: PublicKeyInitData
|
||||||
|
): PublicKey {
|
||||||
|
return PublicKey.findProgramAddressSync(seeds, 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<Buffer>} info - Solana AccountInfo
|
||||||
|
* @returns serialized data as Buffer
|
||||||
|
*/
|
||||||
|
export function getAccountData(info: AccountInfo<Buffer> | null): Buffer {
|
||||||
|
if (info === null) {
|
||||||
|
throw Error("account info is null");
|
||||||
|
}
|
||||||
|
return info.data;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { PublicKey, PublicKeyInitData } from "@solana/web3.js";
|
||||||
|
import { deriveAddress } from "./account";
|
||||||
|
|
||||||
|
export class BpfLoaderUpgradeable {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public key that identifies the SPL Token Metadata program
|
||||||
|
*/
|
||||||
|
static programId: PublicKey = new PublicKey(
|
||||||
|
"BPFLoaderUpgradeab1e11111111111111111111111"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveUpgradeableProgramKey(programId: PublicKeyInitData) {
|
||||||
|
return deriveAddress(
|
||||||
|
[new PublicKey(programId).toBuffer()],
|
||||||
|
BpfLoaderUpgradeable.programId
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Provider } from "@project-serum/anchor";
|
||||||
|
import { Connection } from "@solana/web3.js";
|
||||||
|
|
||||||
|
export function createReadOnlyProvider(
|
||||||
|
connection?: Connection
|
||||||
|
): Provider | undefined {
|
||||||
|
if (connection === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { connection };
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from "./account";
|
||||||
|
export * from "./bpfLoaderUpgradeable";
|
||||||
|
export * from "./connection";
|
||||||
|
export * from "./secp256k1";
|
||||||
|
export * from "./splMetadata";
|
||||||
|
export * from "./transaction";
|
|
@ -0,0 +1,124 @@
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
import {
|
||||||
|
AccountMeta,
|
||||||
|
Commitment,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
PublicKeyInitData,
|
||||||
|
SystemProgram,
|
||||||
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
deriveAddress,
|
||||||
|
getAccountData,
|
||||||
|
newAccountMeta,
|
||||||
|
newReadOnlyAccountMeta,
|
||||||
|
} from "./account";
|
||||||
|
|
||||||
|
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
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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[] = [
|
||||||
|
newAccountMeta(metadataAccount, false),
|
||||||
|
newReadOnlyAccountMeta(mint, false),
|
||||||
|
newReadOnlyAccountMeta(mintAuthority, true),
|
||||||
|
newReadOnlyAccountMeta(payer, true),
|
||||||
|
newReadOnlyAccountMeta(updateAuthority, updateAuthorityIsSigner),
|
||||||
|
newReadOnlyAccountMeta(SystemProgram.programId, false),
|
||||||
|
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 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<Metadata> {
|
||||||
|
return connection
|
||||||
|
.getAccountInfo(deriveSplTokenMetadataKey(mint), commitment)
|
||||||
|
.then((info) => Metadata.deserialize(getAccountData(info)));
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
import {
|
||||||
|
Transaction,
|
||||||
|
Keypair,
|
||||||
|
Connection,
|
||||||
|
PublicKeyInitData,
|
||||||
|
PublicKey,
|
||||||
|
ConfirmOptions,
|
||||||
|
RpcResponseAndContext,
|
||||||
|
SignatureResult,
|
||||||
|
TransactionSignature,
|
||||||
|
Signer,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object that holds list of unsigned {@link Transaction}s and {@link Keypair}s
|
||||||
|
* required to sign for each transaction.
|
||||||
|
*/
|
||||||
|
export interface PreparedTransactions {
|
||||||
|
unsignedTransactions: Transaction[];
|
||||||
|
signers: Signer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionSignatureAndResponse {
|
||||||
|
signature: TransactionSignature;
|
||||||
|
response: RpcResponseAndContext<SignatureResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resembles WalletContextState and Anchor's NodeWallet's signTransaction function signature
|
||||||
|
*/
|
||||||
|
export type SignTransaction = (
|
||||||
|
transaction: Transaction
|
||||||
|
) => Promise<Transaction>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param payers
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function signTransactionFactory(...payers: Signer[]): SignTransaction {
|
||||||
|
return modifySignTransaction(
|
||||||
|
(transaction: Transaction) => Promise.resolve(transaction),
|
||||||
|
...payers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifySignTransaction(
|
||||||
|
signTransaction: SignTransaction,
|
||||||
|
...payers: Signer[]
|
||||||
|
): SignTransaction {
|
||||||
|
return (transaction: Transaction) => {
|
||||||
|
for (const payer of payers) {
|
||||||
|
transaction.partialSign(payer);
|
||||||
|
}
|
||||||
|
return signTransaction(transaction);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for {@link Keypair} resembling Solana web3 browser wallet
|
||||||
|
*/
|
||||||
|
export class NodeWallet {
|
||||||
|
payer: Keypair;
|
||||||
|
signTransaction: SignTransaction;
|
||||||
|
|
||||||
|
constructor(payer: Keypair) {
|
||||||
|
this.payer = payer;
|
||||||
|
this.signTransaction = signTransactionFactory(this.payer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromSecretKey(
|
||||||
|
secretKey: Uint8Array,
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
skipValidation?: boolean | undefined;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
): NodeWallet {
|
||||||
|
return new NodeWallet(Keypair.fromSecretKey(secretKey, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey(): PublicKey {
|
||||||
|
return this.payer.publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey(): PublicKey {
|
||||||
|
return this.publicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
key(): PublicKey {
|
||||||
|
return this.publicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.publicKey().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
keypair(): Keypair {
|
||||||
|
return this.payer;
|
||||||
|
}
|
||||||
|
|
||||||
|
signer(): Signer {
|
||||||
|
return this.keypair();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transactions provided to this function should be ready to send.
|
||||||
|
* This function will do the following:
|
||||||
|
* 1. Add the {@param payer} as the feePayer and latest blockhash to the {@link Transaction}.
|
||||||
|
* 2. Sign using {@param signTransaction}.
|
||||||
|
* 3. Send raw transaction.
|
||||||
|
* 4. Confirm transaction.
|
||||||
|
*/
|
||||||
|
export async function signSendAndConfirmTransaction(
|
||||||
|
connection: Connection,
|
||||||
|
payer: PublicKeyInitData,
|
||||||
|
signTransaction: SignTransaction,
|
||||||
|
unsignedTransaction: Transaction,
|
||||||
|
options?: ConfirmOptions
|
||||||
|
): Promise<TransactionSignatureAndResponse> {
|
||||||
|
const commitment = options?.commitment;
|
||||||
|
const { blockhash, lastValidBlockHeight } =
|
||||||
|
await connection.getLatestBlockhash(commitment);
|
||||||
|
unsignedTransaction.recentBlockhash = blockhash;
|
||||||
|
unsignedTransaction.feePayer = new PublicKey(payer);
|
||||||
|
|
||||||
|
// Sign transaction, broadcast, and confirm
|
||||||
|
const signed = await signTransaction(unsignedTransaction);
|
||||||
|
const signature = await connection.sendRawTransaction(
|
||||||
|
signed.serialize(),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const response = await connection.confirmTransaction(
|
||||||
|
{
|
||||||
|
blockhash,
|
||||||
|
lastValidBlockHeight,
|
||||||
|
signature,
|
||||||
|
},
|
||||||
|
commitment
|
||||||
|
);
|
||||||
|
return { signature, response };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Please use {@link signSendAndConfirmTransaction} instead, which allows
|
||||||
|
* retries to be configured in {@link ConfirmOptions}.
|
||||||
|
*
|
||||||
|
* The transactions provided to this function should be ready to send.
|
||||||
|
* This function will do the following:
|
||||||
|
* 1. Add the {@param payer} as the feePayer and latest blockhash to the {@link Transaction}.
|
||||||
|
* 2. Sign using {@param signTransaction}.
|
||||||
|
* 3. Send raw transaction.
|
||||||
|
* 4. Confirm transaction.
|
||||||
|
*/
|
||||||
|
export async function sendAndConfirmTransactionsWithRetry(
|
||||||
|
connection: Connection,
|
||||||
|
signTransaction: SignTransaction,
|
||||||
|
payer: string,
|
||||||
|
unsignedTransactions: Transaction[],
|
||||||
|
maxRetries: number = 0,
|
||||||
|
options?: ConfirmOptions
|
||||||
|
): Promise<TransactionSignatureAndResponse[]> {
|
||||||
|
if (unsignedTransactions.length == 0) {
|
||||||
|
return Promise.reject("No transactions provided to send.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitment = options?.commitment;
|
||||||
|
|
||||||
|
let currentRetries = 0;
|
||||||
|
const output: TransactionSignatureAndResponse[] = [];
|
||||||
|
for (const transaction of unsignedTransactions) {
|
||||||
|
while (currentRetries <= maxRetries) {
|
||||||
|
try {
|
||||||
|
const latest = await connection.getLatestBlockhash(commitment);
|
||||||
|
transaction.recentBlockhash = latest.blockhash;
|
||||||
|
transaction.feePayer = new PublicKey(payer);
|
||||||
|
|
||||||
|
const signed = await signTransaction(transaction).catch((e) => null);
|
||||||
|
if (signed === null) {
|
||||||
|
return Promise.reject("Failed to sign transaction.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = await connection.sendRawTransaction(
|
||||||
|
signed.serialize(),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const response = await connection.confirmTransaction(
|
||||||
|
{
|
||||||
|
signature,
|
||||||
|
...latest,
|
||||||
|
},
|
||||||
|
commitment
|
||||||
|
);
|
||||||
|
output.push({ signature, response });
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
++currentRetries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentRetries > maxRetries) {
|
||||||
|
return Promise.reject("Reached the maximum number of retries.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(output);
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
export * from "@certusone/wormhole-sdk-wasm";
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue