Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cspell-custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@ behaviour
Berachain
bigset
Bigtable
Blackhole
blackhole
Blackhole
blackholed
Blackholed
blackholing
borsh
bscscan
BUILDKIT
bytecodes
callstack
Blackhole
Blackholed
blackholing
blackholed
ccqlistener
CCTP
celestia
Expand All @@ -56,6 +54,7 @@ Cyfrin
datagram
denoms
devnet
Dogecoin
dymension
Dymension
ethcrypto
Expand Down Expand Up @@ -105,6 +104,7 @@ Keccak
kevm
KEVM
keymap
keypair
keytool
klaytn
Klaytn
Expand Down Expand Up @@ -169,8 +169,8 @@ readyz
regen
reinit
reobservation
reobservations
Reobservation
reobservations
Reobservations
reobserved
repoint
Expand Down Expand Up @@ -240,8 +240,8 @@ wormchaind
Wormholescan
wormscan
wormscanurl
XFER
xlayer
xpla
XFER
XPLA
Zellic
6 changes: 6 additions & 0 deletions testing/dogecoin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*.json
!package.json
!tsconfig.json
.env
.env.*
node_modules
87 changes: 87 additions & 0 deletions testing/dogecoin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Dogecoin Multisig Bridge Test

This folder contains a script to test deposits and withdrawals via the Wormhole testnet guardian's 5-of-7 multisig Dogecoin Delegated Manager Set.

## Integrator Notes

Integrators may use Executor to relay their `UTX0` VAA v1 transfers to Dogecoin.

For an on-chain integration on Solana, this flow might look like:

1. Off-chain, call the Executor API to get a signed quote from Solana to Dogecoin.
2. Pass that quote to your on-chain program which emits the Wormhole message.
3. Within you program, after emitting a message, call `request_for_execution` on the Executor contract. This requires constructing a [VAA v1 request](https://github.com/wormholelabs-xyz/example-messaging-executor/blob/2061262868ed420e911a54ef619dd9b00949beb1/svm/modules/executor-requests/src/lib.rs#L7) for the VAA ID (chain, emitter, sequence number) that you just emitted.
4. Off-chain, status the request via the Executor API.

See [withdraw-testnet-executor.ts](./withdraw-testnet-executor.ts) and the [Executor Integration Notes](https://wormholelabs.notion.site/Executor-Integration-Notes-Public-1bd3029e88cb804e8281ec19e3264c3b) for more information.

## Testing Artifacts

https://doge-testnet-explorer.qed.me/tx/8d55cbffc83f27ec16fb3da9c550857533169233c6683f8d97986b14b928de81
https://doge-testnet-explorer.qed.me/tx/2916f3063844f0e2721f81041c3dc2ac02790d24ce3a1b31b8c1fb2e908d52c0

### Testnet Executor Example

https://wormholelabs-xyz.github.io/executor-explorer/#/chain/1/tx/4yfrbt7q6fyW4MPojAkxmN74cH6hwcNA5to1qJZB2A14Z1Sc77URTLF9HaKMd9aFckgqSyVgD5kNqX9VpNxxHEU2?endpoint=https%3A%2F%2Fexecutor-testnet.labsapis.com&env=Testnet

## Prerequisites

### Bun

```bash
curl -fsSL https://bun.sh/install | bash
```

### Install Dependencies

```bash
bun ci
```

## Dogecoin Testnet Wallet (dogecoin-keygen.ts)

Generates a Dogecoin testnet keypair with WIF-encoded private key and P2PKH address.

```bash
bun run dogecoin-keygen.ts
```

## Solana Devnet Wallet (solana-keygen.ts)

Generates a Solana devnet keypair.

```bash
bun run solana-keygen.ts
```

Note: As the Delegated Manager Set feature is currently permissioned, in order for this test to work, your Solana devnet address has to be in the allowlist of the running testnet guardian.

## Testnet Deposit Script (deposit-testnet.ts)

Deposits DOGE to a P2SH multisig address controlled by the delegated manager set. The script builds a custom redeem script with embedded metadata (emitter chain, emitter contract, sub-address seed) and creates a 5-of-7 multisig.

### Prerequisites

- `dogecoin-testnet-keypair.json` - Funded Dogecoin wallet (from `dogecoin-keygen.ts`)
- `solana-devnet-keypair.json` - Solana keypair for emitter contract address (from `solana-keygen.ts`)

### Usage

```bash
bun run deposit-testnet.ts
```

## Testnet Withdraw Script (withdraw-testnet.ts)

Withdraws DOGE from the P2SH multisig address back to the original sender. Collects 5-of-7 signatures from the delegated manager set to satisfy the multisig requirement.

### Prerequisites

- `solana-devnet-keypair.json` - Same Solana keypair used for deposit
- `deposit-info.json` - Created by running `deposit.ts`

### Usage

```bash
bun run withdraw-testnet.ts
```
227 changes: 227 additions & 0 deletions testing/dogecoin/bun.lock

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions testing/dogecoin/deposit-testnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import * as bitcoin from "bitcoinjs-lib";
import { loadManagerKeys } from "./manager";
import {
ECPair,
EMITTER_CHAIN,
buildRedeemScript,
dogecoinTestnet,
loadSolanaEmitterContract,
} from "./redeem-script";
import {
DOGECOIN_CHAIN_ID,
FEE,
KOINU_PER_DOGE,
broadcastTx,
explorerTxUrl,
fetchRawTx,
fetchUtxos,
} from "./shared";

// Load emitter contract and manager keys
const emitterContract = await loadSolanaEmitterContract();

const {
mThreshold,
nTotal,
pubkeys: managerPubkeys,
} = await loadManagerKeys(DOGECOIN_CHAIN_ID);

console.log("\nManager keys:");
for (let i = 0; i < managerPubkeys.length; i++) {
console.log(` ${i}: ${managerPubkeys[i]!.toString("hex")}`);
}

// Build the redeem script (using Solana pubkey as recipient_address)
const redeemScript = buildRedeemScript({
emitterChain: EMITTER_CHAIN,
emitterContract,
recipientAddress: emitterContract, // Use Solana public key as recipient_address
managerPubkeys,
mThreshold,
nTotal,
});

console.log("\nRedeem script length:", redeemScript.length, "bytes");
console.log("Redeem script (hex):", redeemScript.toString("hex"));

// Generate P2SH address from redeem script
const p2sh = bitcoin.payments.p2sh({
redeem: { output: new Uint8Array(redeemScript) },
network: dogecoinTestnet,
});

console.log("\nP2SH Address:", p2sh.address);
console.log(
"Script Hash:",
p2sh.hash ? Buffer.from(p2sh.hash).toString("hex") : undefined,
);

// Read sender's keypair
const senderKeypair = await Bun.file("dogecoin-testnet-keypair.json").json();
const senderKeyPair = ECPair.fromWIF(
senderKeypair.privateKeyWIF,
dogecoinTestnet,
);
const senderAddress = senderKeypair.address;

console.log("\nSender address:", senderAddress);

// Amount to send (in koinu) - 1 DOGE = 100,000,000 koinu
const AMOUNT_TO_SEND = KOINU_PER_DOGE; // 1 DOGE

console.log("\nFetching UTXOs...");
const utxos = await fetchUtxos(senderAddress);
console.log(`Found ${utxos.length} UTXOs`);

if (utxos.length === 0) {
console.error("No UTXOs available. Please fund the sender address first.");
console.log(`Get testnet coins from: https://faucet.doge.toys/`);
process.exit(1);
}

// Calculate total available
const totalAvailable = utxos.reduce(
(sum: number, utxo: any) => sum + utxo.value,
0,
);
console.log(`Total available: ${totalAvailable / KOINU_PER_DOGE} DOGE`);

if (totalAvailable < AMOUNT_TO_SEND + FEE) {
console.error(
`Insufficient funds. Need ${(AMOUNT_TO_SEND + FEE) / KOINU_PER_DOGE} DOGE`,
);
process.exit(1);
}

// Build transaction
const psbt = new bitcoin.Psbt({ network: dogecoinTestnet });

// Add inputs
let inputSum = 0;
for (const utxo of utxos) {
// Fetch raw transaction for the UTXO
const rawTxHex = await fetchRawTx(utxo.txid);

psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
nonWitnessUtxo: new Uint8Array(Buffer.from(rawTxHex, "hex")),
});

inputSum += utxo.value;
if (inputSum >= AMOUNT_TO_SEND + FEE) break;
}

