Skip to content

Commit fe68bb6

Browse files
authored
add pinocchio transfer-tokens example (#596)
* add pinocchio transfer-tokens example Add a Pinocchio implementation of tokens/transfer-tokens covering the core SPL flow: create a mint, mint tokens to an associated token account, and transfer tokens between wallets. The program dispatches three instructions by a leading discriminator byte (0=CreateToken, 1=MintTokens, 2=TransferTokens) using pinocchio-token, pinocchio-associated-token-account, and pinocchio-system. A bankrun test exercises the full create -> mint 150 -> transfer 50 flow and asserts the resulting 100/50 balances. * fix pre-existing repo-wide biome errors The whole-repo `biome check ./` (TypeScript CI job) was failing on three files unrelated to this PR, which kept the check red: - basics/cross-program-invocation/pinocchio/tests/test.ts: remove an unused discriminator constant and replace non-null assertions with explicit null guards - tokens/token-swap/anchor/tests/deposit-liquidity.ts: apply the biome formatter - tokens/token-2022/.../app/public/{next,vercel}.svg: add a <title> element (noSvgWithoutTitle) --------- Co-authored-by: MarkFeder <5670736+MarkFeder@users.noreply.github.com>
1 parent 203b246 commit fe68bb6

19 files changed

Lines changed: 1867 additions & 52 deletions

File tree

Cargo.lock

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ members = [
5151
"basics/transfer-sol/asm",
5252

5353
# tokens
54+
"tokens/transfer-tokens/pinocchio/program",
5455
"tokens/token-2022/mint-close-authority/native/program",
5556
"tokens/token-2022/non-transferable/native/program",
5657
"tokens/token-2022/default-account-state/native/program",
@@ -87,6 +88,8 @@ pinocchio = { version = "0.10.2", features = ["cpi"] }
8788
pinocchio-log = "0.5.1"
8889
pinocchio-system = "0.5.0"
8990
pinocchio-pubkey = "0.3.0"
91+
pinocchio-token = "0.5.0"
92+
pinocchio-associated-token-account = "0.3.0"
9093

9194
# testing
9295
litesvm = "0.11.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Create an NFT collection, mint NFTs, and verify NFTs as part of a collection usi
149149

150150
[Transfer tokens between accounts](./tokens/transfer-tokens/README.md)
151151

152-
[anchor](./tokens/transfer-tokens/anchor) [native](./tokens/transfer-tokens/native)
152+
[anchor](./tokens/transfer-tokens/anchor) [pinocchio](./tokens/transfer-tokens/pinocchio) [native](./tokens/transfer-tokens/native)
153153

154154
### Allowing users to swap digital assets - Escrow
155155

basics/cross-program-invocation/pinocchio/tests/test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ describe("Pinocchio: CPI", async () => {
1717
const client = context.banksClient;
1818
const payer = context.payer;
1919

20-
// Lever instruction discriminators
20+
// Lever instruction discriminator
2121
const IX_INITIALIZE = 0;
22-
const IX_SWITCH_POWER = 1;
2322

2423
const powerAccount = Keypair.generate();
2524

@@ -43,8 +42,8 @@ describe("Pinocchio: CPI", async () => {
4342
await sendTx(ix, [powerAccount]);
4443

4544
const acct = await client.getAccount(powerAccount.publicKey);
46-
assert.isNotNull(acct);
47-
assert.deepEqual(Buffer.from(acct!.data), Buffer.from([0])); // is_on = false
45+
if (acct === null) throw new Error("power account not found");
46+
assert.deepEqual(Buffer.from(acct.data), Buffer.from([0])); // is_on = false
4847
});
4948

5049
it("Pull the lever!", async () => {
@@ -60,7 +59,8 @@ describe("Pinocchio: CPI", async () => {
6059
await sendTx(ix, []);
6160

6261
const acct = await client.getAccount(powerAccount.publicKey);
63-
assert.deepEqual(Buffer.from(acct!.data), Buffer.from([1])); // is_on = true
62+
if (acct === null) throw new Error("power account not found");
63+
assert.deepEqual(Buffer.from(acct.data), Buffer.from([1])); // is_on = true
6464
});
6565

6666
it("Pull it again!", async () => {
@@ -76,7 +76,8 @@ describe("Pinocchio: CPI", async () => {
7676
await sendTx(ix, []);
7777

7878
const acct = await client.getAccount(powerAccount.publicKey);
79-
assert.deepEqual(Buffer.from(acct!.data), Buffer.from([0])); // is_on = false (flipped back)
79+
if (acct === null) throw new Error("power account not found");
80+
assert.deepEqual(Buffer.from(acct.data), Buffer.from([0])); // is_on = false (flipped back)
8081
});
8182

8283
it("Lever rejects switch_power directly with no name", async () => {
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading

tokens/token-swap/anchor/tests/deposit-liquidity.ts

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe("Deposit liquidity", () => {
7272
expect(depositTokenAccountB.value.amount).to.equal(values.defaultSupply.sub(values.depositAmountA).toString());
7373
});
7474

75-
it('Deposit with existing liquidity (same ratio)', async () => {
75+
it("Deposit with existing liquidity (same ratio)", async () => {
7676
// 1. Initial Deposit
7777
await program.methods
7878
.depositLiquidity(values.depositAmountA, values.depositAmountA)
@@ -116,7 +116,7 @@ describe("Deposit liquidity", () => {
116116
expect(poolAccountA.value.amount).to.equal(values.depositAmountA.add(secondDepositAmount).toString());
117117
});
118118

119-
it('Deposit with different ratio', async () => {
119+
it("Deposit with different ratio", async () => {
120120
// 1. Initial Deposit with 1:5 ratio
121121
// Pool A: 1,000,000
122122
// Pool B: 5,000,000
@@ -141,44 +141,44 @@ describe("Deposit liquidity", () => {
141141
.signers([values.admin])
142142
.rpc({ skipPreflight: true });
143143

144-
// 2. Second Deposit with mismatched input
145-
// Input A: 500,000
146-
// Input B: 500,000
147-
// Logic:
148-
// - 500k A requires 2.5M B. (User only provided 500k B).
149-
// - 500k B requires 100k A. (User provided 500k A).
150-
// Result: Deposit 100k A and 500k B.
151-
const secondDepositA = new anchor.BN(500000);
152-
const secondDepositBInput = new anchor.BN(500000);
153-
154-
await program.methods
155-
.depositLiquidity(secondDepositA, secondDepositBInput)
156-
.accounts({
157-
pool: values.poolKey,
158-
poolAuthority: values.poolAuthority,
159-
depositor: values.admin.publicKey,
160-
mintLiquidity: values.mintLiquidity,
161-
mintA: values.mintAKeypair.publicKey,
162-
mintB: values.mintBKeypair.publicKey,
163-
poolAccountA: values.poolAccountA,
164-
poolAccountB: values.poolAccountB,
165-
depositorAccountLiquidity: values.liquidityAccount,
166-
depositorAccountA: values.holderAccountA,
167-
depositorAccountB: values.holderAccountB,
168-
})
169-
.signers([values.admin])
170-
.rpc({ skipPreflight: true });
171-
172-
// 3. Verify Balances
173-
const poolAccountA = await connection.getTokenAccountBalance(values.poolAccountA);
174-
const poolAccountB = await connection.getTokenAccountBalance(values.poolAccountB);
175-
176-
// Total A: 1,000,000 + 100,000 = 1,100,000
177-
// We expect 100,000 A to be deposited.
178-
const expectedAdditionalA = secondDepositBInput.mul(initialAmountA).div(initialAmountB); // 500k * (1M/5M) = 100k
179-
expect(poolAccountA.value.amount).to.equal(initialAmountA.add(expectedAdditionalA).toString());
180-
181-
// Total B: 5,000,000 + 500,000 = 5,500,000
182-
expect(poolAccountB.value.amount).to.equal(initialAmountB.add(secondDepositBInput).toString());
183-
});
184-
});
144+
// 2. Second Deposit with mismatched input
145+
// Input A: 500,000
146+
// Input B: 500,000
147+
// Logic:
148+
// - 500k A requires 2.5M B. (User only provided 500k B).
149+
// - 500k B requires 100k A. (User provided 500k A).
150+
// Result: Deposit 100k A and 500k B.
151+
const secondDepositA = new anchor.BN(500000);
152+
const secondDepositBInput = new anchor.BN(500000);
153+
154+
await program.methods
155+
.depositLiquidity(secondDepositA, secondDepositBInput)
156+
.accounts({
157+
pool: values.poolKey,
158+
poolAuthority: values.poolAuthority,
159+
depositor: values.admin.publicKey,
160+
mintLiquidity: values.mintLiquidity,
161+
mintA: values.mintAKeypair.publicKey,
162+
mintB: values.mintBKeypair.publicKey,
163+
poolAccountA: values.poolAccountA,
164+
poolAccountB: values.poolAccountB,
165+
depositorAccountLiquidity: values.liquidityAccount,
166+
depositorAccountA: values.holderAccountA,
167+
depositorAccountB: values.holderAccountB,
168+
})
169+
.signers([values.admin])
170+
.rpc({ skipPreflight: true });
171+
172+
// 3. Verify Balances
173+
const poolAccountA = await connection.getTokenAccountBalance(values.poolAccountA);
174+
const poolAccountB = await connection.getTokenAccountBalance(values.poolAccountB);
175+
176+
// Total A: 1,000,000 + 100,000 = 1,100,000
177+
// We expect 100,000 A to be deposited.
178+
const expectedAdditionalA = secondDepositBInput.mul(initialAmountA).div(initialAmountB); // 500k * (1M/5M) = 100k
179+
expect(poolAccountA.value.amount).to.equal(initialAmountA.add(expectedAdditionalA).toString());
180+
181+
// Total B: 5,000,000 + 500,000 = 5,500,000
182+
expect(poolAccountB.value.amount).to.equal(initialAmountB.add(secondDepositBInput).toString());
183+
});
184+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
# This script is for quick building & deploying of the program.
4+
# It also serves as a reference for the commands used for building & deploying Solana programs.
5+
# Run this bad boy with "bash cicd.sh" or "./cicd.sh"
6+
7+
cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so
8+
solana program deploy ./program/target/so/program.so
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"type": "module",
3+
"scripts": {
4+
"test": "pnpm ts-mocha -p ./tsconfig.json -t 1000000 ./tests/test.ts",
5+
"build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test",
6+
"build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so",
7+
"deploy": "solana program deploy ./program/target/so/program.so"
8+
},
9+
"dependencies": {
10+
"@solana/web3.js": "^1.98.4",
11+
"borsh": "^2.0.0"
12+
},
13+
"devDependencies": {
14+
"@types/bn.js": "^5.1.0",
15+
"@types/chai": "^4.3.1",
16+
"@types/mocha": "^9.1.1",
17+
"chai": "^4.3.4",
18+
"mocha": "^9.0.3",
19+
"solana-bankrun": "^0.3.0",
20+
"ts-mocha": "^10.0.0",
21+
"typescript": "^4.3.5"
22+
}
23+
}

0 commit comments

Comments
 (0)