From 32be07b4b254681f6f07a6c55ad7388cf45059b4 Mon Sep 17 00:00:00 2001 From: Brian Monroy <48845597+brianUtn98@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:20:29 -0300 Subject: [PATCH] Treasury selection (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add lookup table back to config for ease of use in sdk * manually update program IDLs in claim_sdk * formatting * fix integration tests * more integration test fixes * fix compilation by putting stub in place * formatting * adds treasury pub-key to ClaimEvent * minor fix * multi-treasury deployment * mt-02 * WIP treasury and funders pull and selection * treasury list without env * support multiple funders and use same one than frontend to sign * sign transaction in the frontend and pass the funder used to the backend * fix mayority of tests * remove injective test to be up to date with main * fix BE tests * FE prettier * BE prettier * fix linter * updated treasury addresses * fix linting * Fix algorand claim info identity serialization (#56) * Add message for EVM wallet selector (#59) * backend: modifies private key secret format We expect a list of private keys as an array of number arrays. * backend: adds types for node v18 * frontend: use `JSON.parse` to parse private key in test * backend: prettier run * all: replaces token dispenser program id env var with constant * update toStringWithDecimals from 9 to 6 * remove injective test * fix yarn.lock * fix backend prettier * frontend fix prettier * token dispenser fix prettier * run pre-commit locally * linter * added solana/web3 dep * multi-treasury deployment 03 * use tokenDispenserProgramId from same workspace * prettier * remove dep between front and back * update treasuries and funders * frontend prettier --------- Co-authored-by: nonergodic Co-authored-by: solanoe Co-authored-by: Martin Picco Co-authored-by: Valentino Conti <41705567+valentinoConti@users.noreply.github.com> Co-authored-by: Sebastián Claudio Nale --- .github/workflows/frontend.yml | 3 - backend/README.md | 1 - backend/package.json | 1 + backend/src/config.ts | 2 +- backend/src/handlers/fund-transactions.ts | 43 +- backend/src/utils/fund-transactions.ts | 31 +- backend/src/utils/secrets.ts | 21 +- .../test/handlers/fund-transactions.test.ts | 27 +- backend/yarn.lock | 7 + frontend/.env.sample | 1 - frontend/claim_sdk/idl/token_dispenser.json | 75 +- frontend/claim_sdk/idl/token_dispenser.ts | 1588 ++++++++------- frontend/claim_sdk/solana.ts | 54 +- frontend/claim_sdk/testWallets.ts | 43 +- frontend/claim_sdk/treasury.ts | 32 + frontend/hooks/useTokenDispenserProvider.tsx | 3 +- frontend/integration/api.test.ts | 233 ++- frontend/integration/api.ts | 69 +- frontend/integration/integrationTest.test.ts | 64 +- frontend/next.config.js | 1 - frontend/scripts/datadog.ts | 4 +- frontend/sections/SignAndClaim.tsx | 4 - frontend/utils/api.ts | 17 +- frontend/utils/constants.ts | 3 + frontend/utils/isClaimAlreadySubmitted.ts | 3 +- frontend/utils/toStringWithDecimals.ts | 2 +- token-dispenser/Anchor.toml | 6 +- token-dispenser/package-lock.json | 1 + token-dispenser/package.json | 1 + .../programs/token-dispenser/src/lib.rs | 2 +- token-dispenser/scripts/create-treasuries.sh | 2 - token-dispenser/scripts/fund-treasuries.sh | 2 - token-dispenser/scripts/treasuries | 12 +- token-dispenser/ts/scripts/config.ts | 20 +- .../ts/scripts/create-spl-token.ts | 2 +- token-dispenser/ts/scripts/initialize.ts | 16 +- .../ts/scripts/print-config-pda.ts | 10 +- .../ts/sdk/idl/token_dispenser.json | 75 +- token-dispenser/ts/sdk/idl/token_dispenser.ts | 1700 ++++++++--------- token-dispenser/ts/sdk/token-dispenser.ts | 16 +- token-dispenser/yarn.lock | 126 +- 41 files changed, 2150 insertions(+), 2173 deletions(-) create mode 100644 frontend/claim_sdk/treasury.ts mode change 100644 => 100755 token-dispenser/scripts/fund-treasuries.sh diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 36146e38..09ac16c3 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -14,9 +14,6 @@ jobs: env: ENDPOINT: http://localhost:8899 - env: - PROGRAM_ID: WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC - defaults: run: working-directory: ./frontend diff --git a/backend/README.md b/backend/README.md index e68ec925..7deef69f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -28,4 +28,3 @@ Following env vars are required: - `DISPENSER_KEY_SECRET_NAME`: private key of the wallet that will be used to sign the discord message - `FUNDER_WALLET_KEY_SECRET_NAME`: private key of the wallet that will be used to fund the transactions -- `TOKEN_DISPENSER_PROGRAM_ID`: the program id of the token dispenser diff --git a/backend/package.json b/backend/package.json index d79178fe..b821deae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -34,6 +34,7 @@ "@types/aws-lambda": "^8.10.136", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", + "@types/node": "^18", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", "body-parser": "^1.20.2", diff --git a/backend/src/config.ts b/backend/src/config.ts index 7b44ab5b..c84775c4 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -5,7 +5,7 @@ export default { aws: { region: process.env.AWS_REGION ?? 'us-east-2' }, - tokenDispenserProgramId: () => process.env.TOKEN_DISPENSER_PROGRAM_ID, + tokenDispenserProgramId: () => 'WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA', secrets: { dispenserGuard: { /** optional. mostly for local testing */ diff --git a/backend/src/handlers/fund-transactions.ts b/backend/src/handlers/fund-transactions.ts index 443ea1d7..71f02daf 100644 --- a/backend/src/handlers/fund-transactions.ts +++ b/backend/src/handlers/fund-transactions.ts @@ -1,12 +1,12 @@ import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' -import { getFundingKey } from '../utils/secrets' +import { getFundingKeys } from '../utils/secrets' import { checkTransactions, deserializeTransactions, extractCallData } from '../utils/fund-transactions' -import { Keypair, VersionedTransaction } from '@solana/web3.js' +import { VersionedTransaction } from '@solana/web3.js' import bs58 from 'bs58' import { HandlerError } from '../utils/errors' import { asJsonResponse } from '../utils/response' @@ -15,7 +15,7 @@ import { saveSignedTransactions } from '../utils/persistence' export type FundTransactionRequest = Uint8Array[] -let funderWallet: NodeWallet +const funderWallets: Record = {} export const fundTransactions = async ( event: APIGatewayProxyEvent @@ -24,15 +24,29 @@ export const fundTransactions = async ( const requestBody = JSON.parse(event.body!) validateFundTransactions(requestBody) const transactions = deserializeTransactions(requestBody) - const isTransactionsValid = await checkTransactions(transactions) + const isTransactionsValid = await checkTransactions( + transactions.map((txWithFunder) => txWithFunder.transaction) + ) if (!isTransactionsValid) { return asJsonResponse(403, { error: 'Unauthorized transactions' }) } - const wallet = await loadFunderWallet() + const wallets = await loadFunderWallets() + + const signedTransactions: VersionedTransaction[] = [] + + for (const txWithFunder of transactions) { + const funderWallet = wallets[txWithFunder.funder] + if (!funderWallet) { + return asJsonResponse(403, { error: 'Unauthorized funder' }) + } + + signedTransactions.push( + await funderWallet.signTransaction(txWithFunder.transaction) + ) + } - const signedTransactions = await wallet.signAllTransactions(transactions) await saveSignedTransactions(getSignatures(signedTransactions)) return asJsonResponse( @@ -60,19 +74,18 @@ function validateFundTransactions(transactions: unknown) { } } -async function loadFunderWallet(): Promise { - if (funderWallet) { - return funderWallet +async function loadFunderWallets(): Promise> { + if (Object.keys(funderWallets).length > 0) { + return funderWallets } - const secretData = await getFundingKey() - const funderWalletKey = secretData.key + const secretData = await getFundingKeys() - const keypair = Keypair.fromSecretKey(Uint8Array.from(funderWalletKey)) + secretData.forEach((keypair) => { + funderWallets[keypair.publicKey.toBase58()] = new NodeWallet(keypair) + }) - funderWallet = new NodeWallet(keypair) - console.log('Loaded funder wallet') - return funderWallet + return funderWallets } function getSignature(tx: VersionedTransaction): string { diff --git a/backend/src/utils/fund-transactions.ts b/backend/src/utils/fund-transactions.ts index fee2c986..d744ef15 100644 --- a/backend/src/utils/fund-transactions.ts +++ b/backend/src/utils/fund-transactions.ts @@ -17,12 +17,29 @@ const SET_COMPUTE_UNIT_PRICE_DISCRIMINANT = 3 const MAX_COMPUTE_UNIT_PRICE = BigInt(1_000_000) +export type TransactionWithFunder = { + transaction: VersionedTransaction + funder: string +} + +export type SerializedTransactionWithFunder = { + tx: Uint8Array + funder: string +} + export function deserializeTransactions( transactions: unknown -): VersionedTransaction[] { +): TransactionWithFunder[] { try { - return (transactions as Uint8Array[]).map((serializedTx) => - VersionedTransaction.deserialize(Buffer.from(serializedTx)) + return (transactions as SerializedTransactionWithFunder[]).map( + (serializedTx) => { + return { + transaction: VersionedTransaction.deserialize( + Buffer.from(serializedTx.tx) + ), + funder: serializedTx.funder + } + } ) } catch (err) { console.error('Failed to deserialize transactions', err) @@ -31,14 +48,14 @@ export function deserializeTransactions( } async function loadWhitelistedProgramIds(): Promise { - const programId = config.tokenDispenserProgramId() - if (!programId) { + const tokenDispenserProgramId = config.tokenDispenserProgramId() + if (!tokenDispenserProgramId) { throw new Error('Token dispenser program ID not set') } - const PROGRAM_ID = new PublicKey(programId) + const tokenDispenserPublicKey = new PublicKey(tokenDispenserProgramId) return [ - PROGRAM_ID, + tokenDispenserPublicKey, Secp256k1Program.programId, Ed25519Program.programId, ComputeBudgetProgram.programId diff --git a/backend/src/utils/secrets.ts b/backend/src/utils/secrets.ts index 9939649c..f148c0d1 100644 --- a/backend/src/utils/secrets.ts +++ b/backend/src/utils/secrets.ts @@ -4,9 +4,14 @@ import { GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' import config from '../config' +import { Keypair } from '@solana/web3.js' const client = new SecretsManagerClient({ region: config.aws.region }) +interface SecretPrivateKeys { + keys: string +} + export async function getDispenserKey() { let key: string if (config.secrets.dispenserGuard.key) { @@ -19,16 +24,12 @@ export async function getDispenserKey() { return { key: JSON.parse(key) } } -export async function getFundingKey(): Promise<{ key: Uint8Array }> { - let key: string - if (config.secrets.funding.key()) { - console.log('Using funding key from config') - key = config.secrets.funding.key()! - } else { - key = await getSecretKey(config.secrets.funding.secretName, 'key') - } - - return { key: JSON.parse(key) } +export async function getFundingKeys(): Promise { + const secret = (await getSecret( + config.secrets.funding.secretName + )) as SecretPrivateKeys + const keys = JSON.parse(secret.keys) as number[][] + return keys.map((key) => Keypair.fromSecretKey(Uint8Array.from(key))) } export async function getInfluxToken(): Promise { diff --git a/backend/test/handlers/fund-transactions.test.ts b/backend/test/handlers/fund-transactions.test.ts index 0784df04..34df093b 100644 --- a/backend/test/handlers/fund-transactions.test.ts +++ b/backend/test/handlers/fund-transactions.test.ts @@ -21,11 +21,13 @@ import { IDL, TokenDispenser } from '../../src/token-dispenser' import { fundTransactions } from '../../src/handlers/fund-transactions' import { GenericContainer, StartedTestContainer } from 'testcontainers' import { InfluxDB } from '@influxdata/influxdb-client' +import config from '../../src/config' const RANDOM_BLOCKHASH = 'HXq5QPm883r7834LWwDpcmEM8G8uQ9Hqm1xakCHGxprV' +const tokenDispenserProgramId = config.tokenDispenserProgramId() +const TokenDispenserPublicKey = new PublicKey(tokenDispenserProgramId) const INFLUX_TOKEN = 'jsNTEHNBohEjgKqWj1fR8fJjYlBvcYaRTY68-iQ5Y55X_Qr3VKGSvqJz78g4jV8mPiUTQLPYq2tLs_Dy8M--nw==' -const PROGRAM_ID = new PublicKey('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS') const FUNDER_KEY = new Keypair() const server = setupServer() const influx = new GenericContainer('influxdb') @@ -55,9 +57,9 @@ describe('fundTransactions integration test', () => { 8086 )}` process.env.INFLUXDB_FLUSH_ENABLED = 'true' - + process.env.AWS_ACCESS_KEY_ID = 'key' + process.env.AWS_SECRET_ACCESS_KEY = 'secret' process.env.FUNDING_WALLET_KEY = `[${FUNDER_KEY.secretKey}]` - process.env.TOKEN_DISPENSER_PROGRAM_ID = PROGRAM_ID.toString() }, 20_000) afterAll(async () => { @@ -230,7 +232,7 @@ describe('fundTransactions integration test', () => { const instructions = [ tokenDispenserInstruction, createComputeUnitLimitInstruction(200), - createComputeUnitPriceInstruction(BigInt(10000000)), + createComputeUnitPriceInstruction(BigInt(10_000_000)), createSecp256k1ProgramInstruction() ] @@ -246,7 +248,7 @@ describe('fundTransactions integration test', () => { await createTokenDispenserProgramInstruction() ]) - const callData = extractCallData(versionedTx, PROGRAM_ID.toBase58()) + const callData = extractCallData(versionedTx, tokenDispenserProgramId) expect(callData).not.toBeNull() expect(callData?.amount.toNumber()).toBe(3000000) @@ -272,7 +274,9 @@ const givenDownstreamServicesWork = () => { server.use( http.all('https://secretsmanager.us-east-2.amazonaws.com', () => { return HttpResponse.json({ - SecretString: JSON.stringify({ key: `[${[...FUNDER_KEY.secretKey]}]` }) + SecretString: JSON.stringify({ + keys: JSON.stringify([[...FUNDER_KEY.secretKey]]) + }) }) }) ) @@ -295,7 +299,14 @@ const givenCorrectTransaction = async () => { const whenFundTransactionsCalled = async () => { response = await fundTransactions({ - body: JSON.stringify(input.map((tx) => Buffer.from(tx.serialize()))) + body: JSON.stringify( + input.map((tx) => { + return { + tx: Buffer.from(tx.serialize()), + funder: FUNDER_KEY.publicKey + } + }) + ) } as unknown as APIGatewayProxyEvent) } @@ -354,7 +365,7 @@ const createTestLegacyTransactionFromInstructions = ( const createTokenDispenserProgramInstruction = async () => { const tokenDispenser = new Program( IDL, - PROGRAM_ID, + TokenDispenserPublicKey, new AnchorProvider( new Connection('http://localhost:8899'), new NodeWallet(new Keypair()), diff --git a/backend/yarn.lock b/backend/yarn.lock index 5382da0e..f3b086df 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2038,6 +2038,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== +"@types/node@^18": + version "18.19.28" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.28.tgz#c64a2c992c8ebbf61100a4570e4eebc1934ae030" + integrity sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw== + dependencies: + undici-types "~5.26.4" + "@types/node@^18.11.18": version "18.19.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.26.tgz#18991279d0a0e53675285e8cf4a0823766349729" diff --git a/frontend/.env.sample b/frontend/.env.sample index a571604a..f2d9fda0 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -31,7 +31,6 @@ FUNDER_KEYPAIR=[145,197,43,77,224,103,196,174,132,195,48,31,177,97,237,163,15,19 DEPLOYER_WALLET=[145,197,43,77,224,103,196,174,132,195,48,31,177,97,237,163,15,196,217,142,181,204,104,107,98,82,213,0,155,140,218,180,30,119,201,38,51,176,207,221,193,222,235,244,163,250,125,66,68,196,45,208,212,201,232,178,100,163,24,21,106,83,66,174] -PROGRAM_ID=WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC CSV_DIR=/tmp # Datadog Event Subscriber Configs diff --git a/frontend/claim_sdk/idl/token_dispenser.json b/frontend/claim_sdk/idl/token_dispenser.json index 371a31d3..098c2327 100644 --- a/frontend/claim_sdk/idl/token_dispenser.json +++ b/frontend/claim_sdk/idl/token_dispenser.json @@ -38,10 +38,7 @@ { "name": "merkleRoot", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -141,10 +138,7 @@ { "name": "merkleRoot", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -334,10 +328,7 @@ "name": "proofOfInclusion", "type": { "vec": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } } } @@ -367,10 +358,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -381,10 +369,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } } ] @@ -395,10 +380,7 @@ { "name": "address", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -409,10 +391,7 @@ { "name": "address", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -441,10 +420,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -476,10 +452,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -497,10 +470,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { @@ -515,10 +485,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { @@ -537,10 +504,7 @@ { "name": "signature", "type": { - "array": [ - "u8", - 64 - ] + "array": ["u8", 64] } }, { @@ -550,10 +514,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 65 - ] + "array": ["u8", 65] } }, { @@ -568,10 +529,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -586,10 +544,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { diff --git a/frontend/claim_sdk/idl/token_dispenser.ts b/frontend/claim_sdk/idl/token_dispenser.ts index da5ef49d..5f813551 100644 --- a/frontend/claim_sdk/idl/token_dispenser.ts +++ b/frontend/claim_sdk/idl/token_dispenser.ts @@ -1,344 +1,334 @@ -export type TokenDispenser = -{ - "version": "0.1.0", - "name": "token_dispenser", - "instructions": [ +export type TokenDispenser = { + version: '0.1.0' + name: 'token_dispenser' + instructions: [ { - "name": "initialize", - "docs": [ - "This can only be called once and should be called right after the program is deployed." - ], - "accounts": [ + name: 'initialize' + docs: [ + 'This can only be called once and should be called right after the program is deployed.' + ] + accounts: [ { - "name": "payer", - "isMut": true, - "isSigner": true + name: 'payer' + isMut: true + isSigner: true }, { - "name": "config", - "isMut": true, - "isSigner": false + name: 'config' + isMut: true + isSigner: false }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: 'mint' + isMut: false + isSigner: false }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'systemProgram' + isMut: false + isSigner: false }, { - "name": "addressLookupTable", - "isMut": false, - "isSigner": false + name: 'addressLookupTable' + isMut: false + isSigner: false } - ], - "args": [ + ] + args: [ { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] + name: 'merkleRoot' + type: { + array: ['u8', 20] } }, { - "name": "dispenserGuard", - "type": "publicKey" + name: 'dispenserGuard' + type: 'publicKey' }, { - "name": "maxTransfer", - "type": "u64" + name: 'maxTransfer' + type: 'u64' } ] }, { - "name": "claim", - "docs": [ + name: 'claim' + docs: [ "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE" - ], - "accounts": [ + ] + accounts: [ { - "name": "funder", - "isMut": true, - "isSigner": true + name: 'funder' + isMut: true + isSigner: true }, { - "name": "claimant", - "isMut": false, - "isSigner": true + name: 'claimant' + isMut: false + isSigner: true }, { - "name": "claimantFund", - "isMut": true, - "isSigner": false, - "docs": [ + name: 'claimantFund' + isMut: true + isSigner: false + docs: [ "Claimant's associated token account to receive the tokens", - "Should be initialized outside of this program." + 'Should be initialized outside of this program.' ] }, { - "name": "config", - "isMut": false, - "isSigner": false + name: 'config' + isMut: false + isSigner: false }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: 'mint' + isMut: false + isSigner: false }, { - "name": "treasury", - "isMut": true, - "isSigner": false + name: 'treasury' + isMut: true + isSigner: false }, { - "name": "tokenProgram", - "isMut": false, - "isSigner": false + name: 'tokenProgram' + isMut: false + isSigner: false }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'systemProgram' + isMut: false + isSigner: false }, { - "name": "sysvarInstruction", - "isMut": false, - "isSigner": false, - "docs": [ + name: 'sysvarInstruction' + isMut: false + isSigner: false + docs: [ "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked." ] }, { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false + name: 'associatedTokenProgram' + isMut: false + isSigner: false } - ], - "args": [ + ] + args: [ { - "name": "claimCertificate", - "type": { - "defined": "ClaimCertificate" + name: 'claimCertificate' + type: { + defined: 'ClaimCertificate' } } ] } - ], - "accounts": [ + ] + accounts: [ { - "name": "Config", - "type": { - "kind": "struct", - "fields": [ + name: 'Config' + type: { + kind: 'struct' + fields: [ { - "name": "bump", - "type": "u8" + name: 'bump' + type: 'u8' }, { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] + name: 'merkleRoot' + type: { + array: ['u8', 20] } }, { - "name": "dispenserGuard", - "type": "publicKey" + name: 'dispenserGuard' + type: 'publicKey' }, { - "name": "mint", - "type": "publicKey" + name: 'mint' + type: 'publicKey' }, { - "name": "addressLookupTable", - "type": "publicKey" + name: 'addressLookupTable' + type: 'publicKey' }, { - "name": "maxTransfer", - "type": "u64" + name: 'maxTransfer' + type: 'u64' } ] } }, { - "name": "Receipt", - "type": { - "kind": "struct", - "fields": [] + name: 'Receipt' + type: { + kind: 'struct' + fields: [] } } - ], - "types": [ + ] + types: [ { - "name": "CosmosMessage", - "docs": [ - "* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key." - ], - "type": { - "kind": "struct", - "fields": [ + name: 'CosmosMessage' + docs: [ + '* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key.' + ] + type: { + kind: 'struct' + fields: [ { - "name": "payload", - "type": "bytes" + name: 'payload' + type: 'bytes' }, { - "name": "signer", - "type": "string" + name: 'signer' + type: 'string' } ] } }, { - "name": "DiscordMessage", - "docs": [ + name: 'DiscordMessage' + docs: [ "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else." - ], - "type": { - "kind": "struct", - "fields": [ + ] + type: { + kind: 'struct' + fields: [ { - "name": "username", - "type": "string" + name: 'username' + type: 'string' }, { - "name": "claimant", - "type": "publicKey" + name: 'claimant' + type: 'publicKey' } ] } }, { - "name": "Ed25519InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: 'Ed25519InstructionHeader' + type: { + kind: 'struct' + fields: [ { - "name": "numSignatures", - "type": "u8" + name: 'numSignatures' + type: 'u8' }, { - "name": "padding", - "type": "u8" + name: 'padding' + type: 'u8' }, { - "name": "signatureOffset", - "type": "u16" + name: 'signatureOffset' + type: 'u16' }, { - "name": "signatureInstructionIndex", - "type": "u16" + name: 'signatureInstructionIndex' + type: 'u16' }, { - "name": "publicKeyOffset", - "type": "u16" + name: 'publicKeyOffset' + type: 'u16' }, { - "name": "publicKeyInstructionIndex", - "type": "u16" + name: 'publicKeyInstructionIndex' + type: 'u16' }, { - "name": "messageDataOffset", - "type": "u16" + name: 'messageDataOffset' + type: 'u16' }, { - "name": "messageDataSize", - "type": "u16" + name: 'messageDataSize' + type: 'u16' }, { - "name": "messageInstructionIndex", - "type": "u16" + name: 'messageInstructionIndex' + type: 'u16' } ] } }, { - "name": "Secp256k1InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: 'Secp256k1InstructionHeader' + type: { + kind: 'struct' + fields: [ { - "name": "numSignatures", - "type": "u8" + name: 'numSignatures' + type: 'u8' }, { - "name": "signatureOffset", - "type": "u16" + name: 'signatureOffset' + type: 'u16' }, { - "name": "signatureInstructionIndex", - "type": "u8" + name: 'signatureInstructionIndex' + type: 'u8' }, { - "name": "ethAddressOffset", - "type": "u16" + name: 'ethAddressOffset' + type: 'u16' }, { - "name": "ethAddressInstructionIndex", - "type": "u8" + name: 'ethAddressInstructionIndex' + type: 'u8' }, { - "name": "messageDataOffset", - "type": "u16" + name: 'messageDataOffset' + type: 'u16' }, { - "name": "messageDataSize", - "type": "u16" + name: 'messageDataSize' + type: 'u16' }, { - "name": "messageInstructionIndex", - "type": "u8" + name: 'messageInstructionIndex' + type: 'u8' } ] } }, { - "name": "ClaimInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "identity", - "type": { - "defined": "Identity" + name: 'ClaimInfo' + type: { + kind: 'struct' + fields: [ + { + name: 'identity' + type: { + defined: 'Identity' } }, { - "name": "amount", - "type": "u64" + name: 'amount' + type: 'u64' } ] } }, { - "name": "ClaimCertificate", - "type": { - "kind": "struct", - "fields": [ + name: 'ClaimCertificate' + type: { + kind: 'struct' + fields: [ { - "name": "amount", - "type": "u64" + name: 'amount' + type: 'u64' }, { - "name": "proofOfIdentity", - "type": { - "defined": "IdentityCertificate" + name: 'proofOfIdentity' + type: { + defined: 'IdentityCertificate' } }, { - "name": "proofOfInclusion", - "type": { - "vec": { - "array": [ - "u8", - 20 - ] + name: 'proofOfInclusion' + type: { + vec: { + array: ['u8', 20] } } } @@ -346,106 +336,91 @@ export type TokenDispenser = } }, { - "name": "Identity", - "docs": [ + name: 'Identity' + docs: [ "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space." - ], - "type": { - "kind": "enum", - "variants": [ + ] + type: { + kind: 'enum' + variants: [ { - "name": "Discord", - "fields": [ + name: 'Discord' + fields: [ { - "name": "username", - "type": "string" + name: 'username' + type: 'string' } ] }, { - "name": "Solana", - "fields": [ + name: 'Solana' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] + name: 'pubkey' + type: { + array: ['u8', 32] } } ] }, { - "name": "Evm", - "fields": [ + name: 'Evm' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] + name: 'pubkey' + type: { + array: ['u8', 20] } } ] }, { - "name": "Sui", - "fields": [ + name: 'Sui' + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] + name: 'address' + type: { + array: ['u8', 32] } } ] }, { - "name": "Aptos", - "fields": [ + name: 'Aptos' + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] + name: 'address' + type: { + array: ['u8', 32] } } ] }, { - "name": "Cosmwasm", - "fields": [ + name: 'Cosmwasm' + fields: [ { - "name": "address", - "type": "string" + name: 'address' + type: 'string' } ] }, { - "name": "Injective", - "fields": [ + name: 'Injective' + fields: [ { - "name": "address", - "type": "string" + name: 'address' + type: 'string' } ] }, { - "name": "Algorand", - "fields": [ + name: 'Algorand' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] + name: 'pubkey' + type: { + array: ['u8', 32] } } ] @@ -454,911 +429,842 @@ export type TokenDispenser = } }, { - "name": "IdentityCertificate", - "type": { - "kind": "enum", - "variants": [ + name: 'IdentityCertificate' + type: { + kind: 'enum' + variants: [ { - "name": "Discord", - "fields": [ + name: 'Discord' + fields: [ { - "name": "username", - "type": "string" + name: 'username' + type: 'string' }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] }, { - "name": "Evm", - "fields": [ + name: 'Evm' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] + name: 'pubkey' + type: { + array: ['u8', 20] } }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] }, { - "name": "Solana" + name: 'Solana' }, { - "name": "Sui", - "fields": [ + name: 'Sui' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] + name: 'pubkey' + type: { + array: ['u8', 32] } }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] }, { - "name": "Aptos", - "fields": [ + name: 'Aptos' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] + name: 'pubkey' + type: { + array: ['u8', 32] } }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] }, { - "name": "Cosmwasm", - "fields": [ + name: 'Cosmwasm' + fields: [ { - "name": "chain_id", - "type": "string" + name: 'chain_id' + type: 'string' }, { - "name": "signature", - "type": { - "array": [ - "u8", - 64 - ] + name: 'signature' + type: { + array: ['u8', 64] } }, { - "name": "recovery_id", - "type": "u8" + name: 'recovery_id' + type: 'u8' }, { - "name": "pubkey", - "type": { - "array": [ - "u8", - 65 - ] + name: 'pubkey' + type: { + array: ['u8', 65] } }, { - "name": "message", - "type": "bytes" + name: 'message' + type: 'bytes' } ] }, { - "name": "Injective", - "fields": [ + name: 'Injective' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] + name: 'pubkey' + type: { + array: ['u8', 20] } }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] }, { - "name": "Algorand", - "fields": [ + name: 'Algorand' + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] + name: 'pubkey' + type: { + array: ['u8', 32] } }, { - "name": "verification_instruction_index", - "type": "u8" + name: 'verification_instruction_index' + type: 'u8' } ] } ] } } - ], - "events": [ + ] + events: [ { - "name": "ClaimEvent", - "fields": [ + name: 'ClaimEvent' + fields: [ { - "name": "treasury", - "type": "publicKey", - "index": false + name: 'treasury' + type: 'publicKey' + index: false }, { - "name": "remainingBalance", - "type": "u64", - "index": false + name: 'remainingBalance' + type: 'u64' + index: false }, { - "name": "claimant", - "type": "publicKey", - "index": false + name: 'claimant' + type: 'publicKey' + index: false }, { - "name": "claimInfo", - "type": { - "defined": "ClaimInfo" - }, - "index": false + name: 'claimInfo' + type: { + defined: 'ClaimInfo' + } + index: false } ] } - ], - "errors": [ + ] + errors: [ { - "code": 6000, - "name": "AlreadyClaimed" + code: 6000 + name: 'AlreadyClaimed' }, { - "code": 6001, - "name": "InvalidInclusionProof" + code: 6001 + name: 'InvalidInclusionProof' }, { - "code": 6002, - "name": "WrongPda" + code: 6002 + name: 'WrongPda' }, { - "code": 6003, - "name": "SignatureVerificationWrongProgram" + code: 6003 + name: 'SignatureVerificationWrongProgram' }, { - "code": 6004, - "name": "SignatureVerificationWrongAccounts" + code: 6004 + name: 'SignatureVerificationWrongAccounts' }, { - "code": 6005, - "name": "SignatureVerificationWrongHeader" + code: 6005 + name: 'SignatureVerificationWrongHeader' }, { - "code": 6006, - "name": "SignatureVerificationWrongPayload" + code: 6006 + name: 'SignatureVerificationWrongPayload' }, { - "code": 6007, - "name": "SignatureVerificationWrongPayloadMetadata" + code: 6007 + name: 'SignatureVerificationWrongPayloadMetadata' }, { - "code": 6008, - "name": "SignatureVerificationWrongSigner" + code: 6008 + name: 'SignatureVerificationWrongSigner' }, { - "code": 6009, - "name": "UnauthorizedCosmosChainId" + code: 6009 + name: 'UnauthorizedCosmosChainId' }, { - "code": 6010, - "name": "TransferExceedsMax" + code: 6010 + name: 'TransferExceedsMax' } ] } -; -export const IDL: TokenDispenser = -{ - "version": "0.1.0", - "name": "token_dispenser", - "instructions": [ +export const IDL: TokenDispenser = { + version: '0.1.0', + name: 'token_dispenser', + instructions: [ { - "name": "initialize", - "docs": [ - "This can only be called once and should be called right after the program is deployed." + name: 'initialize', + docs: [ + 'This can only be called once and should be called right after the program is deployed.', ], - "accounts": [ + accounts: [ { - "name": "payer", - "isMut": true, - "isSigner": true + name: 'payer', + isMut: true, + isSigner: true, }, { - "name": "config", - "isMut": true, - "isSigner": false + name: 'config', + isMut: true, + isSigner: false, }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: 'mint', + isMut: false, + isSigner: false, }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'systemProgram', + isMut: false, + isSigner: false, }, { - "name": "addressLookupTable", - "isMut": false, - "isSigner": false - } + name: 'addressLookupTable', + isMut: false, + isSigner: false, + }, ], - "args": [ + args: [ { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: 'merkleRoot', + type: { + array: ['u8', 20], + }, }, { - "name": "dispenserGuard", - "type": "publicKey" + name: 'dispenserGuard', + type: 'publicKey', }, { - "name": "maxTransfer", - "type": "u64" - } - ] + name: 'maxTransfer', + type: 'u64', + }, + ], }, { - "name": "claim", - "docs": [ - "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE" + name: 'claim', + docs: [ + "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE", ], - "accounts": [ + accounts: [ { - "name": "funder", - "isMut": true, - "isSigner": true + name: 'funder', + isMut: true, + isSigner: true, }, { - "name": "claimant", - "isMut": false, - "isSigner": true + name: 'claimant', + isMut: false, + isSigner: true, }, { - "name": "claimantFund", - "isMut": true, - "isSigner": false, - "docs": [ + name: 'claimantFund', + isMut: true, + isSigner: false, + docs: [ "Claimant's associated token account to receive the tokens", - "Should be initialized outside of this program." - ] + 'Should be initialized outside of this program.', + ], }, { - "name": "config", - "isMut": false, - "isSigner": false + name: 'config', + isMut: false, + isSigner: false, }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: 'mint', + isMut: false, + isSigner: false, }, { - "name": "treasury", - "isMut": true, - "isSigner": false + name: 'treasury', + isMut: true, + isSigner: false, }, { - "name": "tokenProgram", - "isMut": false, - "isSigner": false + name: 'tokenProgram', + isMut: false, + isSigner: false, }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'systemProgram', + isMut: false, + isSigner: false, }, { - "name": "sysvarInstruction", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked." - ] + name: 'sysvarInstruction', + isMut: false, + isSigner: false, + docs: [ + "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked.", + ], }, { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false - } + name: 'associatedTokenProgram', + isMut: false, + isSigner: false, + }, ], - "args": [ + args: [ { - "name": "claimCertificate", - "type": { - "defined": "ClaimCertificate" - } - } - ] - } + name: 'claimCertificate', + type: { + defined: 'ClaimCertificate', + }, + }, + ], + }, ], - "accounts": [ + accounts: [ { - "name": "Config", - "type": { - "kind": "struct", - "fields": [ + name: 'Config', + type: { + kind: 'struct', + fields: [ { - "name": "bump", - "type": "u8" + name: 'bump', + type: 'u8', }, { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: 'merkleRoot', + type: { + array: ['u8', 20], + }, }, { - "name": "dispenserGuard", - "type": "publicKey" + name: 'dispenserGuard', + type: 'publicKey', }, { - "name": "mint", - "type": "publicKey" + name: 'mint', + type: 'publicKey', }, { - "name": "addressLookupTable", - "type": "publicKey" + name: 'addressLookupTable', + type: 'publicKey', }, { - "name": "maxTransfer", - "type": "u64" - } - ] - } + name: 'maxTransfer', + type: 'u64', + }, + ], + }, }, { - "name": "Receipt", - "type": { - "kind": "struct", - "fields": [] - } - } + name: 'Receipt', + type: { + kind: 'struct', + fields: [], + }, + }, ], - "types": [ + types: [ { - "name": "CosmosMessage", - "docs": [ - "* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key." + name: 'CosmosMessage', + docs: [ + '* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key.', ], - "type": { - "kind": "struct", - "fields": [ + type: { + kind: 'struct', + fields: [ { - "name": "payload", - "type": "bytes" + name: 'payload', + type: 'bytes', }, { - "name": "signer", - "type": "string" - } - ] - } + name: 'signer', + type: 'string', + }, + ], + }, }, { - "name": "DiscordMessage", - "docs": [ - "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else." + name: 'DiscordMessage', + docs: [ + "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else.", ], - "type": { - "kind": "struct", - "fields": [ + type: { + kind: 'struct', + fields: [ { - "name": "username", - "type": "string" + name: 'username', + type: 'string', }, { - "name": "claimant", - "type": "publicKey" - } - ] - } + name: 'claimant', + type: 'publicKey', + }, + ], + }, }, { - "name": "Ed25519InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: 'Ed25519InstructionHeader', + type: { + kind: 'struct', + fields: [ { - "name": "numSignatures", - "type": "u8" + name: 'numSignatures', + type: 'u8', }, { - "name": "padding", - "type": "u8" + name: 'padding', + type: 'u8', }, { - "name": "signatureOffset", - "type": "u16" + name: 'signatureOffset', + type: 'u16', }, { - "name": "signatureInstructionIndex", - "type": "u16" + name: 'signatureInstructionIndex', + type: 'u16', }, { - "name": "publicKeyOffset", - "type": "u16" + name: 'publicKeyOffset', + type: 'u16', }, { - "name": "publicKeyInstructionIndex", - "type": "u16" + name: 'publicKeyInstructionIndex', + type: 'u16', }, { - "name": "messageDataOffset", - "type": "u16" + name: 'messageDataOffset', + type: 'u16', }, { - "name": "messageDataSize", - "type": "u16" + name: 'messageDataSize', + type: 'u16', }, { - "name": "messageInstructionIndex", - "type": "u16" - } - ] - } + name: 'messageInstructionIndex', + type: 'u16', + }, + ], + }, }, { - "name": "Secp256k1InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: 'Secp256k1InstructionHeader', + type: { + kind: 'struct', + fields: [ { - "name": "numSignatures", - "type": "u8" + name: 'numSignatures', + type: 'u8', }, { - "name": "signatureOffset", - "type": "u16" + name: 'signatureOffset', + type: 'u16', }, { - "name": "signatureInstructionIndex", - "type": "u8" + name: 'signatureInstructionIndex', + type: 'u8', }, { - "name": "ethAddressOffset", - "type": "u16" + name: 'ethAddressOffset', + type: 'u16', }, { - "name": "ethAddressInstructionIndex", - "type": "u8" + name: 'ethAddressInstructionIndex', + type: 'u8', }, { - "name": "messageDataOffset", - "type": "u16" + name: 'messageDataOffset', + type: 'u16', }, { - "name": "messageDataSize", - "type": "u16" + name: 'messageDataSize', + type: 'u16', }, { - "name": "messageInstructionIndex", - "type": "u8" - } - ] - } + name: 'messageInstructionIndex', + type: 'u8', + }, + ], + }, }, { - "name": "ClaimInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "identity", - "type": { - "defined": "Identity" - } + name: 'ClaimInfo', + type: { + kind: 'struct', + fields: [ + { + name: 'identity', + type: { + defined: 'Identity', + }, }, { - "name": "amount", - "type": "u64" - } - ] - } + name: 'amount', + type: 'u64', + }, + ], + }, }, { - "name": "ClaimCertificate", - "type": { - "kind": "struct", - "fields": [ + name: 'ClaimCertificate', + type: { + kind: 'struct', + fields: [ { - "name": "amount", - "type": "u64" + name: 'amount', + type: 'u64', }, { - "name": "proofOfIdentity", - "type": { - "defined": "IdentityCertificate" - } + name: 'proofOfIdentity', + type: { + defined: 'IdentityCertificate', + }, }, { - "name": "proofOfInclusion", - "type": { - "vec": { - "array": [ - "u8", - 20 - ] - } - } - } - ] - } + name: 'proofOfInclusion', + type: { + vec: { + array: ['u8', 20], + }, + }, + }, + ], + }, }, { - "name": "Identity", - "docs": [ - "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space." + name: 'Identity', + docs: [ + "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space.", ], - "type": { - "kind": "enum", - "variants": [ + type: { + kind: 'enum', + variants: [ { - "name": "Discord", - "fields": [ + name: 'Discord', + fields: [ { - "name": "username", - "type": "string" - } - ] + name: 'username', + type: 'string', + }, + ], }, { - "name": "Solana", - "fields": [ + name: 'Solana', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: 'pubkey', + type: { + array: ['u8', 32], + }, + }, + ], }, { - "name": "Evm", - "fields": [ + name: 'Evm', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } - } - ] + name: 'pubkey', + type: { + array: ['u8', 20], + }, + }, + ], }, { - "name": "Sui", - "fields": [ + name: 'Sui', + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: 'address', + type: { + array: ['u8', 32], + }, + }, + ], }, { - "name": "Aptos", - "fields": [ + name: 'Aptos', + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: 'address', + type: { + array: ['u8', 32], + }, + }, + ], }, { - "name": "Cosmwasm", - "fields": [ + name: 'Cosmwasm', + fields: [ { - "name": "address", - "type": "string" - } - ] + name: 'address', + type: 'string', + }, + ], }, { - "name": "Injective", - "fields": [ + name: 'Injective', + fields: [ { - "name": "address", - "type": "string" - } - ] + name: 'address', + type: 'string', + }, + ], }, { - "name": "Algorand", - "fields": [ + name: 'Algorand', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] - } - ] - } + name: 'pubkey', + type: { + array: ['u8', 32], + }, + }, + ], + }, + ], + }, }, { - "name": "IdentityCertificate", - "type": { - "kind": "enum", - "variants": [ + name: 'IdentityCertificate', + type: { + kind: 'enum', + variants: [ { - "name": "Discord", - "fields": [ + name: 'Discord', + fields: [ { - "name": "username", - "type": "string" + name: 'username', + type: 'string', }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: 'verification_instruction_index', + type: 'u8', + }, + ], }, { - "name": "Evm", - "fields": [ + name: 'Evm', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: 'pubkey', + type: { + array: ['u8', 20], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: 'verification_instruction_index', + type: 'u8', + }, + ], }, { - "name": "Solana" + name: 'Solana', }, { - "name": "Sui", - "fields": [ + name: 'Sui', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: 'pubkey', + type: { + array: ['u8', 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: 'verification_instruction_index', + type: 'u8', + }, + ], }, { - "name": "Aptos", - "fields": [ + name: 'Aptos', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: 'pubkey', + type: { + array: ['u8', 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: 'verification_instruction_index', + type: 'u8', + }, + ], }, { - "name": "Cosmwasm", - "fields": [ + name: 'Cosmwasm', + fields: [ { - "name": "chain_id", - "type": "string" + name: 'chain_id', + type: 'string', }, { - "name": "signature", - "type": { - "array": [ - "u8", - 64 - ] - } + name: 'signature', + type: { + array: ['u8', 64], + }, }, { - "name": "recovery_id", - "type": "u8" + name: 'recovery_id', + type: 'u8', }, { - "name": "pubkey", - "type": { - "array": [ - "u8", - 65 - ] - } + name: 'pubkey', + type: { + array: ['u8', 65], + }, }, { - "name": "message", - "type": "bytes" - } - ] + name: 'message', + type: 'bytes', + }, + ], }, { - "name": "Injective", - "fields": [ + name: 'Injective', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: 'pubkey', + type: { + array: ['u8', 20], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: 'verification_instruction_index', + type: 'u8', + }, + ], }, { - "name": "Algorand", - "fields": [ + name: 'Algorand', + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: 'pubkey', + type: { + array: ['u8', 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] - } - ] - } - } + name: 'verification_instruction_index', + type: 'u8', + }, + ], + }, + ], + }, + }, ], - "events": [ + events: [ { - "name": "ClaimEvent", - "fields": [ + name: 'ClaimEvent', + fields: [ { - "name": "treasury", - "type": "publicKey", - "index": false + name: 'treasury', + type: 'publicKey', + index: false, }, { - "name": "remainingBalance", - "type": "u64", - "index": false + name: 'remainingBalance', + type: 'u64', + index: false, }, { - "name": "claimant", - "type": "publicKey", - "index": false + name: 'claimant', + type: 'publicKey', + index: false, }, { - "name": "claimInfo", - "type": { - "defined": "ClaimInfo" + name: 'claimInfo', + type: { + defined: 'ClaimInfo', }, - "index": false - } - ] - } + index: false, + }, + ], + }, ], - "errors": [ + errors: [ { - "code": 6000, - "name": "AlreadyClaimed" + code: 6000, + name: 'AlreadyClaimed', }, { - "code": 6001, - "name": "InvalidInclusionProof" + code: 6001, + name: 'InvalidInclusionProof', }, { - "code": 6002, - "name": "WrongPda" + code: 6002, + name: 'WrongPda', }, { - "code": 6003, - "name": "SignatureVerificationWrongProgram" + code: 6003, + name: 'SignatureVerificationWrongProgram', }, { - "code": 6004, - "name": "SignatureVerificationWrongAccounts" + code: 6004, + name: 'SignatureVerificationWrongAccounts', }, { - "code": 6005, - "name": "SignatureVerificationWrongHeader" + code: 6005, + name: 'SignatureVerificationWrongHeader', }, { - "code": 6006, - "name": "SignatureVerificationWrongPayload" + code: 6006, + name: 'SignatureVerificationWrongPayload', }, { - "code": 6007, - "name": "SignatureVerificationWrongPayloadMetadata" + code: 6007, + name: 'SignatureVerificationWrongPayloadMetadata', }, { - "code": 6008, - "name": "SignatureVerificationWrongSigner" + code: 6008, + name: 'SignatureVerificationWrongSigner', }, { - "code": 6009, - "name": "UnauthorizedCosmosChainId" + code: 6009, + name: 'UnauthorizedCosmosChainId', }, { - "code": 6010, - "name": "TransferExceedsMax" - } - ] + code: 6010, + name: 'TransferExceedsMax', + }, + ], } -; diff --git a/frontend/claim_sdk/solana.ts b/frontend/claim_sdk/solana.ts index 33b069ec..a079ef8f 100644 --- a/frontend/claim_sdk/solana.ts +++ b/frontend/claim_sdk/solana.ts @@ -27,6 +27,8 @@ import { TOKEN_PROGRAM_ID, Token } from '@solana/spl-token' import { SignedMessage } from './ecosystems/signatures' import { extractChainId } from './ecosystems/cosmos' import { fetchFundTransaction } from '../utils/api' +import { getClaimPayers } from './treasury' +import { inspect } from 'util' export const ERROR_SIGNING_TX = 'error: signing transaction' export const ERROR_FUNDING_TX = 'error: funding transaction' @@ -41,6 +43,11 @@ const AUTHORIZATION_PAYLOAD = [ '\nto claim my W tokens.\n', ] +export type TransactionWithPayers = { + tx: VersionedTransaction + payers: [PublicKey, PublicKey] +} + /** * This class wraps the interaction with the TokenDispenser * program for a specific claimant. The claimant will be the @@ -220,47 +227,66 @@ export class TokenDispenserProvider { public async submitClaims( claims: { - funder: PublicKey - treasury: PublicKey claimInfo: ClaimInfo proofOfInclusion: Uint8Array[] signedMessage: SignedMessage | undefined }[], fetchFundTransactionFunction: ( - transactions: VersionedTransaction[] - ) => Promise = fetchFundTransaction // This argument is only used for testing where we can't call the API + transactions: TransactionWithPayers[] + ) => Promise = fetchFundTransaction, // This argument is only used for testing where we can't call the API + getPayersForClaim: ( + claimInfo: ClaimInfo + ) => [anchor.web3.PublicKey, anchor.web3.PublicKey] = getClaimPayers // This argument is only used for testing where we can't call the API ): Promise[]> { - const txs: VersionedTransaction[] = [] + const txs: TransactionWithPayers[] = [] try { for (const claim of claims) { - txs.push( - await this.generateClaimTransaction( - claim.funder, - claim.treasury, + const [funder, treasury] = getPayersForClaim(claim.claimInfo) + + txs.push({ + tx: await this.generateClaimTransaction( + funder, + treasury, claim.claimInfo, claim.proofOfInclusion, claim.signedMessage - ) - ) + ), + payers: [funder, treasury], + }) } } catch (e) { + console.error(e) throw new Error(ERROR_CRAFTING_TX) } - let txsSignedOnce + let txsSignedOnce: VersionedTransaction[] + try { txsSignedOnce = await ( this.tokenDispenserProgram.provider as anchor.AnchorProvider - ).wallet.signAllTransactions(txs) + ).wallet.signAllTransactions(txs.map((tx) => tx.tx)) } catch (e) { + console.error(e) throw new Error(ERROR_SIGNING_TX) } + const txsSignedOnceWithPayers: TransactionWithPayers[] = txsSignedOnce.map( + (tx, index) => { + return { + tx: tx, + payers: txs[index].payers, + } + } + ) + let txsSignedTwice try { - txsSignedTwice = await fetchFundTransactionFunction(txsSignedOnce) + txsSignedTwice = await fetchFundTransactionFunction( + txsSignedOnceWithPayers + ) } catch (e) { + console.error(e) throw new Error(ERROR_FUNDING_TX) } diff --git a/frontend/claim_sdk/testWallets.ts b/frontend/claim_sdk/testWallets.ts index c7f264b4..e91ef6ac 100644 --- a/frontend/claim_sdk/testWallets.ts +++ b/frontend/claim_sdk/testWallets.ts @@ -1,6 +1,6 @@ import path from 'path' import dotenv from 'dotenv' -import { Ecosystem } from './claim' +import { ClaimInfo, Ecosystem } from './claim' import { SignedMessage, cosmwasmBuildSignedMessage, @@ -25,6 +25,7 @@ import { getInjectiveAddress } from '../utils/getInjectiveAddress' import { algorandGetFullMessage } from './ecosystems/algorand' import { getAlgorandAddress } from '../utils/getAlgorandAddress' import nacl from 'tweetnacl' +import { inspect } from 'util' dotenv.config() // Load environment variables from .env file @@ -50,18 +51,40 @@ export function loadAnchorWallet(): NodeWallet { return new NodeWallet(keypair) } -export function loadFunderWallet(): NodeWallet { - const keypair = Keypair.fromSecretKey( - new Uint8Array( - JSON.parse( - fs.readFileSync( - path.resolve(KEY_DIR, 'funder_private_key.json'), - 'utf-8' +export function loadFunderWallets(): Record { + const result: Record = {} + let keypair: Keypair + if (process.env.FUNDER_KEYPAIR) { + keypair = Keypair.fromSecretKey( + Uint8Array.from(JSON.parse(process.env.FUNDER_KEYPAIR)) + ) + } else { + keypair = Keypair.fromSecretKey( + new Uint8Array( + JSON.parse( + fs.readFileSync( + path.resolve(KEY_DIR, 'funder_private_key.json'), + 'utf-8' + ) ) ) ) - ) - return new NodeWallet(keypair) + } + + result[keypair.publicKey.toBase58()] = new NodeWallet(keypair) + + // console.log('Funder wallets:', inspect(result, false, null, true)); + + return result +} + +export function getTestClaimPayers(treasury: PublicKey) { + return function (claimInfo: ClaimInfo): [PublicKey, PublicKey] { + const funder = Keypair.fromSecretKey( + Object.entries(loadFunderWallets())[0][1].payer.secretKey + ) + return [funder.publicKey, treasury] + } } export async function loadTestWallets(): Promise< diff --git a/frontend/claim_sdk/treasury.ts b/frontend/claim_sdk/treasury.ts new file mode 100644 index 00000000..cec841d4 --- /dev/null +++ b/frontend/claim_sdk/treasury.ts @@ -0,0 +1,32 @@ +import { ClaimInfo } from './claim' + +import { PublicKey } from '@solana/web3.js' + +export const treasuries = [ + new PublicKey('waTh9vNmRz8hHxQv1zW81ExKQ7fwaa1TR6tQT5fTkjg'), + new PublicKey('waTirsbdRyyxm8DFxH9Z9P5Pyrj74jJ7E74zqhmGX1k'), + new PublicKey('wAtMcoqwHb7obEL2USNXPqxg1CmJDSjjjsNcEm9bzCZ'), + new PublicKey('Wats1sM7W4UvxUuQPevGxexgcaFiaXMXxActKUmcvHx'), + new PublicKey('waTtgLt1WVRgoWYNZh97AYAUqoU4nN98jVThPdYQVwj'), + new PublicKey('WaTxa5w4TjYHRdHwG2zaN9ynA1c9z4J9TswsBCmzy4N'), +] + +export const funders = [ + new PublicKey('wAf15NnY6VVVQXDSdhp79LndkHXSD9BmcUhh4bqSSGp'), + new PublicKey('WafepN7ToYGS4WjaqtMV8g2q3YV1GgcoUZ4nvK4LhD8'), + new PublicKey('wAfmKAiAgtfywPhfRdLQ7uMeS2RW3kN57kodaRabX6V'), + new PublicKey('WAfN8P3NdYeTnnvFPKYUiveyqZSZtBboxUyrMSAzQc1'), + new PublicKey('wafydZpGcTLk3FYEQ5Q4wwSKJXMy3EMTngxeVEXG45e'), + new PublicKey('WafzGQMEqwABhMdNHH86yDRqzZQvK5rELK5Nrto97Va'), +] + +export function getClaimPayers(claimInfo: ClaimInfo): [PublicKey, PublicKey] { + const buffer = claimInfo.toBuffer() + const toHex = buffer.toString('hex') + const toNumber = parseInt(toHex, 16) + const funderIndex = toNumber % funders.length + + const treasuryIndex = toNumber % treasuries.length + + return [funders[funderIndex], treasuries[treasuryIndex]] +} diff --git a/frontend/hooks/useTokenDispenserProvider.tsx b/frontend/hooks/useTokenDispenserProvider.tsx index a9297ff9..5f5c221d 100644 --- a/frontend/hooks/useTokenDispenserProvider.tsx +++ b/frontend/hooks/useTokenDispenserProvider.tsx @@ -2,6 +2,7 @@ import { TokenDispenserProvider as CTokenDispenserProvider } from 'claim_sdk/sol import { useMemo } from 'react' import { useAnchorWallet } from '@solana/wallet-adapter-react' import { web3 } from '@coral-xyz/anchor' +import { tokenDispenserProgramId } from '../utils/constants' // It will return undefined if no Solana wallet is connected. export function useTokenDispenserProvider() { @@ -11,7 +12,7 @@ export function useTokenDispenserProvider() { return new CTokenDispenserProvider( process.env.ENDPOINT!, anchorWallet, - new web3.PublicKey(process.env.PROGRAM_ID!) + new web3.PublicKey(tokenDispenserProgramId) ) }, [anchorWallet]) } diff --git a/frontend/integration/api.test.ts b/frontend/integration/api.test.ts index b49af9e4..d8204c7e 100644 --- a/frontend/integration/api.test.ts +++ b/frontend/integration/api.test.ts @@ -15,7 +15,7 @@ import { import dotenv from 'dotenv' import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' import { ethers } from 'ethers' -import { loadFunderWallet } from '../claim_sdk/testWallets' +import { loadFunderWallets } from '../claim_sdk/testWallets' import { mockfetchFundTransaction } from './api' import { checkAllProgramsWhitelisted, @@ -26,18 +26,27 @@ import { checkV0, countTotalSignatures, } from '../utils/verifyTransaction' +import { treasuries } from '../claim_sdk/treasury' +import { tokenDispenserProgramId } from '../utils/constants' dotenv.config() -const PROGRAM_ID = new PublicKey(process.env.PROGRAM_ID!) +const tokenDispenserPublicKey = new PublicKey(tokenDispenserProgramId) const WHITELISTED_PROGRAMS: PublicKey[] = [ - PROGRAM_ID, + tokenDispenserPublicKey, Secp256k1Program.programId, Ed25519Program.programId, ComputeBudgetProgram.programId, ] const RANDOM_BLOCKHASH = 'HXq5QPm883r7834LWwDpcmEM8G8uQ9Hqm1xakCHGxprV' -const funderPubkey = loadFunderWallet().publicKey +const funderWallets = loadFunderWallets() +const funderPubkey = Object.entries(funderWallets)[0][1].publicKey + +function getTestPayers(): [PublicKey, PublicKey] { + const wallets = loadFunderWallets() + const walletsArray = Object.entries(wallets) + return [walletsArray[0][1].publicKey, treasuries[0]] +} function createTestTransactionFromInstructions( instructions: TransactionInstruction[] @@ -55,7 +64,7 @@ describe('test fund transaction api', () => { it('tests the api', async () => { const tokenDispenser = new Program( IDL as any, - PROGRAM_ID, + tokenDispenserPublicKey, new AnchorProvider( new Connection('http://localhost:8899'), new NodeWallet(new Keypair()), @@ -162,94 +171,154 @@ describe('test fund transaction api', () => { }).compileToLegacyMessage() ) - await mockfetchFundTransaction([transactionOK1]) + await mockfetchFundTransaction([ + { + tx: transactionOK1, + payers: getTestPayers(), + }, + ]) expect( - checkTransactions([transactionOK1], PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransactions( + [transactionOK1], + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(true) - await mockfetchFundTransaction([transactionOK2]) + await mockfetchFundTransaction([ + { + tx: transactionOK2, + payers: getTestPayers(), + }, + ]) expect( - checkTransactions([transactionOK2], PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransactions( + [transactionOK2], + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(true) await expect( - mockfetchFundTransaction([transactionTooManySigs]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionTooManySigs, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionTooManySigs], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionBadTransfer]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionBadTransfer, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionBadTransfer], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionBadNoTokenDispenser]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionBadNoTokenDispenser, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionBadNoTokenDispenser], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionBadComputeHeap]).catch((e) => e) + mockfetchFundTransaction([ + { tx: transactionBadComputeHeap, payers: getTestPayers() }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionBadComputeHeap], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionBadTransfer2]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionBadTransfer2, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionBadTransfer2], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionBadTransfer3]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionBadTransfer3, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionBadTransfer3], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( - mockfetchFundTransaction([transactionLegacy]).catch((e) => e) + mockfetchFundTransaction([ + { + tx: transactionLegacy, + payers: getTestPayers(), + }, + ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( - checkTransactions([transactionLegacy], PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransactions( + [transactionLegacy], + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(false) // More granular tests expect( - checkTransaction(transactionOK1, PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransaction( + transactionOK1, + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(true) - expect(checkProgramAppears(transactionOK1, PROGRAM_ID)).toBe(true) + expect(checkProgramAppears(transactionOK1, tokenDispenserPublicKey)).toBe( + true + ) expect( checkAllProgramsWhitelisted(transactionOK1, WHITELISTED_PROGRAMS) ).toBe(true) @@ -260,9 +329,15 @@ describe('test fund transaction api', () => { expect(countTotalSignatures(transactionOK1)).toBe(2) expect( - checkTransaction(transactionOK2, PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransaction( + transactionOK2, + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(true) - expect(checkProgramAppears(transactionOK2, PROGRAM_ID)).toBe(true) + expect(checkProgramAppears(transactionOK2, tokenDispenserPublicKey)).toBe( + true + ) expect( checkAllProgramsWhitelisted(transactionOK2, WHITELISTED_PROGRAMS) ).toBe(true) @@ -273,9 +348,15 @@ describe('test fund transaction api', () => { expect(countTotalSignatures(transactionOK2)).toBe(3) expect( - checkTransaction(transactionTooManySigs, PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransaction( + transactionTooManySigs, + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(false) - expect(checkProgramAppears(transactionTooManySigs, PROGRAM_ID)).toBe(true) + expect( + checkProgramAppears(transactionTooManySigs, tokenDispenserPublicKey) + ).toBe(true) expect( checkAllProgramsWhitelisted(transactionTooManySigs, WHITELISTED_PROGRAMS) ).toBe(true) @@ -288,9 +369,15 @@ describe('test fund transaction api', () => { expect(countTotalSignatures(transactionTooManySigs)).toBe(4) expect( - checkTransaction(transactionBadTransfer, PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransaction( + transactionBadTransfer, + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) + ).toBe(false) + expect( + checkProgramAppears(transactionBadTransfer, tokenDispenserPublicKey) ).toBe(false) - expect(checkProgramAppears(transactionBadTransfer, PROGRAM_ID)).toBe(false) expect( checkAllProgramsWhitelisted(transactionBadTransfer, WHITELISTED_PROGRAMS) ).toBe(false) @@ -305,12 +392,15 @@ describe('test fund transaction api', () => { expect( checkTransaction( transactionBadNoTokenDispenser, - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) expect( - checkProgramAppears(transactionBadNoTokenDispenser, PROGRAM_ID) + checkProgramAppears( + transactionBadNoTokenDispenser, + tokenDispenserPublicKey + ) ).toBe(false) expect( checkAllProgramsWhitelisted( @@ -329,13 +419,13 @@ describe('test fund transaction api', () => { expect( checkTransaction( transactionBadComputeHeap, - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) - expect(checkProgramAppears(transactionBadComputeHeap, PROGRAM_ID)).toBe( - true - ) + expect( + checkProgramAppears(transactionBadComputeHeap, tokenDispenserPublicKey) + ).toBe(true) expect( checkAllProgramsWhitelisted( transactionBadComputeHeap, @@ -353,11 +443,13 @@ describe('test fund transaction api', () => { expect( checkTransaction( transactionBadTransfer2, - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) - expect(checkProgramAppears(transactionBadTransfer2, PROGRAM_ID)).toBe(true) + expect( + checkProgramAppears(transactionBadTransfer2, tokenDispenserPublicKey) + ).toBe(true) expect( checkAllProgramsWhitelisted(transactionBadTransfer2, WHITELISTED_PROGRAMS) ).toBe(false) @@ -372,11 +464,13 @@ describe('test fund transaction api', () => { expect( checkTransaction( transactionBadTransfer3, - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) - expect(checkProgramAppears(transactionBadTransfer3, PROGRAM_ID)).toBe(true) + expect( + checkProgramAppears(transactionBadTransfer3, tokenDispenserPublicKey) + ).toBe(true) expect( checkAllProgramsWhitelisted(transactionBadTransfer3, WHITELISTED_PROGRAMS) ).toBe(false) @@ -389,9 +483,15 @@ describe('test fund transaction api', () => { expect(countTotalSignatures(transactionBadTransfer3)).toBe(3) expect( - checkTransaction(transactionLegacy, PROGRAM_ID, WHITELISTED_PROGRAMS) + checkTransaction( + transactionLegacy, + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) ).toBe(false) - expect(checkProgramAppears(transactionLegacy, PROGRAM_ID)).toBe(true) + expect( + checkProgramAppears(transactionLegacy, tokenDispenserPublicKey) + ).toBe(true) expect( checkAllProgramsWhitelisted(transactionLegacy, WHITELISTED_PROGRAMS) ).toBe(true) @@ -402,40 +502,67 @@ describe('test fund transaction api', () => { expect(countTotalSignatures(transactionLegacy)).toBe(2) // Grouped transactions - await mockfetchFundTransaction([transactionOK1, transactionOK2]) + await mockfetchFundTransaction([ + { + tx: transactionOK1, + payers: getTestPayers(), + }, + { + tx: transactionOK2, + payers: getTestPayers(), + }, + ]) expect( checkTransactions( [transactionOK1, transactionOK2], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(true) await expect( mockfetchFundTransaction([ - transactionOK1, - transactionBadTransfer3, - transactionOK2, + { + tx: transactionOK1, + payers: getTestPayers(), + }, + { + tx: transactionBadTransfer3, + payers: getTestPayers(), + }, + { + tx: transactionOK2, + payers: getTestPayers(), + }, ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionOK1, transactionBadTransfer3, transactionOK2], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) await expect( mockfetchFundTransaction([ - transactionOK1, - transactionOK2, - transactionBadComputeHeap, + { + tx: transactionOK1, + payers: getTestPayers(), + }, + { + tx: transactionOK2, + payers: getTestPayers(), + }, + { + tx: transactionBadComputeHeap, + payers: getTestPayers(), + }, ]).catch((e) => e) ).resolves.toThrow('Unauthorized transaction') expect( checkTransactions( [transactionOK1, transactionOK2, transactionBadComputeHeap], - PROGRAM_ID, + tokenDispenserPublicKey, WHITELISTED_PROGRAMS ) ).toBe(false) diff --git a/frontend/integration/api.ts b/frontend/integration/api.ts index 19dcb9c6..f812335a 100644 --- a/frontend/integration/api.ts +++ b/frontend/integration/api.ts @@ -1,40 +1,34 @@ -import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' import { ComputeBudgetProgram, Ed25519Program, - Keypair, PublicKey, Secp256k1Program, VersionedTransaction, } from '@solana/web3.js' import { NextApiRequest, NextApiResponse } from 'next' import { + TransactionWithPayers, getFundTransactionRoute, handleAmountAndProofResponse, handleFundTransaction, } from '../utils/api' import { ClaimInfo, Ecosystem } from '../claim_sdk/claim' -import { loadFunderWallet } from '../claim_sdk/testWallets' +import { loadFunderWallets } from '../claim_sdk/testWallets' import { checkTransactions } from '../utils/verifyTransaction' -import fs from 'fs' -import path from 'path' import { getInMemoryDb } from './utils' +import { tokenDispenserProgramId } from '../utils/constants' -//import handlerAmountAndProof from '../pages/api/grant/v1/amount_and_proof' -//import handlerFundTransaction from '../pages/api/grant/v1/fund_transaction' +export type TransactionWithFunder = { + transaction: VersionedTransaction + funder: string +} -const wallet = process.env.FUNDER_KEYPAIR - ? new NodeWallet( - Keypair.fromSecretKey( - Uint8Array.from(JSON.parse(process.env.FUNDER_KEYPAIR)) - ) - ) - : loadFunderWallet() +const wallets = loadFunderWallets() -const PROGRAM_ID = new PublicKey(process.env.PROGRAM_ID!) +const tokenDispenserPublicKey = new PublicKey(tokenDispenserProgramId) const WHITELISTED_PROGRAMS: PublicKey[] = [ - PROGRAM_ID, + tokenDispenserPublicKey, Secp256k1Program.programId, Ed25519Program.programId, ComputeBudgetProgram.programId, @@ -92,7 +86,7 @@ export default async function handlerFundTransaction( } const data = req.body - let transactions: VersionedTransaction[] = [] + let transactions: TransactionWithFunder[] = [] let signedTransactions: VersionedTransaction[] = [] if (data.length >= 10) { @@ -103,18 +97,40 @@ export default async function handlerFundTransaction( try { transactions = data.map((serializedTx: any) => { - return VersionedTransaction.deserialize(Buffer.from(serializedTx)) + return { + transaction: VersionedTransaction.deserialize( + Buffer.from(serializedTx.tx) + ), + funder: serializedTx.funder, + } }) - } catch { + } catch (e) { + console.error(e) return res.status(400).json({ error: 'Failed to deserialize transactions', }) } - if (checkTransactions(transactions, PROGRAM_ID, WHITELISTED_PROGRAMS)) { + if ( + checkTransactions( + transactions.map((tx) => tx.transaction), + tokenDispenserPublicKey, + WHITELISTED_PROGRAMS + ) + ) { try { - signedTransactions = await wallet.signAllTransactions(transactions) - } catch { + for (const txWithFunder of transactions) { + const wallet = wallets[txWithFunder.funder] + if (!wallet) { + return res.status(403).json({ error: 'Unauthorized funder' }) + } + + signedTransactions.push( + await wallet.signTransaction(txWithFunder.transaction) + ) + } + } catch (e) { + console.error('Failed to sign transactions', e) return res.status(400).json({ error: 'Failed to sign transactions, make sure the transactions have the right funder', @@ -145,7 +161,7 @@ export class NextApiResponseMock { } } export async function mockfetchFundTransaction( - transactions: VersionedTransaction[] + transactions: TransactionWithPayers[] ): Promise { const req: NextApiRequest = { url: getFundTransactionRoute(), @@ -153,7 +169,12 @@ export async function mockfetchFundTransaction( headers: { 'Content-Type': 'application/json', }, - body: transactions.map((tx) => Buffer.from(tx.serialize())), + body: transactions.map((txWitPayers) => { + return { + tx: Buffer.from(txWitPayers.tx.serialize()), + funder: txWitPayers.payers[0].toBase58(), + } + }), } as unknown as NextApiRequest const res = new NextApiResponseMock() await handlerFundTransaction(req, res as unknown as NextApiResponse) diff --git a/frontend/integration/integrationTest.test.ts b/frontend/integration/integrationTest.test.ts index 0640c2ef..68bd9c1a 100644 --- a/frontend/integration/integrationTest.test.ts +++ b/frontend/integration/integrationTest.test.ts @@ -15,8 +15,9 @@ import { TokenDispenserEventSubscriber } from '../claim_sdk/eventSubscriber' import { DiscordTestWallet, TestWallet, + getTestClaimPayers, loadAnchorWallet, - loadFunderWallet, + loadFunderWallets, } from '../claim_sdk/testWallets' import { loadTestWallets } from '../claim_sdk/testWallets' import { mockFetchAmountAndProof, mockfetchFundTransaction } from './api' @@ -71,10 +72,10 @@ describe('integration test', () => { describe('token dispenser e2e', () => { const wallet = loadAnchorWallet() - const funderWallet = loadFunderWallet() + const funderWallet = Object.entries(loadFunderWallets())[0][1] const endpoint = 'http://127.0.0.1:8899' const tokenDispenserPid = new PublicKey( - 'WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC' + 'WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA' ) const confirmOpts: anchor.web3.ConfirmOptions = { @@ -188,7 +189,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -244,7 +246,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -321,7 +324,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( claims, - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -349,39 +353,6 @@ describe('integration test', () => { ).toBeTruthy() }) - it('submits an injective claim', async () => { - const wallet = testWallets.injective[0] - const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( - 'injective', - wallet.address() - ))! - const signedMessage = await wallet.signMessage( - tokenDispenserProvider.generateAuthorizationPayload() - ) - - await Promise.all( - await tokenDispenserProvider.submitClaims( - [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction - ) - ) - - expect( - await tokenDispenserProvider.isClaimAlreadySubmitted(claimInfo) - ).toBeTruthy() - - const claimantFundPubkey = - await tokenDispenserProvider.getClaimantFundAddress() - - const claimantFund = await mint.getAccountInfo(claimantFundPubkey) - - expect( - claimantFund.amount.eq( - new anchor.BN(3000000 + 6000000 + 6100000 + 6200000 + 7000000) - ) - ).toBeTruthy() - }, 40000) - it('submits an aptos claim', async () => { const wallet = testWallets.aptos[0] const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( @@ -395,7 +366,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -432,7 +404,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -466,7 +439,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, undefined)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -501,7 +475,8 @@ describe('integration test', () => { await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) @@ -543,7 +518,8 @@ describe('integration test', () => { const res = await Promise.all( await tokenDispenserProvider.submitClaims( [makeClaim(claimInfo, proofOfInclusion, signedMessage)], - mockfetchFundTransaction + mockfetchFundTransaction, + getTestClaimPayers(treasury) ) ) expect(JSON.stringify(res[0]).includes('InstructionError')).toBeTruthy() diff --git a/frontend/next.config.js b/frontend/next.config.js index 402a3a3a..4980c0f0 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -22,7 +22,6 @@ module.exports = withBundleAnalyzer({ env: { ENDPOINT: process.env.ENDPOINT, CLUSTER: process.env.CLUSTER, - PROGRAM_ID: process.env.PROGRAM_ID, }, webpack: (config, { isServer }) => { if (!isServer) { diff --git a/frontend/scripts/datadog.ts b/frontend/scripts/datadog.ts index 63b6c51d..31e109d8 100644 --- a/frontend/scripts/datadog.ts +++ b/frontend/scripts/datadog.ts @@ -20,9 +20,9 @@ import { } from '@datadog/datadog-api-client/dist/packages/datadog-api-client-v1/models/EventAlertType' import { envOrErr } from '../claim_sdk' import { BN } from '@coral-xyz/anchor' +import { tokenDispenserProgramId } from '../utils/constants' const ENDPOINT = envOrErr('ENDPOINT') -const PROGRAM_ID = envOrErr('PROGRAM_ID') const CLUSTER = envOrErr('CLUSTER') const TIME_WINDOW_SECS = Number.parseInt(envOrErr('TIME_WINDOW_SECS'), 10) const CHUNK_SIZE = Number.parseInt(envOrErr('CHUNK_SIZE'), 10) @@ -41,7 +41,7 @@ const MAX_AMOUNT_PER_ECOSYSTEM = new Map([ async function main() { const tokenDispenserEventSubscriber = new TokenDispenserEventSubscriber( ENDPOINT, - new anchor.web3.PublicKey(PROGRAM_ID), + new anchor.web3.PublicKey(tokenDispenserProgramId), TIME_WINDOW_SECS, CHUNK_SIZE, { diff --git a/frontend/sections/SignAndClaim.tsx b/frontend/sections/SignAndClaim.tsx index 281fc1fb..dbc051d0 100644 --- a/frontend/sections/SignAndClaim.tsx +++ b/frontend/sections/SignAndClaim.tsx @@ -106,10 +106,6 @@ export const SignAndClaim = ({ onBack, onProceed }: SignAndClaimProps) => { try { broadcastPromises = await tokenDispenser?.submitClaims( claims.map((claim) => ({ - //TODO FIXME set funder for this claim - funder: tokenDispenser.getConfigPda()[0], - //TODO FIXME set treasury for this claim - treasury: tokenDispenser.getConfigPda()[0], ...claim, })) ) diff --git a/frontend/utils/api.ts b/frontend/utils/api.ts index 633ae4d7..cf8a40ec 100644 --- a/frontend/utils/api.ts +++ b/frontend/utils/api.ts @@ -4,6 +4,7 @@ import { HASH_SIZE } from '../claim_sdk/merkleTree' import { PublicKey, VersionedTransaction } from '@solana/web3.js' import { SignedMessage } from '../claim_sdk/ecosystems/signatures' import { ECOSYSTEM_IDS } from './constants' +import { inspect } from 'util' const MERKLE_PROOFS = process.env.NEXT_PUBLIC_MERKLE_PROOFS const BACKEND_API = process.env.NEXT_PUBLIC_BACKEND_API @@ -142,15 +143,27 @@ export function handleFundTransaction( } } +export type TransactionWithPayers = { + tx: VersionedTransaction + payers: [PublicKey, PublicKey] +} + export async function fetchFundTransaction( - transactions: VersionedTransaction[] + transactions: TransactionWithPayers[] ): Promise { const response = await fetch(getFundTransactionRoute(), { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(transactions.map((tx) => Buffer.from(tx.serialize()))), + body: JSON.stringify( + transactions.map((txWithPayers) => { + return { + tx: Buffer.from(txWithPayers.tx.serialize()), + funder: txWithPayers.payers[0].toBase58(), + } + }) + ), }) return handleFundTransaction(response.status, await response.json()) diff --git a/frontend/utils/constants.ts b/frontend/utils/constants.ts index 0c19b39b..936e9207 100644 --- a/frontend/utils/constants.ts +++ b/frontend/utils/constants.ts @@ -39,3 +39,6 @@ export const EVM_CHAINS = [ ] as const export type EvmChains = typeof EVM_CHAINS[number] + +export const tokenDispenserProgramId = + 'WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA' diff --git a/frontend/utils/isClaimAlreadySubmitted.ts b/frontend/utils/isClaimAlreadySubmitted.ts index 3ca234de..1331fcf0 100644 --- a/frontend/utils/isClaimAlreadySubmitted.ts +++ b/frontend/utils/isClaimAlreadySubmitted.ts @@ -3,13 +3,14 @@ import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet' import { Keypair } from '@solana/web3.js' import { ClaimInfo } from 'claim_sdk/claim' import { TokenDispenserProvider } from 'claim_sdk/solana' +import { tokenDispenserProgramId } from './constants' // Tokendispenser with randomly generated keypair. Since we don't need a // specific one to check if claims were already submitted const tokenDispenser = new TokenDispenserProvider( process.env.ENDPOINT!, new NodeWallet(new Keypair()), - new web3.PublicKey(process.env.PROGRAM_ID!) + new web3.PublicKey(tokenDispenserProgramId) ) // isClaimAlreadySubmitted help us check if a claim has already been submitted or not. diff --git a/frontend/utils/toStringWithDecimals.ts b/frontend/utils/toStringWithDecimals.ts index 2b8552f3..9e075134 100644 --- a/frontend/utils/toStringWithDecimals.ts +++ b/frontend/utils/toStringWithDecimals.ts @@ -1,7 +1,7 @@ import BN from 'bn.js' const TRAILING_ZEROS = new RegExp(/\.?0+$/) -const W_DECIMALS = 9 +const W_DECIMALS = 6 export function toStringWithDecimals(amount: BN) { const padded = amount.toString().padStart(W_DECIMALS + 1, '0') diff --git a/token-dispenser/Anchor.toml b/token-dispenser/Anchor.toml index c0bdf069..922e13cf 100644 --- a/token-dispenser/Anchor.toml +++ b/token-dispenser/Anchor.toml @@ -14,13 +14,13 @@ cluster = "devnet" wallet = "../frontend/integration/keys/funder_private_key.json" [programs.localnet] -token_dispenser = "WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC" +token_dispenser = "WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA" [programs.mainnet] -token_dispenser = "WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC" +token_dispenser = "WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA" [programs.devnet] -token_dispenser = "WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC" +token_dispenser = "WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA" [scripts] test = "cargo test-bpf" diff --git a/token-dispenser/package-lock.json b/token-dispenser/package-lock.json index cf7862c5..ac9da631 100644 --- a/token-dispenser/package-lock.json +++ b/token-dispenser/package-lock.json @@ -7,6 +7,7 @@ "devDependencies": { "@coral-xyz/anchor": "0.29.0", "@solana/spl-token": "^0.4.1", + "@solana/web3.js": "^1.91.1", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", diff --git a/token-dispenser/package.json b/token-dispenser/package.json index 51176163..958d5b5e 100644 --- a/token-dispenser/package.json +++ b/token-dispenser/package.json @@ -10,6 +10,7 @@ "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", "@wormhole-foundation/sdk-base": "^0.5.0-beta.9", + "@solana/web3.js": "^1.91.1", "@xlabs-xyz/ledger-signer-solana": "^0.0.1", "chai": "^4.3.4", "mocha": "^9.0.3", diff --git a/token-dispenser/programs/token-dispenser/src/lib.rs b/token-dispenser/programs/token-dispenser/src/lib.rs index 291ea516..3a218370 100644 --- a/token-dispenser/programs/token-dispenser/src/lib.rs +++ b/token-dispenser/programs/token-dispenser/src/lib.rs @@ -73,7 +73,7 @@ mod tests; mod ecosystems; -declare_id!("WApA1JC9eJLaULc2Ximo5TffuqCESzf47JZsuhYvfzC"); +declare_id!("WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA"); const CONFIG_SEED: &[u8] = b"config"; const RECEIPT_SEED: &[u8] = b"receipt"; diff --git a/token-dispenser/scripts/create-treasuries.sh b/token-dispenser/scripts/create-treasuries.sh index 5cadd384..bcfb107b 100755 --- a/token-dispenser/scripts/create-treasuries.sh +++ b/token-dispenser/scripts/create-treasuries.sh @@ -29,5 +29,3 @@ while IFS= read -r TREASURY; do --fee-payer usb://ledger?key=$LEDGER_CLI_DERIVATION_PATH \ --owner usb://ledger?key=$LEDGER_CLI_DERIVATION_PATH done < "$(dirname "$0")/treasuries" - - diff --git a/token-dispenser/scripts/fund-treasuries.sh b/token-dispenser/scripts/fund-treasuries.sh old mode 100644 new mode 100755 index 2e01a009..d8d488c0 --- a/token-dispenser/scripts/fund-treasuries.sh +++ b/token-dispenser/scripts/fund-treasuries.sh @@ -18,5 +18,3 @@ while IFS= read -r TREASURY; do --fee-payer usb://ledger?key=$LEDGER_CLI_DERIVATION_PATH \ --mint-authority usb://ledger?key=$LEDGER_CLI_DERIVATION_PATH done < "$(dirname "$0")/treasuries" - - diff --git a/token-dispenser/scripts/treasuries b/token-dispenser/scripts/treasuries index fd794c02..c31d9682 100644 --- a/token-dispenser/scripts/treasuries +++ b/token-dispenser/scripts/treasuries @@ -1,6 +1,6 @@ -WAT5qnYC6k6UUyRx5nH1sFpSHA7k9jjJ2QNKytwpprG -WAt8sFNiekVbH1ssH2dqoCEaXUU1ZD2vs1F8V4y1nVh -WaTaaPXBpshpGyAcctT4JatEicmavMqv8JR93gRtYeQ -WaTUZowJtRjyiUnxLy2nwNij9G9EuGpfiQGrNM8st1B -WaTyrB78jtyZun9JcAF2psWUFhurzxotckJr9J96ksS -wAtZcn6ZeL3pxtDJJ19LJ5qRb5YewtUdr6CTYRjLfZL \ No newline at end of file +waTh9vNmRz8hHxQv1zW81ExKQ7fwaa1TR6tQT5fTkjg +waTirsbdRyyxm8DFxH9Z9P5Pyrj74jJ7E74zqhmGX1k +wAtMcoqwHb7obEL2USNXPqxg1CmJDSjjjsNcEm9bzCZ +Wats1sM7W4UvxUuQPevGxexgcaFiaXMXxActKUmcvHx +waTtgLt1WVRgoWYNZh97AYAUqoU4nN98jVThPdYQVwj +WaTxa5w4TjYHRdHwG2zaN9ynA1c9z4J9TswsBCmzy4N diff --git a/token-dispenser/ts/scripts/config.ts b/token-dispenser/ts/scripts/config.ts index 558a75ff..ec22b8a9 100644 --- a/token-dispenser/ts/scripts/config.ts +++ b/token-dispenser/ts/scripts/config.ts @@ -1,12 +1,12 @@ import { PublicKey } from "@solana/web3.js"; export const treasuries = [ - new PublicKey("WAT5qnYC6k6UUyRx5nH1sFpSHA7k9jjJ2QNKytwpprG"), - new PublicKey("WAt8sFNiekVbH1ssH2dqoCEaXUU1ZD2vs1F8V4y1nVh"), - new PublicKey("WaTaaPXBpshpGyAcctT4JatEicmavMqv8JR93gRtYeQ"), - new PublicKey("WaTUZowJtRjyiUnxLy2nwNij9G9EuGpfiQGrNM8st1B"), - new PublicKey("WaTyrB78jtyZun9JcAF2psWUFhurzxotckJr9J96ksS"), - new PublicKey("wAtZcn6ZeL3pxtDJJ19LJ5qRb5YewtUdr6CTYRjLfZL") + new PublicKey("waTh9vNmRz8hHxQv1zW81ExKQ7fwaa1TR6tQT5fTkjg"), + new PublicKey("waTirsbdRyyxm8DFxH9Z9P5Pyrj74jJ7E74zqhmGX1k"), + new PublicKey("wAtMcoqwHb7obEL2USNXPqxg1CmJDSjjjsNcEm9bzCZ"), + new PublicKey("Wats1sM7W4UvxUuQPevGxexgcaFiaXMXxActKUmcvHx"), + new PublicKey("waTtgLt1WVRgoWYNZh97AYAUqoU4nN98jVThPdYQVwj"), + new PublicKey("WaTxa5w4TjYHRdHwG2zaN9ynA1c9z4J9TswsBCmzy4N"), ]; export const funders = [ @@ -15,10 +15,8 @@ export const funders = [ new PublicKey("wAfmKAiAgtfywPhfRdLQ7uMeS2RW3kN57kodaRabX6V"), new PublicKey("WAfN8P3NdYeTnnvFPKYUiveyqZSZtBboxUyrMSAzQc1"), new PublicKey("wafydZpGcTLk3FYEQ5Q4wwSKJXMy3EMTngxeVEXG45e"), - new PublicKey("WafzGQMEqwABhMdNHH86yDRqzZQvK5rELK5Nrto97Va") + new PublicKey("WafzGQMEqwABhMdNHH86yDRqzZQvK5rELK5Nrto97Va"), ]; - - - - +export const tokenDispenserProgramId = + "WapFw9mSyHh8trDDRy7AamUn1V7QiGaVvtouj5AucQA"; diff --git a/token-dispenser/ts/scripts/create-spl-token.ts b/token-dispenser/ts/scripts/create-spl-token.ts index 79b2fe08..28a131e4 100644 --- a/token-dispenser/ts/scripts/create-spl-token.ts +++ b/token-dispenser/ts/scripts/create-spl-token.ts @@ -17,7 +17,7 @@ console.log( (async () => { const tokenConfig = { - decimals: 9, + decimals: 6, name: "Test grant Token", symbol: "TEST_GRANT_TKN", uri: "https://thisisnot.arealurl/info.json", diff --git a/token-dispenser/ts/scripts/initialize.ts b/token-dispenser/ts/scripts/initialize.ts index e8b33da0..4ec8a5ff 100644 --- a/token-dispenser/ts/scripts/initialize.ts +++ b/token-dispenser/ts/scripts/initialize.ts @@ -8,7 +8,7 @@ import { import { TokenDispenserSdk } from "../sdk"; import { ledgerSignAndSend, ledgerSignAndSendV0 } from "./helpers"; import { connection, getSigner, getEnv } from "./env"; -import { funders, treasuries } from "./config"; +import { funders, tokenDispenserProgramId, treasuries } from "./config"; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -25,7 +25,7 @@ type InitConfig = { (async () => { const config: InitConfig = { - tokenDispenser: getEnv("PROGRAM_ID"), + tokenDispenser: tokenDispenserProgramId, mint: getEnv("MINT"), dispenserGuard: getEnv("DISPENSER_GUARD"), merkleRoot: Buffer.from(getEnv("MERKLE_ROOT"), "hex"), @@ -51,7 +51,6 @@ type InitConfig = { recentSlot: await connection.getSlot(), }); - console.log("Config PDA: ", configPda.toBase58()); const extendAddressLooupTableIx = AddressLookupTableProgram.extendLookupTable( @@ -66,7 +65,6 @@ type InitConfig = { SystemProgram.programId, SYSVAR_INSTRUCTIONS_PUBKEY, ASSOCIATED_TOKEN_PROGRAM_ID, - ], } ); @@ -82,17 +80,11 @@ type InitConfig = { payer: signerPk, authority: signerPk, lookupTable, - addresses: [ - ...funders, - ...treasuries, - ], + addresses: [...funders, ...treasuries], } ); - await ledgerSignAndSendV0( - [addFundersAndTreasuriesIx], - [] - ); + await ledgerSignAndSendV0([addFundersAndTreasuriesIx], []); console.log("Funders and Treasuries added to lookup table"); const initializeIx = await tokenDispenser.createInitializeInstruction({ diff --git a/token-dispenser/ts/scripts/print-config-pda.ts b/token-dispenser/ts/scripts/print-config-pda.ts index 40977927..916ec8f7 100644 --- a/token-dispenser/ts/scripts/print-config-pda.ts +++ b/token-dispenser/ts/scripts/print-config-pda.ts @@ -1,9 +1,8 @@ -import { - PublicKey, -} from "@solana/web3.js"; +import { PublicKey } from "@solana/web3.js"; import { TokenDispenserSdk } from "../sdk"; -import { connection, getSigner, getEnv } from "./env"; +import { connection, getSigner } from "./env"; +import { tokenDispenserProgramId } from "./config"; type InitConfig = { // Account Addresses (base58 encoded): @@ -12,7 +11,7 @@ type InitConfig = { (async () => { const config: InitConfig = { - tokenDispenser: getEnv("PROGRAM_ID"), + tokenDispenser: tokenDispenserProgramId, }; const signer = await getSigner(); @@ -24,6 +23,5 @@ type InitConfig = { }); const configPda = new PublicKey(tokenDispenser.configAccountAddress()); - console.log("Config PDA: ", configPda.toBase58()); })(); diff --git a/token-dispenser/ts/sdk/idl/token_dispenser.json b/token-dispenser/ts/sdk/idl/token_dispenser.json index 371a31d3..098c2327 100644 --- a/token-dispenser/ts/sdk/idl/token_dispenser.json +++ b/token-dispenser/ts/sdk/idl/token_dispenser.json @@ -38,10 +38,7 @@ { "name": "merkleRoot", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -141,10 +138,7 @@ { "name": "merkleRoot", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -334,10 +328,7 @@ "name": "proofOfInclusion", "type": { "vec": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } } } @@ -367,10 +358,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -381,10 +369,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } } ] @@ -395,10 +380,7 @@ { "name": "address", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -409,10 +391,7 @@ { "name": "address", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -441,10 +420,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } } ] @@ -476,10 +452,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -497,10 +470,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { @@ -515,10 +485,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { @@ -537,10 +504,7 @@ { "name": "signature", "type": { - "array": [ - "u8", - 64 - ] + "array": ["u8", 64] } }, { @@ -550,10 +514,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 65 - ] + "array": ["u8", 65] } }, { @@ -568,10 +529,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 20 - ] + "array": ["u8", 20] } }, { @@ -586,10 +544,7 @@ { "name": "pubkey", "type": { - "array": [ - "u8", - 32 - ] + "array": ["u8", 32] } }, { diff --git a/token-dispenser/ts/sdk/idl/token_dispenser.ts b/token-dispenser/ts/sdk/idl/token_dispenser.ts index da5ef49d..688f9694 100644 --- a/token-dispenser/ts/sdk/idl/token_dispenser.ts +++ b/token-dispenser/ts/sdk/idl/token_dispenser.ts @@ -1,1364 +1,1270 @@ -export type TokenDispenser = -{ - "version": "0.1.0", - "name": "token_dispenser", - "instructions": [ +export type TokenDispenser = { + version: "0.1.0"; + name: "token_dispenser"; + instructions: [ { - "name": "initialize", - "docs": [ + name: "initialize"; + docs: [ "This can only be called once and should be called right after the program is deployed." - ], - "accounts": [ + ]; + accounts: [ { - "name": "payer", - "isMut": true, - "isSigner": true + name: "payer"; + isMut: true; + isSigner: true; }, { - "name": "config", - "isMut": true, - "isSigner": false + name: "config"; + isMut: true; + isSigner: false; }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: "mint"; + isMut: false; + isSigner: false; }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: "systemProgram"; + isMut: false; + isSigner: false; }, { - "name": "addressLookupTable", - "isMut": false, - "isSigner": false + name: "addressLookupTable"; + isMut: false; + isSigner: false; } - ], - "args": [ + ]; + args: [ { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "merkleRoot"; + type: { + array: ["u8", 20]; + }; }, { - "name": "dispenserGuard", - "type": "publicKey" + name: "dispenserGuard"; + type: "publicKey"; }, { - "name": "maxTransfer", - "type": "u64" + name: "maxTransfer"; + type: "u64"; } - ] + ]; }, { - "name": "claim", - "docs": [ + name: "claim"; + docs: [ "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE" - ], - "accounts": [ + ]; + accounts: [ { - "name": "funder", - "isMut": true, - "isSigner": true + name: "funder"; + isMut: true; + isSigner: true; }, { - "name": "claimant", - "isMut": false, - "isSigner": true + name: "claimant"; + isMut: false; + isSigner: true; }, { - "name": "claimantFund", - "isMut": true, - "isSigner": false, - "docs": [ + name: "claimantFund"; + isMut: true; + isSigner: false; + docs: [ "Claimant's associated token account to receive the tokens", "Should be initialized outside of this program." - ] + ]; }, { - "name": "config", - "isMut": false, - "isSigner": false + name: "config"; + isMut: false; + isSigner: false; }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: "mint"; + isMut: false; + isSigner: false; }, { - "name": "treasury", - "isMut": true, - "isSigner": false + name: "treasury"; + isMut: true; + isSigner: false; }, { - "name": "tokenProgram", - "isMut": false, - "isSigner": false + name: "tokenProgram"; + isMut: false; + isSigner: false; }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: "systemProgram"; + isMut: false; + isSigner: false; }, { - "name": "sysvarInstruction", - "isMut": false, - "isSigner": false, - "docs": [ + name: "sysvarInstruction"; + isMut: false; + isSigner: false; + docs: [ "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked." - ] + ]; }, { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false + name: "associatedTokenProgram"; + isMut: false; + isSigner: false; } - ], - "args": [ + ]; + args: [ { - "name": "claimCertificate", - "type": { - "defined": "ClaimCertificate" - } + name: "claimCertificate"; + type: { + defined: "ClaimCertificate"; + }; } - ] + ]; } - ], - "accounts": [ + ]; + accounts: [ { - "name": "Config", - "type": { - "kind": "struct", - "fields": [ + name: "Config"; + type: { + kind: "struct"; + fields: [ { - "name": "bump", - "type": "u8" + name: "bump"; + type: "u8"; }, { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "merkleRoot"; + type: { + array: ["u8", 20]; + }; }, { - "name": "dispenserGuard", - "type": "publicKey" + name: "dispenserGuard"; + type: "publicKey"; }, { - "name": "mint", - "type": "publicKey" + name: "mint"; + type: "publicKey"; }, { - "name": "addressLookupTable", - "type": "publicKey" + name: "addressLookupTable"; + type: "publicKey"; }, { - "name": "maxTransfer", - "type": "u64" + name: "maxTransfer"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "Receipt", - "type": { - "kind": "struct", - "fields": [] - } + name: "Receipt"; + type: { + kind: "struct"; + fields: []; + }; } - ], - "types": [ + ]; + types: [ { - "name": "CosmosMessage", - "docs": [ + name: "CosmosMessage"; + docs: [ "* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key." - ], - "type": { - "kind": "struct", - "fields": [ + ]; + type: { + kind: "struct"; + fields: [ { - "name": "payload", - "type": "bytes" + name: "payload"; + type: "bytes"; }, { - "name": "signer", - "type": "string" + name: "signer"; + type: "string"; } - ] - } + ]; + }; }, { - "name": "DiscordMessage", - "docs": [ + name: "DiscordMessage"; + docs: [ "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else." - ], - "type": { - "kind": "struct", - "fields": [ + ]; + type: { + kind: "struct"; + fields: [ { - "name": "username", - "type": "string" + name: "username"; + type: "string"; }, { - "name": "claimant", - "type": "publicKey" + name: "claimant"; + type: "publicKey"; } - ] - } + ]; + }; }, { - "name": "Ed25519InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: "Ed25519InstructionHeader"; + type: { + kind: "struct"; + fields: [ { - "name": "numSignatures", - "type": "u8" + name: "numSignatures"; + type: "u8"; }, { - "name": "padding", - "type": "u8" + name: "padding"; + type: "u8"; }, { - "name": "signatureOffset", - "type": "u16" + name: "signatureOffset"; + type: "u16"; }, { - "name": "signatureInstructionIndex", - "type": "u16" + name: "signatureInstructionIndex"; + type: "u16"; }, { - "name": "publicKeyOffset", - "type": "u16" + name: "publicKeyOffset"; + type: "u16"; }, { - "name": "publicKeyInstructionIndex", - "type": "u16" + name: "publicKeyInstructionIndex"; + type: "u16"; }, { - "name": "messageDataOffset", - "type": "u16" + name: "messageDataOffset"; + type: "u16"; }, { - "name": "messageDataSize", - "type": "u16" + name: "messageDataSize"; + type: "u16"; }, { - "name": "messageInstructionIndex", - "type": "u16" + name: "messageInstructionIndex"; + type: "u16"; } - ] - } + ]; + }; }, { - "name": "Secp256k1InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: "Secp256k1InstructionHeader"; + type: { + kind: "struct"; + fields: [ { - "name": "numSignatures", - "type": "u8" + name: "numSignatures"; + type: "u8"; }, { - "name": "signatureOffset", - "type": "u16" + name: "signatureOffset"; + type: "u16"; }, { - "name": "signatureInstructionIndex", - "type": "u8" + name: "signatureInstructionIndex"; + type: "u8"; }, { - "name": "ethAddressOffset", - "type": "u16" + name: "ethAddressOffset"; + type: "u16"; }, { - "name": "ethAddressInstructionIndex", - "type": "u8" + name: "ethAddressInstructionIndex"; + type: "u8"; }, { - "name": "messageDataOffset", - "type": "u16" + name: "messageDataOffset"; + type: "u16"; }, { - "name": "messageDataSize", - "type": "u16" + name: "messageDataSize"; + type: "u16"; }, { - "name": "messageInstructionIndex", - "type": "u8" + name: "messageInstructionIndex"; + type: "u8"; } - ] - } + ]; + }; }, { - "name": "ClaimInfo", - "type": { - "kind": "struct", - "fields": [ + name: "ClaimInfo"; + type: { + kind: "struct"; + fields: [ { - "name": "identity", - "type": { - "defined": "Identity" - } + name: "identity"; + type: { + defined: "Identity"; + }; }, { - "name": "amount", - "type": "u64" + name: "amount"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "ClaimCertificate", - "type": { - "kind": "struct", - "fields": [ + name: "ClaimCertificate"; + type: { + kind: "struct"; + fields: [ { - "name": "amount", - "type": "u64" + name: "amount"; + type: "u64"; }, { - "name": "proofOfIdentity", - "type": { - "defined": "IdentityCertificate" - } + name: "proofOfIdentity"; + type: { + defined: "IdentityCertificate"; + }; }, { - "name": "proofOfInclusion", - "type": { - "vec": { - "array": [ - "u8", - 20 - ] - } - } + name: "proofOfInclusion"; + type: { + vec: { + array: ["u8", 20]; + }; + }; } - ] - } + ]; + }; }, { - "name": "Identity", - "docs": [ + name: "Identity"; + docs: [ "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space." - ], - "type": { - "kind": "enum", - "variants": [ + ]; + type: { + kind: "enum"; + variants: [ { - "name": "Discord", - "fields": [ + name: "Discord"; + fields: [ { - "name": "username", - "type": "string" + name: "username"; + type: "string"; } - ] + ]; }, { - "name": "Solana", - "fields": [ + name: "Solana"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey"; + type: { + array: ["u8", 32]; + }; } - ] + ]; }, { - "name": "Evm", - "fields": [ + name: "Evm"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "pubkey"; + type: { + array: ["u8", 20]; + }; } - ] + ]; }, { - "name": "Sui", - "fields": [ + name: "Sui"; + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "address"; + type: { + array: ["u8", 32]; + }; } - ] + ]; }, { - "name": "Aptos", - "fields": [ + name: "Aptos"; + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "address"; + type: { + array: ["u8", 32]; + }; } - ] + ]; }, { - "name": "Cosmwasm", - "fields": [ + name: "Cosmwasm"; + fields: [ { - "name": "address", - "type": "string" + name: "address"; + type: "string"; } - ] + ]; }, { - "name": "Injective", - "fields": [ + name: "Injective"; + fields: [ { - "name": "address", - "type": "string" + name: "address"; + type: "string"; } - ] + ]; }, { - "name": "Algorand", - "fields": [ + name: "Algorand"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey"; + type: { + array: ["u8", 32]; + }; } - ] + ]; } - ] - } + ]; + }; }, { - "name": "IdentityCertificate", - "type": { - "kind": "enum", - "variants": [ + name: "IdentityCertificate"; + type: { + kind: "enum"; + variants: [ { - "name": "Discord", - "fields": [ + name: "Discord"; + fields: [ { - "name": "username", - "type": "string" + name: "username"; + type: "string"; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; }, { - "name": "Evm", - "fields": [ + name: "Evm"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "pubkey"; + type: { + array: ["u8", 20]; + }; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; }, { - "name": "Solana" + name: "Solana"; }, { - "name": "Sui", - "fields": [ + name: "Sui"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey"; + type: { + array: ["u8", 32]; + }; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; }, { - "name": "Aptos", - "fields": [ + name: "Aptos"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey"; + type: { + array: ["u8", 32]; + }; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; }, { - "name": "Cosmwasm", - "fields": [ + name: "Cosmwasm"; + fields: [ { - "name": "chain_id", - "type": "string" + name: "chain_id"; + type: "string"; }, { - "name": "signature", - "type": { - "array": [ - "u8", - 64 - ] - } + name: "signature"; + type: { + array: ["u8", 64]; + }; }, { - "name": "recovery_id", - "type": "u8" + name: "recovery_id"; + type: "u8"; }, { - "name": "pubkey", - "type": { - "array": [ - "u8", - 65 - ] - } + name: "pubkey"; + type: { + array: ["u8", 65]; + }; }, { - "name": "message", - "type": "bytes" + name: "message"; + type: "bytes"; } - ] + ]; }, { - "name": "Injective", - "fields": [ + name: "Injective"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "pubkey"; + type: { + array: ["u8", 20]; + }; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; }, { - "name": "Algorand", - "fields": [ + name: "Algorand"; + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey"; + type: { + array: ["u8", 32]; + }; }, { - "name": "verification_instruction_index", - "type": "u8" + name: "verification_instruction_index"; + type: "u8"; } - ] + ]; } - ] - } + ]; + }; } - ], - "events": [ + ]; + events: [ { - "name": "ClaimEvent", - "fields": [ + name: "ClaimEvent"; + fields: [ { - "name": "treasury", - "type": "publicKey", - "index": false + name: "treasury"; + type: "publicKey"; + index: false; }, { - "name": "remainingBalance", - "type": "u64", - "index": false + name: "remainingBalance"; + type: "u64"; + index: false; }, { - "name": "claimant", - "type": "publicKey", - "index": false + name: "claimant"; + type: "publicKey"; + index: false; }, { - "name": "claimInfo", - "type": { - "defined": "ClaimInfo" - }, - "index": false + name: "claimInfo"; + type: { + defined: "ClaimInfo"; + }; + index: false; } - ] + ]; } - ], - "errors": [ + ]; + errors: [ { - "code": 6000, - "name": "AlreadyClaimed" + code: 6000; + name: "AlreadyClaimed"; }, { - "code": 6001, - "name": "InvalidInclusionProof" + code: 6001; + name: "InvalidInclusionProof"; }, { - "code": 6002, - "name": "WrongPda" + code: 6002; + name: "WrongPda"; }, { - "code": 6003, - "name": "SignatureVerificationWrongProgram" + code: 6003; + name: "SignatureVerificationWrongProgram"; }, { - "code": 6004, - "name": "SignatureVerificationWrongAccounts" + code: 6004; + name: "SignatureVerificationWrongAccounts"; }, { - "code": 6005, - "name": "SignatureVerificationWrongHeader" + code: 6005; + name: "SignatureVerificationWrongHeader"; }, { - "code": 6006, - "name": "SignatureVerificationWrongPayload" + code: 6006; + name: "SignatureVerificationWrongPayload"; }, { - "code": 6007, - "name": "SignatureVerificationWrongPayloadMetadata" + code: 6007; + name: "SignatureVerificationWrongPayloadMetadata"; }, { - "code": 6008, - "name": "SignatureVerificationWrongSigner" + code: 6008; + name: "SignatureVerificationWrongSigner"; }, { - "code": 6009, - "name": "UnauthorizedCosmosChainId" + code: 6009; + name: "UnauthorizedCosmosChainId"; }, { - "code": 6010, - "name": "TransferExceedsMax" + code: 6010; + name: "TransferExceedsMax"; } - ] -} -; -export const IDL: TokenDispenser = -{ - "version": "0.1.0", - "name": "token_dispenser", - "instructions": [ + ]; +}; +export const IDL: TokenDispenser = { + version: "0.1.0", + name: "token_dispenser", + instructions: [ { - "name": "initialize", - "docs": [ - "This can only be called once and should be called right after the program is deployed." + name: "initialize", + docs: [ + "This can only be called once and should be called right after the program is deployed.", ], - "accounts": [ + accounts: [ { - "name": "payer", - "isMut": true, - "isSigner": true + name: "payer", + isMut: true, + isSigner: true, }, { - "name": "config", - "isMut": true, - "isSigner": false + name: "config", + isMut: true, + isSigner: false, }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: "mint", + isMut: false, + isSigner: false, }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: "systemProgram", + isMut: false, + isSigner: false, }, { - "name": "addressLookupTable", - "isMut": false, - "isSigner": false - } + name: "addressLookupTable", + isMut: false, + isSigner: false, + }, ], - "args": [ + args: [ { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "merkleRoot", + type: { + array: ["u8", 20], + }, }, { - "name": "dispenserGuard", - "type": "publicKey" + name: "dispenserGuard", + type: "publicKey", }, { - "name": "maxTransfer", - "type": "u64" - } - ] + name: "maxTransfer", + type: "u64", + }, + ], }, { - "name": "claim", - "docs": [ - "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE" + name: "claim", + docs: [ + "* Claim a claimant's tokens. This instructions needs to enforce :\n * - The dispenser guard has signed the transaction - DONE\n * - The claimant is claiming no more than once per ecosystem - DONE\n * - The claimant has provided a valid proof of identity (is the owner of the wallet\n * entitled to the tokens)\n * - The claimant has provided a valid proof of inclusion (this confirm that the claimant --\n * DONE\n * - The claimant has not already claimed tokens -- DONE", ], - "accounts": [ + accounts: [ { - "name": "funder", - "isMut": true, - "isSigner": true + name: "funder", + isMut: true, + isSigner: true, }, { - "name": "claimant", - "isMut": false, - "isSigner": true + name: "claimant", + isMut: false, + isSigner: true, }, { - "name": "claimantFund", - "isMut": true, - "isSigner": false, - "docs": [ + name: "claimantFund", + isMut: true, + isSigner: false, + docs: [ "Claimant's associated token account to receive the tokens", - "Should be initialized outside of this program." - ] + "Should be initialized outside of this program.", + ], }, { - "name": "config", - "isMut": false, - "isSigner": false + name: "config", + isMut: false, + isSigner: false, }, { - "name": "mint", - "isMut": false, - "isSigner": false + name: "mint", + isMut: false, + isSigner: false, }, { - "name": "treasury", - "isMut": true, - "isSigner": false + name: "treasury", + isMut: true, + isSigner: false, }, { - "name": "tokenProgram", - "isMut": false, - "isSigner": false + name: "tokenProgram", + isMut: false, + isSigner: false, }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: "systemProgram", + isMut: false, + isSigner: false, }, { - "name": "sysvarInstruction", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked." - ] + name: "sysvarInstruction", + isMut: false, + isSigner: false, + docs: [ + "CHECK : Anchor wants me to write this comment because I'm using AccountInfo which doesn't check for ownership and doesn't deserialize the account automatically. But it's fine because I check the address and I load it using load_instruction_at_checked.", + ], }, { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false - } + name: "associatedTokenProgram", + isMut: false, + isSigner: false, + }, ], - "args": [ + args: [ { - "name": "claimCertificate", - "type": { - "defined": "ClaimCertificate" - } - } - ] - } + name: "claimCertificate", + type: { + defined: "ClaimCertificate", + }, + }, + ], + }, ], - "accounts": [ + accounts: [ { - "name": "Config", - "type": { - "kind": "struct", - "fields": [ + name: "Config", + type: { + kind: "struct", + fields: [ { - "name": "bump", - "type": "u8" + name: "bump", + type: "u8", }, { - "name": "merkleRoot", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "merkleRoot", + type: { + array: ["u8", 20], + }, }, { - "name": "dispenserGuard", - "type": "publicKey" + name: "dispenserGuard", + type: "publicKey", }, { - "name": "mint", - "type": "publicKey" + name: "mint", + type: "publicKey", }, { - "name": "addressLookupTable", - "type": "publicKey" + name: "addressLookupTable", + type: "publicKey", }, { - "name": "maxTransfer", - "type": "u64" - } - ] - } + name: "maxTransfer", + type: "u64", + }, + ], + }, }, { - "name": "Receipt", - "type": { - "kind": "struct", - "fields": [] - } - } + name: "Receipt", + type: { + kind: "struct", + fields: [], + }, + }, ], - "types": [ + types: [ { - "name": "CosmosMessage", - "docs": [ - "* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key." + name: "CosmosMessage", + docs: [ + "* An ADR036 message used in Cosmos. ADR036 is a standard for signing arbitrary data.\n* Only the message payload is stored in this struct.\n* The message signed for Cosmos is a JSON serialized CosmosStdSignDoc containing the payload and ADR036 compliant parameters.\n* The message also contains the bech32 address of the signer. We check that the signer corresponds to the public key.", ], - "type": { - "kind": "struct", - "fields": [ + type: { + kind: "struct", + fields: [ { - "name": "payload", - "type": "bytes" + name: "payload", + type: "bytes", }, { - "name": "signer", - "type": "string" - } - ] - } + name: "signer", + type: "string", + }, + ], + }, }, { - "name": "DiscordMessage", - "docs": [ - "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else." + name: "DiscordMessage", + docs: [ + "* This message (borsh-serialized) needs to be signed by the dispenser guard after\n * verifying the claimant's pubkey controls the discord account.\n * The dispenser guard key should not be used for anything else.", ], - "type": { - "kind": "struct", - "fields": [ + type: { + kind: "struct", + fields: [ { - "name": "username", - "type": "string" + name: "username", + type: "string", }, { - "name": "claimant", - "type": "publicKey" - } - ] - } + name: "claimant", + type: "publicKey", + }, + ], + }, }, { - "name": "Ed25519InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: "Ed25519InstructionHeader", + type: { + kind: "struct", + fields: [ { - "name": "numSignatures", - "type": "u8" + name: "numSignatures", + type: "u8", }, { - "name": "padding", - "type": "u8" + name: "padding", + type: "u8", }, { - "name": "signatureOffset", - "type": "u16" + name: "signatureOffset", + type: "u16", }, { - "name": "signatureInstructionIndex", - "type": "u16" + name: "signatureInstructionIndex", + type: "u16", }, { - "name": "publicKeyOffset", - "type": "u16" + name: "publicKeyOffset", + type: "u16", }, { - "name": "publicKeyInstructionIndex", - "type": "u16" + name: "publicKeyInstructionIndex", + type: "u16", }, { - "name": "messageDataOffset", - "type": "u16" + name: "messageDataOffset", + type: "u16", }, { - "name": "messageDataSize", - "type": "u16" + name: "messageDataSize", + type: "u16", }, { - "name": "messageInstructionIndex", - "type": "u16" - } - ] - } + name: "messageInstructionIndex", + type: "u16", + }, + ], + }, }, { - "name": "Secp256k1InstructionHeader", - "type": { - "kind": "struct", - "fields": [ + name: "Secp256k1InstructionHeader", + type: { + kind: "struct", + fields: [ { - "name": "numSignatures", - "type": "u8" + name: "numSignatures", + type: "u8", }, { - "name": "signatureOffset", - "type": "u16" + name: "signatureOffset", + type: "u16", }, { - "name": "signatureInstructionIndex", - "type": "u8" + name: "signatureInstructionIndex", + type: "u8", }, { - "name": "ethAddressOffset", - "type": "u16" + name: "ethAddressOffset", + type: "u16", }, { - "name": "ethAddressInstructionIndex", - "type": "u8" + name: "ethAddressInstructionIndex", + type: "u8", }, { - "name": "messageDataOffset", - "type": "u16" + name: "messageDataOffset", + type: "u16", }, { - "name": "messageDataSize", - "type": "u16" + name: "messageDataSize", + type: "u16", }, { - "name": "messageInstructionIndex", - "type": "u8" - } - ] - } + name: "messageInstructionIndex", + type: "u8", + }, + ], + }, }, { - "name": "ClaimInfo", - "type": { - "kind": "struct", - "fields": [ + name: "ClaimInfo", + type: { + kind: "struct", + fields: [ { - "name": "identity", - "type": { - "defined": "Identity" - } + name: "identity", + type: { + defined: "Identity", + }, }, { - "name": "amount", - "type": "u64" - } - ] - } + name: "amount", + type: "u64", + }, + ], + }, }, { - "name": "ClaimCertificate", - "type": { - "kind": "struct", - "fields": [ + name: "ClaimCertificate", + type: { + kind: "struct", + fields: [ { - "name": "amount", - "type": "u64" + name: "amount", + type: "u64", }, { - "name": "proofOfIdentity", - "type": { - "defined": "IdentityCertificate" - } + name: "proofOfIdentity", + type: { + defined: "IdentityCertificate", + }, }, { - "name": "proofOfInclusion", - "type": { - "vec": { - "array": [ - "u8", - 20 - ] - } - } - } - ] - } + name: "proofOfInclusion", + type: { + vec: { + array: ["u8", 20], + }, + }, + }, + ], + }, }, { - "name": "Identity", - "docs": [ - "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space." + name: "Identity", + docs: [ + "* This is the identity that the claimant will use to claim tokens.\n * A claimant can claim tokens for 1 identity on each ecosystem.\n * Typically for a blockchain it is a public key in the blockchain's address space.", ], - "type": { - "kind": "enum", - "variants": [ + type: { + kind: "enum", + variants: [ { - "name": "Discord", - "fields": [ + name: "Discord", + fields: [ { - "name": "username", - "type": "string" - } - ] + name: "username", + type: "string", + }, + ], }, { - "name": "Solana", - "fields": [ + name: "Solana", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: "pubkey", + type: { + array: ["u8", 32], + }, + }, + ], }, { - "name": "Evm", - "fields": [ + name: "Evm", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } - } - ] + name: "pubkey", + type: { + array: ["u8", 20], + }, + }, + ], }, { - "name": "Sui", - "fields": [ + name: "Sui", + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: "address", + type: { + array: ["u8", 32], + }, + }, + ], }, { - "name": "Aptos", - "fields": [ + name: "Aptos", + fields: [ { - "name": "address", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] + name: "address", + type: { + array: ["u8", 32], + }, + }, + ], }, { - "name": "Cosmwasm", - "fields": [ + name: "Cosmwasm", + fields: [ { - "name": "address", - "type": "string" - } - ] + name: "address", + type: "string", + }, + ], }, { - "name": "Injective", - "fields": [ + name: "Injective", + fields: [ { - "name": "address", - "type": "string" - } - ] + name: "address", + type: "string", + }, + ], }, { - "name": "Algorand", - "fields": [ + name: "Algorand", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } - } - ] - } - ] - } + name: "pubkey", + type: { + array: ["u8", 32], + }, + }, + ], + }, + ], + }, }, { - "name": "IdentityCertificate", - "type": { - "kind": "enum", - "variants": [ + name: "IdentityCertificate", + type: { + kind: "enum", + variants: [ { - "name": "Discord", - "fields": [ + name: "Discord", + fields: [ { - "name": "username", - "type": "string" + name: "username", + type: "string", }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: "verification_instruction_index", + type: "u8", + }, + ], }, { - "name": "Evm", - "fields": [ + name: "Evm", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "pubkey", + type: { + array: ["u8", 20], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: "verification_instruction_index", + type: "u8", + }, + ], }, { - "name": "Solana" + name: "Solana", }, { - "name": "Sui", - "fields": [ + name: "Sui", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey", + type: { + array: ["u8", 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: "verification_instruction_index", + type: "u8", + }, + ], }, { - "name": "Aptos", - "fields": [ + name: "Aptos", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey", + type: { + array: ["u8", 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: "verification_instruction_index", + type: "u8", + }, + ], }, { - "name": "Cosmwasm", - "fields": [ + name: "Cosmwasm", + fields: [ { - "name": "chain_id", - "type": "string" + name: "chain_id", + type: "string", }, { - "name": "signature", - "type": { - "array": [ - "u8", - 64 - ] - } + name: "signature", + type: { + array: ["u8", 64], + }, }, { - "name": "recovery_id", - "type": "u8" + name: "recovery_id", + type: "u8", }, { - "name": "pubkey", - "type": { - "array": [ - "u8", - 65 - ] - } + name: "pubkey", + type: { + array: ["u8", 65], + }, }, { - "name": "message", - "type": "bytes" - } - ] + name: "message", + type: "bytes", + }, + ], }, { - "name": "Injective", - "fields": [ + name: "Injective", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 20 - ] - } + name: "pubkey", + type: { + array: ["u8", 20], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] + name: "verification_instruction_index", + type: "u8", + }, + ], }, { - "name": "Algorand", - "fields": [ + name: "Algorand", + fields: [ { - "name": "pubkey", - "type": { - "array": [ - "u8", - 32 - ] - } + name: "pubkey", + type: { + array: ["u8", 32], + }, }, { - "name": "verification_instruction_index", - "type": "u8" - } - ] - } - ] - } - } + name: "verification_instruction_index", + type: "u8", + }, + ], + }, + ], + }, + }, ], - "events": [ + events: [ { - "name": "ClaimEvent", - "fields": [ + name: "ClaimEvent", + fields: [ { - "name": "treasury", - "type": "publicKey", - "index": false + name: "treasury", + type: "publicKey", + index: false, }, { - "name": "remainingBalance", - "type": "u64", - "index": false + name: "remainingBalance", + type: "u64", + index: false, }, { - "name": "claimant", - "type": "publicKey", - "index": false + name: "claimant", + type: "publicKey", + index: false, }, { - "name": "claimInfo", - "type": { - "defined": "ClaimInfo" + name: "claimInfo", + type: { + defined: "ClaimInfo", }, - "index": false - } - ] - } + index: false, + }, + ], + }, ], - "errors": [ + errors: [ { - "code": 6000, - "name": "AlreadyClaimed" + code: 6000, + name: "AlreadyClaimed", }, { - "code": 6001, - "name": "InvalidInclusionProof" + code: 6001, + name: "InvalidInclusionProof", }, { - "code": 6002, - "name": "WrongPda" + code: 6002, + name: "WrongPda", }, { - "code": 6003, - "name": "SignatureVerificationWrongProgram" + code: 6003, + name: "SignatureVerificationWrongProgram", }, { - "code": 6004, - "name": "SignatureVerificationWrongAccounts" + code: 6004, + name: "SignatureVerificationWrongAccounts", }, { - "code": 6005, - "name": "SignatureVerificationWrongHeader" + code: 6005, + name: "SignatureVerificationWrongHeader", }, { - "code": 6006, - "name": "SignatureVerificationWrongPayload" + code: 6006, + name: "SignatureVerificationWrongPayload", }, { - "code": 6007, - "name": "SignatureVerificationWrongPayloadMetadata" + code: 6007, + name: "SignatureVerificationWrongPayloadMetadata", }, { - "code": 6008, - "name": "SignatureVerificationWrongSigner" + code: 6008, + name: "SignatureVerificationWrongSigner", }, { - "code": 6009, - "name": "UnauthorizedCosmosChainId" + code: 6009, + name: "UnauthorizedCosmosChainId", }, { - "code": 6010, - "name": "TransferExceedsMax" - } - ] -} -; + code: 6010, + name: "TransferExceedsMax", + }, + ], +}; diff --git a/token-dispenser/ts/sdk/token-dispenser.ts b/token-dispenser/ts/sdk/token-dispenser.ts index 73f572dd..d43ae437 100644 --- a/token-dispenser/ts/sdk/token-dispenser.ts +++ b/token-dispenser/ts/sdk/token-dispenser.ts @@ -1,7 +1,7 @@ import { Program, BN, Address, web3, AnchorProvider } from "@coral-xyz/anchor"; import { TokenDispenser } from "./idl/token_dispenser"; import IDL from "./idl/token_dispenser.json"; -import { PublicKey, Connection } from '@solana/web3.js'; +import { PublicKey, Connection } from "@solana/web3.js"; import { derivePda } from "./utils"; @@ -15,19 +15,19 @@ export class TokenDispenserSdk { connection: Connection, args: { programId: string; - payer: PublicKey, + payer: PublicKey; } ) { const wallet = { publicKey: args.payer, - signTransaction: async function () : Promise { + signTransaction: async function (): Promise { throw new Error("ilegal call"); }, signAllTransactions: async function (...args: any[]): Promise { throw new Error("ilegal call"); }, // payer: args.payer, - } + }; const provider = new AnchorProvider( connection, wallet, @@ -35,7 +35,11 @@ export class TokenDispenserSdk { ); // this.program = new Program(IDL as any, new PublicKey(args.programId), provider); - this.program = new Program(IDL as any, new PublicKey(args.programId), provider); + this.program = new Program( + IDL as any, + new PublicKey(args.programId), + provider + ); } // Acounts: @@ -56,7 +60,7 @@ export class TokenDispenserSdk { .initialize( Array.from(args.merkleRoot), args.dispenserGuard, - new BN(args.maxTransfer.toString()), + new BN(args.maxTransfer.toString()) ) .accounts({ config: new PublicKey(this.configAccountAddress()), diff --git a/token-dispenser/yarn.lock b/token-dispenser/yarn.lock index 2859b4f0..34f9d04a 100644 --- a/token-dispenser/yarn.lock +++ b/token-dispenser/yarn.lock @@ -11,6 +11,8 @@ "@coral-xyz/anchor@0.29.0": version "0.29.0" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.29.0.tgz#bd0be95bedfb30a381c3e676e5926124c310ff12" + integrity sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA== dependencies: "@coral-xyz/borsh" "^0.29.0" "@noble/hashes" "^1.3.1" @@ -84,7 +86,7 @@ node-hid "^2.1.2" usb "2.9.0" -"@ledgerhq/hw-transport@^6.30.4", "@ledgerhq/hw-transport@6.30.4": +"@ledgerhq/hw-transport@6.30.4", "@ledgerhq/hw-transport@^6.30.4": version "6.30.4" resolved "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.30.4.tgz" integrity sha512-VBcVd7UG8HDrjWMoZI5rqBDz+PBxLHTIPZOGY/fdMoEUwaBbss0Z3MxuJanMyerlfaLqnBSVuL0blz7rOyagkw== @@ -116,7 +118,7 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3": version "1.4.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== @@ -208,7 +210,7 @@ dependencies: buffer "^6.0.3" -"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.87.6", "@solana/web3.js@^1.90.0": +"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.91.1": version "1.91.2" resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.2.tgz" integrity sha512-WXPl5VXtfNKWM2RkGj7mvX6dKcZURDKe1lWBFAt/RqDBI9Rjr9hr7Y+U+yz2+TyViMmoinfJVlkS4gk2FPDG/g== @@ -301,6 +303,14 @@ "@ledgerhq/hw-transport" "6.30.4" "@ledgerhq/hw-transport-node-hid" "6.28.4" +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + agentkeepalive@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" @@ -454,6 +464,14 @@ buffer-layout@^1.2.0, buffer-layout@^1.2.2: resolved "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz" integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== +buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -462,14 +480,6 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3, buffer@~6.0.3, buffer@6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.7" resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz" @@ -612,16 +622,16 @@ detect-libc@^2.0.0: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== -diff@^3.1.0: - version "3.5.0" - resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - diff@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^3.1.0: + version "3.5.0" + resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" @@ -689,11 +699,6 @@ fast-stable-stringify@^1.0.0: resolved "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== -fastestsmallesttextencoderdecoder@^1.0.22: - version "1.0.22" - resolved "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz" - integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" @@ -729,6 +734,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" @@ -798,7 +808,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@^2.0.4, inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -865,13 +875,13 @@ jayson@^4.1.0: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" "@types/ws" "^7.4.4" + JSONStream "^1.3.5" commander "^2.20.3" delay "^5.0.0" es6-promisify "^5.0.0" eyes "^0.1.8" isomorphic-ws "^4.0.1" json-stringify-safe "^5.0.1" - JSONStream "^1.3.5" uuid "^8.3.2" ws "^7.4.5" @@ -899,14 +909,6 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" @@ -958,13 +960,6 @@ mimic-response@^3.1.0: resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - minimatch@4.2.1: version "4.2.1" resolved "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz" @@ -972,6 +967,13 @@ minimatch@4.2.1: dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -989,7 +991,7 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -"mocha@^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X", mocha@^9.0.3: +mocha@^9.0.3: version "9.2.2" resolved "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz" integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== @@ -1019,7 +1021,7 @@ mkdirp@^0.5.1: yargs-parser "20.2.4" yargs-unparser "2.0.0" -ms@^2.0.0, ms@2.1.2: +ms@2.1.2, ms@^2.0.0: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -1284,13 +1286,6 @@ source-map@^0.6.0: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -1300,6 +1295,13 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -1312,16 +1314,16 @@ strip-bom@^3.0.0: resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - strip-json-comments@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + superstruct@^0.14.2: version "0.14.2" resolved "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz" @@ -1332,13 +1334,6 @@ superstruct@^0.15.4: resolved "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz" integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - supports-color@8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" @@ -1346,6 +1341,13 @@ supports-color@8.1.1: dependencies: has-flag "^4.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" @@ -1458,7 +1460,7 @@ usb@2.9.0: node-addon-api "^6.0.0" node-gyp-build "^4.5.0" -utf-8-validate@^5.0.2, utf-8-validate@>=5.0.2: +utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz" integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== @@ -1514,7 +1516,7 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@*, ws@^7.4.5: +ws@^7.4.5: version "7.5.9" resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== @@ -1534,7 +1536,7 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^20.2.2, yargs-parser@20.2.4: +yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==