Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.14.2
24.13.0
107 changes: 67 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ yarn add @adena-wallet/sdk
Here is a basic example of how to use the SDK:

```
import { AdenaSDK, WalletProvider } from '@adena-wallet/sdk';
import { AdenaSDK, AdenaWalletProvider } from '@adena-wallet/sdk';

const walletProvider = new WalletProvider();
// `WalletProvider` is the interface; pass a concrete provider —
// `AdenaWalletProvider` for the extension, `GnoWalletProvider` /
// `GnoSocialWalletProvider` for a locally held key.
const walletProvider = new AdenaWalletProvider();
const adenaSDK = new AdenaSDK(walletProvider);

// Connect to the wallet
Expand Down Expand Up @@ -150,13 +153,14 @@ adenaSDK.getNetwork().then((network) => {

### `switchNetwork`

Switches the wallet to a different network.
Switches the wallet to a different network. With `GnoWalletProvider`, an unreachable RPC returns
`NETWORK_TIMEOUT` and leaves the previously connected network active.

**Example:**

```
adenaSDK.switchNetwork({ chainId: 'new-chain-id' }).then(() => {
console.log('Network switched');
adenaSDK.switchNetwork({ chainId: 'new-chain-id' }).then((response) => {
console.log('Network switch:', response.status, response.type);
});
```

Expand Down Expand Up @@ -275,97 +279,115 @@ const multisigAccountParams = {
noSort: true,
};

await adena.CreateMultisigAccount(multisigAccountParams);
await adenaSDK.createMultisigAccount(multisigAccountParams);
```

### `createMultisigTransaction`

Creates a multisig transaction that can be signed by multiple signers. Returns a multisig document that should be shared with all signers.
Creates a multisig transaction that can be signed by multiple signers. Returns the unsigned
amino transaction that should be shared with all signers.

> Note: This transaction must be created from a multisig account. Make sure you have switched to the multisig account in Adena wallet before calling this method.

**Parameters:**

- chain_id: Chain identifier
- msgs: Array of transaction messages
- fee: Transaction fee configuration
- messages: Array of transaction messages
- fee: `{ gasFee, gasWanted }`, both as strings (`gasFee` is a `<amount><denom>` coin)
- memo (Optional): Transaction memo
- networkInfo (Optional): `{ chainId, rpcUrl }` to pin the transaction to a specific network

**Example:**

```typescript
const multisigTxParams = {
chain_id: "staging",
msgs: [
messages: [
{
type: "/vm.m_call",
value: {
caller: "g1multisigaddress...",
send: "",
max_deposit: "",
pkg_path: "gno.land/r/gnoland/wugnot",
func: "Approve",
args: ["g1recipient...", "1000"],
},
},
],
fee: {
amount: [
{
amount: "6113",
denom: "ugnot",
},
],
gas: "6112955",
gasFee: "6113ugnot",
gasWanted: "6112955",
},
memo: "",
};

const response = await adena.CreateMultisigTransaction(multisigTxParams);

const multisigDocument = response.data;
const response = await adenaSDK.createMultisigTransaction(multisigTxParams);

// response.data structure:
// {
// tx: {
// msgs: [...],
// msg: [...],
// fee: { gas_wanted: "6112955", gas_fee: "6113ugnot" },
// signatures: null,
// memo: ""
// },
// chainId: "staging",
// accountNumber: "5315",
// sequence: "2"
// }
// }
```

The signing methods take a multisig document — the transaction plus the account context it
is signed against. Adena no longer returns that context from `createMultisigTransaction`, so
assemble it from `getAccount` and `getNetwork`:

```typescript
const account = await adenaSDK.getAccount();
const network = await adenaSDK.getNetwork();

const multisigDocument = {
tx: response.data.tx,
chainId: network.data.chainId,
accountNumber: account.data.accountNumber,
sequence: account.data.sequence,
};
```

### `signMultisigTransaction`

Signs a multisig transaction with the current account. This method adds your signature to an existing multisig transaction document.

**Parameters:**

- multisigDocument: from createMultisigTransaction or previous signer's result
- multisigDocument: `{ tx, chainId, accountNumber, sequence }`
- multisigSignatures (Optional): only for 2nd signer onwards, from previous signer's result

**Example:**

```typescript
// Signer 1: Received multisigDocument from createMultisigTransaction
const signer1Response = await adena.SignMultisigTransaction(multisigDocument);
const signer1Response = await adenaSDK.signMultisigTransaction({ multisigDocument });

// Signer 2: Received result from Signer 1
const { multisigDocument, multisigSignatures } = signer1Response.result;
const { multisigDocument: doc2, multisigSignatures: sigs2 } = signer1Response.data.result;

const signer2Response = await adena.SignMultisigTransaction(
multisigDocument,
multisigSignatures
);
const signer2Response = await adenaSDK.signMultisigTransaction({
multisigDocument: doc2,
multisigSignatures: sigs2,
});

// Signer 3: Received result from Signer 2
const { multisigDocument: doc3, multisigSignatures: sigs3 } =
signer2Response.result;
const { multisigDocument: doc3, multisigSignatures: sigs3 } = signer2Response.data.result;

const signer3Response = await adena.SignMultisigTransaction(doc3, sigs3);
const signer3Response = await adenaSDK.signMultisigTransaction({
multisigDocument: doc3,
multisigSignatures: sigs3,
});
```

Each collected signature is an amino secp256k1 signature:

```typescript
// {
// pub_key: { "@type": "/tm.PubKeySecp256k1", value: "A1b2..." },
// signature: "c3D4..."
// }
```

### `broadcastMultisigTransaction`
Expand All @@ -383,9 +405,9 @@ Broadcasts a multisig transaction once enough signatures have been collected to

```typescript
// Received result from the final signer (when threshold is met)
const { multisigDocument, multisigSignatures } = finalSignerResponse.result;
const { multisigDocument, multisigSignatures } = finalSignerResponse.data.result;

await adena.BroadcastMultisigTransaction(multisigDocument, multisigSignatures);
await adenaSDK.broadcastMultisigTransaction({ multisigDocument, multisigSignatures });
```

## Utility Functions
Expand All @@ -398,6 +420,11 @@ Generate transaction data.
- `makeAddPackageMessage`: Generate a `AddPackage` of vm transaction message.
- `makeMsgCallMessage`: Generate a `MsgCall` of vm transaction message.
- `makeMsgRunMessage`: Generates a `MsgRun` of vm transaction message.
- `makeMsgEnablePackageMessage`: Generates a `MsgEnablePackage` of vm transaction message.
- `makeMsgRejectPackageMessage`: Generates a `MsgRejectPackage` of vm transaction message.
- `makeMsgCreateSessionMessage`: Generates a `MsgCreateSession` of auth transaction message.
- `makeMsgRevokeSessionMessage`: Generates a `MsgRevokeSession` of auth transaction message.
- `makeMsgRevokeAllSessionsMessage`: Generates a `MsgRevokeAllSessions` of auth transaction message.

**Example:**

Expand All @@ -417,7 +444,7 @@ const tx = TransactionBuilder.create()

## Development Setup

The Node.js version is 18.14.2.
The Node.js version is 24.13.0.
We recommend using [nvm](https://github.com/nvm-sh/nvm).

```bash
Expand Down
16 changes: 10 additions & 6 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ yarn add @adena-wallet/sdk
Here is a basic example of how to use the SDK:

```
import { AdenaSDK, WalletProvider } from '@adena-wallet/sdk';
import { AdenaSDK, AdenaWalletProvider } from '@adena-wallet/sdk';

const walletProvider = new WalletProvider();
// `WalletProvider` is the interface; pass a concrete provider —
// `AdenaWalletProvider` for the extension, `GnoWalletProvider` /
// `GnoSocialWalletProvider` for a locally held key.
const walletProvider = new AdenaWalletProvider();
const adenaSDK = new AdenaSDK(walletProvider);

// Connect to the wallet
Expand Down Expand Up @@ -136,13 +139,14 @@ adenaSDK.getAccount().then((account) => {

### `switchNetwork`

Switches the wallet to a different network.
Switches the wallet to a different network. With `GnoWalletProvider`, an unreachable RPC returns
`NETWORK_TIMEOUT` and leaves the previously connected network active.

**Example:**

```
adenaSDK.switchNetwork({ chainId: 'new-chain-id' }).then(() => {
console.log('Network switched');
adenaSDK.switchNetwork({ chainId: 'new-chain-id' }).then((response) => {
console.log('Network switch:', response.status, response.type);
});
```

Expand Down Expand Up @@ -238,7 +242,7 @@ adenaSDK.onChangeNetwork({

## Development Setup

The Node.js version is 18.14.2.
The Node.js version is 24.13.0.
We recommend using [nvm](https://github.com/nvm-sh/nvm).

```bash
Expand Down
33 changes: 28 additions & 5 deletions packages/sdk/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,33 @@
"preset": "ts-jest",
"testEnvironment": "node",
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
"^.+\\.(ts|tsx|js|jsx|mjs)$": [
"ts-jest",
{
"tsconfig": "<rootDir>/tsconfig.spec.json"
}
]
},
"testMatch": ["<rootDir>/**/*.test.(ts|tsx)"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx"],
"moduleDirectories": ["node_modules", "<rootDir>/packages"],
"modulePathIgnorePatterns": ["bin", "node_modules", "coverage", "__mocks__"]
"transformIgnorePatterns": [
"node_modules/(?!.*(?:uuid|readonly-date-esm|@noble/|@scure/))"
],
"testMatch": [
"<rootDir>/**/*.test.(ts|tsx)"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx"
],
"moduleDirectories": [
"node_modules",
"<rootDir>/packages"
],
"modulePathIgnorePatterns": [
"bin",
"node_modules",
"coverage",
"__mocks__"
]
}
14 changes: 4 additions & 10 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,15 @@
"test:ci": "jest --coverage --passWithNoTests "
},
"dependencies": {
"@gnolang/gno-js-client": "1.4.5",
"@gnolang/tm2-js-client": "1.3.3",
"@noble/curves": "1.9.4",
"@noble/hashes": "1.8.0",
"@scure/bip32": "1.7.0",
"@scure/bip39": "1.6.0",
"@bufbuild/protobuf": "2.15.0",
"@gnolang/gno-js-client": "3.1.0",
"@gnolang/tm2-js-client": "3.3.0",
"@web3auth/auth-adapter": "9.5.3",
"@web3auth/base": "9.5.3",
"@web3auth/base-provider": "9.5.3",
"@web3auth/no-modal": "9.5.3",
"@web3auth/openlogin-adapter": "8.12.4",
"ethereum-cryptography": "3.2.0"
"@web3auth/no-modal": "9.5.3"
},
"devDependencies": {
"@bufbuild/protobuf": "2.2.3",
"@types/eslint": "9",
"@types/jest": "29.5.12",
"@types/node": "22.0.0",
Expand Down
57 changes: 57 additions & 0 deletions packages/sdk/src/core/__tests__/connection/session-restore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { JSONRPCProvider, Wallet as TM2Wallet } from '@gnolang/tm2-js-client';

import { GnoWalletProvider } from '../../../providers';
import { defineGlobalMock } from '../../__mocks__/mock-global';
import { ConnectionState } from '../../connection';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';
import { AdenaSDK } from '../../sdk';
import { NetworkInfo } from '../../types';

jest.mock('@gnolang/tm2-js-client', () => ({
...jest.requireActual('@gnolang/tm2-js-client'),
JSONRPCProvider: { create: jest.fn() },
}));

const createMock = JSONRPCProvider.create as unknown as jest.Mock;

const network: NetworkInfo = {
chainId: 'chain-a',
networkName: 'Chain A',
rpcUrl: 'https://a.example',
addressPrefix: GNO_ADDRESS_PREFIX,
indexerUrl: null,
};

const flush = (): Promise<void> => new Promise((resolve) => setImmediate(resolve));

/**
* `ConnectionManager` restores a session from its constructor without awaiting.
* Since tm2-js-client 3.x the provider factory rejects for an unreachable node,
* where the previous synchronous constructor could not, so that rejection has
* to be caught at the call site or it escapes unhandled.
*/
describe('session restore with an unreachable node', () => {
let walletProvider: GnoWalletProvider;

beforeEach(() => {
jest.clearAllMocks();
defineGlobalMock();

const wallet = { connect: jest.fn() } as unknown as TM2Wallet;
walletProvider = new GnoWalletProvider(wallet, [network]);
createMock.mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:26657'));
});

it('does not leak an unhandled rejection when the session cannot be restored', async () => {
const sdk = new AdenaSDK(walletProvider, { isSession: true });

// An escaping rejection fails this suite before the assertion runs.
await flush();

expect(sdk.getConnectionState()).toBe(ConnectionState.ERROR);
});

it('leaves the session disconnected rather than throwing from the constructor', () => {
expect(() => new AdenaSDK(walletProvider, { isSession: true })).not.toThrow();
});
});
Loading
Loading