Skip to content

Commit 12129db

Browse files
committed
feat: cosmos
1 parent 3840134 commit 12129db

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed

packages/sign-client/src/constants/engine.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,9 @@ export const TVF_METHODS = {
228228
polkadot_signTransaction: {
229229
key: "",
230230
},
231+
232+
// cosmos
233+
cosmos_signDirect: {
234+
key: "",
235+
},
231236
};

packages/sign-client/src/controllers/engine.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {
104104
getNearTransactionIdFromSignedTransaction,
105105
getAlgorandTransactionId,
106106
buildSignedExtrinsicHash,
107+
getSignDirectHash,
107108
} from "@walletconnect/utils";
108109
import EventEmmiter from "events";
109110
import {
@@ -3192,6 +3193,10 @@ export class Engine extends IEngine {
31923193
: [getAlgorandTransactionId(result)];
31933194
}
31943195

3196+
if (method === "cosmos_signDirect") {
3197+
return [getSignDirectHash(result)];
3198+
}
3199+
31953200
// result = 0x...
31963201
if (typeof result === "string") {
31973202
return [result];

packages/sign-client/test/sdk/client.spec.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,11 @@ describe("Sign Client Integration", () => {
692692
events: [],
693693
chains: ["polkadot:mainnet"],
694694
},
695+
cosmos: {
696+
methods: ["cosmos_signDirect"],
697+
events: [],
698+
chains: ["cosmos:mainnet"],
699+
},
695700
},
696701
namespaces: {
697702
solana: {
@@ -767,6 +772,12 @@ describe("Sign Client Integration", () => {
767772
chains: ["polkadot:mainnet"],
768773
accounts: ["polkadot:mainnet:0x"],
769774
},
775+
cosmos: {
776+
methods: ["cosmos_signDirect"],
777+
events: [],
778+
chains: ["cosmos:mainnet"],
779+
accounts: ["cosmos:mainnet:0x"],
780+
},
770781
},
771782
},
772783
);
@@ -2311,6 +2322,102 @@ describe("Sign Client Integration", () => {
23112322
}),
23122323
]);
23132324

