Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Aptos CCTP support #774

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions core/base/src/constants/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const usdcContracts = [[
["Base", "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
["Polygon", "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"],
["Sui", "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC"],
["Aptos", "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b"]
]], [
"Testnet", [
["Sepolia", "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"],
Expand All @@ -30,6 +31,7 @@ const usdcContracts = [[
["BaseSepolia", "0x036CbD53842c5426634e7929541eC2318f3dCF7e"],
["Polygon", "0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97"],
["Sui", "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC"],
["Aptos", "0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832"]
]],
] as const satisfies MapLevel<Network, MapLevel<Chain, string>>;
export const usdcContract = constMap(usdcContracts);
Expand All @@ -46,6 +48,7 @@ const circleDomains = [[
["Base", 6],
["Polygon", 7],
["Sui", 8],
["Aptos", 9],
]], [
"Testnet", [
["Sepolia", 0],
Expand All @@ -56,6 +59,7 @@ const circleDomains = [[
["BaseSepolia", 6],
["Polygon", 7],
["Sui", 8],
["Aptos", 9],
]],
] as const satisfies MapLevel<Network, MapLevel<Chain, number>>;

Expand Down
16 changes: 14 additions & 2 deletions core/base/src/constants/contracts/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,14 @@ export const circleContracts = [[
messageTransmitter: "0x08d87d37ba49e785dde270a83f8e979605b03dc552b5548f26fdf2f49bf7ed1b",
wormholeRelayer: "",
wormhole: "",
}], [
"Aptos", {
tokenMessenger: "0x9e6702a472080ea3caaf6ba9dfaa6effad2290a9ba9adaacd5af5c618e42782d",
messageTransmitter: "0x177e17751820e4b4371873ca8c30279be63bdea63b88ed0f2239c2eea10f1772",
wormholeRelayer: "",
wormhole: "",
}],
]], [
]], [
"Testnet", [[
"Sepolia", {
tokenMessenger: "0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5",
Expand Down Expand Up @@ -109,6 +115,12 @@ export const circleContracts = [[
messageTransmitter: "0x4931e06dce648b3931f890035bd196920770e913e43e45990b383f6486fdd0a5",
wormholeRelayer: "",
wormhole: "",
}], [
"Aptos", {
tokenMessenger: "0x5f9b937419dda90aa06c1836b7847f65bbbe3f1217567758dc2488be31a477b9",
messageTransmitter: "0x081e86cebf457a0c6004f35bd648a2794698f52e0dde09a48619dcd3d4cc23d9",
wormholeRelayer: "",
wormhole: "",
}],
]],
]],
] as const satisfies MapLevels<[Network, Chain, CircleContracts]>;
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"platforms/aptos",
"platforms/aptos/protocols/core",
"platforms/aptos/protocols/tokenBridge",
"platforms/aptos/protocols/cctp",
"sdk",
"examples"
],
Expand Down
75 changes: 75 additions & 0 deletions platforms/aptos/protocols/cctp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"name": "@wormhole-foundation/sdk-aptos-cctp",
"version": "1.0.3",
"repository": {
"type": "git",
"url": "git+https://github.com/wormhole-foundation/wormhole-sdk-ts.git"
},
"bugs": {
"url": "https://github.com/wormhole-foundation/wormhole-sdk-ts/issues"
},
"homepage": "https://github.com/wormhole-foundation/wormhole-sdk-ts#readme",
"directories": {
"test": "tests"
},
"license": "Apache-2.0",
"main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts",
"module": "./dist/esm/index.js",
"description": "SDK for Aptos chains, used in conjunction with @wormhole-foundation/sdk",
"files": [
"dist/esm",
"dist/cjs"
],
"keywords": [
"wormhole",
"sdk",
"typescript",
"connect",
"aptos"
],
"engines": {
"node": ">=16"
},
"sideEffects": [
"./dist/cjs/index.js",
"./dist/esm/index.js"
],
"scripts": {
"build:cjs": "tsc -p ./tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
"build:esm": "tsc -p ./tsconfig.esm.json",
"build": "npm run build:esm && npm run build:cjs",
"rebuild": "npm run clean && npm run build",
"clean": "rm -rf ./dist && rm -rf ./.turbo",
"lint": "npm run prettier && eslint --fix ./src --ext .ts",
"prettier": "prettier --write ./src"
},
"dependencies": {
"@aptos-labs/ts-sdk": "^1.33.1",
"@wormhole-foundation/sdk-connect": "1.0.3",
"@wormhole-foundation/sdk-aptos": "1.0.3"
},
"type": "module",
"exports": {
".": {
"react-native": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
},
"default": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
}
}
}
}
186 changes: 186 additions & 0 deletions platforms/aptos/protocols/cctp/src/circleBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {
AccountAddress as AptosAccountAddress,
Aptos,
InputGenerateTransactionPayloadData,
InputScriptData,
MoveVector,
U32,
U64,
UserTransactionResponse,
} from "@aptos-labs/ts-sdk";
import {
AptosPlatform,
AptosUnsignedTransaction,
type AptosChains,
} from "@wormhole-foundation/sdk-aptos";
import type {
AccountAddress,
ChainAddress,
ChainsConfig,
Network,
Platform,
} from "@wormhole-foundation/sdk-connect";
import {
CircleBridge,
CircleTransferMessage,
circle,
Contracts,
encoding,
keccak256,
} from "@wormhole-foundation/sdk-connect";
import { AptosCCTPMoveScripts, aptosCCTPMoveScripts } from "./moveScripts.js";

