Skip to content

Commit

Permalink
executeMultiChainMsg
Browse files Browse the repository at this point in the history
  • Loading branch information
thal0x committed Aug 29, 2023
1 parent 078aaf2 commit b75dc49
Show file tree
Hide file tree
Showing 11 changed files with 1,423 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ jobs:
run: npm ci

- name: Setup starship
run: npm run e2e:setup
run: npm run e2e:setup HELM_FILE=starship/ci.yaml

- name: Start starship
run: make start
run: make start HELM_FILE=starship/ci.yaml

- name: Run e2e tests
run: npm run e2e:test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
HELM_NAME = skip-router
HELM_FILE = starship.yaml
HELM_FILE = config/local.yaml
HELM_REPO = starship
HELM_CHART = devnet
HELM_VERSION = 0.1.38
Expand Down
898 changes: 872 additions & 26 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@
"e2e:setup": "make setup && make setup-kind",
"e2e:stop": "make stop",
"e2e:clean": "make stop clean",
"e2e:test": "jest --testPathPattern=tests/ --runInBand --verbose --bail"
"e2e:test": "jest --testPathPattern=tests/ --runInBand --verbose --bail --testTimeout=30000"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.4.0"
"@cosmjs/amino": "^0.31.1",
"@cosmjs/cosmwasm-stargate": "^0.31.1",
"@cosmjs/encoding": "^0.31.1",
"@cosmjs/math": "^0.31.1",
"@cosmjs/proto-signing": "^0.31.1",
"@cosmjs/stargate": "^0.31.1",
"@cosmjs/tendermint-rpc": "^0.31.1",
"axios": "^1.4.0",
"cosmjs-types": "^0.8.0"
},
"devDependencies": {
"@types/jest": "^29.5.3",
Expand Down
268 changes: 268 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
import {
encodeSecp256k1Pubkey,
makeSignDoc as makeSignDocAmino,
OfflineAminoSigner,
} from "@cosmjs/amino";
import { createWasmAminoConverters } from "@cosmjs/cosmwasm-stargate";
import { fromBase64 } from "@cosmjs/encoding";
import { Int53 } from "@cosmjs/math";
import {
Coin,
encodePubkey,
isOfflineDirectSigner,
makeAuthInfoBytes,
makeSignDoc,
OfflineDirectSigner,
OfflineSigner,
Registry,
TxBodyEncodeObject,
} from "@cosmjs/proto-signing";
import {
AminoTypes,
createDefaultAminoConverters,
defaultRegistryTypes,
SignerData,
SigningStargateClient,
StdFee,
} from "@cosmjs/stargate";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";

import { RequestClient } from "./request-client";
import {
getAccountNumberAndSequence,
getEncodeObjectFromMultiChainMessage,
getGasAmountForMessage,
} from "./transactions";
import {
Asset,
assetFromJSON,
Expand Down Expand Up @@ -50,8 +86,22 @@ export const SKIP_API_URL = "https://api.skip.money/v1";
export class SkipAPIClient {
private requestClient: RequestClient;

private aminoTypes: AminoTypes;
private registry: Registry;

constructor(apiURL: string) {
this.requestClient = new RequestClient(apiURL);

this.aminoTypes = new AminoTypes({
...createDefaultAminoConverters(),
...createWasmAminoConverters(),
});

this.registry = new Registry(defaultRegistryTypes);
this.registry.register(
"/cosmwasm.wasm.v1.MsgExecuteContract",
MsgExecuteContract,
);
}

async assets(options: AssetsRequest = {}): Promise<Record<string, Asset[]>> {
Expand Down Expand Up @@ -98,6 +148,224 @@ export class SkipAPIClient {
return response.chains.map((chain) => chainFromJSON(chain));
}

async executeMultiChainMessage(
signerAddress: string,
signer: OfflineSigner,
message: MultiChainMsg,
feeAmount: Coin,
options: {
rpcEndpoint: string;
},
) {
const accounts = await signer.getAccounts();
const accountFromSigner = accounts.find(
(account) => account.address === signerAddress,
);

if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}

const { accountNumber, sequence } = await getAccountNumberAndSequence(
signerAddress,
options.rpcEndpoint,
);

const gas = getGasAmountForMessage(message);

let rawTx: TxRaw;
if (isOfflineDirectSigner(signer)) {
rawTx = await this.signMultiChainMessageDirect(
signerAddress,
signer,
message,
{
amount: [feeAmount],
gas,
},
{ accountNumber, sequence, chainId: message.chainID },
);
} else {
rawTx = await this.signMultiChainMessageAmino(
signerAddress,
signer,
message,
{
amount: [feeAmount],
gas,
},
{ accountNumber, sequence, chainId: message.chainID },
);
}

const txBytes = TxRaw.encode(rawTx).finish();

const stargateClient = await SigningStargateClient.connectWithSigner(
options.rpcEndpoint,
signer,
);

const tx = await stargateClient.broadcastTx(txBytes);

return tx;
}

async signMultiChainMessageDirect(
signerAddress: string,
signer: OfflineDirectSigner,
multiChainMessage: MultiChainMsg,
fee: StdFee,
{ accountNumber, sequence, chainId }: SignerData,
): Promise<TxRaw> {
// TODO: Uncomment when EVMOS and Injective are supported
// if (multiChainMessage.chain_id.includes("evmos")) {
// return this.signMultiChainMessageDirectEvmos(
// signerAddress,
// signer,
// multiChainMessage,
// fee,
// { accountNumber, sequence, chainId }
// );
// }

// if (multiChainMessage.chain_id.includes("injective")) {
// return this.signMultiChainMessageDirectInjective(
// signerAddress,
// signer,
// multiChainMessage,
// fee,
// { accountNumber, sequence, chainId }
// );
// }

const accounts = await signer.getAccounts();
const accountFromSigner = accounts.find(
(account) => account.address === signerAddress,
);

if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}

const message = getEncodeObjectFromMultiChainMessage(multiChainMessage);

const pubkey = encodePubkey(
encodeSecp256k1Pubkey(accountFromSigner.pubkey),
);

const txBodyEncodeObject: TxBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: {
messages: [message],
},
};

const txBodyBytes = this.registry.encode(txBodyEncodeObject);

const gasLimit = Int53.fromString(fee.gas).toNumber();

const authInfoBytes = makeAuthInfoBytes(
[{ pubkey, sequence }],
fee.amount,
gasLimit,
fee.granter,
fee.payer,
);

const signDoc = makeSignDoc(
txBodyBytes,
authInfoBytes,
chainId,
accountNumber,
);

const { signature, signed } = await signer.signDirect(
signerAddress,
signDoc,
);

return TxRaw.fromPartial({
bodyBytes: signed.bodyBytes,
authInfoBytes: signed.authInfoBytes,
signatures: [fromBase64(signature.signature)],
});
}

async signMultiChainMessageAmino(
signerAddress: string,
signer: OfflineAminoSigner,
multiChainMessage: MultiChainMsg,
fee: StdFee,
{ accountNumber, sequence, chainId }: SignerData,
): Promise<TxRaw> {
const accounts = await signer.getAccounts();
const accountFromSigner = accounts.find(
(account) => account.address === signerAddress,
);

if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}

const message = getEncodeObjectFromMultiChainMessage(multiChainMessage);

const pubkey = encodePubkey(
encodeSecp256k1Pubkey(accountFromSigner.pubkey),
);

const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;

const msgs = [this.aminoTypes.toAmino(message)];

msgs[0].value.memo = message.value.memo;

const signDoc = makeSignDocAmino(
msgs,
fee,
chainId,
"",
accountNumber,
sequence,
);

const { signature, signed } = await signer.signAmino(
signerAddress,
signDoc,
);

const signedTxBody = {
messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)),
memo: signed.memo,
};

signedTxBody.messages[0].value.memo = message.value.memo;

const signedTxBodyEncodeObject: TxBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: signedTxBody,
};

const signedTxBodyBytes = this.registry.encode(signedTxBodyEncodeObject);

const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber();
const signedSequence = Int53.fromString(signed.sequence).toNumber();

const signedAuthInfoBytes = makeAuthInfoBytes(
[{ pubkey, sequence: signedSequence }],
signed.fee.amount,
signedGasLimit,
signed.fee.granter,
signed.fee.payer,
signMode,
);

return TxRaw.fromPartial({
bodyBytes: signedTxBodyBytes,
authInfoBytes: signedAuthInfoBytes,
signatures: [fromBase64(signature.signature)],
});
}

async messages(options: MsgsRequest): Promise<MultiChainMsg[]> {
const response = await this.requestClient.post<
MsgsResponseJSON,
Expand Down
Loading

0 comments on commit b75dc49

Please sign in to comment.