2325+
// cosmos cosmos_signDirect example
2326+
await Promise.all([
2327+
new Promise<void>((resolve) => {
2328+
clients.B.once("session_request", async (args) => {
2329+
const pendingRequests = clients.B.pendingRequest.getAll();
2330+
const { id, topic, params } = pendingRequests[0];
2331+
expect(params).toEqual(args.params);
2332+
expect(topic).toEqual(args.topic);
2333+
expect(id).toEqual(args.id);
2334+
const expectedTxHashes = [
2335+
"A7284BA475C55983E5BCB7D52F5C82CBFF19FD75725F5E0E33BA4384FCFC6052",
2336+
];
2337+
const signedResult = {
2338+
signature: {
2339+
pub_key: {
2340+
type: "tendermint/PubKeySecp256k1",
2341+
value: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW",
2342+
},
2343+
signature:
2344+
"S7BJEbiXQ6vxvF9o4Wj7qAcocMQqBsqz+NVH4wilhidFsRpyqpSP5XiXoQZxTDrT9uET/S5SH6+5gUmjYntH/Q==",
2345+
},
2346+
signed: {
2347+
chainId: "cosmoshub-4",
2348+
accountNumber: "1",
2349+
authInfoBytes:
2350+
"ClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDNOXj4u60JFq00+VbLBCNBTYy76Pn864AvYNFG/9cQwMSBAoCCH8YAhITCg0KBXVhdG9tEgQ0NTM1EIaJCw==",
2351+
bodyBytes:
2352+
"CpoICikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchLsBwoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxODg4MDYiLWNvc21vczFhanBkZndsZmRqY240MG5yZXN5ZHJxazRhOGo2ZG0wemY0MGszcSo/b3NtbzEwYTNrNGh2azM3Y2M0aG54Y3R3NHA5NWZoc2NkMno2aDJybXgwYXVrYzZybTh1OXFxeDlzbWZzaDd1MgcIARDFt5YRQsgGeyJ3YXNtIjp7ImNvbnRyYWN0Ijoib3NtbzEwYTNrNGh2azM3Y2M0aG54Y3R3NHA5NWZoc2NkMno2aDJybXgwYXVrYzZybTh1OXFxeDlzbWZzaDd1IiwibXNnIjp7InN3YXBfYW5kX2FjdGlvbiI6eyJ1c2VyX3N3YXAiOnsic3dhcF9leGFjdF9hc3NldF9pbiI6eyJzd2FwX3ZlbnVlX25hbWUiOiJvc21vc2lzLXBvb2xtYW5hZ2VyIiwib3BlcmF0aW9ucyI6W3sicG9vbCI6IjE0MDAiLCJkZW5vbV9pbiI6ImliYy8yNzM5NEZCMDkyRDJFQ0NENTYxMjNDNzRGMzZFNEMxRjkyNjAwMUNFQURBOUNBOTdFQTYyMkIyNUY0MUU1RUIyIiwiZGVub21fb3V0IjoidW9zbW8ifSx7InBvb2wiOiIxMzQ3IiwiZGVub21faW4iOiJ1b3NtbyIsImRlbm9tX291dCI6ImliYy9ENzlFN0Q4M0FCMzk5QkZGRjkzNDMzRTU0RkFBNDgwQzE5MTI0OEZDNTU2OTI0QTJBODM1MUFFMjYzOEIzODc3In1dfX0sIm1pbl9hc3NldCI6eyJuYXRpdmUiOnsiZGVub20iOiJpYmMvRDc5RTdEODNBQjM5OUJGRkY5MzQzM0U1NEZBQTQ4MEMxOTEyNDhGQzU1NjkyNEEyQTgzNTFBRTI2MzhCMzg3NyIsImFtb3VudCI6IjMzOTQ2NyJ9fSwidGltZW91dF90aW1lc3RhbXAiOjE3NDc3NDY3MzM3OTU4OTgzNjQsInBvc3Rfc3dhcF9hY3Rpb24iOnsiaWJjX3RyYW5zZmVyIjp7ImliY19pbmZvIjp7InNvdXJjZV9jaGFubmVsIjoiY2hhbm5lbC02OTk0IiwicmVjZWl2ZXIiOiJjZWxlc3RpYTFhanBkZndsZmRqY240MG5yZXN5ZHJxazRhOGo2ZG0wemNsN3h0ZCIsIm1lbW8iOiIiLCJyZWNvdmVyX2FkZHJlc3MiOiJvc21vMWFqcGRmd2xmZGpjbjQwbnJlc3lkcnFrNGE4ajZkbTB6cHd1eDhqIn19fSwiYWZmaWxpYXRlcyI6W119fX19",
2353+
},
2354+
};
2355+
2356+
const result = formatJsonRpcResult(id, signedResult);
2357+
2358+
let checkedWalletPublish = false;
2359+
clients.B.core.relayer.once(RELAYER_EVENTS.publish, (publishPayload: any) => {
2360+
const tvf = publishPayload.tvf;
2361+
if (!tvf) {
2362+
return console.error("cosmos tvf is undefined");
2363+
}
2364+
if (!tvf.chainId || !tvf.rpcMethods || !tvf.txHashes) {
2365+
return console.error("cosmos tvf is missing required fields");
2366+
}
2367+
if (tvf.rpcMethods.length !== 1 && tvf.rpcMethods[0] !== "cosmos_signDirect") {
2368+
return console.error("cosmos tvf rpcMethods is invalid", tvf.rpcMethods);
2369+
}
2370+
if (tvf.txHashes.join(",") !== expectedTxHashes.join(",")) {
2371+
return console.error(
2372+
"cosmos txHashes do not match: transactionId",
2373+
tvf.txHashes,
2374+
expectedTxHashes,
2375+
);
2376+
}
2377+
2378+
checkedWalletPublish = true;
2379+
});
2380+
2381+
await clients.B.respond({
2382+
topic,
2383+
response: result,
2384+
});
2385+
2386+
expect(checkedWalletPublish).to.be.true;
2387+
resolve();
2388+
});
2389+
}),
2390+
new Promise<void>(async (resolve) => {
2391+
const requestParams = {
2392+
method: "cosmos_signDirect",
2393+
params: {},
2394+
};
2395+
let checkedDappPublish = false;
2396+
2397+
clients.A.core.relayer.once(RELAYER_EVENTS.publish, (publishPayload: any) => {
2398+
checkedDappPublish = true;
2399+
const tvf = publishPayload.tvf;
2400+
expect(tvf).to.exist;
2401+
expect(tvf?.chainId).to.eq(TEST_REQUEST_PARAMS.chainId);
2402+
expect(tvf?.rpcMethods).to.eql([requestParams.method]);
2403+
expect(tvf?.txHashes).to.be.undefined;
2404+
expect(tvf?.contractAddresses).to.eql([requestParams.params[0].to]);
2405+
});
2406+
2407+
await clients.A.request({
2408+
topic,
2409+
...TEST_REQUEST_PARAMS,
2410+
request: {
2411+
...TEST_REQUEST_PARAMS.request,
2412+
...requestParams,
2413+
},
2414+
chainId: "cosmos:mainnet",
2415+
});
2416+
expect(checkedDappPublish).to.be.true;
2417+
resolve();
2418+
}),
2419+
]);
2420+
23142421
await throttle(1_000);
23152422
await deleteClients(clients);
23162423
});