export class AptosCircleBridge<N extends Network, C extends AptosChains>
implements CircleBridge<N, C>
{
readonly usdcId: string;
readonly tokenMessengerId: string;
readonly messageTransmitterId: string;
readonly moveScripts: AptosCCTPMoveScripts;

constructor(
readonly network: N,
readonly chain: C,
readonly provider: Aptos,
readonly contracts: Contracts,
) {
if (network === "Devnet") throw new Error("CircleBridge not supported on Devnet");

const usdcId = circle.usdcContract.get(this.network, this.chain);
if (!usdcId) {
throw new Error(
`No USDC contract configured for network=${this.network} chain=${this.chain}`,
);
}

if (!contracts.cctp?.tokenMessenger)
throw new Error(`Circle Token Messenger contract for domain ${chain} not found`);

if (!contracts.cctp?.messageTransmitter)
throw new Error(`Circle Message Transmitter contract for domain ${chain} not found`);

if (!aptosCCTPMoveScripts.has(network)) throw new Error("No Aptos CCTP move scripts found");

this.usdcId = usdcId;
this.tokenMessengerId = contracts.cctp?.tokenMessenger;
this.messageTransmitterId = contracts.cctp.messageTransmitter;
this.moveScripts = aptosCCTPMoveScripts.get(network)!;
}

async *transfer(
sender: AccountAddress<C>,
recipient: ChainAddress,
amount: bigint,
): AsyncGenerator<AptosUnsignedTransaction<N, C>> {
const destinationDomain = new U32(circle.circleChainId.get(this.network, recipient.chain)!);
const burnToken = AptosAccountAddress.from(this.usdcId);
const mintRecipient = AptosAccountAddress.from(
recipient.address.toUniversalAddress().toUint8Array(),
);

const functionArguments = [new U64(amount), destinationDomain, mintRecipient, burnToken];

const txData: InputScriptData = {
bytecode: this.moveScripts.depositForBurn,
functionArguments,
};

yield this.createUnsignedTx(txData, "Aptos.CircleBridge.Transfer");
}

async isTransferCompleted(message: CircleBridge.Message): Promise<boolean> {
const sourceBytes = new U32(message.sourceDomain).bcsToBytes();
const nonceBytes = new U64(message.nonce).bcsToBytes();
const hash = keccak256(new Uint8Array([...sourceBytes, "-".charCodeAt(0), ...nonceBytes]));
const hashStr = encoding.hex.encode(hash);

const isNonceUsed = await this.provider.view<[boolean]>({
payload: {
function: `${this.messageTransmitterId}::message_transmitter::is_nonce_used`,
functionArguments: [hashStr],
},
});

return isNonceUsed[0];
}

async *redeem(
sender: AccountAddress<C>,
message: CircleBridge.Message,
attestation: string,
): AsyncGenerator<AptosUnsignedTransaction<N, C>> {
const functionArguments = [
MoveVector.U8(CircleBridge.serialize(message)),
MoveVector.U8(encoding.hex.decode(attestation)),
];

const txData: InputScriptData = {
bytecode: this.moveScripts.handleReceiveMessage,
functionArguments,
};

yield this.createUnsignedTx(txData, "Aptos.CircleBridge.Redeem");
}

async parseTransactionDetails(digest: string): Promise<CircleTransferMessage> {
const tx = await this.provider.getTransactionByHash({ transactionHash: digest });

const messageTransmitterId = this.messageTransmitterId.replace(/^0x0+/, "0x"); // remove any leading zeros to match event
const circleMessageSentEvent = (tx as UserTransactionResponse).events?.find(
(e) => e.type === `${messageTransmitterId}::message_transmitter::MessageSent`,
);

if (!circleMessageSentEvent) {
throw new Error("No MessageSent event found");
}

const circleMessage = encoding.hex.decode(circleMessageSentEvent.data.message);

const [msg, hash] = CircleBridge.deserialize(circleMessage);
const { payload } = msg;

const xferSender = payload.messageSender;
const xferReceiver = payload.mintRecipient;

const sendChain = circle.toCircleChain(this.network, msg.sourceDomain);
const rcvChain = circle.toCircleChain(this.network, msg.destinationDomain);

const token = { chain: sendChain, address: payload.burnToken };

return {
from: { chain: sendChain, address: xferSender },
to: { chain: rcvChain, address: xferReceiver },
token: token,
amount: payload.amount,
message: msg,
id: { hash },
};
}

static async fromRpc<N extends Network>(
provider: Aptos,
config: ChainsConfig<N, Platform>,
): Promise<AptosCircleBridge<N, AptosChains>> {
const [network, chain] = await AptosPlatform.chainFromRpc(provider);
const conf = config[chain]!;
if (conf.network !== network) {
throw new Error(`Network mismatch: ${conf.network} != ${network}`);
}

return new AptosCircleBridge(network as N, chain, provider, conf.contracts);
}

private createUnsignedTx(
txReq: InputGenerateTransactionPayloadData,
description: string,
parallelizable: boolean = false,
): AptosUnsignedTransaction<N, C> {
return new AptosUnsignedTransaction(
txReq,
this.network,
this.chain,
description,
parallelizable,
);
}
}
6 changes: 6 additions & 0 deletions platforms/aptos/protocols/cctp/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerProtocol } from "@wormhole-foundation/sdk-connect";

import { AptosCircleBridge } from "./circleBridge.js";

registerProtocol("Aptos", "CircleBridge", AptosCircleBridge);
export * from "./circleBridge.js";
Loading
Loading