Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Add spl-memo typescript implementation #2583

Merged
merged 5 commits into from
Jun 14, 2022
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
16 changes: 16 additions & 0 deletions .github/workflows/pull-request-memo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ jobs:

- name: Build and test
run: ./ci/cargo-test-bpf.sh memo

js-test:
runs-on: ubuntu-latest
env:
NODE_VERSION: 16.x
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- uses: actions/cache@v2
with:
path: ~/.cache/yarn
key: yarn-${{ hashFiles('memo/ts/yarn.lock') }}
- run: ./ci/ts-test-memo.sh
12 changes: 12 additions & 0 deletions ci/ts-test-memo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

set -e
cd "$(dirname "$0")/.."
source ./ci/solana-version.sh install

set -x
cd memo/ts
yarn
yarn build
yarn lint
yarn test
1 change: 1 addition & 0 deletions memo/ts/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
12 changes: 12 additions & 0 deletions memo/ts/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
};
1 change: 1 addition & 0 deletions memo/ts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
6 changes: 6 additions & 0 deletions memo/ts/.prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
arrowParens: "avoid"
bracketSpacing: false
semi: true
singleQuote: true
tabWidth: 2
trailingComma: "all"
5 changes: 5 additions & 0 deletions memo/ts/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
56 changes: 56 additions & 0 deletions memo/ts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@solana/spl-memo",
"version": "0.1.0",
"description": "SPL Memo Program JS API",
"files": [
"dist",
"src"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"sideEffects": false,
"scripts": {
"build": "npm run clean && tsc",
"clean": "rimraf ./dist",
"lint": "eslint . && npm run pretty",
"lint:fix": "eslint . --fix && npm run pretty:fix",
"pretty": "prettier --check '{,{src,test}/**/}*.{j,t}s'",
"pretty:fix": "prettier --write '{,{src,test}/**/}*.{j,t}s'",
"test": "npm run test:unit && npm run test:e2e",
"test:unit": "jest test/unit",
"test:e2e": "start-server-and-test 'solana-test-validator -r -q' http://localhost:8899/health 'jest test/e2e'"
},
"repository": {
"type": "git",
"url": "https://github.com/solana-labs/solana-program-library"
},
"publishConfig": {
"access": "public"
},
"author": "Solana Maintainers <maintainers@solana.com>",
"license": "MIT",
"devDependencies": {
"@types/chai": "^4.3.1",
"@types/jest": "^28.1.1",
"@types/node": "^17.0.42",
"@types/node-fetch": "^2.6.1",
"@types/prettier": "^2.6.3",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"chai": "^4.3.6",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"jest": "^28.1.1",
"prettier": "^2.7.0",
"process": "^0.11.10",
"start-server-and-test": "^1.14.0",
"ts-jest": "^28.0.5",
"ts-node": "^10.8.1",
"typescript": "^4.7.3"
},
"dependencies": {
"@solana/web3.js": "^1.41.0",
"buffer": "^6.0.3"
}
}
43 changes: 43 additions & 0 deletions memo/ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {Buffer} from 'buffer';
import {PublicKey, TransactionInstruction} from '@solana/web3.js';

export const MEMO_PROGRAM_ID: PublicKey = new PublicKey(
'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr',
);

/**
* Creates and returns an instruction which validates a string of UTF-8
* encoded characters and verifies that any accounts provided are signers of
* the transaction. The program also logs the memo, as well as any verified
* signer addresses, to the transaction log, so that anyone can easily observe
* memos and know they were approved by zero or more addresses by inspecting
* the transaction log from a trusted provider.
*
* Public keys passed in via the signerPubkeys will identify Signers which
* must subsequently sign the Transaction including the returned
* TransactionInstruction in order for the transaction to be valid.
*
* @param memo The UTF-8 encoded memo string to validate
* @param signerPubkeys An array of public keys which must sign the
* Transaction including the returned TransactionInstruction in order
* for the transaction to be valid and the memo verification to
* succeed. null is allowed if there are no signers for the memo
* verification.
**/
export function createMemoInstruction(
memo: string,
signerPubkeys?: Array<PublicKey>,
): TransactionInstruction {
const keys =
signerPubkeys == null
? []
: signerPubkeys.map(function (key) {
return {pubkey: key, isSigner: true, isWritable: false};
});

return new TransactionInstruction({
keys: keys,
programId: MEMO_PROGRAM_ID,
data: Buffer.from(memo, 'utf8'),
});
}
28 changes: 28 additions & 0 deletions memo/ts/test/e2e/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {createMemoInstruction} from '../../src';
import {
Connection,
Keypair,
Transaction,
LAMPORTS_PER_SOL,
sendAndConfirmTransaction,
} from '@solana/web3.js';

test('transaction: live', async () => {
const url = 'http://localhost:8899';
const connection = new Connection(url, 'confirmed');
await connection.getVersion();
const signer = new Keypair(); // also fee-payer

const airdropSignature = await connection.requestAirdrop(
signer.publicKey,
LAMPORTS_PER_SOL / 10,
);
await connection.confirmTransaction(airdropSignature, 'confirmed');

const memoTx = new Transaction().add(
createMemoInstruction('this is a test memo', [signer.publicKey]),
);
await sendAndConfirmTransaction(connection, memoTx, [signer], {
preflightCommitment: 'confirmed',
});
});
41 changes: 41 additions & 0 deletions memo/ts/test/unit/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {createMemoInstruction, MEMO_PROGRAM_ID} from '../../src';
import {expect} from 'chai';
import {Keypair} from '@solana/web3.js';

test('instruction: no signers', () => {
const ix = createMemoInstruction('this is a test memo', []);
expect(ix.programId).to.eql(MEMO_PROGRAM_ID);
expect(ix.keys).to.have.length(0);
expect(ix.data).to.have.length(19);

const ix2 = createMemoInstruction('this is a test');
expect(ix2.programId).to.eql(MEMO_PROGRAM_ID);
expect(ix2.keys).to.have.length(0);
expect(ix2.data).to.have.length(14);
});

test('instruction: one signer', () => {
const signer = new Keypair();
const ix = createMemoInstruction('this is a test memo', [signer.publicKey]);
expect(ix.programId).to.eql(MEMO_PROGRAM_ID);
expect(ix.keys).to.have.length(1);
expect(ix.data).to.have.length(19);
});

test('instruction: many signers', () => {
const signer0 = new Keypair();
const signer1 = new Keypair();
const signer2 = new Keypair();
const signer3 = new Keypair();
const signer4 = new Keypair();
const ix = createMemoInstruction('this is a test memo', [
signer0.publicKey,
signer1.publicKey,
signer2.publicKey,
signer3.publicKey,
signer4.publicKey,
]);
expect(ix.programId).to.eql(MEMO_PROGRAM_ID);
expect(ix.keys).to.have.length(5);
expect(ix.data).to.have.length(19);
});
16 changes: 16 additions & 0 deletions memo/ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"strict": true,
"isolatedModules": true,
"noImplicitReturns": true,
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
Loading