packages/utils/src/signatures.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,53 @@ export function getAlgorandTransactionId(transaction: string) {
202202
// Encode to base32 and remove padding
203203
return base32.encode(hash).replace(/=+$/, "");
204204
}
205+
206+
function encodeVarint(value: number | bigint): Buffer {
207+
const result: number[] = [];
208+
let v = BigInt(value);
209+
while (v >= 0x80n) {
210+
result.push(Number((v & 0x7fn) | 0x80n));
211+
v >>= 7n;
212+
}
213+
result.push(Number(v));
214+
return Buffer.from(result);
215+
}
216+
217+
export function getSignDirectHash(payload: {
218+
signed: {
219+
chainId: string;
220+
accountNumber: string;
221+
authInfoBytes: string;
222+
bodyBytes: string;
223+
};
224+
signature: {
225+
pub_key: {
226+
type: string;
227+
value: string;
228+
};
229+
signature: string;
230+
};
231+
}) {
232+
const bodyBytes = Buffer.from(payload.signed.bodyBytes, "base64");
233+
const authInfoBytes = Buffer.from(payload.signed.authInfoBytes, "base64");
234+
const signature = Buffer.from(payload.signature.signature, "base64");
235+
236+
const chunks: Buffer[] = [];
237+
238+
chunks.push(Buffer.from([0x0a]));
239+
chunks.push(encodeVarint(bodyBytes.length));
240+
chunks.push(bodyBytes);
241+
242+
chunks.push(Buffer.from([0x12]));
243+
chunks.push(encodeVarint(authInfoBytes.length));
244+
chunks.push(authInfoBytes);
245+
246+
chunks.push(Buffer.from([0x1a]));
247+
chunks.push(encodeVarint(signature.length));
248+
chunks.push(signature);
249+
250+
const txRawBytes = Buffer.concat(chunks);
251+
const hashBytes = sha256(txRawBytes);
252+
253+
return Buffer.from(hashBytes).toString("hex").toUpperCase();
254+
}

