- EVM
- Solana
Prerequisites
Before you begin, ensure that you’ve:- Installed Node.js v22+
-
Prepared an EVM testnet wallet with the private key available
- Added the supported Testnets of your choice to your wallet (this guide uses Arc Testnet, Avalanche Fuji and Sei Testnet)
- Funded your testnet wallet with native tokens on the destination chain (this guide uses Sei Testnet)
- Deposited 10 USDC into the Gateway Wallet contracts on Arc Testnet and Avalanche Fuji (creating a unified balance of 20 USDC)
-
Created a TypeScript project and have
vieminstalled -
You’ve set up a
.envfile with the following variables:.envReport incorrect codeCopyEVM_PRIVATE_KEY={YOUR_PRIVATE_KEY}
Steps
Follow these steps to transfer a unified USDC balance. This example uses a unified balance split between Arc Testnet and Avalanche Fuji. You can adapt it for any chains where you hold a unified balance.Step 1. Create and sign burn intents for the source chains
Create a new file calledtransfer.ts in the root of your project and add the
following code to it. This code creates and signs
burn intents for 5 USDC on
Arc Testnet and 5 USDC on Avalanche Fuji.transfer.ts
Report incorrect code
Copy
import { randomBytes } from "node:crypto";
import {
http,
maxUint256,
zeroAddress,
pad,
createPublicClient,
getContract,
createWalletClient,
formatUnits,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { avalancheFuji, arcTestnet, seiTestnet } from "viem/chains";
/* Constants */
const GATEWAY_WALLET_ADDRESS = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
const GATEWAY_MINTER_ADDRESS = "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B";
const TRANSFER_VALUE = 5_000000n; // 5 USDC (6 decimals)
const MAX_FEE = 2_010000n;
// Source chains configuration
const sourceChains = [
{
name: "arcTestnet",
chain: arcTestnet,
usdcAddress: "0x3600000000000000000000000000000000000000",
domainId: 26,
},
{
name: "avalancheFuji",
chain: avalancheFuji,
usdcAddress: "0x5425890298aed601595a70ab815c96711a31bc65",
domainId: 1,
},
];
// Destination chain configuration
const destinationChain = {
name: "seiTestnet",
chain: seiTestnet,
usdcAddress: "0x4fCF1784B31630811181f670Aea7A7bEF803eaED",
domainId: 16,
};
const domain = { name: "GatewayWallet", version: "1" };
const EIP712Domain = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
] as const;
const TransferSpec = [
{ name: "version", type: "uint32" },
{ name: "sourceDomain", type: "uint32" },
{ name: "destinationDomain", type: "uint32" },
{ name: "sourceContract", type: "bytes32" },
{ name: "destinationContract", type: "bytes32" },
{ name: "sourceToken", type: "bytes32" },
{ name: "destinationToken", type: "bytes32" },
{ name: "sourceDepositor", type: "bytes32" },
{ name: "destinationRecipient", type: "bytes32" },
{ name: "sourceSigner", type: "bytes32" },
{ name: "destinationCaller", type: "bytes32" },
{ name: "value", type: "uint256" },
{ name: "salt", type: "bytes32" },
{ name: "hookData", type: "bytes" },
] as const;
const BurnIntent = [
{ name: "maxBlockHeight", type: "uint256" },
{ name: "maxFee", type: "uint256" },
{ name: "spec", type: "TransferSpec" },
] as const;
const gatewayMinterAbi = [
{
type: "function",
name: "gatewayMint",
inputs: [
{ name: "attestationPayload", type: "bytes" },
{ name: "signature", type: "bytes" },
],
outputs: [],
stateMutability: "nonpayable",
},
] as const;
// Get account from environment
if (!process.env.EVM_PRIVATE_KEY) throw new Error("EVM_PRIVATE_KEY not set");
const account = privateKeyToAccount(
process.env.EVM_PRIVATE_KEY as `0x${string}`,
);
console.log(`Using account: ${account.address}`);
console.log(`Transferring from: ${sourceChains.map((c) => c.name).join(", ")}`);
console.log(`Transferring to: ${destinationChain.name}\n`);
// Create and sign burn intents for each source chain
const requests = [];
for (const sourceChain of sourceChains) {
console.log(
`Creating burn intent from ${sourceChain.name} → ${destinationChain.name}...`,
);
const burnIntent = {
maxBlockHeight: maxUint256,
maxFee: MAX_FEE,
spec: {
version: 1,
sourceDomain: sourceChain.domainId,
destinationDomain: destinationChain.domainId,
sourceContract: GATEWAY_WALLET_ADDRESS,
destinationContract: GATEWAY_MINTER_ADDRESS,
sourceToken: sourceChain.usdcAddress,
destinationToken: destinationChain.usdcAddress,
sourceDepositor: account.address,
destinationRecipient: account.address,
sourceSigner: account.address,
destinationCaller: zeroAddress,
value: TRANSFER_VALUE,
salt: "0x" + randomBytes(32).toString("hex"),
hookData: "0x",
},
};
const typedData = {
types: { EIP712Domain, TransferSpec, BurnIntent },
domain,
primaryType: "BurnIntent" as const,
message: {
...burnIntent,
spec: {
...burnIntent.spec,
sourceContract: pad(
burnIntent.spec.sourceContract.toLowerCase() as `0x${string}`,
{ size: 32 },
),
destinationContract: pad(
burnIntent.spec.destinationContract.toLowerCase() as `0x${string}`,
{ size: 32 },
),
sourceToken: pad(
burnIntent.spec.sourceToken.toLowerCase() as `0x${string}`,
{ size: 32 },
),
destinationToken: pad(
burnIntent.spec.destinationToken.toLowerCase() as `0x${string}`,
{ size: 32 },
),
sourceDepositor: pad(
burnIntent.spec.sourceDepositor.toLowerCase() as `0x${string}`,
{ size: 32 },
),
destinationRecipient: pad(
burnIntent.spec.destinationRecipient.toLowerCase() as `0x${string}`,
{ size: 32 },
),
sourceSigner: pad(
burnIntent.spec.sourceSigner.toLowerCase() as `0x${string}`,
{ size: 32 },
),
destinationCaller: pad(
burnIntent.spec.destinationCaller.toLowerCase() as `0x${string}`,
{ size: 32 },
),
},
},
};
const signature = await account.signTypedData(
typedData as Parameters<typeof account.signTypedData>[0],
);
requests.push({ burnIntent: typedData.message, signature });
}
console.log("Signed burn intents.\n");
Note: For production apps, verifying the balance on each chain before
creating burn intents is best practice. For this how-to, it’s assumed that the
balances are created per the prerequisites. For a complete
end-to-end example that includes checking and error handling, see the Gateway
quickstarts (EVM,
Solana).
Step 2. Submit the burn intents to the Gateway API to obtain an attestation
Add the following code totransfer.ts. This code constructs a Gateway API
request to the
/transfer endpoint
and obtains the attestation from that endpoint.transfer.ts
Report incorrect code
Copy
console.log("Submitting to Gateway API...");
const response = await fetch(
"https://gateway-api-testnet.circle.com/v1/transfer",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(requests, (_key, value) =>
typeof value === "bigint" ? value.toString() : value,
),
},
);
if (!response.ok) {
console.error("Gateway API error status:", response.status);
console.error(await response.text());
throw new Error("Gateway API request failed");
}
const json = await response.json();
console.log("Gateway API response:", JSON.stringify(json, null, 2));
const { attestation, signature } = json;
Step 3. Transfer USDC to the destination chain
Add the following code totransfer.ts. This code performs a call to the
Gateway Minter contract
on Sei Testnet to instantly mint the USDC to your account on that chain.transfer.ts
Report incorrect code
Copy
console.log(`\nMinting funds on ${destinationChain.chain.name}...`);
const publicClient = createPublicClient({
chain: destinationChain.chain,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: destinationChain.chain,
transport: http(),
});
const gatewayMinter = getContract({
address: GATEWAY_MINTER_ADDRESS,
abi: gatewayMinterAbi,
client: walletClient,
});
const mintTx = await gatewayMinter.write.gatewayMint([attestation, signature], {
account,
});
await publicClient.waitForTransactionReceipt({ hash: mintTx });
const totalMinted = BigInt(requests.length) * TRANSFER_VALUE;
console.log(`\nMinted ${formatUnits(totalMinted, 6)} USDC`);
console.log(`Mint transaction hash:`, mintTx);
Step 4. Run the script
Run the script with the following command:Report incorrect code
Copy
npx tsx --env-file=.env transfer.ts
Prerequisites
Before you begin, ensure that you’ve:- Installed Node.js v22+
- Prepared Solana Devnet wallets (sender and recipient) and have the private key pairs exported as JSON arrays
- Funded your testnet wallet with USDC and native tokens
- Deposited 10 USDC into the Gateway Wallet contract on Solana Devnet
-
Created a TypeScript project and have the following dependencies installed:
@solana/web3.js@solana/spl-token@coral-xyz/anchor@solana/buffer-layoutbn.jsbs58
-
You’ve set up a
.envfile with the following variable:.envReport incorrect codeCopySOLANA_PRIVATE_KEYPAIR={YOUR_SOLANA_KEYPAIR_ARRAY} RECIPIENT_KEYPAIR={YOUR_RECIPIENT_KEYPAIR_ARRAY}
To generate a new Solana key pair, run:Copy the byte array from the JSON file to your
Report incorrect code
Copy
solana-keygen new -o keypair.json --no-bip39-passphrase
.env file.If your wallet exports a private key hash instead, you can use bs58 to convert
it:TypeScript
Report incorrect code
Copy
const bytes = bs58.decode({ YOUR_PRIVATE_KEY_HASH });
console.log(JSON.stringify(Array.from(bytes)));
Steps
Follow these steps to transfer a unified USDC balance from Solana to another Solana account. You can adapt this example for crosschain transfers.Step 1. Set up your Solana client and helpers
Create a new file calledtransfer.ts in the root of your project and add the
following code. This code imports required dependencies and defines constants
for the Solana connection, Gateway addresses, and transfer parameters.transfer.ts
Report incorrect code
Copy
import { randomBytes } from "node:crypto";
import * as crypto from "crypto";
import {
Wallet,
AnchorProvider,
setProvider,
Program,
utils,
} from "@coral-xyz/anchor";
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
TOKEN_PROGRAM_ID,
getAssociatedTokenAddressSync,
createAssociatedTokenAccountIdempotentInstruction,
} from "@solana/spl-token";
import {
u32be,
nu64be,
struct,
seq,
blob,
offset,
Layout,
} from "@solana/buffer-layout";
import bs58 from "bs58";
/* Constants */
const RPC_ENDPOINT = "https://api.devnet.solana.com";
const GATEWAY_WALLET_ADDRESS = "GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu";
const GATEWAY_MINTER_ADDRESS = "GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr";
const USDC_ADDRESS = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
const SOLANA_ZERO_ADDRESS = "11111111111111111111111111111111";
const SOLANA_DOMAIN = 5;
const TRANSFER_AMOUNT = 2; // 2 USDC
const TRANSFER_VALUE = BigInt(Math.floor(TRANSFER_AMOUNT * 1e6));
const MAX_FEE = 2_010000n;
const MAX_UINT64 = 2n ** 64n - 1n;
const TRANSFER_SPEC_MAGIC = 0xca85def7;
const BURN_INTENT_MAGIC = 0x070afbc2;
Step 2. Set up buffer layouts and IDL
Add the following code totransfer.ts to define custom layout classes for
serialization, structures for encoding burn intents and decoding attestations,
and the Gateway Minter IDL. Solana doesn’t support EIP-712 typed data, so burn
intents must be encoded manually using buffer layouts.transfer.ts
Report incorrect code
Copy
// Custom layout for Solana PublicKey (32 bytes)
class PublicKeyLayout extends Layout<PublicKey> {
constructor(property: string) {
super(32, property);
}
decode(b: Buffer, offset = 0): PublicKey {
return new PublicKey(b.subarray(offset, offset + 32));
}
encode(src: PublicKey, b: Buffer, offset = 0): number {
const pubkeyBuffer = src.toBuffer();
pubkeyBuffer.copy(b, offset);
return 32;
}
}
const publicKey = (property: string) => new PublicKeyLayout(property);
// Custom layout for 256-bit unsigned integers
class UInt256BE extends Layout<bigint> {
constructor(property: string) {
super(32, property);
}
decode(b: Buffer, offset = 0) {
const buffer = b.subarray(offset, offset + 32);
return buffer.readBigUInt64BE(24);
}
encode(src: bigint, b: Buffer, offset = 0) {
const buffer = Buffer.alloc(32);
buffer.writeBigUInt64BE(BigInt(src), 24);
buffer.copy(b, offset);
return 32;
}
}
const uint256be = (property: string) => new UInt256BE(property);
// Type 'as any' used due to @solana/buffer-layout's incomplete TypeScript definitions (archived Jan 2025)
const BurnIntentLayout = struct([
u32be("magic"),
uint256be("maxBlockHeight"),
uint256be("maxFee"),
u32be("transferSpecLength"),
struct(
[
u32be("magic"),
u32be("version"),
u32be("sourceDomain"),
u32be("destinationDomain"),
publicKey("sourceContract"),
publicKey("destinationContract"),
publicKey("sourceToken"),
publicKey("destinationToken"),
publicKey("sourceDepositor"),
publicKey("destinationRecipient"),
publicKey("sourceSigner"),
publicKey("destinationCaller"),
uint256be("value"),
blob(32, "salt"),
u32be("hookDataLength"),
blob(offset(u32be(), -4), "hookData"),
] as any,
"spec",
),
] as any);
const MintAttestationElementLayout = struct([
publicKey("destinationToken"),
publicKey("destinationRecipient"),
nu64be("value"),
blob(32, "transferSpecHash"),
u32be("hookDataLength"),
blob(offset(u32be(), -4), "hookData"),
] as any);
const MintAttestationSetLayout = struct([
u32be("magic"),
u32be("version"),
u32be("destinationDomain"),
publicKey("destinationContract"),
publicKey("destinationCaller"),
nu64be("maxBlockHeight"),
u32be("numAttestations"),
seq(MintAttestationElementLayout, offset(u32be(), -4), "attestations"),
] as any);
const gatewayMinterIdl = {
address: GATEWAY_MINTER_ADDRESS,
metadata: { name: "gatewayMinter", version: "0.1.0", spec: "0.1.0" },
instructions: [
{
name: "gatewayMint",
discriminator: [12, 0],
accounts: [
{ name: "payer", writable: true, signer: true },
{ name: "destinationCaller", signer: true },
{ name: "gatewayMinter" },
{ name: "systemProgram" },
{ name: "tokenProgram" },
{ name: "eventAuthority" },
{ name: "program" },
],
args: [
{ name: "params", type: { defined: { name: "gatewayMintParams" } } },
],
},
],
types: [
{
name: "gatewayMintParams",
type: {
kind: "struct",
fields: [
{ name: "attestation", type: "bytes" },
{ name: "signature", type: "bytes" },
],
},
},
],
};
Step 3. Define helper functions
Add the following code totransfer.ts to define helper functions for creating,
encoding, and signing burn intents, converting addresses between formats,
decoding attestations from the Gateway API, and finding Program Derived
Addresses (PDAs). The signing process uses Ed25519 with a specific prefix
required by the Gateway Wallet program.transfer.ts
Report incorrect code
Copy
function createBurnIntent(params: {
sourceDepositor: string;
destinationRecipient: string;
sourceSigner: string;
}) {
const { sourceDepositor, destinationRecipient, sourceSigner } = params;
return {
maxBlockHeight: MAX_UINT64,
maxFee: MAX_FEE,
spec: {
version: 1,
sourceDomain: SOLANA_DOMAIN,
destinationDomain: SOLANA_DOMAIN,
sourceContract: addressToBytes32(GATEWAY_WALLET_ADDRESS),
destinationContract: addressToBytes32(GATEWAY_MINTER_ADDRESS),
sourceToken: addressToBytes32(USDC_ADDRESS),
destinationToken: addressToBytes32(USDC_ADDRESS),
sourceDepositor: addressToBytes32(sourceDepositor),
destinationRecipient: addressToBytes32(destinationRecipient),
sourceSigner: addressToBytes32(sourceSigner),
destinationCaller: addressToBytes32(SOLANA_ZERO_ADDRESS),
value: TRANSFER_VALUE,
salt: "0x" + randomBytes(32).toString("hex"),
hookData: "0x",
},
};
}
function encodeBurnIntent(bi: any): Buffer {
const hookData = Buffer.from((bi.spec.hookData || "0x").slice(2), "hex");
const prepared = {
magic: BURN_INTENT_MAGIC,
maxBlockHeight: bi.maxBlockHeight,
maxFee: bi.maxFee,
transferSpecLength: 340 + hookData.length,
spec: {
magic: TRANSFER_SPEC_MAGIC,
version: bi.spec.version,
sourceDomain: bi.spec.sourceDomain,
destinationDomain: bi.spec.destinationDomain,
sourceContract: hexToPublicKey(bi.spec.sourceContract),
destinationContract: hexToPublicKey(bi.spec.destinationContract),
sourceToken: hexToPublicKey(bi.spec.sourceToken),
destinationToken: hexToPublicKey(bi.spec.destinationToken),
sourceDepositor: hexToPublicKey(bi.spec.sourceDepositor),
destinationRecipient: hexToPublicKey(bi.spec.destinationRecipient),
sourceSigner: hexToPublicKey(bi.spec.sourceSigner),
destinationCaller: hexToPublicKey(bi.spec.destinationCaller),
value: bi.spec.value,
salt: Buffer.from(bi.spec.salt.slice(2), "hex"),
hookDataLength: hookData.length,
hookData,
},
};
const buffer = Buffer.alloc(72 + 340 + hookData.length);
const bytesWritten = BurnIntentLayout.encode(prepared, buffer);
return buffer.subarray(0, bytesWritten);
}
function signBurnIntent(keypair: Keypair, payload: any): string {
const encoded = encodeBurnIntent(payload);
const prefixed = Buffer.concat([
Buffer.from([0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
encoded,
]);
const privateKey = crypto.createPrivateKey({
key: Buffer.concat([
Buffer.from("302e020100300506032b657004220420", "hex"),
Buffer.from(keypair.secretKey.slice(0, 32)),
]),
format: "der",
type: "pkcs8",
});
return `0x${crypto.sign(null, prefixed, privateKey).toString("hex")}`;
}
function addressToBytes32(address: string): string {
const decoded = Buffer.from(bs58.decode(address));
return `0x${decoded.toString("hex")}`;
}
function hexToPublicKey(hex: string): PublicKey {
return new PublicKey(Buffer.from(hex.slice(2), "hex"));
}
function decodeAttestationSet(attestation: string) {
const buffer = Buffer.from(attestation.slice(2), "hex");
return MintAttestationSetLayout.decode(buffer) as {
attestations: Array<{
destinationToken: PublicKey;
destinationRecipient: PublicKey;
transferSpecHash: Uint8Array;
}>;
};
}
function findCustodyPda(
mint: PublicKey,
minterProgramId: PublicKey,
): PublicKey {
return PublicKey.findProgramAddressSync(
[Buffer.from("gateway_minter_custody"), mint.toBuffer()],
minterProgramId,
)[0];
}
function findTransferSpecHashPda(
transferSpecHash: Uint8Array | Buffer,
minterProgramId: PublicKey,
): PublicKey {
return PublicKey.findProgramAddressSync(
[Buffer.from("used_transfer_spec_hash"), Buffer.from(transferSpecHash)],
minterProgramId,
)[0];
}
Step 4. Set up the environment and create the burn intent
Add the following code totransfer.ts to load keypairs from environment
variables, create a Solana connection, set up the recipient’s Associated Token
Account, and create and sign the burn intent.transfer.ts
Report incorrect code
Copy
if (!process.env.SOLANA_PRIVATE_KEYPAIR)
throw new Error("SOLANA_PRIVATE_KEYPAIR not set");
if (!process.env.RECIPIENT_KEYPAIR)
throw new Error("RECIPIENT_KEYPAIR not set");
const senderKeypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEYPAIR)),
);
const recipientKeypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(process.env.RECIPIENT_KEYPAIR)),
);
const connection = new Connection(RPC_ENDPOINT, "confirmed");
const usdcMint = new PublicKey(USDC_ADDRESS);
console.log(`Using account: ${senderKeypair.publicKey.toBase58()}`);
console.log(`Transferring from: Solana Devnet → Solana Devnet\n`);
// Create recipient's Associated Token Account
const recipientAta = getAssociatedTokenAddressSync(
usdcMint,
recipientKeypair.publicKey,
);
console.log("Creating recipient's Associated Token Account...");
const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(
senderKeypair.publicKey,
recipientAta,
recipientKeypair.publicKey,
usdcMint,
);
const tx = new Transaction().add(createAtaIx);
await sendAndConfirmTransaction(connection, tx, [senderKeypair]);
console.log(`Recipient ATA: ${recipientAta.toBase58()}\n`);
// Create and sign burn intent
console.log(`Creating burn intent from Solana Devnet → Solana Devnet...`);
const burnIntent = createBurnIntent({
sourceDepositor: senderKeypair.publicKey.toBase58(),
destinationRecipient: recipientAta.toBase58(),
sourceSigner: senderKeypair.publicKey.toBase58(),
});
const burnIntentSignature = signBurnIntent(senderKeypair, burnIntent);
const request = [{ burnIntent, signature: burnIntentSignature }];
console.log("Signed burn intent.\n");
Step 5. Submit to Gateway API and decode the attestation
Add the following code totransfer.ts to submit the burn intent to the Gateway
API, obtain an attestation, and decode the attestation set for use in the mint
operation.transfer.ts
Report incorrect code
Copy
console.log("Submitting to Gateway API...");
const response = await fetch(
"https://gateway-api-testnet.circle.com/v1/transfer",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request, (_key, value) =>
typeof value === "bigint" ? value.toString() : value,
),
},
);
const json = await response.json();
if (json.success === false) {
throw new Error(`Gateway API error: ${json.message}`);
}
console.log("Gateway API response:", JSON.stringify(json, null, 2));
const { attestation, signature: mintSignature } = json;
const decoded = decodeAttestationSet(attestation);
Step 6. Mint USDC on the destination chain
Add the following code totransfer.ts to set up the Anchor program, prepare
the required accounts, and call the Gateway Minter to mint USDC on Solana Devnet
using the attestation from the previous step.transfer.ts
Report incorrect code
Copy
const minterProgramId = new PublicKey(GATEWAY_MINTER_ADDRESS);
const anchorWallet = new Wallet(senderKeypair);
const provider = new AnchorProvider(
connection,
anchorWallet,
AnchorProvider.defaultOptions(),
);
setProvider(provider);
const minterProgram = new Program(gatewayMinterIdl, provider);
const [minterPda] = PublicKey.findProgramAddressSync(
[Buffer.from(utils.bytes.utf8.encode("gateway_minter"))],
minterProgramId,
);
// Mint on Solana
const remainingAccounts = decoded.attestations.flatMap((e) => [
{
pubkey: findCustodyPda(e.destinationToken, minterProgramId),
isWritable: true,
isSigner: false,
},
{ pubkey: e.destinationRecipient, isWritable: true, isSigner: false },
{
pubkey: findTransferSpecHashPda(e.transferSpecHash, minterProgramId),
isWritable: true,
isSigner: false,
},
]);
const attestationBytes = Buffer.from(attestation.slice(2), "hex");
const signatureBytes = Buffer.from(mintSignature.slice(2), "hex");
console.log("\nMinting funds on Solana Devnet...");
const mintTx = await minterProgram.methods
.gatewayMint({ attestation: attestationBytes, signature: signatureBytes })
.accountsPartial({
gatewayMinter: minterPda,
destinationCaller: senderKeypair.publicKey,
payer: senderKeypair.publicKey,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(remainingAccounts)
.signers([senderKeypair])
.rpc();
// Wait for confirmation
const latest = await connection.getLatestBlockhash();
await connection.confirmTransaction(
{
signature: mintTx,
blockhash: latest.blockhash,
lastValidBlockHeight: latest.lastValidBlockHeight,
},
"confirmed",
);
console.log(`\nMinted ${Number(TRANSFER_VALUE) / 1_000_000} USDC`);
console.log(`Mint transaction hash:`, mintTx);
Step 7. Run the script
Run the script with the following command:Report incorrect code
Copy
npx tsx --env-file=.env transfer.ts