// Add output to P2SH address
psbt.addOutput({
address: p2sh.address!,
value: BigInt(AMOUNT_TO_SEND),
});

// Add change output if needed
const change = inputSum - AMOUNT_TO_SEND - FEE;
if (change > 0) {
psbt.addOutput({
address: senderAddress,
value: BigInt(change),
});
}

// Sign all inputs
psbt.signAllInputs(senderKeyPair);
psbt.finalizeAllInputs();

// Extract and broadcast
const tx = psbt.extractTransaction();
const txHex = tx.toHex();

console.log("\nTransaction hex:", txHex);
console.log("Transaction ID:", tx.getId());

console.log("\nBroadcasting transaction...");
try {
const txid = await broadcastTx(txHex);
console.log("Transaction broadcast successfully!");
console.log("TXID:", txid);
console.log(`\nView on explorer: ${explorerTxUrl(txid)}`);

// Save deposit info
const depositInfo = {
txid: tx.getId(),
amount: AMOUNT_TO_SEND,
p2shAddress: p2sh.address,
redeemScript: redeemScript.toString("hex"),
senderAddress,
timestamp: new Date().toISOString(),
};
await Bun.write("deposit-info.json", JSON.stringify(depositInfo, null, 2));
console.log("\nDeposit info saved to deposit-info.json");
} catch (error) {
console.error("Broadcast failed:", error);
}
50 changes: 50 additions & 0 deletions testing/dogecoin/dogecoin-keygen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { ECPairFactory } from "ecpair";

bitcoin.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);

// Dogecoin Testnet network parameters
const dogecoinTestnet: bitcoin.Network = {
messagePrefix: "\x19Dogecoin Signed Message:\n",
bech32: "tdge",
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x71, // addresses start with 'n'
scriptHash: 0xc4,
wif: 0xf1, // WIF prefix for testnet
};

// Generate a new keypair
const keyPair = ECPair.makeRandom({ network: dogecoinTestnet });

// Get private key in WIF format
const privateKeyWIF = keyPair.toWIF();

// Get public key
const publicKey = Buffer.from(keyPair.publicKey).toString("hex");

// Generate address (P2PKH)
const { address } = bitcoin.payments.p2pkh({
pubkey: new Uint8Array(Buffer.from(keyPair.publicKey)),
network: dogecoinTestnet,
});

// Save to JSON file
const keypairData = {
network: "dogecoin-testnet",
privateKeyWIF,
publicKey,
address,
generatedAt: new Date().toISOString(),
};

await Bun.write(
"dogecoin-testnet-keypair.json",
JSON.stringify(keypairData, null, 2),
);

console.log("Keypair saved to dogecoin-testnet-keypair.json");
Loading
Loading