packages/utils/test/signatures.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
extractSolanaTransactionId,
77
getAlgorandTransactionId,
88
getNearTransactionIdFromSignedTransaction,
9+
getSignDirectHash,
910
getSuiDigest,
1011
isValidEip191Signature,
1112
verifySignature,
@@ -489,5 +490,32 @@ Expiration Time: 2022-10-11T23:03:35.700Z`;
489490

490491
expect(hash).toBe("0x48016b3c80b7b61d32d1db6f52038de70d7d30ef948da047442cc9c952b92e84");
491492
});
493+
494+
it("should derive sign direct hash", () => {
495+
const result = {
496+
signature: {
497+
pub_key: {
498+
type: "tendermint/PubKeySecp256k1",
499+
value: "AgSEjOuOr991QlHCORRmdE5ahVKeyBrmtgoYepCpQGOW",
500+
},
501+
signature:
502+
"S7BJEbiXQ6vxvF9o4Wj7qAcocMQqBsqz+NVH4wilhidFsRpyqpSP5XiXoQZxTDrT9uET/S5SH6+5gUmjYntH/Q==",
503+
},
504+
signed: {
505+
chainId: "cosmoshub-4",
506+
accountNumber: "1",
507+
authInfoBytes:
508+
"ClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDNOXj4u60JFq00+VbLBCNBTYy76Pn864AvYNFG/9cQwMSBAoCCH8YAhITCg0KBXVhdG9tEgQ0NTM1EIaJCw==",
509+
bodyBytes:
510+
"CpoICikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchLsBwoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxODg4MDYiLWNvc21vczFhanBkZndsZmRqY240MG5yZXN5ZHJxazRhOGo2ZG0wemY0MGszcSo/b3NtbzEwYTNrNGh2azM3Y2M0aG54Y3R3NHA5NWZoc2NkMno2aDJybXgwYXVrYzZybTh1OXFxeDlzbWZzaDd1MgcIARDFt5YRQsgGeyJ3YXNtIjp7ImNvbnRyYWN0Ijoib3NtbzEwYTNrNGh2azM3Y2M0aG54Y3R3NHA5NWZoc2NkMno2aDJybXgwYXVrYzZybTh1OXFxeDlzbWZzaDd1IiwibXNnIjp7InN3YXBfYW5kX2FjdGlvbiI6eyJ1c2VyX3N3YXAiOnsic3dhcF9leGFjdF9hc3NldF9pbiI6eyJzd2FwX3ZlbnVlX25hbWUiOiJvc21vc2lzLXBvb2xtYW5hZ2VyIiwib3BlcmF0aW9ucyI6W3sicG9vbCI6IjE0MDAiLCJkZW5vbV9pbiI6ImliYy8yNzM5NEZCMDkyRDJFQ0NENTYxMjNDNzRGMzZFNEMxRjkyNjAwMUNFQURBOUNBOTdFQTYyMkIyNUY0MUU1RUIyIiwiZGVub21fb3V0IjoidW9zbW8ifSx7InBvb2wiOiIxMzQ3IiwiZGVub21faW4iOiJ1b3NtbyIsImRlbm9tX291dCI6ImliYy9ENzlFN0Q4M0FCMzk5QkZGRjkzNDMzRTU0RkFBNDgwQzE5MTI0OEZDNTU2OTI0QTJBODM1MUFFMjYzOEIzODc3In1dfX0sIm1pbl9hc3NldCI6eyJuYXRpdmUiOnsiZGVub20iOiJpYmMvRDc5RTdEODNBQjM5OUJGRkY5MzQzM0U1NEZBQTQ4MEMxOTEyNDhGQzU1NjkyNEEyQTgzNTFBRTI2MzhCMzg3NyIsImFtb3VudCI6IjMzOTQ2NyJ9fSwidGltZW91dF90aW1lc3RhbXAiOjE3NDc3NDY3MzM3OTU4OTgzNjQsInBvc3Rfc3dhcF9hY3Rpb24iOnsiaWJjX3RyYW5zZmVyIjp7ImliY19pbmZvIjp7InNvdXJjZV9jaGFubmVsIjoiY2hhbm5lbC02OTk0IiwicmVjZWl2ZXIiOiJjZWxlc3RpYTFhanBkZndsZmRqY240MG5yZXN5ZHJxazRhOGo2ZG0wemNsN3h0ZCIsIm1lbW8iOiIiLCJyZWNvdmVyX2FkZHJlc3MiOiJvc21vMWFqcGRmd2xmZGpjbjQwbnJlc3lkcnFrNGE4ajZkbTB6cHd1eDhqIn19fSwiYWZmaWxpYXRlcyI6W119fX19",
511+
},
512+
};
513+
514+
const expectedHash = "A7284BA475C55983E5BCB7D52F5C82CBFF19FD75725F5E0E33BA4384FCFC6052";
515+
516+
const hash = getSignDirectHash(result);
517+
console.log("hash", hash);
518+
expect(hash).toBe(expectedHash);
519+
});
492520
});
493521
});

0 commit comments

Comments
 (0)