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
|
||||
|
||||
# build
|
||||
/lib
|
||||
/lib
|
||||
|
||||
# solana idl
|
||||
/src/anchor-idl
|
||||
/src/solana/types
|
||||
|
|
|
@ -2,10 +2,30 @@
|
|||
|
||||
## 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
|
||||
|
||||
Use FQTs in Aptos SDK
|
||||
|
||||
### Removed
|
||||
|
||||
Dependency: @certusone/wormhole-sdk-wasm
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Added
|
||||
|
@ -17,6 +37,7 @@ Aptos support
|
|||
Wormchain rename
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Added
|
||||
|
||||
XPLA mainnet support and functions
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,8 @@
|
|||
"scripts": {
|
||||
"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-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-all": "npm run build-deps && npm run build-lib",
|
||||
"test": "jest --config jestconfig.json --verbose",
|
||||
|
@ -43,9 +44,11 @@
|
|||
"@injectivelabs/tx-ts": "^1.0.22",
|
||||
"@openzeppelin/contracts": "^4.2.0",
|
||||
"@typechain/ethers-v5": "^7.0.1",
|
||||
"@types/elliptic": "^6.4.14",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/react": "^17.0.19",
|
||||
"copy-dir": "^1.3.0",
|
||||
"ethers": "^5.6.8",
|
||||
|
@ -61,14 +64,17 @@
|
|||
"@certusone/wormhole-sdk-proto-web": "^0.0.5",
|
||||
"@certusone/wormhole-sdk-wasm": "^0.0.1",
|
||||
"@injectivelabs/sdk-ts": "^1.0.75",
|
||||
"@solana/spl-token": "^0.1.8",
|
||||
"@solana/web3.js": "^1.24.0",
|
||||
"@project-serum/anchor": "^0.25.0",
|
||||
"@solana/spl-token": "^0.3.5",
|
||||
"@solana/web3.js": "^1.66.2",
|
||||
"@terra-money/terra.js": "^3.1.3",
|
||||
"@xpla/xpla.js": "^0.2.1",
|
||||
"algosdk": "^1.15.0",
|
||||
"aptos": "^1.3.16",
|
||||
"axios": "^0.24.0",
|
||||
"bech32": "^2.0.0",
|
||||
"binary-parser": "^2.2.1",
|
||||
"elliptic": "^6.5.4",
|
||||
"js-base64": "^3.6.1",
|
||||
"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,
|
||||
getApplicationAddress,
|
||||
} from "algosdk";
|
||||
import { setDefaultWasm } from "../../solana/wasm";
|
||||
import { hexToUint8Array, uint8ArrayToHex } from "../../utils";
|
||||
import {
|
||||
accountExists,
|
||||
|
@ -23,8 +22,6 @@ import { PopulateData, TmplSig } from "../TmplSig";
|
|||
const CORE_ID = BigInt(4);
|
||||
const TOKEN_BRIDGE_ID = BigInt(6);
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
describe("Unit Tests", () => {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { importCoreWasm } from "../solana/wasm";
|
||||
import { PublicKeyInitData } from "@solana/web3.js";
|
||||
import { deriveClaimKey } from "../solana/wormhole";
|
||||
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||
|
||||
export async function getClaimAddressSolana(
|
||||
programAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
programAddress: PublicKeyInitData,
|
||||
signedVaa: SignedVaa
|
||||
) {
|
||||
const { claim_address } = await importCoreWasm();
|
||||
return new PublicKey(claim_address(programAddress, signedVAA));
|
||||
const parsed = parseVaa(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 { bech32 } from "bech32";
|
||||
import {
|
||||
|
@ -8,7 +8,7 @@ import {
|
|||
Hexable,
|
||||
zeroPad,
|
||||
} from "ethers/lib/utils";
|
||||
import { importTokenWasm } from "../solana/wasm";
|
||||
import { deriveWormholeEmitterKey } from "../solana/wormhole";
|
||||
import { uint8ArrayToHex } from "../utils";
|
||||
|
||||
export function getEmitterAddressEth(
|
||||
|
@ -17,11 +17,10 @@ export function getEmitterAddressEth(
|
|||
return Buffer.from(zeroPad(arrayify(contractAddress), 32)).toString("hex");
|
||||
}
|
||||
|
||||
export async function getEmitterAddressSolana(programAddress: string) {
|
||||
const { emitter_address } = await importTokenWasm();
|
||||
return Buffer.from(
|
||||
zeroPad(new PublicKey(emitter_address(programAddress)).toBytes(), 32)
|
||||
).toString("hex");
|
||||
export async function getEmitterAddressSolana(
|
||||
programAddress: PublicKeyInitData
|
||||
) {
|
||||
return deriveWormholeEmitterKey(programAddress).toBuffer().toString("hex");
|
||||
}
|
||||
|
||||
export async function getEmitterAddressTerra(programAddress: string) {
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
import { importCoreWasm } from "../solana/wasm";
|
||||
import { ethers } from "ethers";
|
||||
import { uint8ArrayToHex } from "..";
|
||||
import { keccak256 } from "../utils";
|
||||
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||
|
||||
export async function getSignedVAAHash(signedVAA: Uint8Array) {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
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("")])]);
|
||||
export function getSignedVAAHash(signedVaa: SignedVaa): string {
|
||||
return `0x${keccak256(parseVaa(signedVaa).hash).toString("hex")}`;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
require("dotenv").config({ path: ".env" });
|
||||
import { getNetworkInfo, Network } from "@injectivelabs/networks";
|
||||
import {
|
||||
ChainGrpcWasmApi,
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
export * from "./cosmos";
|
||||
export * from "./ethers-contracts";
|
||||
export * from "./solana";
|
||||
export * from "./terra";
|
||||
export * from "./rpc";
|
||||
export * from "./utils";
|
||||
export * from "./bridge";
|
||||
export * from "./token_bridge";
|
||||
export * from "./cosmwasm";
|
||||
export * from "./vaa";
|
||||
|
||||
export * as cosmos from "./cosmos";
|
||||
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 nft_bridge from "./nft_bridge";
|
||||
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";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
getAssociatedTokenAddress,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
|
@ -28,10 +28,8 @@ import {
|
|||
parseSequenceFromLogSolana,
|
||||
getEmitterAddressSolana,
|
||||
CHAIN_ID_SOLANA,
|
||||
parseNFTPayload,
|
||||
} from "../..";
|
||||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||
import { importCoreWasm, setDefaultWasm } from "../../solana/wasm";
|
||||
import {
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY,
|
||||
|
@ -59,10 +57,10 @@ import {
|
|||
import { postVaaSolanaWithRetry } from "../../solana";
|
||||
import { tryNativeToUint8Array } from "../../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");
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
type Address = string;
|
||||
|
@ -158,11 +156,7 @@ describe("Integration Tests", () => {
|
|||
test("Send Solana SPL to Ethereum and back", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
|
||||
const fromAddress = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
const fromAddress = await getAssociatedTokenAddress(
|
||||
new PublicKey(TEST_SOLANA_TOKEN),
|
||||
keypair.publicKey
|
||||
);
|
||||
|
@ -177,9 +171,7 @@ describe("Integration Tests", () => {
|
|||
let signedVAA = await waitUntilSolanaTxObserved(transaction1);
|
||||
|
||||
// we get the solana token id from the VAA
|
||||
const { tokenId } = parseNFTPayload(
|
||||
Buffer.from(new Uint8Array(parse_vaa(signedVAA).payload))
|
||||
);
|
||||
const { tokenId } = parseNftTransferVaa(signedVAA);
|
||||
|
||||
await _redeemOnEth(signedVAA);
|
||||
const eth_addr = await nft_bridge.getForeignAssetEth(
|
||||
|
@ -200,9 +192,7 @@ describe("Integration Tests", () => {
|
|||
);
|
||||
signedVAA = await waitUntilEthTxObserved(transaction3);
|
||||
|
||||
const { name, symbol } = parseNFTPayload(
|
||||
Buffer.from(new Uint8Array(parse_vaa(signedVAA).payload))
|
||||
);
|
||||
const { name, symbol } = parseNftTransferVaa(signedVAA);
|
||||
|
||||
// if the names match up here, it means all the spl caches work
|
||||
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 { ethers } from "ethers";
|
||||
import { isBytes } from "ethers/lib/utils";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { CHAIN_ID_SOLANA } from "..";
|
||||
import { NFTBridge__factory } from "../ethers-contracts";
|
||||
import { importNftWasm } from "../solana/wasm";
|
||||
import { deriveWrappedMintKey } from "../solana/nftBridge";
|
||||
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
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param provider
|
||||
* @param originChain
|
||||
* @param originAsset zero pad to 32 bytes
|
||||
* @returns
|
||||
*/
|
||||
export async function getForeignAssetEth(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array
|
||||
): Promise<string | null> {
|
||||
const originChainId = coalesceChainId(originChain);
|
||||
const tokenBridge = NFTBridge__factory.connect(tokenBridgeAddress, provider);
|
||||
const tokenBridge = NFTBridge__factory.connect(nftBridgeAddress, provider);
|
||||
try {
|
||||
if (originChainId === CHAIN_ID_SOLANA) {
|
||||
// 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
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param client
|
||||
* @param originChain
|
||||
* @param originAsset
|
||||
* @returns
|
||||
*/
|
||||
export async function getForeignAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
client: LCDClient,
|
||||
originChain: ChainId,
|
||||
originAsset: Uint8Array
|
||||
|
@ -60,7 +62,7 @@ export async function getForeignAssetTerra(
|
|||
? "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE="
|
||||
: fromUint8Array(originAsset);
|
||||
const result: { address: string } = await client.wasm.contractQuery(
|
||||
tokenBridgeAddress,
|
||||
nftBridgeAddress,
|
||||
{
|
||||
wrapped_registry: {
|
||||
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
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param originChain
|
||||
* @param originAsset zero pad to 32 bytes
|
||||
* @returns
|
||||
*/
|
||||
export async function getForeignAssetSol(
|
||||
tokenBridgeAddress: string,
|
||||
export async function getForeignAssetSolana(
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
originChain: ChainId | ChainName,
|
||||
originAsset: Uint8Array,
|
||||
tokenId: Uint8Array
|
||||
originAsset: string | Uint8Array | Buffer,
|
||||
tokenId: Uint8Array | Buffer | bigint
|
||||
): 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.
|
||||
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 { NFTBridge__factory } from "../ethers-contracts";
|
||||
import { getSignedVAAHash } from "../bridge";
|
||||
import { importCoreWasm } from "../solana/wasm";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { Commitment, Connection, PublicKeyInitData } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import axios from "axios";
|
||||
import { redeemOnTerra } from ".";
|
||||
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS } from "..";
|
||||
import { getClaim } from "../solana/wormhole";
|
||||
import { parseVaa, SignedVaa } from "../vaa/wormhole";
|
||||
|
||||
export async function getIsTransferCompletedEth(
|
||||
nftBridgeAddress: string,
|
||||
|
@ -14,7 +15,7 @@ export async function getIsTransferCompletedEth(
|
|||
signedVAA: Uint8Array
|
||||
) {
|
||||
const nftBridge = NFTBridge__factory.connect(nftBridgeAddress, provider);
|
||||
const signedVAAHash = await getSignedVAAHash(signedVAA);
|
||||
const signedVAAHash = getSignedVAAHash(signedVAA);
|
||||
return await nftBridge.isTransferCompleted(signedVAAHash);
|
||||
}
|
||||
|
||||
|
@ -57,15 +58,18 @@ export async function getIsTransferCompletedTerra(
|
|||
}
|
||||
|
||||
export async function getIsTransferCompletedSolana(
|
||||
nftBridgeAddress: string,
|
||||
signedVAA: Uint8Array,
|
||||
connection: Connection
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
signedVAA: SignedVaa,
|
||||
connection: Connection,
|
||||
commitment?: Commitment
|
||||
) {
|
||||
const { claim_address } = await importCoreWasm();
|
||||
const claimAddress = await claim_address(nftBridgeAddress, signedVAA);
|
||||
const claimInfo = await connection.getAccountInfo(
|
||||
new PublicKey(claimAddress),
|
||||
"confirmed"
|
||||
);
|
||||
return !!claimInfo;
|
||||
const parsed = parseVaa(signedVAA);
|
||||
return getClaim(
|
||||
connection,
|
||||
nftBridgeAddress,
|
||||
parsed.emitterAddress,
|
||||
parsed.emitterChain,
|
||||
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 { 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
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param provider
|
||||
* @param assetAddress
|
||||
* @returns
|
||||
*/
|
||||
export async function getIsWrappedAssetEth(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
assetAddress: string
|
||||
) {
|
||||
if (!assetAddress) return false;
|
||||
const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
|
||||
const tokenBridge = Bridge__factory.connect(nftBridgeAddress, provider);
|
||||
return await tokenBridge.isWrappedAsset(assetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an asset on Solana is a wormhole wrapped asset
|
||||
* @param connection
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param mintAddress
|
||||
* @param [commitment]
|
||||
* @returns
|
||||
*/
|
||||
export async function getIsWrappedAssetSol(
|
||||
export async function getIsWrappedAssetSolana(
|
||||
connection: Connection,
|
||||
tokenBridgeAddress: string,
|
||||
mintAddress: string
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
mintAddress: PublicKeyInitData,
|
||||
commitment?: Commitment
|
||||
) {
|
||||
if (!mintAddress) return false;
|
||||
const { 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
|
||||
);
|
||||
return !!wrappedMetaAccountInfo;
|
||||
if (!mintAddress) {
|
||||
return false;
|
||||
}
|
||||
return getWrappedMeta(connection, nftBridgeAddress, mintAddress, commitment)
|
||||
.catch((_) => null)
|
||||
.then((meta) => meta != null);
|
||||
}
|
||||
|
||||
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 { BigNumber, ethers } from "ethers";
|
||||
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 { importNftWasm } from "../solana/wasm";
|
||||
import { getWrappedMeta } from "../solana/nftBridge";
|
||||
import {
|
||||
ChainId,
|
||||
ChainName,
|
||||
|
@ -24,20 +30,20 @@ export interface WormholeWrappedNFTInfo {
|
|||
|
||||
/**
|
||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param provider
|
||||
* @param wrappedAddress
|
||||
* @returns
|
||||
*/
|
||||
export async function getOriginalAssetEth(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
provider: ethers.Signer | ethers.providers.Provider,
|
||||
wrappedAddress: string,
|
||||
tokenId: string,
|
||||
lookupChain: ChainId | ChainName
|
||||
): Promise<WormholeWrappedNFTInfo> {
|
||||
const isWrapped = await getIsWrappedAssetEth(
|
||||
tokenBridgeAddress,
|
||||
nftBridgeAddress,
|
||||
provider,
|
||||
wrappedAddress
|
||||
);
|
||||
|
@ -69,56 +75,49 @@ export async function getOriginalAssetEth(
|
|||
/**
|
||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||
* @param connection
|
||||
* @param tokenBridgeAddress
|
||||
* @param nftBridgeAddress
|
||||
* @param mintAddress
|
||||
* @param [commitment]
|
||||
* @returns
|
||||
*/
|
||||
export async function getOriginalAssetSol(
|
||||
export async function getOriginalAssetSolana(
|
||||
connection: Connection,
|
||||
tokenBridgeAddress: string,
|
||||
mintAddress: string
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
mintAddress: PublicKeyInitData,
|
||||
commitment?: Commitment
|
||||
): 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 {
|
||||
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 {
|
||||
isWrapped: false,
|
||||
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/
|
||||
const big0 = BigInt(0);
|
||||
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 { ethers, Overrides } from "ethers";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { CHAIN_ID_SOLANA } from "..";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { ixFromRust } from "../solana";
|
||||
import { importCoreWasm, importNftWasm } from "../solana/wasm";
|
||||
import {
|
||||
createCompleteTransferNativeInstruction,
|
||||
createCompleteTransferWrappedInstruction,
|
||||
createCompleteWrappedMetaInstruction,
|
||||
} from "../solana/nftBridge";
|
||||
import { parseNftTransferVaa, parseVaa, SignedVaa } from "../vaa";
|
||||
|
||||
export async function redeemOnEth(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
signer: ethers.Signer,
|
||||
signedVAA: Uint8Array,
|
||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||
): 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 receipt = await v.wait();
|
||||
return receipt;
|
||||
|
@ -22,52 +32,33 @@ export async function redeemOnEth(
|
|||
export async function isNFTVAASolanaNative(
|
||||
signedVAA: Uint8Array
|
||||
): Promise<boolean> {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
const parsedVAA = parse_vaa(signedVAA);
|
||||
const isSolanaNative =
|
||||
Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(33) ===
|
||||
CHAIN_ID_SOLANA;
|
||||
return isSolanaNative;
|
||||
return parseVaa(signedVAA).payload.readUInt16BE(33) === CHAIN_ID_SOLANA;
|
||||
}
|
||||
|
||||
export async function redeemOnSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
payerAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
bridgeAddress: PublicKeyInitData,
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
payerAddress: PublicKeyInitData,
|
||||
signedVaa: SignedVaa,
|
||||
toAuthorityAddress?: PublicKeyInitData,
|
||||
commitment?: Commitment
|
||||
): Promise<Transaction> {
|
||||
const isSolanaNative = await isNFTVAASolanaNative(signedVAA);
|
||||
const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
|
||||
await importNftWasm();
|
||||
const ixs = [];
|
||||
if (isSolanaNative) {
|
||||
ixs.push(
|
||||
ixFromRust(
|
||||
complete_transfer_native_ix(
|
||||
tokenBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
payerAddress, //TODO: allow for a different address than payer
|
||||
signedVAA
|
||||
)
|
||||
)
|
||||
);
|
||||
} 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();
|
||||
const parsed = parseNftTransferVaa(signedVaa);
|
||||
const createCompleteTransferInstruction =
|
||||
parsed.tokenChain == CHAIN_ID_SOLANA
|
||||
? createCompleteTransferNativeInstruction
|
||||
: createCompleteTransferWrappedInstruction;
|
||||
const transaction = new Transaction().add(
|
||||
createCompleteTransferInstruction(
|
||||
nftBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
parsed,
|
||||
toAuthorityAddress
|
||||
)
|
||||
);
|
||||
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
return transaction;
|
||||
|
@ -75,33 +66,36 @@ export async function redeemOnSolana(
|
|||
|
||||
export async function createMetaOnSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
payerAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
bridgeAddress: PublicKeyInitData,
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
payerAddress: PublicKeyInitData,
|
||||
signedVaa: SignedVaa,
|
||||
commitment?: Commitment
|
||||
): Promise<Transaction> {
|
||||
const { complete_transfer_wrapped_meta_ix } = await importNftWasm();
|
||||
const ix = ixFromRust(
|
||||
complete_transfer_wrapped_meta_ix(
|
||||
tokenBridgeAddress,
|
||||
const parsed = parseNftTransferVaa(signedVaa);
|
||||
if (parsed.tokenChain == CHAIN_ID_SOLANA) {
|
||||
return Promise.reject("parsed.tokenChain == CHAIN_ID_SOLANA");
|
||||
}
|
||||
const transaction = new Transaction().add(
|
||||
createCompleteWrappedMetaInstruction(
|
||||
nftBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
parsed
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
export async function redeemOnTerra(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
walletAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
): Promise<MsgExecuteContract> {
|
||||
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
||||
return new MsgExecuteContract(walletAddress, nftBridgeAddress, {
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
|
|
|
@ -1,17 +1,36 @@
|
|||
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { createApproveInstruction } from "@solana/spl-token";
|
||||
import {
|
||||
Commitment,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
PublicKeyInitData,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ethers, Overrides } from "ethers";
|
||||
import { BN } from "@project-serum/anchor";
|
||||
import {
|
||||
NFTBridge__factory,
|
||||
NFTImplementation__factory,
|
||||
} from "../ethers-contracts";
|
||||
import { getBridgeFeeIx, ixFromRust } from "../solana";
|
||||
import { importNftWasm } from "../solana/wasm";
|
||||
import { ChainId, ChainName, CHAIN_ID_SOLANA, coalesceChainId, createNonce } from "../utils";
|
||||
import { createBridgeFeeTransferInstruction } from "../solana";
|
||||
import {
|
||||
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(
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
signer: ethers.Signer,
|
||||
tokenAddress: string,
|
||||
tokenID: ethers.BigNumberish,
|
||||
|
@ -19,11 +38,11 @@ export async function transferFromEth(
|
|||
recipientAddress: Uint8Array,
|
||||
overrides: Overrides & { from?: string | Promise<string> } = {}
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const recipientChainId = coalesceChainId(recipientChain)
|
||||
const recipientChainId = coalesceChainId(recipientChain);
|
||||
//TODO: should we check if token attestation exists on the target chain
|
||||
const token = NFTImplementation__factory.connect(tokenAddress, signer);
|
||||
await (await token.approve(tokenBridgeAddress, tokenID, overrides)).wait();
|
||||
const bridge = NFTBridge__factory.connect(tokenBridgeAddress, signer);
|
||||
await (await token.approve(nftBridgeAddress, tokenID, overrides)).wait();
|
||||
const bridge = NFTBridge__factory.connect(nftBridgeAddress, signer);
|
||||
const v = await bridge.transferNFT(
|
||||
tokenAddress,
|
||||
tokenID,
|
||||
|
@ -38,90 +57,89 @@ export async function transferFromEth(
|
|||
|
||||
export async function transferFromSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
payerAddress: string,
|
||||
fromAddress: string,
|
||||
mintAddress: string,
|
||||
targetAddress: Uint8Array,
|
||||
bridgeAddress: PublicKeyInitData,
|
||||
nftBridgeAddress: PublicKeyInitData,
|
||||
payerAddress: PublicKeyInitData,
|
||||
fromAddress: PublicKeyInitData,
|
||||
mintAddress: PublicKeyInitData,
|
||||
targetAddress: Uint8Array | Buffer,
|
||||
targetChain: ChainId | ChainName,
|
||||
originAddress?: Uint8Array,
|
||||
originAddress?: Uint8Array | Buffer,
|
||||
originChain?: ChainId | ChainName,
|
||||
originTokenId?: Uint8Array
|
||||
originTokenId?: Uint8Array | Buffer | number | bigint,
|
||||
commitment?: Commitment
|
||||
): Promise<Transaction> {
|
||||
const originChainId: ChainId | undefined = originChain ? coalesceChainId(originChain) : undefined
|
||||
const originChainId: ChainId | undefined = originChain
|
||||
? coalesceChainId(originChain)
|
||||
: undefined;
|
||||
const nonce = createNonce().readUInt32LE(0);
|
||||
const transferIx = await getBridgeFeeIx(
|
||||
const transferIx = await createBridgeFeeTransferInstruction(
|
||||
connection,
|
||||
bridgeAddress,
|
||||
payerAddress
|
||||
);
|
||||
const {
|
||||
transfer_native_ix,
|
||||
transfer_wrapped_ix,
|
||||
approval_authority_address,
|
||||
} = await importNftWasm();
|
||||
const approvalIx = Token.createApproveInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(fromAddress),
|
||||
new PublicKey(approval_authority_address(tokenBridgeAddress)),
|
||||
new PublicKey(payerAddress),
|
||||
[],
|
||||
Number(1)
|
||||
const approvalIx = createApproveAuthoritySignerInstruction(
|
||||
nftBridgeAddress,
|
||||
fromAddress,
|
||||
payerAddress
|
||||
);
|
||||
let messageKey = Keypair.generate();
|
||||
let message = Keypair.generate();
|
||||
const isSolanaNative =
|
||||
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
||||
if (!isSolanaNative && (!originAddress || !originTokenId)) {
|
||||
throw new Error(
|
||||
return Promise.reject(
|
||||
"originAddress and originTokenId are required when specifying originChain"
|
||||
);
|
||||
}
|
||||
const ix = ixFromRust(
|
||||
isSolanaNative
|
||||
? transfer_native_ix(
|
||||
tokenBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
: transfer_wrapped_ix(
|
||||
tokenBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
payerAddress,
|
||||
originChainId as number, // checked by isSolanaNative
|
||||
originAddress as Uint8Array, // checked by throw
|
||||
originTokenId as Uint8Array, // checked by throw
|
||||
nonce,
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
const nftBridgeTransferIx = isSolanaNative
|
||||
? createTransferNativeInstruction(
|
||||
nftBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
message.publicKey,
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain)
|
||||
)
|
||||
: createTransferWrappedInstruction(
|
||||
nftBridgeAddress,
|
||||
bridgeAddress,
|
||||
payerAddress,
|
||||
message.publicKey,
|
||||
fromAddress,
|
||||
payerAddress,
|
||||
originChainId!,
|
||||
originAddress!,
|
||||
isBytes(originTokenId)
|
||||
? BigInt(new BN(originTokenId).toString())
|
||||
: originTokenId!,
|
||||
nonce,
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain)
|
||||
);
|
||||
const transaction = new Transaction().add(
|
||||
transferIx,
|
||||
approvalIx,
|
||||
nftBridgeTransferIx
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
const { blockhash } = await connection.getLatestBlockhash(commitment);
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
transaction.partialSign(messageKey);
|
||||
transaction.partialSign(message);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
export async function transferFromTerra(
|
||||
walletAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
nftBridgeAddress: string,
|
||||
tokenAddress: string,
|
||||
tokenID: string,
|
||||
recipientChain: ChainId | ChainName,
|
||||
recipientAddress: Uint8Array
|
||||
): Promise<MsgExecuteContract[]> {
|
||||
const recipientChainId = coalesceChainId(recipientChain)
|
||||
const recipientChainId = coalesceChainId(recipientChain);
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
return [
|
||||
new MsgExecuteContract(
|
||||
|
@ -129,7 +147,7 @@ export async function transferFromTerra(
|
|||
tokenAddress,
|
||||
{
|
||||
approve: {
|
||||
spender: tokenBridgeAddress,
|
||||
spender: nftBridgeAddress,
|
||||
token_id: tokenID,
|
||||
},
|
||||
},
|
||||
|
@ -137,7 +155,7 @@ export async function transferFromTerra(
|
|||
),
|
||||
new MsgExecuteContract(
|
||||
walletAddress,
|
||||
tokenBridgeAddress,
|
||||
nftBridgeAddress,
|
||||
{
|
||||
initiate_transfer: {
|
||||
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 {
|
||||
createPostVaaInstruction as createPostVaaInstructionSolana,
|
||||
createVerifySignaturesInstructions as createVerifySignaturesInstructionsSolana,
|
||||
postVaa as postVaaSolana,
|
||||
postVaaWithRetry as postVaaSolanaWithRetry,
|
||||
} from "./postVaa";
|
||||
export * from "./rust";
|
||||
export * from "./wasm";
|
||||
} from "./sendAndConfirmPostVaa";
|
||||
export {
|
||||
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